diff --git a/components/canvas/bounds/bounds-bg.tsx b/components/canvas/bounds/bounds-bg.tsx index e82bf0735..1de16c675 100644 --- a/components/canvas/bounds/bounds-bg.tsx +++ b/components/canvas/bounds/bounds-bg.tsx @@ -44,8 +44,10 @@ export default function BoundsBg() { const isAllHandles = useSelector((s) => { const page = getPage(s.data) - return Array.from(s.values.selectedIds.values()).every( - (id) => page.shapes[id]?.handles !== undefined + const selectedIds = Array.from(s.values.selectedIds.values()) + return ( + selectedIds.length === 1 && + page.shapes[selectedIds[0]]?.handles !== undefined ) }) diff --git a/components/canvas/bounds/handles.tsx b/components/canvas/bounds/handles.tsx index 4f9330f15..f59b2e43f 100644 --- a/components/canvas/bounds/handles.tsx +++ b/components/canvas/bounds/handles.tsx @@ -53,6 +53,7 @@ function Handle({ key={id} ref={rGroup} {...events} + cursor="pointer" pointerEvents="all" transform={`translate(${point})`} > diff --git a/components/canvas/canvas.tsx b/components/canvas/canvas.tsx index 4f7941179..0a080fd32 100644 --- a/components/canvas/canvas.tsx +++ b/components/canvas/canvas.tsx @@ -11,6 +11,7 @@ import Bounds from './bounds/bounding-box' import BoundsBg from './bounds/bounds-bg' import Selected from './selected' import Handles from './bounds/handles' +import { isMobile } from 'utils/utils' export default function Canvas() { const rCanvas = useRef(null) @@ -30,6 +31,10 @@ export default function Canvas() { const handleTouchStart = useCallback((e: React.TouchEvent) => { if (e.touches.length === 2) { state.send('TOUCH_UNDO') + } else { + if (isMobile()) { + state.send('TOUCHED_CANVAS') + } } }, []) diff --git a/components/style-panel/align-distribute.tsx b/components/style-panel/align-distribute.tsx index eeb8ea27d..a11f7e8fb 100644 --- a/components/style-panel/align-distribute.tsx +++ b/components/style-panel/align-distribute.tsx @@ -102,11 +102,7 @@ const Container = styled('div', { display: 'grid', padding: 4, gridTemplateColumns: 'repeat(5, auto)', - [`& ${IconButton}`]: { - color: '$text', - }, [`& ${IconButton} > svg`]: { - fill: 'red', stroke: 'transparent', }, }) diff --git a/components/style-panel/color-picker.tsx b/components/style-panel/color-picker.tsx index 3f752ca5c..d64cec2c0 100644 --- a/components/style-panel/color-picker.tsx +++ b/components/style-panel/color-picker.tsx @@ -3,19 +3,15 @@ import { Square } from 'react-feather' import styled from 'styles' interface Props { - label: string - color: string colors: Record onChange: (color: string) => void + children: React.ReactNode } -export default function ColorPicker({ label, color, colors, onChange }: Props) { +export default function ColorPicker({ colors, onChange, children }: Props) { return ( - - - - + {children} {Object.entries(colors).map(([name, color]) => ( onChange(color)}> @@ -27,7 +23,7 @@ export default function ColorPicker({ label, color, colors, onChange }: Props) { ) } -function ColorIcon({ color }: { color: string }) { +export function ColorIcon({ color }: { color: string }) { return ( ) @@ -43,7 +39,7 @@ const Colors = styled(DropdownMenu.Content, { boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)', }) -const ColorButton = styled(DropdownMenu.Item, { +export const ColorButton = styled(DropdownMenu.Item, { position: 'relative', cursor: 'pointer', height: 32, @@ -79,7 +75,7 @@ const ColorButton = styled(DropdownMenu.Item, { }, }) -const CurrentColor = styled(DropdownMenu.Trigger, { +export const CurrentColor = styled(DropdownMenu.Trigger, { position: 'relative', display: 'flex', width: '100%', diff --git a/components/style-panel/style-panel.tsx b/components/style-panel/style-panel.tsx index 1d0b9d58f..fc2970125 100644 --- a/components/style-panel/style-panel.tsx +++ b/components/style-panel/style-panel.tsx @@ -3,7 +3,7 @@ import state, { useSelector } from 'state' import * as Panel from 'components/panel' import { useRef } from 'react' import { IconButton } from 'components/shared' -import { Circle, Copy, Lock, Trash, Unlock, X } from 'react-feather' +import { Circle, Copy, Lock, Trash, Trash2, Unlock, X } from 'react-feather' import { deepCompare, deepCompareArrays, @@ -12,7 +12,7 @@ import { } from 'utils/utils' import { shades, fills, strokes } from 'lib/colors' -import ColorPicker from './color-picker' +import ColorPicker, { ColorIcon, CurrentColor } from './color-picker' import AlignDistribute from './align-distribute' import { MoveType, ShapeStyles } from 'types' import WidthPicker from './width-picker' @@ -23,6 +23,7 @@ import { AspectRatioIcon, BoxIcon, CopyIcon, + DotsHorizontalIcon, EyeClosedIcon, EyeOpenIcon, LockClosedIcon, @@ -46,7 +47,7 @@ export default function StylePanel() { ) : ( state.send('TOGGLED_STYLE_PANEL_OPEN')}> - + )} @@ -118,17 +119,23 @@ function SelectedShapeStyles({}: {}) { state.send('CHANGED_STYLE', { fill: color })} - /> + > + + + + + state.send('CHANGED_STYLE', { stroke: color })} - /> + > + + + + + @@ -194,7 +201,7 @@ function SelectedShapeStyles({}: {}) { disabled={!hasSelection} onClick={() => state.send('DELETED')} > - + - + *:nth-child(n+2)': { - marginLeft: 16, + justifyContent: 'space-between', + width: '100%', + padding: '0 4px', + + variants: { + size: { + small: { + width: 'auto', + padding: '0', + '& > *:nth-child(n+2)': { + marginLeft: 16, + }, + }, + }, }, }) diff --git a/lib/shape-utils/arrow.tsx b/lib/shape-utils/arrow.tsx index 4ea0a4965..a99016810 100644 --- a/lib/shape-utils/arrow.tsx +++ b/lib/shape-utils/arrow.tsx @@ -74,16 +74,13 @@ const arrow = registerShapeUtils({ const arrowDist = vec.dist(start.point, end.point) const bendDist = arrowDist * bend - const showCircle = Math.abs(bendDist) > 20 - const v = vec.rot( - vec.mul( - vec.neg(vec.uni(vec.sub(points[1], points[0]))), - Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2) - ), - showCircle ? (bend * Math.PI) / 2 : 0 - ) + // Arrowhead + const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2) + const angle = showCircle ? bend * (Math.PI * 0.48) : 0 + const u = vec.uni(vec.vec(start.point, end.point)) + const v = vec.rot(vec.mul(vec.neg(u), length), angle) const b = vec.add(points[1], vec.rot(v, Math.PI / 6)) const c = vec.add(points[1], vec.rot(v, -(Math.PI / 6))) @@ -99,23 +96,13 @@ const arrow = registerShapeUtils({ return ( {circle ? ( - + <> + + ) : ( ({ transform(shape, bounds, { initialShape, scaleX, scaleY }) { const initialShapeBounds = this.getBounds(initialShape) - // console.log([...shape.point], [bounds.minX, bounds.minY]) - shape.point = [bounds.minX, bounds.minY] shape.points = shape.points.map((_, i) => { @@ -234,12 +219,6 @@ const arrow = registerShapeUtils({ start.point = shape.points[0] end.point = shape.points[1] - // start.point[0] = initialShape.handles.start.point[0] * scaleX - // end.point[0] = initialShape.handles.end.point[0] * scaleX - - // start.point[1] = initialShape.handles.start.point[1] * scaleY - // end.point[1] = initialShape.handles.end.point[1] * scaleY - const bendDist = (vec.dist(start.point, end.point) / 2) * shape.bend const midPoint = vec.med(start.point, end.point) const u = vec.uni(vec.vec(start.point, end.point)) @@ -286,7 +265,10 @@ const arrow = registerShapeUtils({ true ) shape.bend = clamp(distance / (dist / 2), -1, 1) - if (!vec.clockwise(start.point, bend.point, end.point)) shape.bend *= -1 + + const a0 = vec.angle(handle.point, end.point) + const a1 = vec.angle(start.point, end.point) + if (a0 - a1 < 0) shape.bend *= -1 } } @@ -295,7 +277,7 @@ const arrow = registerShapeUtils({ const bendDist = (dist / 2) * shape.bend const u = vec.uni(vec.vec(start.point, end.point)) - bend.point = + shape.handles.bend.point = Math.abs(bendDist) > 10 ? vec.add(midPoint, vec.mul(vec.per(u), bendDist)) : midPoint @@ -310,14 +292,22 @@ const arrow = registerShapeUtils({ export default arrow function getArrowArcPath( - cx: number, - cy: number, - r: number, - start: number[], - end: number[] + start: ShapeHandle, + end: ShapeHandle, + circle: number[], + bend: number ) { - return ` - A ${r},${r},0, - ${getSweep([cx, cy], start, end) > 0 ? '1' : '0'}, - 0,${end[0]},${end[1]}` + return [ + 'M', + start.point[0], + start.point[1], + 'A', + circle[2], + circle[2], + 0, + 0, + bend < 0 ? 0 : 1, + end.point[0], + end.point[1], + ].join(' ') } diff --git a/state/sessions/arrow-session.ts b/state/sessions/arrow-session.ts index 9ee7d2b1e..cbc9a72f9 100644 --- a/state/sessions/arrow-session.ts +++ b/state/sessions/arrow-session.ts @@ -70,7 +70,7 @@ export default class PointsSession extends BaseSession { } complete(data: Data) { - const { id, initialShape } = this.snapshot + const { id } = this.snapshot const shape = getPage(data).shapes[id] as ArrowShape diff --git a/state/state.ts b/state/state.ts index bc245c7bc..abc3ca534 100644 --- a/state/state.ts +++ b/state/state.ts @@ -128,6 +128,7 @@ const state = createState({ SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' }, TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel', TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel', + TOUCHED_CANVAS: 'closeStylePanel', CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'], SELECTED_ALL: { to: 'selecting', do: 'selectAll' }, NUDGED: { do: 'nudgeSelection' }, @@ -1176,6 +1177,9 @@ const state = createState({ toggleStylePanel(data) { data.settings.isStyleOpen = !data.settings.isStyleOpen }, + closeStylePanel(data) { + data.settings.isStyleOpen = false + }, updateStyles(data, payload: Partial) { Object.assign(data.currentStyle, payload) },