Adds error boundary, improves code shapes types.
This commit is contained in:
parent
69bdab520a
commit
32922b3f85
16 changed files with 518 additions and 76 deletions
|
@ -1,5 +1,6 @@
|
|||
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 useZoomEvents from 'hooks/useZoomEvents'
|
||||
import useCamera from 'hooks/useCamera'
|
||||
|
@ -27,16 +28,23 @@ export default function Canvas(): JSX.Element {
|
|||
return (
|
||||
<ContextMenu>
|
||||
<MainSVG ref={rCanvas} {...events}>
|
||||
<Defs />
|
||||
{isReady && (
|
||||
<g ref={rGroup} id="shapes">
|
||||
<BoundsBg />
|
||||
<Page />
|
||||
<Bounds />
|
||||
<Handles />
|
||||
<Brush />
|
||||
</g>
|
||||
)}
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorFallback}
|
||||
onReset={() => {
|
||||
// reset the state of your app so the error doesn't happen again
|
||||
}}
|
||||
>
|
||||
<Defs />
|
||||
{isReady && (
|
||||
<g ref={rGroup} id="shapes">
|
||||
<BoundsBg />
|
||||
<Page />
|
||||
<Bounds />
|
||||
<Handles />
|
||||
<Brush />
|
||||
</g>
|
||||
)}
|
||||
</ErrorBoundary>
|
||||
</MainSVG>
|
||||
</ContextMenu>
|
||||
)
|
||||
|
@ -59,3 +67,20 @@ const MainSVG = styled('svg', {
|
|||
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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ interface GroupShape extends BaseShape {
|
|||
size: number[]
|
||||
}
|
||||
|
||||
type ShapeProps<T extends Shape> = Partial<T> & {
|
||||
type ShapeProps<T extends Shape> = Partial<Omit<T, 'style'>> & {
|
||||
style?: Partial<ShapeStyles>
|
||||
}
|
||||
|
||||
|
@ -608,57 +608,235 @@ interface ShapeUtility<K extends Shape> {
|
|||
return { ...this._shape }
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the shape.
|
||||
*/
|
||||
destroy(): void {
|
||||
codeShapes.delete(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the shape to a point.
|
||||
* @param delta
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
translate(delta: number[]): CodeShape<T> {
|
||||
this.utils.setProperty(
|
||||
this._shape,
|
||||
'point',
|
||||
vec.add(this._shape.point, delta)
|
||||
)
|
||||
/**
|
||||
* Move the shape by a delta.
|
||||
* @param delta
|
||||
*/
|
||||
translateBy(delta: number[]): CodeShape<T> {
|
||||
this.utils.translateTo(this._shape, delta)
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
this.utils.getBounds(this.shape)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a point is inside of the shape.
|
||||
*/
|
||||
hitTest(point: number[]): CodeShape<T> {
|
||||
this.utils.hitTest(this.shape, point)
|
||||
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 {
|
||||
return this._shape
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape's current point.
|
||||
*/
|
||||
get point(): number[] {
|
||||
return [...this.shape.point]
|
||||
}
|
||||
|
||||
set point(point: number[]) {
|
||||
getShapeUtils(this.shape).translateTo(this._shape, point)
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape's rotation.
|
||||
*/
|
||||
get rotation(): number {
|
||||
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
|
||||
*/
|
||||
class Dot extends CodeShape<DotShape> {
|
||||
constructor(props = {} as Partial<DotShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<DotShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
@ -686,7 +864,7 @@ interface ShapeUtility<K extends Shape> {
|
|||
* ## Ellipse
|
||||
*/
|
||||
class Ellipse extends CodeShape<EllipseShape> {
|
||||
constructor(props = {} as Partial<EllipseShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<EllipseShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
@ -720,7 +898,7 @@ interface ShapeUtility<K extends Shape> {
|
|||
* ## Line
|
||||
*/
|
||||
class Line extends CodeShape<LineShape> {
|
||||
constructor(props = {} as Partial<LineShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<LineShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
@ -753,7 +931,7 @@ interface ShapeUtility<K extends Shape> {
|
|||
* ## Polyline
|
||||
*/
|
||||
class Polyline extends CodeShape<PolylineShape> {
|
||||
constructor(props = {} as Partial<PolylineShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<PolylineShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
@ -782,7 +960,7 @@ interface ShapeUtility<K extends Shape> {
|
|||
* ## Ray
|
||||
*/
|
||||
class Ray extends CodeShape<RayShape> {
|
||||
constructor(props = {} as Partial<RayShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<RayShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
@ -846,8 +1024,7 @@ interface ShapeUtility<K extends Shape> {
|
|||
*/
|
||||
class Arrow extends CodeShape<ArrowShape> {
|
||||
constructor(
|
||||
props = {} as Partial<ArrowShape> &
|
||||
Partial<ShapeStyles> & { start?: number[]; end?: number[] }
|
||||
props = {} as ShapeProps<ArrowShape> & { start: number[]; end: number[] }
|
||||
) {
|
||||
const { start = [0, 0], end = [0, 0] } = props
|
||||
|
||||
|
@ -940,7 +1117,7 @@ interface ShapeUtility<K extends Shape> {
|
|||
* ## Draw
|
||||
*/
|
||||
class Draw extends CodeShape<DrawShape> {
|
||||
constructor(props = {} as Partial<DrawShape>) {
|
||||
constructor(props = {} as ShapeProps<DrawShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
@ -1459,12 +1636,24 @@ interface ShapeUtility<K extends Shape> {
|
|||
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))
|
||||
.map((_, i) => {
|
||||
const t = i / steps
|
||||
return t * t * t
|
||||
})
|
||||
.map((_, i) => ease(i / steps))
|
||||
.map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"perfect-freehand": "^0.4.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-use-gesture": "^9.1.3",
|
||||
"sucrase": "^3.19.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import CodeShape from './index'
|
||||
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 { getShapeUtils } from 'state/shape-utils'
|
||||
import Vec from 'utils/vec'
|
||||
|
@ -10,8 +10,7 @@ import Vec from 'utils/vec'
|
|||
*/
|
||||
export default class Arrow extends CodeShape<ArrowShape> {
|
||||
constructor(
|
||||
props = {} as Partial<ArrowShape> &
|
||||
Partial<ShapeStyles> & { start?: number[]; end?: number[] }
|
||||
props = {} as ShapeProps<ArrowShape> & { start: number[]; end: number[] }
|
||||
) {
|
||||
const { start = [0, 0], end = [0, 0] } = props
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils'
|
||||
import { DotShape, ShapeStyles, ShapeType } from 'types'
|
||||
import { DotShape, ShapeProps, ShapeType } from 'types'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
/**
|
||||
* ## Dot
|
||||
*/
|
||||
export default class Dot extends CodeShape<DotShape> {
|
||||
constructor(props = {} as Partial<DotShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<DotShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils'
|
||||
import { DrawShape, ShapeType } from 'types'
|
||||
import { DrawShape, ShapeProps, ShapeType } from 'types'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
/**
|
||||
* ## Draw
|
||||
*/
|
||||
export default class Draw extends CodeShape<DrawShape> {
|
||||
constructor(props = {} as Partial<DrawShape>) {
|
||||
constructor(props = {} as ShapeProps<DrawShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils'
|
||||
import { EllipseShape, ShapeStyles, ShapeType } from 'types'
|
||||
import { EllipseShape, ShapeProps, ShapeType } from 'types'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
/**
|
||||
* ## Ellipse
|
||||
*/
|
||||
export default class Ellipse extends CodeShape<EllipseShape> {
|
||||
constructor(props = {} as Partial<EllipseShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<EllipseShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
|
|
@ -10,8 +10,15 @@ import Utils from './utils'
|
|||
import Vec from 'utils/vec'
|
||||
import { NumberControl, VectorControl, codeControls, controls } from './control'
|
||||
import { codeShapes } from './index'
|
||||
import { CodeControl, Data, Shape } from 'types'
|
||||
import { getPage } from 'utils'
|
||||
import {
|
||||
CodeControl,
|
||||
Data,
|
||||
Shape,
|
||||
DashStyle,
|
||||
ColorStyle,
|
||||
SizeStyle,
|
||||
} from 'types'
|
||||
import { getPage, getShapes } from 'utils'
|
||||
import { transform } from 'sucrase'
|
||||
|
||||
const baseScope = {
|
||||
|
@ -27,6 +34,9 @@ const baseScope = {
|
|||
Draw,
|
||||
VectorControl,
|
||||
NumberControl,
|
||||
DashStyle,
|
||||
ColorStyle,
|
||||
SizeStyle,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,11 +63,19 @@ export function generateFromCode(
|
|||
|
||||
new Function(...Object.keys(scope), `${transformed}`)(...Object.values(scope))
|
||||
|
||||
const generatedShapes = Array.from(codeShapes.values()).map((instance) => ({
|
||||
...instance.shape,
|
||||
isGenerated: true,
|
||||
parentId: getPage(data).id,
|
||||
}))
|
||||
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,
|
||||
isGenerated: true,
|
||||
parentId: getPage(data).id,
|
||||
childIndex: startingChildIndex + i,
|
||||
}))
|
||||
|
||||
const generatedControls = Array.from(codeControls.values())
|
||||
|
||||
|
|
|
@ -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 vec from 'utils/vec'
|
||||
import { setToArray } from 'utils'
|
||||
|
||||
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
|
||||
* 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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the shape.
|
||||
*/
|
||||
destroy(): void {
|
||||
codeShapes.delete(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the shape to a point.
|
||||
* @param delta
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
translate(delta: number[]): CodeShape<T> {
|
||||
this.utils.setProperty(
|
||||
this._shape,
|
||||
'point',
|
||||
vec.add(this._shape.point, delta)
|
||||
)
|
||||
/**
|
||||
* Move the shape by a delta.
|
||||
* @param delta
|
||||
*/
|
||||
translateBy(delta: number[]): CodeShape<T> {
|
||||
this.utils.translateTo(this._shape, delta)
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
this.utils.getBounds(this.shape)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a point is inside of the shape.
|
||||
*/
|
||||
hitTest(point: number[]): CodeShape<T> {
|
||||
this.utils.hitTest(this.shape, point)
|
||||
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 {
|
||||
return this._shape
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape's current point.
|
||||
*/
|
||||
get point(): number[] {
|
||||
return [...this.shape.point]
|
||||
}
|
||||
|
||||
set point(point: number[]) {
|
||||
getShapeUtils(this.shape).translateTo(this._shape, point)
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape's rotation.
|
||||
*/
|
||||
get rotation(): number {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils'
|
||||
import { LineShape, ShapeStyles, ShapeType } from 'types'
|
||||
import { LineShape, ShapeProps, ShapeType } from 'types'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
/**
|
||||
* ## Line
|
||||
*/
|
||||
export default class Line extends CodeShape<LineShape> {
|
||||
constructor(props = {} as Partial<LineShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<LineShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils'
|
||||
import { PolylineShape, ShapeStyles, ShapeType } from 'types'
|
||||
import { PolylineShape, ShapeProps, ShapeType } from 'types'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
/**
|
||||
* ## Polyline
|
||||
*/
|
||||
export default class Polyline extends CodeShape<PolylineShape> {
|
||||
constructor(props = {} as Partial<PolylineShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<PolylineShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils'
|
||||
import { RayShape, ShapeStyles, ShapeType } from 'types'
|
||||
import { RayShape, ShapeProps, ShapeType } from 'types'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
/**
|
||||
* ## Ray
|
||||
*/
|
||||
export default class Ray extends CodeShape<RayShape> {
|
||||
constructor(props = {} as Partial<RayShape> & Partial<ShapeStyles>) {
|
||||
constructor(props = {} as ShapeProps<RayShape>) {
|
||||
super({
|
||||
id: uniqueId(),
|
||||
seed: Math.random(),
|
||||
|
|
|
@ -496,12 +496,24 @@ export default class Utils {
|
|||
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))
|
||||
.map((_, i) => {
|
||||
const t = i / steps
|
||||
return t * t * t
|
||||
})
|
||||
.map((_, i) => ease(i / steps))
|
||||
.map((t) => [...vec.lrp(a, b, t), (1 - t) / 2])
|
||||
}
|
||||
|
||||
|
|
|
@ -335,15 +335,15 @@ class Storage {
|
|||
}
|
||||
)
|
||||
|
||||
const documentName = data.document.name
|
||||
|
||||
const fa = await import('browser-fs-access')
|
||||
|
||||
fa.fileSave(
|
||||
blob,
|
||||
{
|
||||
fileName: `${
|
||||
saveAs
|
||||
? data.document.name
|
||||
: this.previousSaveHandle?.name || 'My Document'
|
||||
saveAs ? documentName : this.previousSaveHandle?.name || 'My Document'
|
||||
}.tldr`,
|
||||
description: 'tldraw file',
|
||||
extensions: ['.tldr'],
|
||||
|
|
2
types.ts
2
types.ts
|
@ -191,7 +191,7 @@ export interface GroupShape extends BaseShape {
|
|||
size: number[]
|
||||
}
|
||||
|
||||
export type ShapeProps<T extends Shape> = Partial<T> & {
|
||||
export type ShapeProps<T extends Shape> = Partial<Omit<T, 'style'>> & {
|
||||
style?: Partial<ShapeStyles>
|
||||
}
|
||||
|
||||
|
|
|
@ -6592,6 +6592,13 @@ react-dom@^17.0.2:
|
|||
object-assign "^4.1.1"
|
||||
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:
|
||||
version "2.0.9"
|
||||
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"
|
||||
|
|
Loading…
Reference in a new issue