Adds arrows, handles

This commit is contained in:
Steve Ruiz 2021-05-31 20:13:43 +01:00
parent facd9e9845
commit bcffee6458
27 changed files with 1120 additions and 292 deletions

View file

@ -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 (

View file

@ -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

View 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',
})

View file

@ -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>
)}

View file

@ -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)