Kaynağa Gözat

Moves selectedIds into page state, state mounts only one page state / page at a time

main
Steve Ruiz 4 yıl önce
ebeveyn
işleme
350c1debde

+ 2
- 1
components/canvas/bounds/bounding-box.tsx Dosyayı Görüntüle

6
   getBoundsCenter,
6
   getBoundsCenter,
7
   getCurrentCamera,
7
   getCurrentCamera,
8
   getPage,
8
   getPage,
9
+  getSelectedIds,
9
   getSelectedShapes,
10
   getSelectedShapes,
10
   isMobile,
11
   isMobile,
11
 } from 'utils/utils'
12
 } from 'utils/utils'
28
   )
29
   )
29
 
30
 
30
   const rotation = useSelector(({ data }) =>
31
   const rotation = useSelector(({ data }) =>
31
-    data.selectedIds.size === 1 ? getSelectedShapes(data)[0].rotation : 0
32
+    getSelectedIds(data).size === 1 ? getSelectedShapes(data)[0].rotation : 0
32
   )
33
   )
33
 
34
 
34
   const isAllLocked = useSelector((s) => {
35
   const isAllLocked = useSelector((s) => {

+ 0
- 1
components/canvas/page.tsx Dosyayı Görüntüle

43
         .filter((shape) => shape.parentId === page.id)
43
         .filter((shape) => shape.parentId === page.id)
44
         // .filter((shape) => {
44
         // .filter((shape) => {
45
         //   const shapeBounds = getShapeUtils(shape).getBounds(shape)
45
         //   const shapeBounds = getShapeUtils(shape).getBounds(shape)
46
-        //   console.log(shapeBounds, viewport)
47
         //   return boundsContain(viewport, shapeBounds)
46
         //   return boundsContain(viewport, shapeBounds)
48
         // })
47
         // })
49
         .sort((a, b) => a.childIndex - b.childIndex)
48
         .sort((a, b) => a.childIndex - b.childIndex)

+ 11
- 4
components/canvas/selected.tsx Dosyayı Görüntüle

1
 import styled from 'styles'
1
 import styled from 'styles'
2
 import { useSelector } from 'state'
2
 import { useSelector } from 'state'
3
-import { deepCompareArrays, getBoundsCenter, getPage } from 'utils/utils'
3
+import {
4
+  deepCompareArrays,
5
+  getBoundsCenter,
6
+  getPage,
7
+  getSelectedIds,
8
+  setToArray,
9
+} from 'utils/utils'
4
 import { getShapeUtils } from 'lib/shape-utils'
10
 import { getShapeUtils } from 'lib/shape-utils'
5
 import useShapeEvents from 'hooks/useShapeEvents'
11
 import useShapeEvents from 'hooks/useShapeEvents'
6
 import { memo, useRef } from 'react'
12
 import { memo, useRef } from 'react'
8
 import * as vec from 'utils/vec'
14
 import * as vec from 'utils/vec'
9
 
15
 
10
 export default function Selected() {
16
 export default function Selected() {
11
-  const currentSelectedShapeIds = useSelector(({ data }) => {
12
-    return Array.from(data.selectedIds.values())
13
-  }, deepCompareArrays)
17
+  const currentSelectedShapeIds = useSelector(
18
+    ({ data }) => setToArray(getSelectedIds(data)),
19
+    deepCompareArrays
20
+  )
14
 
21
 
15
   const isSelecting = useSelector((s) => s.isIn('selecting'))
22
   const isSelecting = useSelector((s) => s.isIn('selecting'))
16
 
23
 

+ 1
- 1
components/page-panel/page-panel.tsx Dosyayı Görüntüle

52
               value={currentPageId}
52
               value={currentPageId}
53
               onValueChange={(id) => {
53
               onValueChange={(id) => {
54
                 setIsOpen(false)
54
                 setIsOpen(false)
55
-                state.send('CHANGED_CURRENT_PAGE', { id })
55
+                state.send('CHANGED_PAGE', { id })
56
               }}
56
               }}
57
             >
57
             >
58
               {sorted.map(({ id, name }) => (
58
               {sorted.map(({ id, name }) => (

+ 8
- 2
components/style-panel/style-panel.tsx Dosyayı Görüntüle

4
 import { useRef } from 'react'
4
 import { useRef } from 'react'
5
 import { IconButton } from 'components/shared'
5
 import { IconButton } from 'components/shared'
6
 import { ChevronDown, Trash2, X } from 'react-feather'
6
 import { ChevronDown, Trash2, X } from 'react-feather'
7
-import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
7
+import {
8
+  deepCompare,
9
+  deepCompareArrays,
10
+  getPage,
11
+  getSelectedIds,
12
+  setToArray,
13
+} from 'utils/utils'
8
 import AlignDistribute from './align-distribute'
14
 import AlignDistribute from './align-distribute'
9
 import { MoveType } from 'types'
15
 import { MoveType } from 'types'
10
 import SizePicker from './size-picker'
16
 import SizePicker from './size-picker'
65
 
71
 
66
 function SelectedShapeStyles() {
72
 function SelectedShapeStyles() {
67
   const selectedIds = useSelector(
73
   const selectedIds = useSelector(
68
-    (s) => Array.from(s.data.selectedIds.values()),
74
+    (s) => setToArray(getSelectedIds(s.data)),
69
     deepCompareArrays
75
     deepCompareArrays
70
   )
76
   )
71
 
77
 

+ 24
- 8
lib/shape-utils/draw.tsx Dosyayı Görüntüle

96
     if (shape.rotation === 0) {
96
     if (shape.rotation === 0) {
97
       return (
97
       return (
98
         boundsContain(brushBounds, this.getBounds(shape)) ||
98
         boundsContain(brushBounds, this.getBounds(shape)) ||
99
-        intersectPolylineBounds(shape.points, brushBounds).length > 0
99
+        intersectPolylineBounds(
100
+          shape.points,
101
+          translateBounds(brushBounds, vec.neg(shape.point))
102
+        ).length > 0
100
       )
103
       )
101
     }
104
     }
102
 
105
 
104
     const rBounds = this.getRotatedBounds(shape)
107
     const rBounds = this.getRotatedBounds(shape)
105
 
108
 
106
     if (!rotatedCache.has(shape)) {
109
     if (!rotatedCache.has(shape)) {
107
-      const c = getBoundsCenter(rBounds)
110
+      const c = getBoundsCenter(getBoundsFromPoints(shape.points))
108
       rotatedCache.set(
111
       rotatedCache.set(
109
         shape,
112
         shape,
110
-        shape.points.map((pt) =>
111
-          vec.rotWith(vec.add(pt, shape.point), c, shape.rotation)
112
-        )
113
+        shape.points.map((pt) => vec.rotWith(pt, c, shape.rotation))
113
       )
114
       )
114
     }
115
     }
115
 
116
 
116
     return (
117
     return (
117
       boundsContain(brushBounds, rBounds) ||
118
       boundsContain(brushBounds, rBounds) ||
118
-      intersectPolylineBounds(rotatedCache.get(shape), brushBounds).length > 0
119
+      intersectPolylineBounds(
120
+        rotatedCache.get(shape),
121
+        translateBounds(brushBounds, vec.neg(shape.point))
122
+      ).length > 0
119
     )
123
     )
120
   },
124
   },
121
 
125
 
152
     return this
156
     return this
153
   },
157
   },
154
 
158
 
159
+  onSessionComplete(shape) {
160
+    const bounds = this.getBounds(shape)
161
+
162
+    const [x1, y1] = vec.sub([bounds.minX, bounds.minY], shape.point)
163
+
164
+    shape.points = shape.points.map(([x0, y0, p]) => [x0 - x1, y0 - y1, p])
165
+
166
+    this.translateTo(shape, vec.add(shape.point, [x1, y1]))
167
+
168
+    return this
169
+  },
170
+
155
   canStyleFill: false,
171
   canStyleFill: false,
156
 })
172
 })
157
 
173
 
164
 const realPressureSettings = {
180
 const realPressureSettings = {
165
   easing: (t: number) => t * t,
181
   easing: (t: number) => t * t,
166
   simulatePressure: false,
182
   simulatePressure: false,
167
-  // start: { taper: 1 },
168
-  // end: { taper: 1 },
183
+  start: { taper: 1 },
184
+  end: { taper: 1 },
169
 }
185
 }
170
 
186
 
171
 function renderPath(shape: DrawShape, style: ShapeStyles) {
187
 function renderPath(shape: DrawShape, style: ShapeStyles) {

+ 6
- 4
state/commands/arrow.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
 import { Data } from 'types'
3
 import { Data } from 'types'
4
-import { getPage } from 'utils/utils'
4
+import { getPage, getSelectedIds } from 'utils/utils'
5
 import { ArrowSnapshot } from 'state/sessions/arrow-session'
5
 import { ArrowSnapshot } from 'state/sessions/arrow-session'
6
 
6
 
7
 export default function arrowCommand(
7
 export default function arrowCommand(
24
 
24
 
25
         page.shapes[initialShape.id] = initialShape
25
         page.shapes[initialShape.id] = initialShape
26
 
26
 
27
-        data.selectedIds.clear()
28
-        data.selectedIds.add(initialShape.id)
27
+        const selectedIds = getSelectedIds(data)
28
+        selectedIds.clear()
29
+        selectedIds.add(initialShape.id)
29
         data.hoveredId = undefined
30
         data.hoveredId = undefined
30
         data.pointedId = undefined
31
         data.pointedId = undefined
31
       },
32
       },
35
 
36
 
36
         delete shapes[initialShape.id]
37
         delete shapes[initialShape.id]
37
 
38
 
38
-        data.selectedIds.clear()
39
+        const selectedIds = getSelectedIds(data)
40
+        selectedIds.clear()
39
         data.hoveredId = undefined
41
         data.hoveredId = undefined
40
         data.pointedId = undefined
42
         data.pointedId = undefined
41
       },
43
       },

+ 5
- 3
state/commands/change-page.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
 import { Data } from 'types'
3
 import { Data } from 'types'
4
-import { getPage, getSelectedShapes } from 'utils/utils'
5
-import { getShapeUtils } from 'lib/shape-utils'
6
-import * as vec from 'utils/vec'
4
+import storage from 'state/storage'
7
 
5
 
8
 export default function changePage(data: Data, pageId: string) {
6
 export default function changePage(data: Data, pageId: string) {
9
   const { currentPageId: prevPageId } = data
7
   const { currentPageId: prevPageId } = data
13
     new Command({
11
     new Command({
14
       name: 'change_page',
12
       name: 'change_page',
15
       category: 'canvas',
13
       category: 'canvas',
14
+      manualSelection: true,
16
       do(data) {
15
       do(data) {
16
+        storage.savePage(data, data.currentPageId)
17
         data.currentPageId = pageId
17
         data.currentPageId = pageId
18
+        storage.loadPage(data, data.currentPageId)
18
       },
19
       },
19
       undo(data) {
20
       undo(data) {
20
         data.currentPageId = prevPageId
21
         data.currentPageId = prevPageId
22
+        storage.loadPage(data, prevPageId)
21
       },
23
       },
22
     })
24
     })
23
   )
25
   )

+ 15
- 6
state/commands/command.ts Dosyayı Görüntüle

1
-import { Data } from "types"
1
+import { Data } from 'types'
2
+import { getSelectedIds, setSelectedIds, setToArray } from 'utils/utils'
2
 
3
 
3
 /* ------------------ Command Class ----------------- */
4
 /* ------------------ Command Class ----------------- */
4
 
5
 
52
   }
53
   }
53
 
54
 
54
   redo = (data: T, initial = false) => {
55
   redo = (data: T, initial = false) => {
56
+    if (this.manualSelection) {
57
+      this.doFn(data, initial)
58
+
59
+      return
60
+    }
61
+
55
     if (initial) {
62
     if (initial) {
56
       this.restoreBeforeSelectionState = this.saveSelectionState(data)
63
       this.restoreBeforeSelectionState = this.saveSelectionState(data)
57
     } else {
64
     } else {
76
  */
83
  */
77
 export default class Command extends BaseCommand<Data> {
84
 export default class Command extends BaseCommand<Data> {
78
   saveSelectionState = (data: Data) => {
85
   saveSelectionState = (data: Data) => {
79
-    const selectedIds = new Set(data.selectedIds)
80
-    return (data: Data) => {
81
-      data.hoveredId = undefined
82
-      data.pointedId = undefined
83
-      data.selectedIds = selectedIds
86
+    const { currentPageId } = data
87
+    const selectedIds = setToArray(getSelectedIds(data))
88
+    return (next: Data) => {
89
+      next.currentPageId = currentPageId
90
+      next.hoveredId = undefined
91
+      next.pointedId = undefined
92
+      setSelectedIds(next, selectedIds)
84
     }
93
     }
85
   }
94
   }
86
 }
95
 }

+ 6
- 4
state/commands/create-page.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
-import { Data, Page } from 'types'
3
+import { Data, Page, PageState } from 'types'
4
 import { v4 as uuid } from 'uuid'
4
 import { v4 as uuid } from 'uuid'
5
 import { current } from 'immer'
5
 import { current } from 'immer'
6
+import { getSelectedIds } from 'utils/utils'
7
+import storage from 'state/storage'
6
 
8
 
7
 export default function createPage(data: Data) {
9
 export default function createPage(data: Data) {
8
   const snapshot = getSnapshot(data)
10
   const snapshot = getSnapshot(data)
13
       name: 'change_page',
15
       name: 'change_page',
14
       category: 'canvas',
16
       category: 'canvas',
15
       do(data) {
17
       do(data) {
16
-        data.selectedIds.clear()
17
         const { page, pageState } = snapshot
18
         const { page, pageState } = snapshot
18
         data.document.pages[page.id] = page
19
         data.document.pages[page.id] = page
19
         data.pageStates[page.id] = pageState
20
         data.pageStates[page.id] = pageState
20
         data.currentPageId = page.id
21
         data.currentPageId = page.id
22
+        storage.savePage(data, page.id)
21
       },
23
       },
22
       undo(data) {
24
       undo(data) {
23
-        data.selectedIds.clear()
24
         const { page, currentPageId } = snapshot
25
         const { page, currentPageId } = snapshot
25
         delete data.document.pages[page.id]
26
         delete data.document.pages[page.id]
26
         delete data.pageStates[page.id]
27
         delete data.pageStates[page.id]
44
     childIndex: pages.length,
45
     childIndex: pages.length,
45
     shapes: {},
46
     shapes: {},
46
   }
47
   }
47
-  const pageState = {
48
+  const pageState: PageState = {
49
+    selectedIds: new Set<string>(),
48
     camera: {
50
     camera: {
49
       point: [0, 0],
51
       point: [0, 0],
50
       zoom: 1,
52
       zoom: 1,

+ 15
- 7
state/commands/delete-selected.ts Dosyayı Görüntüle

2
 import history from '../history'
2
 import history from '../history'
3
 import { TranslateSnapshot } from 'state/sessions/translate-session'
3
 import { TranslateSnapshot } from 'state/sessions/translate-session'
4
 import { Data, ShapeType } from 'types'
4
 import { Data, ShapeType } from 'types'
5
-import { getDocumentBranch, getPage, updateParents } from 'utils/utils'
5
+import {
6
+  getDocumentBranch,
7
+  getPage,
8
+  getSelectedIds,
9
+  setSelectedIds,
10
+  setToArray,
11
+  updateParents,
12
+} from 'utils/utils'
6
 import { current } from 'immer'
13
 import { current } from 'immer'
7
 import { getShapeUtils } from 'lib/shape-utils'
14
 import { getShapeUtils } from 'lib/shape-utils'
8
 
15
 
9
 export default function deleteSelected(data: Data) {
16
 export default function deleteSelected(data: Data) {
10
   const { currentPageId } = data
17
   const { currentPageId } = data
11
 
18
 
12
-  const selectedIds = Array.from(data.selectedIds.values())
19
+  const selectedIds = getSelectedIds(data)
20
+  const selectedIdsArr = setToArray(selectedIds)
13
 
21
 
14
   const page = getPage(current(data))
22
   const page = getPage(current(data))
15
 
23
 
16
-  const childrenToDelete = selectedIds
24
+  const childrenToDelete = selectedIdsArr
17
     .flatMap((id) => getDocumentBranch(data, id))
25
     .flatMap((id) => getDocumentBranch(data, id))
18
     .map((id) => page.shapes[id])
26
     .map((id) => page.shapes[id])
19
 
27
 
20
-  data.selectedIds.clear()
28
+  selectedIds.clear()
21
 
29
 
22
   history.execute(
30
   history.execute(
23
     data,
31
     data,
28
       do(data) {
36
       do(data) {
29
         const page = getPage(data, currentPageId)
37
         const page = getPage(data, currentPageId)
30
 
38
 
31
-        for (let id of selectedIds) {
39
+        for (let id of selectedIdsArr) {
32
           const shape = page.shapes[id]
40
           const shape = page.shapes[id]
33
           if (!shape) {
41
           if (!shape) {
34
             console.error('no shape ' + id)
42
             console.error('no shape ' + id)
54
           delete page.shapes[shape.id]
62
           delete page.shapes[shape.id]
55
         }
63
         }
56
 
64
 
57
-        data.selectedIds.clear()
65
+        setSelectedIds(data, [])
58
       },
66
       },
59
       undo(data) {
67
       undo(data) {
60
         const page = getPage(data, currentPageId)
68
         const page = getPage(data, currentPageId)
75
           }
83
           }
76
         }
84
         }
77
 
85
 
78
-        data.selectedIds = new Set(selectedIds)
86
+        setSelectedIds(data, selectedIdsArr)
79
       },
87
       },
80
     })
88
     })
81
   )
89
   )

+ 5
- 11
state/commands/draw.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
 import { Data, DrawShape } from 'types'
3
 import { Data, DrawShape } from 'types'
4
-import { getPage } from 'utils/utils'
5
-import { getShapeUtils } from 'lib/shape-utils'
4
+import { getPage, setSelectedIds } from 'utils/utils'
6
 import { current } from 'immer'
5
 import { current } from 'immer'
7
 
6
 
8
-export default function drawCommand(
9
-  data: Data,
10
-  id: string,
11
-  points: number[][]
12
-) {
13
-  const restoreShape = current(getPage(data)).shapes[id] as DrawShape
14
-  getShapeUtils(restoreShape).setProperty(restoreShape, 'points', points)
7
+export default function drawCommand(data: Data, id: string) {
8
+  const restoreShape = getPage(current(data)).shapes[id] as DrawShape
15
 
9
 
16
   history.execute(
10
   history.execute(
17
     data,
11
     data,
24
           getPage(data).shapes[id] = restoreShape
18
           getPage(data).shapes[id] = restoreShape
25
         }
19
         }
26
 
20
 
27
-        data.selectedIds.clear()
21
+        setSelectedIds(data, [])
28
       },
22
       },
29
       undo(data) {
23
       undo(data) {
24
+        setSelectedIds(data, [])
30
         delete getPage(data).shapes[id]
25
         delete getPage(data).shapes[id]
31
-        data.selectedIds.clear()
32
       },
26
       },
33
     })
27
     })
34
   )
28
   )

+ 16
- 8
state/commands/duplicate.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
 import { Data } from 'types'
3
 import { Data } from 'types'
4
-import { getCurrentCamera, getPage, getSelectedShapes } from 'utils/utils'
4
+import {
5
+  getCurrentCamera,
6
+  getPage,
7
+  getSelectedIds,
8
+  getSelectedShapes,
9
+  setSelectedIds,
10
+} from 'utils/utils'
5
 import { v4 as uuid } from 'uuid'
11
 import { v4 as uuid } from 'uuid'
6
 import { current } from 'immer'
12
 import { current } from 'immer'
7
 import * as vec from 'utils/vec'
13
 import * as vec from 'utils/vec'
24
       do(data) {
30
       do(data) {
25
         const { shapes } = getPage(data, currentPageId)
31
         const { shapes } = getPage(data, currentPageId)
26
 
32
 
27
-        data.selectedIds.clear()
28
-
29
         for (const duplicate of duplicates) {
33
         for (const duplicate of duplicates) {
30
           shapes[duplicate.id] = duplicate
34
           shapes[duplicate.id] = duplicate
31
-          data.selectedIds.add(duplicate.id)
32
         }
35
         }
36
+
37
+        setSelectedIds(
38
+          data,
39
+          duplicates.map((d) => d.id)
40
+        )
33
       },
41
       },
34
       undo(data) {
42
       undo(data) {
35
         const { shapes } = getPage(data, currentPageId)
43
         const { shapes } = getPage(data, currentPageId)
36
-        data.selectedIds.clear()
37
 
44
 
38
         for (const duplicate of duplicates) {
45
         for (const duplicate of duplicates) {
39
           delete shapes[duplicate.id]
46
           delete shapes[duplicate.id]
40
         }
47
         }
41
 
48
 
42
-        for (let id in selectedShapes) {
43
-          data.selectedIds.add(id)
44
-        }
49
+        setSelectedIds(
50
+          data,
51
+          selectedShapes.map((d) => d.id)
52
+        )
45
       },
53
       },
46
     })
54
     })
47
   )
55
   )

+ 8
- 8
state/commands/generate.ts Dosyayı Görüntüle

1
-import Command from "./command"
2
-import history from "../history"
3
-import { CodeControl, Data, Shape } from "types"
4
-import { current } from "immer"
5
-import { getPage } from "utils/utils"
1
+import Command from './command'
2
+import history from '../history'
3
+import { CodeControl, Data, Shape } from 'types'
4
+import { current } from 'immer'
5
+import { getPage, getSelectedIds, setSelectedIds } from 'utils/utils'
6
 
6
 
7
 export default function generateCommand(
7
 export default function generateCommand(
8
   data: Data,
8
   data: Data,
33
   history.execute(
33
   history.execute(
34
     data,
34
     data,
35
     new Command({
35
     new Command({
36
-      name: "translate_shapes",
37
-      category: "canvas",
36
+      name: 'translate_shapes',
37
+      category: 'canvas',
38
       do(data) {
38
       do(data) {
39
         const { shapes } = getPage(data)
39
         const { shapes } = getPage(data)
40
 
40
 
41
-        data.selectedIds.clear()
41
+        setSelectedIds(data, [])
42
 
42
 
43
         // Remove previous generated shapes
43
         // Remove previous generated shapes
44
         for (let id in shapes) {
44
         for (let id in shapes) {

+ 8
- 5
state/commands/group.ts Dosyayı Görüntüle

4
 import {
4
 import {
5
   getCommonBounds,
5
   getCommonBounds,
6
   getPage,
6
   getPage,
7
+  getSelectedIds,
7
   getSelectedShapes,
8
   getSelectedShapes,
8
   getShape,
9
   getShape,
10
+  setSelectedIds,
9
 } from 'utils/utils'
11
 } from 'utils/utils'
10
 import { current } from 'immer'
12
 import { current } from 'immer'
11
 import { createShape, getShapeUtils } from 'lib/shape-utils'
13
 import { createShape, getShapeUtils } from 'lib/shape-utils'
15
 
17
 
16
 export default function groupCommand(data: Data) {
18
 export default function groupCommand(data: Data) {
17
   const cData = current(data)
19
   const cData = current(data)
18
-  const { currentPageId, selectedIds } = cData
20
+  const { currentPageId } = cData
21
+
22
+  const oldSelectedIds = getSelectedIds(cData)
19
 
23
 
20
   const initialShapes = getSelectedShapes(cData).sort(
24
   const initialShapes = getSelectedShapes(cData).sort(
21
     (a, b) => a.childIndex - b.childIndex
25
     (a, b) => a.childIndex - b.childIndex
108
             getShapeUtils(oldParent).setProperty(
112
             getShapeUtils(oldParent).setProperty(
109
               oldParent,
113
               oldParent,
110
               'children',
114
               'children',
111
-              oldParent.children.filter((id) => !selectedIds.has(id))
115
+              oldParent.children.filter((id) => !oldSelectedIds.has(id))
112
             )
116
             )
113
           }
117
           }
114
 
118
 
119
             .setProperty(shape, 'parentId', newGroupShape.id)
123
             .setProperty(shape, 'parentId', newGroupShape.id)
120
         })
124
         })
121
 
125
 
122
-        data.selectedIds.clear()
123
-        data.selectedIds.add(newGroupShape.id)
126
+        setSelectedIds(data, [newGroupShape.id])
124
       },
127
       },
125
       undo(data) {
128
       undo(data) {
126
         const { shapes } = getPage(data, currentPageId)
129
         const { shapes } = getPage(data, currentPageId)
157
         delete shapes[newGroupShape.id]
160
         delete shapes[newGroupShape.id]
158
 
161
 
159
         // Reselect the children of the group
162
         // Reselect the children of the group
160
-        data.selectedIds = new Set(initialShapeIds)
163
+        setSelectedIds(data, initialShapeIds)
161
       },
164
       },
162
     })
165
     })
163
   )
166
   )

+ 8
- 2
state/commands/move.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
 import { Data, MoveType, Shape } from 'types'
3
 import { Data, MoveType, Shape } from 'types'
4
-import { forceIntegerChildIndices, getChildren, getPage } from 'utils/utils'
4
+import {
5
+  forceIntegerChildIndices,
6
+  getChildren,
7
+  getPage,
8
+  getSelectedIds,
9
+  setToArray,
10
+} from 'utils/utils'
5
 import { getShapeUtils } from 'lib/shape-utils'
11
 import { getShapeUtils } from 'lib/shape-utils'
6
 
12
 
7
 export default function moveCommand(data: Data, type: MoveType) {
13
 export default function moveCommand(data: Data, type: MoveType) {
9
 
15
 
10
   const page = getPage(data)
16
   const page = getPage(data)
11
 
17
 
12
-  const selectedIds = Array.from(data.selectedIds.values())
18
+  const selectedIds = setToArray(getSelectedIds(data))
13
 
19
 
14
   const initialIndices = Object.fromEntries(
20
   const initialIndices = Object.fromEntries(
15
     selectedIds.map((id) => [id, page.shapes[id].childIndex])
21
     selectedIds.map((id) => [id, page.shapes[id].childIndex])

+ 10
- 2
state/commands/style.ts Dosyayı Görüntüle

1
 import Command from './command'
1
 import Command from './command'
2
 import history from '../history'
2
 import history from '../history'
3
 import { Data, ShapeStyles } from 'types'
3
 import { Data, ShapeStyles } from 'types'
4
-import { getDocumentBranch, getPage, getSelectedShapes } from 'utils/utils'
4
+import {
5
+  getDocumentBranch,
6
+  getPage,
7
+  getSelectedIds,
8
+  getSelectedShapes,
9
+  setToArray,
10
+} from 'utils/utils'
5
 import { getShapeUtils } from 'lib/shape-utils'
11
 import { getShapeUtils } from 'lib/shape-utils'
6
 import { current } from 'immer'
12
 import { current } from 'immer'
7
 
13
 
10
   const page = getPage(cData)
16
   const page = getPage(cData)
11
   const { currentPageId } = cData
17
   const { currentPageId } = cData
12
 
18
 
13
-  const shapesToStyle = Array.from(data.selectedIds.values())
19
+  const selectedIds = setToArray(getSelectedIds(data))
20
+
21
+  const shapesToStyle = selectedIds
14
     .flatMap((id) => getDocumentBranch(data, id))
22
     .flatMap((id) => getDocumentBranch(data, id))
15
     .map((id) => page.shapes[id])
23
     .map((id) => page.shapes[id])
16
 
24
 

+ 9
- 5
state/commands/transform-single.ts Dosyayı Görüntüle

4
 import { getShapeUtils } from 'lib/shape-utils'
4
 import { getShapeUtils } from 'lib/shape-utils'
5
 import { current } from 'immer'
5
 import { current } from 'immer'
6
 import { TransformSingleSnapshot } from 'state/sessions/transform-single-session'
6
 import { TransformSingleSnapshot } from 'state/sessions/transform-single-session'
7
-import { getPage, updateParents } from 'utils/utils'
7
+import {
8
+  getPage,
9
+  getSelectedIds,
10
+  setSelectedIds,
11
+  updateParents,
12
+} from 'utils/utils'
8
 
13
 
9
 export default function transformSingleCommand(
14
 export default function transformSingleCommand(
10
   data: Data,
15
   data: Data,
25
 
30
 
26
         const { shapes } = getPage(data, after.currentPageId)
31
         const { shapes } = getPage(data, after.currentPageId)
27
 
32
 
28
-        data.selectedIds.clear()
29
-        data.selectedIds.add(id)
33
+        setSelectedIds(data, [id])
30
 
34
 
31
         shapes[id] = shape
35
         shapes[id] = shape
32
 
36
 
38
         const { shapes } = getPage(data, before.currentPageId)
42
         const { shapes } = getPage(data, before.currentPageId)
39
 
43
 
40
         if (isCreating) {
44
         if (isCreating) {
41
-          data.selectedIds.clear()
45
+          setSelectedIds(data, [])
42
           delete shapes[id]
46
           delete shapes[id]
43
         } else {
47
         } else {
44
           const page = getPage(data)
48
           const page = getPage(data)
45
           page.shapes[id] = initialShape
49
           page.shapes[id] = initialShape
46
           updateParents(data, [id])
50
           updateParents(data, [id])
47
-          data.selectedIds = new Set([id])
51
+          setSelectedIds(data, [id])
48
         }
52
         }
49
       },
53
       },
50
     })
54
     })

+ 14
- 3
state/commands/translate.ts Dosyayı Görüntüle

2
 import history from '../history'
2
 import history from '../history'
3
 import { TranslateSnapshot } from 'state/sessions/translate-session'
3
 import { TranslateSnapshot } from 'state/sessions/translate-session'
4
 import { Data, GroupShape, Shape, ShapeType } from 'types'
4
 import { Data, GroupShape, Shape, ShapeType } from 'types'
5
-import { getDocumentBranch, getPage, updateParents } from 'utils/utils'
5
+import {
6
+  getDocumentBranch,
7
+  getPage,
8
+  setSelectedIds,
9
+  updateParents,
10
+} from 'utils/utils'
6
 import { getShapeUtils } from 'lib/shape-utils'
11
 import { getShapeUtils } from 'lib/shape-utils'
7
 import { v4 as uuid } from 'uuid'
12
 import { v4 as uuid } from 'uuid'
8
 
13
 
50
         }
55
         }
51
 
56
 
52
         // Set selected shapes
57
         // Set selected shapes
53
-        data.selectedIds = new Set(initialShapes.map((s) => s.id))
58
+        setSelectedIds(
59
+          data,
60
+          initialShapes.map((s) => s.id)
61
+        )
54
 
62
 
55
         // Update parents
63
         // Update parents
56
         updateParents(
64
         updateParents(
72
         if (isCloning) for (const { id } of clones) delete shapes[id]
80
         if (isCloning) for (const { id } of clones) delete shapes[id]
73
 
81
 
74
         // Set selected shapes
82
         // Set selected shapes
75
-        data.selectedIds = new Set(initialShapes.map((s) => s.id))
83
+        setSelectedIds(
84
+          data,
85
+          initialShapes.map((s) => s.id)
86
+        )
76
 
87
 
77
         // Restore children on parents
88
         // Restore children on parents
78
         initialParents.forEach(({ id, children }) => {
89
         initialParents.forEach(({ id, children }) => {

+ 10
- 6
state/commands/ungroup.ts Dosyayı Görüntüle

6
   getPage,
6
   getPage,
7
   getSelectedShapes,
7
   getSelectedShapes,
8
   getShape,
8
   getShape,
9
+  setSelectedIds,
9
 } from 'utils/utils'
10
 } from 'utils/utils'
10
 import { current } from 'immer'
11
 import { current } from 'immer'
11
 import { createShape, getShapeUtils } from 'lib/shape-utils'
12
 import { createShape, getShapeUtils } from 'lib/shape-utils'
14
 
15
 
15
 export default function ungroupCommand(data: Data) {
16
 export default function ungroupCommand(data: Data) {
16
   const cData = current(data)
17
   const cData = current(data)
17
-  const { currentPageId, selectedIds } = cData
18
+  const { currentPageId } = cData
18
 
19
 
19
   const selectedGroups = getSelectedShapes(cData)
20
   const selectedGroups = getSelectedShapes(cData)
20
     .filter((shape) => shape.type === ShapeType.Group)
21
     .filter((shape) => shape.type === ShapeType.Group)
55
               (oldGroupShape.children.length + 1)
56
               (oldGroupShape.children.length + 1)
56
           }
57
           }
57
 
58
 
58
-          data.selectedIds.clear()
59
-
60
           // Move shapes to page
59
           // Move shapes to page
61
           oldGroupShape.children
60
           oldGroupShape.children
62
             .map((id) => shapes[id])
61
             .map((id) => shapes[id])
63
             .forEach(({ id }, i) => {
62
             .forEach(({ id }, i) => {
64
               const shape = shapes[id]
63
               const shape = shapes[id]
65
-              data.selectedIds.add(id)
66
               getShapeUtils(shape)
64
               getShapeUtils(shape)
67
                 .setProperty(shape, 'parentId', oldGroupShape.parentId)
65
                 .setProperty(shape, 'parentId', oldGroupShape.parentId)
68
                 .setProperty(
66
                 .setProperty(
72
                 )
70
                 )
73
             })
71
             })
74
 
72
 
73
+          setSelectedIds(data, oldGroupShape.children)
74
+
75
           delete shapes[oldGroupShape.id]
75
           delete shapes[oldGroupShape.id]
76
         }
76
         }
77
       },
77
       },
78
       undo(data) {
78
       undo(data) {
79
         const { shapes } = getPage(data, currentPageId)
79
         const { shapes } = getPage(data, currentPageId)
80
-        selectedIds.clear()
80
+
81
         selectedGroups.forEach((group) => {
81
         selectedGroups.forEach((group) => {
82
-          selectedIds.add(group.id)
83
           shapes[group.id] = group
82
           shapes[group.id] = group
84
           group.children.forEach((id, i) => {
83
           group.children.forEach((id, i) => {
85
             const shape = shapes[id]
84
             const shape = shapes[id]
88
               .setProperty(shape, 'childIndex', i)
87
               .setProperty(shape, 'childIndex', i)
89
           })
88
           })
90
         })
89
         })
90
+
91
+        setSelectedIds(
92
+          data,
93
+          selectedGroups.map((g) => g.id)
94
+        )
91
       },
95
       },
92
     })
96
     })
93
   )
97
   )

+ 3
- 3
state/hacks.ts Dosyayı Görüntüle

2
 import {
2
 import {
3
   getCameraZoom,
3
   getCameraZoom,
4
   getCurrentCamera,
4
   getCurrentCamera,
5
+  getSelectedIds,
5
   screenToWorld,
6
   screenToWorld,
7
+  setToArray,
6
   setZoomCSS,
8
   setZoomCSS,
7
 } from 'utils/utils'
9
 } from 'utils/utils'
8
 import session from './session'
10
 import session from './session'
26
     info.shiftKey
28
     info.shiftKey
27
   )
29
   )
28
 
30
 
29
-  const selectedId = Array.from(data.selectedIds.values())[0]
31
+  const selectedId = setToArray(getSelectedIds(data))[0]
30
 
32
 
31
   const shape = data.document.pages[data.currentPageId].shapes[selectedId]
33
   const shape = data.document.pages[data.currentPageId].shapes[selectedId]
32
 
34
 
88
   const data = { ...state.data }
90
   const data = { ...state.data }
89
   session.current.update(data, screenToWorld(point, data))
91
   session.current.update(data, screenToWorld(point, data))
90
 
92
 
91
-  data.selectedIds = new Set(data.selectedIds)
92
-
93
   state.forceData(Object.freeze(data))
93
   state.forceData(Object.freeze(data))
94
 }
94
 }

+ 6
- 73
state/history.ts Dosyayı Görüntüle

1
-import { Data } from 'types'
1
+import { Data, Page, PageState } from 'types'
2
 import { BaseCommand } from './commands/command'
2
 import { BaseCommand } from './commands/command'
3
-
4
-const CURRENT_VERSION = 'code_slate_0.0.3'
3
+import storage from './storage'
5
 
4
 
6
 // A singleton to manage history changes.
5
 // A singleton to manage history changes.
7
 
6
 
8
-class BaseHistory<T> {
7
+class History<T extends Data> {
9
   private stack: BaseCommand<T>[] = []
8
   private stack: BaseCommand<T>[] = []
10
   private pointer = -1
9
   private pointer = -1
11
   private maxLength = 100
10
   private maxLength = 100
24
       this.pointer = this.maxLength - 1
23
       this.pointer = this.maxLength - 1
25
     }
24
     }
26
 
25
 
27
-    this.save(data)
26
+    storage.save(data)
28
   }
27
   }
29
 
28
 
30
   undo = (data: T) => {
29
   undo = (data: T) => {
33
     command.undo(data)
32
     command.undo(data)
34
     if (this.disabled) return
33
     if (this.disabled) return
35
     this.pointer--
34
     this.pointer--
36
-    this.save(data)
35
+    storage.save(data)
37
   }
36
   }
38
 
37
 
39
   redo = (data: T) => {
38
   redo = (data: T) => {
42
     command.redo(data, false)
41
     command.redo(data, false)
43
     if (this.disabled) return
42
     if (this.disabled) return
44
     this.pointer++
43
     this.pointer++
45
-    this.save(data)
46
-  }
47
-
48
-  load(data: T, id = CURRENT_VERSION) {
49
-    if (typeof window === 'undefined') return
50
-    if (typeof localStorage === 'undefined') return
51
-
52
-    const savedData = localStorage.getItem(id)
53
-
54
-    if (savedData !== null) {
55
-      Object.assign(data, this.restoreSavedData(JSON.parse(savedData)))
56
-    }
57
-  }
58
-
59
-  save = (data: T, id = CURRENT_VERSION) => {
60
-    if (typeof window === 'undefined') return
61
-    if (typeof localStorage === 'undefined') return
62
-
63
-    localStorage.setItem(id, JSON.stringify(this.prepareDataForSave(data)))
44
+    storage.save(data)
64
   }
45
   }
65
 
46
 
66
   disable = () => {
47
   disable = () => {
71
     this._enabled = true
52
     this._enabled = true
72
   }
53
   }
73
 
54
 
74
-  prepareDataForSave(data: T): any {
75
-    return { ...data }
76
-  }
77
-
78
-  restoreSavedData(data: any): T {
79
-    return { ...data }
80
-  }
81
-
82
   pop() {
55
   pop() {
83
     if (this.stack.length > 0) {
56
     if (this.stack.length > 0) {
84
       this.stack.pop()
57
       this.stack.pop()
91
   }
64
   }
92
 }
65
 }
93
 
66
 
94
-// App-specific
95
-
96
-class History extends BaseHistory<Data> {
97
-  constructor() {
98
-    super()
99
-  }
100
-
101
-  prepareDataForSave(data: Data): any {
102
-    const dataToSave: any = { ...data }
103
-
104
-    dataToSave.selectedIds = Array.from(data.selectedIds.values())
105
-
106
-    return dataToSave
107
-  }
108
-
109
-  restoreSavedData(data: any): Data {
110
-    const restoredData: Data = { ...data }
111
-
112
-    restoredData.selectedIds = new Set(restoredData.selectedIds)
113
-
114
-    // Also restore camera position, which is saved separately in this app
115
-    const cameraInfo = localStorage.getItem('code_slate_camera')
116
-
117
-    if (cameraInfo !== null) {
118
-      Object.assign(
119
-        restoredData.pageStates[data.currentPageId].camera,
120
-        JSON.parse(cameraInfo)
121
-      )
122
-
123
-      // And update the CSS property
124
-      document.documentElement.style.setProperty(
125
-        '--camera-zoom',
126
-        restoredData.pageStates[data.currentPageId].camera.zoom.toString()
127
-      )
128
-    }
129
-
130
-    return restoredData
131
-  }
132
-}
133
-
134
 export default new History()
67
 export default new History()

+ 8
- 2
state/sessions/arrow-session.ts Dosyayı Görüntüle

3
 import BaseSession from './base-session'
3
 import BaseSession from './base-session'
4
 import commands from 'state/commands'
4
 import commands from 'state/commands'
5
 import { current } from 'immer'
5
 import { current } from 'immer'
6
-import { getBoundsFromPoints, getPage, updateParents } from 'utils/utils'
6
+import {
7
+  getBoundsFromPoints,
8
+  getPage,
9
+  getSelectedIds,
10
+  setToArray,
11
+  updateParents,
12
+} from 'utils/utils'
7
 import { getShapeUtils } from 'lib/shape-utils'
13
 import { getShapeUtils } from 'lib/shape-utils'
8
 
14
 
9
 export default class ArrowSession extends BaseSession {
15
 export default class ArrowSession extends BaseSession {
116
   return {
122
   return {
117
     id,
123
     id,
118
     initialShape,
124
     initialShape,
119
-    selectedIds: new Set(data.selectedIds),
125
+    selectedIds: setToArray(getSelectedIds(data)),
120
     currentPageId: data.currentPageId,
126
     currentPageId: data.currentPageId,
121
   }
127
   }
122
 }
128
 }

+ 16
- 7
state/sessions/brush-session.ts Dosyayı Görüntüle

2
 import { Bounds, Data, ShapeType } from 'types'
2
 import { Bounds, Data, ShapeType } from 'types'
3
 import BaseSession from './base-session'
3
 import BaseSession from './base-session'
4
 import { getShapeUtils } from 'lib/shape-utils'
4
 import { getShapeUtils } from 'lib/shape-utils'
5
-import { getBoundsFromPoints, getPage, getShapes } from 'utils/utils'
5
+import {
6
+  getBoundsFromPoints,
7
+  getPage,
8
+  getSelectedIds,
9
+  getShapes,
10
+  setSelectedIds,
11
+  setToArray,
12
+} from 'utils/utils'
6
 import * as vec from 'utils/vec'
13
 import * as vec from 'utils/vec'
7
 import state from 'state/state'
14
 import state from 'state/state'
8
 
15
 
25
 
32
 
26
     const hits = new Set<string>([])
33
     const hits = new Set<string>([])
27
 
34
 
35
+    const selectedIds = getSelectedIds(data)
36
+
28
     for (let id in snapshot.shapeHitTests) {
37
     for (let id in snapshot.shapeHitTests) {
29
       const { test, selectId } = snapshot.shapeHitTests[id]
38
       const { test, selectId } = snapshot.shapeHitTests[id]
30
       if (!hits.has(selectId)) {
39
       if (!hits.has(selectId)) {
32
           hits.add(selectId)
41
           hits.add(selectId)
33
 
42
 
34
           // When brushing a shape, select its top group parent.
43
           // When brushing a shape, select its top group parent.
35
-          if (!data.selectedIds.has(selectId)) {
36
-            data.selectedIds.add(selectId)
44
+          if (!selectedIds.has(selectId)) {
45
+            selectedIds.add(selectId)
37
           }
46
           }
38
-        } else if (data.selectedIds.has(selectId)) {
39
-          data.selectedIds.delete(selectId)
47
+        } else if (selectedIds.has(selectId)) {
48
+          selectedIds.delete(selectId)
40
         }
49
         }
41
       }
50
       }
42
     }
51
     }
46
 
55
 
47
   cancel = (data: Data) => {
56
   cancel = (data: Data) => {
48
     data.brush = undefined
57
     data.brush = undefined
49
-    data.selectedIds = new Set(this.snapshot.selectedIds)
58
+    setSelectedIds(data, this.snapshot.selectedIds)
50
   }
59
   }
51
 
60
 
52
   complete = (data: Data) => {
61
   complete = (data: Data) => {
61
  */
70
  */
62
 export function getBrushSnapshot(data: Data) {
71
 export function getBrushSnapshot(data: Data) {
63
   return {
72
   return {
64
-    selectedIds: new Set(data.selectedIds),
73
+    selectedIds: setToArray(getSelectedIds(data)),
65
     shapeHitTests: Object.fromEntries(
74
     shapeHitTests: Object.fromEntries(
66
       getShapes(state.data)
75
       getShapes(state.data)
67
         .filter((shape) => shape.type !== ShapeType.Group)
76
         .filter((shape) => shape.type !== ShapeType.Group)

+ 8
- 8
state/sessions/direction-session.ts Dosyayı Görüntüle

1
-import { Data, LineShape, RayShape } from "types"
2
-import * as vec from "utils/vec"
3
-import BaseSession from "./base-session"
4
-import commands from "state/commands"
5
-import { current } from "immer"
6
-import { getPage } from "utils/utils"
1
+import { Data, LineShape, RayShape } from 'types'
2
+import * as vec from 'utils/vec'
3
+import BaseSession from './base-session'
4
+import commands from 'state/commands'
5
+import { current } from 'immer'
6
+import { getPage, getSelectedIds } from 'utils/utils'
7
 
7
 
8
 export default class DirectionSession extends BaseSession {
8
 export default class DirectionSession extends BaseSession {
9
   delta = [0, 0]
9
   delta = [0, 0]
47
 
47
 
48
   let snapshapes: { id: string; direction: number[] }[] = []
48
   let snapshapes: { id: string; direction: number[] }[] = []
49
 
49
 
50
-  data.selectedIds.forEach((id) => {
50
+  getSelectedIds(data).forEach((id) => {
51
     const shape = shapes[id]
51
     const shape = shapes[id]
52
-    if ("direction" in shape) {
52
+    if ('direction' in shape) {
53
       snapshapes.push({ id: shape.id, direction: shape.direction })
53
       snapshapes.push({ id: shape.id, direction: shape.direction })
54
     }
54
     }
55
   })
55
   })

+ 5
- 24
state/sessions/draw-session.ts Dosyayı Görüntüle

95
   }
95
   }
96
 
96
 
97
   complete = (data: Data) => {
97
   complete = (data: Data) => {
98
-    if (this.points.length > 1) {
99
-      let minX = Infinity
100
-      let minY = Infinity
101
-      const pts = [...this.points]
102
-
103
-      for (let pt of pts) {
104
-        minX = Math.min(pt[0], minX)
105
-        minY = Math.min(pt[1], minY)
106
-      }
107
-
108
-      for (let pt of pts) {
109
-        pt[0] -= minX
110
-        pt[1] -= minY
111
-      }
112
-
113
-      const { snapshot } = this
114
-      const page = getPage(data)
115
-      const shape = page.shapes[snapshot.id] as DrawShape
116
-
117
-      getShapeUtils(shape)
118
-        .setProperty(shape, 'points', pts)
119
-        .setProperty(shape, 'point', vec.add(shape.point, [minX, minY]))
120
-    }
98
+    const { snapshot } = this
99
+    const page = getPage(data)
100
+    const shape = page.shapes[snapshot.id] as DrawShape
121
 
101
 
122
-    commands.draw(data, this.snapshot.id, this.points)
102
+    getShapeUtils(shape).onSessionComplete(shape)
103
+    commands.draw(data, this.snapshot.id)
123
   }
104
   }
124
 }
105
 }
125
 
106
 

+ 3
- 1
state/sessions/rotate-session.ts Dosyayı Görüntüle

13
   getShapeBounds,
13
   getShapeBounds,
14
   updateParents,
14
   updateParents,
15
   getDocumentBranch,
15
   getDocumentBranch,
16
+  setToArray,
17
+  getSelectedIds,
16
 } from 'utils/utils'
18
 } from 'utils/utils'
17
 import { getShapeUtils } from 'lib/shape-utils'
19
 import { getShapeUtils } from 'lib/shape-utils'
18
 
20
 
101
   const cData = current(data)
103
   const cData = current(data)
102
   const page = getPage(cData)
104
   const page = getPage(cData)
103
 
105
 
104
-  const initialShapes = Array.from(cData.selectedIds.values())
106
+  const initialShapes = setToArray(getSelectedIds(data))
105
     .flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id]))
107
     .flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id]))
106
     .filter((shape) => !shape.isLocked)
108
     .filter((shape) => !shape.isLocked)
107
 
109
 

+ 3
- 1
state/sessions/transform-session.ts Dosyayı Görüntüle

11
   getDocumentBranch,
11
   getDocumentBranch,
12
   getPage,
12
   getPage,
13
   getRelativeTransformedBoundingBox,
13
   getRelativeTransformedBoundingBox,
14
+  getSelectedIds,
14
   getSelectedShapes,
15
   getSelectedShapes,
15
   getShapes,
16
   getShapes,
16
   getTransformedBoundingBox,
17
   getTransformedBoundingBox,
18
+  setToArray,
17
   updateParents,
19
   updateParents,
18
 } from 'utils/utils'
20
 } from 'utils/utils'
19
 
21
 
118
   const { currentPageId } = cData
120
   const { currentPageId } = cData
119
   const page = getPage(cData)
121
   const page = getPage(cData)
120
 
122
 
121
-  const initialShapes = Array.from(cData.selectedIds.values())
123
+  const initialShapes = setToArray(getSelectedIds(data))
122
     .flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id]))
124
     .flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id]))
123
     .filter((shape) => !shape.isLocked)
125
     .filter((shape) => !shape.isLocked)
124
 
126
 

+ 10
- 8
state/sessions/translate-session.ts Dosyayı Görüntüle

9
   getDocumentBranch,
9
   getDocumentBranch,
10
   getPage,
10
   getPage,
11
   getSelectedShapes,
11
   getSelectedShapes,
12
+  setSelectedIds,
12
   updateParents,
13
   updateParents,
13
 } from 'utils/utils'
14
 } from 'utils/utils'
14
 import { getShapeUtils } from 'lib/shape-utils'
15
 import { getShapeUtils } from 'lib/shape-utils'
47
     if (isCloning) {
48
     if (isCloning) {
48
       if (!this.isCloning) {
49
       if (!this.isCloning) {
49
         this.isCloning = true
50
         this.isCloning = true
50
-        data.selectedIds.clear()
51
 
51
 
52
         for (const { id, point } of initialShapes) {
52
         for (const { id, point } of initialShapes) {
53
           const shape = shapes[id]
53
           const shape = shapes[id]
54
           getShapeUtils(shape).translateTo(shape, point)
54
           getShapeUtils(shape).translateTo(shape, point)
55
         }
55
         }
56
 
56
 
57
-        data.selectedIds.clear()
58
-
59
         for (const clone of clones) {
57
         for (const clone of clones) {
60
-          data.selectedIds.add(clone.id)
61
           shapes[clone.id] = { ...clone }
58
           shapes[clone.id] = { ...clone }
62
           const parent = shapes[clone.parentId]
59
           const parent = shapes[clone.parentId]
63
           if (!parent) continue
60
           if (!parent) continue
66
             clone.id,
63
             clone.id,
67
           ])
64
           ])
68
         }
65
         }
66
+
67
+        setSelectedIds(
68
+          data,
69
+          clones.map((c) => c.id)
70
+        )
69
       }
71
       }
70
 
72
 
71
       for (const { id, point } of clones) {
73
       for (const { id, point } of clones) {
80
     } else {
82
     } else {
81
       if (this.isCloning) {
83
       if (this.isCloning) {
82
         this.isCloning = false
84
         this.isCloning = false
83
-        data.selectedIds.clear()
84
 
85
 
85
-        for (const { id } of initialShapes) {
86
-          data.selectedIds.add(id)
87
-        }
86
+        setSelectedIds(
87
+          data,
88
+          initialShapes.map((c) => c.id)
89
+        )
88
 
90
 
89
         for (const clone of clones) {
91
         for (const clone of clones) {
90
           delete shapes[clone.id]
92
           delete shapes[clone.id]

+ 42
- 29
state/state.ts Dosyayı Görüntüle

1
 import { createSelectorHook, createState } from '@state-designer/react'
1
 import { createSelectorHook, createState } from '@state-designer/react'
2
+import { updateFromCode } from 'lib/code/generate'
3
+import { createShape, getShapeUtils } from 'lib/shape-utils'
2
 import * as vec from 'utils/vec'
4
 import * as vec from 'utils/vec'
3
 import inputs from './inputs'
5
 import inputs from './inputs'
4
 import { defaultDocument } from './data'
6
 import { defaultDocument } from './data'
5
-import { createShape, getShapeUtils } from 'lib/shape-utils'
6
-import history from 'state/history'
7
+import history from './history'
8
+import storage from './storage'
7
 import * as Sessions from './sessions'
9
 import * as Sessions from './sessions'
8
 import commands from './commands'
10
 import commands from './commands'
9
-import { updateFromCode } from 'lib/code/generate'
10
 import {
11
 import {
11
   clamp,
12
   clamp,
12
   getChildren,
13
   getChildren,
26
   getBoundsCenter,
27
   getBoundsCenter,
27
   getDocumentBranch,
28
   getDocumentBranch,
28
   getCameraZoom,
29
   getCameraZoom,
30
+  getSelectedIds,
31
+  setSelectedIds,
29
 } from 'utils/utils'
32
 } from 'utils/utils'
30
 import {
33
 import {
31
   Data,
34
   Data,
69
   boundsRotation: 0,
72
   boundsRotation: 0,
70
   pointedId: null,
73
   pointedId: null,
71
   hoveredId: null,
74
   hoveredId: null,
72
-  selectedIds: new Set([]),
73
   currentPageId: 'page1',
75
   currentPageId: 'page1',
74
   currentParentId: 'page1',
76
   currentParentId: 'page1',
75
   currentCodeFileId: 'file0',
77
   currentCodeFileId: 'file0',
77
   document: defaultDocument,
79
   document: defaultDocument,
78
   pageStates: {
80
   pageStates: {
79
     page1: {
81
     page1: {
82
+      selectedIds: new Set([]),
80
       camera: {
83
       camera: {
81
         point: [0, 0],
84
         point: [0, 0],
82
         zoom: 1,
85
         zoom: 1,
83
       },
86
       },
84
     },
87
     },
85
     page2: {
88
     page2: {
89
+      selectedIds: new Set([]),
86
       camera: {
90
       camera: {
87
         point: [0, 0],
91
         point: [0, 0],
88
         zoom: 1,
92
         zoom: 1,
164
           do: 'deleteSelection',
168
           do: 'deleteSelection',
165
           else: ['selectAll', 'deleteSelection'],
169
           else: ['selectAll', 'deleteSelection'],
166
         },
170
         },
167
-        CHANGED_CURRENT_PAGE: ['clearSelectedIds', 'setCurrentPage'],
171
+        CHANGED_PAGE: 'changePage',
168
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
172
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
169
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
173
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
170
       },
174
       },
743
       return vec.dist2(payload.origin, payload.point) > 8
747
       return vec.dist2(payload.origin, payload.point) > 8
744
     },
748
     },
745
     isPointedShapeSelected(data) {
749
     isPointedShapeSelected(data) {
746
-      return data.selectedIds.has(data.pointedId)
750
+      return getSelectedIds(data).has(data.pointedId)
747
     },
751
     },
748
     isPressingShiftKey(data, payload: PointerInfo) {
752
     isPressingShiftKey(data, payload: PointerInfo) {
749
       return payload.shiftKey
753
       return payload.shiftKey
769
       return payload.target === 'rotate'
773
       return payload.target === 'rotate'
770
     },
774
     },
771
     hasSelection(data) {
775
     hasSelection(data) {
772
-      return data.selectedIds.size > 0
776
+      return getSelectedIds(data).size > 0
773
     },
777
     },
774
     hasMultipleSelection(data) {
778
     hasMultipleSelection(data) {
775
-      return data.selectedIds.size > 1
779
+      return getSelectedIds(data).size > 1
776
     },
780
     },
777
     isToolLocked(data) {
781
     isToolLocked(data) {
778
       return data.settings.isToolLocked
782
       return data.settings.isToolLocked
791
   },
795
   },
792
   actions: {
796
   actions: {
793
     /* ---------------------- Pages --------------------- */
797
     /* ---------------------- Pages --------------------- */
794
-    setCurrentPage(data, payload: { id: string }) {
798
+    changePage(data, payload: { id: string }) {
795
       commands.changePage(data, payload.id)
799
       commands.changePage(data, payload.id)
796
     },
800
     },
797
     createPage(data) {
801
     createPage(data) {
818
 
822
 
819
       getPage(data).shapes[shape.id] = shape
823
       getPage(data).shapes[shape.id] = shape
820
 
824
 
821
-      data.selectedIds.clear()
822
-      data.selectedIds.add(shape.id)
825
+      setSelectedIds(data, [shape.id])
823
     },
826
     },
824
     /* -------------------- Sessions -------------------- */
827
     /* -------------------- Sessions -------------------- */
825
 
828
 
902
 
905
 
903
     // Dragging Handle
906
     // Dragging Handle
904
     startHandleSession(data, payload: PointerInfo) {
907
     startHandleSession(data, payload: PointerInfo) {
905
-      const shapeId = Array.from(data.selectedIds.values())[0]
908
+      const shapeId = Array.from(getSelectedIds(data).values())[0]
906
       const handleId = payload.target
909
       const handleId = payload.target
907
 
910
 
908
       session.current = new Sessions.HandleSession(
911
       session.current = new Sessions.HandleSession(
939
     ) {
942
     ) {
940
       const point = screenToWorld(inputs.pointer.origin, data)
943
       const point = screenToWorld(inputs.pointer.origin, data)
941
       session.current =
944
       session.current =
942
-        data.selectedIds.size === 1
945
+        getSelectedIds(data).size === 1
943
           ? new Sessions.TransformSingleSession(data, payload.target, point)
946
           ? new Sessions.TransformSingleSession(data, payload.target, point)
944
           : new Sessions.TransformSession(data, payload.target, point)
947
           : new Sessions.TransformSession(data, payload.target, point)
945
     },
948
     },
981
 
984
 
982
     // Drawing
985
     // Drawing
983
     startDrawSession(data, payload: PointerInfo) {
986
     startDrawSession(data, payload: PointerInfo) {
984
-      const id = Array.from(data.selectedIds.values())[0]
987
+      const id = Array.from(getSelectedIds(data).values())[0]
985
       session.current = new Sessions.DrawSession(
988
       session.current = new Sessions.DrawSession(
986
         data,
989
         data,
987
         id,
990
         id,
1008
 
1011
 
1009
     // Arrow
1012
     // Arrow
1010
     startArrowSession(data, payload: PointerInfo) {
1013
     startArrowSession(data, payload: PointerInfo) {
1011
-      const id = Array.from(data.selectedIds.values())[0]
1014
+      const id = Array.from(getSelectedIds(data).values())[0]
1012
       session.current = new Sessions.ArrowSession(
1015
       session.current = new Sessions.ArrowSession(
1013
         data,
1016
         data,
1014
         id,
1017
         id,
1047
     /* -------------------- Selection ------------------- */
1050
     /* -------------------- Selection ------------------- */
1048
 
1051
 
1049
     selectAll(data) {
1052
     selectAll(data) {
1050
-      const { selectedIds } = data
1053
+      const selectedIds = getSelectedIds(data)
1051
       const page = getPage(data)
1054
       const page = getPage(data)
1052
       selectedIds.clear()
1055
       selectedIds.clear()
1053
       for (let id in page.shapes) {
1056
       for (let id in page.shapes) {
1078
       data.pointedId = undefined
1081
       data.pointedId = undefined
1079
     },
1082
     },
1080
     clearSelectedIds(data) {
1083
     clearSelectedIds(data) {
1081
-      data.selectedIds.clear()
1084
+      setSelectedIds(data, [])
1082
     },
1085
     },
1083
     pullPointedIdFromSelectedIds(data) {
1086
     pullPointedIdFromSelectedIds(data) {
1084
-      const { selectedIds, pointedId } = data
1087
+      const { pointedId } = data
1088
+      const selectedIds = getSelectedIds(data)
1085
       selectedIds.delete(pointedId)
1089
       selectedIds.delete(pointedId)
1086
     },
1090
     },
1087
     pushPointedIdToSelectedIds(data) {
1091
     pushPointedIdToSelectedIds(data) {
1088
-      data.selectedIds.add(data.pointedId)
1092
+      getSelectedIds(data).add(data.pointedId)
1089
     },
1093
     },
1090
     moveSelection(data, payload: { type: MoveType }) {
1094
     moveSelection(data, payload: { type: MoveType }) {
1091
       commands.move(data, payload.type)
1095
       commands.move(data, payload.type)
1311
     popHistory() {
1315
     popHistory() {
1312
       history.pop()
1316
       history.pop()
1313
     },
1317
     },
1314
-    forceSave(data) {
1315
-      history.save(data)
1316
-    },
1317
     enableHistory() {
1318
     enableHistory() {
1318
       history.enable()
1319
       history.enable()
1319
     },
1320
     },
1377
 
1378
 
1378
       history.disable()
1379
       history.disable()
1379
 
1380
 
1380
-      data.selectedIds.clear()
1381
+      setSelectedIds(data, [])
1381
 
1382
 
1382
       try {
1383
       try {
1383
         const { shapes } = updateFromCode(
1384
         const { shapes } = updateFromCode(
1407
 
1408
 
1408
     /* ---------------------- Data ---------------------- */
1409
     /* ---------------------- Data ---------------------- */
1409
 
1410
 
1411
+    forceSave(data) {
1412
+      storage.save(data)
1413
+    },
1414
+
1415
+    savePage(data) {
1416
+      storage.savePage(data, data.currentPageId)
1417
+    },
1418
+
1419
+    loadPage(data) {
1420
+      storage.loadPage(data, data.currentPageId)
1421
+    },
1422
+
1410
     saveCode(data, payload: { code: string }) {
1423
     saveCode(data, payload: { code: string }) {
1411
       data.document.code[data.currentCodeFileId].code = payload.code
1424
       data.document.code[data.currentCodeFileId].code = payload.code
1412
-      history.save(data)
1425
+      storage.save(data)
1413
     },
1426
     },
1414
 
1427
 
1415
     restoreSavedData(data) {
1428
     restoreSavedData(data) {
1416
-      history.load(data)
1429
+      storage.load(data)
1417
     },
1430
     },
1418
 
1431
 
1419
     clearBoundsRotation(data) {
1432
     clearBoundsRotation(data) {
1422
   },
1435
   },
1423
   values: {
1436
   values: {
1424
     selectedIds(data) {
1437
     selectedIds(data) {
1425
-      return new Set(data.selectedIds)
1438
+      return new Set(getSelectedIds(data))
1426
     },
1439
     },
1427
     selectedBounds(data) {
1440
     selectedBounds(data) {
1428
-      const { selectedIds } = data
1441
+      const selectedIds = getSelectedIds(data)
1429
 
1442
 
1430
       const page = getPage(data)
1443
       const page = getPage(data)
1431
 
1444
 
1438
       if (selectedIds.size === 1) {
1451
       if (selectedIds.size === 1) {
1439
         if (!shapes[0]) {
1452
         if (!shapes[0]) {
1440
           console.error('Could not find that shape! Clearing selected IDs.')
1453
           console.error('Could not find that shape! Clearing selected IDs.')
1441
-          data.selectedIds.clear()
1454
+          setSelectedIds(data, [])
1442
           return null
1455
           return null
1443
         }
1456
         }
1444
 
1457
 
1497
       return commonBounds
1510
       return commonBounds
1498
     },
1511
     },
1499
     selectedStyle(data) {
1512
     selectedStyle(data) {
1500
-      const selectedIds = Array.from(data.selectedIds.values())
1513
+      const selectedIds = Array.from(getSelectedIds(data).values())
1501
       const { currentStyle } = data
1514
       const { currentStyle } = data
1502
 
1515
 
1503
       if (selectedIds.length === 0) {
1516
       if (selectedIds.length === 0) {

+ 139
- 0
state/storage.ts Dosyayı Görüntüle

1
+import { Data, Page, PageState } from 'types'
2
+import { setToArray } from 'utils/utils'
3
+
4
+const CURRENT_VERSION = 'code_slate_0.0.4'
5
+const DOCUMENT_ID = '0001'
6
+
7
+function storageId(label: string, id: string) {
8
+  return `${CURRENT_VERSION}_doc_${DOCUMENT_ID}_${label}_${id}`
9
+}
10
+
11
+class Storage {
12
+  // Saving
13
+  load(data: Data, id = CURRENT_VERSION) {
14
+    if (typeof window === 'undefined') return
15
+    if (typeof localStorage === 'undefined') return
16
+
17
+    // Load data from local storage
18
+    const savedData = localStorage.getItem(id)
19
+
20
+    if (savedData !== null) {
21
+      const restoredData = JSON.parse(savedData)
22
+
23
+      // Empty shapes in state for each page
24
+      for (let key in restoredData.document.pages) {
25
+        restoredData.document.pages[key].shapes = {}
26
+      }
27
+
28
+      // Empty page states for each page
29
+      for (let key in restoredData.pageStates) {
30
+        restoredData.document.pages[key].shapes = {}
31
+      }
32
+
33
+      // Merge restored data into state
34
+      Object.assign(data, restoredData)
35
+
36
+      // Load current page
37
+      this.loadPage(data, data.currentPageId)
38
+    }
39
+  }
40
+
41
+  save = (data: Data, id = CURRENT_VERSION) => {
42
+    if (typeof window === 'undefined') return
43
+    if (typeof localStorage === 'undefined') return
44
+
45
+    const dataToSave: any = { ...data }
46
+
47
+    // Don't save pageStates
48
+    dataToSave.pageStates = {}
49
+
50
+    // Save current data to local storage
51
+    localStorage.setItem(id, JSON.stringify(dataToSave))
52
+
53
+    // Save current page
54
+    this.savePage(data, data.currentPageId)
55
+  }
56
+
57
+  savePage(data: Data, pageId: string) {
58
+    if (typeof window === 'undefined') return
59
+    if (typeof localStorage === 'undefined') return
60
+
61
+    // Save page
62
+    const page = data.document.pages[pageId]
63
+
64
+    localStorage.setItem(storageId('page', pageId), JSON.stringify(page))
65
+
66
+    // Save page state
67
+
68
+    let currentPageState = {
69
+      camera: {
70
+        point: [0, 0],
71
+        zoom: 1,
72
+      },
73
+      selectedIds: new Set([]),
74
+      ...data.pageStates[pageId],
75
+    }
76
+
77
+    const pageState = {
78
+      ...currentPageState,
79
+      selectedIds: setToArray(currentPageState.selectedIds),
80
+    }
81
+
82
+    localStorage.setItem(
83
+      storageId('pageState', pageId),
84
+      JSON.stringify(pageState)
85
+    )
86
+  }
87
+
88
+  loadPage(data: Data, pageId: string) {
89
+    if (typeof window === 'undefined') return
90
+    if (typeof localStorage === 'undefined') return
91
+
92
+    // Load page and merge into state
93
+    const savedPage = localStorage.getItem(storageId('page', pageId))
94
+
95
+    if (savedPage !== null) {
96
+      const restored: Page = JSON.parse(savedPage)
97
+      data.document.pages[pageId] = restored
98
+    }
99
+
100
+    // Load page state and merge into state
101
+    const savedPageState = localStorage.getItem(storageId('pageState', pageId))
102
+
103
+    if (savedPageState !== null) {
104
+      const restored: PageState = JSON.parse(savedPageState)
105
+      restored.selectedIds = new Set(restored.selectedIds)
106
+      data.pageStates[pageId] = restored
107
+    } else {
108
+      data.pageStates[pageId] = {
109
+        camera: {
110
+          point: [0, 0],
111
+          zoom: 1,
112
+        },
113
+        selectedIds: new Set([]),
114
+      }
115
+    }
116
+
117
+    // Empty shapes in state for other pages
118
+    for (let key in data.document.pages) {
119
+      if (key === pageId) continue
120
+      data.document.pages[key].shapes = {}
121
+    }
122
+
123
+    // Empty page states for other pages
124
+    for (let key in data.pageStates) {
125
+      if (key === pageId) continue
126
+      data.document.pages[key].shapes = {}
127
+    }
128
+
129
+    // Update camera
130
+    document.documentElement.style.setProperty(
131
+      '--camera-zoom',
132
+      data.pageStates[data.currentPageId].camera.zoom.toString()
133
+    )
134
+  }
135
+}
136
+
137
+const storage = new Storage()
138
+
139
+export default storage

+ 6
- 0
todo.md Dosyayı Görüntüle

19
 
19
 
20
 - shift dragging arrow handles should lock to directions
20
 - shift dragging arrow handles should lock to directions
21
 - fix undo/redo on rotated arrows
21
 - fix undo/redo on rotated arrows
22
+- fix shift-rotation
23
+
24
+## Pages
25
+
26
+- [x] Make selection part of page state
27
+- [ ] Allow only one page to be in the document at a time

+ 1
- 1
types.ts Dosyayı Görüntüle

22
   activeTool: ShapeType | 'select'
22
   activeTool: ShapeType | 'select'
23
   brush?: Bounds
23
   brush?: Bounds
24
   boundsRotation: number
24
   boundsRotation: number
25
-  selectedIds: Set<string>
26
   pointedId?: string
25
   pointedId?: string
27
   hoveredId?: string
26
   hoveredId?: string
28
   currentPageId: string
27
   currentPageId: string
49
 }
48
 }
50
 
49
 
51
 export interface PageState {
50
 export interface PageState {
51
+  selectedIds: Set<string>
52
   camera: {
52
   camera: {
53
     point: number[]
53
     point: number[]
54
     zoom: number
54
     zoom: number

+ 14
- 1
utils/utils.ts Dosyayı Görüntüle

1381
 
1381
 
1382
 export function getSelectedShapes(data: Data, pageId = data.currentPageId) {
1382
 export function getSelectedShapes(data: Data, pageId = data.currentPageId) {
1383
   const page = getPage(data, pageId)
1383
   const page = getPage(data, pageId)
1384
-  const ids = Array.from(data.selectedIds.values())
1384
+  const ids = setToArray(getSelectedIds(data))
1385
   return ids.map((id) => page.shapes[id])
1385
   return ids.map((id) => page.shapes[id])
1386
 }
1386
 }
1387
 
1387
 
1664
     ...shape.children.flatMap((childId) => getDocumentBranch(data, childId)),
1664
     ...shape.children.flatMap((childId) => getDocumentBranch(data, childId)),
1665
   ]
1665
   ]
1666
 }
1666
 }
1667
+
1668
+export function getSelectedIds(data: Data) {
1669
+  return data.pageStates[data.currentPageId].selectedIds
1670
+}
1671
+
1672
+export function setSelectedIds(data: Data, ids: string[]) {
1673
+  data.pageStates[data.currentPageId].selectedIds = new Set(ids)
1674
+  return data.pageStates[data.currentPageId].selectedIds
1675
+}
1676
+
1677
+export function setToArray<T>(set: Set<T>): T[] {
1678
+  return Array.from(set.values())
1679
+}

Loading…
İptal
Kaydet