Adds arrows, handles
This commit is contained in:
parent
facd9e9845
commit
bcffee6458
27 changed files with 1120 additions and 292 deletions
|
@ -1,12 +1,18 @@
|
|||
import * as React from 'react'
|
||||
import { Edge, Corner } from 'types'
|
||||
import { Edge, Corner, LineShape, ArrowShape } from 'types'
|
||||
import { useSelector } from 'state'
|
||||
import { getPage, getSelectedShapes, isMobile } from 'utils/utils'
|
||||
import {
|
||||
deepCompareArrays,
|
||||
getPage,
|
||||
getSelectedShapes,
|
||||
isMobile,
|
||||
} from 'utils/utils'
|
||||
|
||||
import CenterHandle from './center-handle'
|
||||
import CornerHandle from './corner-handle'
|
||||
import EdgeHandle from './edge-handle'
|
||||
import RotateHandle from './rotate-handle'
|
||||
import Handles from './handles'
|
||||
|
||||
export default function Bounds() {
|
||||
const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
|
||||
|
@ -14,20 +20,31 @@ export default function Bounds() {
|
|||
const zoom = useSelector((s) => s.data.camera.zoom)
|
||||
const bounds = useSelector((s) => s.values.selectedBounds)
|
||||
|
||||
const selectedIds = useSelector(
|
||||
(s) => Array.from(s.values.selectedIds.values()),
|
||||
deepCompareArrays
|
||||
)
|
||||
|
||||
const rotation = useSelector(({ data }) =>
|
||||
data.selectedIds.size === 1 ? getSelectedShapes(data)[0].rotation : 0
|
||||
)
|
||||
|
||||
const isAllLocked = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return Array.from(s.data.selectedIds.values()).every(
|
||||
(id) => page.shapes[id].isLocked
|
||||
)
|
||||
return selectedIds.every((id) => page.shapes[id]?.isLocked)
|
||||
})
|
||||
|
||||
const isAllHandles = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return selectedIds.every((id) => page.shapes[id]?.handles !== undefined)
|
||||
})
|
||||
|
||||
if (!bounds) return null
|
||||
|
||||
if (!isSelecting) return null
|
||||
|
||||
if (isAllHandles) return null
|
||||
|
||||
const size = (isMobile().any ? 10 : 8) / zoom // Touch target size
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useCallback, useRef } from 'react'
|
|||
import state, { useSelector } from 'state'
|
||||
import inputs from 'state/inputs'
|
||||
import styled from 'styles'
|
||||
import { getPage } from 'utils/utils'
|
||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||
|
||||
function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
|
||||
if (e.buttons !== 1) return
|
||||
|
@ -22,18 +22,34 @@ function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
|
|||
|
||||
export default function BoundsBg() {
|
||||
const rBounds = useRef<SVGRectElement>(null)
|
||||
|
||||
const bounds = useSelector((state) => state.values.selectedBounds)
|
||||
|
||||
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||
|
||||
const selectedIds = useSelector(
|
||||
(s) => Array.from(s.values.selectedIds.values()),
|
||||
deepCompareArrays
|
||||
)
|
||||
|
||||
const rotation = useSelector((s) => {
|
||||
if (s.data.selectedIds.size === 1) {
|
||||
if (selectedIds.length === 1) {
|
||||
const { shapes } = getPage(s.data)
|
||||
const selected = Array.from(s.data.selectedIds.values())[0]
|
||||
const selected = Array.from(s.values.selectedIds.values())[0]
|
||||
return shapes[selected].rotation
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
const isAllHandles = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return Array.from(s.values.selectedIds.values()).every(
|
||||
(id) => page.shapes[id]?.handles !== undefined
|
||||
)
|
||||
})
|
||||
|
||||
if (isAllHandles) return null
|
||||
if (!bounds) return null
|
||||
if (!isSelecting) return null
|
||||
|
||||
|
|
79
components/canvas/bounds/handles.tsx
Normal file
79
components/canvas/bounds/handles.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import useHandleEvents from 'hooks/useHandleEvents'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { useRef } from 'react'
|
||||
import { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||
import * as vec from 'utils/vec'
|
||||
|
||||
export default function Handles() {
|
||||
const selectedIds = useSelector(
|
||||
(s) => Array.from(s.values.selectedIds.values()),
|
||||
deepCompareArrays
|
||||
)
|
||||
|
||||
const shape = useSelector(
|
||||
({ data }) =>
|
||||
selectedIds.length === 1 && getPage(data).shapes[selectedIds[0]]
|
||||
)
|
||||
|
||||
const isTranslatingHandles = useSelector((s) => s.isIn('translatingHandles'))
|
||||
|
||||
if (!shape.handles || isTranslatingHandles) return null
|
||||
|
||||
return (
|
||||
<g>
|
||||
{Object.values(shape.handles).map((handle) => (
|
||||
<Handle
|
||||
key={handle.id}
|
||||
shapeId={shape.id}
|
||||
id={handle.id}
|
||||
point={vec.add(handle.point, shape.point)}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
function Handle({
|
||||
shapeId,
|
||||
id,
|
||||
point,
|
||||
}: {
|
||||
shapeId: string
|
||||
id: string
|
||||
point: number[]
|
||||
}) {
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
const events = useHandleEvents(id, rGroup)
|
||||
|
||||
const transform = `
|
||||
translate(${point})
|
||||
`
|
||||
|
||||
return (
|
||||
<g
|
||||
key={id}
|
||||
ref={rGroup}
|
||||
{...events}
|
||||
pointerEvents="all"
|
||||
transform={`translate(${point})`}
|
||||
>
|
||||
<HandleCircleOuter r={8} />
|
||||
<HandleCircle r={4} />
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
const HandleCircleOuter = styled('circle', {
|
||||
fill: 'transparent',
|
||||
pointerEvents: 'all',
|
||||
cursor: 'pointer',
|
||||
})
|
||||
|
||||
const HandleCircle = styled('circle', {
|
||||
zStrokeWidth: 2,
|
||||
stroke: '$text',
|
||||
fill: '$panel',
|
||||
pointerEvents: 'none',
|
||||
})
|
|
@ -10,6 +10,7 @@ import Brush from './brush'
|
|||
import Bounds from './bounds/bounding-box'
|
||||
import BoundsBg from './bounds/bounds-bg'
|
||||
import Selected from './selected'
|
||||
import Handles from './bounds/handles'
|
||||
|
||||
export default function Canvas() {
|
||||
const rCanvas = useRef<SVGSVGElement>(null)
|
||||
|
@ -60,6 +61,7 @@ export default function Canvas() {
|
|||
<Page />
|
||||
<Selected />
|
||||
<Bounds />
|
||||
<Handles />
|
||||
<Brush />
|
||||
</g>
|
||||
)}
|
||||
|
|
|
@ -26,7 +26,8 @@ function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
|
|||
const center = getShapeUtils(shape).getCenter(shape)
|
||||
const transform = `
|
||||
rotate(${shape.rotation * (180 / Math.PI)}, ${center})
|
||||
translate(${shape.point})`
|
||||
translate(${shape.point})
|
||||
`
|
||||
|
||||
return (
|
||||
<StyledGroup
|
||||
|
@ -54,22 +55,6 @@ const StyledShape = memo(
|
|||
}
|
||||
)
|
||||
|
||||
function Label({ text }: { text: string }) {
|
||||
return (
|
||||
<text
|
||||
y={4}
|
||||
x={4}
|
||||
fontSize={18}
|
||||
fill="black"
|
||||
stroke="none"
|
||||
alignmentBaseline="text-before-edge"
|
||||
pointerEvents="none"
|
||||
>
|
||||
{text}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
const HoverIndicator = styled('path', {
|
||||
fill: 'none',
|
||||
stroke: 'transparent',
|
||||
|
@ -133,6 +118,22 @@ const StyledGroup = styled('g', {
|
|||
],
|
||||
})
|
||||
|
||||
function Label({ text }: { text: string }) {
|
||||
return (
|
||||
<text
|
||||
y={4}
|
||||
x={4}
|
||||
fontSize={18}
|
||||
fill="black"
|
||||
stroke="none"
|
||||
alignmentBaseline="text-before-edge"
|
||||
pointerEvents="none"
|
||||
>
|
||||
{text}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
export { HoverIndicator }
|
||||
|
||||
export default memo(Shape)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue