Adds tool lock
This commit is contained in:
parent
c95c54dae6
commit
fe3980c80c
28 changed files with 1136 additions and 738 deletions
|
@ -23,9 +23,9 @@ function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
|
|||
// detects the change and pulls this component.
|
||||
if (!shape) return null
|
||||
|
||||
const center = getShapeUtils(shape).getCenter(shape)
|
||||
const transform = `
|
||||
rotate(${shape.rotation * (180 / Math.PI)},
|
||||
${getShapeUtils(shape).getCenter(shape)})
|
||||
rotate(${shape.rotation * (180 / Math.PI)}, ${center})
|
||||
translate(${shape.point})`
|
||||
|
||||
return (
|
||||
|
@ -38,18 +38,37 @@ function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
|
|||
>
|
||||
{isSelecting && <HoverIndicator as="use" href={'#' + id} />}
|
||||
<StyledShape id={id} style={shape.style} />
|
||||
{/*
|
||||
<text
|
||||
y={4}
|
||||
x={4}
|
||||
fontSize={18}
|
||||
fill="black"
|
||||
stroke="none"
|
||||
alignmentBaseline="text-before-edge"
|
||||
pointerEvents="none"
|
||||
>
|
||||
{center.toString()}
|
||||
</text> */}
|
||||
</StyledGroup>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledShape = memo(
|
||||
({ id, style }: { id: string; style: ShapeStyles }) => {
|
||||
return <MainShape as="use" href={'#' + id} {...style} />
|
||||
return (
|
||||
<MainShape
|
||||
as="use"
|
||||
href={'#' + id}
|
||||
{...style}
|
||||
// css={{ zStrokeWidth: Number(style.strokeWidth) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const MainShape = styled('use', {
|
||||
zStrokeWidth: 1,
|
||||
// zStrokeWidth: 1,
|
||||
})
|
||||
|
||||
const HoverIndicator = styled('path', {
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import styled from "styles"
|
||||
import styled from 'styles'
|
||||
|
||||
export const Root = styled("div", {
|
||||
position: "relative",
|
||||
backgroundColor: "$panel",
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
border: "1px solid $border",
|
||||
pointerEvents: "all",
|
||||
userSelect: "none",
|
||||
export const Root = styled('div', {
|
||||
position: 'relative',
|
||||
backgroundColor: '$panel',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid $border',
|
||||
pointerEvents: 'all',
|
||||
userSelect: 'none',
|
||||
zIndex: 200,
|
||||
boxShadow: "0px 2px 25px rgba(0,0,0,.16)",
|
||||
boxShadow: '0px 2px 25px rgba(0,0,0,.16)',
|
||||
|
||||
variants: {
|
||||
isOpen: {
|
||||
true: {
|
||||
width: "auto",
|
||||
width: 'auto',
|
||||
minWidth: 300,
|
||||
},
|
||||
false: {
|
||||
|
@ -25,63 +25,74 @@ export const Root = styled("div", {
|
|||
},
|
||||
})
|
||||
|
||||
export const Layout = styled("div", {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
gridTemplateRows: "auto 1fr",
|
||||
gridAutoRows: "28px",
|
||||
height: "100%",
|
||||
width: "auto",
|
||||
minWidth: "100%",
|
||||
export const Layout = styled('div', {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr',
|
||||
gridTemplateRows: 'auto 1fr',
|
||||
gridAutoRows: '28px',
|
||||
height: '100%',
|
||||
width: 'auto',
|
||||
minWidth: '100%',
|
||||
maxWidth: 560,
|
||||
overflow: "hidden",
|
||||
userSelect: "none",
|
||||
pointerEvents: "all",
|
||||
overflow: 'hidden',
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'all',
|
||||
})
|
||||
|
||||
export const Header = styled("div", {
|
||||
pointerEvents: "all",
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: "1px solid $border",
|
||||
position: "relative",
|
||||
export const Header = styled('div', {
|
||||
pointerEvents: 'all',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderBottom: '1px solid $border',
|
||||
position: 'relative',
|
||||
|
||||
"& h3": {
|
||||
position: "absolute",
|
||||
'& h3': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
textAlign: "center",
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
textAlign: 'center',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
fontSize: "13px",
|
||||
pointerEvents: "none",
|
||||
userSelect: "none",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
fontSize: '13px',
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
},
|
||||
|
||||
variants: {
|
||||
side: {
|
||||
left: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
right: {
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const ButtonsGroup = styled("div", {
|
||||
display: "flex",
|
||||
export const ButtonsGroup = styled('div', {
|
||||
display: 'flex',
|
||||
})
|
||||
|
||||
export const Content = styled("div", {
|
||||
position: "relative",
|
||||
pointerEvents: "all",
|
||||
overflowY: "scroll",
|
||||
export const Content = styled('div', {
|
||||
position: 'relative',
|
||||
pointerEvents: 'all',
|
||||
overflowY: 'scroll',
|
||||
})
|
||||
|
||||
export const Footer = styled("div", {
|
||||
overflowX: "scroll",
|
||||
color: "$text",
|
||||
font: "$debug",
|
||||
padding: "0 12px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderTop: "1px solid $border",
|
||||
export const Footer = styled('div', {
|
||||
overflowX: 'scroll',
|
||||
color: '$text',
|
||||
font: '$debug',
|
||||
padding: '0 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderTop: '1px solid $border',
|
||||
})
|
||||
|
|
|
@ -9,50 +9,50 @@ import {
|
|||
SpaceEvenlyVerticallyIcon,
|
||||
StretchHorizontallyIcon,
|
||||
StretchVerticallyIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import { IconButton } from "components/shared"
|
||||
import state from "state"
|
||||
import styled from "styles"
|
||||
import { AlignType, DistributeType, StretchType } from "types"
|
||||
} from '@radix-ui/react-icons'
|
||||
import { IconButton } from 'components/shared'
|
||||
import state from 'state'
|
||||
import styled from 'styles'
|
||||
import { AlignType, DistributeType, StretchType } from 'types'
|
||||
|
||||
function alignTop() {
|
||||
state.send("ALIGNED", { type: AlignType.Top })
|
||||
state.send('ALIGNED', { type: AlignType.Top })
|
||||
}
|
||||
|
||||
function alignCenterVertical() {
|
||||
state.send("ALIGNED", { type: AlignType.CenterVertical })
|
||||
state.send('ALIGNED', { type: AlignType.CenterVertical })
|
||||
}
|
||||
|
||||
function alignBottom() {
|
||||
state.send("ALIGNED", { type: AlignType.Bottom })
|
||||
state.send('ALIGNED', { type: AlignType.Bottom })
|
||||
}
|
||||
|
||||
function stretchVertically() {
|
||||
state.send("STRETCHED", { type: StretchType.Vertical })
|
||||
state.send('STRETCHED', { type: StretchType.Vertical })
|
||||
}
|
||||
|
||||
function distributeVertically() {
|
||||
state.send("DISTRIBUTED", { type: DistributeType.Vertical })
|
||||
state.send('DISTRIBUTED', { type: DistributeType.Vertical })
|
||||
}
|
||||
|
||||
function alignLeft() {
|
||||
state.send("ALIGNED", { type: AlignType.Left })
|
||||
state.send('ALIGNED', { type: AlignType.Left })
|
||||
}
|
||||
|
||||
function alignCenterHorizontal() {
|
||||
state.send("ALIGNED", { type: AlignType.CenterHorizontal })
|
||||
state.send('ALIGNED', { type: AlignType.CenterHorizontal })
|
||||
}
|
||||
|
||||
function alignRight() {
|
||||
state.send("ALIGNED", { type: AlignType.Right })
|
||||
state.send('ALIGNED', { type: AlignType.Right })
|
||||
}
|
||||
|
||||
function stretchHorizontally() {
|
||||
state.send("STRETCHED", { type: StretchType.Horizontal })
|
||||
state.send('STRETCHED', { type: StretchType.Horizontal })
|
||||
}
|
||||
|
||||
function distributeHorizontally() {
|
||||
state.send("DISTRIBUTED", { type: DistributeType.Horizontal })
|
||||
state.send('DISTRIBUTED', { type: DistributeType.Horizontal })
|
||||
}
|
||||
|
||||
export default function AlignDistribute({
|
||||
|
@ -98,15 +98,15 @@ export default function AlignDistribute({
|
|||
)
|
||||
}
|
||||
|
||||
const Container = styled("div", {
|
||||
display: "grid",
|
||||
const Container = styled('div', {
|
||||
display: 'grid',
|
||||
padding: 4,
|
||||
gridTemplateColumns: "repeat(5, auto)",
|
||||
gridTemplateColumns: 'repeat(5, auto)',
|
||||
[`& ${IconButton}`]: {
|
||||
color: "$text",
|
||||
color: '$text',
|
||||
},
|
||||
[`& ${IconButton} > svg`]: {
|
||||
fill: "red",
|
||||
stroke: "transparent",
|
||||
fill: 'red',
|
||||
stroke: 'transparent',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
|
||||
import { Square } from "react-feather"
|
||||
import styled from "styles"
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { Square } from 'react-feather'
|
||||
import styled from 'styles'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
|
@ -13,7 +13,7 @@ export default function ColorPicker({ label, color, colors, onChange }: Props) {
|
|||
return (
|
||||
<DropdownMenu.Root>
|
||||
<CurrentColor>
|
||||
<h3>{label}</h3>
|
||||
<label>{label}</label>
|
||||
<ColorIcon color={color} />
|
||||
</CurrentColor>
|
||||
<Colors sideOffset={4}>
|
||||
|
@ -31,96 +31,96 @@ function ColorIcon({ color }: { color: string }) {
|
|||
return (
|
||||
<Square
|
||||
fill={color}
|
||||
strokeDasharray={color === "transparent" ? "2, 3" : "none"}
|
||||
strokeDasharray={color === 'transparent' ? '2, 3' : 'none'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const Colors = styled(DropdownMenu.Content, {
|
||||
display: "grid",
|
||||
display: 'grid',
|
||||
padding: 4,
|
||||
gridTemplateColumns: "repeat(6, 1fr)",
|
||||
border: "1px solid $border",
|
||||
backgroundColor: "$panel",
|
||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
||||
border: '1px solid $border',
|
||||
backgroundColor: '$panel',
|
||||
borderRadius: 4,
|
||||
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, {
|
||||
position: "relative",
|
||||
cursor: "pointer",
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
height: 32,
|
||||
width: 32,
|
||||
border: "none",
|
||||
padding: "none",
|
||||
background: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
border: 'none',
|
||||
padding: 'none',
|
||||
background: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
"&::before": {
|
||||
'&::before': {
|
||||
content: "''",
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
pointerEvents: "none",
|
||||
pointerEvents: 'none',
|
||||
zIndex: 0,
|
||||
},
|
||||
|
||||
"&:hover::before": {
|
||||
backgroundColor: "$hover",
|
||||
'&:hover::before': {
|
||||
backgroundColor: '$hover',
|
||||
borderRadius: 4,
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
position: "relative",
|
||||
stroke: "rgba(0,0,0,.2)",
|
||||
'& svg': {
|
||||
position: 'relative',
|
||||
stroke: 'rgba(0,0,0,.2)',
|
||||
strokeWidth: 1,
|
||||
zIndex: 1,
|
||||
},
|
||||
})
|
||||
|
||||
const CurrentColor = styled(DropdownMenu.Trigger, {
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
outline: "none",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "4px 6px 4px 12px",
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '4px 6px 4px 12px',
|
||||
|
||||
"&::before": {
|
||||
'&::before': {
|
||||
content: "''",
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
pointerEvents: "none",
|
||||
pointerEvents: 'none',
|
||||
zIndex: -1,
|
||||
},
|
||||
|
||||
"&:hover::before": {
|
||||
backgroundColor: "$hover",
|
||||
'&:hover::before': {
|
||||
backgroundColor: '$hover',
|
||||
borderRadius: 4,
|
||||
},
|
||||
|
||||
"& h3": {
|
||||
fontFamily: "$ui",
|
||||
fontSize: "$2",
|
||||
fontWeight: "$1",
|
||||
'& label': {
|
||||
fontFamily: '$ui',
|
||||
fontSize: '$2',
|
||||
fontWeight: '$1',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
position: "relative",
|
||||
stroke: "rgba(0,0,0,.2)",
|
||||
'& svg': {
|
||||
position: 'relative',
|
||||
stroke: 'rgba(0,0,0,.2)',
|
||||
strokeWidth: 1,
|
||||
zIndex: 1,
|
||||
},
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import styled from "styles"
|
||||
import state, { useSelector } from "state"
|
||||
import * as Panel from "components/panel"
|
||||
import { useRef } from "react"
|
||||
import { IconButton } from "components/shared"
|
||||
import { Circle, Trash, X } from "react-feather"
|
||||
import { deepCompare, deepCompareArrays, getSelectedShapes } from "utils/utils"
|
||||
import { shades, fills, strokes } from "lib/colors"
|
||||
import styled from 'styles'
|
||||
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 { deepCompare, deepCompareArrays, getSelectedShapes } from 'utils/utils'
|
||||
import { shades, fills, strokes } from 'lib/colors'
|
||||
|
||||
import ColorPicker from "./color-picker"
|
||||
import AlignDistribute from "./align-distribute"
|
||||
import { ShapeStyles } from "types"
|
||||
import ColorPicker from './color-picker'
|
||||
import AlignDistribute from './align-distribute'
|
||||
import { ShapeStyles } from 'types'
|
||||
import WidthPicker from './width-picker'
|
||||
import { CopyIcon } from '@radix-ui/react-icons'
|
||||
|
||||
const fillColors = { ...shades, ...fills }
|
||||
const strokeColors = { ...shades, ...strokes }
|
||||
|
@ -23,7 +25,7 @@ export default function StylePanel() {
|
|||
{isOpen ? (
|
||||
<SelectedShapeStyles />
|
||||
) : (
|
||||
<IconButton onClick={() => state.send("TOGGLED_STYLE_PANEL_OPEN")}>
|
||||
<IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
|
||||
<Circle />
|
||||
</IconButton>
|
||||
)}
|
||||
|
@ -72,17 +74,9 @@ function SelectedShapeStyles({}: {}) {
|
|||
|
||||
return (
|
||||
<Panel.Layout>
|
||||
<Panel.Header>
|
||||
<Panel.Header side="right">
|
||||
<h3>Style</h3>
|
||||
<Panel.ButtonsGroup>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send("DELETED")}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
</Panel.ButtonsGroup>
|
||||
<IconButton onClick={() => state.send("TOGGLED_STYLE_PANEL_OPEN")}>
|
||||
<IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
|
||||
<X />
|
||||
</IconButton>
|
||||
</Panel.Header>
|
||||
|
@ -91,18 +85,40 @@ function SelectedShapeStyles({}: {}) {
|
|||
label="Fill"
|
||||
color={shapesStyle.fill}
|
||||
colors={fillColors}
|
||||
onChange={(color) => state.send("CHANGED_STYLE", { fill: color })}
|
||||
onChange={(color) => state.send('CHANGED_STYLE', { fill: color })}
|
||||
/>
|
||||
<ColorPicker
|
||||
label="Stroke"
|
||||
color={shapesStyle.stroke}
|
||||
colors={strokeColors}
|
||||
onChange={(color) => state.send("CHANGED_STYLE", { stroke: color })}
|
||||
onChange={(color) => state.send('CHANGED_STYLE', { stroke: color })}
|
||||
/>
|
||||
<Row>
|
||||
<label htmlFor="width">Width</label>
|
||||
<WidthPicker strokeWidth={Number(shapesStyle.strokeWidth)} />
|
||||
</Row>
|
||||
<AlignDistribute
|
||||
hasTwoOrMore={selectedIds.length > 1}
|
||||
hasThreeOrMore={selectedIds.length > 2}
|
||||
/>
|
||||
<ButtonsRow>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('DELETED')}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('DUPLICATED')}
|
||||
>
|
||||
<Copy />
|
||||
</IconButton>
|
||||
|
||||
<IconButton>
|
||||
<Unlock />
|
||||
</IconButton>
|
||||
</ButtonsRow>
|
||||
</Content>
|
||||
</Panel.Layout>
|
||||
)
|
||||
|
@ -112,8 +128,8 @@ const StylePanelRoot = styled(Panel.Root, {
|
|||
minWidth: 1,
|
||||
width: 184,
|
||||
maxWidth: 184,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
|
||||
variants: {
|
||||
isOpen: {
|
||||
|
@ -129,3 +145,41 @@ const StylePanelRoot = styled(Panel.Root, {
|
|||
const Content = styled(Panel.Content, {
|
||||
padding: 8,
|
||||
})
|
||||
|
||||
const Row = styled('div', {
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '4px 2px 4px 12px',
|
||||
|
||||
'& label': {
|
||||
fontFamily: '$ui',
|
||||
fontSize: '$2',
|
||||
fontWeight: '$1',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
|
||||
'& > svg': {
|
||||
position: 'relative',
|
||||
},
|
||||
})
|
||||
|
||||
const ButtonsRow = styled('div', {
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
padding: 4,
|
||||
})
|
||||
|
|
82
components/style-panel/width-picker.tsx
Normal file
82
components/style-panel/width-picker.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||
import { IconButton } from 'components/shared'
|
||||
import { ChangeEvent } from 'react'
|
||||
import { Circle } from 'react-feather'
|
||||
import state from 'state'
|
||||
import styled from 'styles'
|
||||
|
||||
function setWidth(e: ChangeEvent<HTMLInputElement>) {
|
||||
state.send('CHANGED_STYLE', {
|
||||
strokeWidth: Number(e.currentTarget.value),
|
||||
})
|
||||
}
|
||||
|
||||
export default function WidthPicker({
|
||||
strokeWidth = 2,
|
||||
}: {
|
||||
strokeWidth?: number
|
||||
}) {
|
||||
return (
|
||||
<Group name="width" onValueChange={setWidth}>
|
||||
<RadioItem value="2" isActive={strokeWidth === 2}>
|
||||
<Circle size={6} />
|
||||
</RadioItem>
|
||||
<RadioItem value="4" isActive={strokeWidth === 4}>
|
||||
<Circle size={12} />
|
||||
</RadioItem>
|
||||
<RadioItem value="8" isActive={strokeWidth === 8}>
|
||||
<Circle size={18} />
|
||||
</RadioItem>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
const Group = styled(RadioGroup.Root, {
|
||||
display: 'flex',
|
||||
})
|
||||
|
||||
const RadioItem = styled(RadioGroup.Item, {
|
||||
height: '32px',
|
||||
width: '32px',
|
||||
backgroundColor: '$panel',
|
||||
borderRadius: '4px',
|
||||
padding: '0',
|
||||
margin: '0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
pointerEvents: 'all',
|
||||
cursor: 'pointer',
|
||||
|
||||
'&:hover:not(:disabled)': {
|
||||
backgroundColor: '$hover',
|
||||
'& svg': {
|
||||
fill: '$text',
|
||||
strokeWidth: '0',
|
||||
},
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
opacity: '0.5',
|
||||
},
|
||||
|
||||
variants: {
|
||||
isActive: {
|
||||
true: {
|
||||
'& svg': {
|
||||
fill: '$text',
|
||||
strokeWidth: '0',
|
||||
},
|
||||
},
|
||||
false: {
|
||||
'& svg': {
|
||||
fill: '$inactive',
|
||||
strokeWidth: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,134 +1,147 @@
|
|||
import state, { useSelector } from "state"
|
||||
import styled from "styles"
|
||||
import { Menu } from "react-feather"
|
||||
import state, { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { Lock, Menu, Unlock } from 'react-feather'
|
||||
import { IconButton } from './shared'
|
||||
|
||||
export default function Toolbar() {
|
||||
const activeTool = useSelector((state) =>
|
||||
state.whenIn({
|
||||
selecting: "select",
|
||||
dot: "dot",
|
||||
circle: "circle",
|
||||
ellipse: "ellipse",
|
||||
ray: "ray",
|
||||
line: "line",
|
||||
polyline: "polyline",
|
||||
rectangle: "rectangle",
|
||||
draw: "draw",
|
||||
selecting: 'select',
|
||||
dot: 'dot',
|
||||
circle: 'circle',
|
||||
ellipse: 'ellipse',
|
||||
ray: 'ray',
|
||||
line: 'line',
|
||||
polyline: 'polyline',
|
||||
rectangle: 'rectangle',
|
||||
draw: 'draw',
|
||||
})
|
||||
)
|
||||
|
||||
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
|
||||
|
||||
return (
|
||||
<ToolbarContainer>
|
||||
<Section>
|
||||
<Button>
|
||||
<Menu />
|
||||
</Button>
|
||||
<Button onClick={() => state.send('TOGGLED_TOOL_LOCK')}>
|
||||
{isToolLocked ? <Lock /> : <Unlock />}
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "select"}
|
||||
onClick={() => state.send("SELECTED_SELECT_TOOL")}
|
||||
isSelected={activeTool === 'select'}
|
||||
onClick={() => state.send('SELECTED_SELECT_TOOL')}
|
||||
>
|
||||
Select
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "draw"}
|
||||
onClick={() => state.send("SELECTED_DRAW_TOOL")}
|
||||
isSelected={activeTool === 'draw'}
|
||||
onClick={() => state.send('SELECTED_DRAW_TOOL')}
|
||||
>
|
||||
Draw
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "dot"}
|
||||
onClick={() => state.send("SELECTED_DOT_TOOL")}
|
||||
isSelected={activeTool === 'dot'}
|
||||
onClick={() => state.send('SELECTED_DOT_TOOL')}
|
||||
>
|
||||
Dot
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "circle"}
|
||||
onClick={() => state.send("SELECTED_CIRCLE_TOOL")}
|
||||
isSelected={activeTool === 'circle'}
|
||||
onClick={() => state.send('SELECTED_CIRCLE_TOOL')}
|
||||
>
|
||||
Circle
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "ellipse"}
|
||||
onClick={() => state.send("SELECTED_ELLIPSE_TOOL")}
|
||||
isSelected={activeTool === 'ellipse'}
|
||||
onClick={() => state.send('SELECTED_ELLIPSE_TOOL')}
|
||||
>
|
||||
Ellipse
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "ray"}
|
||||
onClick={() => state.send("SELECTED_RAY_TOOL")}
|
||||
isSelected={activeTool === 'ray'}
|
||||
onClick={() => state.send('SELECTED_RAY_TOOL')}
|
||||
>
|
||||
Ray
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "line"}
|
||||
onClick={() => state.send("SELECTED_LINE_TOOL")}
|
||||
isSelected={activeTool === 'line'}
|
||||
onClick={() => state.send('SELECTED_LINE_TOOL')}
|
||||
>
|
||||
Line
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "polyline"}
|
||||
onClick={() => state.send("SELECTED_POLYLINE_TOOL")}
|
||||
isSelected={activeTool === 'polyline'}
|
||||
onClick={() => state.send('SELECTED_POLYLINE_TOOL')}
|
||||
>
|
||||
Polyline
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === "rectangle"}
|
||||
onClick={() => state.send("SELECTED_RECTANGLE_TOOL")}
|
||||
isSelected={activeTool === 'rectangle'}
|
||||
onClick={() => state.send('SELECTED_RECTANGLE_TOOL')}
|
||||
>
|
||||
Rectangle
|
||||
</Button>
|
||||
<Button onClick={() => state.send("RESET_CAMERA")}>Reset Camera</Button>
|
||||
<Button onClick={() => state.send('RESET_CAMERA')}>Reset Camera</Button>
|
||||
</Section>
|
||||
<Section>
|
||||
<Button onClick={() => state.send("UNDO")}>Undo</Button>
|
||||
<Button onClick={() => state.send("REDO")}>Redo</Button>
|
||||
<Button onClick={() => state.send('UNDO')}>Undo</Button>
|
||||
<Button onClick={() => state.send('REDO')}>Redo</Button>
|
||||
</Section>
|
||||
</ToolbarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ToolbarContainer = styled("div", {
|
||||
gridArea: "toolbar",
|
||||
userSelect: "none",
|
||||
borderBottom: "1px solid black",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
backgroundColor: "$panel",
|
||||
const ToolbarContainer = styled('div', {
|
||||
gridArea: 'toolbar',
|
||||
userSelect: 'none',
|
||||
borderBottom: '1px solid black',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '$panel',
|
||||
gap: 8,
|
||||
fontSize: "$1",
|
||||
fontSize: '$1',
|
||||
zIndex: 200,
|
||||
})
|
||||
|
||||
const Section = styled("div", {
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
const Section = styled('div', {
|
||||
whiteSpace: 'nowrap',
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'auto',
|
||||
display: 'flex',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
'-webkit-appearance': 'none',
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const Button = styled("button", {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
font: "$ui",
|
||||
fontSize: "$ui",
|
||||
height: "40px",
|
||||
outline: "none",
|
||||
const Button = styled('button', {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
font: '$ui',
|
||||
fontSize: '$ui',
|
||||
height: '40px',
|
||||
outline: 'none',
|
||||
borderRadius: 0,
|
||||
border: "none",
|
||||
padding: "0 12px",
|
||||
background: "none",
|
||||
"&:hover": {
|
||||
backgroundColor: "$hint",
|
||||
border: 'none',
|
||||
padding: '0 12px',
|
||||
background: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: '$hint',
|
||||
},
|
||||
"& svg": {
|
||||
'& svg': {
|
||||
height: 16,
|
||||
width: 16,
|
||||
},
|
||||
variants: {
|
||||
isSelected: {
|
||||
true: {
|
||||
color: "$selected",
|
||||
color: '$selected',
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
|
|
|
@ -1,197 +1,217 @@
|
|||
import { useEffect } from "react"
|
||||
import state from "state"
|
||||
import { MoveType } from "types"
|
||||
import { getKeyboardEventInfo, metaKey } from "utils/utils"
|
||||
import { useEffect } from 'react'
|
||||
import state from 'state'
|
||||
import { MoveType } from 'types'
|
||||
import { getKeyboardEventInfo, metaKey } from 'utils/utils'
|
||||
|
||||
export default function useKeyboardEvents() {
|
||||
useEffect(() => {
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (metaKey(e) && !["i", "r", "j"].includes(e.key)) {
|
||||
if (metaKey(e) && !['i', 'r', 'j'].includes(e.key)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "!": {
|
||||
case 'ArrowUp': {
|
||||
state.send('NUDGED', { delta: [0, -1], ...getKeyboardEventInfo(e) })
|
||||
break
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
state.send('NUDGED', { delta: [1, 0], ...getKeyboardEventInfo(e) })
|
||||
break
|
||||
}
|
||||
case 'ArrowDown': {
|
||||
state.send('NUDGED', { delta: [0, 1], ...getKeyboardEventInfo(e) })
|
||||
break
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
state.send('NUDGED', { delta: [-1, 0], ...getKeyboardEventInfo(e) })
|
||||
break
|
||||
}
|
||||
case '!': {
|
||||
// Shift + 1
|
||||
if (e.shiftKey) {
|
||||
state.send("ZOOMED_TO_FIT")
|
||||
state.send('ZOOMED_TO_FIT')
|
||||
}
|
||||
break
|
||||
}
|
||||
case "@": {
|
||||
case '@': {
|
||||
// Shift + 2
|
||||
if (e.shiftKey) {
|
||||
state.send("ZOOMED_TO_SELECTION")
|
||||
state.send('ZOOMED_TO_SELECTION')
|
||||
}
|
||||
break
|
||||
}
|
||||
case ")": {
|
||||
case ')': {
|
||||
// Shift + 0
|
||||
if (e.shiftKey) {
|
||||
state.send("ZOOMED_TO_ACTUAL")
|
||||
state.send('ZOOMED_TO_ACTUAL')
|
||||
}
|
||||
break
|
||||
}
|
||||
case "Escape": {
|
||||
state.send("CANCELLED")
|
||||
case 'Escape': {
|
||||
state.send('CANCELLED')
|
||||
break
|
||||
}
|
||||
case "z": {
|
||||
case 'z': {
|
||||
if (metaKey(e)) {
|
||||
if (e.shiftKey) {
|
||||
state.send("REDO", getKeyboardEventInfo(e))
|
||||
state.send('REDO', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send("UNDO", getKeyboardEventInfo(e))
|
||||
state.send('UNDO', getKeyboardEventInfo(e))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case "‘": {
|
||||
case '‘': {
|
||||
if (metaKey(e)) {
|
||||
state.send("MOVED", {
|
||||
state.send('MOVED', {
|
||||
...getKeyboardEventInfo(e),
|
||||
type: MoveType.ToFront,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "“": {
|
||||
case '“': {
|
||||
if (metaKey(e)) {
|
||||
state.send("MOVED", {
|
||||
state.send('MOVED', {
|
||||
...getKeyboardEventInfo(e),
|
||||
type: MoveType.ToBack,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "]": {
|
||||
case ']': {
|
||||
if (metaKey(e)) {
|
||||
state.send("MOVED", {
|
||||
state.send('MOVED', {
|
||||
...getKeyboardEventInfo(e),
|
||||
type: MoveType.Forward,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "[": {
|
||||
case '[': {
|
||||
if (metaKey(e)) {
|
||||
state.send("MOVED", {
|
||||
state.send('MOVED', {
|
||||
...getKeyboardEventInfo(e),
|
||||
type: MoveType.Backward,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "Shift": {
|
||||
state.send("PRESSED_SHIFT_KEY", getKeyboardEventInfo(e))
|
||||
case 'Shift': {
|
||||
state.send('PRESSED_SHIFT_KEY', getKeyboardEventInfo(e))
|
||||
break
|
||||
}
|
||||
case "Alt": {
|
||||
state.send("PRESSED_ALT_KEY", getKeyboardEventInfo(e))
|
||||
case 'Alt': {
|
||||
state.send('PRESSED_ALT_KEY', getKeyboardEventInfo(e))
|
||||
break
|
||||
}
|
||||
case "Backspace": {
|
||||
state.send("DELETED", getKeyboardEventInfo(e))
|
||||
case 'Backspace': {
|
||||
state.send('DELETED', getKeyboardEventInfo(e))
|
||||
break
|
||||
}
|
||||
case "s": {
|
||||
case 's': {
|
||||
if (metaKey(e)) {
|
||||
state.send("SAVED", getKeyboardEventInfo(e))
|
||||
state.send('SAVED', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "a": {
|
||||
case 'a': {
|
||||
if (metaKey(e)) {
|
||||
state.send("SELECTED_ALL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_ALL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "v": {
|
||||
case 'v': {
|
||||
if (metaKey(e)) {
|
||||
state.send("PASTED", getKeyboardEventInfo(e))
|
||||
state.send('PASTED', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send("SELECTED_SELECT_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_SELECT_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "d": {
|
||||
state.send("SELECTED_DRAW_TOOL", getKeyboardEventInfo(e))
|
||||
break
|
||||
}
|
||||
case "t": {
|
||||
case 'd': {
|
||||
if (metaKey(e)) {
|
||||
state.send("DUPLICATED", getKeyboardEventInfo(e))
|
||||
state.send('DUPLICATED', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send("SELECTED_DOT_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_DRAW_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "c": {
|
||||
case 't': {
|
||||
if (metaKey(e)) {
|
||||
state.send("COPIED", getKeyboardEventInfo(e))
|
||||
state.send('DUPLICATED', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send("SELECTED_CIRCLE_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_DOT_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "i": {
|
||||
case 'c': {
|
||||
if (metaKey(e)) {
|
||||
state.send('COPIED', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send('SELECTED_CIRCLE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'i': {
|
||||
if (metaKey(e)) {
|
||||
} else {
|
||||
state.send("SELECTED_ELLIPSE_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_ELLIPSE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "l": {
|
||||
case 'l': {
|
||||
if (metaKey(e)) {
|
||||
} else {
|
||||
state.send("SELECTED_LINE_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_LINE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "y": {
|
||||
case 'y': {
|
||||
if (metaKey(e)) {
|
||||
} else {
|
||||
state.send("SELECTED_RAY_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_RAY_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "p": {
|
||||
case 'p': {
|
||||
if (metaKey(e)) {
|
||||
} else {
|
||||
state.send("SELECTED_POLYLINE_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_POLYLINE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
case "r": {
|
||||
case 'r': {
|
||||
if (metaKey(e)) {
|
||||
} else {
|
||||
state.send("SELECTED_RECTANGLE_TOOL", getKeyboardEventInfo(e))
|
||||
state.send('SELECTED_RECTANGLE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
state.send("PRESSED_KEY", getKeyboardEventInfo(e))
|
||||
state.send('PRESSED_KEY', getKeyboardEventInfo(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(e: KeyboardEvent) {
|
||||
if (e.key === "Shift") {
|
||||
state.send("RELEASED_SHIFT_KEY", getKeyboardEventInfo(e))
|
||||
if (e.key === 'Shift') {
|
||||
state.send('RELEASED_SHIFT_KEY', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
||||
if (e.key === "Alt") {
|
||||
state.send("RELEASED_ALT_KEY", getKeyboardEventInfo(e))
|
||||
if (e.key === 'Alt') {
|
||||
state.send('RELEASED_ALT_KEY', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
||||
state.send("RELEASED_KEY", getKeyboardEventInfo(e))
|
||||
state.send('RELEASED_KEY', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
||||
document.body.addEventListener("keydown", handleKeyDown)
|
||||
document.body.addEventListener("keyup", handleKeyUp)
|
||||
document.body.addEventListener('keydown', handleKeyDown)
|
||||
document.body.addEventListener('keyup', handleKeyUp)
|
||||
return () => {
|
||||
document.body.removeEventListener("keydown", handleKeyDown)
|
||||
document.body.removeEventListener("keyup", handleKeyUp)
|
||||
document.body.removeEventListener('keydown', handleKeyDown)
|
||||
document.body.removeEventListener('keyup', handleKeyUp)
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
export const shades = {
|
||||
transparent: "transparent",
|
||||
white: "rgba(248, 249, 250, 1.000)",
|
||||
lightGray: "rgba(224, 226, 230, 1.000)",
|
||||
gray: "rgba(172, 181, 189, 1.000)",
|
||||
darkGray: "rgba(52, 58, 64, 1.000)",
|
||||
black: "rgba(0,0,0, 1.000)",
|
||||
none: 'none',
|
||||
white: 'rgba(248, 249, 250, 1.000)',
|
||||
lightGray: 'rgba(224, 226, 230, 1.000)',
|
||||
gray: 'rgba(172, 181, 189, 1.000)',
|
||||
darkGray: 'rgba(52, 58, 64, 1.000)',
|
||||
black: 'rgba(0,0,0, 1.000)',
|
||||
}
|
||||
|
||||
export const strokes = {
|
||||
lime: "rgba(115, 184, 23, 1.000)",
|
||||
green: "rgba(54, 178, 77, 1.000)",
|
||||
teal: "rgba(9, 167, 120, 1.000)",
|
||||
cyan: "rgba(14, 152, 173, 1.000)",
|
||||
blue: "rgba(28, 126, 214, 1.000)",
|
||||
indigo: "rgba(66, 99, 235, 1.000)",
|
||||
violet: "rgba(112, 72, 232, 1.000)",
|
||||
grape: "rgba(174, 62, 200, 1.000)",
|
||||
pink: "rgba(214, 51, 108, 1.000)",
|
||||
red: "rgba(240, 63, 63, 1.000)",
|
||||
orange: "rgba(247, 103, 6, 1.000)",
|
||||
yellow: "rgba(245, 159, 0, 1.000)",
|
||||
lime: 'rgba(115, 184, 23, 1.000)',
|
||||
green: 'rgba(54, 178, 77, 1.000)',
|
||||
teal: 'rgba(9, 167, 120, 1.000)',
|
||||
cyan: 'rgba(14, 152, 173, 1.000)',
|
||||
blue: 'rgba(28, 126, 214, 1.000)',
|
||||
indigo: 'rgba(66, 99, 235, 1.000)',
|
||||
violet: 'rgba(112, 72, 232, 1.000)',
|
||||
grape: 'rgba(174, 62, 200, 1.000)',
|
||||
pink: 'rgba(214, 51, 108, 1.000)',
|
||||
red: 'rgba(240, 63, 63, 1.000)',
|
||||
orange: 'rgba(247, 103, 6, 1.000)',
|
||||
yellow: 'rgba(245, 159, 0, 1.000)',
|
||||
}
|
||||
|
||||
export const fills = {
|
||||
lime: "rgba(217, 245, 162, 1.000)",
|
||||
green: "rgba(177, 242, 188, 1.000)",
|
||||
teal: "rgba(149, 242, 215, 1.000)",
|
||||
cyan: "rgba(153, 233, 242, 1.000)",
|
||||
blue: "rgba(166, 216, 255, 1.000)",
|
||||
indigo: "rgba(186, 200, 255, 1.000)",
|
||||
violet: "rgba(208, 191, 255, 1.000)",
|
||||
grape: "rgba(237, 190, 250, 1.000)",
|
||||
pink: "rgba(252, 194, 215, 1.000)",
|
||||
red: "rgba(255, 201, 201, 1.000)",
|
||||
orange: "rgba(255, 216, 168, 1.000)",
|
||||
yellow: "rgba(255, 236, 153, 1.000)",
|
||||
lime: 'rgba(217, 245, 162, 1.000)',
|
||||
green: 'rgba(177, 242, 188, 1.000)',
|
||||
teal: 'rgba(149, 242, 215, 1.000)',
|
||||
cyan: 'rgba(153, 233, 242, 1.000)',
|
||||
blue: 'rgba(166, 216, 255, 1.000)',
|
||||
indigo: 'rgba(186, 200, 255, 1.000)',
|
||||
violet: 'rgba(208, 191, 255, 1.000)',
|
||||
grape: 'rgba(237, 190, 250, 1.000)',
|
||||
pink: 'rgba(252, 194, 215, 1.000)',
|
||||
red: 'rgba(255, 201, 201, 1.000)',
|
||||
orange: 'rgba(255, 216, 168, 1.000)',
|
||||
yellow: 'rgba(255, 236, 153, 1.000)',
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { CircleShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { boundsContained } from "utils/bounds"
|
||||
import { intersectCircleBounds } from "utils/intersections"
|
||||
import { pointInCircle } from "utils/hitTests"
|
||||
import { translateBounds } from "utils/utils"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { CircleShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { pointInCircle } from 'utils/hitTests'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
|
||||
const circle = registerShapeUtils<CircleShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -15,22 +15,29 @@ const circle = registerShapeUtils<CircleShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Circle,
|
||||
isGenerated: false,
|
||||
name: "Circle",
|
||||
parentId: "page0",
|
||||
name: 'Circle',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
radius: 1,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
},
|
||||
...props,
|
||||
}
|
||||
},
|
||||
|
||||
render({ id, radius }) {
|
||||
return <circle id={id} cx={radius} cy={radius} r={radius} />
|
||||
render({ id, radius, style }) {
|
||||
return (
|
||||
<circle
|
||||
id={id}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
r={Math.max(0, radius - Number(style.strokeWidth) / 2)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
applyStyles(shape, style) {
|
||||
|
@ -92,7 +99,7 @@ const circle = registerShapeUtils<CircleShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { DotShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { boundsContained } from "utils/bounds"
|
||||
import { intersectCircleBounds } from "utils/intersections"
|
||||
import { DotCircle } from "components/canvas/misc"
|
||||
import { translateBounds } from "utils/utils"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { DotShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { DotCircle } from 'components/canvas/misc'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
|
||||
const dot = registerShapeUtils<DotShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -15,14 +15,14 @@ const dot = registerShapeUtils<DotShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Dot,
|
||||
isGenerated: false,
|
||||
name: "Dot",
|
||||
parentId: "page0",
|
||||
name: 'Dot',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
strokeWidth: "0",
|
||||
fill: '#c6cacb',
|
||||
strokeWidth: '0',
|
||||
},
|
||||
...props,
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ const dot = registerShapeUtils<DotShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -34,13 +34,13 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
...props.style,
|
||||
stroke: 'transparent',
|
||||
fill: props.style.stroke,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
render(shape) {
|
||||
const { id, point, points } = shape
|
||||
const { id, point, points, style } = shape
|
||||
|
||||
if (!pathCache.has(points)) {
|
||||
if (points.length < 2) {
|
||||
|
@ -51,7 +51,12 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
}
|
||||
pathCache.set(points, getSvgPathFromStroke(d))
|
||||
} else {
|
||||
pathCache.set(points, getSvgPathFromStroke(getStroke(points)))
|
||||
pathCache.set(
|
||||
points,
|
||||
getSvgPathFromStroke(
|
||||
getStroke(points, { size: Number(style.strokeWidth) * 2 })
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +65,7 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
|
||||
applyStyles(shape, style) {
|
||||
Object.assign(shape.style, style)
|
||||
shape.style.fill = shape.style.stroke
|
||||
return this
|
||||
},
|
||||
|
||||
|
@ -128,7 +134,7 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { EllipseShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { boundsContained, getRotatedEllipseBounds } from "utils/bounds"
|
||||
import { intersectEllipseBounds } from "utils/intersections"
|
||||
import { pointInEllipse } from "utils/hitTests"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { EllipseShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
|
||||
import { intersectEllipseBounds } from 'utils/intersections'
|
||||
import { pointInEllipse } from 'utils/hitTests'
|
||||
import {
|
||||
getBoundsFromPoints,
|
||||
getRotatedCorners,
|
||||
rotateBounds,
|
||||
translateBounds,
|
||||
} from "utils/utils"
|
||||
} from 'utils/utils'
|
||||
|
||||
const ellipse = registerShapeUtils<EllipseShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -20,24 +20,30 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Ellipse,
|
||||
isGenerated: false,
|
||||
name: "Ellipse",
|
||||
parentId: "page0",
|
||||
name: 'Ellipse',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
radiusX: 1,
|
||||
radiusY: 1,
|
||||
rotation: 0,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
},
|
||||
...props,
|
||||
}
|
||||
},
|
||||
|
||||
render({ id, radiusX, radiusY }) {
|
||||
render({ id, radiusX, radiusY, style }) {
|
||||
return (
|
||||
<ellipse id={id} cx={radiusX} cy={radiusY} rx={radiusX} ry={radiusY} />
|
||||
<ellipse
|
||||
id={id}
|
||||
cx={radiusX}
|
||||
cy={radiusY}
|
||||
rx={Math.max(0, radiusX - Number(style.strokeWidth) / 2)}
|
||||
ry={Math.max(0, radiusY - Number(style.strokeWidth) / 2)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
|
@ -110,7 +116,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { LineShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { boundsContained } from "utils/bounds"
|
||||
import { intersectCircleBounds } from "utils/intersections"
|
||||
import { DotCircle } from "components/canvas/misc"
|
||||
import { translateBounds } from "utils/utils"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { LineShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { DotCircle } from 'components/canvas/misc'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
|
||||
const line = registerShapeUtils<LineShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -15,15 +15,15 @@ const line = registerShapeUtils<LineShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Line,
|
||||
isGenerated: false,
|
||||
name: "Line",
|
||||
parentId: "page0",
|
||||
name: 'Line',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
direction: [0, 0],
|
||||
rotation: 0,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
},
|
||||
...props,
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ const line = registerShapeUtils<LineShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { PolylineShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { intersectPolylineBounds } from "utils/intersections"
|
||||
import { boundsContainPolygon } from "utils/bounds"
|
||||
import { getBoundsFromPoints, translateBounds } from "utils/utils"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { PolylineShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { intersectPolylineBounds } from 'utils/intersections'
|
||||
import { boundsContainPolygon } from 'utils/bounds'
|
||||
import { getBoundsFromPoints, translateBounds } from 'utils/utils'
|
||||
|
||||
const polyline = registerShapeUtils<PolylineShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -14,16 +14,16 @@ const polyline = registerShapeUtils<PolylineShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Polyline,
|
||||
isGenerated: false,
|
||||
name: "Polyline",
|
||||
parentId: "page0",
|
||||
name: 'Polyline',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
points: [[0, 0]],
|
||||
rotation: 0,
|
||||
style: {
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: "round",
|
||||
strokeLinejoin: "round",
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
},
|
||||
...props,
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ const polyline = registerShapeUtils<PolylineShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { RayShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { boundsContained } from "utils/bounds"
|
||||
import { intersectCircleBounds } from "utils/intersections"
|
||||
import { DotCircle } from "components/canvas/misc"
|
||||
import { translateBounds } from "utils/utils"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { RayShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { boundsContained } from 'utils/bounds'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { DotCircle } from 'components/canvas/misc'
|
||||
import { translateBounds } from 'utils/utils'
|
||||
|
||||
const ray = registerShapeUtils<RayShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -15,15 +15,15 @@ const ray = registerShapeUtils<RayShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Ray,
|
||||
isGenerated: false,
|
||||
name: "Ray",
|
||||
parentId: "page0",
|
||||
name: 'Ray',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
direction: [0, 1],
|
||||
rotation: 0,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
@ -88,7 +88,7 @@ const ray = registerShapeUtils<RayShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
import * as vec from "utils/vec"
|
||||
import { RectangleShape, ShapeType } from "types"
|
||||
import { registerShapeUtils } from "./index"
|
||||
import { boundsCollidePolygon, boundsContainPolygon } from "utils/bounds"
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import * as vec from 'utils/vec'
|
||||
import { RectangleShape, ShapeType } from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { boundsCollidePolygon, boundsContainPolygon } from 'utils/bounds'
|
||||
import {
|
||||
getBoundsFromPoints,
|
||||
getRotatedCorners,
|
||||
translateBounds,
|
||||
} from "utils/utils"
|
||||
} from 'utils/utils'
|
||||
|
||||
const rectangle = registerShapeUtils<RectangleShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -17,42 +17,31 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
id: uuid(),
|
||||
type: ShapeType.Rectangle,
|
||||
isGenerated: false,
|
||||
name: "Rectangle",
|
||||
parentId: "page0",
|
||||
name: 'Rectangle',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
size: [1, 1],
|
||||
radius: 2,
|
||||
rotation: 0,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
},
|
||||
...props,
|
||||
}
|
||||
},
|
||||
|
||||
render({ id, size, radius, childIndex }) {
|
||||
render({ id, size, radius, style }) {
|
||||
return (
|
||||
<g id={id}>
|
||||
<rect
|
||||
id={id}
|
||||
width={size[0]}
|
||||
height={size[1]}
|
||||
rx={radius}
|
||||
ry={radius}
|
||||
width={Math.max(0, size[0] - Number(style.strokeWidth) / 2)}
|
||||
height={Math.max(0, size[1] - Number(style.strokeWidth) / 2)}
|
||||
/>
|
||||
<text
|
||||
y={4}
|
||||
x={4}
|
||||
fontSize={18}
|
||||
fill="black"
|
||||
stroke="none"
|
||||
alignmentBaseline="text-before-edge"
|
||||
pointerEvents="none"
|
||||
>
|
||||
{childIndex}
|
||||
</text>
|
||||
</g>
|
||||
)
|
||||
},
|
||||
|
@ -113,7 +102,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
},
|
||||
|
||||
translateTo(shape, point) {
|
||||
shape.point = point
|
||||
shape.point = vec.toPrecision(point)
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"@monaco-editor/react": "^4.1.3",
|
||||
"@radix-ui/react-dropdown-menu": "^0.0.19",
|
||||
"@radix-ui/react-icons": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^0.0.16",
|
||||
"@state-designer/react": "^1.7.1",
|
||||
"@stitches/react": "^0.1.9",
|
||||
"framer-motion": "^4.1.16",
|
||||
|
|
48
state/commands/duplicate.ts
Normal file
48
state/commands/duplicate.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data } from 'types'
|
||||
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { current } from 'immer'
|
||||
import * as vec from 'utils/vec'
|
||||
|
||||
export default function duplicateCommand(data: Data) {
|
||||
const { currentPageId } = data
|
||||
const selectedShapes = getSelectedShapes(current(data))
|
||||
const duplicates = selectedShapes.map((shape) => ({
|
||||
...shape,
|
||||
id: uuid(),
|
||||
point: vec.add(shape.point, vec.div([16, 16], data.camera.zoom)),
|
||||
}))
|
||||
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: 'duplicate_shapes',
|
||||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
data.selectedIds.clear()
|
||||
|
||||
for (const duplicate of duplicates) {
|
||||
shapes[duplicate.id] = duplicate
|
||||
data.selectedIds.add(duplicate.id)
|
||||
}
|
||||
},
|
||||
undo(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
data.selectedIds.clear()
|
||||
|
||||
for (const duplicate of duplicates) {
|
||||
delete shapes[duplicate.id]
|
||||
}
|
||||
|
||||
for (let id in selectedShapes) {
|
||||
data.selectedIds.add(id)
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
|
@ -1,22 +1,25 @@
|
|||
import align from "./align"
|
||||
import deleteSelected from "./delete-selected"
|
||||
import direct from "./direct"
|
||||
import distribute from "./distribute"
|
||||
import generate from "./generate"
|
||||
import move from "./move"
|
||||
import draw from "./draw"
|
||||
import rotate from "./rotate"
|
||||
import stretch from "./stretch"
|
||||
import style from "./style"
|
||||
import transform from "./transform"
|
||||
import transformSingle from "./transform-single"
|
||||
import translate from "./translate"
|
||||
import align from './align'
|
||||
import deleteSelected from './delete-selected'
|
||||
import direct from './direct'
|
||||
import distribute from './distribute'
|
||||
import duplicate from './duplicate'
|
||||
import generate from './generate'
|
||||
import move from './move'
|
||||
import draw from './draw'
|
||||
import rotate from './rotate'
|
||||
import stretch from './stretch'
|
||||
import style from './style'
|
||||
import transform from './transform'
|
||||
import transformSingle from './transform-single'
|
||||
import translate from './translate'
|
||||
import nudge from './nudge'
|
||||
|
||||
const commands = {
|
||||
align,
|
||||
deleteSelected,
|
||||
direct,
|
||||
distribute,
|
||||
duplicate,
|
||||
generate,
|
||||
move,
|
||||
draw,
|
||||
|
@ -26,6 +29,7 @@ const commands = {
|
|||
transform,
|
||||
transformSingle,
|
||||
translate,
|
||||
nudge,
|
||||
}
|
||||
|
||||
export default commands
|
||||
|
|
40
state/commands/nudge.ts
Normal file
40
state/commands/nudge.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data } from 'types'
|
||||
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import * as vec from 'utils/vec'
|
||||
|
||||
export default function nudgeCommand(data: Data, delta: number[]) {
|
||||
const { currentPageId } = data
|
||||
const selectedShapes = getSelectedShapes(data)
|
||||
const shapeBounds = Object.fromEntries(
|
||||
selectedShapes.map(
|
||||
(shape) => [shape.id, getShapeUtils(shape).getBounds(shape)] as const
|
||||
)
|
||||
)
|
||||
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: 'set_direction',
|
||||
category: 'canvas',
|
||||
do(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
for (let id in shapeBounds) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, vec.add(shape.point, delta))
|
||||
}
|
||||
},
|
||||
undo(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
for (let id in shapeBounds) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, vec.sub(shape.point, delta))
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import Command from "./command"
|
||||
import history from "../history"
|
||||
import { Data, Corner, Edge } from "types"
|
||||
import { getShapeUtils } from "lib/shape-utils"
|
||||
import { current } from "immer"
|
||||
import { TransformSingleSnapshot } from "state/sessions/transform-single-session"
|
||||
import { getPage } from "utils/utils"
|
||||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data, Corner, Edge } from 'types'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { current } from 'immer'
|
||||
import { TransformSingleSnapshot } from 'state/sessions/transform-single-session'
|
||||
import { getPage } from 'utils/utils'
|
||||
|
||||
export default function transformSingleCommand(
|
||||
data: Data,
|
||||
|
@ -14,13 +14,13 @@ export default function transformSingleCommand(
|
|||
scaleY: number,
|
||||
isCreating: boolean
|
||||
) {
|
||||
const shape = getPage(data, after.currentPageId).shapes[after.id]
|
||||
const shape = current(getPage(data, after.currentPageId).shapes[after.id])
|
||||
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: "transform_single_shape",
|
||||
category: "canvas",
|
||||
name: 'transform_single_shape',
|
||||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data) {
|
||||
const { id, type, initialShape, initialShapeBounds } = after
|
||||
|
|
|
@ -41,10 +41,15 @@ const initialData: Data = {
|
|||
isDarkMode: false,
|
||||
isCodeOpen: false,
|
||||
isStyleOpen: false,
|
||||
isToolLocked: false,
|
||||
isPenLocked: false,
|
||||
nudgeDistanceLarge: 10,
|
||||
nudgeDistanceSmall: 1,
|
||||
},
|
||||
currentStyle: {
|
||||
fill: shades.lightGray,
|
||||
stroke: shades.darkGray,
|
||||
strokeWidth: 2,
|
||||
},
|
||||
camera: {
|
||||
point: [0, 0],
|
||||
|
@ -94,6 +99,9 @@ const state = createState({
|
|||
else: 'zoomCameraToActual',
|
||||
},
|
||||
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
||||
NUDGED: { do: 'nudgeSelection' },
|
||||
USED_PEN_DEVICE: 'enablePenLock',
|
||||
DISABLED_PEN_LOCK: 'disablePenLock',
|
||||
},
|
||||
initial: 'loading',
|
||||
states: {
|
||||
|
@ -124,20 +132,22 @@ const state = createState({
|
|||
selecting: {
|
||||
on: {
|
||||
SAVED: 'forceSave',
|
||||
UNDO: { do: 'undo' },
|
||||
REDO: { do: 'redo' },
|
||||
CANCELLED: { do: 'clearSelectedIds' },
|
||||
DELETED: { do: 'deleteSelectedIds' },
|
||||
UNDO: 'undo',
|
||||
REDO: 'redo',
|
||||
SAVED_CODE: 'saveCode',
|
||||
GENERATED_FROM_CODE: ['setCodeControls', 'setGeneratedShapes'],
|
||||
CANCELLED: 'clearSelectedIds',
|
||||
DELETED: 'deleteSelectedIds',
|
||||
STARTED_PINCHING: { to: 'pinching' },
|
||||
INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
|
||||
DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
|
||||
CHANGED_CODE_CONTROL: 'updateControls',
|
||||
ALIGNED: 'alignSelection',
|
||||
STRETCHED: 'stretchSelection',
|
||||
DISTRIBUTED: 'distributeSelection',
|
||||
MOVED: 'moveSelection',
|
||||
STARTED_PINCHING: { to: 'pinching' },
|
||||
GENERATED_FROM_CODE: ['setCodeControls', 'setGeneratedShapes'],
|
||||
TOGGLED_TOOL_LOCK: 'toggleToolLock',
|
||||
MOVED: { if: 'hasSelection', do: 'moveSelection' },
|
||||
ALIGNED: { if: 'hasSelection', do: 'alignSelection' },
|
||||
STRETCHED: { if: 'hasSelection', do: 'stretchSelection' },
|
||||
DISTRIBUTED: { if: 'hasSelection', do: 'distributeSelection' },
|
||||
DUPLICATED: { if: 'hasSelection', do: 'duplicateSelection' },
|
||||
},
|
||||
initial: 'notPointing',
|
||||
states: {
|
||||
|
@ -262,6 +272,9 @@ const state = createState({
|
|||
PINCHED: { do: 'pinchCamera' },
|
||||
},
|
||||
},
|
||||
usingTool: {
|
||||
initial: 'draw',
|
||||
states: {
|
||||
draw: {
|
||||
initial: 'creating',
|
||||
states: {
|
||||
|
@ -309,7 +322,16 @@ const state = createState({
|
|||
},
|
||||
editing: {
|
||||
on: {
|
||||
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
|
||||
STOPPED_POINTING: [
|
||||
'completeSession',
|
||||
{
|
||||
if: 'isToolLocked',
|
||||
to: 'dot.creating',
|
||||
else: {
|
||||
to: 'selecting',
|
||||
},
|
||||
},
|
||||
],
|
||||
CANCELLED: {
|
||||
do: ['cancelSession', 'deleteSelectedIds'],
|
||||
to: 'selecting',
|
||||
|
@ -472,10 +494,14 @@ const state = createState({
|
|||
},
|
||||
drawingShape: {
|
||||
on: {
|
||||
STOPPED_POINTING: {
|
||||
do: 'completeSession',
|
||||
to: 'selecting',
|
||||
STOPPED_POINTING: [
|
||||
'completeSession',
|
||||
{
|
||||
if: 'isToolLocked',
|
||||
to: 'usingTool.previous',
|
||||
else: { to: 'selecting' },
|
||||
},
|
||||
],
|
||||
CANCELLED: {
|
||||
do: ['cancelSession', 'deleteSelectedIds'],
|
||||
to: 'selecting',
|
||||
|
@ -500,6 +526,8 @@ const state = createState({
|
|||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
results: {
|
||||
newDraw() {
|
||||
return ShapeType.Draw
|
||||
|
@ -562,6 +590,12 @@ const state = createState({
|
|||
hasSelection(data) {
|
||||
return data.selectedIds.size > 0
|
||||
},
|
||||
isToolLocked(data) {
|
||||
return data.settings.isToolLocked
|
||||
},
|
||||
isPenLocked(data) {
|
||||
return data.settings.isPenLocked
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
/* --------------------- Shapes --------------------- */
|
||||
|
@ -712,6 +746,19 @@ const state = createState({
|
|||
session.update(data, screenToWorld(payload.point, data))
|
||||
},
|
||||
|
||||
// Nudges
|
||||
nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
|
||||
commands.nudge(
|
||||
data,
|
||||
vec.mul(
|
||||
payload.delta,
|
||||
payload.shiftKey
|
||||
? data.settings.nudgeDistanceLarge
|
||||
: data.settings.nudgeDistanceSmall
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
/* -------------------- Selection ------------------- */
|
||||
|
||||
selectAll(data) {
|
||||
|
@ -756,6 +803,9 @@ const state = createState({
|
|||
distributeSelection(data, payload: { type: DistributeType }) {
|
||||
commands.distribute(data, payload.type)
|
||||
},
|
||||
duplicateSelection(data) {
|
||||
commands.duplicate(data)
|
||||
},
|
||||
|
||||
/* --------------------- Camera --------------------- */
|
||||
|
||||
|
@ -913,6 +963,7 @@ const state = createState({
|
|||
},
|
||||
|
||||
/* ---------------------- Code ---------------------- */
|
||||
|
||||
closeCodePanel(data) {
|
||||
data.settings.isCodeOpen = false
|
||||
},
|
||||
|
@ -962,7 +1013,20 @@ const state = createState({
|
|||
history.enable()
|
||||
},
|
||||
|
||||
// Data
|
||||
/* -------------------- Settings -------------------- */
|
||||
|
||||
enablePenLock(data) {
|
||||
data.settings.isPenLocked = true
|
||||
},
|
||||
disablePenLock(data) {
|
||||
data.settings.isPenLocked = false
|
||||
},
|
||||
toggleToolLock(data) {
|
||||
data.settings.isToolLocked = !data.settings.isToolLocked
|
||||
},
|
||||
|
||||
/* ---------------------- Data ---------------------- */
|
||||
|
||||
saveCode(data, payload: { code: string }) {
|
||||
data.document.code[data.currentCodeFileId].code = payload.code
|
||||
history.save(data)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createCss, defaultThemeMap } from "@stitches/react"
|
||||
import { createCss, defaultThemeMap } from '@stitches/react'
|
||||
|
||||
const { styled, global, css, theme, getCssString } = createCss({
|
||||
themeMap: {
|
||||
|
@ -6,26 +6,27 @@ const { styled, global, css, theme, getCssString } = createCss({
|
|||
},
|
||||
theme: {
|
||||
colors: {
|
||||
brushFill: "rgba(0,0,0,.1)",
|
||||
brushStroke: "rgba(0,0,0,.5)",
|
||||
hint: "rgba(66, 133, 244, 0.200)",
|
||||
selected: "rgba(66, 133, 244, 1.000)",
|
||||
bounds: "rgba(65, 132, 244, 1.000)",
|
||||
boundsBg: "rgba(65, 132, 244, 0.100)",
|
||||
border: "#aaa",
|
||||
panel: "#fefefe",
|
||||
hover: "#efefef",
|
||||
text: "#333",
|
||||
input: "#f3f3f3",
|
||||
inputBorder: "#ddd",
|
||||
brushFill: 'rgba(0,0,0,.1)',
|
||||
brushStroke: 'rgba(0,0,0,.5)',
|
||||
hint: 'rgba(66, 133, 244, 0.200)',
|
||||
selected: 'rgba(66, 133, 244, 1.000)',
|
||||
bounds: 'rgba(65, 132, 244, 1.000)',
|
||||
boundsBg: 'rgba(65, 132, 244, 0.100)',
|
||||
border: '#aaa',
|
||||
panel: '#fefefe',
|
||||
inactive: '#cccccf',
|
||||
hover: '#efefef',
|
||||
text: '#333',
|
||||
input: '#f3f3f3',
|
||||
inputBorder: '#ddd',
|
||||
},
|
||||
space: {},
|
||||
fontSizes: {
|
||||
0: "10px",
|
||||
1: "12px",
|
||||
2: "13px",
|
||||
3: "16px",
|
||||
4: "18px",
|
||||
0: '10px',
|
||||
1: '12px',
|
||||
2: '13px',
|
||||
3: '16px',
|
||||
4: '18px',
|
||||
},
|
||||
fonts: {
|
||||
ui: '"Recursive", system-ui, sans-serif',
|
||||
|
@ -72,17 +73,17 @@ const light = theme({})
|
|||
const dark = theme({})
|
||||
|
||||
const globalStyles = global({
|
||||
"*": { boxSizing: "border-box" },
|
||||
":root": {
|
||||
"--camera-zoom": 1,
|
||||
"--scale": "calc(1 / var(--camera-zoom))",
|
||||
'*': { boxSizing: 'border-box' },
|
||||
':root': {
|
||||
'--camera-zoom': 1,
|
||||
'--scale': 'calc(1 / var(--camera-zoom))',
|
||||
},
|
||||
"html, body": {
|
||||
padding: "0px",
|
||||
margin: "0px",
|
||||
overscrollBehavior: "none",
|
||||
fontFamily: "$ui",
|
||||
fontSize: "$2",
|
||||
'html, body': {
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
overscrollBehavior: 'none',
|
||||
fontFamily: '$ui',
|
||||
fontSize: '$2',
|
||||
},
|
||||
})
|
||||
|
||||
|
|
52
types.ts
52
types.ts
|
@ -1,6 +1,6 @@
|
|||
import * as monaco from "monaco-editor/esm/vs/editor/editor.api"
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
|
||||
import React from "react"
|
||||
import React from 'react'
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* Client State */
|
||||
|
@ -13,6 +13,10 @@ export interface Data {
|
|||
isDarkMode: boolean
|
||||
isCodeOpen: boolean
|
||||
isStyleOpen: boolean
|
||||
nudgeDistanceSmall: number
|
||||
nudgeDistanceLarge: number
|
||||
isToolLocked: boolean
|
||||
isPenLocked: boolean
|
||||
}
|
||||
currentStyle: ShapeStyles
|
||||
camera: {
|
||||
|
@ -39,21 +43,21 @@ export interface Data {
|
|||
|
||||
export interface Page {
|
||||
id: string
|
||||
type: "page"
|
||||
type: 'page'
|
||||
childIndex: number
|
||||
name: string
|
||||
shapes: Record<string, Shape>
|
||||
}
|
||||
|
||||
export enum ShapeType {
|
||||
Dot = "dot",
|
||||
Circle = "circle",
|
||||
Ellipse = "ellipse",
|
||||
Line = "line",
|
||||
Ray = "ray",
|
||||
Polyline = "polyline",
|
||||
Rectangle = "rectangle",
|
||||
Draw = "draw",
|
||||
Dot = 'dot',
|
||||
Circle = 'circle',
|
||||
Ellipse = 'ellipse',
|
||||
Line = 'line',
|
||||
Ray = 'ray',
|
||||
Polyline = 'polyline',
|
||||
Rectangle = 'rectangle',
|
||||
Draw = 'draw',
|
||||
}
|
||||
|
||||
// Consider:
|
||||
|
@ -164,17 +168,17 @@ export interface PointerInfo {
|
|||
}
|
||||
|
||||
export enum Edge {
|
||||
Top = "top_edge",
|
||||
Right = "right_edge",
|
||||
Bottom = "bottom_edge",
|
||||
Left = "left_edge",
|
||||
Top = 'top_edge',
|
||||
Right = 'right_edge',
|
||||
Bottom = 'bottom_edge',
|
||||
Left = 'left_edge',
|
||||
}
|
||||
|
||||
export enum Corner {
|
||||
TopLeft = "top_left_corner",
|
||||
TopRight = "top_right_corner",
|
||||
BottomRight = "bottom_right_corner",
|
||||
BottomLeft = "bottom_left_corner",
|
||||
TopLeft = 'top_left_corner',
|
||||
TopRight = 'top_right_corner',
|
||||
BottomRight = 'bottom_right_corner',
|
||||
BottomLeft = 'bottom_left_corner',
|
||||
}
|
||||
|
||||
export interface Bounds {
|
||||
|
@ -262,10 +266,10 @@ export type IMonaco = typeof monaco
|
|||
export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
|
||||
|
||||
export enum ControlType {
|
||||
Number = "number",
|
||||
Vector = "vector",
|
||||
Text = "text",
|
||||
Select = "select",
|
||||
Number = 'number',
|
||||
Vector = 'vector',
|
||||
Text = 'text',
|
||||
Select = 'select',
|
||||
}
|
||||
|
||||
export interface BaseCodeControl {
|
||||
|
@ -296,7 +300,7 @@ export interface TextCodeControl extends BaseCodeControl {
|
|||
format?: (value: string) => string
|
||||
}
|
||||
|
||||
export interface SelectCodeControl<T extends string = "">
|
||||
export interface SelectCodeControl<T extends string = ''>
|
||||
extends BaseCodeControl {
|
||||
type: ControlType.Select
|
||||
value: T
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Vector from "lib/code/vector"
|
||||
import React from "react"
|
||||
import { Data, Bounds, Edge, Corner, Shape, ShapeStyles } from "types"
|
||||
import * as vec from "./vec"
|
||||
import _isMobile from "ismobilejs"
|
||||
import { getShapeUtils } from "lib/shape-utils"
|
||||
import Vector from 'lib/code/vector'
|
||||
import React from 'react'
|
||||
import { Data, Bounds, Edge, Corner, Shape, ShapeStyles } from 'types'
|
||||
import * as vec from './vec'
|
||||
import _isMobile from 'ismobilejs'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
|
||||
export function screenToWorld(point: number[], data: Data) {
|
||||
return vec.sub(vec.div(point, data.camera.zoom), data.camera.point)
|
||||
|
@ -132,7 +132,7 @@ export function getBezierCurveSegments(points: number[][], tension = 0.4) {
|
|||
cpoints: number[][] = [...points]
|
||||
|
||||
if (len < 2) {
|
||||
throw Error("Curve must have at least two points.")
|
||||
throw Error('Curve must have at least two points.')
|
||||
}
|
||||
|
||||
for (let i = 1; i < len - 1; i++) {
|
||||
|
@ -260,12 +260,12 @@ export function copyToClipboard(string: string) {
|
|||
navigator.clipboard.writeText(string)
|
||||
} catch (e) {
|
||||
try {
|
||||
textarea = document.createElement("textarea")
|
||||
textarea.setAttribute("position", "fixed")
|
||||
textarea.setAttribute("top", "0")
|
||||
textarea.setAttribute("readonly", "true")
|
||||
textarea.setAttribute("contenteditable", "true")
|
||||
textarea.style.position = "fixed" // prevent scroll from jumping to the bottom when focus is set.
|
||||
textarea = document.createElement('textarea')
|
||||
textarea.setAttribute('position', 'fixed')
|
||||
textarea.setAttribute('top', '0')
|
||||
textarea.setAttribute('readonly', 'true')
|
||||
textarea.setAttribute('contenteditable', 'true')
|
||||
textarea.style.position = 'fixed' // prevent scroll from jumping to the bottom when focus is set.
|
||||
textarea.value = string
|
||||
|
||||
document.body.appendChild(textarea)
|
||||
|
@ -281,7 +281,7 @@ export function copyToClipboard(string: string) {
|
|||
sel.addRange(range)
|
||||
|
||||
textarea.setSelectionRange(0, textarea.value.length)
|
||||
result = document.execCommand("copy")
|
||||
result = document.execCommand('copy')
|
||||
} catch (err) {
|
||||
result = null
|
||||
} finally {
|
||||
|
@ -549,7 +549,7 @@ export function arrsIntersect<T>(
|
|||
|
||||
export function getTouchDisplay() {
|
||||
return (
|
||||
"ontouchstart" in window ||
|
||||
'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0
|
||||
)
|
||||
|
@ -604,7 +604,7 @@ export function modulate(
|
|||
export function clamp(n: number, min: number): number
|
||||
export function clamp(n: number, min: number, max: number): number
|
||||
export function clamp(n: number, min: number, max?: number): number {
|
||||
return Math.max(min, typeof max !== "undefined" ? Math.min(n, max) : n)
|
||||
return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
|
||||
}
|
||||
|
||||
// CURVES
|
||||
|
@ -871,8 +871,8 @@ export async function postJsonToEndpoint(
|
|||
const d = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_BASE_API_URL}/api/${endpoint}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
)
|
||||
|
@ -962,7 +962,7 @@ export function getTransformAnchor(
|
|||
}
|
||||
|
||||
export function vectorToPoint(point: number[] | Vector | undefined) {
|
||||
if (typeof point === "undefined") {
|
||||
if (typeof point === 'undefined') {
|
||||
return [0, 0]
|
||||
}
|
||||
|
||||
|
@ -1062,7 +1062,7 @@ export function getRotatedCorners(b: Bounds, rotation: number) {
|
|||
|
||||
export function getTransformedBoundingBox(
|
||||
bounds: Bounds,
|
||||
handle: Corner | Edge | "center",
|
||||
handle: Corner | Edge | 'center',
|
||||
delta: number[],
|
||||
rotation = 0,
|
||||
isAspectRatioLocked = false
|
||||
|
@ -1076,7 +1076,7 @@ export function getTransformedBoundingBox(
|
|||
let [bx1, by1] = [bounds.maxX, bounds.maxY]
|
||||
|
||||
// If the drag is on the center, just translate the bounds.
|
||||
if (handle === "center") {
|
||||
if (handle === 'center') {
|
||||
return {
|
||||
minX: bx0 + delta[0],
|
||||
minY: by0 + delta[1],
|
||||
|
@ -1491,7 +1491,7 @@ export function forceIntegerChildIndices(shapes: Shape[]) {
|
|||
}
|
||||
}
|
||||
export function setZoomCSS(zoom: number) {
|
||||
document.documentElement.style.setProperty("--camera-zoom", zoom.toString())
|
||||
document.documentElement.style.setProperty('--camera-zoom', zoom.toString())
|
||||
}
|
||||
|
||||
export function getCurrent<T extends object>(source: T): T {
|
||||
|
@ -1539,7 +1539,7 @@ export function simplify(points: number[][], tolerance = 1) {
|
|||
}
|
||||
|
||||
export function getSvgPathFromStroke(stroke: number[][]) {
|
||||
if (!stroke.length) return ""
|
||||
if (!stroke.length) return ''
|
||||
|
||||
const d = stroke.reduce(
|
||||
(acc, [x0, y0], i, arr) => {
|
||||
|
@ -1547,9 +1547,9 @@ export function getSvgPathFromStroke(stroke: number[][]) {
|
|||
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
|
||||
return acc
|
||||
},
|
||||
["M", ...stroke[0], "Q"]
|
||||
['M', ...stroke[0], 'Q']
|
||||
)
|
||||
|
||||
d.push("Z")
|
||||
return d.join(" ")
|
||||
d.push('Z')
|
||||
return d.join(' ')
|
||||
}
|
||||
|
|
|
@ -483,6 +483,6 @@ export function nudge(A: number[], B: number[], d: number) {
|
|||
* @param a
|
||||
* @param n
|
||||
*/
|
||||
export function toPrecision(a: number[], n = 3) {
|
||||
export function toPrecision(a: number[], n = 4) {
|
||||
return [+a[0].toPrecision(n), +a[1].toPrecision(n)]
|
||||
}
|
||||
|
|
29
yarn.lock
29
yarn.lock
|
@ -1358,6 +1358,17 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-label@0.0.13":
|
||||
version "0.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.0.13.tgz#b71930fa16a2cf859296317436cb88e31efb8ecf"
|
||||
integrity sha512-csNElm8qA38pOHr772CXIvBXd/eCGaoAMImuLdawUxQNzwxQ4npd8lr/f9fi/4OLkgeNOVOqjsaVamiNmF/lIw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.0.5"
|
||||
"@radix-ui/react-id" "0.0.6"
|
||||
"@radix-ui/react-polymorphic" "0.0.11"
|
||||
"@radix-ui/react-primitive" "0.0.13"
|
||||
|
||||
"@radix-ui/react-menu@0.0.18":
|
||||
version "0.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.0.18.tgz#b36f7657eb6757c623ffc688c48a4781ffd82351"
|
||||
|
@ -1431,6 +1442,24 @@
|
|||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-polymorphic" "0.0.11"
|
||||
|
||||
"@radix-ui/react-radio-group@^0.0.16":
|
||||
version "0.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.0.16.tgz#10fc6e5c3102599cf422e9f6f8d2766088e602a1"
|
||||
integrity sha512-vOtgflNWcauSul+EvnPCxATdmPw7fb1cuqBJX07yJdjbrw1Iv5v/+d79fNyIwPR+KrkhP+uCMIBfF0gvo6K7ZQ==
|
||||
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-label" "0.0.13"
|
||||
"@radix-ui/react-polymorphic" "0.0.11"
|
||||
"@radix-ui/react-presence" "0.0.14"
|
||||
"@radix-ui/react-primitive" "0.0.13"
|
||||
"@radix-ui/react-roving-focus" "0.0.13"
|
||||
"@radix-ui/react-slot" "0.0.10"
|
||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||
|
||||
"@radix-ui/react-roving-focus@0.0.13":
|
||||
version "0.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.0.13.tgz#c72f503832577979c4caa9efcfd59140730c2f80"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue