Improves layout on mobile

This commit is contained in:
Steve Ruiz 2021-06-01 09:56:41 +01:00
parent 7b1dcdeeeb
commit ea996b627b
10 changed files with 87 additions and 74 deletions

View file

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

View file

@ -53,6 +53,7 @@ function Handle({
key={id}
ref={rGroup}
{...events}
cursor="pointer"
pointerEvents="all"
transform={`translate(${point})`}
>

View file

@ -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')
}
}
}, [])

View file

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

View file

@ -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%',

View file

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

View file

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

View file

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

View file

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

View file

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