Browse Source

Adds deleting, z-index re-ordering

main
Steve Ruiz 4 years ago
parent
commit
780eba3d98
7 changed files with 263 additions and 125 deletions
  1. 14
    10
      hooks/useKeyboardEvents.ts
  2. 44
    0
      state/commands/delete-selected.ts
  3. 4
    0
      state/commands/index.ts
  4. 165
    0
      state/commands/move.ts
  5. 7
    114
      state/state.ts
  6. 8
    0
      types.ts
  7. 21
    1
      utils/utils.ts

+ 14
- 10
hooks/useKeyboardEvents.ts View File

@@ -24,23 +24,27 @@ export default function useKeyboardEvents() {
24 24
           }
25 25
           break
26 26
         }
27
+        case "‘": {
28
+          if (metaKey(e)) {
29
+            state.send("MOVED_TO_FRONT", getKeyboardEventInfo(e))
30
+          }
31
+          break
32
+        }
33
+        case "“": {
34
+          if (metaKey(e)) {
35
+            state.send("MOVED_TO_BACK", getKeyboardEventInfo(e))
36
+          }
37
+          break
38
+        }
27 39
         case "]": {
28 40
           if (metaKey(e)) {
29
-            if (e.altKey) {
30
-              state.send("MOVED_TO_FRONT", getKeyboardEventInfo(e))
31
-            } else {
32
-              state.send("MOVED_FORWARD", getKeyboardEventInfo(e))
33
-            }
41
+            state.send("MOVED_FORWARD", getKeyboardEventInfo(e))
34 42
           }
35 43
           break
36 44
         }
37 45
         case "[": {
38 46
           if (metaKey(e)) {
39
-            if (e.altKey) {
40
-              state.send("MOVED_TO_BACK", getKeyboardEventInfo(e))
41
-            } else {
42
-              state.send("MOVED_BACKWARD", getKeyboardEventInfo(e))
43
-            }
47
+            state.send("MOVED_BACKWARD", getKeyboardEventInfo(e))
44 48
           }
45 49
           break
46 50
         }

+ 44
- 0
state/commands/delete-selected.ts View File

@@ -0,0 +1,44 @@
1
+import Command from "./command"
2
+import history from "../history"
3
+import { TranslateSnapshot } from "state/sessions/translate-session"
4
+import { Data } from "types"
5
+import { getPage } from "utils/utils"
6
+import { current } from "immer"
7
+
8
+export default function deleteSelected(data: Data) {
9
+  const { currentPageId } = data
10
+
11
+  const selectedIds = Array.from(data.selectedIds.values())
12
+
13
+  const page = getPage(current(data))
14
+
15
+  const shapes = selectedIds.map((id) => page.shapes[id])
16
+
17
+  data.selectedIds.clear()
18
+
19
+  history.execute(
20
+    data,
21
+    new Command({
22
+      name: "delete_shapes",
23
+      category: "canvas",
24
+      manualSelection: true,
25
+      do(data) {
26
+        const page = getPage(data, currentPageId)
27
+
28
+        for (let id of selectedIds) {
29
+          delete page.shapes[id]
30
+        }
31
+
32
+        data.selectedIds.clear()
33
+      },
34
+      undo(data) {
35
+        const page = getPage(data, currentPageId)
36
+        data.selectedIds.clear()
37
+        for (let shape of shapes) {
38
+          page.shapes[shape.id] = shape
39
+          data.selectedIds.add(shape.id)
40
+        }
41
+      },
42
+    })
43
+  )
44
+}

+ 4
- 0
state/commands/index.ts View File

@@ -4,6 +4,8 @@ import transformSingle from "./transform-single"
4 4
 import generate from "./generate"
5 5
 import direct from "./direct"
6 6
 import rotate from "./rotate"
7
+import move from "./move"
8
+import deleteSelected from "./delete-selected"
7 9
 
8 10
 const commands = {
9 11
   translate,
@@ -12,6 +14,8 @@ const commands = {
12 14
   generate,
13 15
   direct,
14 16
   rotate,
17
+  move,
18
+  deleteSelected,
15 19
 }
16 20
 
17 21
 export default commands

+ 165
- 0
state/commands/move.ts View File

@@ -0,0 +1,165 @@
1
+import Command from "./command"
2
+import history from "../history"
3
+import { Data, MoveType, Shape } from "types"
4
+import { forceIntegerChildIndices, getChildren, getPage } from "utils/utils"
5
+
6
+export default function moveCommand(data: Data, type: MoveType) {
7
+  const { currentPageId } = data
8
+
9
+  const page = getPage(data)
10
+
11
+  const selectedIds = Array.from(data.selectedIds.values())
12
+
13
+  const initialIndices = Object.fromEntries(
14
+    selectedIds.map((id) => [id, page.shapes[id].childIndex])
15
+  )
16
+
17
+  history.execute(
18
+    data,
19
+    new Command({
20
+      name: "move_shapes",
21
+      category: "canvas",
22
+      manualSelection: true,
23
+      do(data) {
24
+        const page = getPage(data, currentPageId)
25
+
26
+        const shapes = selectedIds.map((id) => page.shapes[id])
27
+
28
+        const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
29
+          (acc, shape) => {
30
+            if (acc[shape.parentId] === undefined) {
31
+              acc[shape.parentId] = []
32
+            }
33
+            acc[shape.parentId].push(shape)
34
+            return acc
35
+          },
36
+          {}
37
+        )
38
+
39
+        switch (type) {
40
+          case MoveType.ToFront: {
41
+            for (let id in shapesByParentId) {
42
+              moveToFront(shapesByParentId[id], getChildren(data, id))
43
+            }
44
+            break
45
+          }
46
+          case MoveType.ToBack: {
47
+            for (let id in shapesByParentId) {
48
+              moveToBack(shapesByParentId[id], getChildren(data, id))
49
+            }
50
+            break
51
+          }
52
+          case MoveType.Forward: {
53
+            for (let id in shapesByParentId) {
54
+              const visited = new Set<string>()
55
+              const siblings = getChildren(data, id)
56
+              shapesByParentId[id]
57
+                .sort((a, b) => b.childIndex - a.childIndex)
58
+                .forEach((shape) => moveForward(shape, siblings, visited))
59
+            }
60
+            break
61
+          }
62
+          case MoveType.Backward: {
63
+            for (let id in shapesByParentId) {
64
+              const visited = new Set<string>()
65
+              const siblings = getChildren(data, id)
66
+              shapesByParentId[id]
67
+                .sort((a, b) => a.childIndex - b.childIndex)
68
+                .forEach((shape) => moveBackward(shape, siblings, visited))
69
+            }
70
+            break
71
+          }
72
+        }
73
+      },
74
+      undo(data) {
75
+        const page = getPage(data)
76
+
77
+        for (let id of selectedIds) {
78
+          page.shapes[id].childIndex = initialIndices[id]
79
+        }
80
+      },
81
+    })
82
+  )
83
+}
84
+
85
+function moveToFront(shapes: Shape[], siblings: Shape[]) {
86
+  shapes.sort((a, b) => a.childIndex - b.childIndex)
87
+
88
+  const diff = siblings
89
+    .filter((sib) => !shapes.includes(sib))
90
+    .sort((a, b) => b.childIndex - a.childIndex)
91
+
92
+  if (diff.length === 0) return
93
+
94
+  const startIndex = Math.ceil(diff[0].childIndex) + 1
95
+
96
+  shapes.forEach((shape, i) => (shape.childIndex = startIndex + i))
97
+}
98
+
99
+function moveToBack(shapes: Shape[], siblings: Shape[]) {
100
+  shapes.sort((a, b) => b.childIndex - a.childIndex)
101
+
102
+  const diff = siblings
103
+    .filter((sib) => !shapes.includes(sib))
104
+    .sort((a, b) => a.childIndex - b.childIndex)
105
+
106
+  if (diff.length === 0) return
107
+
108
+  const startIndex = diff[0]?.childIndex
109
+
110
+  const step = startIndex / (shapes.length + 1)
111
+
112
+  shapes.forEach((shape, i) => (shape.childIndex = startIndex - (i + 1) * step))
113
+}
114
+
115
+function moveForward(shape: Shape, siblings: Shape[], visited: Set<string>) {
116
+  visited.add(shape.id)
117
+  const index = siblings.indexOf(shape)
118
+  const nextSibling = siblings[index + 1]
119
+
120
+  if (nextSibling && !visited.has(nextSibling.id)) {
121
+    const nextNextSibling = siblings[index + 2]
122
+
123
+    let nextIndex = nextNextSibling
124
+      ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
125
+      : Math.ceil(nextSibling.childIndex + 1)
126
+
127
+    if (nextIndex === nextSibling.childIndex) {
128
+      forceIntegerChildIndices(siblings)
129
+
130
+      nextIndex = nextNextSibling
131
+        ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
132
+        : Math.ceil(nextSibling.childIndex + 1)
133
+    }
134
+
135
+    shape.childIndex = nextIndex
136
+
137
+    siblings.sort((a, b) => a.childIndex - b.childIndex)
138
+  }
139
+}
140
+
141
+function moveBackward(shape: Shape, siblings: Shape[], visited: Set<string>) {
142
+  visited.add(shape.id)
143
+  const index = siblings.indexOf(shape)
144
+  const nextSibling = siblings[index - 1]
145
+
146
+  if (nextSibling && !visited.has(nextSibling.id)) {
147
+    const nextNextSibling = siblings[index - 2]
148
+
149
+    let nextIndex = nextNextSibling
150
+      ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
151
+      : nextSibling.childIndex / 2
152
+
153
+    if (shape.childIndex === nextSibling.childIndex) {
154
+      forceIntegerChildIndices(siblings)
155
+
156
+      nextNextSibling
157
+        ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2
158
+        : nextSibling.childIndex / 2
159
+    }
160
+
161
+    shape.childIndex = nextIndex
162
+
163
+    siblings.sort((a, b) => a.childIndex - b.childIndex)
164
+  }
165
+}

+ 7
- 114
state/state.ts View File

@@ -17,6 +17,7 @@ import {
17 17
   Corner,
18 18
   Edge,
19 19
   CodeControl,
20
+  MoveType,
20 21
 } from "types"
21 22
 import inputs from "./inputs"
22 23
 import { defaultDocument } from "./data"
@@ -280,7 +281,7 @@ const state = createState({
280 281
                 MOVED_POINTER: {
281 282
                   if: "distanceImpliesDrag",
282 283
                   then: {
283
-                    get: "newDot",
284
+                    get: "newCircle",
284 285
                     do: "createShape",
285 286
                     to: "drawingShape.bounds",
286 287
                   },
@@ -676,114 +677,16 @@ const state = createState({
676 677
       data.selectedIds.add(data.pointedId)
677 678
     },
678 679
     moveSelectionToFront(data) {
679
-      const { selectedIds } = data
680
+      commands.move(data, MoveType.ToFront)
680 681
     },
681 682
     moveSelectionToBack(data) {
682
-      const { selectedIds } = data
683
+      commands.move(data, MoveType.ToBack)
683 684
     },
684 685
     moveSelectionForward(data) {
685
-      const { selectedIds } = data
686
-
687
-      const page = getPage(data)
688
-
689
-      const shapes = Array.from(selectedIds.values()).map(
690
-        (id) => page.shapes[id]
691
-      )
692
-
693
-      const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
694
-        (acc, shape) => {
695
-          if (acc[shape.parentId] === undefined) {
696
-            acc[shape.parentId] = []
697
-          }
698
-          acc[shape.parentId].push(shape)
699
-          return acc
700
-        },
701
-        {}
702
-      )
703
-
704
-      const visited = new Set<string>()
705
-
706
-      for (let id in shapesByParentId) {
707
-        const children = getChildren(data, id)
708
-
709
-        shapesByParentId[id]
710
-          .sort((a, b) => b.childIndex - a.childIndex)
711
-          .forEach((shape) => {
712
-            visited.add(shape.id)
713
-            children.sort((a, b) => a.childIndex - b.childIndex)
714
-            const index = children.indexOf(shape)
715
-
716
-            const nextSibling = children[index + 1]
717
-
718
-            if (!nextSibling || visited.has(nextSibling.id)) {
719
-              // At the top already, no change
720
-              return
721
-            }
722
-
723
-            const nextNextSibling = children[index + 2]
724
-
725
-            if (!nextNextSibling) {
726
-              // Moving to the top
727
-              shape.childIndex = nextSibling.childIndex + 1
728
-              return
729
-            }
730
-
731
-            shape.childIndex =
732
-              (nextSibling.childIndex + nextNextSibling.childIndex) / 2
733
-          })
734
-      }
686
+      commands.move(data, MoveType.Forward)
735 687
     },
736 688
     moveSelectionBackward(data) {
737
-      const { selectedIds } = data
738
-
739
-      const page = getPage(data)
740
-
741
-      const shapes = Array.from(selectedIds.values()).map(
742
-        (id) => page.shapes[id]
743
-      )
744
-
745
-      const shapesByParentId = shapes.reduce<Record<string, Shape[]>>(
746
-        (acc, shape) => {
747
-          if (acc[shape.parentId] === undefined) {
748
-            acc[shape.parentId] = []
749
-          }
750
-          acc[shape.parentId].push(shape)
751
-          return acc
752
-        },
753
-        {}
754
-      )
755
-
756
-      const visited = new Set<string>()
757
-
758
-      for (let id in shapesByParentId) {
759
-        const children = getChildren(data, id)
760
-
761
-        shapesByParentId[id]
762
-          .sort((a, b) => a.childIndex - b.childIndex)
763
-          .forEach((shape) => {
764
-            visited.add(shape.id)
765
-            children.sort((a, b) => a.childIndex - b.childIndex)
766
-            const index = children.indexOf(shape)
767
-
768
-            const nextSibling = children[index - 1]
769
-
770
-            if (!nextSibling || visited.has(nextSibling.id)) {
771
-              // At the bottom already, no change
772
-              return
773
-            }
774
-
775
-            const nextNextSibling = children[index - 2]
776
-
777
-            if (!nextNextSibling) {
778
-              // Moving to the bottom
779
-              shape.childIndex = nextSibling.childIndex / 2
780
-              return
781
-            }
782
-
783
-            shape.childIndex =
784
-              (nextSibling.childIndex + nextNextSibling.childIndex) / 2
785
-          })
786
-      }
689
+      commands.move(data, MoveType.Backward)
787 690
     },
788 691
 
789 692
     /* --------------------- Camera --------------------- */
@@ -826,17 +729,7 @@ const state = createState({
826 729
       )
827 730
     },
828 731
     deleteSelectedIds(data) {
829
-      const page = getPage(data)
830
-
831
-      data.hoveredId = undefined
832
-      data.pointedId = undefined
833
-
834
-      data.selectedIds.forEach((id) => {
835
-        delete page.shapes[id]
836
-        // TODO: recursively delete children
837
-      })
838
-
839
-      data.selectedIds.clear()
732
+      commands.deleteSelected(data)
840 733
     },
841 734
 
842 735
     /* ---------------------- History ---------------------- */

+ 8
- 0
types.ts View File

@@ -209,6 +209,14 @@ export type ShapeUtil<K extends Shape> = {
209 209
   stretch(shape: K, scaleX: number, scaleY: number): K
210 210
   render(shape: K): JSX.Element
211 211
 }
212
+
213
+export enum MoveType {
214
+  Backward,
215
+  Forward,
216
+  ToFront,
217
+  ToBack,
218
+}
219
+
212 220
 /* -------------------------------------------------- */
213 221
 /*                     Code Editor                    */
214 222
 /* -------------------------------------------------- */

+ 21
- 1
utils/utils.ts View File

@@ -1428,7 +1428,14 @@ export function getChildIndexAbove(
1428 1428
     return shape.childIndex + 1
1429 1429
   }
1430 1430
 
1431
-  return (shape.childIndex + nextSibling.childIndex) / 2
1431
+  let nextIndex = (shape.childIndex + nextSibling.childIndex) / 2
1432
+
1433
+  if (nextIndex === nextSibling.childIndex) {
1434
+    forceIntegerChildIndices(siblings)
1435
+    nextIndex = (shape.childIndex + nextSibling.childIndex) / 2
1436
+  }
1437
+
1438
+  return nextIndex
1432 1439
 }
1433 1440
 
1434 1441
 export function getChildIndexBelow(
@@ -1452,5 +1459,18 @@ export function getChildIndexBelow(
1452 1459
     return shape.childIndex / 2
1453 1460
   }
1454 1461
 
1462
+  let nextIndex = (shape.childIndex + prevSibling.childIndex) / 2
1463
+
1464
+  if (nextIndex === prevSibling.childIndex) {
1465
+    forceIntegerChildIndices(siblings)
1466
+    nextIndex = (shape.childIndex + prevSibling.childIndex) / 2
1467
+  }
1468
+
1455 1469
   return (shape.childIndex + prevSibling.childIndex) / 2
1456 1470
 }
1471
+
1472
+export function forceIntegerChildIndices(shapes: Shape[]) {
1473
+  for (let i = 0; i < shapes.length; i++) {
1474
+    shapes[i].childIndex = i + 1
1475
+  }
1476
+}

Loading…
Cancel
Save