Adds error boundary, improves code shapes types.

This commit is contained in:
Steve Ruiz 2021-06-24 23:09:36 +01:00
parent 69bdab520a
commit 32922b3f85
16 changed files with 518 additions and 76 deletions

View file

@ -1,5 +1,6 @@
import styled from 'styles' import styled from 'styles'
import { useSelector } from 'state' import { ErrorBoundary } from 'react-error-boundary'
import state, { useSelector } from 'state'
import React, { useRef } from 'react' import React, { useRef } from 'react'
import useZoomEvents from 'hooks/useZoomEvents' import useZoomEvents from 'hooks/useZoomEvents'
import useCamera from 'hooks/useCamera' import useCamera from 'hooks/useCamera'
@ -27,6 +28,12 @@ export default function Canvas(): JSX.Element {
return ( return (
<ContextMenu> <ContextMenu>
<MainSVG ref={rCanvas} {...events}> <MainSVG ref={rCanvas} {...events}>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<Defs /> <Defs />
{isReady && ( {isReady && (
<g ref={rGroup} id="shapes"> <g ref={rGroup} id="shapes">
@ -37,6 +44,7 @@ export default function Canvas(): JSX.Element {
<Brush /> <Brush />
</g> </g>
)} )}
</ErrorBoundary>
</MainSVG> </MainSVG>
</ContextMenu> </ContextMenu>
) )
@ -59,3 +67,20 @@ const MainSVG = styled('svg', {
userSelect: 'none', userSelect: 'none',
}, },
}) })
function ErrorFallback({ error, resetErrorBoundary }) {
React.useEffect(() => {
console.error(error)
const copy = 'Sorry, something went wrong. Clear canvas and continue?'
if (window.confirm(copy)) {
state.send('CLEARED_PAGE')
resetErrorBoundary()
}
}, [])
return (
<g>
<text>Oops</text>
</g>
)
}

View file

@ -199,7 +199,7 @@ interface GroupShape extends BaseShape {
size: number[] size: number[]
} }
type ShapeProps<T extends Shape> = Partial<T> & { type ShapeProps<T extends Shape> = Partial<Omit<T, 'style'>> & {
style?: Partial<ShapeStyles> style?: Partial<ShapeStyles>
} }
@ -608,57 +608,235 @@ interface ShapeUtility<K extends Shape> {
return { ...this._shape } return { ...this._shape }
} }
/**
* Destroy the shape.
*/
destroy(): void { destroy(): void {
codeShapes.delete(this) codeShapes.delete(this)
} }
/**
* Move the shape to a point.
* @param delta
*/
moveTo(point: number[]): CodeShape<T> { moveTo(point: number[]): CodeShape<T> {
this.utils.setProperty(this._shape, 'point', point) return this.translateTo(point)
}
/**
* Move the shape to a point.
* @param delta
*/
translateTo(point: number[]): CodeShape<T> {
this.utils.translateTo(this._shape, point)
return this return this
} }
translate(delta: number[]): CodeShape<T> { /**
this.utils.setProperty( * Move the shape by a delta.
this._shape, * @param delta
'point', */
vec.add(this._shape.point, delta) translateBy(delta: number[]): CodeShape<T> {
) this.utils.translateTo(this._shape, delta)
return this return this
} }
rotate(rotation: number): CodeShape<T> { /**
this.utils.setProperty(this._shape, 'rotation', rotation) * Rotate the shape.
*/
rotateTo(rotation: number): CodeShape<T> {
this.utils.rotateTo(this._shape, rotation, this.shape.rotation - rotation)
return this return this
} }
/**
* Rotate the shape by a delta.
*/
rotateBy(rotation: number): CodeShape<T> {
this.utils.rotateBy(this._shape, rotation)
return this
}
/**
* Get the shape's bounding box.
*/
getBounds(): CodeShape<T> { getBounds(): CodeShape<T> {
this.utils.getBounds(this.shape) this.utils.getBounds(this.shape)
return this return this
} }
/**
* Test whether a point is inside of the shape.
*/
hitTest(point: number[]): CodeShape<T> { hitTest(point: number[]): CodeShape<T> {
this.utils.hitTest(this.shape, point) this.utils.hitTest(this.shape, point)
return this return this
} }
/**
* Move the shape to the back of the painting order.
*/
moveToBack(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const first = sorted[0].childIndex
sorted.forEach((shape) => shape.childIndex++)
this.childIndex = first
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* Move the shape to the top of the painting order.
*/
moveToFront(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const ahead = sorted.slice(sorted.indexOf(this))
const last = ahead[ahead.length - 1].childIndex
ahead.forEach((shape) => shape.childIndex--)
this.childIndex = last
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* Move the shape backward in the painting order.
*/
moveBackward(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const next = sorted[sorted.indexOf(this) - 1]
if (!next) return
const index = next.childIndex
next.childIndex = this.childIndex
this.childIndex = index
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* Move the shape forward in the painting order.
*/
moveForward(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const next = sorted[sorted.indexOf(this) + 1]
if (!next) return
const index = next.childIndex
next.childIndex = this.childIndex
this.childIndex = index
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* The shape's underlying shape.
*/
get shape(): T { get shape(): T {
return this._shape return this._shape
} }
/**
* The shape's current point.
*/
get point(): number[] { get point(): number[] {
return [...this.shape.point] return [...this.shape.point]
} }
set point(point: number[]) {
getShapeUtils(this.shape).translateTo(this._shape, point)
}
/**
* The shape's rotation.
*/
get rotation(): number { get rotation(): number {
return this.shape.rotation return this.shape.rotation
} }
set rotation(rotation: number) {
getShapeUtils(this.shape).rotateTo(
this._shape,
rotation,
rotation - this.shape.rotation
)
}
/**
* The shape's color style.
*/
get color(): ColorStyle {
return this.shape.style.color
}
set color(color: ColorStyle) {
getShapeUtils(this.shape).applyStyles(this._shape, { color })
}
/**
* The shape's dash style.
*/
get dash(): DashStyle {
return this.shape.style.dash
}
set dash(dash: DashStyle) {
getShapeUtils(this.shape).applyStyles(this._shape, { dash })
}
/**
* The shape's stroke width.
*/
get strokeWidth(): SizeStyle {
return this.shape.style.size
}
set strokeWidth(size: SizeStyle) {
getShapeUtils(this.shape).applyStyles(this._shape, { size })
}
/**
* The shape's index in the painting order.
*/
get childIndex(): number {
return this.shape.childIndex
}
set childIndex(childIndex: number) {
getShapeUtils(this.shape).setProperty(this._shape, 'childIndex', childIndex)
}
} }
/** /**
* ## Dot * ## Dot
*/ */
class Dot extends CodeShape<DotShape> { class Dot extends CodeShape<DotShape> {
constructor(props = {} as Partial<DotShape> & Partial<ShapeStyles>) { constructor(props = {} as ShapeProps<DotShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(), seed: Math.random(),
@ -686,7 +864,7 @@ interface ShapeUtility<K extends Shape> {
* ## Ellipse * ## Ellipse
*/ */
class Ellipse extends CodeShape<EllipseShape> { class Ellipse extends CodeShape<EllipseShape> {
constructor(props = {} as Partial<EllipseShape> & Partial<ShapeStyles>) { constructor(props = {} as ShapeProps<EllipseShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(), seed: Math.random(),
@ -720,7 +898,7 @@ interface ShapeUtility<K extends Shape> {
* ## Line * ## Line
*/ */
class Line extends CodeShape<LineShape> { class Line extends CodeShape<LineShape> {
constructor(props = {} as Partial<LineShape> & Partial<ShapeStyles>) { constructor(props = {} as ShapeProps<LineShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(), seed: Math.random(),
@ -753,7 +931,7 @@ interface ShapeUtility<K extends Shape> {
* ## Polyline * ## Polyline
*/ */
class Polyline extends CodeShape<PolylineShape> { class Polyline extends CodeShape<PolylineShape> {
constructor(props = {} as Partial<PolylineShape> & Partial<ShapeStyles>) { constructor(props = {} as ShapeProps<PolylineShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(), seed: Math.random(),
@ -782,7 +960,7 @@ interface ShapeUtility<K extends Shape> {
* ## Ray * ## Ray
*/ */
class Ray extends CodeShape<RayShape> { class Ray extends CodeShape<RayShape> {
constructor(props = {} as Partial<RayShape> & Partial<ShapeStyles>) { constructor(props = {} as ShapeProps<RayShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(), seed: Math.random(),
@ -846,8 +1024,7 @@ interface ShapeUtility<K extends Shape> {
*/ */
class Arrow extends CodeShape<ArrowShape> { class Arrow extends CodeShape<ArrowShape> {
constructor( constructor(
props = {} as Partial<ArrowShape> & props = {} as ShapeProps<ArrowShape> & { start: number[]; end: number[] }
Partial<ShapeStyles> & { start?: number[]; end?: number[] }
) { ) {
const { start = [0, 0], end = [0, 0] } = props const { start = [0, 0], end = [0, 0] } = props
@ -940,7 +1117,7 @@ interface ShapeUtility<K extends Shape> {
* ## Draw * ## Draw
*/ */
class Draw extends CodeShape<DrawShape> { class Draw extends CodeShape<DrawShape> {
constructor(props = {} as Partial<DrawShape>) { constructor(props = {} as ShapeProps<DrawShape>) {
super({ super({
id: uniqueId(), id: uniqueId(),
seed: Math.random(), seed: Math.random(),
@ -1459,12 +1636,24 @@ interface ShapeUtility<K extends Shape> {
return -c / 2 + -step return -c / 2 + -step
} }
static getPointsBetween(a: number[], b: number[], steps = 6): number[][] { /**
* Get an array of points between two points.
* @param a
* @param b
* @param options
*/
static getPointsBetween(
a: number[],
b: number[],
options = {} as {
steps?: number
ease?: (t: number) => number
}
): number[][] {
const { steps = 6, ease = (t) => t * t * t } = options
return Array.from(Array(steps)) return Array.from(Array(steps))
.map((_, i) => { .map((_, i) => ease(i / steps))
const t = i / steps
return t * t * t
})
.map((t) => [...vec.lrp(a, b, t), (1 - t) / 2]) .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,8 +10,15 @@ import Utils from './utils'
import Vec from 'utils/vec' import Vec from 'utils/vec'
import { NumberControl, VectorControl, codeControls, controls } from './control' import { NumberControl, VectorControl, codeControls, controls } from './control'
import { codeShapes } from './index' import { codeShapes } from './index'
import { CodeControl, Data, Shape } from 'types' import {
import { getPage } from 'utils' CodeControl,
Data,
Shape,
DashStyle,
ColorStyle,
SizeStyle,
} from 'types'
import { getPage, getShapes } from 'utils'
import { transform } from 'sucrase' import { transform } from 'sucrase'
const baseScope = { const baseScope = {
@ -27,6 +34,9 @@ const baseScope = {
Draw, Draw,
VectorControl, VectorControl,
NumberControl, NumberControl,
DashStyle,
ColorStyle,
SizeStyle,
} }
/** /**
@ -53,10 +63,18 @@ export function generateFromCode(
new Function(...Object.keys(scope), `${transformed}`)(...Object.values(scope)) new Function(...Object.keys(scope), `${transformed}`)(...Object.values(scope))
const generatedShapes = Array.from(codeShapes.values()).map((instance) => ({ const startingChildIndex =
getShapes(data)
.filter((shape) => shape.parentId === data.currentPageId)
.sort((a, b) => a.childIndex - b.childIndex)[0]?.childIndex || 1
const generatedShapes = Array.from(codeShapes.values())
.sort((a, b) => a.shape.childIndex - b.shape.childIndex)
.map((instance, i) => ({
...instance.shape, ...instance.shape,
isGenerated: true, isGenerated: true,
parentId: getPage(data).id, parentId: getPage(data).id,
childIndex: startingChildIndex + i,
})) }))
const generatedControls = Array.from(codeControls.values()) const generatedControls = Array.from(codeControls.values())

View file

@ -1,9 +1,22 @@
import { Mutable, Shape, ShapeUtility } from 'types' import {
ColorStyle,
DashStyle,
Mutable,
Shape,
ShapeUtility,
SizeStyle,
} from 'types'
import { createShape, getShapeUtils } from 'state/shape-utils' import { createShape, getShapeUtils } from 'state/shape-utils'
import vec from 'utils/vec' import { setToArray } from 'utils'
export const codeShapes = new Set<CodeShape<Shape>>([]) export const codeShapes = new Set<CodeShape<Shape>>([])
function getOrderedShapes() {
return setToArray(codeShapes).sort(
(a, b) => a.shape.childIndex - b.shape.childIndex
)
}
/** /**
* A base class for code shapes. Note that creating a shape adds it to the * A base class for code shapes. Note that creating a shape adds it to the
* shape map, while deleting it removes it from the collected shapes set * shape map, while deleting it removes it from the collected shapes set
@ -22,48 +35,226 @@ export default class CodeShape<T extends Shape> {
return { ...this._shape } return { ...this._shape }
} }
/**
* Destroy the shape.
*/
destroy(): void { destroy(): void {
codeShapes.delete(this) codeShapes.delete(this)
} }
/**
* Move the shape to a point.
* @param delta
*/
moveTo(point: number[]): CodeShape<T> { moveTo(point: number[]): CodeShape<T> {
this.utils.setProperty(this._shape, 'point', point) return this.translateTo(point)
}
/**
* Move the shape to a point.
* @param delta
*/
translateTo(point: number[]): CodeShape<T> {
this.utils.translateTo(this._shape, point)
return this return this
} }
translate(delta: number[]): CodeShape<T> { /**
this.utils.setProperty( * Move the shape by a delta.
this._shape, * @param delta
'point', */
vec.add(this._shape.point, delta) translateBy(delta: number[]): CodeShape<T> {
) this.utils.translateTo(this._shape, delta)
return this return this
} }
rotate(rotation: number): CodeShape<T> { /**
this.utils.setProperty(this._shape, 'rotation', rotation) * Rotate the shape.
*/
rotateTo(rotation: number): CodeShape<T> {
this.utils.rotateTo(this._shape, rotation, this.shape.rotation - rotation)
return this return this
} }
/**
* Rotate the shape by a delta.
*/
rotateBy(rotation: number): CodeShape<T> {
this.utils.rotateBy(this._shape, rotation)
return this
}
/**
* Get the shape's bounding box.
*/
getBounds(): CodeShape<T> { getBounds(): CodeShape<T> {
this.utils.getBounds(this.shape) this.utils.getBounds(this.shape)
return this return this
} }
/**
* Test whether a point is inside of the shape.
*/
hitTest(point: number[]): CodeShape<T> { hitTest(point: number[]): CodeShape<T> {
this.utils.hitTest(this.shape, point) this.utils.hitTest(this.shape, point)
return this return this
} }
/**
* Move the shape to the back of the painting order.
*/
moveToBack(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const first = sorted[0].childIndex
sorted.forEach((shape) => shape.childIndex++)
this.childIndex = first
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* Move the shape to the top of the painting order.
*/
moveToFront(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const ahead = sorted.slice(sorted.indexOf(this))
const last = ahead[ahead.length - 1].childIndex
ahead.forEach((shape) => shape.childIndex--)
this.childIndex = last
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* Move the shape backward in the painting order.
*/
moveBackward(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const next = sorted[sorted.indexOf(this) - 1]
if (!next) return
const index = next.childIndex
next.childIndex = this.childIndex
this.childIndex = index
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* Move the shape forward in the painting order.
*/
moveForward(): CodeShape<T> {
const sorted = getOrderedShapes()
if (sorted.length <= 1) return
const next = sorted[sorted.indexOf(this) + 1]
if (!next) return
const index = next.childIndex
next.childIndex = this.childIndex
this.childIndex = index
codeShapes.clear()
sorted.forEach((shape) => codeShapes.add(shape))
return this
}
/**
* The shape's underlying shape.
*/
get shape(): T { get shape(): T {
return this._shape return this._shape
} }
/**
* The shape's current point.
*/
get point(): number[] { get point(): number[] {
return [...this.shape.point] return [...this.shape.point]
} }
set point(point: number[]) {
getShapeUtils(this.shape).translateTo(this._shape, point)
}
/**
* The shape's rotation.
*/
get rotation(): number { get rotation(): number {
return this.shape.rotation return this.shape.rotation
} }
set rotation(rotation: number) {
getShapeUtils(this.shape).rotateTo(
this._shape,
rotation,
rotation - this.shape.rotation
)
}
/**
* The shape's color style.
*/
get color(): ColorStyle {
return this.shape.style.color
}
set color(color: ColorStyle) {
getShapeUtils(this.shape).applyStyles(this._shape, { color })
}
/**
* The shape's dash style.
*/
get dash(): DashStyle {
return this.shape.style.dash
}
set dash(dash: DashStyle) {
getShapeUtils(this.shape).applyStyles(this._shape, { dash })
}
/**
* The shape's stroke width.
*/
get strokeWidth(): SizeStyle {
return this.shape.style.size
}
set strokeWidth(size: SizeStyle) {
getShapeUtils(this.shape).applyStyles(this._shape, { size })
}
/**
* The shape's index in the painting order.
*/
get childIndex(): number {
return this.shape.childIndex
}
set childIndex(childIndex: number) {
getShapeUtils(this.shape).setProperty(this._shape, 'childIndex', childIndex)
}
} }

View file

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

View file

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

View file

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

View file

@ -496,12 +496,24 @@ export default class Utils {
return -c / 2 + -step return -c / 2 + -step
} }
static getPointsBetween(a: number[], b: number[], steps = 6): number[][] { /**
* Get an array of points between two points.
* @param a
* @param b
* @param options
*/
static getPointsBetween(
a: number[],
b: number[],
options = {} as {
steps?: number
ease?: (t: number) => number
}
): number[][] {
const { steps = 6, ease = (t) => t * t * t } = options
return Array.from(Array(steps)) return Array.from(Array(steps))
.map((_, i) => { .map((_, i) => ease(i / steps))
const t = i / steps
return t * t * t
})
.map((t) => [...vec.lrp(a, b, t), (1 - t) / 2]) .map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
} }

View file

@ -335,15 +335,15 @@ class Storage {
} }
) )
const documentName = data.document.name
const fa = await import('browser-fs-access') const fa = await import('browser-fs-access')
fa.fileSave( fa.fileSave(
blob, blob,
{ {
fileName: `${ fileName: `${
saveAs saveAs ? documentName : this.previousSaveHandle?.name || 'My Document'
? data.document.name
: this.previousSaveHandle?.name || 'My Document'
}.tldr`, }.tldr`,
description: 'tldraw file', description: 'tldraw file',
extensions: ['.tldr'], extensions: ['.tldr'],

View file

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

View file

@ -6592,6 +6592,13 @@ react-dom@^17.0.2:
object-assign "^4.1.1" object-assign "^4.1.1"
scheduler "^0.20.2" scheduler "^0.20.2"
react-error-boundary@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.3.tgz#276bfa05de8ac17b863587c9e0647522c25e2a0b"
integrity sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA==
dependencies:
"@babel/runtime" "^7.12.5"
react-feather@^2.0.9: react-feather@^2.0.9:
version "2.0.9" version "2.0.9"
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480" resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"