adds tooltips, improves arrow
This commit is contained in:
parent
492d3e9769
commit
34256f992a
16 changed files with 359 additions and 167 deletions
|
@ -31,7 +31,7 @@ export const IconButton = styled('button', {
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
small: {
|
small: {
|
||||||
'& > svg': {
|
'& svg': {
|
||||||
height: '16px',
|
height: '16px',
|
||||||
width: '16px',
|
width: '16px',
|
||||||
},
|
},
|
||||||
|
@ -39,7 +39,7 @@ export const IconButton = styled('button', {
|
||||||
medium: {
|
medium: {
|
||||||
height: 44,
|
height: 44,
|
||||||
width: 44,
|
width: 44,
|
||||||
'& > svg': {
|
'& svg': {
|
||||||
height: '20px',
|
height: '20px',
|
||||||
width: '20px',
|
width: '20px',
|
||||||
},
|
},
|
||||||
|
@ -47,7 +47,7 @@ export const IconButton = styled('button', {
|
||||||
large: {
|
large: {
|
||||||
height: 44,
|
height: 44,
|
||||||
width: 44,
|
width: 44,
|
||||||
'& > svg': {
|
'& svg': {
|
||||||
height: '24px',
|
height: '24px',
|
||||||
width: '24px',
|
width: '24px',
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { IconButton } from 'components/shared'
|
import { IconButton } from 'components/shared'
|
||||||
|
import Tooltip from 'components/tooltip'
|
||||||
import { strokes } from 'lib/shape-styles'
|
import { strokes } from 'lib/shape-styles'
|
||||||
import { Square } from 'react-feather'
|
import { Square } from 'react-feather'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
|
@ -10,8 +11,10 @@ export default function QuickColorSelect() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger as={IconButton} title="color">
|
<DropdownMenu.Trigger as={IconButton}>
|
||||||
<Square fill={strokes[color]} stroke={strokes[color]} />
|
<Tooltip label="Color">
|
||||||
|
<Square fill={strokes[color]} stroke={strokes[color]} />
|
||||||
|
</Tooltip>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<ColorContent
|
<ColorContent
|
||||||
onChange={(color) => state.send('CHANGED_STYLE', { color })}
|
onChange={(color) => state.send('CHANGED_STYLE', { color })}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { IconButton } from 'components/shared'
|
import { IconButton } from 'components/shared'
|
||||||
|
import Tooltip from 'components/tooltip'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { DashStyle } from 'types'
|
import { DashStyle } from 'types'
|
||||||
import {
|
import {
|
||||||
|
@ -21,8 +22,8 @@ export default function QuickdashSelect() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger as={IconButton} title="dash">
|
<DropdownMenu.Trigger as={IconButton}>
|
||||||
{dashes[dash]}
|
<Tooltip label="Dash">{dashes[dash]}</Tooltip>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownContent direction="vertical">
|
<DropdownContent direction="vertical">
|
||||||
<DashItem isActive={dash === DashStyle.Solid} dash={DashStyle.Solid} />
|
<DashItem isActive={dash === DashStyle.Solid} dash={DashStyle.Solid} />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { IconButton } from 'components/shared'
|
import { IconButton } from 'components/shared'
|
||||||
|
import Tooltip from 'components/tooltip'
|
||||||
import { Circle } from 'react-feather'
|
import { Circle } from 'react-feather'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { SizeStyle } from 'types'
|
import { SizeStyle } from 'types'
|
||||||
|
@ -16,8 +17,10 @@ export default function QuickSizeSelect() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger as={IconButton} title="size">
|
<DropdownMenu.Trigger as={IconButton}>
|
||||||
<Circle size={sizes[size]} stroke="none" fill="currentColor" />
|
<Tooltip label="Size">
|
||||||
|
<Circle size={sizes[size]} stroke="none" fill="currentColor" />
|
||||||
|
</Tooltip>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownContent direction="vertical">
|
<DropdownContent direction="vertical">
|
||||||
<SizeItem isActive={size === SizeStyle.Small} size={SizeStyle.Small} />
|
<SizeItem isActive={size === SizeStyle.Small} size={SizeStyle.Small} />
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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 * as Checkbox from '@radix-ui/react-checkbox'
|
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||||
import { ChevronDown, Square, Trash2, X } from 'react-feather'
|
import { ChevronDown, Square, Tool, Trash2, X } from 'react-feather'
|
||||||
import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
|
import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
|
||||||
import { strokes } from 'lib/shape-styles'
|
import { strokes } from 'lib/shape-styles'
|
||||||
import AlignDistribute from './align-distribute'
|
import AlignDistribute from './align-distribute'
|
||||||
|
@ -35,6 +35,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import IsFilledPicker from './is-filled-picker'
|
import IsFilledPicker from './is-filled-picker'
|
||||||
import QuickSizeSelect from './quick-size-select'
|
import QuickSizeSelect from './quick-size-select'
|
||||||
import QuickdashSelect from './quick-dash-select'
|
import QuickdashSelect from './quick-dash-select'
|
||||||
|
import Tooltip from 'components/tooltip'
|
||||||
|
|
||||||
export default function StylePanel() {
|
export default function StylePanel() {
|
||||||
const rContainer = useRef<HTMLDivElement>(null)
|
const rContainer = useRef<HTMLDivElement>(null)
|
||||||
|
@ -54,7 +55,9 @@ export default function StylePanel() {
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
||||||
>
|
>
|
||||||
<ChevronDown />
|
<Tooltip label="More">
|
||||||
|
<ChevronDown />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -125,35 +128,49 @@ function SelectedShapeStyles() {
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('DUPLICATED')}
|
onClick={() => state.send('DUPLICATED')}
|
||||||
>
|
>
|
||||||
<CopyIcon />
|
<Tooltip label="Duplicate">
|
||||||
|
<CopyIcon />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('ROTATED_CCW')}
|
onClick={() => state.send('ROTATED_CCW')}
|
||||||
>
|
>
|
||||||
<RotateCounterClockwiseIcon />
|
<Tooltip label="Rotate">
|
||||||
|
<RotateCounterClockwiseIcon />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
|
onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
|
||||||
>
|
>
|
||||||
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
<Tooltip label="Toogle Hidden">
|
||||||
|
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
|
onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
|
||||||
>
|
>
|
||||||
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
<Tooltip label="Toogle Locked">
|
||||||
|
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
|
onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
|
||||||
>
|
>
|
||||||
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
<Tooltip label="Toogle Aspect Ratio Lock">
|
||||||
|
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ButtonsRow>
|
</ButtonsRow>
|
||||||
<ButtonsRow>
|
<ButtonsRow>
|
||||||
|
@ -162,35 +179,49 @@ function SelectedShapeStyles() {
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
|
onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
|
||||||
>
|
>
|
||||||
<PinBottomIcon />
|
<Tooltip label="Move to Back">
|
||||||
|
<PinBottomIcon />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.Backward })}
|
onClick={() => state.send('MOVED', { type: MoveType.Backward })}
|
||||||
>
|
>
|
||||||
<ArrowDownIcon />
|
<Tooltip label="Move Backward">
|
||||||
|
<ArrowDownIcon />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.Forward })}
|
onClick={() => state.send('MOVED', { type: MoveType.Forward })}
|
||||||
>
|
>
|
||||||
<ArrowUpIcon />
|
<Tooltip label="Move Forward">
|
||||||
|
<ArrowUpIcon />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
|
onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
|
||||||
>
|
>
|
||||||
<PinTopIcon />
|
<Tooltip label="More to Front">
|
||||||
|
<PinTopIcon />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('DELETED')}
|
onClick={() => state.send('DELETED')}
|
||||||
>
|
>
|
||||||
<Trash2 />
|
<Tooltip label="Delete">
|
||||||
|
<Trash2 size="15" />
|
||||||
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ButtonsRow>
|
</ButtonsRow>
|
||||||
<AlignDistribute
|
<AlignDistribute
|
||||||
|
|
|
@ -18,6 +18,7 @@ import styled from 'styles'
|
||||||
import { ShapeType } from 'types'
|
import { ShapeType } from 'types'
|
||||||
import UndoRedo from './undo-redo'
|
import UndoRedo from './undo-redo'
|
||||||
import Zoom from './zoom'
|
import Zoom from './zoom'
|
||||||
|
import Tooltip from '../tooltip'
|
||||||
|
|
||||||
const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
|
const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
|
||||||
const selectCircleTool = () => state.send('SELECTED_CIRCLE_TOOL')
|
const selectCircleTool = () => state.send('SELECTED_CIRCLE_TOOL')
|
||||||
|
@ -32,20 +33,7 @@ const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
|
||||||
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
|
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
|
||||||
|
|
||||||
export default function ToolsPanel() {
|
export default function ToolsPanel() {
|
||||||
const activeTool = useSelector((state) =>
|
const activeTool = useSelector((s) => s.data.activeTool)
|
||||||
state.whenIn({
|
|
||||||
arrow: ShapeType.Arrow,
|
|
||||||
circle: ShapeType.Circle,
|
|
||||||
dot: ShapeType.Dot,
|
|
||||||
draw: ShapeType.Draw,
|
|
||||||
ellipse: ShapeType.Ellipse,
|
|
||||||
line: ShapeType.Line,
|
|
||||||
polyline: ShapeType.Polyline,
|
|
||||||
ray: ShapeType.Ray,
|
|
||||||
rectangle: ShapeType.Rectangle,
|
|
||||||
selecting: 'select',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
|
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
|
||||||
|
|
||||||
|
@ -56,48 +44,58 @@ export default function ToolsPanel() {
|
||||||
<Zoom />
|
<Zoom />
|
||||||
<Flex size={{ '@sm': 'small' }}>
|
<Flex size={{ '@sm': 'small' }}>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<Tooltip label="Select">
|
||||||
name="select"
|
<IconButton
|
||||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
name="select"
|
||||||
onClick={selectSelectTool}
|
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
||||||
isActive={activeTool === 'select'}
|
onClick={selectSelectTool}
|
||||||
>
|
isActive={activeTool === 'select'}
|
||||||
<CursorArrowIcon />
|
>
|
||||||
</IconButton>
|
<CursorArrowIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</Container>
|
</Container>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<Tooltip label="Draw">
|
||||||
name={ShapeType.Draw}
|
<IconButton
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
name={ShapeType.Draw}
|
||||||
onClick={selectDrawTool}
|
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||||
isActive={activeTool === ShapeType.Draw}
|
onClick={selectDrawTool}
|
||||||
>
|
isActive={activeTool === ShapeType.Draw}
|
||||||
<Pencil1Icon />
|
>
|
||||||
</IconButton>
|
<Pencil1Icon />
|
||||||
<IconButton
|
</IconButton>
|
||||||
name={ShapeType.Rectangle}
|
</Tooltip>
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
<Tooltip label="Rectangle">
|
||||||
onClick={selectRectangleTool}
|
<IconButton
|
||||||
isActive={activeTool === ShapeType.Rectangle}
|
name={ShapeType.Rectangle}
|
||||||
>
|
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||||
<SquareIcon />
|
onClick={selectRectangleTool}
|
||||||
</IconButton>
|
isActive={activeTool === ShapeType.Rectangle}
|
||||||
<IconButton
|
>
|
||||||
name={ShapeType.Circle}
|
<SquareIcon />
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
</IconButton>
|
||||||
onClick={selectEllipseTool}
|
</Tooltip>
|
||||||
isActive={activeTool === ShapeType.Ellipse}
|
<Tooltip label="Ellipse">
|
||||||
>
|
<IconButton
|
||||||
<CircleIcon />
|
name={ShapeType.Circle}
|
||||||
</IconButton>
|
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||||
<IconButton
|
onClick={selectEllipseTool}
|
||||||
name={ShapeType.Arrow}
|
isActive={activeTool === ShapeType.Ellipse}
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
>
|
||||||
onClick={selectArrowTool}
|
<CircleIcon />
|
||||||
isActive={activeTool === ShapeType.Arrow}
|
</IconButton>
|
||||||
>
|
</Tooltip>
|
||||||
<ArrowTopRightIcon />
|
<Tooltip label="Arrow">
|
||||||
</IconButton>
|
<IconButton
|
||||||
|
name={ShapeType.Arrow}
|
||||||
|
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||||
|
onClick={selectArrowTool}
|
||||||
|
isActive={activeTool === ShapeType.Arrow}
|
||||||
|
>
|
||||||
|
<ArrowTopRightIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
{/* <IconButton
|
{/* <IconButton
|
||||||
name={ShapeType.Circle}
|
name={ShapeType.Circle}
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||||
|
@ -132,19 +130,23 @@ export default function ToolsPanel() {
|
||||||
</IconButton> */}
|
</IconButton> */}
|
||||||
</Container>
|
</Container>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<Tooltip label="Lock Tool">
|
||||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
|
||||||
onClick={selectToolLock}
|
|
||||||
>
|
|
||||||
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
|
||||||
</IconButton>
|
|
||||||
{isPenLocked && (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectToolLock}
|
onClick={selectToolLock}
|
||||||
>
|
>
|
||||||
<Pencil2Icon />
|
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
{isPenLocked && (
|
||||||
|
<Tooltip label="Unlock Pen">
|
||||||
|
<IconButton
|
||||||
|
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
||||||
|
onClick={selectToolLock}
|
||||||
|
>
|
||||||
|
<Pencil2Icon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { IconButton } from 'components/shared'
|
||||||
import { RotateCcw, RotateCw, Trash2 } from 'react-feather'
|
import { RotateCcw, RotateCw, Trash2 } from 'react-feather'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
|
import Tooltip from '../tooltip'
|
||||||
|
|
||||||
const undo = () => state.send('UNDO')
|
const undo = () => state.send('UNDO')
|
||||||
const redo = () => state.send('REDO')
|
const redo = () => state.send('REDO')
|
||||||
|
@ -10,15 +11,21 @@ const clear = () => state.send('CLEARED_PAGE')
|
||||||
export default function UndoRedo() {
|
export default function UndoRedo() {
|
||||||
return (
|
return (
|
||||||
<Container size={{ '@sm': 'small' }}>
|
<Container size={{ '@sm': 'small' }}>
|
||||||
<IconButton onClick={undo}>
|
<Tooltip label="Undo">
|
||||||
<RotateCcw />
|
<IconButton onClick={undo}>
|
||||||
</IconButton>
|
<RotateCcw />
|
||||||
<IconButton onClick={redo}>
|
</IconButton>
|
||||||
<RotateCw />
|
</Tooltip>
|
||||||
</IconButton>
|
<Tooltip label="Redo">
|
||||||
<IconButton onClick={clear}>
|
<IconButton onClick={redo}>
|
||||||
<Trash2 />
|
<RotateCw />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Clear Canvas">
|
||||||
|
<IconButton onClick={clear}>
|
||||||
|
<Trash2 />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons'
|
||||||
import { IconButton } from 'components/shared'
|
import { IconButton } from 'components/shared'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
|
import Tooltip from '../tooltip'
|
||||||
|
|
||||||
const zoomIn = () => state.send('ZOOMED_IN')
|
const zoomIn = () => state.send('ZOOMED_IN')
|
||||||
const zoomOut = () => state.send('ZOOMED_OUT')
|
const zoomOut = () => state.send('ZOOMED_OUT')
|
||||||
|
@ -11,13 +12,19 @@ const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
|
||||||
export default function Zoom() {
|
export default function Zoom() {
|
||||||
return (
|
return (
|
||||||
<Container size={{ '@sm': 'small' }}>
|
<Container size={{ '@sm': 'small' }}>
|
||||||
<IconButton onClick={zoomOut}>
|
<Tooltip label="Zoom Out">
|
||||||
<ZoomOutIcon />
|
<IconButton onClick={zoomOut}>
|
||||||
</IconButton>
|
<ZoomOutIcon />
|
||||||
<IconButton onClick={zoomIn}>
|
</IconButton>
|
||||||
<ZoomInIcon />
|
</Tooltip>
|
||||||
</IconButton>
|
<Tooltip label="Zoom In">
|
||||||
<ZoomCounter />
|
<IconButton onClick={zoomIn}>
|
||||||
|
<ZoomInIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Reset Zoom">
|
||||||
|
<ZoomCounter />
|
||||||
|
</Tooltip>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
36
components/tooltip.tsx
Normal file
36
components/tooltip.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as _Tooltip from '@radix-ui/react-tooltip'
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styles'
|
||||||
|
|
||||||
|
export default function Tooltip({
|
||||||
|
children,
|
||||||
|
label,
|
||||||
|
side = 'top',
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
label: string
|
||||||
|
side?: 'bottom' | 'left' | 'right' | 'top'
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<_Tooltip.Root>
|
||||||
|
<_Tooltip.Trigger as="span">{children}</_Tooltip.Trigger>
|
||||||
|
<StyledContent side={side} sideOffset={8}>
|
||||||
|
{label}
|
||||||
|
<StyledArrow />
|
||||||
|
</StyledContent>
|
||||||
|
</_Tooltip.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledContent = styled(_Tooltip.Content, {
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: '6px 12px',
|
||||||
|
fontSize: '$1',
|
||||||
|
backgroundColor: '$text',
|
||||||
|
color: '$panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
const StyledArrow = styled(_Tooltip.Arrow, {
|
||||||
|
fill: '$text',
|
||||||
|
margin: '0 8px',
|
||||||
|
})
|
|
@ -6,15 +6,11 @@ export const strokes: Record<ColorStyle, string> = {
|
||||||
[ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
|
[ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
|
||||||
[ColorStyle.Gray]: 'rgba(172, 181, 189, 1.000)',
|
[ColorStyle.Gray]: 'rgba(172, 181, 189, 1.000)',
|
||||||
[ColorStyle.Black]: 'rgba(0,0,0, 1.000)',
|
[ColorStyle.Black]: 'rgba(0,0,0, 1.000)',
|
||||||
[ColorStyle.Lime]: 'rgba(115, 184, 23, 1.000)',
|
|
||||||
[ColorStyle.Green]: 'rgba(54, 178, 77, 1.000)',
|
[ColorStyle.Green]: 'rgba(54, 178, 77, 1.000)',
|
||||||
[ColorStyle.Teal]: 'rgba(9, 167, 120, 1.000)',
|
|
||||||
[ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
|
[ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
|
||||||
[ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
|
[ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
|
||||||
[ColorStyle.Indigo]: 'rgba(66, 99, 235, 1.000)',
|
[ColorStyle.Indigo]: 'rgba(66, 99, 235, 1.000)',
|
||||||
[ColorStyle.Violet]: 'rgba(112, 72, 232, 1.000)',
|
[ColorStyle.Violet]: 'rgba(112, 72, 232, 1.000)',
|
||||||
[ColorStyle.Grape]: 'rgba(174, 62, 200, 1.000)',
|
|
||||||
[ColorStyle.Pink]: 'rgba(214, 51, 108, 1.000)',
|
|
||||||
[ColorStyle.Red]: 'rgba(240, 63, 63, 1.000)',
|
[ColorStyle.Red]: 'rgba(240, 63, 63, 1.000)',
|
||||||
[ColorStyle.Orange]: 'rgba(247, 103, 6, 1.000)',
|
[ColorStyle.Orange]: 'rgba(247, 103, 6, 1.000)',
|
||||||
[ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
|
[ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
|
||||||
|
@ -24,16 +20,12 @@ export const fills = {
|
||||||
[ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
|
[ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
|
||||||
[ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
|
[ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
|
||||||
[ColorStyle.Gray]: 'rgba(224, 226, 230, 1.000)',
|
[ColorStyle.Gray]: 'rgba(224, 226, 230, 1.000)',
|
||||||
[ColorStyle.Black]: 'rgba(224, 226, 230, 1.000)',
|
[ColorStyle.Black]: 'rgba(255, 255, 255, 1.000)',
|
||||||
[ColorStyle.Lime]: 'rgba(243, 252, 227, 1.000)',
|
|
||||||
[ColorStyle.Green]: 'rgba(235, 251, 238, 1.000)',
|
[ColorStyle.Green]: 'rgba(235, 251, 238, 1.000)',
|
||||||
[ColorStyle.Teal]: 'rgba(230, 252, 245, 1.000)',
|
|
||||||
[ColorStyle.Cyan]: 'rgba(227, 250, 251, 1.000)',
|
[ColorStyle.Cyan]: 'rgba(227, 250, 251, 1.000)',
|
||||||
[ColorStyle.Blue]: 'rgba(231, 245, 255, 1.000)',
|
[ColorStyle.Blue]: 'rgba(231, 245, 255, 1.000)',
|
||||||
[ColorStyle.Indigo]: 'rgba(237, 242, 255, 1.000)',
|
[ColorStyle.Indigo]: 'rgba(237, 242, 255, 1.000)',
|
||||||
[ColorStyle.Violet]: 'rgba(242, 240, 255, 1.000)',
|
[ColorStyle.Violet]: 'rgba(242, 240, 255, 1.000)',
|
||||||
[ColorStyle.Grape]: 'rgba(249, 240, 252, 1.000)',
|
|
||||||
[ColorStyle.Pink]: 'rgba(254, 241, 246, 1.000)',
|
|
||||||
[ColorStyle.Red]: 'rgba(255, 245, 245, 1.000)',
|
[ColorStyle.Red]: 'rgba(255, 245, 245, 1.000)',
|
||||||
[ColorStyle.Orange]: 'rgba(255, 244, 229, 1.000)',
|
[ColorStyle.Orange]: 'rgba(255, 244, 229, 1.000)',
|
||||||
[ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',
|
[ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',
|
||||||
|
|
|
@ -97,44 +97,60 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
const { start, end, bend: _bend } = handles
|
const { start, end, bend: _bend } = handles
|
||||||
|
|
||||||
const arrowDist = vec.dist(start.point, end.point)
|
const arrowDist = vec.dist(start.point, end.point)
|
||||||
const bendDist = arrowDist * bend
|
const showCircle = !vec.isEqual(
|
||||||
const showCircle = Math.abs(bendDist) > 20
|
_bend.point,
|
||||||
|
vec.med(start.point, end.point)
|
||||||
|
)
|
||||||
|
|
||||||
const style = getShapeStyle(shape.style)
|
const style = getShapeStyle(shape.style)
|
||||||
|
|
||||||
|
let body: JSX.Element
|
||||||
|
let endAngle: number
|
||||||
|
|
||||||
|
if (showCircle) {
|
||||||
|
if (!ctpCache.has(handles)) {
|
||||||
|
ctpCache.set(
|
||||||
|
handles,
|
||||||
|
circleFromThreePoints(start.point, end.point, _bend.point)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const circle = getCtp(shape)
|
||||||
|
|
||||||
|
body = (
|
||||||
|
<path
|
||||||
|
d={getArrowArcPath(start, end, circle, bend)}
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const CE =
|
||||||
|
vec.angle([circle[0], circle[1]], end.point) -
|
||||||
|
vec.angle(start.point, end.point) +
|
||||||
|
(Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
|
||||||
|
|
||||||
|
endAngle = CE
|
||||||
|
} else {
|
||||||
|
body = (
|
||||||
|
<polyline
|
||||||
|
points={[start.point, end.point].join(' ')}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
endAngle = 0
|
||||||
|
}
|
||||||
|
|
||||||
// Arrowhead
|
// Arrowhead
|
||||||
const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
|
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 u = vec.uni(vec.vec(start.point, end.point))
|
||||||
const v = vec.rot(vec.mul(vec.neg(u), length), angle)
|
const v = vec.rot(vec.mul(vec.neg(u), length), endAngle)
|
||||||
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)))
|
||||||
|
|
||||||
if (showCircle && !ctpCache.has(handles)) {
|
|
||||||
ctpCache.set(
|
|
||||||
handles,
|
|
||||||
circleFromThreePoints(start.point, end.point, _bend.point)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const circle = showCircle && getCtp(shape)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g id={id}>
|
<g id={id}>
|
||||||
{circle ? (
|
{body}
|
||||||
<>
|
|
||||||
<path
|
|
||||||
d={getArrowArcPath(start, end, circle, bend)}
|
|
||||||
fill="none"
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<polyline
|
|
||||||
points={[start.point, end.point].join(' ')}
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<circle
|
<circle
|
||||||
cx={start.point[0]}
|
cx={start.point[0]}
|
||||||
cy={start.point[1]}
|
cy={start.point[1]}
|
||||||
|
@ -301,11 +317,11 @@ function getArrowArcPath(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBendPoint(shape: ArrowShape) {
|
function getBendPoint(shape: ArrowShape) {
|
||||||
const { start, end, bend } = shape.handles
|
const { start, end } = shape.handles
|
||||||
|
|
||||||
const dist = vec.dist(start.point, end.point)
|
const dist = vec.dist(start.point, end.point)
|
||||||
const midPoint = vec.med(start.point, end.point)
|
const midPoint = vec.med(start.point, end.point)
|
||||||
const bendDist = (dist / 2) * shape.bend
|
const bendDist = (dist / 2) * shape.bend * Math.min(1, dist / 128)
|
||||||
const u = vec.uni(vec.vec(start.point, end.point))
|
const u = vec.uni(vec.vec(start.point, end.point))
|
||||||
|
|
||||||
return Math.abs(bendDist) < 10
|
return Math.abs(bendDist) < 10
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import * as vec from 'utils/vec'
|
import * as vec from 'utils/vec'
|
||||||
import { DashStyle, DrawShape, ShapeType } from 'types'
|
import { DashStyle, DrawShape, ShapeStyles, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { intersectPolylineBounds } from 'utils/intersections'
|
import { intersectPolylineBounds } from 'utils/intersections'
|
||||||
import { boundsContainPolygon } from 'utils/bounds'
|
import { boundsContainPolygon } from 'utils/bounds'
|
||||||
|
@ -11,7 +11,6 @@ import {
|
||||||
getSvgPathFromStroke,
|
getSvgPathFromStroke,
|
||||||
translateBounds,
|
translateBounds,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import styled from 'styles'
|
|
||||||
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const pathCache = new WeakMap<DrawShape['points'], string>([])
|
const pathCache = new WeakMap<DrawShape['points'], string>([])
|
||||||
|
@ -48,17 +47,7 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style)
|
||||||
|
|
||||||
if (!pathCache.has(points)) {
|
if (!pathCache.has(points)) {
|
||||||
pathCache.set(
|
renderPath(shape, style)
|
||||||
points,
|
|
||||||
getSvgPathFromStroke(
|
|
||||||
getStroke(points, {
|
|
||||||
size: +styles.strokeWidth * 2,
|
|
||||||
thinning: 0.9,
|
|
||||||
end: { taper: 100 },
|
|
||||||
start: { taper: 40 },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (points.length < 2) {
|
if (points.length < 2) {
|
||||||
|
@ -155,9 +144,11 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
},
|
},
|
||||||
|
|
||||||
applyStyles(shape, style) {
|
applyStyles(shape, style) {
|
||||||
Object.assign(shape.style, style)
|
const styles = { ...shape.style, ...style }
|
||||||
shape.style.isFilled = false
|
styles.isFilled = false
|
||||||
shape.style.dash = DashStyle.Solid
|
styles.dash = DashStyle.Solid
|
||||||
|
shape.style = styles
|
||||||
|
shape.points = [...shape.points]
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -166,6 +157,18 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
|
|
||||||
export default draw
|
export default draw
|
||||||
|
|
||||||
const DrawPath = styled('path', {
|
function renderPath(shape: DrawShape, style: ShapeStyles) {
|
||||||
strokeWidth: 0,
|
const styles = getShapeStyle(style)
|
||||||
})
|
|
||||||
|
pathCache.set(
|
||||||
|
shape.points,
|
||||||
|
getSvgPathFromStroke(
|
||||||
|
getStroke(shape.points, {
|
||||||
|
size: +styles.strokeWidth * 2,
|
||||||
|
thinning: 0.9,
|
||||||
|
end: { taper: 100 },
|
||||||
|
start: { taper: 40 },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"@radix-ui/react-dropdown-menu": "^0.0.19",
|
"@radix-ui/react-dropdown-menu": "^0.0.19",
|
||||||
"@radix-ui/react-icons": "^1.0.3",
|
"@radix-ui/react-icons": "^1.0.3",
|
||||||
"@radix-ui/react-radio-group": "^0.0.16",
|
"@radix-ui/react-radio-group": "^0.0.16",
|
||||||
|
"@radix-ui/react-tooltip": "^0.0.18",
|
||||||
"@state-designer/react": "^1.7.1",
|
"@state-designer/react": "^1.7.1",
|
||||||
"@stitches/react": "^0.1.9",
|
"@stitches/react": "^0.1.9",
|
||||||
"framer-motion": "^4.1.16",
|
"framer-motion": "^4.1.16",
|
||||||
|
|
|
@ -58,6 +58,7 @@ const initialData: Data = {
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
},
|
},
|
||||||
|
activeTool: 'select',
|
||||||
brush: undefined,
|
brush: undefined,
|
||||||
boundsRotation: 0,
|
boundsRotation: 0,
|
||||||
pointedId: null,
|
pointedId: null,
|
||||||
|
@ -142,6 +143,7 @@ const state = createState({
|
||||||
initial: 'selecting',
|
initial: 'selecting',
|
||||||
states: {
|
states: {
|
||||||
selecting: {
|
selecting: {
|
||||||
|
onEnter: 'setActiveToolSelect',
|
||||||
on: {
|
on: {
|
||||||
SAVED: 'forceSave',
|
SAVED: 'forceSave',
|
||||||
UNDO: 'undo',
|
UNDO: 'undo',
|
||||||
|
@ -322,6 +324,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
states: {
|
states: {
|
||||||
draw: {
|
draw: {
|
||||||
|
onEnter: 'setActiveToolDraw',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -361,6 +364,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dot: {
|
dot: {
|
||||||
|
onEnter: 'setActiveToolDot',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -417,6 +421,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
arrow: {
|
arrow: {
|
||||||
|
onEnter: 'setActiveToolArrow',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -462,6 +467,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
circle: {
|
circle: {
|
||||||
|
onEnter: 'setActiveToolCircle',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -492,6 +498,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ellipse: {
|
ellipse: {
|
||||||
|
onEnter: 'setActiveToolEllipse',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -519,6 +526,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rectangle: {
|
rectangle: {
|
||||||
|
onEnter: 'setActiveToolRectangle',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -549,6 +557,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ray: {
|
ray: {
|
||||||
|
onEnter: 'setActiveToolRay',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -579,6 +588,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
|
onEnter: 'setActiveToolLine',
|
||||||
initial: 'creating',
|
initial: 'creating',
|
||||||
states: {
|
states: {
|
||||||
creating: {
|
creating: {
|
||||||
|
@ -608,7 +618,9 @@ const state = createState({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
polyline: {},
|
polyline: {
|
||||||
|
onEnter: 'setActiveToolPolyline',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
drawingShape: {
|
drawingShape: {
|
||||||
|
@ -1011,6 +1023,42 @@ const state = createState({
|
||||||
commands.rotateCcw(data)
|
commands.rotateCcw(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* ---------------------- Tool ---------------------- */
|
||||||
|
|
||||||
|
setActiveTool(data, payload: { tool: ShapeType | 'select' }) {
|
||||||
|
data.activeTool = payload.tool
|
||||||
|
},
|
||||||
|
setActiveToolSelect(data) {
|
||||||
|
data.activeTool = 'select'
|
||||||
|
},
|
||||||
|
setActiveToolDraw(data) {
|
||||||
|
data.activeTool = ShapeType.Draw
|
||||||
|
},
|
||||||
|
setActiveToolRectangle(data) {
|
||||||
|
data.activeTool = ShapeType.Rectangle
|
||||||
|
},
|
||||||
|
setActiveToolEllipse(data) {
|
||||||
|
data.activeTool = ShapeType.Ellipse
|
||||||
|
},
|
||||||
|
setActiveToolArrow(data) {
|
||||||
|
data.activeTool = ShapeType.Arrow
|
||||||
|
},
|
||||||
|
setActiveToolDot(data) {
|
||||||
|
data.activeTool = ShapeType.Dot
|
||||||
|
},
|
||||||
|
setActiveToolPolyline(data) {
|
||||||
|
data.activeTool = ShapeType.Polyline
|
||||||
|
},
|
||||||
|
setActiveToolRay(data) {
|
||||||
|
data.activeTool = ShapeType.Ray
|
||||||
|
},
|
||||||
|
setActiveToolCircle(data) {
|
||||||
|
data.activeTool = ShapeType.Circle
|
||||||
|
},
|
||||||
|
setActiveToolLine(data) {
|
||||||
|
data.activeTool = ShapeType.Line
|
||||||
|
},
|
||||||
|
|
||||||
/* --------------------- Camera --------------------- */
|
/* --------------------- Camera --------------------- */
|
||||||
|
|
||||||
zoomIn(data) {
|
zoomIn(data) {
|
||||||
|
@ -1019,7 +1067,7 @@ const state = createState({
|
||||||
const center = [window.innerWidth / 2, window.innerHeight / 2]
|
const center = [window.innerWidth / 2, window.innerHeight / 2]
|
||||||
|
|
||||||
const p0 = screenToWorld(center, data)
|
const p0 = screenToWorld(center, data)
|
||||||
camera.zoom = Math.min(3, (i + 1) * 0.25)
|
camera.zoom = getCameraZoom((i + 1) * 0.25)
|
||||||
const p1 = screenToWorld(center, data)
|
const p1 = screenToWorld(center, data)
|
||||||
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
||||||
|
|
||||||
|
@ -1031,7 +1079,7 @@ const state = createState({
|
||||||
const center = [window.innerWidth / 2, window.innerHeight / 2]
|
const center = [window.innerWidth / 2, window.innerHeight / 2]
|
||||||
|
|
||||||
const p0 = screenToWorld(center, data)
|
const p0 = screenToWorld(center, data)
|
||||||
camera.zoom = Math.max(0.1, (i - 1) * 0.25)
|
camera.zoom = getCameraZoom((i - 1) * 0.25)
|
||||||
const p1 = screenToWorld(center, data)
|
const p1 = screenToWorld(center, data)
|
||||||
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
||||||
|
|
||||||
|
@ -1067,10 +1115,11 @@ const state = createState({
|
||||||
|
|
||||||
const bounds = getSelectedBounds(data)
|
const bounds = getSelectedBounds(data)
|
||||||
|
|
||||||
const zoom =
|
const zoom = getCameraZoom(
|
||||||
bounds.width > bounds.height
|
bounds.width > bounds.height
|
||||||
? (window.innerWidth - 128) / bounds.width
|
? (window.innerWidth - 128) / bounds.width
|
||||||
: (window.innerHeight - 128) / bounds.height
|
: (window.innerHeight - 128) / bounds.height
|
||||||
|
)
|
||||||
|
|
||||||
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
|
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
|
||||||
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
|
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
|
||||||
|
@ -1096,10 +1145,11 @@ const state = createState({
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const zoom =
|
const zoom = getCameraZoom(
|
||||||
bounds.width > bounds.height
|
bounds.width > bounds.height
|
||||||
? (window.innerWidth - 128) / bounds.width
|
? (window.innerWidth - 128) / bounds.width
|
||||||
: (window.innerHeight - 128) / bounds.height
|
: (window.innerHeight - 128) / bounds.height
|
||||||
|
)
|
||||||
|
|
||||||
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
|
const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
|
||||||
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
|
const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
|
||||||
|
@ -1114,7 +1164,7 @@ const state = createState({
|
||||||
const next = camera.zoom - (payload.delta / 100) * camera.zoom
|
const next = camera.zoom - (payload.delta / 100) * camera.zoom
|
||||||
|
|
||||||
const p0 = screenToWorld(payload.point, data)
|
const p0 = screenToWorld(payload.point, data)
|
||||||
camera.zoom = clamp(next, 0.1, 3)
|
camera.zoom = getCameraZoom(next)
|
||||||
const p1 = screenToWorld(payload.point, data)
|
const p1 = screenToWorld(payload.point, data)
|
||||||
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
||||||
|
|
||||||
|
@ -1140,7 +1190,7 @@ const state = createState({
|
||||||
const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom
|
const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom
|
||||||
|
|
||||||
const p0 = screenToWorld(payload.point, data)
|
const p0 = screenToWorld(payload.point, data)
|
||||||
camera.zoom = clamp(next, 0.1, 3)
|
camera.zoom = getCameraZoom(next)
|
||||||
const p1 = screenToWorld(payload.point, data)
|
const p1 = screenToWorld(payload.point, data)
|
||||||
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
camera.point = vec.add(camera.point, vec.sub(p1, p0))
|
||||||
|
|
||||||
|
@ -1336,3 +1386,7 @@ let session: Sessions.BaseSession
|
||||||
export default state
|
export default state
|
||||||
|
|
||||||
export const useSelector = createSelectorHook(state)
|
export const useSelector = createSelectorHook(state)
|
||||||
|
|
||||||
|
function getCameraZoom(zoom: number) {
|
||||||
|
return clamp(zoom, 0.1, 5)
|
||||||
|
}
|
||||||
|
|
5
types.ts
5
types.ts
|
@ -23,6 +23,7 @@ export interface Data {
|
||||||
point: number[]
|
point: number[]
|
||||||
zoom: number
|
zoom: number
|
||||||
}
|
}
|
||||||
|
activeTool: ShapeType | 'select'
|
||||||
brush?: Bounds
|
brush?: Bounds
|
||||||
boundsRotation: number
|
boundsRotation: number
|
||||||
selectedIds: Set<string>
|
selectedIds: Set<string>
|
||||||
|
@ -72,15 +73,11 @@ export enum ColorStyle {
|
||||||
LightGray = 'LightGray',
|
LightGray = 'LightGray',
|
||||||
Gray = 'Gray',
|
Gray = 'Gray',
|
||||||
Black = 'Black',
|
Black = 'Black',
|
||||||
Lime = 'Lime',
|
|
||||||
Green = 'Green',
|
Green = 'Green',
|
||||||
Teal = 'Teal',
|
|
||||||
Cyan = 'Cyan',
|
Cyan = 'Cyan',
|
||||||
Blue = 'Blue',
|
Blue = 'Blue',
|
||||||
Indigo = 'Indigo',
|
Indigo = 'Indigo',
|
||||||
Violet = 'Violet',
|
Violet = 'Violet',
|
||||||
Grape = 'Grape',
|
|
||||||
Pink = 'Pink',
|
|
||||||
Red = 'Red',
|
Red = 'Red',
|
||||||
Orange = 'Orange',
|
Orange = 'Orange',
|
||||||
Yellow = 'Yellow',
|
Yellow = 'Yellow',
|
||||||
|
|
39
yarn.lock
39
yarn.lock
|
@ -1500,6 +1500,29 @@
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
|
|
||||||
|
"@radix-ui/react-tooltip@^0.0.18":
|
||||||
|
version "0.0.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.18.tgz#7594297dbc2acf101ac45fdb414c7bc0ac9426bc"
|
||||||
|
integrity sha512-oYRAbbTZJ8zEokrrk5pe7QbzF+ZnzMby8mBplrkColi0ntToJ7RKzPgUs1OOFvmg/Nld0Iy2FufrTNlyyEI3kQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "0.0.5"
|
||||||
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
|
"@radix-ui/react-context" "0.0.5"
|
||||||
|
"@radix-ui/react-id" "0.0.6"
|
||||||
|
"@radix-ui/react-polymorphic" "0.0.11"
|
||||||
|
"@radix-ui/react-popper" "0.0.16"
|
||||||
|
"@radix-ui/react-portal" "0.0.13"
|
||||||
|
"@radix-ui/react-presence" "0.0.14"
|
||||||
|
"@radix-ui/react-primitive" "0.0.13"
|
||||||
|
"@radix-ui/react-slot" "0.0.10"
|
||||||
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
|
"@radix-ui/react-use-escape-keydown" "0.0.6"
|
||||||
|
"@radix-ui/react-use-layout-effect" "0.0.5"
|
||||||
|
"@radix-ui/react-use-previous" "0.0.5"
|
||||||
|
"@radix-ui/react-use-rect" "0.0.7"
|
||||||
|
"@radix-ui/react-visually-hidden" "0.0.13"
|
||||||
|
|
||||||
"@radix-ui/react-use-body-pointer-events@0.0.6":
|
"@radix-ui/react-use-body-pointer-events@0.0.6":
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.0.6.tgz#30b21301880417e7dbb345871ff5a83f2abe0d8d"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.0.6.tgz#30b21301880417e7dbb345871ff5a83f2abe0d8d"
|
||||||
|
@ -1538,6 +1561,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-previous@0.0.5":
|
||||||
|
version "0.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.0.5.tgz#75191d1fa0ac24c560fe8cfbaa2f1174858cbb2f"
|
||||||
|
integrity sha512-GjtJlWlDAEMqCm2RDnVdWI6tk4/ZQfRq/VlP05Xy5rFZj6lD37VZWVWUELMBasRPzd2AS/9wPmphOgjH0VnE5A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-use-rect@0.0.7":
|
"@radix-ui/react-use-rect@0.0.7":
|
||||||
version "0.0.7"
|
version "0.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.0.7.tgz#e3a55fa7183ef436042198787bf38f8c9befcc14"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.0.7.tgz#e3a55fa7183ef436042198787bf38f8c9befcc14"
|
||||||
|
@ -1553,6 +1583,15 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-visually-hidden@0.0.13":
|
||||||
|
version "0.0.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.13.tgz#c7f69097eb7d796dcd9117cdd228d87991c08baf"
|
||||||
|
integrity sha512-8VNuE4/3PnyrLv1je56fxaa5qka0Nb6/FlyQEDF2HCPpxVOWR4sxRfSBe8cjy+Me+pJN9ZoKBIuoFCVRk54xJA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-polymorphic" "0.0.11"
|
||||||
|
"@radix-ui/react-primitive" "0.0.13"
|
||||||
|
|
||||||
"@radix-ui/rect@0.0.5":
|
"@radix-ui/rect@0.0.5":
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.0.5.tgz#6000d8d800288114af4bbc5863e6b58755d7d978"
|
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.0.5.tgz#6000d8d800288114af4bbc5863e6b58755d7d978"
|
||||||
|
|
Loading…
Reference in a new issue