ソースを参照

Adds error boundary, improves code shapes types.

main
Steve Ruiz 4年前
コミット
32922b3f85

+ 36
- 11
components/canvas/canvas.tsx ファイルの表示

@@ -1,5 +1,6 @@
1 1
 import styled from 'styles'
2
-import { useSelector } from 'state'
2
+import { ErrorBoundary } from 'react-error-boundary'
3
+import state, { useSelector } from 'state'
3 4
 import React, { useRef } from 'react'
4 5
 import useZoomEvents from 'hooks/useZoomEvents'
5 6
 import useCamera from 'hooks/useCamera'
@@ -27,16 +28,23 @@ export default function Canvas(): JSX.Element {
27 28
   return (
28 29
     <ContextMenu>
29 30
       <MainSVG ref={rCanvas} {...events}>
30
-        <Defs />
31
-        {isReady && (
32
-          <g ref={rGroup} id="shapes">
33
-            <BoundsBg />
34
-            <Page />
35
-            <Bounds />
36
-            <Handles />
37
-            <Brush />
38
-          </g>
39
-        )}
31
+        <ErrorBoundary
32
+          FallbackComponent={ErrorFallback}
33
+          onReset={() => {
34
+            // reset the state of your app so the error doesn't happen again
35
+          }}
36
+        >
37
+          <Defs />
38
+          {isReady && (
39
+            <g ref={rGroup} id="shapes">
40
+              <BoundsBg />
41
+              <Page />
42
+              <Bounds />
43
+              <Handles />
44
+              <Brush />
45
+            </g>
46
+          )}
47
+        </ErrorBoundary>
40 48
       </MainSVG>
41 49
     </ContextMenu>
42 50
   )
@@ -59,3 +67,20 @@ const MainSVG = styled('svg', {
59 67
     userSelect: 'none',
60 68
   },
61 69
 })
70
+
71
+function ErrorFallback({ error, resetErrorBoundary }) {
72
+  React.useEffect(() => {
73
+    console.error(error)
74
+    const copy = 'Sorry, something went wrong. Clear canvas and continue?'
75
+    if (window.confirm(copy)) {
76
+      state.send('CLEARED_PAGE')
77
+      resetErrorBoundary()
78
+    }
79
+  }, [])
80
+
81
+  return (
82
+    <g>
83
+      <text>Oops</text>
84
+    </g>
85
+  )
86
+}

+ 212
- 23
components/code-panel/types-import.ts ファイルの表示

@@ -199,7 +199,7 @@ interface GroupShape extends BaseShape {
199 199
   size: number[]
200 200
 }
201 201
 
202
-type ShapeProps<T extends Shape> = Partial<T> & {
202
+type ShapeProps<T extends Shape> = Partial<Omit<T, 'style'>> & {
203 203
   style?: Partial<ShapeStyles>
204 204
 }
205 205
 
@@ -608,57 +608,235 @@ interface ShapeUtility<K extends Shape> {
608 608
     return { ...this._shape }
609 609
   }
610 610
 
611
+  /**
612
+   * Destroy the shape.
613
+   */
611 614
   destroy(): void {
612 615
     codeShapes.delete(this)
613 616
   }
614 617
 
618
+  /**
619
+   * Move the shape to a point.
620
+   * @param delta
621
+   */
615 622
   moveTo(point: number[]): CodeShape<T> {
616
-    this.utils.setProperty(this._shape, 'point', point)
623
+    return this.translateTo(point)
624
+  }
625
+
626
+  /**
627
+   * Move the shape to a point.
628
+   * @param delta
629
+   */
630
+  translateTo(point: number[]): CodeShape<T> {
631
+    this.utils.translateTo(this._shape, point)
617 632
     return this
618 633
   }
619 634
 
620
-  translate(delta: number[]): CodeShape<T> {
621
-    this.utils.setProperty(
622
-      this._shape,
623
-      'point',
624
-      vec.add(this._shape.point, delta)
625
-    )
635
+  /**
636
+   * Move the shape by a delta.
637
+   * @param delta
638
+   */
639
+  translateBy(delta: number[]): CodeShape<T> {
640
+    this.utils.translateTo(this._shape, delta)
641
+    return this
642
+  }
643
+
644
+  /**
645
+   * Rotate the shape.
646
+   */
647
+  rotateTo(rotation: number): CodeShape<T> {
648
+    this.utils.rotateTo(this._shape, rotation, this.shape.rotation - rotation)
626 649
     return this
627 650
   }
628 651
 
629
-  rotate(rotation: number): CodeShape<T> {
630
-    this.utils.setProperty(this._shape, 'rotation', rotation)
652
+  /**
653
+   * Rotate the shape by a delta.
654
+   */
655
+  rotateBy(rotation: number): CodeShape<T> {
656
+    this.utils.rotateBy(this._shape, rotation)
631 657
     return this
632 658
   }
633 659
 
660
+  /**
661
+   * Get the shape's bounding box.
662
+   */
634 663
   getBounds(): CodeShape<T> {
635 664
     this.utils.getBounds(this.shape)
636 665
     return this
637 666
   }
638 667
 
668
+  /**
669
+   * Test whether a point is inside of the shape.
670
+   */
639 671
   hitTest(point: number[]): CodeShape<T> {
640 672
     this.utils.hitTest(this.shape, point)
641 673
     return this
642 674
   }
643 675
 
676
+  /**
677
+   * Move the shape to the back of the painting order.
678
+   */
679
+  moveToBack(): CodeShape<T> {
680
+    const sorted = getOrderedShapes()
681
+
682
+    if (sorted.length <= 1) return
683
+
684
+    const first = sorted[0].childIndex
685
+    sorted.forEach((shape) => shape.childIndex++)
686
+    this.childIndex = first
687
+
688
+    codeShapes.clear()
689
+    sorted.forEach((shape) => codeShapes.add(shape))
690
+
691
+    return this
692
+  }
693
+
694
+  /**
695
+   * Move the shape to the top of the painting order.
696
+   */
697
+  moveToFront(): CodeShape<T> {
698
+    const sorted = getOrderedShapes()
699
+
700
+    if (sorted.length <= 1) return
701
+
702
+    const ahead = sorted.slice(sorted.indexOf(this))
703
+    const last = ahead[ahead.length - 1].childIndex
704
+    ahead.forEach((shape) => shape.childIndex--)
705
+    this.childIndex = last
706
+
707
+    codeShapes.clear()
708
+    sorted.forEach((shape) => codeShapes.add(shape))
709
+
710
+    return this
711
+  }
712
+
713
+  /**
714
+   * Move the shape backward in the painting order.
715
+   */
716
+  moveBackward(): CodeShape<T> {
717
+    const sorted = getOrderedShapes()
718
+
719
+    if (sorted.length <= 1) return
720
+
721
+    const next = sorted[sorted.indexOf(this) - 1]
722
+
723
+    if (!next) return
724
+
725
+    const index = next.childIndex
726
+    next.childIndex = this.childIndex
727
+    this.childIndex = index
728
+
729
+    codeShapes.clear()
730
+    sorted.forEach((shape) => codeShapes.add(shape))
731
+
732
+    return this
733
+  }
734
+
735
+  /**
736
+   * Move the shape forward in the painting order.
737
+   */
738
+  moveForward(): CodeShape<T> {
739
+    const sorted = getOrderedShapes()
740
+
741
+    if (sorted.length <= 1) return
742
+
743
+    const next = sorted[sorted.indexOf(this) + 1]
744
+
745
+    if (!next) return
746
+
747
+    const index = next.childIndex
748
+    next.childIndex = this.childIndex
749
+    this.childIndex = index
750
+
751
+    codeShapes.clear()
752
+    sorted.forEach((shape) => codeShapes.add(shape))
753
+
754
+    return this
755
+  }
756
+
757
+  /**
758
+   * The shape's underlying shape.
759
+   */
644 760
   get shape(): T {
645 761
     return this._shape
646 762
   }
647 763
 
764
+  /**
765
+   * The shape's current point.
766
+   */
648 767
   get point(): number[] {
649 768
     return [...this.shape.point]
650 769
   }
651 770
 
771
+  set point(point: number[]) {
772
+    getShapeUtils(this.shape).translateTo(this._shape, point)
773
+  }
774
+
775
+  /**
776
+   * The shape's rotation.
777
+   */
652 778
   get rotation(): number {
653 779
     return this.shape.rotation
654 780
   }
781
+
782
+  set rotation(rotation: number) {
783
+    getShapeUtils(this.shape).rotateTo(
784
+      this._shape,
785
+      rotation,
786
+      rotation - this.shape.rotation
787
+    )
788
+  }
789
+
790
+  /**
791
+   * The shape's color style.
792
+   */
793
+  get color(): ColorStyle {
794
+    return this.shape.style.color
795
+  }
796
+
797
+  set color(color: ColorStyle) {
798
+    getShapeUtils(this.shape).applyStyles(this._shape, { color })
799
+  }
800
+
801
+  /**
802
+   * The shape's dash style.
803
+   */
804
+  get dash(): DashStyle {
805
+    return this.shape.style.dash
806
+  }
807
+
808
+  set dash(dash: DashStyle) {
809
+    getShapeUtils(this.shape).applyStyles(this._shape, { dash })
810
+  }
811
+
812
+  /**
813
+   * The shape's stroke width.
814
+   */
815
+  get strokeWidth(): SizeStyle {
816
+    return this.shape.style.size
817
+  }
818
+
819
+  set strokeWidth(size: SizeStyle) {
820
+    getShapeUtils(this.shape).applyStyles(this._shape, { size })
821
+  }
822
+
823
+  /**
824
+   * The shape's index in the painting order.
825
+   */
826
+  get childIndex(): number {
827
+    return this.shape.childIndex
828
+  }
829
+
830
+  set childIndex(childIndex: number) {
831
+    getShapeUtils(this.shape).setProperty(this._shape, 'childIndex', childIndex)
832
+  }
655 833
 }
656 834
 
657 835
 /**
658 836
  * ## Dot
659 837
  */
660 838
  class Dot extends CodeShape<DotShape> {
661
-  constructor(props = {} as Partial<DotShape> & Partial<ShapeStyles>) {
839
+  constructor(props = {} as ShapeProps<DotShape>) {
662 840
     super({
663 841
       id: uniqueId(),
664 842
       seed: Math.random(),
@@ -686,7 +864,7 @@ interface ShapeUtility<K extends Shape> {
686 864
  * ## Ellipse
687 865
  */
688 866
  class Ellipse extends CodeShape<EllipseShape> {
689
-  constructor(props = {} as Partial<EllipseShape> & Partial<ShapeStyles>) {
867
+  constructor(props = {} as ShapeProps<EllipseShape>) {
690 868
     super({
691 869
       id: uniqueId(),
692 870
       seed: Math.random(),
@@ -720,7 +898,7 @@ interface ShapeUtility<K extends Shape> {
720 898
  * ## Line
721 899
  */
722 900
  class Line extends CodeShape<LineShape> {
723
-  constructor(props = {} as Partial<LineShape> & Partial<ShapeStyles>) {
901
+  constructor(props = {} as ShapeProps<LineShape>) {
724 902
     super({
725 903
       id: uniqueId(),
726 904
       seed: Math.random(),
@@ -753,7 +931,7 @@ interface ShapeUtility<K extends Shape> {
753 931
  * ## Polyline
754 932
  */
755 933
  class Polyline extends CodeShape<PolylineShape> {
756
-  constructor(props = {} as Partial<PolylineShape> & Partial<ShapeStyles>) {
934
+  constructor(props = {} as ShapeProps<PolylineShape>) {
757 935
     super({
758 936
       id: uniqueId(),
759 937
       seed: Math.random(),
@@ -782,7 +960,7 @@ interface ShapeUtility<K extends Shape> {
782 960
  * ## Ray
783 961
  */
784 962
  class Ray extends CodeShape<RayShape> {
785
-  constructor(props = {} as Partial<RayShape> & Partial<ShapeStyles>) {
963
+  constructor(props = {} as ShapeProps<RayShape>) {
786 964
     super({
787 965
       id: uniqueId(),
788 966
       seed: Math.random(),
@@ -846,8 +1024,7 @@ interface ShapeUtility<K extends Shape> {
846 1024
  */
847 1025
  class Arrow extends CodeShape<ArrowShape> {
848 1026
   constructor(
849
-    props = {} as Partial<ArrowShape> &
850
-      Partial<ShapeStyles> & { start?: number[]; end?: number[] }
1027
+    props = {} as ShapeProps<ArrowShape> & { start: number[]; end: number[] }
851 1028
   ) {
852 1029
     const { start = [0, 0], end = [0, 0] } = props
853 1030
 
@@ -940,7 +1117,7 @@ interface ShapeUtility<K extends Shape> {
940 1117
  * ## Draw
941 1118
  */
942 1119
  class Draw extends CodeShape<DrawShape> {
943
-  constructor(props = {} as Partial<DrawShape>) {
1120
+  constructor(props = {} as ShapeProps<DrawShape>) {
944 1121
     super({
945 1122
       id: uniqueId(),
946 1123
       seed: Math.random(),
@@ -1459,12 +1636,24 @@ interface ShapeUtility<K extends Shape> {
1459 1636
     return -c / 2 + -step
1460 1637
   }
1461 1638
 
1462
-  static getPointsBetween(a: number[], b: number[], steps = 6): number[][] {
1639
+  /**
1640
+   * Get an array of points between two points.
1641
+   * @param a
1642
+   * @param b
1643
+   * @param options
1644
+   */
1645
+  static getPointsBetween(
1646
+    a: number[],
1647
+    b: number[],
1648
+    options = {} as {
1649
+      steps?: number
1650
+      ease?: (t: number) => number
1651
+    }
1652
+  ): number[][] {
1653
+    const { steps = 6, ease = (t) => t * t * t } = options
1654
+
1463 1655
     return Array.from(Array(steps))
1464
-      .map((_, i) => {
1465
-        const t = i / steps
1466
-        return t * t * t
1467
-      })
1656
+      .map((_, i) => ease(i / steps))
1468 1657
       .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
1469 1658
   }
1470 1659
 

+ 1
- 0
package.json ファイルの表示

@@ -59,6 +59,7 @@
59 59
     "perfect-freehand": "^0.4.9",
60 60
     "react": "^17.0.2",
61 61
     "react-dom": "^17.0.2",
62
+    "react-error-boundary": "^3.1.3",
62 63
     "react-feather": "^2.0.9",
63 64
     "react-use-gesture": "^9.1.3",
64 65
     "sucrase": "^3.19.0",

+ 2
- 3
state/code/arrow.ts ファイルの表示

@@ -1,6 +1,6 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { ArrowShape, Decoration, ShapeStyles, ShapeType } from 'types'
3
+import { ArrowShape, Decoration, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 import { getShapeUtils } from 'state/shape-utils'
6 6
 import Vec from 'utils/vec'
@@ -10,8 +10,7 @@ import Vec from 'utils/vec'
10 10
  */
11 11
 export default class Arrow extends CodeShape<ArrowShape> {
12 12
   constructor(
13
-    props = {} as Partial<ArrowShape> &
14
-      Partial<ShapeStyles> & { start?: number[]; end?: number[] }
13
+    props = {} as ShapeProps<ArrowShape> & { start: number[]; end: number[] }
15 14
   ) {
16 15
     const { start = [0, 0], end = [0, 0] } = props
17 16
 

+ 2
- 2
state/code/dot.ts ファイルの表示

@@ -1,13 +1,13 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { DotShape, ShapeStyles, ShapeType } from 'types'
3
+import { DotShape, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 
6 6
 /**
7 7
  * ## Dot
8 8
  */
9 9
 export default class Dot extends CodeShape<DotShape> {
10
-  constructor(props = {} as Partial<DotShape> & Partial<ShapeStyles>) {
10
+  constructor(props = {} as ShapeProps<DotShape>) {
11 11
     super({
12 12
       id: uniqueId(),
13 13
       seed: Math.random(),

+ 2
- 2
state/code/draw.ts ファイルの表示

@@ -1,13 +1,13 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { DrawShape, ShapeType } from 'types'
3
+import { DrawShape, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 
6 6
 /**
7 7
  * ## Draw
8 8
  */
9 9
 export default class Draw extends CodeShape<DrawShape> {
10
-  constructor(props = {} as Partial<DrawShape>) {
10
+  constructor(props = {} as ShapeProps<DrawShape>) {
11 11
     super({
12 12
       id: uniqueId(),
13 13
       seed: Math.random(),

+ 2
- 2
state/code/ellipse.ts ファイルの表示

@@ -1,13 +1,13 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { EllipseShape, ShapeStyles, ShapeType } from 'types'
3
+import { EllipseShape, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 
6 6
 /**
7 7
  * ## Ellipse
8 8
  */
9 9
 export default class Ellipse extends CodeShape<EllipseShape> {
10
-  constructor(props = {} as Partial<EllipseShape> & Partial<ShapeStyles>) {
10
+  constructor(props = {} as ShapeProps<EllipseShape>) {
11 11
     super({
12 12
       id: uniqueId(),
13 13
       seed: Math.random(),

+ 25
- 7
state/code/generate.ts ファイルの表示

@@ -10,8 +10,15 @@ import Utils from './utils'
10 10
 import Vec from 'utils/vec'
11 11
 import { NumberControl, VectorControl, codeControls, controls } from './control'
12 12
 import { codeShapes } from './index'
13
-import { CodeControl, Data, Shape } from 'types'
14
-import { getPage } from 'utils'
13
+import {
14
+  CodeControl,
15
+  Data,
16
+  Shape,
17
+  DashStyle,
18
+  ColorStyle,
19
+  SizeStyle,
20
+} from 'types'
21
+import { getPage, getShapes } from 'utils'
15 22
 import { transform } from 'sucrase'
16 23
 
17 24
 const baseScope = {
@@ -27,6 +34,9 @@ const baseScope = {
27 34
   Draw,
28 35
   VectorControl,
29 36
   NumberControl,
37
+  DashStyle,
38
+  ColorStyle,
39
+  SizeStyle,
30 40
 }
31 41
 
32 42
 /**
@@ -53,11 +63,19 @@ export function generateFromCode(
53 63
 
54 64
   new Function(...Object.keys(scope), `${transformed}`)(...Object.values(scope))
55 65
 
56
-  const generatedShapes = Array.from(codeShapes.values()).map((instance) => ({
57
-    ...instance.shape,
58
-    isGenerated: true,
59
-    parentId: getPage(data).id,
60
-  }))
66
+  const startingChildIndex =
67
+    getShapes(data)
68
+      .filter((shape) => shape.parentId === data.currentPageId)
69
+      .sort((a, b) => a.childIndex - b.childIndex)[0]?.childIndex || 1
70
+
71
+  const generatedShapes = Array.from(codeShapes.values())
72
+    .sort((a, b) => a.shape.childIndex - b.shape.childIndex)
73
+    .map((instance, i) => ({
74
+      ...instance.shape,
75
+      isGenerated: true,
76
+      parentId: getPage(data).id,
77
+      childIndex: startingChildIndex + i,
78
+    }))
61 79
 
62 80
   const generatedControls = Array.from(codeControls.values())
63 81
 

+ 202
- 11
state/code/index.ts ファイルの表示

@@ -1,9 +1,22 @@
1
-import { Mutable, Shape, ShapeUtility } from 'types'
1
+import {
2
+  ColorStyle,
3
+  DashStyle,
4
+  Mutable,
5
+  Shape,
6
+  ShapeUtility,
7
+  SizeStyle,
8
+} from 'types'
2 9
 import { createShape, getShapeUtils } from 'state/shape-utils'
3
-import vec from 'utils/vec'
10
+import { setToArray } from 'utils'
4 11
 
5 12
 export const codeShapes = new Set<CodeShape<Shape>>([])
6 13
 
14
+function getOrderedShapes() {
15
+  return setToArray(codeShapes).sort(
16
+    (a, b) => a.shape.childIndex - b.shape.childIndex
17
+  )
18
+}
19
+
7 20
 /**
8 21
  * A base class for code shapes. Note that creating a shape adds it to the
9 22
  * shape map, while deleting it removes it from the collected shapes set
@@ -22,48 +35,226 @@ export default class CodeShape<T extends Shape> {
22 35
     return { ...this._shape }
23 36
   }
24 37
 
38
+  /**
39
+   * Destroy the shape.
40
+   */
25 41
   destroy(): void {
26 42
     codeShapes.delete(this)
27 43
   }
28 44
 
45
+  /**
46
+   * Move the shape to a point.
47
+   * @param delta
48
+   */
29 49
   moveTo(point: number[]): CodeShape<T> {
30
-    this.utils.setProperty(this._shape, 'point', point)
50
+    return this.translateTo(point)
51
+  }
52
+
53
+  /**
54
+   * Move the shape to a point.
55
+   * @param delta
56
+   */
57
+  translateTo(point: number[]): CodeShape<T> {
58
+    this.utils.translateTo(this._shape, point)
31 59
     return this
32 60
   }
33 61
 
34
-  translate(delta: number[]): CodeShape<T> {
35
-    this.utils.setProperty(
36
-      this._shape,
37
-      'point',
38
-      vec.add(this._shape.point, delta)
39
-    )
62
+  /**
63
+   * Move the shape by a delta.
64
+   * @param delta
65
+   */
66
+  translateBy(delta: number[]): CodeShape<T> {
67
+    this.utils.translateTo(this._shape, delta)
68
+    return this
69
+  }
70
+
71
+  /**
72
+   * Rotate the shape.
73
+   */
74
+  rotateTo(rotation: number): CodeShape<T> {
75
+    this.utils.rotateTo(this._shape, rotation, this.shape.rotation - rotation)
40 76
     return this
41 77
   }
42 78
 
43
-  rotate(rotation: number): CodeShape<T> {
44
-    this.utils.setProperty(this._shape, 'rotation', rotation)
79
+  /**
80
+   * Rotate the shape by a delta.
81
+   */
82
+  rotateBy(rotation: number): CodeShape<T> {
83
+    this.utils.rotateBy(this._shape, rotation)
45 84
     return this
46 85
   }
47 86
 
87
+  /**
88
+   * Get the shape's bounding box.
89
+   */
48 90
   getBounds(): CodeShape<T> {
49 91
     this.utils.getBounds(this.shape)
50 92
     return this
51 93
   }
52 94
 
95
+  /**
96
+   * Test whether a point is inside of the shape.
97
+   */
53 98
   hitTest(point: number[]): CodeShape<T> {
54 99
     this.utils.hitTest(this.shape, point)
55 100
     return this
56 101
   }
57 102
 
103
+  /**
104
+   * Move the shape to the back of the painting order.
105
+   */
106
+  moveToBack(): CodeShape<T> {
107
+    const sorted = getOrderedShapes()
108
+
109
+    if (sorted.length <= 1) return
110
+
111
+    const first = sorted[0].childIndex
112
+    sorted.forEach((shape) => shape.childIndex++)
113
+    this.childIndex = first
114
+
115
+    codeShapes.clear()
116
+    sorted.forEach((shape) => codeShapes.add(shape))
117
+
118
+    return this
119
+  }
120
+
121
+  /**
122
+   * Move the shape to the top of the painting order.
123
+   */
124
+  moveToFront(): CodeShape<T> {
125
+    const sorted = getOrderedShapes()
126
+
127
+    if (sorted.length <= 1) return
128
+
129
+    const ahead = sorted.slice(sorted.indexOf(this))
130
+    const last = ahead[ahead.length - 1].childIndex
131
+    ahead.forEach((shape) => shape.childIndex--)
132
+    this.childIndex = last
133
+
134
+    codeShapes.clear()
135
+    sorted.forEach((shape) => codeShapes.add(shape))
136
+
137
+    return this
138
+  }
139
+
140
+  /**
141
+   * Move the shape backward in the painting order.
142
+   */
143
+  moveBackward(): CodeShape<T> {
144
+    const sorted = getOrderedShapes()
145
+
146
+    if (sorted.length <= 1) return
147
+
148
+    const next = sorted[sorted.indexOf(this) - 1]
149
+
150
+    if (!next) return
151
+
152
+    const index = next.childIndex
153
+    next.childIndex = this.childIndex
154
+    this.childIndex = index
155
+
156
+    codeShapes.clear()
157
+    sorted.forEach((shape) => codeShapes.add(shape))
158
+
159
+    return this
160
+  }
161
+
162
+  /**
163
+   * Move the shape forward in the painting order.
164
+   */
165
+  moveForward(): CodeShape<T> {
166
+    const sorted = getOrderedShapes()
167
+
168
+    if (sorted.length <= 1) return
169
+
170
+    const next = sorted[sorted.indexOf(this) + 1]
171
+
172
+    if (!next) return
173
+
174
+    const index = next.childIndex
175
+    next.childIndex = this.childIndex
176
+    this.childIndex = index
177
+
178
+    codeShapes.clear()
179
+    sorted.forEach((shape) => codeShapes.add(shape))
180
+
181
+    return this
182
+  }
183
+
184
+  /**
185
+   * The shape's underlying shape.
186
+   */
58 187
   get shape(): T {
59 188
     return this._shape
60 189
   }
61 190
 
191
+  /**
192
+   * The shape's current point.
193
+   */
62 194
   get point(): number[] {
63 195
     return [...this.shape.point]
64 196
   }
65 197
 
198
+  set point(point: number[]) {
199
+    getShapeUtils(this.shape).translateTo(this._shape, point)
200
+  }
201
+
202
+  /**
203
+   * The shape's rotation.
204
+   */
66 205
   get rotation(): number {
67 206
     return this.shape.rotation
68 207
   }
208
+
209
+  set rotation(rotation: number) {
210
+    getShapeUtils(this.shape).rotateTo(
211
+      this._shape,
212
+      rotation,
213
+      rotation - this.shape.rotation
214
+    )
215
+  }
216
+
217
+  /**
218
+   * The shape's color style.
219
+   */
220
+  get color(): ColorStyle {
221
+    return this.shape.style.color
222
+  }
223
+
224
+  set color(color: ColorStyle) {
225
+    getShapeUtils(this.shape).applyStyles(this._shape, { color })
226
+  }
227
+
228
+  /**
229
+   * The shape's dash style.
230
+   */
231
+  get dash(): DashStyle {
232
+    return this.shape.style.dash
233
+  }
234
+
235
+  set dash(dash: DashStyle) {
236
+    getShapeUtils(this.shape).applyStyles(this._shape, { dash })
237
+  }
238
+
239
+  /**
240
+   * The shape's stroke width.
241
+   */
242
+  get strokeWidth(): SizeStyle {
243
+    return this.shape.style.size
244
+  }
245
+
246
+  set strokeWidth(size: SizeStyle) {
247
+    getShapeUtils(this.shape).applyStyles(this._shape, { size })
248
+  }
249
+
250
+  /**
251
+   * The shape's index in the painting order.
252
+   */
253
+  get childIndex(): number {
254
+    return this.shape.childIndex
255
+  }
256
+
257
+  set childIndex(childIndex: number) {
258
+    getShapeUtils(this.shape).setProperty(this._shape, 'childIndex', childIndex)
259
+  }
69 260
 }

+ 2
- 2
state/code/line.ts ファイルの表示

@@ -1,13 +1,13 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { LineShape, ShapeStyles, ShapeType } from 'types'
3
+import { LineShape, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 
6 6
 /**
7 7
  * ## Line
8 8
  */
9 9
 export default class Line extends CodeShape<LineShape> {
10
-  constructor(props = {} as Partial<LineShape> & Partial<ShapeStyles>) {
10
+  constructor(props = {} as ShapeProps<LineShape>) {
11 11
     super({
12 12
       id: uniqueId(),
13 13
       seed: Math.random(),

+ 2
- 2
state/code/polyline.ts ファイルの表示

@@ -1,13 +1,13 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { PolylineShape, ShapeStyles, ShapeType } from 'types'
3
+import { PolylineShape, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 
6 6
 /**
7 7
  * ## Polyline
8 8
  */
9 9
 export default class Polyline extends CodeShape<PolylineShape> {
10
-  constructor(props = {} as Partial<PolylineShape> & Partial<ShapeStyles>) {
10
+  constructor(props = {} as ShapeProps<PolylineShape>) {
11 11
     super({
12 12
       id: uniqueId(),
13 13
       seed: Math.random(),

+ 2
- 2
state/code/ray.ts ファイルの表示

@@ -1,13 +1,13 @@
1 1
 import CodeShape from './index'
2 2
 import { uniqueId } from 'utils'
3
-import { RayShape, ShapeStyles, ShapeType } from 'types'
3
+import { RayShape, ShapeProps, ShapeType } from 'types'
4 4
 import { defaultStyle } from 'state/shape-styles'
5 5
 
6 6
 /**
7 7
  * ## Ray
8 8
  */
9 9
 export default class Ray extends CodeShape<RayShape> {
10
-  constructor(props = {} as Partial<RayShape> & Partial<ShapeStyles>) {
10
+  constructor(props = {} as ShapeProps<RayShape>) {
11 11
     super({
12 12
       id: uniqueId(),
13 13
       seed: Math.random(),

+ 17
- 5
state/code/utils.ts ファイルの表示

@@ -496,12 +496,24 @@ export default class Utils {
496 496
     return -c / 2 + -step
497 497
   }
498 498
 
499
-  static getPointsBetween(a: number[], b: number[], steps = 6): number[][] {
499
+  /**
500
+   * Get an array of points between two points.
501
+   * @param a
502
+   * @param b
503
+   * @param options
504
+   */
505
+  static getPointsBetween(
506
+    a: number[],
507
+    b: number[],
508
+    options = {} as {
509
+      steps?: number
510
+      ease?: (t: number) => number
511
+    }
512
+  ): number[][] {
513
+    const { steps = 6, ease = (t) => t * t * t } = options
514
+
500 515
     return Array.from(Array(steps))
501
-      .map((_, i) => {
502
-        const t = i / steps
503
-        return t * t * t
504
-      })
516
+      .map((_, i) => ease(i / steps))
505 517
       .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
506 518
   }
507 519
 

+ 3
- 3
state/storage.ts ファイルの表示

@@ -335,15 +335,15 @@ class Storage {
335 335
       }
336 336
     )
337 337
 
338
+    const documentName = data.document.name
339
+
338 340
     const fa = await import('browser-fs-access')
339 341
 
340 342
     fa.fileSave(
341 343
       blob,
342 344
       {
343 345
         fileName: `${
344
-          saveAs
345
-            ? data.document.name
346
-            : this.previousSaveHandle?.name || 'My Document'
346
+          saveAs ? documentName : this.previousSaveHandle?.name || 'My Document'
347 347
         }.tldr`,
348 348
         description: 'tldraw file',
349 349
         extensions: ['.tldr'],

+ 1
- 1
types.ts ファイルの表示

@@ -191,7 +191,7 @@ export interface GroupShape extends BaseShape {
191 191
   size: number[]
192 192
 }
193 193
 
194
-export type ShapeProps<T extends Shape> = Partial<T> & {
194
+export type ShapeProps<T extends Shape> = Partial<Omit<T, 'style'>> & {
195 195
   style?: Partial<ShapeStyles>
196 196
 }
197 197
 

+ 7
- 0
yarn.lock ファイルの表示

@@ -6592,6 +6592,13 @@ react-dom@^17.0.2:
6592 6592
     object-assign "^4.1.1"
6593 6593
     scheduler "^0.20.2"
6594 6594
 
6595
+react-error-boundary@^3.1.3:
6596
+  version "3.1.3"
6597
+  resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.3.tgz#276bfa05de8ac17b863587c9e0647522c25e2a0b"
6598
+  integrity sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA==
6599
+  dependencies:
6600
+    "@babel/runtime" "^7.12.5"
6601
+
6595 6602
 react-feather@^2.0.9:
6596 6603
   version "2.0.9"
6597 6604
   resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"

読み込み中…
キャンセル
保存