Improves layout on mobile
This commit is contained in:
parent
7b1dcdeeeb
commit
ea996b627b
10 changed files with 87 additions and 74 deletions
|
@ -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
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ function Handle({
|
|||
key={id}
|
||||
ref={rGroup}
|
||||
{...events}
|
||||
cursor="pointer"
|
||||
pointerEvents="all"
|
||||
transform={`translate(${point})`}
|
||||
>
|
||||
|
|
|
@ -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<SVGSVGElement>(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')
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -3,19 +3,15 @@ import { Square } from 'react-feather'
|
|||
import styled from 'styles'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
color: string
|
||||
colors: Record<string, string>
|
||||
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 (
|
||||
<DropdownMenu.Root>
|
||||
<CurrentColor>
|
||||
<label>{label}</label>
|
||||
<ColorIcon color={color} />
|
||||
</CurrentColor>
|
||||
{children}
|
||||
<Colors sideOffset={4}>
|
||||
{Object.entries(colors).map(([name, color]) => (
|
||||
<ColorButton key={name} title={name} onSelect={() => 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 (
|
||||
<Square fill={color} strokeDasharray={color === 'none' ? '2, 3' : 'none'} />
|
||||
)
|
||||
|
@ -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%',
|
||||
|
|
|
@ -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() {
|
|||
<SelectedShapeStyles />
|
||||
) : (
|
||||
<IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
|
||||
<Circle />
|
||||
<DotsHorizontalIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</StylePanelRoot>
|
||||
|
@ -118,17 +119,23 @@ function SelectedShapeStyles({}: {}) {
|
|||
</Panel.Header>
|
||||
<Content>
|
||||
<ColorPicker
|
||||
label="Fill"
|
||||
color={commonStyle.fill}
|
||||
colors={fillColors}
|
||||
onChange={(color) => state.send('CHANGED_STYLE', { fill: color })}
|
||||
/>
|
||||
>
|
||||
<CurrentColor>
|
||||
<label>Fill</label>
|
||||
<ColorIcon color={commonStyle.fill} />
|
||||
</CurrentColor>
|
||||
</ColorPicker>
|
||||
<ColorPicker
|
||||
label="Stroke"
|
||||
color={commonStyle.stroke}
|
||||
colors={strokeColors}
|
||||
onChange={(color) => state.send('CHANGED_STYLE', { stroke: color })}
|
||||
/>
|
||||
>
|
||||
<CurrentColor>
|
||||
<label>Stroke</label>
|
||||
<ColorIcon color={commonStyle.stroke} />
|
||||
</CurrentColor>
|
||||
</ColorPicker>
|
||||
<Row>
|
||||
<label htmlFor="width">Width</label>
|
||||
<WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
|
||||
|
@ -194,7 +201,7 @@ function SelectedShapeStyles({}: {}) {
|
|||
disabled={!hasSelection}
|
||||
onClick={() => state.send('DELETED')}
|
||||
>
|
||||
<TrashIcon />
|
||||
<Trash2 />
|
||||
</IconButton>
|
||||
</ButtonsRow>
|
||||
<AlignDistribute
|
||||
|
|
|
@ -54,7 +54,7 @@ export default function ToolsPanel() {
|
|||
return (
|
||||
<OuterContainer>
|
||||
<Zoom />
|
||||
<Flex>
|
||||
<Flex size={{ '@sm': 'small' }}>
|
||||
<Container>
|
||||
<IconButton
|
||||
name="select"
|
||||
|
@ -170,8 +170,20 @@ const OuterContainer = styled('div', {
|
|||
|
||||
const Flex = styled('div', {
|
||||
display: 'flex',
|
||||
'& > *: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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -74,16 +74,13 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
|
||||
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<ArrowShape>({
|
|||
return (
|
||||
<g id={id}>
|
||||
{circle ? (
|
||||
<path
|
||||
d={[
|
||||
'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(' ')}
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<>
|
||||
<path
|
||||
d={getArrowArcPath(start, end, circle, bend)}
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<polyline
|
||||
points={[start.point, end.point].join(' ')}
|
||||
|
@ -208,8 +195,6 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
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<ArrowShape>({
|
|||
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<ArrowShape>({
|
|||
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<ArrowShape>({
|
|||
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<ArrowShape>({
|
|||
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(' ')
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<ShapeStyles>) {
|
||||
Object.assign(data.currentStyle, payload)
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue