浏览代码

Improves file saving / page saving and loading

main
Steve Ruiz 4 年前
父节点
当前提交
4ce2b8cc6b
共有 68 个文件被更改,包括 1113 次插入944 次删除
  1. 1
    1
      components/canvas/bounds/handles.tsx
  2. 2
    2
      components/canvas/selected.tsx
  3. 1
    2
      components/canvas/shape.tsx
  4. 106
    76
      components/context-menu.tsx
  5. 0
    1
      components/style-panel/color-content.tsx
  6. 2
    5
      components/style-panel/dash-picker.tsx
  7. 2
    4
      components/style-panel/is-filled-picker.tsx
  8. 2
    5
      components/style-panel/size-picker.tsx
  9. 10
    1
      hooks/useBoundsEvents.ts
  10. 2
    4
      hooks/useCamera.ts
  11. 11
    1
      hooks/useCanvasEvents.ts
  12. 1
    1
      hooks/useZoomEvents.ts
  13. 2
    2
      lib/code/circle.ts
  14. 6
    6
      lib/code/control.ts
  15. 2
    2
      lib/code/dot.ts
  16. 2
    2
      lib/code/ellipse.ts
  17. 1
    1
      lib/code/index.ts
  18. 2
    2
      lib/code/line.ts
  19. 2
    2
      lib/code/polyline.ts
  20. 2
    2
      lib/code/ray.ts
  21. 2
    2
      lib/code/rectangle.ts
  22. 3
    3
      lib/shape-utils/arrow.tsx
  23. 3
    3
      lib/shape-utils/circle.tsx
  24. 3
    3
      lib/shape-utils/dot.tsx
  25. 3
    3
      lib/shape-utils/draw.tsx
  26. 3
    3
      lib/shape-utils/ellipse.tsx
  27. 3
    3
      lib/shape-utils/group.tsx
  28. 3
    3
      lib/shape-utils/index.tsx
  29. 3
    3
      lib/shape-utils/line.tsx
  30. 3
    3
      lib/shape-utils/polyline.tsx
  31. 3
    3
      lib/shape-utils/ray.tsx
  32. 3
    3
      lib/shape-utils/rectangle.tsx
  33. 3
    3
      lib/shape-utils/text.tsx
  34. 8
    8
      package.json
  35. 7
    7
      state/commands/change-page.ts
  36. 6
    4
      state/commands/create-page.ts
  37. 1
    1
      state/commands/delete-page.ts
  38. 3
    3
      state/commands/duplicate.ts
  39. 1
    1
      state/commands/group.ts
  40. 1
    1
      state/commands/handle.ts
  41. 1
    1
      state/commands/move-to-page.ts
  42. 1
    1
      state/commands/nudge.ts
  43. 1
    1
      state/commands/rotate-ccw.ts
  44. 1
    1
      state/commands/translate.ts
  45. 1
    1
      state/commands/ungroup.ts
  46. 21
    7
      state/hacks.ts
  47. 8
    4
      state/history.ts
  48. 1
    1
      state/inputs.tsx
  49. 1
    1
      state/sessions/arrow-session.ts
  50. 1
    1
      state/sessions/brush-session.ts
  51. 1
    1
      state/sessions/direction-session.ts
  52. 1
    1
      state/sessions/draw-session.ts
  53. 1
    1
      state/sessions/edit-session.ts
  54. 1
    1
      state/sessions/handle-session.ts
  55. 1
    1
      state/sessions/rotate-session.ts
  56. 4
    2
      state/sessions/transform-session.ts
  57. 3
    4
      state/sessions/transform-single-session.ts
  58. 4
    4
      state/sessions/translate-session.ts
  59. 32
    19
      state/state.ts
  60. 197
    109
      state/storage.ts
  61. 1
    0
      todo.md
  62. 1
    2
      types.ts
  63. 2
    2
      utils/hitTests.ts
  64. 1
    1
      utils/intersections.ts
  65. 1
    1
      utils/svg.ts
  66. 7
    11
      utils/utils.ts
  67. 440
    436
      utils/vec.ts
  68. 154
    144
      yarn.lock

+ 1
- 1
components/canvas/bounds/handles.tsx 查看文件

@@ -4,7 +4,7 @@ import { useRef } from 'react'
4 4
 import { useSelector } from 'state'
5 5
 import styled from 'styles'
6 6
 import { deepCompareArrays, getPage } from 'utils/utils'
7
-import * as vec from 'utils/vec'
7
+import vec from 'utils/vec'
8 8
 
9 9
 export default function Handles() {
10 10
   const selectedIds = useSelector(

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

@@ -2,7 +2,6 @@ import styled from 'styles'
2 2
 import { useSelector } from 'state'
3 3
 import {
4 4
   deepCompareArrays,
5
-  getBoundsCenter,
6 5
   getPage,
7 6
   getSelectedIds,
8 7
   setToArray,
@@ -11,7 +10,7 @@ import { getShapeUtils } from 'lib/shape-utils'
11 10
 import useShapeEvents from 'hooks/useShapeEvents'
12 11
 import { memo, useRef } from 'react'
13 12
 import { ShapeType } from 'types'
14
-import * as vec from 'utils/vec'
13
+import vec from 'utils/vec'
15 14
 
16 15
 export default function Selected() {
17 16
   const currentSelectedShapeIds = useSelector(
@@ -69,6 +68,7 @@ const SelectIndicator = styled('path', {
69 68
   strokeLinejoin: 'round',
70 69
   stroke: '$selected',
71 70
   pointerEvents: 'none',
71
+  fill: 'none',
72 72
 
73 73
   variants: {
74 74
     isLocked: {

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

@@ -5,7 +5,7 @@ import { getShapeUtils } from 'lib/shape-utils'
5 5
 import { getBoundsCenter, getPage } from 'utils/utils'
6 6
 import { ShapeStyles, ShapeType } from 'types'
7 7
 import useShapeEvents from 'hooks/useShapeEvents'
8
-import * as vec from 'utils/vec'
8
+import vec from 'utils/vec'
9 9
 import { getShapeStyle } from 'lib/shape-styles'
10 10
 import ContextMenu from 'components/context-menu'
11 11
 
@@ -30,7 +30,6 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
30 30
   // detects the change and pulls this component.
31 31
   if (!shape) return null
32 32
 
33
-  const utils = getShapeUtils(shape)
34 33
   const style = getShapeStyle(shape.style)
35 34
   const shapeUtils = getShapeUtils(shape)
36 35
   const { isShy, isParent, isForeignObject } = shapeUtils

+ 106
- 76
components/context-menu.tsx 查看文件

@@ -1,7 +1,7 @@
1 1
 import * as _ContextMenu from '@radix-ui/react-context-menu'
2 2
 import * as _Dropdown from '@radix-ui/react-dropdown-menu'
3 3
 import styled from 'styles'
4
-import { RowButton } from './shared'
4
+import { IconWrapper, RowButton } from './shared'
5 5
 import {
6 6
   commandKey,
7 7
   deepCompareArrays,
@@ -11,6 +11,7 @@ import {
11 11
 import state, { useSelector } from 'state'
12 12
 import { MoveType, ShapeType } from 'types'
13 13
 import React, { useRef } from 'react'
14
+import { ChevronRightIcon } from '@radix-ui/react-icons'
14 15
 
15 16
 export default function ContextMenu({
16 17
   children,
@@ -56,65 +57,9 @@ export default function ContextMenu({
56 57
               </kbd>
57 58
             </Button>
58 59
             <StyledDivider />
59
-            <Button
60
-              onSelect={() =>
61
-                state.send('MOVED', {
62
-                  type: MoveType.ToFront,
63
-                })
64
-              }
65
-            >
66
-              <span>Move To Front</span>
67
-              <kbd>
68
-                <span>{commandKey()}</span>
69
-                <span>⇧</span>
70
-                <span>]</span>
71
-              </kbd>
72
-            </Button>
73
-
74
-            <Button
75
-              onSelect={() =>
76
-                state.send('MOVED', {
77
-                  type: MoveType.Forward,
78
-                })
79
-              }
80
-            >
81
-              <span>Move Forward</span>
82
-              <kbd>
83
-                <span>{commandKey()}</span>
84
-                <span>]</span>
85
-              </kbd>
86
-            </Button>
87
-            <Button
88
-              onSelect={() =>
89
-                state.send('MOVED', {
90
-                  type: MoveType.Backward,
91
-                })
92
-              }
93
-            >
94
-              <span>Move Backward</span>
95
-              <kbd>
96
-                <span>{commandKey()}</span>
97
-                <span>[</span>
98
-              </kbd>
99
-            </Button>
100
-            <Button
101
-              onSelect={() =>
102
-                state.send('MOVED', {
103
-                  type: MoveType.ToBack,
104
-                })
105
-              }
106
-            >
107
-              <span>Move to Back</span>
108
-              <kbd>
109
-                <span>{commandKey()}</span>
110
-                <span>⇧</span>
111
-                <span>[</span>
112
-              </kbd>
113
-            </Button>
114 60
             {hasGroupSelectd ||
115 61
               (hasMultipleSelected && (
116 62
                 <>
117
-                  <StyledDivider />
118 63
                   {hasGroupSelectd && (
119 64
                     <Button onSelect={() => state.send('UNGROUPED')}>
120 65
                       <span>Ungroup</span>
@@ -136,11 +81,65 @@ export default function ContextMenu({
136 81
                   )}
137 82
                 </>
138 83
               ))}
139
-            <StyledDivider />
84
+            <SubMenu label="Move">
85
+              <Button
86
+                onSelect={() =>
87
+                  state.send('MOVED', {
88
+                    type: MoveType.ToFront,
89
+                  })
90
+                }
91
+              >
92
+                <span>To Front</span>
93
+                <kbd>
94
+                  <span>{commandKey()}</span>
95
+                  <span>⇧</span>
96
+                  <span>]</span>
97
+                </kbd>
98
+              </Button>
140 99
 
141
-            {/* <Button onSelect={() => state.send('MOVED_TO_PAGE')}> */}
142
-            <MoveToPageDropDown>Move to Page</MoveToPageDropDown>
143
-            {/* </Button> */}
100
+              <Button
101
+                onSelect={() =>
102
+                  state.send('MOVED', {
103
+                    type: MoveType.Forward,
104
+                  })
105
+                }
106
+              >
107
+                <span>Forward</span>
108
+                <kbd>
109
+                  <span>{commandKey()}</span>
110
+                  <span>]</span>
111
+                </kbd>
112
+              </Button>
113
+              <Button
114
+                onSelect={() =>
115
+                  state.send('MOVED', {
116
+                    type: MoveType.Backward,
117
+                  })
118
+                }
119
+              >
120
+                <span>Backward</span>
121
+                <kbd>
122
+                  <span>{commandKey()}</span>
123
+                  <span>[</span>
124
+                </kbd>
125
+              </Button>
126
+              <Button
127
+                onSelect={() =>
128
+                  state.send('MOVED', {
129
+                    type: MoveType.ToBack,
130
+                  })
131
+                }
132
+              >
133
+                <span>To Back</span>
134
+                <kbd>
135
+                  <span>{commandKey()}</span>
136
+                  <span>⇧</span>
137
+                  <span>[</span>
138
+                </kbd>
139
+              </Button>
140
+            </SubMenu>
141
+            <MoveToPageMenu />
142
+            <StyledDivider />
144 143
             <Button onSelect={() => state.send('DELETED')}>
145 144
               <span>Delete</span>
146 145
               <kbd>
@@ -180,8 +179,7 @@ const StyledContent = styled(_ContextMenu.Content, {
180 179
   pointerEvents: 'all',
181 180
   userSelect: 'none',
182 181
   zIndex: 200,
183
-  padding: 2,
184
-  border: '1px solid $panel',
182
+  padding: 3,
185 183
   boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
186 184
   minWidth: 128,
187 185
 
@@ -210,7 +208,7 @@ const StyledContent = styled(_ContextMenu.Content, {
210 208
 const StyledDivider = styled(_ContextMenu.Separator, {
211 209
   backgroundColor: '$hover',
212 210
   height: 1,
213
-  margin: '2px -2px',
211
+  margin: '3px -3px',
214 212
 })
215 213
 
216 214
 function Button({
@@ -234,7 +232,33 @@ function Button({
234 232
   )
235 233
 }
236 234
 
237
-function MoveToPageDropDown({ children }: { children: React.ReactNode }) {
235
+function SubMenu({
236
+  children,
237
+  label,
238
+}: {
239
+  label: string
240
+  children: React.ReactNode
241
+}) {
242
+  return (
243
+    <_ContextMenu.Root>
244
+      <_ContextMenu.TriggerItem
245
+        as={RowButton}
246
+        bp={{ '@initial': 'mobile', '@sm': 'small' }}
247
+      >
248
+        <span>{label}</span>
249
+        <IconWrapper size="small">
250
+          <ChevronRightIcon />
251
+        </IconWrapper>
252
+      </_ContextMenu.TriggerItem>
253
+      <StyledContent sideOffset={2} alignOffset={-2} isMobile={isMobile()}>
254
+        {children}
255
+        <StyledArrow offset={13} />
256
+      </StyledContent>
257
+    </_ContextMenu.Root>
258
+  )
259
+}
260
+
261
+function MoveToPageMenu() {
238 262
   const documentPages = useSelector((s) => s.data.document.pages)
239 263
   const currentPageId = useSelector((s) => s.data.currentPageId)
240 264
 
@@ -245,27 +269,29 @@ function MoveToPageDropDown({ children }: { children: React.ReactNode }) {
245 269
     .filter((a) => a.id !== currentPageId)
246 270
 
247 271
   return (
248
-    <_Dropdown.Root>
249
-      <_Dropdown.Trigger
272
+    <_ContextMenu.Root>
273
+      <_ContextMenu.TriggerItem
250 274
         as={RowButton}
251 275
         bp={{ '@initial': 'mobile', '@sm': 'small' }}
252 276
       >
253
-        {children}
254
-      </_Dropdown.Trigger>
255
-      <StyledDialogContent side="right" sideOffset={8}>
277
+        <span>Move To Page</span>
278
+        <IconWrapper size="small">
279
+          <ChevronRightIcon />
280
+        </IconWrapper>
281
+      </_ContextMenu.TriggerItem>
282
+      <StyledContent sideOffset={2} alignOffset={-2} isMobile={isMobile()}>
256 283
         {sorted.map(({ id, name }) => (
257
-          <_Dropdown.Item
258
-            as={RowButton}
284
+          <Button
259 285
             key={id}
260
-            bp={{ '@initial': 'mobile', '@sm': 'small' }}
261 286
             disabled={id === currentPageId}
262 287
             onSelect={() => state.send('MOVED_TO_PAGE', { id })}
263 288
           >
264 289
             <span>{name}</span>
265
-          </_Dropdown.Item>
290
+          </Button>
266 291
         ))}
267
-      </StyledDialogContent>
268
-    </_Dropdown.Root>
292
+        <StyledArrow offset={13} />
293
+      </StyledContent>
294
+    </_ContextMenu.Root>
269 295
   )
270 296
 }
271 297
 
@@ -293,3 +319,7 @@ const StyledDialogContent = styled(_Dropdown.Content, {
293 319
     outline: 'none',
294 320
   },
295 321
 })
322
+
323
+const StyledArrow = styled(_ContextMenu.Arrow, {
324
+  fill: 'white',
325
+})

+ 0
- 1
components/style-panel/color-content.tsx 查看文件

@@ -3,7 +3,6 @@ import { strokes } from 'lib/shape-styles'
3 3
 import { ColorStyle } from 'types'
4 4
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
5 5
 import { Square } from 'react-feather'
6
-import styled from 'styles'
7 6
 import { DropdownContent } from '../shared'
8 7
 
9 8
 export default function ColorContent({

+ 2
- 5
components/style-panel/dash-picker.tsx 查看文件

@@ -8,12 +8,9 @@ import {
8 8
 import * as RadioGroup from '@radix-ui/react-radio-group'
9 9
 import { DashStyle } from 'types'
10 10
 import state from 'state'
11
-import { ChangeEvent } from 'react'
12 11
 
13
-function handleChange(e: ChangeEvent<HTMLInputElement>) {
14
-  state.send('CHANGED_STYLE', {
15
-    dash: e.currentTarget.value,
16
-  })
12
+function handleChange(dash: string) {
13
+  state.send('CHANGED_STYLE', { dash })
17 14
 }
18 15
 
19 16
 interface Props {

+ 2
- 4
components/style-panel/is-filled-picker.tsx 查看文件

@@ -6,7 +6,7 @@ import { IconWrapper, RowButton } from '../shared'
6 6
 
7 7
 interface Props {
8 8
   isFilled: boolean
9
-  onChange: (isFilled: boolean) => void
9
+  onChange: (isFilled: boolean | string) => void
10 10
 }
11 11
 
12 12
 export default function IsFilledPicker({ isFilled, onChange }: Props) {
@@ -15,9 +15,7 @@ export default function IsFilledPicker({ isFilled, onChange }: Props) {
15 15
       as={RowButton}
16 16
       bp={{ '@initial': 'mobile', '@sm': 'small' }}
17 17
       checked={isFilled}
18
-      onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) =>
19
-        onChange(e.currentTarget.checked)
20
-      }
18
+      onCheckedChange={onChange}
21 19
     >
22 20
       <label htmlFor="fill">Fill</label>
23 21
       <IconWrapper>

+ 2
- 5
components/style-panel/size-picker.tsx 查看文件

@@ -1,14 +1,11 @@
1 1
 import { Group, Item } from '../shared'
2 2
 import * as RadioGroup from '@radix-ui/react-radio-group'
3
-import { ChangeEvent } from 'react'
4 3
 import { Circle } from 'react-feather'
5 4
 import state from 'state'
6 5
 import { SizeStyle } from 'types'
7 6
 
8
-function handleChange(e: ChangeEvent<HTMLInputElement>) {
9
-  state.send('CHANGED_STYLE', {
10
-    size: e.currentTarget.value as SizeStyle,
11
-  })
7
+function handleChange(size: string) {
8
+  state.send('CHANGED_STYLE', { size })
12 9
 }
13 10
 
14 11
 export default function SizePicker({ size }: { size: SizeStyle }) {

+ 10
- 1
hooks/useBoundsEvents.ts 查看文件

@@ -1,4 +1,5 @@
1 1
 import { useCallback } from 'react'
2
+import { fastTransform } from 'state/hacks'
2 3
 import inputs from 'state/inputs'
3 4
 import { Edge, Corner } from 'types'
4 5
 
@@ -29,7 +30,15 @@ export default function useBoundsEvents(handle: Edge | Corner | 'rotate') {
29 30
       if (e.buttons !== 1) return
30 31
       if (!inputs.canAccept(e.pointerId)) return
31 32
       e.stopPropagation()
32
-      state.send('MOVED_POINTER', inputs.pointerMove(e))
33
+
34
+      const info = inputs.pointerMove(e)
35
+
36
+      if (state.isIn('transformingSelection')) {
37
+        fastTransform(info)
38
+        return
39
+      }
40
+
41
+      state.send('MOVED_POINTER', info)
33 42
     },
34 43
     [handle]
35 44
   )

+ 2
- 4
hooks/useCamera.ts 查看文件

@@ -1,5 +1,6 @@
1 1
 import React, { useEffect } from 'react'
2 2
 import state from 'state'
3
+import storage from 'state/storage'
3 4
 import { getCurrentCamera } from 'utils/utils'
4 5
 
5 6
 /**
@@ -23,10 +24,7 @@ export default function useCamera(ref: React.MutableRefObject<SVGGElement>) {
23 24
           `scale(${zoom}) translate(${point[0]} ${point[1]})`
24 25
         )
25 26
 
26
-        localStorage.setItem(
27
-          'code_slate_camera',
28
-          JSON.stringify({ point, zoom })
29
-        )
27
+        storage.savePageState(state.data)
30 28
 
31 29
         prev = getCurrentCamera(state.data)
32 30
       }

+ 11
- 1
hooks/useCanvasEvents.ts 查看文件

@@ -1,6 +1,11 @@
1 1
 import { MutableRefObject, useCallback } from 'react'
2 2
 import state from 'state'
3
-import { fastBrushSelect, fastDrawUpdate, fastTranslate } from 'state/hacks'
3
+import {
4
+  fastBrushSelect,
5
+  fastDrawUpdate,
6
+  fastTransform,
7
+  fastTranslate,
8
+} from 'state/hacks'
4 9
 import inputs from 'state/inputs'
5 10
 import { isMobile } from 'utils/utils'
6 11
 
@@ -47,6 +52,11 @@ export default function useCanvasEvents(
47 52
       return
48 53
     }
49 54
 
55
+    if (state.isIn('transformingSelection')) {
56
+      fastTransform(info)
57
+      return
58
+    }
59
+
50 60
     state.send('MOVED_POINTER', info)
51 61
   }, [])
52 62
 

+ 1
- 1
hooks/useZoomEvents.ts 查看文件

@@ -1,7 +1,7 @@
1 1
 import React, { useEffect, useRef } from 'react'
2 2
 import state from 'state'
3 3
 import inputs from 'state/inputs'
4
-import * as vec from 'utils/vec'
4
+import vec from 'utils/vec'
5 5
 import { useGesture } from 'react-use-gesture'
6 6
 import {
7 7
   fastBrushSelect,

+ 2
- 2
lib/code/circle.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { CircleShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -9,7 +9,7 @@ export default class Circle extends CodeShape<CircleShape> {
9 9
     props.point = vectorToPoint(props.point)
10 10
 
11 11
     super({
12
-      id: uuid(),
12
+      id: uniqueId(),
13 13
       seed: Math.random(),
14 14
       parentId: (window as any).currentPageId,
15 15
       type: ShapeType.Circle,

+ 6
- 6
lib/code/control.ts 查看文件

@@ -3,8 +3,8 @@ import {
3 3
   ControlType,
4 4
   NumberCodeControl,
5 5
   VectorCodeControl,
6
-} from "types"
7
-import { v4 as uuid } from "uuid"
6
+} from 'types'
7
+import { v4 as uuid } from 'uuid'
8 8
 
9 9
 export const controls: Record<string, any> = {}
10 10
 
@@ -13,8 +13,8 @@ export const codeControls = new Set<CodeControl>([])
13 13
 export class Control<T extends CodeControl> {
14 14
   control: T
15 15
 
16
-  constructor(control: Omit<T, "id">) {
17
-    this.control = { ...control, id: uuid() } as T
16
+  constructor(control: Omit<T, 'id'>) {
17
+    this.control = { ...control, id: uniqueId() } as T
18 18
     codeControls.add(this.control)
19 19
 
20 20
     // Could there be a better way to prevent this?
@@ -32,7 +32,7 @@ export class Control<T extends CodeControl> {
32 32
 }
33 33
 
34 34
 export class NumberControl extends Control<NumberCodeControl> {
35
-  constructor(options: Omit<NumberCodeControl, "id" | "type">) {
35
+  constructor(options: Omit<NumberCodeControl, 'id' | 'type'>) {
36 36
     const { value = 0, step = 1 } = options
37 37
     super({
38 38
       type: ControlType.Number,
@@ -44,7 +44,7 @@ export class NumberControl extends Control<NumberCodeControl> {
44 44
 }
45 45
 
46 46
 export class VectorControl extends Control<VectorCodeControl> {
47
-  constructor(options: Omit<VectorCodeControl, "id" | "type">) {
47
+  constructor(options: Omit<VectorCodeControl, 'id' | 'type'>) {
48 48
     const { value = [0, 0], isNormalized = false } = options
49 49
     super({
50 50
       type: ControlType.Vector,

+ 2
- 2
lib/code/dot.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { DotShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -9,7 +9,7 @@ export default class Dot extends CodeShape<DotShape> {
9 9
     props.point = vectorToPoint(props.point)
10 10
 
11 11
     super({
12
-      id: uuid(),
12
+      id: uniqueId(),
13 13
       seed: Math.random(),
14 14
       parentId: (window as any).currentPageId,
15 15
       type: ShapeType.Dot,

+ 2
- 2
lib/code/ellipse.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { EllipseShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -9,7 +9,7 @@ export default class Ellipse extends CodeShape<EllipseShape> {
9 9
     props.point = vectorToPoint(props.point)
10 10
 
11 11
     super({
12
-      id: uuid(),
12
+      id: uniqueId(),
13 13
       seed: Math.random(),
14 14
       parentId: (window as any).currentPageId,
15 15
       type: ShapeType.Ellipse,

+ 1
- 1
lib/code/index.ts 查看文件

@@ -4,7 +4,7 @@ import shapeUtilityMap, {
4 4
   getShapeUtils,
5 5
   ShapeUtility,
6 6
 } from 'lib/shape-utils'
7
-import * as vec from 'utils/vec'
7
+import vec from 'utils/vec'
8 8
 import Vector from './vector'
9 9
 import { vectorToPoint } from 'utils/utils'
10 10
 

+ 2
- 2
lib/code/line.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { LineShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -10,7 +10,7 @@ export default class Line extends CodeShape<LineShape> {
10 10
     props.direction = vectorToPoint(props.direction)
11 11
 
12 12
     super({
13
-      id: uuid(),
13
+      id: uniqueId(),
14 14
       seed: Math.random(),
15 15
       parentId: (window as any).currentPageId,
16 16
       type: ShapeType.Line,

+ 2
- 2
lib/code/polyline.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { PolylineShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -10,7 +10,7 @@ export default class Polyline extends CodeShape<PolylineShape> {
10 10
     props.points = props.points.map(vectorToPoint)
11 11
 
12 12
     super({
13
-      id: uuid(),
13
+      id: uniqueId(),
14 14
       seed: Math.random(),
15 15
       parentId: (window as any).currentPageId,
16 16
       type: ShapeType.Polyline,

+ 2
- 2
lib/code/ray.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { RayShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -10,7 +10,7 @@ export default class Ray extends CodeShape<RayShape> {
10 10
     props.direction = vectorToPoint(props.direction)
11 11
 
12 12
     super({
13
-      id: uuid(),
13
+      id: uniqueId(),
14 14
       seed: Math.random(),
15 15
       type: ShapeType.Ray,
16 16
       isGenerated: true,

+ 2
- 2
lib/code/rectangle.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import CodeShape from './index'
2
-import { v4 as uuid } from 'uuid'
2
+import { uniqueId } from 'utils/utils'
3 3
 import { RectangleShape, ShapeType } from 'types'
4 4
 import { vectorToPoint } from 'utils/utils'
5 5
 import { defaultStyle } from 'lib/shape-styles'
@@ -10,7 +10,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
10 10
     props.size = vectorToPoint(props.size)
11 11
 
12 12
     super({
13
-      id: uuid(),
13
+      id: uniqueId(),
14 14
       seed: Math.random(),
15 15
       parentId: (window as any).currentPageId,
16 16
       type: ShapeType.Rectangle,

+ 3
- 3
lib/shape-utils/arrow.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import {
4 4
   ease,
5 5
   getSvgPathFromStroke,
@@ -65,7 +65,7 @@ const arrow = registerShapeUtils<ArrowShape>({
65 65
     } = props
66 66
 
67 67
     return {
68
-      id: uuid(),
68
+      id: uniqueId(),
69 69
       seed: Math.random(),
70 70
       type: ShapeType.Arrow,
71 71
       isGenerated: false,

+ 3
- 3
lib/shape-utils/circle.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { CircleShape, ColorStyle, DashStyle, ShapeType, SizeStyle } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { boundsContained } from 'utils/bounds'
@@ -13,7 +13,7 @@ const circle = registerShapeUtils<CircleShape>({
13 13
 
14 14
   create(props) {
15 15
     return {
16
-      id: uuid(),
16
+      id: uniqueId(),
17 17
       seed: Math.random(),
18 18
       type: ShapeType.Circle,
19 19
       isGenerated: false,

+ 3
- 3
lib/shape-utils/dot.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { DotShape, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { boundsContained } from 'utils/bounds'
@@ -12,7 +12,7 @@ const dot = registerShapeUtils<DotShape>({
12 12
 
13 13
   create(props) {
14 14
     return {
15
-      id: uuid(),
15
+      id: uniqueId(),
16 16
       seed: Math.random(),
17 17
       type: ShapeType.Dot,
18 18
       isGenerated: false,

+ 3
- 3
lib/shape-utils/draw.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { DashStyle, DrawShape, ShapeStyles, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { intersectPolylineBounds } from 'utils/intersections'
@@ -23,7 +23,7 @@ const draw = registerShapeUtils<DrawShape>({
23 23
 
24 24
   create(props) {
25 25
     return {
26
-      id: uuid(),
26
+      id: uniqueId(),
27 27
       seed: Math.random(),
28 28
       type: ShapeType.Draw,
29 29
       isGenerated: false,

+ 3
- 3
lib/shape-utils/ellipse.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { EllipseShape, ShapeType } from 'types'
4 4
 import { getShapeUtils, registerShapeUtils } from './index'
5 5
 import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
@@ -16,7 +16,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
16 16
 
17 17
   create(props) {
18 18
     return {
19
-      id: uuid(),
19
+      id: uniqueId(),
20 20
       seed: Math.random(),
21 21
       type: ShapeType.Ellipse,
22 22
       isGenerated: false,

+ 3
- 3
lib/shape-utils/group.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import {
4 4
   GroupShape,
5 5
   RectangleShape,
@@ -27,7 +27,7 @@ const group = registerShapeUtils<GroupShape>({
27 27
 
28 28
   create(props) {
29 29
     return {
30
-      id: uuid(),
30
+      id: uniqueId(),
31 31
       seed: Math.random(),
32 32
       type: ShapeType.Group,
33 33
       isGenerated: false,

+ 3
- 3
lib/shape-utils/index.tsx 查看文件

@@ -9,7 +9,7 @@ import {
9 9
   Mutable,
10 10
   ShapeByType,
11 11
 } from 'types'
12
-import * as vec from 'utils/vec'
12
+import vec from 'utils/vec'
13 13
 import {
14 14
   getBoundsCenter,
15 15
   getBoundsFromPoints,
@@ -20,7 +20,7 @@ import {
20 20
   boundsContainPolygon,
21 21
   pointInBounds,
22 22
 } from 'utils/bounds'
23
-import { v4 as uuid } from 'uuid'
23
+import { uniqueId } from 'utils/utils'
24 24
 import circle from './circle'
25 25
 import dot from './dot'
26 26
 import polyline from './polyline'
@@ -231,7 +231,7 @@ function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
231 231
 
232 232
     create(props) {
233 233
       return {
234
-        id: uuid(),
234
+        id: uniqueId(),
235 235
         isGenerated: false,
236 236
         point: [0, 0],
237 237
         name: 'Shape',

+ 3
- 3
lib/shape-utils/line.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { LineShape, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { boundsContained } from 'utils/bounds'
@@ -14,7 +14,7 @@ const line = registerShapeUtils<LineShape>({
14 14
 
15 15
   create(props) {
16 16
     return {
17
-      id: uuid(),
17
+      id: uniqueId(),
18 18
       seed: Math.random(),
19 19
       type: ShapeType.Line,
20 20
       isGenerated: false,

+ 3
- 3
lib/shape-utils/polyline.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { PolylineShape, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { intersectPolylineBounds } from 'utils/intersections'
@@ -12,7 +12,7 @@ const polyline = registerShapeUtils<PolylineShape>({
12 12
 
13 13
   create(props) {
14 14
     return {
15
-      id: uuid(),
15
+      id: uniqueId(),
16 16
       seed: Math.random(),
17 17
       type: ShapeType.Polyline,
18 18
       isGenerated: false,

+ 3
- 3
lib/shape-utils/ray.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { RayShape, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { boundsContained } from 'utils/bounds'
@@ -13,7 +13,7 @@ const ray = registerShapeUtils<RayShape>({
13 13
 
14 14
   create(props) {
15 15
     return {
16
-      id: uuid(),
16
+      id: uniqueId(),
17 17
       seed: Math.random(),
18 18
       type: ShapeType.Ray,
19 19
       isGenerated: false,

+ 3
- 3
lib/shape-utils/rectangle.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { RectangleShape, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import {
@@ -19,7 +19,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
19 19
 
20 20
   create(props) {
21 21
     return {
22
-      id: uuid(),
22
+      id: uniqueId(),
23 23
       seed: Math.random(),
24 24
       type: ShapeType.Rectangle,
25 25
       isGenerated: false,

+ 3
- 3
lib/shape-utils/text.tsx 查看文件

@@ -1,5 +1,5 @@
1
-import { v4 as uuid } from 'uuid'
2
-import * as vec from 'utils/vec'
1
+import { uniqueId } from 'utils/utils'
2
+import vec from 'utils/vec'
3 3
 import { TextShape, ShapeType, FontSize } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { defaultStyle, getFontStyle, getShapeStyle } from 'lib/shape-styles'
@@ -36,7 +36,7 @@ const text = registerShapeUtils<TextShape>({
36 36
 
37 37
   create(props) {
38 38
     return {
39
-      id: uuid(),
39
+      id: uniqueId(),
40 40
       seed: Math.random(),
41 41
       type: ShapeType.Text,
42 42
       isGenerated: false,

+ 8
- 8
package.json 查看文件

@@ -9,22 +9,22 @@
9 9
   },
10 10
   "dependencies": {
11 11
     "@monaco-editor/react": "^4.1.3",
12
-    "@radix-ui/react-checkbox": "^0.0.15",
13
-    "@radix-ui/react-context-menu": "^0.0.19",
14
-    "@radix-ui/react-dialog": "^0.0.17",
15
-    "@radix-ui/react-dropdown-menu": "^0.0.19",
12
+    "@radix-ui/react-checkbox": "^0.0.16",
13
+    "@radix-ui/react-context-menu": "^0.0.21",
14
+    "@radix-ui/react-dialog": "^0.0.18",
15
+    "@radix-ui/react-dropdown-menu": "^0.0.20",
16 16
     "@radix-ui/react-icons": "^1.0.3",
17
-    "@radix-ui/react-radio-group": "^0.0.16",
18
-    "@radix-ui/react-tooltip": "^0.0.18",
17
+    "@radix-ui/react-radio-group": "^0.0.17",
18
+    "@radix-ui/react-tooltip": "^0.0.19",
19 19
     "@state-designer/react": "^1.7.3",
20
-    "@stitches/react": "^0.1.9",
20
+    "@stitches/react": "^0.2.1",
21 21
     "browser-fs-access": "^0.17.3",
22 22
     "framer-motion": "^4.1.16",
23 23
     "idb-keyval": "^5.0.6",
24 24
     "ismobilejs": "^1.1.1",
25 25
     "next": "10.2.0",
26 26
     "next-pwa": "^5.2.21",
27
-    "perfect-freehand": "^0.4.8",
27
+    "perfect-freehand": "^0.4.9",
28 28
     "prettier": "^2.3.0",
29 29
     "react": "17.0.2",
30 30
     "react-dom": "17.0.2",

+ 7
- 7
state/commands/change-page.ts 查看文件

@@ -3,8 +3,8 @@ import history from '../history'
3 3
 import { Data } from 'types'
4 4
 import storage from 'state/storage'
5 5
 
6
-export default function changePage(data: Data, pageId: string) {
7
-  const { currentPageId: prevPageId } = data
6
+export default function changePage(data: Data, toPageId: string) {
7
+  const { currentPageId: fromPageId } = data
8 8
 
9 9
   history.execute(
10 10
     data,
@@ -13,13 +13,13 @@ export default function changePage(data: Data, pageId: string) {
13 13
       category: 'canvas',
14 14
       manualSelection: true,
15 15
       do(data) {
16
-        storage.savePage(data, data.document.id, prevPageId)
17
-        data.currentPageId = pageId
18
-        storage.loadPage(data)
16
+        storage.savePage(data, data.document.id, fromPageId)
17
+        storage.loadPage(data, data.document.id, toPageId)
18
+        data.currentPageId = toPageId
19 19
       },
20 20
       undo(data) {
21
-        data.currentPageId = prevPageId
22
-        storage.loadPage(data, prevPageId)
21
+        storage.loadPage(data, data.document.id, fromPageId)
22
+        data.currentPageId = fromPageId
23 23
       },
24 24
     })
25 25
   )

+ 6
- 4
state/commands/create-page.ts 查看文件

@@ -1,9 +1,8 @@
1 1
 import Command from './command'
2 2
 import history from '../history'
3 3
 import { Data, Page, PageState } from 'types'
4
-import { v4 as uuid } from 'uuid'
4
+import { uniqueId } from 'utils/utils'
5 5
 import { current } from 'immer'
6
-import { getSelectedIds } from 'utils/utils'
7 6
 import storage from 'state/storage'
8 7
 
9 8
 export default function createPage(data: Data) {
@@ -20,12 +19,14 @@ export default function createPage(data: Data) {
20 19
         data.pageStates[page.id] = pageState
21 20
         data.currentPageId = page.id
22 21
         storage.savePage(data, data.document.id, page.id)
22
+        storage.saveDocumentToLocalStorage(data)
23 23
       },
24 24
       undo(data) {
25 25
         const { page, currentPageId } = snapshot
26 26
         delete data.document.pages[page.id]
27 27
         delete data.pageStates[page.id]
28 28
         data.currentPageId = currentPageId
29
+        storage.saveDocumentToLocalStorage(data)
29 30
       },
30 31
     })
31 32
   )
@@ -36,7 +37,7 @@ function getSnapshot(data: Data) {
36 37
 
37 38
   const pages = Object.values(data.document.pages)
38 39
   const unchanged = pages.filter((page) => page.name.startsWith('Page '))
39
-  const id = uuid()
40
+  const id = uniqueId()
40 41
 
41 42
   const page: Page = {
42 43
     type: 'page',
@@ -46,7 +47,8 @@ function getSnapshot(data: Data) {
46 47
     shapes: {},
47 48
   }
48 49
   const pageState: PageState = {
49
-    selectedIds: new Set<string>(),
50
+    id,
51
+    selectedIds: new Set([]),
50 52
     camera: {
51 53
       point: [0, 0],
52 54
       zoom: 1,

+ 1
- 1
state/commands/delete-page.ts 查看文件

@@ -4,7 +4,7 @@ import { Data } from 'types'
4 4
 import { current } from 'immer'
5 5
 import { getPage, getSelectedShapes } from 'utils/utils'
6 6
 import { getShapeUtils } from 'lib/shape-utils'
7
-import * as vec from 'utils/vec'
7
+import vec from 'utils/vec'
8 8
 import storage from 'state/storage'
9 9
 
10 10
 export default function changePage(data: Data, pageId: string) {

+ 3
- 3
state/commands/duplicate.ts 查看文件

@@ -8,16 +8,16 @@ import {
8 8
   getSelectedShapes,
9 9
   setSelectedIds,
10 10
 } from 'utils/utils'
11
-import { v4 as uuid } from 'uuid'
11
+import { uniqueId } from 'utils/utils'
12 12
 import { current } from 'immer'
13
-import * as vec from 'utils/vec'
13
+import vec from 'utils/vec'
14 14
 
15 15
 export default function duplicateCommand(data: Data) {
16 16
   const { currentPageId } = data
17 17
   const selectedShapes = getSelectedShapes(current(data))
18 18
   const duplicates = selectedShapes.map((shape) => ({
19 19
     ...shape,
20
-    id: uuid(),
20
+    id: uniqueId(),
21 21
     point: vec.add(shape.point, vec.div([16, 16], getCurrentCamera(data).zoom)),
22 22
   }))
23 23
 

+ 1
- 1
state/commands/group.ts 查看文件

@@ -12,7 +12,7 @@ import {
12 12
 import { current } from 'immer'
13 13
 import { createShape, getShapeUtils } from 'lib/shape-utils'
14 14
 import { PropsOfType } from 'types'
15
-import { v4 as uuid } from 'uuid'
15
+import { uniqueId } from 'utils/utils'
16 16
 import commands from '.'
17 17
 
18 18
 export default function groupCommand(data: Data) {

+ 1
- 1
state/commands/handle.ts 查看文件

@@ -4,7 +4,7 @@ import { Data } from 'types'
4 4
 import { getPage } from 'utils/utils'
5 5
 import { HandleSnapshot } from 'state/sessions/handle-session'
6 6
 import { getShapeUtils } from 'lib/shape-utils'
7
-import * as vec from 'utils/vec'
7
+import vec from 'utils/vec'
8 8
 
9 9
 export default function handleCommand(
10 10
   data: Data,

+ 1
- 1
state/commands/move-to-page.ts 查看文件

@@ -12,7 +12,7 @@ import {
12 12
   uniqueArray,
13 13
 } from 'utils/utils'
14 14
 import { getShapeUtils } from 'lib/shape-utils'
15
-import * as vec from 'utils/vec'
15
+import vec from 'utils/vec'
16 16
 import storage from 'state/storage'
17 17
 
18 18
 export default function nudgeCommand(data: Data, newPageId: string) {

+ 1
- 1
state/commands/nudge.ts 查看文件

@@ -3,7 +3,7 @@ import history from '../history'
3 3
 import { Data } from 'types'
4 4
 import { getPage, getSelectedShapes } from 'utils/utils'
5 5
 import { getShapeUtils } from 'lib/shape-utils'
6
-import * as vec from 'utils/vec'
6
+import vec from 'utils/vec'
7 7
 
8 8
 export default function nudgeCommand(data: Data, delta: number[]) {
9 9
   const { currentPageId } = data

+ 1
- 1
state/commands/rotate-ccw.ts 查看文件

@@ -7,7 +7,7 @@ import {
7 7
   getPage,
8 8
   getSelectedShapes,
9 9
 } from 'utils/utils'
10
-import * as vec from 'utils/vec'
10
+import vec from 'utils/vec'
11 11
 import { getShapeUtils } from 'lib/shape-utils'
12 12
 
13 13
 const PI2 = Math.PI * 2

+ 1
- 1
state/commands/translate.ts 查看文件

@@ -9,7 +9,7 @@ import {
9 9
   updateParents,
10 10
 } from 'utils/utils'
11 11
 import { getShapeUtils } from 'lib/shape-utils'
12
-import { v4 as uuid } from 'uuid'
12
+import { uniqueId } from 'utils/utils'
13 13
 
14 14
 export default function translateCommand(
15 15
   data: Data,

+ 1
- 1
state/commands/ungroup.ts 查看文件

@@ -11,7 +11,7 @@ import {
11 11
 import { current } from 'immer'
12 12
 import { createShape, getShapeUtils } from 'lib/shape-utils'
13 13
 import { PropsOfType } from 'types'
14
-import { v4 as uuid } from 'uuid'
14
+import { uniqueId } from 'utils/utils'
15 15
 
16 16
 export default function ungroupCommand(data: Data) {
17 17
   const cData = current(data)

+ 21
- 7
state/hacks.ts 查看文件

@@ -7,9 +7,10 @@ import {
7 7
   setToArray,
8 8
   setZoomCSS,
9 9
 } from 'utils/utils'
10
+import { freeze } from 'immer'
10 11
 import session from './session'
11 12
 import state from './state'
12
-import * as vec from 'utils/vec'
13
+import vec from 'utils/vec'
13 14
 
14 15
 /**
15 16
  * While a user is drawing with the draw tool, we want to update the shape without
@@ -34,7 +35,7 @@ export function fastDrawUpdate(info: PointerInfo) {
34 35
 
35 36
   data.document.pages[data.currentPageId].shapes[selectedId] = { ...shape }
36 37
 
37
-  state.forceData(Object.freeze(data))
38
+  state.forceData(freeze(data))
38 39
 }
39 40
 
40 41
 export function fastPanUpdate(delta: number[]) {
@@ -44,7 +45,7 @@ export function fastPanUpdate(delta: number[]) {
44 45
 
45 46
   data.pageStates[data.currentPageId].camera = { ...camera }
46 47
 
47
-  state.forceData(Object.freeze(data))
48
+  state.forceData(freeze(data))
48 49
 }
49 50
 
50 51
 export function fastZoomUpdate(point: number[], delta: number) {
@@ -60,7 +61,7 @@ export function fastZoomUpdate(point: number[], delta: number) {
60 61
 
61 62
   data.pageStates[data.currentPageId].camera = { ...camera }
62 63
 
63
-  state.forceData(Object.freeze(data))
64
+  state.forceData(freeze(data))
64 65
 }
65 66
 
66 67
 export function fastPinchCamera(
@@ -86,7 +87,7 @@ export function fastPinchCamera(
86 87
 
87 88
   data.pageStates[data.currentPageId] = { ...pageState }
88 89
 
89
-  state.forceData(Object.freeze(data))
90
+  state.forceData(freeze(data))
90 91
 }
91 92
 
92 93
 export function fastBrushSelect(point: number[]) {
@@ -94,7 +95,7 @@ export function fastBrushSelect(point: number[]) {
94 95
 
95 96
   session.current.update(data, screenToWorld(point, data))
96 97
 
97
-  state.forceData(Object.freeze(data))
98
+  state.forceData(freeze(data))
98 99
 }
99 100
 
100 101
 export function fastTranslate(info: PointerInfo) {
@@ -107,5 +108,18 @@ export function fastTranslate(info: PointerInfo) {
107 108
     info.altKey
108 109
   )
109 110
 
110
-  state.forceData(Object.freeze(data))
111
+  state.forceData(freeze(data))
112
+}
113
+
114
+export function fastTransform(info: PointerInfo) {
115
+  const data = { ...state.data }
116
+
117
+  session.current.update(
118
+    data,
119
+    screenToWorld(info.point, data),
120
+    info.shiftKey,
121
+    info.altKey
122
+  )
123
+
124
+  state.forceData(freeze(data))
111 125
 }

+ 8
- 4
state/history.ts 查看文件

@@ -1,4 +1,4 @@
1
-import { Data, Page, PageState } from 'types'
1
+import { Data } from 'types'
2 2
 import { BaseCommand } from './commands/command'
3 3
 import storage from './storage'
4 4
 
@@ -24,7 +24,6 @@ class History<T extends Data> {
24 24
     }
25 25
 
26 26
     storage.savePage(data)
27
-    // storage.saveToLocalStorage(data)
28 27
   }
29 28
 
30 29
   undo = (data: T) => {
@@ -34,7 +33,6 @@ class History<T extends Data> {
34 33
     if (this.disabled) return
35 34
     this.pointer--
36 35
     storage.savePage(data)
37
-    // storage.saveToLocalStorage(data)
38 36
   }
39 37
 
40 38
   redo = (data: T) => {
@@ -44,7 +42,6 @@ class History<T extends Data> {
44 42
     if (this.disabled) return
45 43
     this.pointer++
46 44
     storage.savePage(data)
47
-    // storage.saveToLocalStorage(data)
48 45
   }
49 46
 
50 47
   disable = () => {
@@ -62,6 +59,13 @@ class History<T extends Data> {
62 59
     }
63 60
   }
64 61
 
62
+  reset() {
63
+    this.stack = []
64
+    this.pointer = -1
65
+    this.maxLength = 100
66
+    this._enabled = true
67
+  }
68
+
65 69
   get disabled() {
66 70
     return !this._enabled
67 71
   }

+ 1
- 1
state/inputs.tsx 查看文件

@@ -1,6 +1,6 @@
1 1
 import React from 'react'
2 2
 import { PointerInfo } from 'types'
3
-import * as vec from 'utils/vec'
3
+import vec from 'utils/vec'
4 4
 import { isDarwin, getPoint } from 'utils/utils'
5 5
 
6 6
 const DOUBLE_CLICK_DURATION = 300

+ 1
- 1
state/sessions/arrow-session.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import { ArrowShape, Data, LineShape, RayShape } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'

+ 1
- 1
state/sessions/brush-session.ts 查看文件

@@ -11,7 +11,7 @@ import {
11 11
   setSelectedIds,
12 12
   setToArray,
13 13
 } from 'utils/utils'
14
-import * as vec from 'utils/vec'
14
+import vec from 'utils/vec'
15 15
 import state from 'state/state'
16 16
 
17 17
 export default class BrushSession extends BaseSession {

+ 1
- 1
state/sessions/direction-session.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import { Data, LineShape, RayShape } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'

+ 1
- 1
state/sessions/draw-session.ts 查看文件

@@ -3,7 +3,7 @@ import { Data, DrawShape } from 'types'
3 3
 import BaseSession from './base-session'
4 4
 import { getShapeUtils } from 'lib/shape-utils'
5 5
 import { getPage, getShape, isMobile, updateParents } from 'utils/utils'
6
-import * as vec from 'utils/vec'
6
+import vec from 'utils/vec'
7 7
 import commands from 'state/commands'
8 8
 export default class BrushSession extends BaseSession {
9 9
   origin: number[]

+ 1
- 1
state/sessions/edit-session.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import { Data, LineShape, RayShape, Shape } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'

+ 1
- 1
state/sessions/handle-session.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import { Data } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'

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

@@ -1,5 +1,5 @@
1 1
 import { Data, ShapeType } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'

+ 4
- 2
state/sessions/transform-session.ts 查看文件

@@ -1,8 +1,8 @@
1 1
 import { Data, Edge, Corner, Bounds } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5
-import { current } from 'immer'
5
+import { current, freeze } from 'immer'
6 6
 import { getShapeUtils } from 'lib/shape-utils'
7 7
 import {
8 8
   getBoundsCenter,
@@ -72,6 +72,8 @@ export default class TransformSession extends BaseSession {
72 72
         scaleY: this.scaleY,
73 73
         transformOrigin,
74 74
       })
75
+
76
+      shapes[id] = { ...shape }
75 77
     }
76 78
 
77 79
     updateParents(data, Object.keys(shapeBounds))

+ 3
- 4
state/sessions/transform-single-session.ts 查看文件

@@ -1,14 +1,11 @@
1 1
 import { Data, Edge, Corner } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'
6 6
 import { getShapeUtils } from 'lib/shape-utils'
7 7
 import {
8 8
   getTransformedBoundingBox,
9
-  getCommonBounds,
10
-  getRotatedCorners,
11
-  getTransformAnchor,
12 9
   getPage,
13 10
   getShape,
14 11
   getSelectedShapes,
@@ -63,6 +60,8 @@ export default class TransformSingleSession extends BaseSession {
63 60
       transformOrigin: [0.5, 0.5],
64 61
     })
65 62
 
63
+    data.document.pages[data.currentPageId].shapes[shape.id] = { ...shape }
64
+
66 65
     updateParents(data, [id])
67 66
   }
68 67
 

+ 4
- 4
state/sessions/translate-session.ts 查看文件

@@ -1,9 +1,9 @@
1 1
 import { Data, GroupShape, Shape, ShapeType } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import BaseSession from './base-session'
4 4
 import commands from 'state/commands'
5 5
 import { current } from 'immer'
6
-import { v4 as uuid } from 'uuid'
6
+import { uniqueId } from 'utils/utils'
7 7
 import {
8 8
   getChildIndexAbove,
9 9
   getDocumentBranch,
@@ -215,7 +215,7 @@ export function getTranslateSnapshot(data: Data) {
215 215
       .flatMap((shape) => {
216 216
         const clone = {
217 217
           ...shape,
218
-          id: uuid(),
218
+          id: uniqueId(),
219 219
           seed: Math.random(),
220 220
           parentId: shape.parentId,
221 221
           childIndex: getChildIndexAbove(cData, shape.id),
@@ -235,7 +235,7 @@ function cloneGroup(data: Data, clone: Shape): Shape[] {
235 235
 
236 236
   const page = getPage(data)
237 237
   const childClones = clone.children.flatMap((id) => {
238
-    const newId = uuid()
238
+    const newId = uniqueId()
239 239
     const source = page.shapes[id]
240 240
     const next = { ...source, id: newId, parentId: clone.id }
241 241
 

+ 32
- 19
state/state.ts 查看文件

@@ -1,9 +1,8 @@
1 1
 import { createSelectorHook, createState } from '@state-designer/react'
2 2
 import { updateFromCode } from 'lib/code/generate'
3 3
 import { createShape, getShapeUtils } from 'lib/shape-utils'
4
-import * as vec from 'utils/vec'
4
+import vec from 'utils/vec'
5 5
 import inputs from './inputs'
6
-import { defaultDocument } from './data'
7 6
 import history from './history'
8 7
 import storage from './storage'
9 8
 import * as Sessions from './sessions'
@@ -77,16 +76,29 @@ const initialData: Data = {
77 76
   currentParentId: 'page1',
78 77
   currentCodeFileId: 'file0',
79 78
   codeControls: {},
80
-  document: defaultDocument,
81
-  pageStates: {
82
-    page1: {
83
-      selectedIds: new Set([]),
84
-      camera: {
85
-        point: [0, 0],
86
-        zoom: 1,
79
+  document: {
80
+    id: '0001',
81
+    name: 'My Document',
82
+    pages: {
83
+      page1: {
84
+        id: 'page1',
85
+        type: 'page',
86
+        name: 'Page 1',
87
+        childIndex: 0,
88
+        shapes: {},
87 89
       },
88 90
     },
89
-    page2: {
91
+    code: {
92
+      file0: {
93
+        id: 'file0',
94
+        name: 'index.ts',
95
+        code: ``,
96
+      },
97
+    },
98
+  },
99
+  pageStates: {
100
+    page1: {
101
+      id: 'page1',
90 102
       selectedIds: new Set([]),
91 103
       camera: {
92 104
         point: [0, 0],
@@ -141,7 +153,7 @@ const state = createState({
141 153
         CHANGED_PAGE: 'changePage',
142 154
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
143 155
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
144
-        LOADED_FROM_FILE: 'loadDocumentFromJson',
156
+        LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
145 157
         PANNED_CAMERA: {
146 158
           do: 'panCamera',
147 159
         },
@@ -362,7 +374,7 @@ const state = createState({
362 374
             transformingSelection: {
363 375
               onEnter: 'startTransformSession',
364 376
               on: {
365
-                MOVED_POINTER: 'updateTransformSession',
377
+                // MOVED_POINTER: 'updateTransformSession', using hacks.fastTransform
366 378
                 PANNED_CAMERA: 'updateTransformSession',
367 379
                 PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
368 380
                 RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
@@ -405,8 +417,7 @@ const state = createState({
405 417
               ],
406 418
               on: {
407 419
                 STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
408
-                // Currently using hacks.fastBrushSelect
409
-                // MOVED_POINTER: 'updateBrushSession',
420
+                // MOVED_POINTER: 'updateBrushSession', using hacks.fastBrushSelect
410 421
                 PANNED_CAMERA: 'updateBrushSession',
411 422
                 STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
412 423
                 CANCELLED: { do: 'cancelSession', to: 'selecting' },
@@ -425,8 +436,7 @@ const state = createState({
425 436
         },
426 437
         pinching: {
427 438
           on: {
428
-            // Pinching uses hacks.fastPinchCamera
429
-            // PINCHED: { do: 'pinchCamera' },
439
+            // PINCHED: { do: 'pinchCamera' }, using hacks.fastPinchCamera
430 440
           },
431 441
           initial: 'selectPinching',
432 442
           onExit: { secretlyDo: 'updateZoomCSS' },
@@ -1498,6 +1508,9 @@ const state = createState({
1498 1508
     redo(data) {
1499 1509
       history.redo(data)
1500 1510
     },
1511
+    resetHistory(data) {
1512
+      history.reset()
1513
+    },
1501 1514
 
1502 1515
     /* --------------------- Styles --------------------- */
1503 1516
 
@@ -1595,8 +1608,8 @@ const state = createState({
1595 1608
       storage.loadDocumentFromFilesystem()
1596 1609
     },
1597 1610
 
1598
-    loadDocumentFromJson(data, payload: { restoredData: any }) {
1599
-      storage.loadDocumentFromJson(data, payload.restoredData)
1611
+    loadDocumentFromJson(data, payload: { json: any }) {
1612
+      storage.loadDocumentFromJson(data, payload.json)
1600 1613
     },
1601 1614
 
1602 1615
     forceSave(data) {
@@ -1613,7 +1626,7 @@ const state = createState({
1613 1626
 
1614 1627
     saveCode(data, payload: { code: string }) {
1615 1628
       data.document.code[data.currentCodeFileId].code = payload.code
1616
-      storage.saveToLocalStorage(data)
1629
+      storage.saveDocumentToLocalStorage(data)
1617 1630
     },
1618 1631
 
1619 1632
     clearBoundsRotation(data) {

+ 197
- 109
state/storage.ts 查看文件

@@ -1,13 +1,11 @@
1 1
 import * as fa from 'browser-fs-access'
2
-import { Data, Page, PageState, TLDocument } from 'types'
2
+import { Data, PageState, TLDocument } from 'types'
3 3
 import { decompress, compress, setToArray } from 'utils/utils'
4 4
 import state from './state'
5
-import { current } from 'immer'
6
-import { v4 as uuid } from 'uuid'
5
+import { uniqueId } from 'utils/utils'
7 6
 import * as idb from 'idb-keyval'
8 7
 
9 8
 const CURRENT_VERSION = 'code_slate_0.0.7'
10
-const DOCUMENT_ID = '0001'
11 9
 
12 10
 function storageId(fileId: string, label: string, id?: string) {
13 11
   return [CURRENT_VERSION, fileId, label, id].filter(Boolean).join('_')
@@ -21,99 +19,186 @@ class Storage {
21 19
   }
22 20
 
23 21
   firstLoad(data: Data) {
24
-    const lastOpened = localStorage.getItem(`${CURRENT_VERSION}_lastOpened`)
22
+    const lastOpenedFileId = localStorage.getItem(
23
+      `${CURRENT_VERSION}_lastOpened`
24
+    )
25 25
 
26
-    this.loadDocumentFromLocalStorage(data, lastOpened || DOCUMENT_ID)
26
+    // 1. Load Document from Local Storage
27
+    // Using the "last opened file id" in local storage.
28
+    if (lastOpenedFileId !== null) {
29
+      // Load document from local storage
30
+      const savedDocument = localStorage.getItem(
31
+        storageId(lastOpenedFileId, 'document', lastOpenedFileId)
32
+      )
27 33
 
28
-    this.loadPage(data, data.currentPageId)
34
+      if (savedDocument === null) {
35
+        // If no document found, create a fresh random id.
36
+        data.document.id = uniqueId()
37
+      } else {
38
+        // If we did find a document, load it into state.
39
+        const restoredDocument: TLDocument = JSON.parse(
40
+          decompress(savedDocument)
41
+        )
42
+
43
+        // Merge restored data into state.
44
+        data.document = restoredDocument
45
+      }
46
+    }
29 47
 
30
-    this.saveToLocalStorage(data, data.document.id)
48
+    this.load(data)
49
+  }
31 50
 
32
-    localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
51
+  saveDocumentToLocalStorage(data: Data) {
52
+    const document = this.getCompleteDocument(data)
53
+
54
+    localStorage.setItem(
55
+      storageId(data.document.id, 'document', data.document.id),
56
+      compress(JSON.stringify(document))
57
+    )
33 58
   }
34 59
 
35
-  load(data: Data, restoredData: any) {
36
-    // Before loading the state, save the pages / page states
60
+  getCompleteDocument = (data: Data) => {
61
+    // Create a safely mutable copy of the data
62
+    const document: TLDocument = { ...data.document }
37 63
 
38
-    // Empty current state.
39
-    data.document = {} as TLDocument
40
-    data.pageStates = {}
64
+    // Try to find the document's pages and page states in local storage.
65
+    Object.keys(document.pages).forEach((pageId) => {
66
+      const savedPage = localStorage.getItem(
67
+        storageId(document.id, 'page', pageId)
68
+      )
41 69
 
42
-    // Merge restored data into state.
43
-    Object.assign(data, restoredData)
70
+      if (savedPage !== null) {
71
+        document.pages[pageId] = JSON.parse(decompress(savedPage))
72
+      }
73
+    })
44 74
 
45
-    // Add id and name to document, just in case.
46
-    data.document = {
47
-      id: 'document0',
48
-      name: 'My Document',
49
-      ...restoredData.document,
50
-    }
75
+    return document
51 76
   }
52 77
 
53
-  loadDocumentFromLocalStorage(data: Data, fileId = DOCUMENT_ID) {
54
-    if (typeof window === 'undefined') return
55
-    if (typeof localStorage === 'undefined') return
56
-
57
-    // Load data from local storage
58
-    const savedData = localStorage.getItem(
59
-      storageId(fileId, 'document', fileId)
78
+  savePageState = (data: Data) => {
79
+    localStorage.setItem(
80
+      storageId(data.document.id, 'lastPageState', data.document.id),
81
+      JSON.stringify(data.pageStates[data.currentPageId])
60 82
     )
83
+  }
61 84
 
62
-    if (savedData === null) {
63
-      // If we're going to use the default data, assign the
64
-      // current document a fresh random id.
65
-      data.document.id = uuid()
66
-      return false
67
-    }
85
+  loadDocumentFromJson(data: Data, json: string) {
86
+    const restoredDocument: { document: TLDocument; pageState: PageState } =
87
+      JSON.parse(json)
88
+
89
+    data.document = restoredDocument.document
90
+
91
+    // Save pages to local storage, possibly overwriting unsaved local copies
92
+    Object.values(data.document.pages).forEach((page) => {
93
+      localStorage.setItem(
94
+        storageId(data.document.id, 'page', page.id),
95
+        compress(JSON.stringify(page))
96
+      )
97
+    })
68 98
 
69
-    const restoredData: any = JSON.parse(decompress(savedData))
99
+    localStorage.setItem(
100
+      storageId(data.document.id, 'lastPageState', data.document.id),
101
+      JSON.stringify(restoredDocument.pageState)
102
+    )
70 103
 
71
-    this.load(data, restoredData)
104
+    // Save the new file as the last opened document id
105
+    localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
106
+
107
+    this.load(data)
72 108
   }
73 109
 
74
-  getDataToSave = (data: Data) => {
75
-    const dataToSave = current(data) as any
110
+  load(data: Data) {
111
+    // Once we've loaded data either from local storage or json, run through these steps.
112
+    data.pageStates = {}
76 113
 
77
-    for (let pageId in data.document.pages) {
114
+    // 2. Load Pages from Local Storage
115
+    // Try to find the document's pages and page states in local storage.
116
+    Object.keys(data.document.pages).forEach((pageId) => {
78 117
       const savedPage = localStorage.getItem(
79 118
         storageId(data.document.id, 'page', pageId)
80 119
       )
81 120
 
82 121
       if (savedPage !== null) {
83
-        const restored: Page = JSON.parse(decompress(savedPage))
84
-        dataToSave.document.pages[pageId] = restored
122
+        // If we've found a page in local storage, set it into state.
123
+        data.document.pages[pageId] = JSON.parse(decompress(savedPage))
85 124
       }
86 125
 
87
-      const pageState = { ...dataToSave.pageStates[pageId] }
88
-      pageState.selectedIds = setToArray(pageState.selectedIds)
89
-    }
126
+      const savedPageState = localStorage.getItem(
127
+        storageId(data.document.id, 'pageState', pageId)
128
+      )
90 129
 
91
-    return JSON.stringify(dataToSave, null, 2)
92
-  }
130
+      if (savedPageState !== null) {
131
+        // If we've found a page state in local storage, set it into state.
132
+        data.pageStates[pageId] = JSON.parse(decompress(savedPageState))
133
+        data.pageStates[pageId].selectedIds = new Set([])
134
+      } else {
135
+        // Or else create a new one.
136
+        data.pageStates[pageId] = {
137
+          id: pageId,
138
+          selectedIds: new Set([]),
139
+          camera: {
140
+            point: [0, 0],
141
+            zoom: 1,
142
+          },
143
+        }
144
+      }
145
+    })
93 146
 
94
-  saveToLocalStorage = (data: Data, fileId = data.document.id) => {
95
-    if (typeof window === 'undefined') return
96
-    if (typeof localStorage === 'undefined') return
147
+    // 3. Restore the last page state
148
+    // Using the "last page state" in local storage.
149
+    const savedPageState = localStorage.getItem(
150
+      storageId(data.document.id, 'lastPageState', data.document.id)
151
+    )
97 152
 
98
-    const dataToSave = this.getDataToSave(data)
153
+    if (savedPageState !== null) {
154
+      const pageState = JSON.parse(decompress(savedPageState))
155
+      pageState.selectedIds = new Set([])
156
+      data.pageStates[pageState.id] = pageState
157
+      data.currentPageId = pageState.id
158
+    }
99 159
 
100
-    // Save current data to local storage
160
+    // 4. Save the current document
161
+    // The document is now "full" and ready. Whether we've restored a
162
+    // document or created a new one, save the entire current document.
101 163
     localStorage.setItem(
102
-      storageId(fileId, 'document', fileId),
103
-      compress(dataToSave)
164
+      storageId(data.document.id, 'document', data.document.id),
165
+      compress(JSON.stringify(data.document))
104 166
     )
105
-  }
106 167
 
107
-  loadDocumentFromJson(data: Data, restoredData: any) {
108
-    this.load(data, restoredData)
168
+    // 4.1
169
+    // Also save out copies of each page separately.
170
+    Object.values(data.document.pages).forEach((page) => {
171
+      // Save page
172
+      localStorage.setItem(
173
+        storageId(data.document.id, 'page', page.id),
174
+        compress(JSON.stringify(page))
175
+      )
176
+    })
109 177
 
110
-    for (let key in restoredData.document.pages) {
111
-      this.savePage(restoredData, restoredData.document.id, key)
112
-    }
178
+    // Save the last page state
179
+    const currentPageState = data.pageStates[data.currentPageId]
113 180
 
114
-    this.loadPage(data, data.currentPageId)
115
-    this.saveToLocalStorage(data, data.document.id)
181
+    localStorage.setItem(
182
+      storageId(data.document.id, 'lastPageState', data.document.id),
183
+      JSON.stringify(currentPageState)
184
+    )
185
+
186
+    // Finally, save the current document id as the "last opened" document id.
116 187
     localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
188
+
189
+    // 5. Prepare the new state.
190
+    // Clear out the other pages from state.
191
+    Object.values(data.document.pages).forEach((page) => {
192
+      if (page.id !== data.currentPageId) {
193
+        page.shapes = {}
194
+      }
195
+    })
196
+
197
+    // Update camera for the new page state
198
+    document.documentElement.style.setProperty(
199
+      '--camera-zoom',
200
+      data.pageStates[data.currentPageId].camera.zoom.toString()
201
+    )
117 202
   }
118 203
   /* ---------------------- Pages --------------------- */
119 204
 
@@ -127,25 +212,17 @@ class Storage {
127 212
   }
128 213
 
129 214
   savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
130
-    if (typeof window === 'undefined') return
131
-    if (typeof localStorage === 'undefined') return
215
+    const page = data.document.pages[pageId]
132 216
 
133 217
     // Save page
134
-    const page = data.document.pages[pageId]
135
-    const json = JSON.stringify(page)
136 218
 
137
-    localStorage.setItem(storageId(fileId, 'page', pageId), compress(json))
219
+    localStorage.setItem(
220
+      storageId(fileId, 'page', pageId),
221
+      compress(JSON.stringify(page))
222
+    )
138 223
 
139 224
     // Save page state
140
-
141
-    let currentPageState = {
142
-      camera: {
143
-        point: [0, 0],
144
-        zoom: 1,
145
-      },
146
-      selectedIds: new Set([]),
147
-      ...data.pageStates[pageId],
148
-    }
225
+    let currentPageState = data.pageStates[pageId]
149 226
 
150 227
     localStorage.setItem(
151 228
       storageId(fileId, 'pageState', pageId),
@@ -156,37 +233,42 @@ class Storage {
156 233
     )
157 234
   }
158 235
 
159
-  loadPage(data: Data, pageId = data.currentPageId) {
236
+  loadPage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
160 237
     if (typeof window === 'undefined') return
161 238
     if (typeof localStorage === 'undefined') return
162 239
 
163
-    const fileId = data.document.id
240
+    data.currentPageId = pageId
164 241
 
165
-    // Page
242
+    // Get saved page from local storage
166 243
     const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
167 244
 
168 245
     if (savedPage !== null) {
246
+      // If we have a page, move it into state
169 247
       data.document.pages[pageId] = JSON.parse(decompress(savedPage))
170 248
     } else {
249
+      // If we don't have a page, create a new page
171 250
       data.document.pages[pageId] = {
172 251
         id: pageId,
173 252
         type: 'page',
174
-        childIndex: 0,
175
-        name: 'Page',
253
+        childIndex: Object.keys(data.document.pages).length,
254
+        name: 'New Page',
176 255
         shapes: {},
177 256
       }
178 257
     }
179 258
 
180
-    // Page state
259
+    // Get saved page state from local storage
181 260
     const savedPageState = localStorage.getItem(
182 261
       storageId(fileId, 'pageState', pageId)
183 262
     )
184 263
 
185 264
     if (savedPageState !== null) {
265
+      // If we have a page, move it into state
186 266
       const restored: PageState = JSON.parse(savedPageState)
187 267
       data.pageStates[pageId] = restored
268
+      data.pageStates[pageId].selectedIds = new Set(restored.selectedIds)
188 269
     } else {
189 270
       data.pageStates[pageId] = {
271
+        id: pageId,
190 272
         camera: {
191 273
           point: [0, 0],
192 274
           zoom: 1,
@@ -195,17 +277,20 @@ class Storage {
195 277
       }
196 278
     }
197 279
 
198
-    // Empty shapes in state for other pages
280
+    // Save the last page state
281
+    localStorage.setItem(
282
+      storageId(fileId, 'lastPageState'),
283
+      JSON.stringify(data.pageStates[pageId])
284
+    )
199 285
 
200
-    for (let key in data.document.pages) {
201
-      if (key === pageId) continue
202
-      data.document.pages[key].shapes = {}
203
-    }
286
+    // Prepare new state
204 287
 
205
-    // Force selected Ids into sets
206
-    for (let key in data.pageStates) {
207
-      data.pageStates[key].selectedIds = new Set([])
208
-    }
288
+    // Now clear out the other pages from state.
289
+    Object.values(data.document.pages).forEach((page) => {
290
+      if (page.id !== data.currentPageId) {
291
+        page.shapes = {}
292
+      }
293
+    })
209 294
 
210 295
     // Update camera for the new page state
211 296
     document.documentElement.style.setProperty(
@@ -217,21 +302,32 @@ class Storage {
217 302
   /* ------------------- File System ------------------ */
218 303
 
219 304
   saveToFileSystem = (data: Data) => {
305
+    this.saveDocumentToLocalStorage(data)
220 306
     this.saveDataToFileSystem(data, data.document.id, false)
221 307
   }
222 308
 
223 309
   saveAsToFileSystem = (data: Data) => {
224
-    this.saveDataToFileSystem(data, uuid(), true)
310
+    this.saveDocumentToLocalStorage(data)
311
+    this.saveDataToFileSystem(data, uniqueId(), true)
225 312
   }
226 313
 
227 314
   saveDataToFileSystem = (data: Data, fileId: string, saveAs: boolean) => {
228
-    const json = this.getDataToSave(data)
229
-
230
-    this.saveToLocalStorage(data, fileId)
231
-
232
-    const blob = new Blob([json], {
233
-      type: 'application/vnd.tldraw+json',
234
-    })
315
+    const document = this.getCompleteDocument(data)
316
+
317
+    // Then save to file system
318
+    const blob = new Blob(
319
+      [
320
+        compress(
321
+          JSON.stringify({
322
+            document,
323
+            pageState: data.pageStates[data.currentPageId],
324
+          })
325
+        ),
326
+      ],
327
+      {
328
+        type: 'application/vnd.tldraw+json',
329
+      }
330
+    )
235 331
 
236 332
     fa.fileSave(
237 333
       blob,
@@ -258,23 +354,15 @@ class Storage {
258 354
   }
259 355
 
260 356
   loadDocumentFromFilesystem() {
261
-    console.warn('Loading file from file system.')
262 357
     fa.fileOpen({
263 358
       description: 'tldraw files',
264 359
     })
265 360
       .then((blob) =>
266
-        getTextFromBlob(blob).then((text) => {
267
-          const restoredData = JSON.parse(text)
268
-
269
-          if (restoredData === null) {
270
-            console.warn('Could not load that data.')
271
-            return
272
-          }
273
-
361
+        getTextFromBlob(blob).then((json) => {
274 362
           // Save blob for future saves
275 363
           this.previousSaveHandle = blob.handle
276 364
 
277
-          state.send('LOADED_FROM_FILE', { restoredData: { ...restoredData } })
365
+          state.send('LOADED_FROM_FILE', { json: decompress(json) })
278 366
         })
279 367
       )
280 368
       .catch((e) => {

+ 1
- 0
todo.md 查看文件

@@ -30,6 +30,7 @@
30 30
 
31 31
 - [x] Create context Menu
32 32
 - [x] Wire up events
33
+- [ ] Use nested context menu
33 34
 
34 35
 ## Move to Page
35 36
 

+ 1
- 2
types.ts 查看文件

@@ -1,7 +1,5 @@
1 1
 import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
2 2
 
3
-import React from 'react'
4
-
5 3
 /* -------------------------------------------------- */
6 4
 /*                    Client State                    */
7 5
 /* -------------------------------------------------- */
@@ -53,6 +51,7 @@ export interface Page {
53 51
 }
54 52
 
55 53
 export interface PageState {
54
+  id: string
56 55
   selectedIds: Set<string>
57 56
   camera: {
58 57
     point: number[]

+ 2
- 2
utils/hitTests.ts 查看文件

@@ -1,5 +1,5 @@
1
-import { Bounds } from "types"
2
-import * as vec from "./vec"
1
+import { Bounds } from 'types'
2
+import vec from './vec'
3 3
 
4 4
 /**
5 5
  * Get whether a point is inside of a bounds.

+ 1
- 1
utils/intersections.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import { Bounds } from 'types'
2
-import * as vec from 'utils/vec'
2
+import vec from 'utils/vec'
3 3
 import { isAngleBetween } from './utils'
4 4
 
5 5
 interface Intersection {

+ 1
- 1
utils/svg.ts 查看文件

@@ -1,6 +1,6 @@
1 1
 // Some helpers for drawing SVGs.
2 2
 
3
-import * as vec from './vec'
3
+import vec from './vec'
4 4
 import { getSweep } from 'utils/utils'
5 5
 
6 6
 // General

+ 7
- 11
utils/utils.ts 查看文件

@@ -1,19 +1,15 @@
1 1
 import Vector from 'lib/code/vector'
2 2
 import React from 'react'
3
-import {
4
-  Data,
5
-  Bounds,
6
-  Edge,
7
-  Corner,
8
-  Shape,
9
-  ShapeStyles,
10
-  GroupShape,
11
-  ShapeType,
12
-} from 'types'
13
-import * as vec from './vec'
3
+import { Data, Bounds, Edge, Corner, Shape, GroupShape, ShapeType } from 'types'
4
+import { v4 as uuid } from 'uuid'
5
+import vec from './vec'
14 6
 import _isMobile from 'ismobilejs'
15 7
 import { getShapeUtils } from 'lib/shape-utils'
16 8
 
9
+export function uniqueId() {
10
+  return uuid()
11
+}
12
+
17 13
 export function screenToWorld(point: number[], data: Data) {
18 14
   const camera = getCurrentCamera(data)
19 15
   return vec.sub(vec.div(point, camera.zoom), camera.point)

+ 440
- 436
utils/vec.ts 查看文件

@@ -1,483 +1,487 @@
1
-/**
2
- * Clamp a value into a range.
3
- * @param n
4
- * @param min
5
- */
6
-export function clamp(n: number, min: number): number
7
-export function clamp(n: number, min: number, max: number): number
8
-export function clamp(n: number, min: number, max?: number): number {
9
-  return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
10
-}
1
+// A big collection of vector utilities. Collected into a class to improve logging / packaging.
2
+
3
+export default class Vec {
4
+  /**
5
+   * Clamp a value into a range.
6
+   * @param n
7
+   * @param min
8
+   */
9
+  static clamp(n: number, min: number): number
10
+  static clamp(n: number, min: number, max: number): number
11
+  static clamp(n: number, min: number, max?: number): number {
12
+    return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
13
+  }
11 14
 
12
-/**
13
- * Negate a vector.
14
- * @param A
15
- */
16
-export function neg(A: number[]) {
17
-  return [-A[0], -A[1]]
18
-}
15
+  /**
16
+   * Negate a vector.
17
+   * @param A
18
+   */
19
+  static neg = (A: number[]) => {
20
+    return [-A[0], -A[1]]
21
+  }
19 22
 
20
-/**
21
- * Add vectors.
22
- * @param A
23
- * @param B
24
- */
25
-export function add(A: number[], B: number[]) {
26
-  return [A[0] + B[0], A[1] + B[1]]
27
-}
23
+  /**
24
+   * Add vectors.
25
+   * @param A
26
+   * @param B
27
+   */
28
+  static add = (A: number[], B: number[]) => {
29
+    return [A[0] + B[0], A[1] + B[1]]
30
+  }
28 31
 
29
-/**
30
- * Add scalar to vector.
31
- * @param A
32
- * @param B
33
- */
34
-export function addScalar(A: number[], n: number) {
35
-  return [A[0] + n, A[1] + n]
36
-}
32
+  /**
33
+   * Add scalar to vector.
34
+   * @param A
35
+   * @param B
36
+   */
37
+  static addScalar = (A: number[], n: number) => {
38
+    return [A[0] + n, A[1] + n]
39
+  }
37 40
 
38
-/**
39
- * Subtract vectors.
40
- * @param A
41
- * @param B
42
- */
43
-export function sub(A: number[], B: number[]) {
44
-  return [A[0] - B[0], A[1] - B[1]]
45
-}
41
+  /**
42
+   * Subtract vectors.
43
+   * @param A
44
+   * @param B
45
+   */
46
+  static sub = (A: number[], B: number[]) => {
47
+    return [A[0] - B[0], A[1] - B[1]]
48
+  }
46 49
 
47
-/**
48
- * Subtract scalar from vector.
49
- * @param A
50
- * @param B
51
- */
52
-export function subScalar(A: number[], n: number) {
53
-  return [A[0] - n, A[1] - n]
54
-}
50
+  /**
51
+   * Subtract scalar from vector.
52
+   * @param A
53
+   * @param B
54
+   */
55
+  static subScalar = (A: number[], n: number) => {
56
+    return [A[0] - n, A[1] - n]
57
+  }
55 58
 
56
-/**
57
- * Get the vector from vectors A to B.
58
- * @param A
59
- * @param B
60
- */
61
-export function vec(A: number[], B: number[]) {
62
-  // A, B as vectors get the vector from A to B
63
-  return [B[0] - A[0], B[1] - A[1]]
64
-}
59
+  /**
60
+   * Get the vector from vectors A to B.
61
+   * @param A
62
+   * @param B
63
+   */
64
+  static vec = (A: number[], B: number[]) => {
65
+    // A, B as vectors get the vector from A to B
66
+    return [B[0] - A[0], B[1] - A[1]]
67
+  }
65 68
 
66
-/**
67
- * Vector multiplication by scalar
68
- * @param A
69
- * @param n
70
- */
71
-export function mul(A: number[], n: number) {
72
-  return [A[0] * n, A[1] * n]
73
-}
69
+  /**
70
+   * Vector multiplication by scalar
71
+   * @param A
72
+   * @param n
73
+   */
74
+  static mul = (A: number[], n: number) => {
75
+    return [A[0] * n, A[1] * n]
76
+  }
74 77
 
75
-export function mulV(A: number[], B: number[]) {
76
-  return [A[0] * B[0], A[1] * B[1]]
77
-}
78
+  static mulV = (A: number[], B: number[]) => {
79
+    return [A[0] * B[0], A[1] * B[1]]
80
+  }
78 81
 
79
-/**
80
- * Vector division by scalar.
81
- * @param A
82
- * @param n
83
- */
84
-export function div(A: number[], n: number) {
85
-  return [A[0] / n, A[1] / n]
86
-}
82
+  /**
83
+   * Vector division by scalar.
84
+   * @param A
85
+   * @param n
86
+   */
87
+  static div = (A: number[], n: number) => {
88
+    return [A[0] / n, A[1] / n]
89
+  }
87 90
 
88
-/**
89
- * Vector division by vector.
90
- * @param A
91
- * @param n
92
- */
93
-export function divV(A: number[], B: number[]) {
94
-  return [A[0] / B[0], A[1] / B[1]]
95
-}
91
+  /**
92
+   * Vector division by vector.
93
+   * @param A
94
+   * @param n
95
+   */
96
+  static divV = (A: number[], B: number[]) => {
97
+    return [A[0] / B[0], A[1] / B[1]]
98
+  }
96 99
 
97
-/**
98
- * Perpendicular rotation of a vector A
99
- * @param A
100
- */
101
-export function per(A: number[]) {
102
-  return [A[1], -A[0]]
103
-}
100
+  /**
101
+   * Perpendicular rotation of a vector A
102
+   * @param A
103
+   */
104
+  static per(A: number[]) {
105
+    return [A[1], -A[0]]
106
+  }
104 107
 
105
-/**
106
- * Dot product
107
- * @param A
108
- * @param B
109
- */
110
-export function dpr(A: number[], B: number[]) {
111
-  return A[0] * B[0] + A[1] * B[1]
112
-}
108
+  /**
109
+   * Dot product
110
+   * @param A
111
+   * @param B
112
+   */
113
+  static dpr = (A: number[], B: number[]) => {
114
+    return A[0] * B[0] + A[1] * B[1]
115
+  }
113 116
 
114
-/**
115
- * Cross product (outer product) | A X B |
116
- * @param A
117
- * @param B
118
- */
119
-export function cpr(A: number[], B: number[]) {
120
-  return A[0] * B[1] - B[0] * A[1]
121
-}
117
+  /**
118
+   * Cross product (outer product) | A X B |
119
+   * @param A
120
+   * @param B
121
+   */
122
+  static cpr = (A: number[], B: number[]) => {
123
+    return A[0] * B[1] - B[0] * A[1]
124
+  }
122 125
 
123
-/**
124
- * Length of the vector squared
125
- * @param A
126
- */
127
-export function len2(A: number[]) {
128
-  return A[0] * A[0] + A[1] * A[1]
129
-}
126
+  /**
127
+   * Length of the vector squared
128
+   * @param A
129
+   */
130
+  static len2 = (A: number[]) => {
131
+    return A[0] * A[0] + A[1] * A[1]
132
+  }
130 133
 
131
-/**
132
- * Length of the vector
133
- * @param A
134
- */
135
-export function len(A: number[]) {
136
-  return Math.hypot(A[0], A[1])
137
-}
134
+  /**
135
+   * Length of the vector
136
+   * @param A
137
+   */
138
+  static len = (A: number[]) => {
139
+    return Math.hypot(A[0], A[1])
140
+  }
138 141
 
139
-/**
140
- * Project A over B
141
- * @param A
142
- * @param B
143
- */
144
-export function pry(A: number[], B: number[]) {
145
-  return dpr(A, B) / len(B)
146
-}
142
+  /**
143
+   * Project A over B
144
+   * @param A
145
+   * @param B
146
+   */
147
+  static pry = (A: number[], B: number[]) => {
148
+    return Vec.dpr(A, B) / Vec.len(B)
149
+  }
147 150
 
148
-/**
149
- * Get normalized / unit vector.
150
- * @param A
151
- */
152
-export function uni(A: number[]) {
153
-  return div(A, len(A))
154
-}
151
+  /**
152
+   * Get normalized / unit vector.
153
+   * @param A
154
+   */
155
+  static uni = (A: number[]) => {
156
+    return Vec.div(A, Vec.len(A))
157
+  }
155 158
 
156
-/**
157
- * Get normalized / unit vector.
158
- * @param A
159
- */
160
-export function normalize(A: number[]) {
161
-  return uni(A)
162
-}
159
+  /**
160
+   * Get normalized / unit vector.
161
+   * @param A
162
+   */
163
+  static normalize = (A: number[]) => {
164
+    return Vec.uni(A)
165
+  }
163 166
 
164
-/**
165
- * Get the tangent between two vectors.
166
- * @param A
167
- * @param B
168
- * @returns
169
- */
170
-export function tangent(A: number[], B: number[]) {
171
-  return normalize(sub(A, B))
172
-}
167
+  /**
168
+   * Get the tangent between two vectors.
169
+   * @param A
170
+   * @param B
171
+   * @returns
172
+   */
173
+  static tangent = (A: number[], B: number[]) => {
174
+    return Vec.normalize(Vec.sub(A, B))
175
+  }
173 176
 
174
-/**
175
- * Dist length from A to B squared.
176
- * @param A
177
- * @param B
178
- */
179
-export function dist2(A: number[], B: number[]) {
180
-  return len2(sub(A, B))
181
-}
177
+  /**
178
+   * Dist length from A to B squared.
179
+   * @param A
180
+   * @param B
181
+   */
182
+  static dist2 = (A: number[], B: number[]) => {
183
+    return Vec.len2(Vec.sub(A, B))
184
+  }
182 185
 
183
-/**
184
- * Dist length from A to B
185
- * @param A
186
- * @param B
187
- */
188
-export function dist(A: number[], B: number[]) {
189
-  return Math.hypot(A[1] - B[1], A[0] - B[0])
190
-}
186
+  /**
187
+   * Dist length from A to B
188
+   * @param A
189
+   * @param B
190
+   */
191
+  static dist = (A: number[], B: number[]) => {
192
+    return Math.hypot(A[1] - B[1], A[0] - B[0])
193
+  }
191 194
 
192
-/**
193
- * A faster, though less accurate method for testing distances. Maybe faster?
194
- * @param A
195
- * @param B
196
- * @returns
197
- */
198
-export function fastDist(A: number[], B: number[]) {
199
-  const V = [B[0] - A[0], B[1] - A[1]]
200
-  const aV = [Math.abs(V[0]), Math.abs(V[1])]
201
-  let r = 1 / Math.max(aV[0], aV[1])
202
-  r = r * (1.29289 - (aV[0] + aV[1]) * r * 0.29289)
203
-  return [V[0] * r, V[1] * r]
204
-}
195
+  /**
196
+   * A faster, though less accurate method for testing distances. Maybe faster?
197
+   * @param A
198
+   * @param B
199
+   * @returns
200
+   */
201
+  static fastDist = (A: number[], B: number[]) => {
202
+    const V = [B[0] - A[0], B[1] - A[1]]
203
+    const aV = [Math.abs(V[0]), Math.abs(V[1])]
204
+    let r = 1 / Math.max(aV[0], aV[1])
205
+    r = r * (1.29289 - (aV[0] + aV[1]) * r * 0.29289)
206
+    return [V[0] * r, V[1] * r]
207
+  }
205 208
 
206
-/**
207
- * Angle between vector A and vector B in radians
208
- * @param A
209
- * @param B
210
- */
211
-export function ang(A: number[], B: number[]) {
212
-  return Math.atan2(cpr(A, B), dpr(A, B))
213
-}
209
+  /**
210
+   * Angle between vector A and vector B in radians
211
+   * @param A
212
+   * @param B
213
+   */
214
+  static ang = (A: number[], B: number[]) => {
215
+    return Math.atan2(Vec.cpr(A, B), Vec.dpr(A, B))
216
+  }
214 217
 
215
-/**
216
- * Angle between vector A and vector B in radians
217
- * @param A
218
- * @param B
219
- */
220
-export function angle(A: number[], B: number[]) {
221
-  return Math.atan2(B[1] - A[1], B[0] - A[0])
222
-}
218
+  /**
219
+   * Angle between vector A and vector B in radians
220
+   * @param A
221
+   * @param B
222
+   */
223
+  static angle = (A: number[], B: number[]) => {
224
+    return Math.atan2(B[1] - A[1], B[0] - A[0])
225
+  }
223 226
 
224
-/**
225
- * Mean between two vectors or mid vector between two vectors
226
- * @param A
227
- * @param B
228
- */
229
-export function med(A: number[], B: number[]) {
230
-  return mul(add(A, B), 0.5)
231
-}
227
+  /**
228
+   * Mean between two vectors or mid vector between two vectors
229
+   * @param A
230
+   * @param B
231
+   */
232
+  static med = (A: number[], B: number[]) => {
233
+    return Vec.mul(Vec.add(A, B), 0.5)
234
+  }
232 235
 
233
-/**
234
- * Vector rotation by r (radians)
235
- * @param A
236
- * @param r rotation in radians
237
- */
238
-export function rot(A: number[], r: number) {
239
-  return [
240
-    A[0] * Math.cos(r) - A[1] * Math.sin(r),
241
-    A[0] * Math.sin(r) + A[1] * Math.cos(r),
242
-  ]
243
-}
236
+  /**
237
+   * Vector rotation by r (radians)
238
+   * @param A
239
+   * @param r rotation in radians
240
+   */
241
+  static rot = (A: number[], r: number) => {
242
+    return [
243
+      A[0] * Math.cos(r) - A[1] * Math.sin(r),
244
+      A[0] * Math.sin(r) + A[1] * Math.cos(r),
245
+    ]
246
+  }
244 247
 
245
-/**
246
- * Rotate a vector around another vector by r (radians)
247
- * @param A vector
248
- * @param C center
249
- * @param r rotation in radians
250
- */
251
-export function rotWith(A: number[], C: number[], r: number) {
252
-  if (r === 0) return A
248
+  /**
249
+   * Rotate a vector around another vector by r (radians)
250
+   * @param A vector
251
+   * @param C center
252
+   * @param r rotation in radians
253
+   */
254
+  static rotWith = (A: number[], C: number[], r: number) => {
255
+    if (r === 0) return A
253 256
 
254
-  const s = Math.sin(r)
255
-  const c = Math.cos(r)
257
+    const s = Math.sin(r)
258
+    const c = Math.cos(r)
256 259
 
257
-  const px = A[0] - C[0]
258
-  const py = A[1] - C[1]
260
+    const px = A[0] - C[0]
261
+    const py = A[1] - C[1]
259 262
 
260
-  const nx = px * c - py * s
261
-  const ny = px * s + py * c
263
+    const nx = px * c - py * s
264
+    const ny = px * s + py * c
262 265
 
263
-  return [nx + C[0], ny + C[1]]
264
-}
266
+    return [nx + C[0], ny + C[1]]
267
+  }
265 268
 
266
-/**
267
- * Check of two vectors are identical.
268
- * @param A
269
- * @param B
270
- */
271
-export function isEqual(A: number[], B: number[]) {
272
-  return A[0] === B[0] && A[1] === B[1]
273
-}
269
+  /**
270
+   * Check of two vectors are identical.
271
+   * @param A
272
+   * @param B
273
+   */
274
+  static isEqual = (A: number[], B: number[]) => {
275
+    return A[0] === B[0] && A[1] === B[1]
276
+  }
274 277
 
275
-/**
276
- * Interpolate vector A to B with a scalar t
277
- * @param A
278
- * @param B
279
- * @param t scalar
280
- */
281
-export function lrp(A: number[], B: number[], t: number) {
282
-  return add(A, mul(vec(A, B), t))
283
-}
278
+  /**
279
+   * Interpolate vector A to B with a scalar t
280
+   * @param A
281
+   * @param B
282
+   * @param t scalar
283
+   */
284
+  static lrp = (A: number[], B: number[], t: number) => {
285
+    return Vec.add(A, Vec.mul(Vec.vec(A, B), t))
286
+  }
284 287
 
285
-/**
286
- * Interpolate from A to B when curVAL goes fromVAL => to
287
- * @param A
288
- * @param B
289
- * @param from Starting value
290
- * @param to Ending value
291
- * @param s Strength
292
- */
293
-export function int(A: number[], B: number[], from: number, to: number, s = 1) {
294
-  const t = (clamp(from, to) - from) / (to - from)
295
-  return add(mul(A, 1 - t), mul(B, s))
296
-}
288
+  /**
289
+   * Interpolate from A to B when curVAL goes fromVAL => to
290
+   * @param A
291
+   * @param B
292
+   * @param from Starting value
293
+   * @param to Ending value
294
+   * @param s Strength
295
+   */
296
+  static int = (A: number[], B: number[], from: number, to: number, s = 1) => {
297
+    const t = (Vec.clamp(from, to) - from) / (to - from)
298
+    return Vec.add(Vec.mul(A, 1 - t), Vec.mul(B, s))
299
+  }
297 300
 
298
-/**
299
- * Get the angle between the three vectors A, B, and C.
300
- * @param p1
301
- * @param pc
302
- * @param p2
303
- */
304
-export function ang3(p1: number[], pc: number[], p2: number[]) {
305
-  // this,
306
-  const v1 = vec(pc, p1)
307
-  const v2 = vec(pc, p2)
308
-  return ang(v1, v2)
309
-}
301
+  /**
302
+   * Get the angle between the three vectors A, B, and C.
303
+   * @param p1
304
+   * @param pc
305
+   * @param p2
306
+   */
307
+  static ang3 = (p1: number[], pc: number[], p2: number[]) => {
308
+    // this,
309
+    const v1 = Vec.vec(pc, p1)
310
+    const v2 = Vec.vec(pc, p2)
311
+    return Vec.ang(v1, v2)
312
+  }
310 313
 
311
-/**
312
- * Absolute value of a vector.
313
- * @param A
314
- * @returns
315
- */
316
-export function abs(A: number[]) {
317
-  return [Math.abs(A[0]), Math.abs(A[1])]
318
-}
314
+  /**
315
+   * Absolute value of a vector.
316
+   * @param A
317
+   * @returns
318
+   */
319
+  static abs = (A: number[]) => {
320
+    return [Math.abs(A[0]), Math.abs(A[1])]
321
+  }
319 322
 
320
-export function rescale(a: number[], n: number) {
321
-  const l = len(a)
322
-  return [(n * a[0]) / l, (n * a[1]) / l]
323
-}
323
+  static rescale = (a: number[], n: number) => {
324
+    const l = Vec.len(a)
325
+    return [(n * a[0]) / l, (n * a[1]) / l]
326
+  }
324 327
 
325
-/**
326
- * Get whether p1 is left of p2, relative to pc.
327
- * @param p1
328
- * @param pc
329
- * @param p2
330
- */
331
-export function isLeft(p1: number[], pc: number[], p2: number[]) {
332
-  //  isLeft: >0 for counterclockwise
333
-  //          =0 for none (degenerate)
334
-  //          <0 for clockwise
335
-  return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])
336
-}
328
+  /**
329
+   * Get whether p1 is left of p2, relative to pc.
330
+   * @param p1
331
+   * @param pc
332
+   * @param p2
333
+   */
334
+  static isLeft = (p1: number[], pc: number[], p2: number[]) => {
335
+    //  isLeft: >0 for counterclockwise
336
+    //          =0 for none (degenerate)
337
+    //          <0 for clockwise
338
+    return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])
339
+  }
337 340
 
338
-export function clockwise(p1: number[], pc: number[], p2: number[]) {
339
-  return isLeft(p1, pc, p2) > 0
340
-}
341
+  static clockwise = (p1: number[], pc: number[], p2: number[]) => {
342
+    return Vec.isLeft(p1, pc, p2) > 0
343
+  }
341 344
 
342
-export function round(a: number[], d = 5) {
343
-  return a.map((v) => Number(v.toPrecision(d)))
344
-}
345
+  static round = (a: number[], d = 5) => {
346
+    return a.map((v) => Number(v.toPrecision(d)))
347
+  }
345 348
 
346
-/**
347
- * Get the minimum distance from a point P to a line with a segment AB.
348
- * @param A The start of the line.
349
- * @param B The end of the line.
350
- * @param P A point.
351
- * @returns
352
- */
353
-// export function distanceToLine(A: number[], B: number[], P: number[]) {
354
-//   const delta = sub(B, A)
355
-//   const angle = Math.atan2(delta[1], delta[0])
356
-//   const dir = rot(sub(P, A), -angle)
357
-//   return dir[1]
358
-// }
359
-
360
-/**
361
- * Get the nearest point on a line segment AB.
362
- * @param A The start of the line.
363
- * @param B The end of the line.
364
- * @param P A point.
365
- * @param clamp Whether to clamp the resulting point to the segment.
366
- * @returns
367
- */
368
-// export function nearestPointOnLine(
369
-//   A: number[],
370
-//   B: number[],
371
-//   P: number[],
372
-//   clamp = true
373
-// ) {
374
-//   const delta = sub(B, A)
375
-//   const length = len(delta)
376
-//   const angle = Math.atan2(delta[1], delta[0])
377
-//   const dir = rot(sub(P, A), -angle)
378
-
379
-//   if (clamp) {
380
-//     if (dir[0] < 0) return A
381
-//     if (dir[0] > length) return B
382
-//   }
383
-
384
-//   return add(A, div(mul(delta, dir[0]), length))
385
-// }
386
-
387
-/**
388
- * Get the nearest point on a line with a known unit vector that passes through point A
389
- * @param A Any point on the line
390
- * @param u The unit vector for the line.
391
- * @param P A point not on the line to test.
392
- * @returns
393
- */
394
-export function nearestPointOnLineThroughPoint(
395
-  A: number[],
396
-  u: number[],
397
-  P: number[]
398
-) {
399
-  return add(A, mul(u, pry(sub(P, A), u)))
400
-}
349
+  /**
350
+   * Get the minimum distance from a point P to a line with a segment AB.
351
+   * @param A The start of the line.
352
+   * @param B The end of the line.
353
+   * @param P A point.
354
+   * @returns
355
+   */
356
+  // static distanceToLine(A: number[], B: number[], P: number[]) {
357
+  //   const delta = sub(B, A)
358
+  //   const angle = Math.atan2(delta[1], delta[0])
359
+  //   const dir = rot(sub(P, A), -angle)
360
+  //   return dir[1]
361
+  // }
362
+
363
+  /**
364
+   * Get the nearest point on a line segment AB.
365
+   * @param A The start of the line.
366
+   * @param B The end of the line.
367
+   * @param P A point.
368
+   * @param clamp Whether to clamp the resulting point to the segment.
369
+   * @returns
370
+   */
371
+  // static nearestPointOnLine(
372
+  //   A: number[],
373
+  //   B: number[],
374
+  //   P: number[],
375
+  //   clamp = true
376
+  // ) {
377
+  //   const delta = sub(B, A)
378
+  //   const length = len(delta)
379
+  //   const angle = Math.atan2(delta[1], delta[0])
380
+  //   const dir = rot(sub(P, A), -angle)
381
+
382
+  //   if (clamp) {
383
+  //     if (dir[0] < 0) return A
384
+  //     if (dir[0] > length) return B
385
+  //   }
386
+
387
+  //   return add(A, div(mul(delta, dir[0]), length))
388
+  // }
389
+
390
+  /**
391
+   * Get the nearest point on a line with a known unit vector that passes through point A
392
+   * @param A Any point on the line
393
+   * @param u The unit vector for the line.
394
+   * @param P A point not on the line to test.
395
+   * @returns
396
+   */
397
+  static nearestPointOnLineThroughPoint = (
398
+    A: number[],
399
+    u: number[],
400
+    P: number[]
401
+  ) => {
402
+    return Vec.add(A, Vec.mul(u, Vec.pry(Vec.sub(P, A), u)))
403
+  }
401 404
 
402
-/**
403
- * Distance between a point and a line with a known unit vector that passes through a point.
404
- * @param A Any point on the line
405
- * @param u The unit vector for the line.
406
- * @param P A point not on the line to test.
407
- * @returns
408
- */
409
-export function distanceToLineThroughPoint(
410
-  A: number[],
411
-  u: number[],
412
-  P: number[]
413
-) {
414
-  return dist(P, nearestPointOnLineThroughPoint(A, u, P))
415
-}
405
+  /**
406
+   * Distance between a point and a line with a known unit vector that passes through a point.
407
+   * @param A Any point on the line
408
+   * @param u The unit vector for the line.
409
+   * @param P A point not on the line to test.
410
+   * @returns
411
+   */
412
+  static distanceToLineThroughPoint = (
413
+    A: number[],
414
+    u: number[],
415
+    P: number[]
416
+  ) => {
417
+    return Vec.dist(P, Vec.nearestPointOnLineThroughPoint(A, u, P))
418
+  }
416 419
 
417
-/**
418
- * Get the nearest point on a line segment between A and B
419
- * @param A The start of the line segment
420
- * @param B The end of the line segment
421
- * @param P The off-line point
422
- * @param clamp Whether to clamp the point between A and B.
423
- * @returns
424
- */
425
-export function nearestPointOnLineSegment(
426
-  A: number[],
427
-  B: number[],
428
-  P: number[],
429
-  clamp = true
430
-) {
431
-  const delta = sub(B, A)
432
-  const length = len(delta)
433
-  const u = div(delta, length)
434
-
435
-  const pt = add(A, mul(u, pry(sub(P, A), u)))
436
-
437
-  if (clamp) {
438
-    const da = dist(A, pt)
439
-    const db = dist(B, pt)
440
-
441
-    if (db < da && da > length) return B
442
-    if (da < db && db > length) return A
443
-  }
444
-
445
-  return pt
446
-}
420
+  /**
421
+   * Get the nearest point on a line segment between A and B
422
+   * @param A The start of the line segment
423
+   * @param B The end of the line segment
424
+   * @param P The off-line point
425
+   * @param clamp Whether to clamp the point between A and B.
426
+   * @returns
427
+   */
428
+  static nearestPointOnLineSegment = (
429
+    A: number[],
430
+    B: number[],
431
+    P: number[],
432
+    clamp = true
433
+  ) => {
434
+    const delta = Vec.sub(B, A)
435
+    const length = Vec.len(delta)
436
+    const u = Vec.div(delta, length)
437
+
438
+    const pt = Vec.add(A, Vec.mul(u, Vec.pry(Vec.sub(P, A), u)))
439
+
440
+    if (clamp) {
441
+      const da = Vec.dist(A, pt)
442
+      const db = Vec.dist(B, pt)
443
+
444
+      if (db < da && da > length) return B
445
+      if (da < db && db > length) return A
446
+    }
447
+
448
+    return pt
449
+  }
447 450
 
448
-/**
449
- * Distance between a point and the nearest point on a line segment between A and B
450
- * @param A The start of the line segment
451
- * @param B The end of the line segment
452
- * @param P The off-line point
453
- * @param clamp Whether to clamp the point between A and B.
454
- * @returns
455
- */
456
-export function distanceToLineSegment(
457
-  A: number[],
458
-  B: number[],
459
-  P: number[],
460
-  clamp = true
461
-) {
462
-  return dist(P, nearestPointOnLineSegment(A, B, P, clamp))
463
-}
451
+  /**
452
+   * Distance between a point and the nearest point on a line segment between A and B
453
+   * @param A The start of the line segment
454
+   * @param B The end of the line segment
455
+   * @param P The off-line point
456
+   * @param clamp Whether to clamp the point between A and B.
457
+   * @returns
458
+   */
459
+  static distanceToLineSegment = (
460
+    A: number[],
461
+    B: number[],
462
+    P: number[],
463
+    clamp = true
464
+  ) => {
465
+    return Vec.dist(P, Vec.nearestPointOnLineSegment(A, B, P, clamp))
466
+  }
464 467
 
465
-/**
466
- * Get a vector d distance from A towards B.
467
- * @param A
468
- * @param B
469
- * @param d
470
- * @returns
471
- */
472
-export function nudge(A: number[], B: number[], d: number) {
473
-  return add(A, mul(uni(vec(A, B)), d))
474
-}
468
+  /**
469
+   * Get a vector d distance from A towards B.
470
+   * @param A
471
+   * @param B
472
+   * @param d
473
+   * @returns
474
+   */
475
+  static nudge = (A: number[], B: number[], d: number) => {
476
+    return Vec.add(A, Vec.mul(Vec.uni(Vec.vec(A, B)), d))
477
+  }
475 478
 
476
-/**
477
- * Round a vector to a precision length.
478
- * @param a
479
- * @param n
480
- */
481
-export function toPrecision(a: number[], n = 4) {
482
-  return [+a[0].toPrecision(n), +a[1].toPrecision(n)]
479
+  /**
480
+   * Round a vector to a precision length.
481
+   * @param a
482
+   * @param n
483
+   */
484
+  static toPrecision = (a: number[], n = 4) => {
485
+    return [+a[0].toPrecision(n), +a[1].toPrecision(n)]
486
+  }
483 487
 }

+ 154
- 144
yarn.lock 查看文件

@@ -1573,38 +1573,40 @@
1573 1573
   dependencies:
1574 1574
     "@babel/runtime" "^7.13.10"
1575 1575
 
1576
-"@radix-ui/react-arrow@0.0.13":
1577
-  version "0.0.13"
1578
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.0.13.tgz#06b5a17f33bbc1af1c88d2827f25f37be95daa38"
1579
-  integrity sha512-BHBAULgQYmj36BrJ+1AGhC5p4QjJaE+szJgJ1a1EYOM3G6QOeIQKYvIm8TPEdKAiJhAivK+jZFccsE4Blzqc9g==
1576
+"@radix-ui/react-arrow@0.0.14":
1577
+  version "0.0.14"
1578
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.0.14.tgz#70b2c66efbf3cde0c9dd0895417e39f6cdf31805"
1579
+  integrity sha512-ZWfQM3hTCXN6ub2dMUmMv2ttEB/1zuBlOZdeWFeCCJHwZ+yy7iPzWF6ixaNygBa6t451PImq/dC7sqOnDJCfqQ==
1580 1580
   dependencies:
1581 1581
     "@babel/runtime" "^7.13.10"
1582
-    "@radix-ui/react-polymorphic" "0.0.11"
1583
-    "@radix-ui/react-primitive" "0.0.13"
1582
+    "@radix-ui/react-polymorphic" "0.0.12"
1583
+    "@radix-ui/react-primitive" "0.0.14"
1584 1584
 
1585
-"@radix-ui/react-checkbox@^0.0.15":
1586
-  version "0.0.15"
1587
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.0.15.tgz#d53b56854fbba65e74ed4486116107638951b9d1"
1588
-  integrity sha512-R8ErERPlu2kvmqNjxRyyLcS1y3D7J2bQUUEPsvP0BL2AfisUjbT7c9t19k2K/Un3Iieqe93gTPG4LRdbDQQjBw==
1585
+"@radix-ui/react-checkbox@^0.0.16":
1586
+  version "0.0.16"
1587
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.0.16.tgz#503a934f73745e9c3831a5fd39d3b06062813f0e"
1588
+  integrity sha512-2RJep0OB4FZvGk/PTou8BEt/cISA/i88iiloZuAxlOjYgF90W4xfrQqgczLCC64t0ee3DtUkSOJyjO45M95hVg==
1589 1589
   dependencies:
1590 1590
     "@babel/runtime" "^7.13.10"
1591 1591
     "@radix-ui/primitive" "0.0.5"
1592 1592
     "@radix-ui/react-compose-refs" "0.0.5"
1593 1593
     "@radix-ui/react-context" "0.0.5"
1594
-    "@radix-ui/react-label" "0.0.13"
1595
-    "@radix-ui/react-polymorphic" "0.0.11"
1594
+    "@radix-ui/react-label" "0.0.14"
1595
+    "@radix-ui/react-polymorphic" "0.0.12"
1596 1596
     "@radix-ui/react-presence" "0.0.14"
1597
-    "@radix-ui/react-primitive" "0.0.13"
1597
+    "@radix-ui/react-primitive" "0.0.14"
1598 1598
     "@radix-ui/react-use-controllable-state" "0.0.6"
1599
+    "@radix-ui/react-use-previous" "0.0.5"
1600
+    "@radix-ui/react-use-size" "0.0.6"
1599 1601
 
1600
-"@radix-ui/react-collection@0.0.12":
1601
-  version "0.0.12"
1602
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.0.12.tgz#5cd09312cdec34fdbbe1d31affaba69eb768e342"
1603
-  integrity sha512-jUP99/wCHpXm7ytbmpcjhhxFHBsr2lptEzKO8DUkD2CrJBS4Q3XHMKBDOvNQQzIqJhsz0A5JP6Fo0Vgd8Ld3FQ==
1602
+"@radix-ui/react-collection@0.0.13":
1603
+  version "0.0.13"
1604
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.0.13.tgz#050fc9b1d84b58ab04a5852812b181767dde2e85"
1605
+  integrity sha512-TKUUFLeRlxp+1S1FQz9pQkP1VbRwZGOEH8oUdgWjUsNc+GVURlS3H5twsDtSAAfvebOc6YmDTE23mvvhfsPhnw==
1604 1606
   dependencies:
1605 1607
     "@babel/runtime" "^7.13.10"
1606 1608
     "@radix-ui/react-compose-refs" "0.0.5"
1607
-    "@radix-ui/react-slot" "0.0.10"
1609
+    "@radix-ui/react-slot" "0.0.11"
1608 1610
 
1609 1611
 "@radix-ui/react-compose-refs@0.0.5":
1610 1612
   version "0.0.5"
@@ -1613,17 +1615,17 @@
1613 1615
   dependencies:
1614 1616
     "@babel/runtime" "^7.13.10"
1615 1617
 
1616
-"@radix-ui/react-context-menu@^0.0.19":
1617
-  version "0.0.19"
1618
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.0.19.tgz#47ef194d7bc925ff7f998889be9fd373ce4bd9a1"
1619
-  integrity sha512-FR2jXeFqxD9n+1AC81+7jfMbnz80kdQNMfgIcPQL1S0M5SODADdDiTKKfok4Blc1nYWe7nEwBTYauMirF7avSQ==
1618
+"@radix-ui/react-context-menu@^0.0.21":
1619
+  version "0.0.21"
1620
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.0.21.tgz#b080c8e515b9a0d18fcda9b02f34b7a94347ef08"
1621
+  integrity sha512-snRhgVDtdxBNwds2ULd7RQAB97PfSeKlrL5rRudlj4YTlLq7y/Jr0oJ7FTnu9EylfH2ii0uKcW5dEVhrbzr5YQ==
1620 1622
   dependencies:
1621 1623
     "@babel/runtime" "^7.13.10"
1622 1624
     "@radix-ui/primitive" "0.0.5"
1623 1625
     "@radix-ui/react-context" "0.0.5"
1624
-    "@radix-ui/react-menu" "0.0.18"
1625
-    "@radix-ui/react-polymorphic" "0.0.11"
1626
-    "@radix-ui/react-primitive" "0.0.13"
1626
+    "@radix-ui/react-menu" "0.0.19"
1627
+    "@radix-ui/react-polymorphic" "0.0.12"
1628
+    "@radix-ui/react-primitive" "0.0.14"
1627 1629
     "@radix-ui/react-use-callback-ref" "0.0.5"
1628 1630
 
1629 1631
 "@radix-ui/react-context@0.0.5":
@@ -1633,50 +1635,54 @@
1633 1635
   dependencies:
1634 1636
     "@babel/runtime" "^7.13.10"
1635 1637
 
1636
-"@radix-ui/react-dialog@^0.0.17":
1637
-  version "0.0.17"
1638
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.17.tgz#736e37626c4e9e20d46546ddbbb8869a352578f0"
1639
-  integrity sha512-DOSW8SdniyVLro+MvF0owaEEa8MUYGMuvuuSQpFnD/hA+K0pKzyTSjEuC7OeflPCImBFEbmKhgRoeWypfMZZOA==
1638
+"@radix-ui/react-dialog@^0.0.18":
1639
+  version "0.0.18"
1640
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.18.tgz#8d2d9f8816bb8447056031583a3a3cb2b6305281"
1641
+  integrity sha512-EH8yxFh3hQQ/hIPQsBzdJgx3oWTEmLu2a2x2PfRjxbDhcDIjcYJWdeEMjkTUjkBwpz3h6L/JWqnYJ2dqA65Deg==
1640 1642
   dependencies:
1641 1643
     "@babel/runtime" "^7.13.10"
1642 1644
     "@radix-ui/primitive" "0.0.5"
1643 1645
     "@radix-ui/react-compose-refs" "0.0.5"
1644 1646
     "@radix-ui/react-context" "0.0.5"
1645
-    "@radix-ui/react-dismissable-layer" "0.0.13"
1647
+    "@radix-ui/react-dismissable-layer" "0.0.14"
1646 1648
     "@radix-ui/react-focus-guards" "0.0.7"
1647
-    "@radix-ui/react-focus-scope" "0.0.13"
1649
+    "@radix-ui/react-focus-scope" "0.0.14"
1648 1650
     "@radix-ui/react-id" "0.0.6"
1649
-    "@radix-ui/react-polymorphic" "0.0.11"
1650
-    "@radix-ui/react-portal" "0.0.13"
1651
+    "@radix-ui/react-polymorphic" "0.0.12"
1652
+    "@radix-ui/react-portal" "0.0.14"
1651 1653
     "@radix-ui/react-presence" "0.0.14"
1652
-    "@radix-ui/react-primitive" "0.0.13"
1654
+    "@radix-ui/react-primitive" "0.0.14"
1655
+    "@radix-ui/react-slot" "0.0.11"
1653 1656
     "@radix-ui/react-use-controllable-state" "0.0.6"
1654 1657
     aria-hidden "^1.1.1"
1655 1658
     react-remove-scroll "^2.4.0"
1656 1659
 
1657
-"@radix-ui/react-dismissable-layer@0.0.13":
1658
-  version "0.0.13"
1659
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.13.tgz#7c4be6170a14d8a66c48680a8a8c987bc29bcf05"
1660
-  integrity sha512-g0zhxdZzCJhVeRumaU8ODptRFhYorSRPsLwD30sz9slYaW0yg6lHvMQicRNtgFX0WnsbmrUVY6NMrwWpSHJXbg==
1660
+"@radix-ui/react-dismissable-layer@0.0.14":
1661
+  version "0.0.14"
1662
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.14.tgz#9d8a3415a2830688070c6596dec18b43c33df7b2"
1663
+  integrity sha512-0pmRuGYYvWlEaED1igGFLjic0+hD0OqvsnrZaN3n1nDOkoCd7H5CA2geaShSrlBF5riI2Dr9jIZPGLbDRhs4DA==
1661 1664
   dependencies:
1662 1665
     "@babel/runtime" "^7.13.10"
1666
+    "@radix-ui/primitive" "0.0.5"
1667
+    "@radix-ui/react-polymorphic" "0.0.12"
1668
+    "@radix-ui/react-primitive" "0.0.14"
1663 1669
     "@radix-ui/react-use-body-pointer-events" "0.0.6"
1664 1670
     "@radix-ui/react-use-callback-ref" "0.0.5"
1665 1671
     "@radix-ui/react-use-escape-keydown" "0.0.6"
1666 1672
 
1667
-"@radix-ui/react-dropdown-menu@^0.0.19":
1668
-  version "0.0.19"
1669
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.0.19.tgz#fbc3106bcda65d3060b280f4af3a9c45324ab474"
1670
-  integrity sha512-7UhT6PIXl9fr8Ai9DkXMtxPNQFh8emrklgY5jcad9NHKWAztgfL6Fm+Ns0GEYUkd5OpJLEaIj/EUkwFM73SrGw==
1673
+"@radix-ui/react-dropdown-menu@^0.0.20":
1674
+  version "0.0.20"
1675
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.0.20.tgz#ca4667deb38ff7b74e6741aac1cebd1b50e3ec07"
1676
+  integrity sha512-3MXtFeNkRZxAA32gvAGBdzlmJRMCNgBKWy0rDSd5NNzRjtqNFKZRR5dd1gWQTiS24aLxSiztVc9exnvMx4Lu8g==
1671 1677
   dependencies:
1672 1678
     "@babel/runtime" "^7.13.10"
1673 1679
     "@radix-ui/primitive" "0.0.5"
1674 1680
     "@radix-ui/react-compose-refs" "0.0.5"
1675 1681
     "@radix-ui/react-context" "0.0.5"
1676 1682
     "@radix-ui/react-id" "0.0.6"
1677
-    "@radix-ui/react-menu" "0.0.18"
1678
-    "@radix-ui/react-polymorphic" "0.0.11"
1679
-    "@radix-ui/react-primitive" "0.0.13"
1683
+    "@radix-ui/react-menu" "0.0.19"
1684
+    "@radix-ui/react-polymorphic" "0.0.12"
1685
+    "@radix-ui/react-primitive" "0.0.14"
1680 1686
     "@radix-ui/react-use-controllable-state" "0.0.6"
1681 1687
 
1682 1688
 "@radix-ui/react-focus-guards@0.0.7":
@@ -1686,12 +1692,15 @@
1686 1692
   dependencies:
1687 1693
     "@babel/runtime" "^7.13.10"
1688 1694
 
1689
-"@radix-ui/react-focus-scope@0.0.13":
1690
-  version "0.0.13"
1691
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.13.tgz#06dd6781d457b272601d4c087ac1240907824443"
1692
-  integrity sha512-PelAuc+7HSGruBraSzuHogwaKqCvmO288ecIm3cCAkrJqPQ7hoKSd/LfLfoa/EvjqK9azmm7NQ6LSPoteQvOGQ==
1695
+"@radix-ui/react-focus-scope@0.0.14":
1696
+  version "0.0.14"
1697
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.14.tgz#778e2a3ea607621d82e0139616d7ea6d517d9533"
1698
+  integrity sha512-D3v6Tw8vzpIBNd2I32Q2G4LCiXMIlmc6Pl2VV9CZjSatDOjkV/ckGbhkQyQ7QxnD/0CmiSxNo5hTeGRmZDjwmA==
1693 1699
   dependencies:
1694 1700
     "@babel/runtime" "^7.13.10"
1701
+    "@radix-ui/react-compose-refs" "0.0.5"
1702
+    "@radix-ui/react-polymorphic" "0.0.12"
1703
+    "@radix-ui/react-primitive" "0.0.14"
1695 1704
     "@radix-ui/react-use-callback-ref" "0.0.5"
1696 1705
 
1697 1706
 "@radix-ui/react-icons@^1.0.3":
@@ -1706,72 +1715,72 @@
1706 1715
   dependencies:
1707 1716
     "@babel/runtime" "^7.13.10"
1708 1717
 
1709
-"@radix-ui/react-label@0.0.13":
1710
-  version "0.0.13"
1711
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.0.13.tgz#b71930fa16a2cf859296317436cb88e31efb8ecf"
1712
-  integrity sha512-csNElm8qA38pOHr772CXIvBXd/eCGaoAMImuLdawUxQNzwxQ4npd8lr/f9fi/4OLkgeNOVOqjsaVamiNmF/lIw==
1718
+"@radix-ui/react-label@0.0.14":
1719
+  version "0.0.14"
1720
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.0.14.tgz#32928a0f5d72e3a24b99021f5c6805e96f3d9395"
1721
+  integrity sha512-gtrEpON6Ye8OBYtOEJBEuB/n/h6x07VPs7JR/1t34LaB74GJ+gACcafLcg5gMGWku3Gbc3eaDwfhOkxhTuJdYQ==
1713 1722
   dependencies:
1714 1723
     "@babel/runtime" "^7.13.10"
1715 1724
     "@radix-ui/react-compose-refs" "0.0.5"
1716 1725
     "@radix-ui/react-id" "0.0.6"
1717
-    "@radix-ui/react-polymorphic" "0.0.11"
1718
-    "@radix-ui/react-primitive" "0.0.13"
1726
+    "@radix-ui/react-polymorphic" "0.0.12"
1727
+    "@radix-ui/react-primitive" "0.0.14"
1719 1728
 
1720
-"@radix-ui/react-menu@0.0.18":
1721
-  version "0.0.18"
1722
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.0.18.tgz#b36f7657eb6757c623ffc688c48a4781ffd82351"
1723
-  integrity sha512-js5hFzoxNOnHV8g7RPoPl/GncUCW2aCOuNt9Qh6WznRmxmsETPUWZZe4kADJnZmYbIxG07EEl0iv3E1rmsqNMw==
1729
+"@radix-ui/react-menu@0.0.19":
1730
+  version "0.0.19"
1731
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.0.19.tgz#12036862ad6c813e442a14316b5ed380f54962e9"
1732
+  integrity sha512-ACGk5i83KahzHGyZVpWGyZBi68VCplQgMmS8ZvuuwDGf1vQl5dlpxvWM8iKh6EUL8zCTXNzX2jRtg0NZYoRrPg==
1724 1733
   dependencies:
1725 1734
     "@babel/runtime" "^7.13.10"
1726 1735
     "@radix-ui/primitive" "0.0.5"
1727
-    "@radix-ui/react-collection" "0.0.12"
1736
+    "@radix-ui/react-collection" "0.0.13"
1728 1737
     "@radix-ui/react-compose-refs" "0.0.5"
1729 1738
     "@radix-ui/react-context" "0.0.5"
1730
-    "@radix-ui/react-dismissable-layer" "0.0.13"
1739
+    "@radix-ui/react-dismissable-layer" "0.0.14"
1731 1740
     "@radix-ui/react-focus-guards" "0.0.7"
1732
-    "@radix-ui/react-focus-scope" "0.0.13"
1733
-    "@radix-ui/react-polymorphic" "0.0.11"
1734
-    "@radix-ui/react-popper" "0.0.16"
1735
-    "@radix-ui/react-portal" "0.0.13"
1741
+    "@radix-ui/react-focus-scope" "0.0.14"
1742
+    "@radix-ui/react-id" "0.0.6"
1743
+    "@radix-ui/react-polymorphic" "0.0.12"
1744
+    "@radix-ui/react-popper" "0.0.17"
1745
+    "@radix-ui/react-portal" "0.0.14"
1736 1746
     "@radix-ui/react-presence" "0.0.14"
1737
-    "@radix-ui/react-primitive" "0.0.13"
1738
-    "@radix-ui/react-roving-focus" "0.0.13"
1739
-    "@radix-ui/react-slot" "0.0.10"
1747
+    "@radix-ui/react-primitive" "0.0.14"
1748
+    "@radix-ui/react-roving-focus" "0.0.14"
1749
+    "@radix-ui/react-slot" "0.0.11"
1740 1750
     "@radix-ui/react-use-callback-ref" "0.0.5"
1751
+    "@radix-ui/react-use-direction" "0.0.1"
1741 1752
     aria-hidden "^1.1.1"
1742 1753
     react-remove-scroll "^2.4.0"
1743 1754
 
1744
-"@radix-ui/react-polymorphic@0.0.11":
1745
-  version "0.0.11"
1746
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.11.tgz#23f26b14e67a0e57cd981c37618d603b952af80d"
1747
-  integrity sha512-CztM4962esOx3i1ls6GuY9RBYIY2Df1Bmp5emHRTxZi8owyCZwZYPctYaDuMO0qIGikPiKD8HBion/m7VWUyEA==
1748
-  dependencies:
1749
-    "@babel/runtime" "^7.13.10"
1755
+"@radix-ui/react-polymorphic@0.0.12":
1756
+  version "0.0.12"
1757
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.12.tgz#bf4ae516669b68e059549538104d97322f7c876b"
1758
+  integrity sha512-/GYNMicBnGzjD1d2fCAuzql1VeFrp8mqM3xfzT1kxhnV85TKdURO45jBfMgqo17XNXoNhWIAProUsCO4qFAAIg==
1750 1759
 
1751
-"@radix-ui/react-popper@0.0.16":
1752
-  version "0.0.16"
1753
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.0.16.tgz#62cccb7d920dc89e076bbdc3421db8c84078f428"
1754
-  integrity sha512-njciQ/eIKaDF9h+27Pwi1H6NliQbzTgaHOsT0w/Lxx4vfMe8zcHtiEigYVGErNR4zAYlbW72KzLjtngtNnaorg==
1760
+"@radix-ui/react-popper@0.0.17":
1761
+  version "0.0.17"
1762
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.0.17.tgz#a73486e19a628cb3fecaf3fb6eecf6e2cab9d0be"
1763
+  integrity sha512-2Lk5AjlCcN9B6fQwQ+y1Zb93f1LfyCK6u0oOAUsPtwbnYEHCdC6wuCFBOZ7AonpjHbrHbY6QDrLGcC43TY6ihw==
1755 1764
   dependencies:
1756 1765
     "@babel/runtime" "^7.13.10"
1757 1766
     "@radix-ui/popper" "0.0.10"
1758
-    "@radix-ui/react-arrow" "0.0.13"
1767
+    "@radix-ui/react-arrow" "0.0.14"
1759 1768
     "@radix-ui/react-compose-refs" "0.0.5"
1760 1769
     "@radix-ui/react-context" "0.0.5"
1761
-    "@radix-ui/react-polymorphic" "0.0.11"
1762
-    "@radix-ui/react-primitive" "0.0.13"
1770
+    "@radix-ui/react-polymorphic" "0.0.12"
1771
+    "@radix-ui/react-primitive" "0.0.14"
1763 1772
     "@radix-ui/react-use-rect" "0.0.7"
1764 1773
     "@radix-ui/react-use-size" "0.0.6"
1765 1774
     "@radix-ui/rect" "0.0.5"
1766 1775
 
1767
-"@radix-ui/react-portal@0.0.13":
1768
-  version "0.0.13"
1769
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.13.tgz#8e60e9ee9b1594f98ee4a8f24a9851ef7ef2ad31"
1770
-  integrity sha512-Mw5hrxds2T9HTGwdDbvlKGvTUfcuKhPtqxLnizOrM685e80j+pfjAAfMSXSyfmra9KFQvk3XxmUP0d4U6+kzMg==
1776
+"@radix-ui/react-portal@0.0.14":
1777
+  version "0.0.14"
1778
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.14.tgz#31513d8777cf5e50a3a30ebc9deb34821e890e9e"
1779
+  integrity sha512-Wi9arVwVenonjZIX6znCBYaasua03Tb+UtrBZShepJkLGtbGxDlzExijiGIaIRNetl46Oc2pw0F6Y6HffDnUww==
1771 1780
   dependencies:
1772 1781
     "@babel/runtime" "^7.13.10"
1773
-    "@radix-ui/react-polymorphic" "0.0.11"
1774
-    "@radix-ui/react-primitive" "0.0.13"
1782
+    "@radix-ui/react-polymorphic" "0.0.12"
1783
+    "@radix-ui/react-primitive" "0.0.14"
1775 1784
     "@radix-ui/react-use-layout-effect" "0.0.5"
1776 1785
 
1777 1786
 "@radix-ui/react-presence@0.0.14":
@@ -1782,79 +1791,80 @@
1782 1791
     "@babel/runtime" "^7.13.10"
1783 1792
     "@radix-ui/react-compose-refs" "0.0.5"
1784 1793
 
1785
-"@radix-ui/react-primitive@0.0.13":
1786
-  version "0.0.13"
1787
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.13.tgz#79c606c3e7da9c377b77a7b3f4ec879b45de47a2"
1788
-  integrity sha512-6xxWzw67t7ZuN9Ikn9NNQdb/HSF7dNHPN3kPcgjiVgTEZa3tKk1xjSxGjjQztE61g9GrnTLpu7mBjmEuZDI/lA==
1794
+"@radix-ui/react-primitive@0.0.14":
1795
+  version "0.0.14"
1796
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.14.tgz#752a967cb05d4c5643634fe20274e7dc905d1cce"
1797
+  integrity sha512-FYOWGCrxFpLdB534aWTwMK4Pjg8cxFb+745qWhPfI+cYi+aYUddJQD3ilRHHXxCBD72ve7/PufqeB7Y/QlKqgg==
1789 1798
   dependencies:
1790 1799
     "@babel/runtime" "^7.13.10"
1791
-    "@radix-ui/react-polymorphic" "0.0.11"
1800
+    "@radix-ui/react-polymorphic" "0.0.12"
1792 1801
 
1793
-"@radix-ui/react-radio-group@^0.0.16":
1794
-  version "0.0.16"
1795
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.0.16.tgz#10fc6e5c3102599cf422e9f6f8d2766088e602a1"
1796
-  integrity sha512-vOtgflNWcauSul+EvnPCxATdmPw7fb1cuqBJX07yJdjbrw1Iv5v/+d79fNyIwPR+KrkhP+uCMIBfF0gvo6K7ZQ==
1802
+"@radix-ui/react-radio-group@^0.0.17":
1803
+  version "0.0.17"
1804
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.0.17.tgz#29ae3d361fc6823cd082d29b7d96efd370dfdf2a"
1805
+  integrity sha512-Xvibkj4jGxV9AipZyhFU8xXI5pFPSx/7OGxkRMwHs6U1a28vWppVq++hBhybxCNPO1ZragC2hzl7xdtkz+sugg==
1797 1806
   dependencies:
1798 1807
     "@babel/runtime" "^7.13.10"
1799 1808
     "@radix-ui/primitive" "0.0.5"
1800 1809
     "@radix-ui/react-compose-refs" "0.0.5"
1801 1810
     "@radix-ui/react-context" "0.0.5"
1802
-    "@radix-ui/react-label" "0.0.13"
1803
-    "@radix-ui/react-polymorphic" "0.0.11"
1811
+    "@radix-ui/react-label" "0.0.14"
1812
+    "@radix-ui/react-polymorphic" "0.0.12"
1804 1813
     "@radix-ui/react-presence" "0.0.14"
1805
-    "@radix-ui/react-primitive" "0.0.13"
1806
-    "@radix-ui/react-roving-focus" "0.0.13"
1807
-    "@radix-ui/react-slot" "0.0.10"
1814
+    "@radix-ui/react-primitive" "0.0.14"
1815
+    "@radix-ui/react-roving-focus" "0.0.14"
1816
+    "@radix-ui/react-slot" "0.0.11"
1808 1817
     "@radix-ui/react-use-callback-ref" "0.0.5"
1809 1818
     "@radix-ui/react-use-controllable-state" "0.0.6"
1819
+    "@radix-ui/react-use-previous" "0.0.5"
1820
+    "@radix-ui/react-use-size" "0.0.6"
1810 1821
 
1811
-"@radix-ui/react-roving-focus@0.0.13":
1812
-  version "0.0.13"
1813
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.0.13.tgz#c72f503832577979c4caa9efcfd59140730c2f80"
1814
-  integrity sha512-jTx3TBMYEdj9SFzRDryO62M9ZK28pYvKKxj/mxWMpMbIjF4q6L+yJmDxtv6glHgCRkwBEyulFVGCjjDg+qxYRA==
1822
+"@radix-ui/react-roving-focus@0.0.14":
1823
+  version "0.0.14"
1824
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.0.14.tgz#cedc20ee78227199e4379434489cdd7c00e3edbd"
1825
+  integrity sha512-MHYdC5kyx/3V8/9DbvMDOCPHP/4blRkaD3EUM4w7VTHUHiZgw6+Dnk1Gc2LoTkcx8Oz413aU+S11vyRdw/1pcA==
1815 1826
   dependencies:
1816 1827
     "@babel/runtime" "^7.13.10"
1817 1828
     "@radix-ui/primitive" "0.0.5"
1818
-    "@radix-ui/react-collection" "0.0.12"
1829
+    "@radix-ui/react-collection" "0.0.13"
1819 1830
     "@radix-ui/react-compose-refs" "0.0.5"
1820 1831
     "@radix-ui/react-context" "0.0.5"
1821 1832
     "@radix-ui/react-id" "0.0.6"
1822
-    "@radix-ui/react-polymorphic" "0.0.11"
1823
-    "@radix-ui/react-primitive" "0.0.13"
1833
+    "@radix-ui/react-polymorphic" "0.0.12"
1834
+    "@radix-ui/react-primitive" "0.0.14"
1824 1835
     "@radix-ui/react-use-callback-ref" "0.0.5"
1825 1836
     "@radix-ui/react-use-controllable-state" "0.0.6"
1826 1837
 
1827
-"@radix-ui/react-slot@0.0.10":
1828
-  version "0.0.10"
1829
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.10.tgz#27a17cad7064872117aeb68113fa934bc5d34c37"
1830
-  integrity sha512-p0jJj6lTz1RV2imavnclk8Gda002ZSDR4/zPJ4EQBhspGnx7Y8l6G59c8lxJrT7c7F46F2eRNjpTTjFqqir6EQ==
1838
+"@radix-ui/react-slot@0.0.11":
1839
+  version "0.0.11"
1840
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.11.tgz#d0121018ae20d86683d527b7011b7f21465dffbd"
1841
+  integrity sha512-H/l58Aqoyi9Jq5Jcny5v6HeMNelOivm2NUdaIvMBJzysbbwWhc78VEmpOpfQ6H+y/Fsx6aLprHbfmxPd/66PxQ==
1831 1842
   dependencies:
1832 1843
     "@babel/runtime" "^7.13.10"
1833
-    "@radix-ui/primitive" "0.0.5"
1834 1844
     "@radix-ui/react-compose-refs" "0.0.5"
1835 1845
 
1836
-"@radix-ui/react-tooltip@^0.0.18":
1837
-  version "0.0.18"
1838
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.18.tgz#7594297dbc2acf101ac45fdb414c7bc0ac9426bc"
1839
-  integrity sha512-oYRAbbTZJ8zEokrrk5pe7QbzF+ZnzMby8mBplrkColi0ntToJ7RKzPgUs1OOFvmg/Nld0Iy2FufrTNlyyEI3kQ==
1846
+"@radix-ui/react-tooltip@^0.0.19":
1847
+  version "0.0.19"
1848
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.19.tgz#2ee843c7c5612708b31ad52adbefc580b213a421"
1849
+  integrity sha512-zDFrGH0nQfRZyk7qD73o22nCbA1QNQoo8cl6RhBLBAFG/b+h1Q5Y4VHu2ZKEz3zZEgrZ8lOKnX8A6dOa9619PQ==
1840 1850
   dependencies:
1841 1851
     "@babel/runtime" "^7.13.10"
1842 1852
     "@radix-ui/primitive" "0.0.5"
1843 1853
     "@radix-ui/react-compose-refs" "0.0.5"
1844 1854
     "@radix-ui/react-context" "0.0.5"
1845 1855
     "@radix-ui/react-id" "0.0.6"
1846
-    "@radix-ui/react-polymorphic" "0.0.11"
1847
-    "@radix-ui/react-popper" "0.0.16"
1848
-    "@radix-ui/react-portal" "0.0.13"
1856
+    "@radix-ui/react-polymorphic" "0.0.12"
1857
+    "@radix-ui/react-popper" "0.0.17"
1858
+    "@radix-ui/react-portal" "0.0.14"
1849 1859
     "@radix-ui/react-presence" "0.0.14"
1850
-    "@radix-ui/react-primitive" "0.0.13"
1851
-    "@radix-ui/react-slot" "0.0.10"
1860
+    "@radix-ui/react-primitive" "0.0.14"
1861
+    "@radix-ui/react-slot" "0.0.11"
1852 1862
     "@radix-ui/react-use-controllable-state" "0.0.6"
1853 1863
     "@radix-ui/react-use-escape-keydown" "0.0.6"
1854 1864
     "@radix-ui/react-use-layout-effect" "0.0.5"
1855 1865
     "@radix-ui/react-use-previous" "0.0.5"
1856 1866
     "@radix-ui/react-use-rect" "0.0.7"
1857
-    "@radix-ui/react-visually-hidden" "0.0.13"
1867
+    "@radix-ui/react-visually-hidden" "0.0.14"
1858 1868
 
1859 1869
 "@radix-ui/react-use-body-pointer-events@0.0.6":
1860 1870
   version "0.0.6"
@@ -1879,6 +1889,13 @@
1879 1889
     "@babel/runtime" "^7.13.10"
1880 1890
     "@radix-ui/react-use-callback-ref" "0.0.5"
1881 1891
 
1892
+"@radix-ui/react-use-direction@0.0.1":
1893
+  version "0.0.1"
1894
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.0.1.tgz#9ac72eb6d9902ed505c8a34048981d94f9433e14"
1895
+  integrity sha512-sU+tkP09uEI1m+YJAR1ZAZLVFY1h/JD+jLSSQt5Wo3b9SYrJA889i2hH1P3DNRyWbbbisweiEQdK3MWILhFCig==
1896
+  dependencies:
1897
+    "@babel/runtime" "^7.13.10"
1898
+
1882 1899
 "@radix-ui/react-use-escape-keydown@0.0.6":
1883 1900
   version "0.0.6"
1884 1901
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.0.6.tgz#1ad1c81b99961b7dbe376ef54151ebc8bef627a0"
@@ -1916,14 +1933,14 @@
1916 1933
   dependencies:
1917 1934
     "@babel/runtime" "^7.13.10"
1918 1935
 
1919
-"@radix-ui/react-visually-hidden@0.0.13":
1920
-  version "0.0.13"
1921
-  resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.13.tgz#c7f69097eb7d796dcd9117cdd228d87991c08baf"
1922
-  integrity sha512-8VNuE4/3PnyrLv1je56fxaa5qka0Nb6/FlyQEDF2HCPpxVOWR4sxRfSBe8cjy+Me+pJN9ZoKBIuoFCVRk54xJA==
1936
+"@radix-ui/react-visually-hidden@0.0.14":
1937
+  version "0.0.14"
1938
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.14.tgz#47b764d295275572aa290dabc81c39be3091663b"
1939
+  integrity sha512-fCz6Q24EWfU5uX4JGXFoO2I5H5otGzyyrxyKeOKYpkWiTGY93CO9C4oN7rXiRNCxkm68bgynAgIG40TjOqy0Lw==
1923 1940
   dependencies:
1924 1941
     "@babel/runtime" "^7.13.10"
1925
-    "@radix-ui/react-polymorphic" "0.0.11"
1926
-    "@radix-ui/react-primitive" "0.0.13"
1942
+    "@radix-ui/react-polymorphic" "0.0.12"
1943
+    "@radix-ui/react-primitive" "0.0.14"
1927 1944
 
1928 1945
 "@radix-ui/rect@0.0.5":
1929 1946
   version "0.0.5"
@@ -2034,17 +2051,10 @@
2034 2051
   dependencies:
2035 2052
     "@state-designer/core" latest
2036 2053
 
2037
-"@stitches/core@^0.1.9":
2038
-  version "0.1.9"
2039
-  resolved "https://registry.yarnpkg.com/@stitches/core/-/core-0.1.9.tgz#6cc42ae6052f6900f9add164232449ff8d62c4ba"
2040
-  integrity sha512-70gVb0uhgV6BOJmloirWEGZmofk/Hzr5yWlub1rtm4CvimD3Xl5xr9fVkjcTu+REC6ILCJCnlEP4WmilTfXNOA==
2041
-
2042
-"@stitches/react@^0.1.9":
2043
-  version "0.1.9"
2044
-  resolved "https://registry.yarnpkg.com/@stitches/react/-/react-0.1.9.tgz#b16e05c9ee7e34369bb3b051b92a997ab379863f"
2045
-  integrity sha512-yK4ZAdkuOiOum+bfPYlDNS9UI/Tm9JYsYNMUWLB3uAy+7tvSYfB4cVYkXwFuiKxis03woATTZTe6IlTb+LXm+A==
2046
-  dependencies:
2047
-    "@stitches/core" "^0.1.9"
2054
+"@stitches/react@^0.2.1":
2055
+  version "0.2.1"
2056
+  resolved "https://registry.yarnpkg.com/@stitches/react/-/react-0.2.1.tgz#c63a0a337b849b302da2342cb5d7aeb6ea257913"
2057
+  integrity sha512-LPCOUXsadQM4JrtHQEsOJ+msyss0KySO9jL8L2SUVi9Vi6BfbyEi1mVGaqgvIQRZQfWsM0Se/3AXcl9rUQi5Nw==
2048 2058
 
2049 2059
 "@surma/rollup-plugin-off-main-thread@^1.4.1":
2050 2060
   version "1.4.2"
@@ -7134,10 +7144,10 @@ pend@~1.2.0:
7134 7144
   resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
7135 7145
   integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
7136 7146
 
7137
-perfect-freehand@^0.4.8:
7138
-  version "0.4.8"
7139
-  resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.8.tgz#0054995322fdd9939c0c38c260d96a9d0f22eb4f"
7140
-  integrity sha512-zU0hvTh0ctjb/h5+nwFhb+/5ZnqS8Z16mn7lY2tYy3NwTkjrEKZ8CJvQ2phlOV5kk+BnCDFGVz7tkKrl9+Mr9w==
7147
+perfect-freehand@^0.4.9:
7148
+  version "0.4.9"
7149
+  resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.9.tgz#09e9f5a1057da7eda335367d5f1f2c6fbd369e3e"
7150
+  integrity sha512-mNf9Yd2dxWV3kZs2PPNWjLOt8Yh31+PIu6/zst7I8YJOicYBnRofzXl9us5gxqK4ZfHKO5O7PjttHF0tuHfo0g==
7141 7151
 
7142 7152
 performance-now@^2.1.0:
7143 7153
   version "2.1.0"

正在加载...
取消
保存