Makes shapes immutable, adds parenting methods to utils

This commit is contained in:
Steve Ruiz 2021-05-25 10:00:59 +01:00
parent f4e429af0e
commit a1cc578bb9
18 changed files with 245 additions and 163 deletions

View file

@ -39,10 +39,9 @@ export function generateFromCode(code: string) {
new Function(...Object.keys(scope), `${code}`)(...Object.values(scope)) new Function(...Object.keys(scope), `${code}`)(...Object.values(scope))
const generatedShapes = Array.from(codeShapes.values()).map((instance) => { const generatedShapes = Array.from(codeShapes.values()).map(
instance.shape.isGenerated = true (instance) => instance.shape
return instance.shape )
})
const generatedControls = Array.from(codeControls.values()) const generatedControls = Array.from(codeControls.values())
@ -73,10 +72,9 @@ export function updateFromCode(
new Function(...Object.keys(scope), `${code}`)(...Object.values(scope)) new Function(...Object.keys(scope), `${code}`)(...Object.values(scope))
const generatedShapes = Array.from(codeShapes.values()).map((instance) => { const generatedShapes = Array.from(codeShapes.values()).map(
instance.shape.isGenerated = true (instance) => instance.shape
return instance.shape )
})
return { shapes: generatedShapes } return { shapes: generatedShapes }
} }

View file

@ -1,24 +1,23 @@
import { Shape } from "types" import { Shape } from "types"
import { getShapeUtils } from "lib/shape-utils" import { getShapeUtils, ShapeUtility } from "lib/shape-utils"
import * as vec from "utils/vec" import * as vec from "utils/vec"
import Vector from "./vector" import Vector from "./vector"
import { vectorToPoint } from "utils/utils" import { vectorToPoint } from "utils/utils"
export const codeShapes = new Set<CodeShape<Shape>>([]) export const codeShapes = new Set<CodeShape<Shape>>([])
type WithVectors<T extends Shape> = {
[key in keyof T]: number[] extends T[key] ? Vector : T[key]
}
/** /**
* 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
*/ */
export default class CodeShape<T extends Shape> { export default class CodeShape<T extends Shape> {
private _shape: T private _shape: T
private utils: ShapeUtility<Shape>
constructor(props: T) { constructor(props: T) {
this._shape = props this._shape = props
this.utils = getShapeUtils(this.shape)
codeShapes.add(this) codeShapes.add(this)
} }
@ -27,27 +26,31 @@ export default class CodeShape<T extends Shape> {
} }
moveTo(point: Vector) { moveTo(point: Vector) {
this.shape.point = vectorToPoint(point) this.utils.translate(this._shape, vectorToPoint(point))
return this
} }
translate(delta: Vector) { translate(delta: Vector) {
this.shape.point = vec.add(this._shape.point, vectorToPoint(delta)) this.utils.translate(
this._shape,
vec.add(this._shape.point, vectorToPoint(delta))
)
return this
} }
rotate(rotation: number) { rotate(rotation: number) {
this.shape.rotation = rotation this.utils.rotate(this._shape, rotation)
} return this
scale(scale: number) {
return getShapeUtils(this.shape).scale(this.shape, scale)
} }
getBounds() { getBounds() {
return getShapeUtils(this.shape).getBounds(this.shape) this.utils.getBounds(this.shape)
return this
} }
hitTest(point: Vector) { hitTest(point: Vector) {
return getShapeUtils(this.shape).hitTest(this.shape, vectorToPoint(point)) this.utils.hitTest(this.shape, vectorToPoint(point))
return this
} }
get shape() { get shape() {

View file

@ -81,17 +81,13 @@ const circle = registerShapeUtils<CircleShape>({
) )
}, },
rotate(shape) {
return shape
},
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
}, },
scale(shape, scale) { rotate(shape) {
return shape return this
}, },
transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) { transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) {
@ -107,13 +103,23 @@ const circle = registerShapeUtils<CircleShape>({
(scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]), (scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
] ]
return shape return this
}, },
transformSingle(shape, bounds, info) { transformSingle(shape, bounds, info) {
shape.radius = Math.min(bounds.width, bounds.height) / 2 shape.radius = Math.min(bounds.width, bounds.height) / 2
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
return shape return this
},
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
}, },
canTransform: true, canTransform: true,

View file

@ -70,26 +70,33 @@ const dot = registerShapeUtils<DotShape>({
}, },
rotate(shape) { rotate(shape) {
return shape return this
},
scale(shape, scale: number) {
return shape
}, },
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
}, },
transform(shape, bounds) { transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
return shape return this
}, },
transformSingle(shape, bounds, info) { transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info) this.transform(shape, bounds, info)
return this
},
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
}, },
canTransform: false, canTransform: false,

View file

@ -100,16 +100,12 @@ const ellipse = registerShapeUtils<EllipseShape>({
}, },
rotate(shape) { rotate(shape) {
return shape return this
}, },
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
},
scale(shape, scale: number) {
return shape
}, },
transform(shape, bounds, { scaleX, scaleY, initialShape }) { transform(shape, bounds, { scaleX, scaleY, initialShape }) {
@ -122,13 +118,23 @@ const ellipse = registerShapeUtils<EllipseShape>({
? -initialShape.rotation ? -initialShape.rotation
: initialShape.rotation : initialShape.rotation
return shape return this
}, },
transformSingle(shape, bounds, info) { transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info) return this.transform(shape, bounds, info)
}, },
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
},
canTransform: true, canTransform: true,
canChangeAspectRatio: true, canChangeAspectRatio: true,
}) })

View file

@ -6,6 +6,7 @@ import {
ShapeType, ShapeType,
Corner, Corner,
Edge, Edge,
ShapeByType,
} from "types" } from "types"
import circle from "./circle" import circle from "./circle"
import dot from "./dot" import dot from "./dot"
@ -26,13 +27,66 @@ Operations throughout the app will call these utility methods
when performing tests (such as hit tests) or mutations, such as translations. when performing tests (such as hit tests) or mutations, such as translations.
*/ */
export interface ShapeUtility<K extends Shape> { export interface ShapeUtility<K extends Readonly<Shape>> {
// A cache for the computed bounds of this kind of shape. // A cache for the computed bounds of this kind of shape.
boundsCache: WeakMap<K, Bounds> boundsCache: WeakMap<K, Bounds>
// Whether to show transform controls when this shape is selected.
canTransform: boolean
// Whether the shape's aspect ratio can change
canChangeAspectRatio: boolean
// Create a new shape. // Create a new shape.
create(props: Partial<K>): K create(props: Partial<K>): K
// Apply a translation to a shape.
translate(this: ShapeUtility<K>, shape: K, delta: number[]): ShapeUtility<K>
// Apply a rotation to a shape.
rotate(this: ShapeUtility<K>, shape: K, rotation: number): ShapeUtility<K>
// Transform to fit a new bounding box when more than one shape is selected.
transform(
this: ShapeUtility<K>,
shape: K,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): ShapeUtility<K>
// Transform a single shape to fit a new bounding box.
transformSingle(
this: ShapeUtility<K>,
shape: K,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): ShapeUtility<K>
// Move a shape to a new parent.
setParent(this: ShapeUtility<K>, shape: K, parentId: string): ShapeUtility<K>
// Change the child index of a shape
setChildIndex(
this: ShapeUtility<K>,
shape: K,
childIndex: number
): ShapeUtility<K>
// Render a shape to JSX.
render(this: ShapeUtility<K>, shape: K): JSX.Element
// Get the bounds of the a shape. // Get the bounds of the a shape.
getBounds(this: ShapeUtility<K>, shape: K): Bounds getBounds(this: ShapeUtility<K>, shape: K): Bounds
@ -47,55 +101,10 @@ export interface ShapeUtility<K extends Shape> {
// Test whether bounds collide with or contain a shape. // Test whether bounds collide with or contain a shape.
hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
// Apply a rotation to a shape.
rotate(this: ShapeUtility<K>, shape: K): K
// Apply a translation to a shape.
translate(this: ShapeUtility<K>, shape: K, delta: number[]): K
// Transform to fit a new bounding box.
transform(
this: ShapeUtility<K>,
shape: K,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): K
transformSingle(
this: ShapeUtility<K>,
shape: K,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): K
// Apply a scale to a shape.
scale(this: ShapeUtility<K>, shape: K, scale: number): K
// Render a shape to JSX.
render(this: ShapeUtility<K>, shape: K): JSX.Element
// Whether to show transform controls when this shape is selected.
canTransform: boolean
// Whether the shape's aspect ratio can change
canChangeAspectRatio: boolean
} }
// A mapping of shape types to shape utilities. // A mapping of shape types to shape utilities.
const shapeUtilityMap: { [key in ShapeType]: ShapeUtility<Shapes[key]> } = { const shapeUtilityMap: Record<ShapeType, ShapeUtility<Shape>> = {
[ShapeType.Circle]: circle, [ShapeType.Circle]: circle,
[ShapeType.Dot]: dot, [ShapeType.Dot]: dot,
[ShapeType.Polyline]: polyline, [ShapeType.Polyline]: polyline,
@ -110,8 +119,8 @@ const shapeUtilityMap: { [key in ShapeType]: ShapeUtility<Shapes[key]> } = {
* @param shape * @param shape
* @returns * @returns
*/ */
export function getShapeUtils(shape: Shape): ShapeUtility<typeof shape> { export function getShapeUtils<T extends Shape>(shape: T): ShapeUtility<T> {
return shapeUtilityMap[shape.type] return shapeUtilityMap[shape.type] as ShapeUtility<T>
} }
/** /**
@ -125,4 +134,11 @@ export function registerShapeUtils<T extends Shape>(
return Object.freeze(shape) return Object.freeze(shape)
} }
export function createShape<T extends ShapeType>(
type: T,
props: Partial<ShapeByType<T>>
) {
return shapeUtilityMap[type].create(props) as ShapeByType<T>
}
export default shapeUtilityMap export default shapeUtilityMap

View file

@ -79,28 +79,34 @@ const line = registerShapeUtils<LineShape>({
}, },
rotate(shape) { rotate(shape) {
return shape return this
}, },
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
},
scale(shape, scale: number) {
return shape
}, },
transform(shape, bounds) { transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
return shape return this
}, },
transformSingle(shape, bounds, info) { transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info) return this.transform(shape, bounds, info)
}, },
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
},
canTransform: false, canTransform: false,
canChangeAspectRatio: false, canChangeAspectRatio: false,
}) })

View file

@ -87,16 +87,12 @@ const polyline = registerShapeUtils<PolylineShape>({
}, },
rotate(shape) { rotate(shape) {
return shape return this
}, },
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
},
scale(shape, scale: number) {
return shape
}, },
transform(shape, bounds, { initialShape, scaleX, scaleY }) { transform(shape, bounds, { initialShape, scaleX, scaleY }) {
@ -117,11 +113,22 @@ const polyline = registerShapeUtils<PolylineShape>({
}) })
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
return shape return this
}, },
transformSingle(shape, bounds, info) { transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info) this.transform(shape, bounds, info)
return this
},
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
}, },
canTransform: true, canTransform: true,

View file

@ -79,28 +79,34 @@ const ray = registerShapeUtils<RayShape>({
}, },
rotate(shape) { rotate(shape) {
return shape return this
}, },
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
},
scale(shape, scale: number) {
return shape
}, },
transform(shape, bounds) { transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
return shape return this
}, },
transformSingle(shape, bounds, info) { transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info) return this.transform(shape, bounds, info)
}, },
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
},
canTransform: false, canTransform: false,
canChangeAspectRatio: false, canChangeAspectRatio: false,
}) })

View file

@ -96,16 +96,12 @@ const rectangle = registerShapeUtils<RectangleShape>({
}, },
rotate(shape) { rotate(shape) {
return shape return this
}, },
translate(shape, delta) { translate(shape, delta) {
shape.point = vec.add(shape.point, delta) shape.point = vec.add(shape.point, delta)
return shape return this
},
scale(shape, scale) {
return shape
}, },
transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) { transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) {
@ -133,13 +129,23 @@ const rectangle = registerShapeUtils<RectangleShape>({
: initialShape.rotation : initialShape.rotation
} }
return shape return this
}, },
transformSingle(shape, bounds) { transformSingle(shape, bounds) {
shape.size = [bounds.width, bounds.height] shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
return shape return this
},
setParent(shape, parentId) {
shape.parentId = parentId
return this
},
setChildIndex(shape, childIndex) {
shape.childIndex = childIndex
return this
}, },
canTransform: true, canTransform: true,

View file

@ -2,6 +2,7 @@ import Command from "./command"
import history from "../history" import history from "../history"
import { Data, MoveType, Shape } from "types" import { Data, MoveType, Shape } from "types"
import { forceIntegerChildIndices, getChildren, getPage } from "utils/utils" import { forceIntegerChildIndices, getChildren, getPage } from "utils/utils"
import { getShapeUtils } from "lib/shape-utils"
export default function moveCommand(data: Data, type: MoveType) { export default function moveCommand(data: Data, type: MoveType) {
const { currentPageId } = data const { currentPageId } = data
@ -75,7 +76,8 @@ export default function moveCommand(data: Data, type: MoveType) {
const page = getPage(data) const page = getPage(data)
for (let id of selectedIds) { for (let id of selectedIds) {
page.shapes[id].childIndex = initialIndices[id] const shape = page.shapes[id]
getShapeUtils(shape).setChildIndex(shape, initialIndices[id])
} }
}, },
}) })
@ -93,7 +95,9 @@ function moveToFront(shapes: Shape[], siblings: Shape[]) {
const startIndex = Math.ceil(diff[0].childIndex) + 1 const startIndex = Math.ceil(diff[0].childIndex) + 1
shapes.forEach((shape, i) => (shape.childIndex = startIndex + i)) shapes.forEach((shape, i) =>
getShapeUtils(shape).setChildIndex(shape, startIndex + i)
)
} }
function moveToBack(shapes: Shape[], siblings: Shape[]) { function moveToBack(shapes: Shape[], siblings: Shape[]) {
@ -109,7 +113,9 @@ function moveToBack(shapes: Shape[], siblings: Shape[]) {
const step = startIndex / (shapes.length + 1) const step = startIndex / (shapes.length + 1)
shapes.forEach((shape, i) => (shape.childIndex = startIndex - (i + 1) * step)) shapes.forEach((shape, i) =>
getShapeUtils(shape).setChildIndex(shape, startIndex - (i + 1) * step)
)
} }
function moveForward(shape: Shape, siblings: Shape[], visited: Set<string>) { function moveForward(shape: Shape, siblings: Shape[], visited: Set<string>) {
@ -132,7 +138,7 @@ function moveForward(shape: Shape, siblings: Shape[], visited: Set<string>) {
: Math.ceil(nextSibling.childIndex + 1) : Math.ceil(nextSibling.childIndex + 1)
} }
shape.childIndex = nextIndex getShapeUtils(shape).setChildIndex(shape, nextIndex)
siblings.sort((a, b) => a.childIndex - b.childIndex) siblings.sort((a, b) => a.childIndex - b.childIndex)
} }
@ -158,7 +164,7 @@ function moveBackward(shape: Shape, siblings: Shape[], visited: Set<string>) {
: nextSibling.childIndex / 2 : nextSibling.childIndex / 2
} }
shape.childIndex = nextIndex getShapeUtils(shape).setChildIndex(shape, nextIndex)
siblings.sort((a, b) => a.childIndex - b.childIndex) siblings.sort((a, b) => a.childIndex - b.childIndex)
} }

View file

@ -3,6 +3,7 @@ import history from "../history"
import { Data } from "types" import { Data } from "types"
import { RotateSnapshot } from "state/sessions/rotate-session" import { RotateSnapshot } from "state/sessions/rotate-session"
import { getPage } from "utils/utils" import { getPage } from "utils/utils"
import { getShapeUtils } from "lib/shape-utils"
export default function rotateCommand( export default function rotateCommand(
data: Data, data: Data,
@ -19,8 +20,9 @@ export default function rotateCommand(
for (let { id, point, rotation } of after.shapes) { for (let { id, point, rotation } of after.shapes) {
const shape = shapes[id] const shape = shapes[id]
shape.rotation = rotation const utils = getShapeUtils(shape)
shape.point = point utils.rotate(shape, rotation)
utils.translate(shape, point)
} }
data.boundsRotation = after.boundsRotation data.boundsRotation = after.boundsRotation
@ -30,8 +32,9 @@ export default function rotateCommand(
for (let { id, point, rotation } of before.shapes) { for (let { id, point, rotation } of before.shapes) {
const shape = shapes[id] const shape = shapes[id]
shape.rotation = rotation const utils = getShapeUtils(shape)
shape.point = point utils.rotate(shape, rotation)
utils.translate(shape, point)
} }
data.boundsRotation = before.boundsRotation data.boundsRotation = before.boundsRotation

View file

@ -3,6 +3,7 @@ import history from "../history"
import { TranslateSnapshot } from "state/sessions/translate-session" import { TranslateSnapshot } from "state/sessions/translate-session"
import { Data } from "types" import { Data } from "types"
import { getPage } from "utils/utils" import { getPage } from "utils/utils"
import { getShapeUtils } from "lib/shape-utils"
export default function translateCommand( export default function translateCommand(
data: Data, data: Data,
@ -32,7 +33,8 @@ export default function translateCommand(
} }
for (const { id, point } of initialShapes) { for (const { id, point } of initialShapes) {
shapes[id].point = point const shape = shapes[id]
getShapeUtils(shape).translate(shape, point)
data.selectedIds.add(id) data.selectedIds.add(id)
} }
}, },
@ -49,7 +51,8 @@ export default function translateCommand(
} }
for (const { id, point } of initialShapes) { for (const { id, point } of initialShapes) {
shapes[id].point = point const shape = shapes[id]
getShapeUtils(shape).translate(shape, point)
data.selectedIds.add(id) data.selectedIds.add(id)
} }
}, },

View file

@ -11,6 +11,7 @@ import {
getSelectedShapes, getSelectedShapes,
getShapeBounds, getShapeBounds,
} from "utils/utils" } from "utils/utils"
import { getShapeUtils } from "lib/shape-utils"
const PI2 = Math.PI * 2 const PI2 = Math.PI * 2
@ -42,9 +43,13 @@ export default class RotateSession extends BaseSession {
for (let { id, center, offset, rotation } of shapes) { for (let { id, center, offset, rotation } of shapes) {
const shape = page.shapes[id] const shape = page.shapes[id]
shape.rotation = (PI2 + (rotation + rot)) % PI2
const newCenter = vec.rotWith(center, boundsCenter, rot % PI2) getShapeUtils(shape)
shape.point = vec.sub(newCenter, offset) .rotate(shape, (PI2 + (rotation + rot)) % PI2)
.translate(
shape,
vec.sub(vec.rotWith(center, boundsCenter, rot % PI2), offset)
)
} }
} }
@ -53,8 +58,7 @@ export default class RotateSession extends BaseSession {
for (let { id, point, rotation } of this.snapshot.shapes) { for (let { id, point, rotation } of this.snapshot.shapes) {
const shape = page.shapes[id] const shape = page.shapes[id]
shape.rotation = rotation getShapeUtils(shape).rotate(shape, rotation).translate(shape, point)
shape.point = point
} }
} }

View file

@ -5,6 +5,7 @@ import commands from "state/commands"
import { current } from "immer" import { current } from "immer"
import { v4 as uuid } from "uuid" import { v4 as uuid } from "uuid"
import { getChildIndexAbove, getPage, getSelectedShapes } from "utils/utils" import { getChildIndexAbove, getPage, getSelectedShapes } from "utils/utils"
import { getShapeUtils } from "lib/shape-utils"
export default class TranslateSession extends BaseSession { export default class TranslateSession extends BaseSession {
delta = [0, 0] delta = [0, 0]
@ -38,7 +39,8 @@ export default class TranslateSession extends BaseSession {
data.selectedIds.clear() data.selectedIds.clear()
for (const { id, point } of initialShapes) { for (const { id, point } of initialShapes) {
shapes[id].point = point const shape = shapes[id]
getShapeUtils(shape).translate(shape, point)
} }
for (const clone of clones) { for (const clone of clones) {
@ -48,7 +50,8 @@ export default class TranslateSession extends BaseSession {
} }
for (const { id, point } of clones) { for (const { id, point } of clones) {
shapes[id].point = vec.add(point, delta) const shape = shapes[id]
getShapeUtils(shape).translate(shape, vec.add(point, delta))
} }
} else { } else {
if (this.isCloning) { if (this.isCloning) {
@ -65,7 +68,8 @@ export default class TranslateSession extends BaseSession {
} }
for (const { id, point } of initialShapes) { for (const { id, point } of initialShapes) {
shapes[id].point = vec.add(point, delta) const shape = shapes[id]
getShapeUtils(shape).translate(shape, vec.add(point, delta))
} }
} }
} }
@ -75,7 +79,8 @@ export default class TranslateSession extends BaseSession {
const { shapes } = getPage(data, currentPageId) const { shapes } = getPage(data, currentPageId)
for (const { id, point } of initialShapes) { for (const { id, point } of initialShapes) {
shapes[id].point = point const shape = shapes[id]
getShapeUtils(shape).translate(shape, point)
} }
for (const { id } of clones) { for (const { id } of clones) {

View file

@ -525,12 +525,16 @@ const state = createState({
}, },
actions: { actions: {
/* --------------------- Shapes --------------------- */ /* --------------------- Shapes --------------------- */
createShape(data, payload: PointerInfo, shape: Shape) { createShape(data, payload, shape: Shape) {
const siblings = getChildren(data, shape.parentId) const siblings = getChildren(data, shape.parentId)
shape.childIndex = const childIndex = siblings.length
siblings.length > 0 ? siblings[siblings.length - 1].childIndex + 1 : 1 ? siblings[siblings.length - 1].childIndex + 1
: 1
getShapeUtils(shape).setChildIndex(shape, childIndex)
getPage(data).shapes[shape.id] = shape getPage(data).shapes[shape.id] = shape
data.selectedIds.clear() data.selectedIds.clear()
data.selectedIds.add(shape.id) data.selectedIds.add(shape.id)
}, },
@ -608,19 +612,11 @@ const state = createState({
data, data,
payload: PointerInfo & { target: Corner | Edge } payload: PointerInfo & { target: Corner | Edge }
) { ) {
const point = screenToWorld(inputs.pointer.origin, data)
session = session =
data.selectedIds.size === 1 data.selectedIds.size === 1
? new Sessions.TransformSingleSession( ? new Sessions.TransformSingleSession(data, payload.target, point)
data, : new Sessions.TransformSession(data, payload.target, point)
payload.target,
screenToWorld(payload.point, data),
false
)
: new Sessions.TransformSession(
data,
payload.target,
screenToWorld(payload.point, data)
)
}, },
startDrawTransformSession(data, payload: PointerInfo) { startDrawTransformSession(data, payload: PointerInfo) {
session = new Sessions.TransformSingleSession( session = new Sessions.TransformSingleSession(
@ -651,7 +647,7 @@ const state = createState({
startDirectionSession(data, payload: PointerInfo) { startDirectionSession(data, payload: PointerInfo) {
session = new Sessions.DirectionSession( session = new Sessions.DirectionSession(
data, data,
screenToWorld(payload.point, data) screenToWorld(inputs.pointer.origin, data)
) )
}, },
updateDirectionSession(data, payload: PointerInfo) { updateDirectionSession(data, payload: PointerInfo) {

View file

@ -106,7 +106,7 @@ export interface RectangleShape extends BaseShape {
size: number[] size: number[]
} }
export type Shape = export type Shape = Readonly<
| DotShape | DotShape
| CircleShape | CircleShape
| EllipseShape | EllipseShape
@ -114,8 +114,9 @@ export type Shape =
| RayShape | RayShape
| PolylineShape | PolylineShape
| RectangleShape | RectangleShape
>
export interface Shapes extends Record<ShapeType, Shape> { export interface Shapes {
[ShapeType.Dot]: DotShape [ShapeType.Dot]: DotShape
[ShapeType.Circle]: CircleShape [ShapeType.Circle]: CircleShape
[ShapeType.Ellipse]: EllipseShape [ShapeType.Ellipse]: EllipseShape
@ -125,6 +126,8 @@ export interface Shapes extends Record<ShapeType, Shape> {
[ShapeType.Rectangle]: RectangleShape [ShapeType.Rectangle]: RectangleShape
} }
export type ShapeByType<T extends ShapeType> = Shapes[T]
export interface CodeFile { export interface CodeFile {
id: string id: string
name: string name: string

View file

@ -1479,7 +1479,8 @@ export function getChildIndexBelow(
export function forceIntegerChildIndices(shapes: Shape[]) { export function forceIntegerChildIndices(shapes: Shape[]) {
for (let i = 0; i < shapes.length; i++) { for (let i = 0; i < shapes.length; i++) {
shapes[i].childIndex = i + 1 const shape = shapes[i]
getShapeUtils(shape).setChildIndex(shape, i + 1)
} }
} }
export function setZoomCSS(zoom: number) { export function setZoomCSS(zoom: number) {