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 isAllHandles = useSelector((s) => {
|
||||||
const page = getPage(s.data)
|
const page = getPage(s.data)
|
||||||
return Array.from(s.values.selectedIds.values()).every(
|
const selectedIds = Array.from(s.values.selectedIds.values())
|
||||||
(id) => page.shapes[id]?.handles !== undefined
|
return (
|
||||||
|
selectedIds.length === 1 &&
|
||||||
|
page.shapes[selectedIds[0]]?.handles !== undefined
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ function Handle({
|
||||||
key={id}
|
key={id}
|
||||||
ref={rGroup}
|
ref={rGroup}
|
||||||
{...events}
|
{...events}
|
||||||
|
cursor="pointer"
|
||||||
pointerEvents="all"
|
pointerEvents="all"
|
||||||
transform={`translate(${point})`}
|
transform={`translate(${point})`}
|
||||||
>
|
>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Bounds from './bounds/bounding-box'
|
||||||
import BoundsBg from './bounds/bounds-bg'
|
import BoundsBg from './bounds/bounds-bg'
|
||||||
import Selected from './selected'
|
import Selected from './selected'
|
||||||
import Handles from './bounds/handles'
|
import Handles from './bounds/handles'
|
||||||
|
import { isMobile } from 'utils/utils'
|
||||||
|
|
||||||
export default function Canvas() {
|
export default function Canvas() {
|
||||||
const rCanvas = useRef<SVGSVGElement>(null)
|
const rCanvas = useRef<SVGSVGElement>(null)
|
||||||
|
@ -30,6 +31,10 @@ export default function Canvas() {
|
||||||
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
||||||
if (e.touches.length === 2) {
|
if (e.touches.length === 2) {
|
||||||
state.send('TOUCH_UNDO')
|
state.send('TOUCH_UNDO')
|
||||||
|
} else {
|
||||||
|
if (isMobile()) {
|
||||||
|
state.send('TOUCHED_CANVAS')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -102,11 +102,7 @@ const Container = styled('div', {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
padding: 4,
|
padding: 4,
|
||||||
gridTemplateColumns: 'repeat(5, auto)',
|
gridTemplateColumns: 'repeat(5, auto)',
|
||||||
[`& ${IconButton}`]: {
|
|
||||||
color: '$text',
|
|
||||||
},
|
|
||||||
[`& ${IconButton} > svg`]: {
|
[`& ${IconButton} > svg`]: {
|
||||||
fill: 'red',
|
|
||||||
stroke: 'transparent',
|
stroke: 'transparent',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,19 +3,15 @@ import { Square } from 'react-feather'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
|
||||||
color: string
|
|
||||||
colors: Record<string, string>
|
colors: Record<string, string>
|
||||||
onChange: (color: string) => void
|
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 (
|
return (
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<CurrentColor>
|
{children}
|
||||||
<label>{label}</label>
|
|
||||||
<ColorIcon color={color} />
|
|
||||||
</CurrentColor>
|
|
||||||
<Colors sideOffset={4}>
|
<Colors sideOffset={4}>
|
||||||
{Object.entries(colors).map(([name, color]) => (
|
{Object.entries(colors).map(([name, color]) => (
|
||||||
<ColorButton key={name} title={name} onSelect={() => onChange(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 (
|
return (
|
||||||
<Square fill={color} strokeDasharray={color === 'none' ? '2, 3' : 'none'} />
|
<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)',
|
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
|
||||||
})
|
})
|
||||||
|
|
||||||
const ColorButton = styled(DropdownMenu.Item, {
|
export const ColorButton = styled(DropdownMenu.Item, {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
height: 32,
|
height: 32,
|
||||||
|
@ -79,7 +75,7 @@ const ColorButton = styled(DropdownMenu.Item, {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const CurrentColor = styled(DropdownMenu.Trigger, {
|
export const CurrentColor = styled(DropdownMenu.Trigger, {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|
|
@ -3,7 +3,7 @@ import state, { useSelector } from 'state'
|
||||||
import * as Panel from 'components/panel'
|
import * as Panel from 'components/panel'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { IconButton } from 'components/shared'
|
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 {
|
import {
|
||||||
deepCompare,
|
deepCompare,
|
||||||
deepCompareArrays,
|
deepCompareArrays,
|
||||||
|
@ -12,7 +12,7 @@ import {
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import { shades, fills, strokes } from 'lib/colors'
|
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 AlignDistribute from './align-distribute'
|
||||||
import { MoveType, ShapeStyles } from 'types'
|
import { MoveType, ShapeStyles } from 'types'
|
||||||
import WidthPicker from './width-picker'
|
import WidthPicker from './width-picker'
|
||||||
|
@ -23,6 +23,7 @@ import {
|
||||||
AspectRatioIcon,
|
AspectRatioIcon,
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
|
DotsHorizontalIcon,
|
||||||
EyeClosedIcon,
|
EyeClosedIcon,
|
||||||
EyeOpenIcon,
|
EyeOpenIcon,
|
||||||
LockClosedIcon,
|
LockClosedIcon,
|
||||||
|
@ -46,7 +47,7 @@ export default function StylePanel() {
|
||||||
<SelectedShapeStyles />
|
<SelectedShapeStyles />
|
||||||
) : (
|
) : (
|
||||||
<IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
|
<IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
|
||||||
<Circle />
|
<DotsHorizontalIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</StylePanelRoot>
|
</StylePanelRoot>
|
||||||
|
@ -118,17 +119,23 @@ function SelectedShapeStyles({}: {}) {
|
||||||
</Panel.Header>
|
</Panel.Header>
|
||||||
<Content>
|
<Content>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="Fill"
|
|
||||||
color={commonStyle.fill}
|
|
||||||
colors={fillColors}
|
colors={fillColors}
|
||||||
onChange={(color) => state.send('CHANGED_STYLE', { fill: color })}
|
onChange={(color) => state.send('CHANGED_STYLE', { fill: color })}
|
||||||
/>
|
>
|
||||||
|
<CurrentColor>
|
||||||
|
<label>Fill</label>
|
||||||
|
<ColorIcon color={commonStyle.fill} />
|
||||||
|
</CurrentColor>
|
||||||
|
</ColorPicker>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="Stroke"
|
|
||||||
color={commonStyle.stroke}
|
|
||||||
colors={strokeColors}
|
colors={strokeColors}
|
||||||
onChange={(color) => state.send('CHANGED_STYLE', { stroke: color })}
|
onChange={(color) => state.send('CHANGED_STYLE', { stroke: color })}
|
||||||
/>
|
>
|
||||||
|
<CurrentColor>
|
||||||
|
<label>Stroke</label>
|
||||||
|
<ColorIcon color={commonStyle.stroke} />
|
||||||
|
</CurrentColor>
|
||||||
|
</ColorPicker>
|
||||||
<Row>
|
<Row>
|
||||||
<label htmlFor="width">Width</label>
|
<label htmlFor="width">Width</label>
|
||||||
<WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
|
<WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
|
||||||
|
@ -194,7 +201,7 @@ function SelectedShapeStyles({}: {}) {
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
onClick={() => state.send('DELETED')}
|
onClick={() => state.send('DELETED')}
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<Trash2 />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ButtonsRow>
|
</ButtonsRow>
|
||||||
<AlignDistribute
|
<AlignDistribute
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default function ToolsPanel() {
|
||||||
return (
|
return (
|
||||||
<OuterContainer>
|
<OuterContainer>
|
||||||
<Zoom />
|
<Zoom />
|
||||||
<Flex>
|
<Flex size={{ '@sm': 'small' }}>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<IconButton
|
||||||
name="select"
|
name="select"
|
||||||
|
@ -170,8 +170,20 @@ const OuterContainer = styled('div', {
|
||||||
|
|
||||||
const Flex = styled('div', {
|
const Flex = styled('div', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
'& > *:nth-child(n+2)': {
|
justifyContent: 'space-between',
|
||||||
marginLeft: 16,
|
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 arrowDist = vec.dist(start.point, end.point)
|
||||||
const bendDist = arrowDist * bend
|
const bendDist = arrowDist * bend
|
||||||
|
|
||||||
const showCircle = Math.abs(bendDist) > 20
|
const showCircle = Math.abs(bendDist) > 20
|
||||||
|
|
||||||
const v = vec.rot(
|
// Arrowhead
|
||||||
vec.mul(
|
const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
|
||||||
vec.neg(vec.uni(vec.sub(points[1], points[0]))),
|
const angle = showCircle ? bend * (Math.PI * 0.48) : 0
|
||||||
Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
|
const u = vec.uni(vec.vec(start.point, end.point))
|
||||||
),
|
const v = vec.rot(vec.mul(vec.neg(u), length), angle)
|
||||||
showCircle ? (bend * Math.PI) / 2 : 0
|
|
||||||
)
|
|
||||||
const b = vec.add(points[1], vec.rot(v, Math.PI / 6))
|
const b = vec.add(points[1], vec.rot(v, Math.PI / 6))
|
||||||
const c = 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 (
|
return (
|
||||||
<g id={id}>
|
<g id={id}>
|
||||||
{circle ? (
|
{circle ? (
|
||||||
<path
|
<>
|
||||||
d={[
|
<path
|
||||||
'M',
|
d={getArrowArcPath(start, end, circle, bend)}
|
||||||
start.point[0],
|
fill="none"
|
||||||
start.point[1],
|
strokeLinecap="round"
|
||||||
'A',
|
/>
|
||||||
circle[2],
|
</>
|
||||||
circle[2],
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bend < 0 ? 0 : 1,
|
|
||||||
end.point[0],
|
|
||||||
end.point[1],
|
|
||||||
].join(' ')}
|
|
||||||
fill="none"
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<polyline
|
<polyline
|
||||||
points={[start.point, end.point].join(' ')}
|
points={[start.point, end.point].join(' ')}
|
||||||
|
@ -208,8 +195,6 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
transform(shape, bounds, { initialShape, scaleX, scaleY }) {
|
transform(shape, bounds, { initialShape, scaleX, scaleY }) {
|
||||||
const initialShapeBounds = this.getBounds(initialShape)
|
const initialShapeBounds = this.getBounds(initialShape)
|
||||||
|
|
||||||
// console.log([...shape.point], [bounds.minX, bounds.minY])
|
|
||||||
|
|
||||||
shape.point = [bounds.minX, bounds.minY]
|
shape.point = [bounds.minX, bounds.minY]
|
||||||
|
|
||||||
shape.points = shape.points.map((_, i) => {
|
shape.points = shape.points.map((_, i) => {
|
||||||
|
@ -234,12 +219,6 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
start.point = shape.points[0]
|
start.point = shape.points[0]
|
||||||
end.point = shape.points[1]
|
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 bendDist = (vec.dist(start.point, end.point) / 2) * shape.bend
|
||||||
const midPoint = vec.med(start.point, end.point)
|
const midPoint = vec.med(start.point, end.point)
|
||||||
const u = vec.uni(vec.vec(start.point, end.point))
|
const u = vec.uni(vec.vec(start.point, end.point))
|
||||||
|
@ -286,7 +265,10 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
shape.bend = clamp(distance / (dist / 2), -1, 1)
|
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 bendDist = (dist / 2) * shape.bend
|
||||||
const u = vec.uni(vec.vec(start.point, end.point))
|
const u = vec.uni(vec.vec(start.point, end.point))
|
||||||
|
|
||||||
bend.point =
|
shape.handles.bend.point =
|
||||||
Math.abs(bendDist) > 10
|
Math.abs(bendDist) > 10
|
||||||
? vec.add(midPoint, vec.mul(vec.per(u), bendDist))
|
? vec.add(midPoint, vec.mul(vec.per(u), bendDist))
|
||||||
: midPoint
|
: midPoint
|
||||||
|
@ -310,14 +292,22 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
export default arrow
|
export default arrow
|
||||||
|
|
||||||
function getArrowArcPath(
|
function getArrowArcPath(
|
||||||
cx: number,
|
start: ShapeHandle,
|
||||||
cy: number,
|
end: ShapeHandle,
|
||||||
r: number,
|
circle: number[],
|
||||||
start: number[],
|
bend: number
|
||||||
end: number[]
|
|
||||||
) {
|
) {
|
||||||
return `
|
return [
|
||||||
A ${r},${r},0,
|
'M',
|
||||||
${getSweep([cx, cy], start, end) > 0 ? '1' : '0'},
|
start.point[0],
|
||||||
0,${end[0]},${end[1]}`
|
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) {
|
complete(data: Data) {
|
||||||
const { id, initialShape } = this.snapshot
|
const { id } = this.snapshot
|
||||||
|
|
||||||
const shape = getPage(data).shapes[id] as ArrowShape
|
const shape = getPage(data).shapes[id] as ArrowShape
|
||||||
|
|
||||||
|
|
|
@ -128,6 +128,7 @@ const state = createState({
|
||||||
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
||||||
TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
|
TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
|
||||||
TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
|
TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
|
||||||
|
TOUCHED_CANVAS: 'closeStylePanel',
|
||||||
CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
|
CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
|
||||||
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
||||||
NUDGED: { do: 'nudgeSelection' },
|
NUDGED: { do: 'nudgeSelection' },
|
||||||
|
@ -1176,6 +1177,9 @@ const state = createState({
|
||||||
toggleStylePanel(data) {
|
toggleStylePanel(data) {
|
||||||
data.settings.isStyleOpen = !data.settings.isStyleOpen
|
data.settings.isStyleOpen = !data.settings.isStyleOpen
|
||||||
},
|
},
|
||||||
|
closeStylePanel(data) {
|
||||||
|
data.settings.isStyleOpen = false
|
||||||
|
},
|
||||||
updateStyles(data, payload: Partial<ShapeStyles>) {
|
updateStyles(data, payload: Partial<ShapeStyles>) {
|
||||||
Object.assign(data.currentStyle, payload)
|
Object.assign(data.currentStyle, payload)
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue