Kaynağa Gözat

Adds error boundary, improves code shapes types.

main
Steve Ruiz 4 yıl önce
ebeveyn
işleme
32922b3f85

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

1
 import styled from 'styles'
1
 import styled from 'styles'
2
-import { useSelector } from 'state'
2
+import { ErrorBoundary } from 'react-error-boundary'
3
+import state, { useSelector } from 'state'
3
 import React, { useRef } from 'react'
4
 import React, { useRef } from 'react'
4
 import useZoomEvents from 'hooks/useZoomEvents'
5
 import useZoomEvents from 'hooks/useZoomEvents'
5
 import useCamera from 'hooks/useCamera'
6
 import useCamera from 'hooks/useCamera'
27
   return (
28
   return (
28
     <ContextMenu>
29
     <ContextMenu>
29
       <MainSVG ref={rCanvas} {...events}>
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
       </MainSVG>
48
       </MainSVG>
41
     </ContextMenu>
49
     </ContextMenu>
42
   )
50
   )
59
     userSelect: 'none',
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 Dosyayı Görüntüle

199
   size: number[]
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
   style?: Partial<ShapeStyles>
203
   style?: Partial<ShapeStyles>
204
 }
204
 }
205
 
205
 
608
     return { ...this._shape }
608
     return { ...this._shape }
609
   }
609
   }
610
 
610
 
611
+  /**
612
+   * Destroy the shape.
613
+   */
611
   destroy(): void {
614
   destroy(): void {
612
     codeShapes.delete(this)
615
     codeShapes.delete(this)
613
   }
616
   }
614
 
617
 
618
+  /**
619
+   * Move the shape to a point.
620
+   * @param delta
621
+   */
615
   moveTo(point: number[]): CodeShape<T> {
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
     return this
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
     return this
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
     return this
657
     return this
632
   }
658
   }
633
 
659
 
660
+  /**
661
+   * Get the shape's bounding box.
662
+   */
634
   getBounds(): CodeShape<T> {
663
   getBounds(): CodeShape<T> {
635
     this.utils.getBounds(this.shape)
664
     this.utils.getBounds(this.shape)
636
     return this
665
     return this
637
   }
666
   }
638
 
667
 
668
+  /**
669
+   * Test whether a point is inside of the shape.
670
+   */
639
   hitTest(point: number[]): CodeShape<T> {
671
   hitTest(point: number[]): CodeShape<T> {
640
     this.utils.hitTest(this.shape, point)
672
     this.utils.hitTest(this.shape, point)
641
     return this
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
   get shape(): T {
760
   get shape(): T {
645
     return this._shape
761
     return this._shape
646
   }
762
   }
647
 
763
 
764
+  /**
765
+   * The shape's current point.
766
+   */
648
   get point(): number[] {
767
   get point(): number[] {
649
     return [...this.shape.point]
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
   get rotation(): number {
778
   get rotation(): number {
653
     return this.shape.rotation
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
  * ## Dot
836
  * ## Dot
659
  */
837
  */
660
  class Dot extends CodeShape<DotShape> {
838
  class Dot extends CodeShape<DotShape> {
661
-  constructor(props = {} as Partial<DotShape> & Partial<ShapeStyles>) {
839
+  constructor(props = {} as ShapeProps<DotShape>) {
662
     super({
840
     super({
663
       id: uniqueId(),
841
       id: uniqueId(),
664
       seed: Math.random(),
842
       seed: Math.random(),
686
  * ## Ellipse
864
  * ## Ellipse
687
  */
865
  */
688
  class Ellipse extends CodeShape<EllipseShape> {
866
  class Ellipse extends CodeShape<EllipseShape> {
689
-  constructor(props = {} as Partial<EllipseShape> & Partial<ShapeStyles>) {
867
+  constructor(props = {} as ShapeProps<EllipseShape>) {
690
     super({
868
     super({
691
       id: uniqueId(),
869
       id: uniqueId(),
692
       seed: Math.random(),
870
       seed: Math.random(),
720
  * ## Line
898
  * ## Line
721
  */
899
  */
722
  class Line extends CodeShape<LineShape> {
900
  class Line extends CodeShape<LineShape> {
723
-  constructor(props = {} as Partial<LineShape> & Partial<ShapeStyles>) {
901
+  constructor(props = {} as ShapeProps<LineShape>) {
724
     super({
902
     super({
725
       id: uniqueId(),
903
       id: uniqueId(),
726
       seed: Math.random(),
904
       seed: Math.random(),
753
  * ## Polyline
931
  * ## Polyline
754
  */
932
  */
755
  class Polyline extends CodeShape<PolylineShape> {
933
  class Polyline extends CodeShape<PolylineShape> {
756
-  constructor(props = {} as Partial<PolylineShape> & Partial<ShapeStyles>) {
934
+  constructor(props = {} as ShapeProps<PolylineShape>) {
757
     super({
935
     super({
758
       id: uniqueId(),
936
       id: uniqueId(),
759
       seed: Math.random(),
937
       seed: Math.random(),
782
  * ## Ray
960
  * ## Ray
783
  */
961
  */
784
  class Ray extends CodeShape<RayShape> {
962
  class Ray extends CodeShape<RayShape> {
785
-  constructor(props = {} as Partial<RayShape> & Partial<ShapeStyles>) {
963
+  constructor(props = {} as ShapeProps<RayShape>) {
786
     super({
964
     super({
787
       id: uniqueId(),
965
       id: uniqueId(),
788
       seed: Math.random(),
966
       seed: Math.random(),
846
  */
1024
  */
847
  class Arrow extends CodeShape<ArrowShape> {
1025
  class Arrow extends CodeShape<ArrowShape> {
848
   constructor(
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
     const { start = [0, 0], end = [0, 0] } = props
1029
     const { start = [0, 0], end = [0, 0] } = props
853
 
1030
 
940
  * ## Draw
1117
  * ## Draw
941
  */
1118
  */
942
  class Draw extends CodeShape<DrawShape> {
1119
  class Draw extends CodeShape<DrawShape> {
943
-  constructor(props = {} as Partial<DrawShape>) {
1120
+  constructor(props = {} as ShapeProps<DrawShape>) {
944
     super({
1121
     super({
945
       id: uniqueId(),
1122
       id: uniqueId(),
946
       seed: Math.random(),
1123
       seed: Math.random(),
1459
     return -c / 2 + -step
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
     return Array.from(Array(steps))
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
       .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
1657
       .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
1469
   }
1658
   }
1470
 
1659
 

+ 1
- 0
package.json Dosyayı Görüntüle

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

+ 2
- 3
state/code/arrow.ts Dosyayı Görüntüle

1
 import CodeShape from './index'
1
 import CodeShape from './index'
2
 import { uniqueId } from 'utils'
2
 import { uniqueId } from 'utils'
3
-import { ArrowShape, Decoration, ShapeStyles, ShapeType } from 'types'
3
+import { ArrowShape, Decoration, ShapeProps, ShapeType } from 'types'
4
 import { defaultStyle } from 'state/shape-styles'
4
 import { defaultStyle } from 'state/shape-styles'
5
 import { getShapeUtils } from 'state/shape-utils'
5
 import { getShapeUtils } from 'state/shape-utils'
6
 import Vec from 'utils/vec'
6
 import Vec from 'utils/vec'
10
  */
10
  */
11
 export default class Arrow extends CodeShape<ArrowShape> {
11
 export default class Arrow extends CodeShape<ArrowShape> {
12
   constructor(
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
     const { start = [0, 0], end = [0, 0] } = props
15
     const { start = [0, 0], end = [0, 0] } = props
17
 
16
 

+ 2
- 2
state/code/dot.ts Dosyayı Görüntüle

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

+ 2
- 2
state/code/draw.ts Dosyayı Görüntüle

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

+ 2
- 2
state/code/ellipse.ts Dosyayı Görüntüle

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

+ 25
- 7
state/code/generate.ts Dosyayı Görüntüle

10
 import Vec from 'utils/vec'
10
 import Vec from 'utils/vec'
11
 import { NumberControl, VectorControl, codeControls, controls } from './control'
11
 import { NumberControl, VectorControl, codeControls, controls } from './control'
12
 import { codeShapes } from './index'
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
 import { transform } from 'sucrase'
22
 import { transform } from 'sucrase'
16
 
23
 
17
 const baseScope = {
24
 const baseScope = {
27
   Draw,
34
   Draw,
28
   VectorControl,
35
   VectorControl,
29
   NumberControl,
36
   NumberControl,
37
+  DashStyle,
38
+  ColorStyle,
39
+  SizeStyle,
30
 }
40
 }
31
 
41
 
32
 /**
42
 /**
53
 
63
 
54
   new Function(...Object.keys(scope), `${transformed}`)(...Object.values(scope))
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
   const generatedControls = Array.from(codeControls.values())
80
   const generatedControls = Array.from(codeControls.values())
63
 
81
 

+ 202
- 11
state/code/index.ts Dosyayı Görüntüle

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
 import { createShape, getShapeUtils } from 'state/shape-utils'
9
 import { createShape, getShapeUtils } from 'state/shape-utils'
3
-import vec from 'utils/vec'
10
+import { setToArray } from 'utils'
4
 
11
 
5
 export const codeShapes = new Set<CodeShape<Shape>>([])
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
  * A base class for code shapes. Note that creating a shape adds it to the
21
  * A base class for code shapes. Note that creating a shape adds it to the
9
  * shape map, while deleting it removes it from the collected shapes set
22
  * shape map, while deleting it removes it from the collected shapes set
22
     return { ...this._shape }
35
     return { ...this._shape }
23
   }
36
   }
24
 
37
 
38
+  /**
39
+   * Destroy the shape.
40
+   */
25
   destroy(): void {
41
   destroy(): void {
26
     codeShapes.delete(this)
42
     codeShapes.delete(this)
27
   }
43
   }
28
 
44
 
45
+  /**
46
+   * Move the shape to a point.
47
+   * @param delta
48
+   */
29
   moveTo(point: number[]): CodeShape<T> {
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
     return this
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
     return this
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
     return this
84
     return this
46
   }
85
   }
47
 
86
 
87
+  /**
88
+   * Get the shape's bounding box.
89
+   */
48
   getBounds(): CodeShape<T> {
90
   getBounds(): CodeShape<T> {
49
     this.utils.getBounds(this.shape)
91
     this.utils.getBounds(this.shape)
50
     return this
92
     return this
51
   }
93
   }
52
 
94
 
95
+  /**
96
+   * Test whether a point is inside of the shape.
97
+   */
53
   hitTest(point: number[]): CodeShape<T> {
98
   hitTest(point: number[]): CodeShape<T> {
54
     this.utils.hitTest(this.shape, point)
99
     this.utils.hitTest(this.shape, point)
55
     return this
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
   get shape(): T {
187
   get shape(): T {
59
     return this._shape
188
     return this._shape
60
   }
189
   }
61
 
190
 
191
+  /**
192
+   * The shape's current point.
193
+   */
62
   get point(): number[] {
194
   get point(): number[] {
63
     return [...this.shape.point]
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
   get rotation(): number {
205
   get rotation(): number {
67
     return this.shape.rotation
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 Dosyayı Görüntüle

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

+ 2
- 2
state/code/polyline.ts Dosyayı Görüntüle

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

+ 2
- 2
state/code/ray.ts Dosyayı Görüntüle

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

+ 17
- 5
state/code/utils.ts Dosyayı Görüntüle

496
     return -c / 2 + -step
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
     return Array.from(Array(steps))
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
       .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
517
       .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
506
   }
518
   }
507
 
519
 

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

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

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

191
   size: number[]
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
   style?: Partial<ShapeStyles>
195
   style?: Partial<ShapeStyles>
196
 }
196
 }
197
 
197
 

+ 7
- 0
yarn.lock Dosyayı Görüntüle

6592
     object-assign "^4.1.1"
6592
     object-assign "^4.1.1"
6593
     scheduler "^0.20.2"
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
 react-feather@^2.0.9:
6602
 react-feather@^2.0.9:
6596
   version "2.0.9"
6603
   version "2.0.9"
6597
   resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"
6604
   resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"

Loading…
İptal
Kaydet