Greatly simplifies shapes
This commit is contained in:
parent
32082492d1
commit
3d52d9e9d2
18 changed files with 438 additions and 610 deletions
|
@ -1,35 +1,123 @@
|
||||||
|
import React, { useCallback, useRef } from "react"
|
||||||
|
import state, { useSelector } from "state"
|
||||||
|
import styled from "styles"
|
||||||
|
import { getPointerEventInfo } from "utils/utils"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { useSelector } from "state"
|
import Shapes from "lib/shapes"
|
||||||
import { ShapeType } from "types"
|
|
||||||
import Circle from "./shapes/circle"
|
|
||||||
import Dot from "./shapes/dot"
|
|
||||||
import Polyline from "./shapes/polyline"
|
|
||||||
import Rectangle from "./shapes/rectangle"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Gets the shape from the current page's shapes, using the
|
Gets the shape from the current page's shapes, using the
|
||||||
provided ID. Depending on the shape's type, return the
|
provided ID. Depending on the shape's type, return the
|
||||||
component for that type.
|
component for that type.
|
||||||
|
|
||||||
|
This component takes an SVG shape as its children. It handles
|
||||||
|
events for the shape as well as provides indicators for hover
|
||||||
|
and selected status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Shape({ id }: { id: string }) {
|
function Shape({ id }: { id: string }) {
|
||||||
|
const rGroup = useRef<SVGGElement>(null)
|
||||||
|
|
||||||
const shape = useSelector((state) => {
|
const shape = useSelector((state) => {
|
||||||
const { currentPageId, document } = state.data
|
const { currentPageId, document } = state.data
|
||||||
return document.pages[currentPageId].shapes[id]
|
return document.pages[currentPageId].shapes[id]
|
||||||
})
|
})
|
||||||
|
|
||||||
switch (shape.type) {
|
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
||||||
case ShapeType.Dot:
|
|
||||||
return <Dot {...shape} />
|
const handlePointerDown = useCallback(
|
||||||
case ShapeType.Circle:
|
(e: React.PointerEvent) => {
|
||||||
return <Circle {...shape} />
|
e.stopPropagation()
|
||||||
case ShapeType.Rectangle:
|
rGroup.current.setPointerCapture(e.pointerId)
|
||||||
return <Rectangle {...shape} />
|
state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
|
||||||
case ShapeType.Polyline:
|
},
|
||||||
return <Polyline {...shape} />
|
[id]
|
||||||
default:
|
)
|
||||||
return null
|
|
||||||
}
|
const handlePointerUp = useCallback(
|
||||||
|
(e: React.PointerEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
rGroup.current.releasePointerCapture(e.pointerId)
|
||||||
|
state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
|
||||||
|
},
|
||||||
|
[id]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handlePointerEnter = useCallback(
|
||||||
|
(e: React.PointerEvent) =>
|
||||||
|
state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
|
||||||
|
[id]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handlePointerLeave = useCallback(
|
||||||
|
(e: React.PointerEvent) =>
|
||||||
|
state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
|
||||||
|
[id]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledGroup
|
||||||
|
ref={rGroup}
|
||||||
|
isSelected={isSelected}
|
||||||
|
transform={`translate(${shape.point})`}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
onPointerUp={handlePointerUp}
|
||||||
|
onPointerEnter={handlePointerEnter}
|
||||||
|
onPointerLeave={handlePointerLeave}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
{Shapes[shape.type] ? Shapes[shape.type].render(shape) : null}
|
||||||
|
</defs>
|
||||||
|
<HoverIndicator as="use" xlinkHref={"#" + id} />
|
||||||
|
<use xlinkHref={"#" + id} {...shape.style} />
|
||||||
|
<Indicator as="use" xlinkHref={"#" + id} />
|
||||||
|
</StyledGroup>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Indicator = styled("path", {
|
||||||
|
fill: "none",
|
||||||
|
stroke: "transparent",
|
||||||
|
strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
|
||||||
|
pointerEvents: "none",
|
||||||
|
strokeLineCap: "round",
|
||||||
|
strokeLinejoin: "round",
|
||||||
|
})
|
||||||
|
|
||||||
|
const HoverIndicator = styled("path", {
|
||||||
|
fill: "none",
|
||||||
|
stroke: "transparent",
|
||||||
|
strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
|
||||||
|
pointerEvents: "all",
|
||||||
|
strokeLinecap: "round",
|
||||||
|
strokeLinejoin: "round",
|
||||||
|
})
|
||||||
|
|
||||||
|
const StyledGroup = styled("g", {
|
||||||
|
[`& ${HoverIndicator}`]: {
|
||||||
|
opacity: "0",
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
isSelected: {
|
||||||
|
true: {
|
||||||
|
[`& ${Indicator}`]: {
|
||||||
|
stroke: "$selected",
|
||||||
|
},
|
||||||
|
[`&:hover ${HoverIndicator}`]: {
|
||||||
|
opacity: "1",
|
||||||
|
stroke: "$hint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false: {
|
||||||
|
[`&:hover ${HoverIndicator}`]: {
|
||||||
|
opacity: "1",
|
||||||
|
stroke: "$hint",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export { Indicator, HoverIndicator }
|
||||||
|
|
||||||
export default memo(Shape)
|
export default memo(Shape)
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { CircleShape, ShapeProps } from "types"
|
|
||||||
import { Indicator, HoverIndicator } from "./indicator"
|
|
||||||
import ShapeGroup from "./shape-group"
|
|
||||||
|
|
||||||
function BaseCircle({
|
|
||||||
radius,
|
|
||||||
fill = "#999",
|
|
||||||
stroke = "none",
|
|
||||||
strokeWidth = 0,
|
|
||||||
}: ShapeProps<CircleShape>) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HoverIndicator as="circle" cx={radius} cy={radius} r={radius} />
|
|
||||||
<circle
|
|
||||||
cx={radius}
|
|
||||||
cy={radius}
|
|
||||||
r={radius}
|
|
||||||
fill={fill}
|
|
||||||
stroke={stroke}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
/>
|
|
||||||
<Indicator as="circle" cx={radius} cy={radius} r={radius} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Circle({ id, point, radius }: CircleShape) {
|
|
||||||
return (
|
|
||||||
<ShapeGroup id={id} point={point}>
|
|
||||||
<BaseCircle radius={radius} />
|
|
||||||
</ShapeGroup>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { Indicator, HoverIndicator } from "./indicator"
|
|
||||||
import { DotShape, ShapeProps } from "types"
|
|
||||||
import ShapeGroup from "./shape-group"
|
|
||||||
|
|
||||||
const dotRadius = 4
|
|
||||||
|
|
||||||
function BaseDot({
|
|
||||||
fill = "#999",
|
|
||||||
stroke = "none",
|
|
||||||
strokeWidth = 0,
|
|
||||||
}: ShapeProps<DotShape>) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HoverIndicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
|
|
||||||
<circle
|
|
||||||
cx={dotRadius}
|
|
||||||
cy={dotRadius}
|
|
||||||
r={dotRadius}
|
|
||||||
fill={fill}
|
|
||||||
stroke={stroke}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
/>
|
|
||||||
<Indicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Dot({ id, point }: DotShape) {
|
|
||||||
return (
|
|
||||||
<ShapeGroup id={id} point={point}>
|
|
||||||
<BaseDot />
|
|
||||||
</ShapeGroup>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import styled from "styles"
|
|
||||||
|
|
||||||
const Indicator = styled("path", {
|
|
||||||
fill: "none",
|
|
||||||
stroke: "transparent",
|
|
||||||
strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
|
|
||||||
pointerEvents: "none",
|
|
||||||
strokeLineCap: "round",
|
|
||||||
strokeLinejoin: "round",
|
|
||||||
})
|
|
||||||
|
|
||||||
const HoverIndicator = styled("path", {
|
|
||||||
fill: "none",
|
|
||||||
stroke: "transparent",
|
|
||||||
strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
|
|
||||||
pointerEvents: "all",
|
|
||||||
strokeLinecap: "round",
|
|
||||||
strokeLinejoin: "round",
|
|
||||||
})
|
|
||||||
|
|
||||||
export { Indicator, HoverIndicator }
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { PolylineShape, ShapeProps } from "types"
|
|
||||||
import { Indicator, HoverIndicator } from "./indicator"
|
|
||||||
import ShapeGroup from "./shape-group"
|
|
||||||
|
|
||||||
function BasePolyline({
|
|
||||||
points,
|
|
||||||
fill = "none",
|
|
||||||
stroke = "#999",
|
|
||||||
strokeWidth = 1,
|
|
||||||
}: ShapeProps<PolylineShape>) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HoverIndicator as="polyline" points={points.toString()} />
|
|
||||||
<polyline
|
|
||||||
points={points.toString()}
|
|
||||||
fill={fill}
|
|
||||||
stroke={stroke}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<Indicator as="polyline" points={points.toString()} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Polyline({ id, point, points }: PolylineShape) {
|
|
||||||
return (
|
|
||||||
<ShapeGroup id={id} point={point}>
|
|
||||||
<BasePolyline points={points} />
|
|
||||||
</ShapeGroup>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { RectangleShape, ShapeProps } from "types"
|
|
||||||
import { HoverIndicator, Indicator } from "./indicator"
|
|
||||||
import ShapeGroup from "./shape-group"
|
|
||||||
|
|
||||||
function BaseRectangle({
|
|
||||||
size,
|
|
||||||
fill = "#999",
|
|
||||||
stroke = "none",
|
|
||||||
strokeWidth = 0,
|
|
||||||
}: ShapeProps<RectangleShape>) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HoverIndicator as="rect" width={size[0]} height={size[1]} />
|
|
||||||
<rect
|
|
||||||
width={size[0]}
|
|
||||||
height={size[1]}
|
|
||||||
fill={fill}
|
|
||||||
stroke={stroke}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
/>
|
|
||||||
<Indicator as="rect" width={size[0]} height={size[1]} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Rectangle({ id, point, size }: RectangleShape) {
|
|
||||||
return (
|
|
||||||
<ShapeGroup id={id} point={point}>
|
|
||||||
<BaseRectangle size={size} />
|
|
||||||
</ShapeGroup>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
import state, { useSelector } from "state"
|
|
||||||
import React, { useCallback, useRef } from "react"
|
|
||||||
import { getPointerEventInfo } from "utils/utils"
|
|
||||||
import { Indicator, HoverIndicator } from "./indicator"
|
|
||||||
import styled from "styles"
|
|
||||||
|
|
||||||
export default function ShapeGroup({
|
|
||||||
id,
|
|
||||||
children,
|
|
||||||
point,
|
|
||||||
}: {
|
|
||||||
id: string
|
|
||||||
children: React.ReactNode
|
|
||||||
point: number[]
|
|
||||||
}) {
|
|
||||||
const rGroup = useRef<SVGGElement>(null)
|
|
||||||
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
|
||||||
|
|
||||||
const handlePointerDown = useCallback(
|
|
||||||
(e: React.PointerEvent) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
rGroup.current.setPointerCapture(e.pointerId)
|
|
||||||
state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handlePointerUp = useCallback(
|
|
||||||
(e: React.PointerEvent) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
rGroup.current.releasePointerCapture(e.pointerId)
|
|
||||||
state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handlePointerEnter = useCallback(
|
|
||||||
(e: React.PointerEvent) =>
|
|
||||||
state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
|
|
||||||
[id]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handlePointerLeave = useCallback(
|
|
||||||
(e: React.PointerEvent) =>
|
|
||||||
state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
|
|
||||||
[id]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledGroup
|
|
||||||
ref={rGroup}
|
|
||||||
isSelected={isSelected}
|
|
||||||
transform={`translate(${point})`}
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
onPointerUp={handlePointerUp}
|
|
||||||
onPointerEnter={handlePointerEnter}
|
|
||||||
onPointerLeave={handlePointerLeave}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</StyledGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const StyledGroup = styled("g", {
|
|
||||||
[`& ${HoverIndicator}`]: {
|
|
||||||
opacity: "0",
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
isSelected: {
|
|
||||||
true: {
|
|
||||||
[`& ${Indicator}`]: {
|
|
||||||
stroke: "$selected",
|
|
||||||
},
|
|
||||||
[`&:hover ${HoverIndicator}`]: {
|
|
||||||
opacity: "1",
|
|
||||||
stroke: "$hint",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false: {
|
|
||||||
[`&:hover ${HoverIndicator}`]: {
|
|
||||||
opacity: "1",
|
|
||||||
stroke: "$hint",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -12,7 +12,7 @@ export default function StatusBar() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusBarContainer>
|
<StatusBarContainer>
|
||||||
<States>{active.join(" | ")}</States>
|
<Section>{active.join(" | ")}</Section>
|
||||||
<Section>| {log}</Section>
|
<Section>| {log}</Section>
|
||||||
<Section title="Renders | Time">
|
<Section title="Renders | Time">
|
||||||
{count} | {time.toString().padStart(3, "0")}
|
{count} | {time.toString().padStart(3, "0")}
|
||||||
|
@ -45,8 +45,6 @@ const Section = styled("div", {
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
})
|
})
|
||||||
|
|
||||||
const States = styled("div", {})
|
|
||||||
|
|
||||||
function useRenderCount() {
|
function useRenderCount() {
|
||||||
const rTime = useRef(Date.now())
|
const rTime = useRef(Date.now())
|
||||||
const rCounter = useRef(0)
|
const rCounter = useRef(0)
|
||||||
|
|
64
lib/shapes/circle.tsx
Normal file
64
lib/shapes/circle.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
import * as vec from "utils/vec"
|
||||||
|
import { BaseLibShape, CircleShape, ShapeType } from "types"
|
||||||
|
|
||||||
|
const Circle: BaseLibShape<ShapeType.Circle> = {
|
||||||
|
create(props): CircleShape {
|
||||||
|
return {
|
||||||
|
id: uuid(),
|
||||||
|
type: ShapeType.Circle,
|
||||||
|
name: "Circle",
|
||||||
|
parentId: "page0",
|
||||||
|
childIndex: 0,
|
||||||
|
point: [0, 0],
|
||||||
|
radius: 20,
|
||||||
|
rotation: 0,
|
||||||
|
style: {},
|
||||||
|
...props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ id, radius }) {
|
||||||
|
return <circle id={id} cx={radius} cy={radius} r={radius} />
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds(shape) {
|
||||||
|
const {
|
||||||
|
point: [cx, cy],
|
||||||
|
radius,
|
||||||
|
} = shape
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX: cx,
|
||||||
|
maxX: cx + radius * 2,
|
||||||
|
minY: cy,
|
||||||
|
maxY: cy + radius * 2,
|
||||||
|
width: radius * 2,
|
||||||
|
height: radius * 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hitTest(shape, test) {
|
||||||
|
return (
|
||||||
|
vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
translate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(shape, scale: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
stretch(shape, scaleX: number, scaleY: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Circle
|
60
lib/shapes/dot.tsx
Normal file
60
lib/shapes/dot.tsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
import * as vec from "utils/vec"
|
||||||
|
import { BaseLibShape, DotShape, ShapeType } from "types"
|
||||||
|
|
||||||
|
const Dot: BaseLibShape<ShapeType.Dot> = {
|
||||||
|
create(props): DotShape {
|
||||||
|
return {
|
||||||
|
id: uuid(),
|
||||||
|
type: ShapeType.Dot,
|
||||||
|
name: "Dot",
|
||||||
|
parentId: "page0",
|
||||||
|
childIndex: 0,
|
||||||
|
point: [0, 0],
|
||||||
|
rotation: 0,
|
||||||
|
style: {},
|
||||||
|
...props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ id }) {
|
||||||
|
return <circle id={id} cx={4} cy={4} r={4} />
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds(shape) {
|
||||||
|
const {
|
||||||
|
point: [cx, cy],
|
||||||
|
} = shape
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX: cx,
|
||||||
|
maxX: cx + 4,
|
||||||
|
minY: cy,
|
||||||
|
maxY: cy + 4,
|
||||||
|
width: 4,
|
||||||
|
height: 4,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hitTest(shape, test) {
|
||||||
|
return vec.dist(shape.point, test) < 4
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
translate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(shape, scale: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
stretch(shape, scaleX: number, scaleY: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dot
|
13
lib/shapes/index.tsx
Normal file
13
lib/shapes/index.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import Circle from "./circle"
|
||||||
|
import Dot from "./dot"
|
||||||
|
import Polyline from "./polyline"
|
||||||
|
import Rectangle from "./rectangle"
|
||||||
|
|
||||||
|
import { ShapeType } from "types"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[ShapeType.Circle]: Circle,
|
||||||
|
[ShapeType.Dot]: Dot,
|
||||||
|
[ShapeType.Polyline]: Polyline,
|
||||||
|
[ShapeType.Rectangle]: Rectangle,
|
||||||
|
}
|
69
lib/shapes/polyline.tsx
Normal file
69
lib/shapes/polyline.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
import * as vec from "utils/vec"
|
||||||
|
import { BaseLibShape, PolylineShape, ShapeType } from "types"
|
||||||
|
|
||||||
|
const Polyline: BaseLibShape<ShapeType.Polyline> = {
|
||||||
|
create(props): PolylineShape {
|
||||||
|
return {
|
||||||
|
id: uuid(),
|
||||||
|
type: ShapeType.Polyline,
|
||||||
|
name: "Polyline",
|
||||||
|
parentId: "page0",
|
||||||
|
childIndex: 0,
|
||||||
|
point: [0, 0],
|
||||||
|
points: [[0, 0]],
|
||||||
|
rotation: 0,
|
||||||
|
style: {},
|
||||||
|
...props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ id, points }) {
|
||||||
|
return <polyline id={id} points={points.toString()} />
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds(shape) {
|
||||||
|
let minX = 0
|
||||||
|
let minY = 0
|
||||||
|
let maxX = 0
|
||||||
|
let maxY = 0
|
||||||
|
|
||||||
|
for (let [x, y] of shape.points) {
|
||||||
|
minX = Math.min(x, minX)
|
||||||
|
minY = Math.min(y, minY)
|
||||||
|
maxX = Math.max(x, maxX)
|
||||||
|
maxY = Math.max(y, maxY)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX: minX + shape.point[0],
|
||||||
|
minY: minY + shape.point[1],
|
||||||
|
maxX: maxX + shape.point[0],
|
||||||
|
maxY: maxY + shape.point[1],
|
||||||
|
width: maxX - minX,
|
||||||
|
height: maxY - minY,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hitTest(shape) {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
translate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(shape, scale: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
stretch(shape, scaleX: number, scaleY: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Polyline
|
62
lib/shapes/rectangle.tsx
Normal file
62
lib/shapes/rectangle.tsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
import * as vec from "utils/vec"
|
||||||
|
import { BaseLibShape, RectangleShape, ShapeType } from "types"
|
||||||
|
|
||||||
|
const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
|
||||||
|
create(props): RectangleShape {
|
||||||
|
return {
|
||||||
|
id: uuid(),
|
||||||
|
type: ShapeType.Rectangle,
|
||||||
|
name: "Rectangle",
|
||||||
|
parentId: "page0",
|
||||||
|
childIndex: 0,
|
||||||
|
point: [0, 0],
|
||||||
|
size: [1, 1],
|
||||||
|
rotation: 0,
|
||||||
|
style: {},
|
||||||
|
...props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ id, size }) {
|
||||||
|
return <rect id={id} width={size[0]} height={size[1]} />
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds(shape) {
|
||||||
|
const {
|
||||||
|
point: [x, y],
|
||||||
|
size: [width, height],
|
||||||
|
} = shape
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX: x,
|
||||||
|
maxX: x + width,
|
||||||
|
minY: y,
|
||||||
|
maxY: y + height,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hitTest(shape) {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
translate(shape) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(shape, scale: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
|
||||||
|
stretch(shape, scaleX: number, scaleY: number) {
|
||||||
|
return shape
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Rectangle
|
|
@ -1,4 +1,5 @@
|
||||||
import { Data, ShapeType } from "types"
|
import { Data, ShapeType } from "types"
|
||||||
|
import Shapes from "lib/shapes"
|
||||||
|
|
||||||
export const defaultDocument: Data["document"] = {
|
export const defaultDocument: Data["document"] = {
|
||||||
pages: {
|
pages: {
|
||||||
|
@ -8,31 +9,32 @@ export const defaultDocument: Data["document"] = {
|
||||||
name: "Page 0",
|
name: "Page 0",
|
||||||
childIndex: 0,
|
childIndex: 0,
|
||||||
shapes: {
|
shapes: {
|
||||||
shape0: {
|
shape3: Shapes[ShapeType.Dot].create({
|
||||||
|
id: "shape3",
|
||||||
|
name: "Shape 3",
|
||||||
|
childIndex: 3,
|
||||||
|
point: [500, 100],
|
||||||
|
style: {
|
||||||
|
fill: "#aaa",
|
||||||
|
stroke: "#777",
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
shape0: Shapes[ShapeType.Circle].create({
|
||||||
id: "shape0",
|
id: "shape0",
|
||||||
type: ShapeType.Circle,
|
|
||||||
name: "Shape 0",
|
name: "Shape 0",
|
||||||
parentId: "page0",
|
|
||||||
childIndex: 1,
|
childIndex: 1,
|
||||||
point: [100, 100],
|
point: [100, 100],
|
||||||
radius: 50,
|
radius: 50,
|
||||||
rotation: 0,
|
style: {
|
||||||
},
|
fill: "#aaa",
|
||||||
shape1: {
|
stroke: "#777",
|
||||||
id: "shape1",
|
strokeWidth: 1,
|
||||||
type: ShapeType.Rectangle,
|
},
|
||||||
name: "Shape 1",
|
}),
|
||||||
parentId: "page0",
|
shape2: Shapes[ShapeType.Polyline].create({
|
||||||
childIndex: 1,
|
|
||||||
point: [300, 300],
|
|
||||||
size: [200, 200],
|
|
||||||
rotation: 0,
|
|
||||||
},
|
|
||||||
shape2: {
|
|
||||||
id: "shape2",
|
id: "shape2",
|
||||||
type: ShapeType.Polyline,
|
|
||||||
name: "Shape 2",
|
name: "Shape 2",
|
||||||
parentId: "page0",
|
|
||||||
childIndex: 2,
|
childIndex: 2,
|
||||||
point: [200, 600],
|
point: [200, 600],
|
||||||
points: [
|
points: [
|
||||||
|
@ -40,17 +42,24 @@ export const defaultDocument: Data["document"] = {
|
||||||
[75, 200],
|
[75, 200],
|
||||||
[100, 50],
|
[100, 50],
|
||||||
],
|
],
|
||||||
rotation: 0,
|
style: {
|
||||||
},
|
fill: "none",
|
||||||
shape3: {
|
stroke: "#777",
|
||||||
id: "shape3",
|
strokeWidth: 2,
|
||||||
type: ShapeType.Dot,
|
},
|
||||||
name: "Shape 3",
|
}),
|
||||||
parentId: "page0",
|
shape1: Shapes[ShapeType.Rectangle].create({
|
||||||
childIndex: 3,
|
id: "shape1",
|
||||||
point: [500, 100],
|
name: "Shape 1",
|
||||||
rotation: 0,
|
childIndex: 1,
|
||||||
},
|
point: [300, 300],
|
||||||
|
size: [200, 200],
|
||||||
|
style: {
|
||||||
|
fill: "#aaa",
|
||||||
|
stroke: "#777",
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { current } from "immer"
|
import { current } from "immer"
|
||||||
import { Bounds, Data, Shape, ShapeType } from "types"
|
import { Bounds, Data, ShapeType } from "types"
|
||||||
import BaseSession from "./base-session"
|
import BaseSession from "./base-session"
|
||||||
import shapeUtils from "utils/shape-utils"
|
import Shapes from "lib/shapes"
|
||||||
import { getBoundsFromPoints } from "utils/utils"
|
import { getBoundsFromPoints } from "utils/utils"
|
||||||
import * as vec from "utils/vec"
|
import * as vec from "utils/vec"
|
||||||
import {
|
import {
|
||||||
|
@ -72,7 +72,7 @@ export default class BrushSession extends BaseSession {
|
||||||
.map((shape) => {
|
.map((shape) => {
|
||||||
switch (shape.type) {
|
switch (shape.type) {
|
||||||
case ShapeType.Dot: {
|
case ShapeType.Dot: {
|
||||||
const bounds = shapeUtils[shape.type].getBounds(shape)
|
const bounds = Shapes[shape.type].getBounds(shape)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: shape.id,
|
id: shape.id,
|
||||||
|
@ -82,7 +82,7 @@ export default class BrushSession extends BaseSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ShapeType.Circle: {
|
case ShapeType.Circle: {
|
||||||
const bounds = shapeUtils[shape.type].getBounds(shape)
|
const bounds = Shapes[shape.type].getBounds(shape)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: shape.id,
|
id: shape.id,
|
||||||
|
@ -96,7 +96,7 @@ export default class BrushSession extends BaseSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ShapeType.Rectangle: {
|
case ShapeType.Rectangle: {
|
||||||
const bounds = shapeUtils[shape.type].getBounds(shape)
|
const bounds = Shapes[shape.type].getBounds(shape)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: shape.id,
|
id: shape.id,
|
||||||
|
@ -106,7 +106,7 @@ export default class BrushSession extends BaseSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ShapeType.Polyline: {
|
case ShapeType.Polyline: {
|
||||||
const bounds = shapeUtils[shape.type].getBounds(shape)
|
const bounds = Shapes[shape.type].getBounds(shape)
|
||||||
const points = shape.points.map((point) =>
|
const points = shape.points.map((point) =>
|
||||||
vec.add(point, shape.point)
|
vec.add(point, shape.point)
|
||||||
)
|
)
|
||||||
|
|
25
types.ts
25
types.ts
|
@ -1,3 +1,5 @@
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
camera: {
|
camera: {
|
||||||
point: number[]
|
point: number[]
|
||||||
|
@ -26,7 +28,7 @@ export enum ShapeType {
|
||||||
Ellipse = "ellipse",
|
Ellipse = "ellipse",
|
||||||
Line = "line",
|
Line = "line",
|
||||||
Ray = "ray",
|
Ray = "ray",
|
||||||
Polyline = "Polyline",
|
Polyline = "polyline",
|
||||||
Rectangle = "rectangle",
|
Rectangle = "rectangle",
|
||||||
// Glob = "glob",
|
// Glob = "glob",
|
||||||
// Spline = "spline",
|
// Spline = "spline",
|
||||||
|
@ -42,6 +44,7 @@ export interface BaseShape {
|
||||||
name: string
|
name: string
|
||||||
point: number[]
|
point: number[]
|
||||||
rotation: 0
|
rotation: 0
|
||||||
|
style: Partial<React.SVGProps<SVGUseElement>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DotShape extends BaseShape {
|
export interface DotShape extends BaseShape {
|
||||||
|
@ -107,12 +110,6 @@ export interface Shapes extends Record<ShapeType, Shape> {
|
||||||
[ShapeType.Rectangle]: RectangleShape
|
[ShapeType.Rectangle]: RectangleShape
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseShapeStyles {
|
|
||||||
fill: string
|
|
||||||
stroke: string
|
|
||||||
strokeWidth: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Difference<A, B> = A extends B ? never : A
|
export type Difference<A, B> = A extends B ? never : A
|
||||||
|
|
||||||
export type ShapeSpecificProps<T extends Shape> = Pick<
|
export type ShapeSpecificProps<T extends Shape> = Pick<
|
||||||
|
@ -120,7 +117,15 @@ export type ShapeSpecificProps<T extends Shape> = Pick<
|
||||||
Difference<keyof T, keyof BaseShape>
|
Difference<keyof T, keyof BaseShape>
|
||||||
>
|
>
|
||||||
|
|
||||||
export type ShapeProps<T extends Shape> = Partial<BaseShapeStyles> &
|
|
||||||
ShapeSpecificProps<T> & { id?: Shape["id"] }
|
|
||||||
|
|
||||||
export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
|
export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
|
||||||
|
|
||||||
|
export type BaseLibShape<K extends ShapeType> = {
|
||||||
|
create(props: Partial<Shapes[K]>): Shapes[K]
|
||||||
|
getBounds(shape: Shapes[K]): Bounds
|
||||||
|
hitTest(shape: Shapes[K], test: number[]): boolean
|
||||||
|
rotate(shape: Shapes[K]): Shapes[K]
|
||||||
|
translate(shape: Shapes[K]): Shapes[K]
|
||||||
|
scale(shape: Shapes[K], scale: number): Shapes[K]
|
||||||
|
stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
|
||||||
|
render(shape: Shapes[K]): JSX.Element
|
||||||
|
}
|
||||||
|
|
|
@ -1,302 +0,0 @@
|
||||||
import {
|
|
||||||
boundsCollide,
|
|
||||||
boundsContain,
|
|
||||||
pointInBounds,
|
|
||||||
} from "state/sessions/brush-session"
|
|
||||||
import { Bounds, ShapeType, Shapes } from "types"
|
|
||||||
import { intersectCircleBounds } from "./intersections"
|
|
||||||
import * as vec from "./vec"
|
|
||||||
|
|
||||||
type BaseShapeUtils<K extends ShapeType> = {
|
|
||||||
getBounds(shape: Shapes[K]): Bounds
|
|
||||||
hitTest(shape: Shapes[K], test: number[]): boolean
|
|
||||||
rotate(shape: Shapes[K]): Shapes[K]
|
|
||||||
translate(shape: Shapes[K]): Shapes[K]
|
|
||||||
scale(shape: Shapes[K], scale: number): Shapes[K]
|
|
||||||
stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------- Dot ---------------------- */
|
|
||||||
|
|
||||||
const DotUtils: BaseShapeUtils<ShapeType.Dot> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
const {
|
|
||||||
point: [cx, cy],
|
|
||||||
} = shape
|
|
||||||
|
|
||||||
return {
|
|
||||||
minX: cx,
|
|
||||||
maxX: cx + 4,
|
|
||||||
minY: cy,
|
|
||||||
maxY: cy + 4,
|
|
||||||
width: 4,
|
|
||||||
height: 4,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape, test) {
|
|
||||||
return vec.dist(shape.point, test) < 4
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------- Circle --------------------- */
|
|
||||||
|
|
||||||
const CircleUtils: BaseShapeUtils<ShapeType.Circle> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
const {
|
|
||||||
point: [cx, cy],
|
|
||||||
radius,
|
|
||||||
} = shape
|
|
||||||
|
|
||||||
return {
|
|
||||||
minX: cx,
|
|
||||||
maxX: cx + radius * 2,
|
|
||||||
minY: cy,
|
|
||||||
maxY: cy + radius * 2,
|
|
||||||
width: radius * 2,
|
|
||||||
height: radius * 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape, test) {
|
|
||||||
return (
|
|
||||||
vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------- Ellipse -------------------- */
|
|
||||||
|
|
||||||
const EllipseUtils: BaseShapeUtils<ShapeType.Ellipse> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
return {
|
|
||||||
minX: 0,
|
|
||||||
minY: 0,
|
|
||||||
maxX: 0,
|
|
||||||
maxY: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape) {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------- Line ---------------------- */
|
|
||||||
|
|
||||||
const LineUtils: BaseShapeUtils<ShapeType.Line> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
return {
|
|
||||||
minX: 0,
|
|
||||||
minY: 0,
|
|
||||||
maxX: 0,
|
|
||||||
maxY: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape) {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------- Ray ---------------------- */
|
|
||||||
|
|
||||||
const RayUtils: BaseShapeUtils<ShapeType.Ray> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
return {
|
|
||||||
minX: Infinity,
|
|
||||||
minY: Infinity,
|
|
||||||
maxX: Infinity,
|
|
||||||
maxY: Infinity,
|
|
||||||
width: Infinity,
|
|
||||||
height: Infinity,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape) {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------ Line Segment ------------------ */
|
|
||||||
|
|
||||||
const PolylineUtils: BaseShapeUtils<ShapeType.Polyline> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
let minX = 0
|
|
||||||
let minY = 0
|
|
||||||
let maxX = 0
|
|
||||||
let maxY = 0
|
|
||||||
|
|
||||||
for (let [x, y] of shape.points) {
|
|
||||||
minX = Math.min(x, minX)
|
|
||||||
minY = Math.min(y, minY)
|
|
||||||
maxX = Math.max(x, maxX)
|
|
||||||
maxY = Math.max(y, maxY)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
minX: minX + shape.point[0],
|
|
||||||
minY: minY + shape.point[1],
|
|
||||||
maxX: maxX + shape.point[0],
|
|
||||||
maxY: maxY + shape.point[1],
|
|
||||||
width: maxX - minX,
|
|
||||||
height: maxY - minY,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape) {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------- Rectangle ------------------- */
|
|
||||||
|
|
||||||
const RectangleUtils: BaseShapeUtils<ShapeType.Rectangle> = {
|
|
||||||
getBounds(shape) {
|
|
||||||
const {
|
|
||||||
point: [x, y],
|
|
||||||
size: [width, height],
|
|
||||||
} = shape
|
|
||||||
|
|
||||||
return {
|
|
||||||
minX: x,
|
|
||||||
maxX: x + width,
|
|
||||||
minY: y,
|
|
||||||
maxY: y + height,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hitTest(shape) {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
rotate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
translate(shape) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
scale(shape, scale: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
|
|
||||||
stretch(shape, scaleX: number, scaleY: number) {
|
|
||||||
return shape
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const shapeUtils: { [K in ShapeType]: BaseShapeUtils<K> } = {
|
|
||||||
[ShapeType.Dot]: DotUtils,
|
|
||||||
[ShapeType.Circle]: CircleUtils,
|
|
||||||
[ShapeType.Ellipse]: EllipseUtils,
|
|
||||||
[ShapeType.Line]: LineUtils,
|
|
||||||
[ShapeType.Ray]: RayUtils,
|
|
||||||
[ShapeType.Polyline]: PolylineUtils,
|
|
||||||
[ShapeType.Rectangle]: RectangleUtils,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default shapeUtils
|
|
|
@ -844,12 +844,14 @@ export async function postJsonToEndpoint(
|
||||||
return await d.json()
|
return await d.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPointerEventInfo(e: React.PointerEvent | WheelEvent) {
|
export function getPointerEventInfo(
|
||||||
|
e: PointerEvent | React.PointerEvent | WheelEvent
|
||||||
|
) {
|
||||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||||
return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
|
return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getKeyboardEventInfo(e: React.KeyboardEvent | KeyboardEvent) {
|
export function getKeyboardEventInfo(e: KeyboardEvent | React.KeyboardEvent) {
|
||||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||||
return { key: e.key, shiftKey, ctrlKey, metaKey, altKey }
|
return { key: e.key, shiftKey, ctrlKey, metaKey, altKey }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue