Adds quick style selects, rewrites styling
This commit is contained in:
parent
815bf1109c
commit
7c768bddf5
45 changed files with 867 additions and 630 deletions
|
@ -75,7 +75,7 @@ export default function Canvas() {
|
||||||
<g ref={rGroup}>
|
<g ref={rGroup}>
|
||||||
<BoundsBg />
|
<BoundsBg />
|
||||||
<Page />
|
<Page />
|
||||||
<Selected />
|
{/* <Selected /> */}
|
||||||
<Bounds />
|
<Bounds />
|
||||||
<Handles />
|
<Handles />
|
||||||
<Brush />
|
<Brush />
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
|
import { memo } from 'react'
|
||||||
import { useSelector } from 'state'
|
import { useSelector } from 'state'
|
||||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||||
|
|
||||||
export default function Defs() {
|
export default function Defs() {
|
||||||
|
const zoom = useSelector((s) => s.data.camera.zoom)
|
||||||
|
|
||||||
const currentPageShapeIds = useSelector(({ data }) => {
|
const currentPageShapeIds = useSelector(({ data }) => {
|
||||||
return Object.values(getPage(data).shapes)
|
return Object.values(getPage(data).shapes)
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
.sort((a, b) => a.childIndex - b.childIndex)
|
||||||
|
@ -14,12 +17,15 @@ export default function Defs() {
|
||||||
{currentPageShapeIds.map((id) => (
|
{currentPageShapeIds.map((id) => (
|
||||||
<Def key={id} id={id} />
|
<Def key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
<filter id="expand">
|
||||||
|
<feMorphology operator="dilate" radius={2 / zoom} />
|
||||||
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Def({ id }: { id: string }) {
|
const Def = memo(({ id }: { id: string }) => {
|
||||||
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
||||||
if (!shape) return null
|
if (!shape) return null
|
||||||
return getShapeUtils(shape).render(shape)
|
return getShapeUtils(shape).render(shape)
|
||||||
}
|
})
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { useSelector } from 'state'
|
||||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import useShapeEvents from 'hooks/useShapeEvents'
|
import useShapeEvents from 'hooks/useShapeEvents'
|
||||||
import { useRef } from 'react'
|
import { memo, useRef } from 'react'
|
||||||
|
|
||||||
export default function Selected() {
|
export default function Selected() {
|
||||||
|
const selectedIds = useSelector((s) => s.data.selectedIds)
|
||||||
|
|
||||||
const currentPageShapeIds = useSelector(({ data }) => {
|
const currentPageShapeIds = useSelector(({ data }) => {
|
||||||
return Array.from(data.selectedIds.values())
|
return Array.from(data.selectedIds.values())
|
||||||
}, deepCompareArrays)
|
}, deepCompareArrays)
|
||||||
|
@ -17,13 +19,14 @@ export default function Selected() {
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
{currentPageShapeIds.map((id) => (
|
{currentPageShapeIds.map((id) => (
|
||||||
<ShapeOutline key={id} id={id} />
|
<ShapeOutline key={id} id={id} isSelected={selectedIds.has(id)} />
|
||||||
))}
|
))}
|
||||||
</g>
|
</g>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShapeOutline({ id }: { id: string }) {
|
export const ShapeOutline = memo(
|
||||||
|
({ id, isSelected }: { id: string; isSelected: boolean }) => {
|
||||||
const rIndicator = useRef<SVGUseElement>(null)
|
const rIndicator = useRef<SVGUseElement>(null)
|
||||||
|
|
||||||
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
||||||
|
@ -48,7 +51,8 @@ export function ShapeOutline({ id }: { id: string }) {
|
||||||
{...events}
|
{...events}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const SelectIndicator = styled('path', {
|
const SelectIndicator = styled('path', {
|
||||||
zStrokeWidth: 3,
|
zStrokeWidth: 3,
|
||||||
|
|
|
@ -5,12 +5,10 @@ import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import { getPage } from 'utils/utils'
|
import { getPage } from 'utils/utils'
|
||||||
import { DashStyle, ShapeStyles } from 'types'
|
import { DashStyle, ShapeStyles } from 'types'
|
||||||
import useShapeEvents from 'hooks/useShapeEvents'
|
import useShapeEvents from 'hooks/useShapeEvents'
|
||||||
import { shades, strokes } from 'lib/colors'
|
import { getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
|
function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
|
||||||
const isHovered = useSelector((state) => state.data.hoveredId === id)
|
const isSelected = useSelector((s) => s.values.selectedIds.has(id))
|
||||||
|
|
||||||
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
|
||||||
|
|
||||||
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
const shape = useSelector(({ data }) => getPage(data).shapes[id])
|
||||||
|
|
||||||
|
@ -25,58 +23,49 @@ function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
|
||||||
if (!shape) return null
|
if (!shape) return null
|
||||||
|
|
||||||
const center = getShapeUtils(shape).getCenter(shape)
|
const center = getShapeUtils(shape).getCenter(shape)
|
||||||
|
|
||||||
const transform = `
|
const transform = `
|
||||||
rotate(${shape.rotation * (180 / Math.PI)}, ${center})
|
rotate(${shape.rotation * (180 / Math.PI)}, ${center})
|
||||||
translate(${shape.point})
|
translate(${shape.point})
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const style = getShapeStyle(shape.style)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledGroup
|
<StyledGroup ref={rGroup} isSelected={isSelected} transform={transform}>
|
||||||
ref={rGroup}
|
|
||||||
isHovered={isHovered}
|
|
||||||
isSelected={isSelected}
|
|
||||||
transform={transform}
|
|
||||||
stroke={'red'}
|
|
||||||
strokeWidth={10}
|
|
||||||
>
|
|
||||||
{isSelecting && (
|
{isSelecting && (
|
||||||
<HoverIndicator
|
<HoverIndicator
|
||||||
as="use"
|
as="use"
|
||||||
href={'#' + id}
|
href={'#' + id}
|
||||||
strokeWidth={+shape.style.strokeWidth + 8}
|
strokeWidth={+style.strokeWidth + 4}
|
||||||
variant={shape.style.fill === 'none' ? 'hollow' : 'filled'}
|
variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'}
|
||||||
{...events}
|
{...events}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!shape.isHidden && (
|
{!shape.isHidden && <RealShape id={id} style={style} />}
|
||||||
<RealShape id={id} style={sanitizeStyle(shape.style)} />
|
|
||||||
)}
|
|
||||||
</StyledGroup>
|
</StyledGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const RealShape = memo(({ id, style }: { id: string; style: ShapeStyles }) => {
|
const RealShape = memo(
|
||||||
return (
|
({ id, style }: { id: string; style: ReturnType<typeof getShapeStyle> }) => {
|
||||||
<StyledShape
|
return <StyledShape as="use" href={'#' + id} {...style} />
|
||||||
as="use"
|
}
|
||||||
href={'#' + id}
|
)
|
||||||
{...style}
|
|
||||||
strokeDasharray={getDash(style.dash, +style.strokeWidth)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const StyledShape = styled('path', {
|
const StyledShape = styled('path', {
|
||||||
strokeLinecap: 'round',
|
strokeLinecap: 'round',
|
||||||
strokeLinejoin: 'round',
|
strokeLinejoin: 'round',
|
||||||
|
pointerEvents: 'none',
|
||||||
})
|
})
|
||||||
|
|
||||||
const HoverIndicator = styled('path', {
|
const HoverIndicator = styled('path', {
|
||||||
fill: 'transparent',
|
stroke: '$selected',
|
||||||
stroke: 'transparent',
|
|
||||||
strokeLinecap: 'round',
|
strokeLinecap: 'round',
|
||||||
strokeLinejoin: 'round',
|
strokeLinejoin: 'round',
|
||||||
transform: 'all .2s',
|
transform: 'all .2s',
|
||||||
|
fill: 'transparent',
|
||||||
|
filter: 'url(#expand)',
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
hollow: {
|
hollow: {
|
||||||
|
@ -90,52 +79,32 @@ const HoverIndicator = styled('path', {
|
||||||
})
|
})
|
||||||
|
|
||||||
const StyledGroup = styled('g', {
|
const StyledGroup = styled('g', {
|
||||||
pointerEvents: 'none',
|
|
||||||
[`& ${HoverIndicator}`]: {
|
[`& ${HoverIndicator}`]: {
|
||||||
opacity: '0',
|
opacity: '0',
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
isSelected: {
|
isSelected: {
|
||||||
true: {},
|
true: {
|
||||||
false: {},
|
|
||||||
},
|
|
||||||
isHovered: {
|
|
||||||
true: {},
|
|
||||||
false: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
compoundVariants: [
|
|
||||||
{
|
|
||||||
isSelected: true,
|
|
||||||
isHovered: true,
|
|
||||||
css: {
|
|
||||||
[`& ${HoverIndicator}`]: {
|
[`& ${HoverIndicator}`]: {
|
||||||
opacity: '.4',
|
opacity: '0.2',
|
||||||
stroke: '$selected',
|
},
|
||||||
|
[`&:hover ${HoverIndicator}`]: {
|
||||||
|
opacity: '0.3',
|
||||||
|
},
|
||||||
|
[`&:active ${HoverIndicator}`]: {
|
||||||
|
opacity: '0.3',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
false: {
|
||||||
{
|
|
||||||
isSelected: true,
|
|
||||||
isHovered: false,
|
|
||||||
css: {
|
|
||||||
[`& ${HoverIndicator}`]: {
|
[`& ${HoverIndicator}`]: {
|
||||||
opacity: '.2',
|
opacity: '0',
|
||||||
stroke: '$selected',
|
},
|
||||||
|
[`&:hover ${HoverIndicator}`]: {
|
||||||
|
opacity: '0.16',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
isSelected: false,
|
|
||||||
isHovered: true,
|
|
||||||
css: {
|
|
||||||
[`& ${HoverIndicator}`]: {
|
|
||||||
opacity: '.2',
|
|
||||||
stroke: '$selected',
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function Label({ text }: { text: string }) {
|
function Label({ text }: { text: string }) {
|
||||||
|
@ -154,25 +123,6 @@ function Label({ text }: { text: string }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDash(dash: DashStyle, s: number) {
|
|
||||||
switch (dash) {
|
|
||||||
case DashStyle.Solid: {
|
|
||||||
return 'none'
|
|
||||||
}
|
|
||||||
case DashStyle.Dashed: {
|
|
||||||
return `${s} ${s * 2}`
|
|
||||||
}
|
|
||||||
case DashStyle.Dotted: {
|
|
||||||
return `0 ${s * 1.5}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeStyle(style: ShapeStyles) {
|
|
||||||
const next = { ...style }
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
export { HoverIndicator }
|
export { HoverIndicator }
|
||||||
|
|
||||||
export default memo(Shape)
|
export default memo(Shape)
|
||||||
|
|
|
@ -119,29 +119,38 @@ export default function CodePanel() {
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<Panel.Layout>
|
<Panel.Layout>
|
||||||
<Panel.Header side="left">
|
<Panel.Header side="left">
|
||||||
<IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
|
||||||
|
>
|
||||||
<X />
|
<X />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<h3>Code</h3>
|
<h3>Code</h3>
|
||||||
<ButtonsGroup>
|
<ButtonsGroup>
|
||||||
<FontSizeButtons>
|
<FontSizeButtons>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size="small"
|
||||||
disabled={!local.isIn('editingCode')}
|
disabled={!local.isIn('editingCode')}
|
||||||
onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
|
onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
|
||||||
>
|
>
|
||||||
<ChevronUp />
|
<ChevronUp />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size="small"
|
||||||
disabled={!local.isIn('editingCode')}
|
disabled={!local.isIn('editingCode')}
|
||||||
onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
|
onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
|
||||||
>
|
>
|
||||||
<ChevronDown />
|
<ChevronDown />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</FontSizeButtons>
|
</FontSizeButtons>
|
||||||
<IconButton onClick={() => local.send('TOGGLED_DOCS')}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => local.send('TOGGLED_DOCS')}
|
||||||
|
>
|
||||||
<Info />
|
<Info />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size="small"
|
||||||
disabled={!local.isIn('editingCode')}
|
disabled={!local.isIn('editingCode')}
|
||||||
onClick={() => local.send('SAVED_CODE')}
|
onClick={() => local.send('SAVED_CODE')}
|
||||||
>
|
>
|
||||||
|
@ -169,7 +178,10 @@ export default function CodePanel() {
|
||||||
</Panel.Footer>
|
</Panel.Footer>
|
||||||
</Panel.Layout>
|
</Panel.Layout>
|
||||||
) : (
|
) : (
|
||||||
<IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
|
||||||
|
>
|
||||||
<Code />
|
<Code />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
import styled from "styles"
|
import styled from 'styles'
|
||||||
import React, { useEffect, useRef } from "react"
|
import React, { useEffect, useRef } from 'react'
|
||||||
import state, { useSelector } from "state"
|
import state, { useSelector } from 'state'
|
||||||
import { X, Code, PlayCircle } from "react-feather"
|
import { X, Code, PlayCircle } from 'react-feather'
|
||||||
import { IconButton } from "components/shared"
|
import { IconButton } from 'components/shared'
|
||||||
import * as Panel from "../panel"
|
import * as Panel from '../panel'
|
||||||
import Control from "./control"
|
import Control from './control'
|
||||||
import { deepCompareArrays } from "utils/utils"
|
import { deepCompareArrays } from 'utils/utils'
|
||||||
|
|
||||||
export default function ControlPanel() {
|
export default function ControlPanel() {
|
||||||
const rContainer = useRef<HTMLDivElement>(null)
|
const rContainer = useRef<HTMLDivElement>(null)
|
||||||
|
@ -21,7 +21,10 @@ export default function ControlPanel() {
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<Panel.Layout>
|
<Panel.Layout>
|
||||||
<Panel.Header>
|
<Panel.Header>
|
||||||
<IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => state.send('CLOSED_CODE_PANEL')}
|
||||||
|
>
|
||||||
<X />
|
<X />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<h3>Controls</h3>
|
<h3>Controls</h3>
|
||||||
|
@ -33,7 +36,10 @@ export default function ControlPanel() {
|
||||||
</ControlsList>
|
</ControlsList>
|
||||||
</Panel.Layout>
|
</Panel.Layout>
|
||||||
) : (
|
) : (
|
||||||
<IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => state.send('OPENED_CODE_PANEL')}
|
||||||
|
>
|
||||||
<Code />
|
<Code />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
@ -43,20 +49,20 @@ export default function ControlPanel() {
|
||||||
|
|
||||||
const ControlsList = styled(Panel.Content, {
|
const ControlsList = styled(Panel.Content, {
|
||||||
padding: 12,
|
padding: 12,
|
||||||
display: "grid",
|
display: 'grid',
|
||||||
gridTemplateColumns: "1fr 4fr",
|
gridTemplateColumns: '1fr 4fr',
|
||||||
gridAutoRows: "24px",
|
gridAutoRows: '24px',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
gridColumnGap: "8px",
|
gridColumnGap: '8px',
|
||||||
gridRowGap: "8px",
|
gridRowGap: '8px',
|
||||||
|
|
||||||
"& input": {
|
'& input': {
|
||||||
font: "$ui",
|
font: '$ui',
|
||||||
fontSize: "$1",
|
fontSize: '$1',
|
||||||
border: "1px solid $inputBorder",
|
border: '1px solid $inputBorder',
|
||||||
backgroundColor: "$input",
|
backgroundColor: '$input',
|
||||||
color: "$text",
|
color: '$text',
|
||||||
height: "100%",
|
height: '100%',
|
||||||
padding: "0px 6px",
|
padding: '0px 6px',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const Root = styled('div', {
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
zIndex: 200,
|
zIndex: 200,
|
||||||
border: '1px solid $panel',
|
border: '1px solid $panel',
|
||||||
boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
|
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
isOpen: {
|
isOpen: {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const IconButton = styled('button', {
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
padding: '0',
|
padding: '0',
|
||||||
margin: '0',
|
margin: '0',
|
||||||
display: 'flex',
|
display: 'grid',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
|
@ -15,6 +15,11 @@ export const IconButton = styled('button', {
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
'& > *': {
|
||||||
|
gridRow: 1,
|
||||||
|
gridColumn: 1,
|
||||||
|
},
|
||||||
|
|
||||||
'&:hover:not(:disabled)': {
|
'&:hover:not(:disabled)': {
|
||||||
backgroundColor: '$hover',
|
backgroundColor: '$hover',
|
||||||
},
|
},
|
||||||
|
@ -23,30 +28,28 @@ export const IconButton = styled('button', {
|
||||||
opacity: '0.5',
|
opacity: '0.5',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
'& > svg': {
|
'& > svg': {
|
||||||
height: '16px',
|
height: '16px',
|
||||||
width: '16px',
|
width: '16px',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
variants: {
|
|
||||||
size: {
|
|
||||||
small: {},
|
|
||||||
medium: {
|
medium: {
|
||||||
height: 44,
|
height: 44,
|
||||||
width: 44,
|
width: 44,
|
||||||
'& svg': {
|
'& > svg': {
|
||||||
height: 16,
|
height: '16px',
|
||||||
width: 16,
|
width: '16px',
|
||||||
strokeWidth: 0,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
large: {
|
large: {
|
||||||
height: 44,
|
height: 44,
|
||||||
width: 44,
|
width: 44,
|
||||||
'& svg': {
|
'& > svg': {
|
||||||
height: 24,
|
height: '24px',
|
||||||
width: 24,
|
width: '24px',
|
||||||
strokeWidth: 0,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -64,34 +64,58 @@ export default function AlignDistribute({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={alignLeft}>
|
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignLeft}>
|
||||||
<AlignLeftIcon />
|
<AlignLeftIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={alignCenterHorizontal}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={!hasTwoOrMore}
|
||||||
|
onClick={alignCenterHorizontal}
|
||||||
|
>
|
||||||
<AlignCenterHorizontallyIcon />
|
<AlignCenterHorizontallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={alignRight}>
|
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignRight}>
|
||||||
<AlignRightIcon />
|
<AlignRightIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={stretchHorizontally}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={!hasTwoOrMore}
|
||||||
|
onClick={stretchHorizontally}
|
||||||
|
>
|
||||||
<StretchHorizontallyIcon />
|
<StretchHorizontallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasThreeOrMore} onClick={distributeHorizontally}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={!hasThreeOrMore}
|
||||||
|
onClick={distributeHorizontally}
|
||||||
|
>
|
||||||
<SpaceEvenlyHorizontallyIcon />
|
<SpaceEvenlyHorizontallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={alignTop}>
|
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignTop}>
|
||||||
<AlignTopIcon />
|
<AlignTopIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={alignCenterVertical}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={!hasTwoOrMore}
|
||||||
|
onClick={alignCenterVertical}
|
||||||
|
>
|
||||||
<AlignCenterVerticallyIcon />
|
<AlignCenterVerticallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={alignBottom}>
|
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignBottom}>
|
||||||
<AlignBottomIcon />
|
<AlignBottomIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasTwoOrMore} onClick={stretchVertically}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={!hasTwoOrMore}
|
||||||
|
onClick={stretchVertically}
|
||||||
|
>
|
||||||
<StretchVerticallyIcon />
|
<StretchVerticallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton disabled={!hasThreeOrMore} onClick={distributeVertically}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disabled={!hasThreeOrMore}
|
||||||
|
onClick={distributeVertically}
|
||||||
|
>
|
||||||
<SpaceEvenlyVerticallyIcon />
|
<SpaceEvenlyVerticallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
28
components/style-panel/color-content.tsx
Normal file
28
components/style-panel/color-content.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { IconButton } from 'components/shared'
|
||||||
|
import { strokes } from 'lib/shape-styles'
|
||||||
|
import { ColorStyle } from 'types'
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { Square } from 'react-feather'
|
||||||
|
import styled from 'styles'
|
||||||
|
import { DropdownContent } from './shared'
|
||||||
|
|
||||||
|
export default function ColorContent({
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
onChange: (color: ColorStyle) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownContent sideOffset={0} side="bottom">
|
||||||
|
{Object.keys(strokes).map((color: ColorStyle) => (
|
||||||
|
<DropdownMenu.DropdownMenuItem
|
||||||
|
as={IconButton}
|
||||||
|
key={color}
|
||||||
|
title={color}
|
||||||
|
onSelect={() => onChange(color)}
|
||||||
|
>
|
||||||
|
<Square fill={strokes[color]} stroke="none" size="22" />
|
||||||
|
</DropdownMenu.DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownContent>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,129 +1,25 @@
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { strokes } from 'lib/shape-styles'
|
||||||
|
import { ColorStyle } from 'types'
|
||||||
|
import { IconWrapper, RowButton } from './shared'
|
||||||
import { Square } from 'react-feather'
|
import { Square } from 'react-feather'
|
||||||
import styled from 'styles'
|
import ColorContent from './color-content'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
colors: Record<string, string>
|
color: ColorStyle
|
||||||
onChange: (color: string) => void
|
onChange: (color: ColorStyle) => void
|
||||||
children: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ColorPicker({ colors, onChange, children }: Props) {
|
export default function ColorPicker({ color, onChange }: Props) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
{children}
|
<DropdownMenu.Trigger as={RowButton}>
|
||||||
<Colors sideOffset={4}>
|
<label htmlFor="color">Color</label>
|
||||||
{Object.entries(colors).map(([name, color]) => (
|
<IconWrapper>
|
||||||
<ColorButton key={name} title={name} onSelect={() => onChange(name)}>
|
<Square fill={strokes[color]} />
|
||||||
<ColorIcon color={color} />
|
</IconWrapper>
|
||||||
</ColorButton>
|
</DropdownMenu.Trigger>
|
||||||
))}
|
<ColorContent onChange={onChange} />
|
||||||
</Colors>
|
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ColorIcon({ color }: { color: string }) {
|
|
||||||
return (
|
|
||||||
<Square fill={color} strokeDasharray={color === 'none' ? '2, 3' : 'none'} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Colors = styled(DropdownMenu.Content, {
|
|
||||||
display: 'grid',
|
|
||||||
padding: 4,
|
|
||||||
gridTemplateColumns: 'repeat(6, 1fr)',
|
|
||||||
border: '1px solid $border',
|
|
||||||
backgroundColor: '$panel',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const ColorButton = styled(DropdownMenu.Item, {
|
|
||||||
position: 'relative',
|
|
||||||
cursor: 'pointer',
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
border: 'none',
|
|
||||||
padding: 'none',
|
|
||||||
background: 'none',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
|
|
||||||
'&::before': {
|
|
||||||
content: "''",
|
|
||||||
position: 'absolute',
|
|
||||||
top: 4,
|
|
||||||
left: 4,
|
|
||||||
right: 4,
|
|
||||||
bottom: 4,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
zIndex: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
'&:hover::before': {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
'& svg': {
|
|
||||||
position: 'relative',
|
|
||||||
stroke: 'rgba(0,0,0,.2)',
|
|
||||||
strokeWidth: 1,
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export 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',
|
|
||||||
|
|
||||||
'&::before': {
|
|
||||||
content: "''",
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
zIndex: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
'&:hover::before': {
|
|
||||||
backgroundColor: '$hover',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
'& label': {
|
|
||||||
fontFamily: '$ui',
|
|
||||||
fontSize: '$2',
|
|
||||||
fontWeight: '$1',
|
|
||||||
margin: 0,
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
'& svg': {
|
|
||||||
position: 'relative',
|
|
||||||
stroke: 'rgba(0,0,0,.2)',
|
|
||||||
strokeWidth: 1,
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
variants: {
|
|
||||||
size: {
|
|
||||||
icon: {
|
|
||||||
padding: '4px ',
|
|
||||||
width: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import { Group, RadioItem } from './shared'
|
import {
|
||||||
|
Group,
|
||||||
|
Item,
|
||||||
|
DashDashedIcon,
|
||||||
|
DashDottedIcon,
|
||||||
|
DashSolidIcon,
|
||||||
|
} from './shared'
|
||||||
|
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||||
import { DashStyle } from 'types'
|
import { DashStyle } from 'types'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import { ChangeEvent } from 'react'
|
import { ChangeEvent } from 'react'
|
||||||
|
@ -16,49 +23,27 @@ interface Props {
|
||||||
export default function DashPicker({ dash }: Props) {
|
export default function DashPicker({ dash }: Props) {
|
||||||
return (
|
return (
|
||||||
<Group name="Dash" onValueChange={handleChange}>
|
<Group name="Dash" onValueChange={handleChange}>
|
||||||
<RadioItem value={DashStyle.Solid} isActive={dash === DashStyle.Solid}>
|
<Item
|
||||||
|
as={RadioGroup.RadioGroupItem}
|
||||||
|
value={DashStyle.Solid}
|
||||||
|
isActive={dash === DashStyle.Solid}
|
||||||
|
>
|
||||||
<DashSolidIcon />
|
<DashSolidIcon />
|
||||||
</RadioItem>
|
</Item>
|
||||||
<RadioItem value={DashStyle.Dashed} isActive={dash === DashStyle.Dashed}>
|
<Item
|
||||||
|
as={RadioGroup.RadioGroupItem}
|
||||||
|
value={DashStyle.Dashed}
|
||||||
|
isActive={dash === DashStyle.Dashed}
|
||||||
|
>
|
||||||
<DashDashedIcon />
|
<DashDashedIcon />
|
||||||
</RadioItem>
|
</Item>
|
||||||
<RadioItem value={DashStyle.Dotted} isActive={dash === DashStyle.Dotted}>
|
<Item
|
||||||
|
as={RadioGroup.RadioGroupItem}
|
||||||
|
value={DashStyle.Dotted}
|
||||||
|
isActive={dash === DashStyle.Dotted}
|
||||||
|
>
|
||||||
<DashDottedIcon />
|
<DashDottedIcon />
|
||||||
</RadioItem>
|
</Item>
|
||||||
</Group>
|
</Group>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DashSolidIcon() {
|
|
||||||
return (
|
|
||||||
<svg width="16" height="16">
|
|
||||||
<path d="M 3,8 L 13,8" strokeWidth={3} strokeLinecap="round" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DashDashedIcon() {
|
|
||||||
return (
|
|
||||||
<svg width="16" height="16">
|
|
||||||
<path
|
|
||||||
d="M 2,8 L 14,8"
|
|
||||||
strokeWidth={3}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeDasharray="4 4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DashDottedIcon() {
|
|
||||||
return (
|
|
||||||
<svg width="16" height="16">
|
|
||||||
<path
|
|
||||||
d="M 3,8 L 14,8"
|
|
||||||
strokeWidth={3}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeDasharray="1 4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
28
components/style-panel/is-filled-picker.tsx
Normal file
28
components/style-panel/is-filled-picker.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||||
|
import { CheckIcon } from '@radix-ui/react-icons'
|
||||||
|
import { strokes } from 'lib/shape-styles'
|
||||||
|
import { Square } from 'react-feather'
|
||||||
|
import { IconWrapper, RowButton } from './shared'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isFilled: boolean
|
||||||
|
onChange: (isFilled: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IsFilledPicker({ isFilled, onChange }: Props) {
|
||||||
|
return (
|
||||||
|
<Checkbox.Root
|
||||||
|
as={RowButton}
|
||||||
|
checked={isFilled}
|
||||||
|
onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
onChange(e.currentTarget.checked)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<label htmlFor="fill">Fill</label>
|
||||||
|
<IconWrapper>
|
||||||
|
{isFilled || <Square stroke={strokes.Black} />}
|
||||||
|
<Checkbox.Indicator as={CheckIcon} />
|
||||||
|
</IconWrapper>
|
||||||
|
</Checkbox.Root>
|
||||||
|
)
|
||||||
|
}
|
21
components/style-panel/quick-color-select.tsx
Normal file
21
components/style-panel/quick-color-select.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { IconButton } from 'components/shared'
|
||||||
|
import { strokes } from 'lib/shape-styles'
|
||||||
|
import { Square } from 'react-feather'
|
||||||
|
import state, { useSelector } from 'state'
|
||||||
|
import ColorContent from './color-content'
|
||||||
|
|
||||||
|
export default function QuickColorSelect() {
|
||||||
|
const color = useSelector((s) => s.values.selectedStyle.color)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger as={IconButton} title="color">
|
||||||
|
<Square fill={strokes[color]} stroke={strokes[color]} />
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<ColorContent
|
||||||
|
onChange={(color) => state.send('CHANGED_STYLE', { color })}
|
||||||
|
/>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
)
|
||||||
|
}
|
52
components/style-panel/quick-dash-select.tsx
Normal file
52
components/style-panel/quick-dash-select.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { IconButton } from 'components/shared'
|
||||||
|
import state, { useSelector } from 'state'
|
||||||
|
import { DashStyle } from 'types'
|
||||||
|
import {
|
||||||
|
DropdownContent,
|
||||||
|
Item,
|
||||||
|
DashDottedIcon,
|
||||||
|
DashSolidIcon,
|
||||||
|
DashDashedIcon,
|
||||||
|
} from './shared'
|
||||||
|
|
||||||
|
const dashes = {
|
||||||
|
[DashStyle.Solid]: <DashSolidIcon />,
|
||||||
|
[DashStyle.Dashed]: <DashDashedIcon />,
|
||||||
|
[DashStyle.Dotted]: <DashDottedIcon />,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QuickdashSelect() {
|
||||||
|
const dash = useSelector((s) => s.values.selectedStyle.dash)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger as={IconButton} title="dash">
|
||||||
|
{dashes[dash]}
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownContent direction="vertical">
|
||||||
|
<DashItem isActive={dash === DashStyle.Solid} dash={DashStyle.Solid} />
|
||||||
|
<DashItem
|
||||||
|
isActive={dash === DashStyle.Dashed}
|
||||||
|
dash={DashStyle.Dashed}
|
||||||
|
/>
|
||||||
|
<DashItem
|
||||||
|
isActive={dash === DashStyle.Dotted}
|
||||||
|
dash={DashStyle.Dotted}
|
||||||
|
/>
|
||||||
|
</DropdownContent>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DashItem({ dash, isActive }: { isActive: boolean; dash: DashStyle }) {
|
||||||
|
return (
|
||||||
|
<Item
|
||||||
|
as={DropdownMenu.DropdownMenuItem}
|
||||||
|
isActive={isActive}
|
||||||
|
onSelect={() => state.send('CHANGED_STYLE', { dash })}
|
||||||
|
>
|
||||||
|
{dashes[dash]}
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
|
}
|
44
components/style-panel/quick-size-select.tsx
Normal file
44
components/style-panel/quick-size-select.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { IconButton } from 'components/shared'
|
||||||
|
import { Circle } from 'react-feather'
|
||||||
|
import state, { useSelector } from 'state'
|
||||||
|
import { SizeStyle } from 'types'
|
||||||
|
import { DropdownContent, Item } from './shared'
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
[SizeStyle.Small]: 6,
|
||||||
|
[SizeStyle.Medium]: 12,
|
||||||
|
[SizeStyle.Large]: 22,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QuickSizeSelect() {
|
||||||
|
const size = useSelector((s) => s.values.selectedStyle.size)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger as={IconButton} title="size">
|
||||||
|
<Circle size={sizes[size]} stroke="none" fill="currentColor" />
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownContent direction="vertical">
|
||||||
|
<SizeItem isActive={size === SizeStyle.Small} size={SizeStyle.Small} />
|
||||||
|
<SizeItem
|
||||||
|
isActive={size === SizeStyle.Medium}
|
||||||
|
size={SizeStyle.Medium}
|
||||||
|
/>
|
||||||
|
<SizeItem isActive={size === SizeStyle.Large} size={SizeStyle.Large} />
|
||||||
|
</DropdownContent>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SizeItem({ size, isActive }: { isActive: boolean; size: SizeStyle }) {
|
||||||
|
return (
|
||||||
|
<Item
|
||||||
|
as={DropdownMenu.DropdownMenuItem}
|
||||||
|
isActive={isActive}
|
||||||
|
onSelect={() => state.send('CHANGED_STYLE', { size })}
|
||||||
|
>
|
||||||
|
<Circle size={sizes[size]} />
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||||
import * as Panel from '../panel'
|
import * as Panel from '../panel'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
|
@ -27,7 +28,7 @@ export const Group = styled(RadioGroup.Root, {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const RadioItem = styled(RadioGroup.Item, {
|
export const Item = styled('button', {
|
||||||
height: '32px',
|
height: '32px',
|
||||||
width: '32px',
|
width: '32px',
|
||||||
backgroundColor: '$panel',
|
backgroundColor: '$panel',
|
||||||
|
@ -61,16 +62,158 @@ export const RadioItem = styled(RadioGroup.Item, {
|
||||||
'& svg': {
|
'& svg': {
|
||||||
fill: '$text',
|
fill: '$text',
|
||||||
stroke: '$text',
|
stroke: '$text',
|
||||||
strokeWidth: '0',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false: {
|
false: {
|
||||||
'& svg': {
|
'& svg': {
|
||||||
fill: '$inactive',
|
fill: '$inactive',
|
||||||
stroke: '$inactive',
|
stroke: '$inactive',
|
||||||
strokeWidth: '0',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const RowButton = styled('button', {
|
||||||
|
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': {
|
||||||
|
content: "''",
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: -1,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:hover::before': {
|
||||||
|
backgroundColor: '$hover',
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
'& label': {
|
||||||
|
fontFamily: '$ui',
|
||||||
|
fontSize: '$2',
|
||||||
|
fontWeight: '$1',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'& svg': {
|
||||||
|
position: 'relative',
|
||||||
|
stroke: 'rgba(0,0,0,.2)',
|
||||||
|
strokeWidth: 1,
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
icon: {
|
||||||
|
padding: '4px ',
|
||||||
|
width: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const IconWrapper = styled('div', {
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '4px',
|
||||||
|
marginRight: '1px',
|
||||||
|
display: 'grid',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
outline: 'none',
|
||||||
|
border: 'none',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
cursor: 'pointer',
|
||||||
|
|
||||||
|
'& svg': {
|
||||||
|
height: 22,
|
||||||
|
width: 22,
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
'& > *': {
|
||||||
|
gridRow: 1,
|
||||||
|
gridColumn: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DropdownContent = styled(DropdownMenu.Content, {
|
||||||
|
display: 'grid',
|
||||||
|
padding: 4,
|
||||||
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||||
|
backgroundColor: '$panel',
|
||||||
|
borderRadius: 4,
|
||||||
|
border: '1px solid $panel',
|
||||||
|
boxShadow: '0px 2px 4px rgba(0,0,0,.28)',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
direction: {
|
||||||
|
vertical: {
|
||||||
|
gridTemplateColumns: '1fr',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function DashSolidIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="24" height="24" stroke="currentColor">
|
||||||
|
<circle
|
||||||
|
cx={12}
|
||||||
|
cy={12}
|
||||||
|
r={8}
|
||||||
|
fill="none"
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DashDashedIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="24" height="24" stroke="currentColor">
|
||||||
|
<circle
|
||||||
|
cx={12}
|
||||||
|
cy={12}
|
||||||
|
r={8}
|
||||||
|
fill="none"
|
||||||
|
strokeWidth={2.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray={50.26548 * 0.1}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}`
|
||||||
|
|
||||||
|
export function DashDottedIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="24" height="24" stroke="currentColor">
|
||||||
|
<circle
|
||||||
|
cx={12}
|
||||||
|
cy={12}
|
||||||
|
r={8}
|
||||||
|
fill="none"
|
||||||
|
strokeWidth={2.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray={dottedDasharray}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
40
components/style-panel/size-picker.tsx
Normal file
40
components/style-panel/size-picker.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Group, Item } from './shared'
|
||||||
|
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||||
|
import { ChangeEvent } from 'react'
|
||||||
|
import { Circle } from 'react-feather'
|
||||||
|
import state from 'state'
|
||||||
|
import { SizeStyle } from 'types'
|
||||||
|
|
||||||
|
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||||
|
state.send('CHANGED_STYLE', {
|
||||||
|
size: e.currentTarget.value as SizeStyle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SizePicker({ size }: { size: SizeStyle }) {
|
||||||
|
return (
|
||||||
|
<Group name="width" onValueChange={handleChange}>
|
||||||
|
<Item
|
||||||
|
as={RadioGroup.Item}
|
||||||
|
value={SizeStyle.Small}
|
||||||
|
isActive={size === SizeStyle.Small}
|
||||||
|
>
|
||||||
|
<Circle size={6} />
|
||||||
|
</Item>
|
||||||
|
<Item
|
||||||
|
as={RadioGroup.Item}
|
||||||
|
value={SizeStyle.Medium}
|
||||||
|
isActive={size === SizeStyle.Medium}
|
||||||
|
>
|
||||||
|
<Circle size={12} />
|
||||||
|
</Item>
|
||||||
|
<Item
|
||||||
|
as={RadioGroup.Item}
|
||||||
|
value={SizeStyle.Large}
|
||||||
|
isActive={size === SizeStyle.Large}
|
||||||
|
>
|
||||||
|
<Circle size={22} />
|
||||||
|
</Item>
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
}
|
|
@ -4,13 +4,12 @@ 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 { Trash2, X } from 'react-feather'
|
import { ChevronDown, Square, Trash2, X } from 'react-feather'
|
||||||
import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
|
import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
|
||||||
import { shades, fills, strokes } from 'lib/colors'
|
import { strokes } from 'lib/shape-styles'
|
||||||
import ColorPicker, { ColorIcon, CurrentColor } from './color-picker'
|
|
||||||
import AlignDistribute from './align-distribute'
|
import AlignDistribute from './align-distribute'
|
||||||
import { MoveType, ShapeStyles } from 'types'
|
import { MoveType } from 'types'
|
||||||
import WidthPicker from './width-picker'
|
import SizePicker from './size-picker'
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
|
@ -28,15 +27,14 @@ import {
|
||||||
RotateCounterClockwiseIcon,
|
RotateCounterClockwiseIcon,
|
||||||
} from '@radix-ui/react-icons'
|
} from '@radix-ui/react-icons'
|
||||||
import DashPicker from './dash-picker'
|
import DashPicker from './dash-picker'
|
||||||
|
import QuickColorSelect from './quick-color-select'
|
||||||
const fillColors = { ...shades, ...fills }
|
import ColorContent from './color-content'
|
||||||
const strokeColors = { ...shades, ...strokes }
|
import { RowButton, IconWrapper } from './shared'
|
||||||
const getFillColor = (color: string) => {
|
import ColorPicker from './color-picker'
|
||||||
if (shades[color]) {
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
return '#fff'
|
import IsFilledPicker from './is-filled-picker'
|
||||||
}
|
import QuickSizeSelect from './quick-size-select'
|
||||||
return fillColors[color]
|
import QuickdashSelect from './quick-dash-select'
|
||||||
}
|
|
||||||
|
|
||||||
export default function StylePanel() {
|
export default function StylePanel() {
|
||||||
const rContainer = useRef<HTMLDivElement>(null)
|
const rContainer = useRef<HTMLDivElement>(null)
|
||||||
|
@ -48,12 +46,15 @@ export default function StylePanel() {
|
||||||
<SelectedShapeStyles />
|
<SelectedShapeStyles />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<QuickColorSelect prop="stroke" colors={strokeColors} />
|
<QuickColorSelect />
|
||||||
|
<QuickSizeSelect />
|
||||||
|
<QuickdashSelect />
|
||||||
<IconButton
|
<IconButton
|
||||||
title="Style"
|
title="Style"
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
||||||
>
|
>
|
||||||
<DotsVerticalIcon />
|
<ChevronDown />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -61,32 +62,11 @@ export default function StylePanel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function QuickColorSelect({
|
|
||||||
prop,
|
|
||||||
colors,
|
|
||||||
}: {
|
|
||||||
prop: ShapeStyles['fill'] | ShapeStyles['stroke']
|
|
||||||
colors: Record<string, string>
|
|
||||||
}) {
|
|
||||||
const value = useSelector((s) => s.values.selectedStyle[prop])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ColorPicker
|
|
||||||
colors={colors}
|
|
||||||
onChange={(color) => state.send('CHANGED_STYLE', { [prop]: color })}
|
|
||||||
>
|
|
||||||
<CurrentColor size="icon" title={prop}>
|
|
||||||
<ColorIcon color={value} />
|
|
||||||
</CurrentColor>
|
|
||||||
</ColorPicker>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This panel is going to be hard to keep cool, as we're selecting computed
|
// This panel is going to be hard to keep cool, as we're selecting computed
|
||||||
// information, based on the user's current selection. We might have to keep
|
// information, based on the user's current selection. We might have to keep
|
||||||
// track of this data manually within our state.
|
// track of this data manually within our state.
|
||||||
|
|
||||||
function SelectedShapeStyles({}: {}) {
|
function SelectedShapeStyles() {
|
||||||
const selectedIds = useSelector(
|
const selectedIds = useSelector(
|
||||||
(s) => Array.from(s.data.selectedIds.values()),
|
(s) => Array.from(s.data.selectedIds.values()),
|
||||||
deepCompareArrays
|
deepCompareArrays
|
||||||
|
@ -115,42 +95,25 @@ function SelectedShapeStyles({}: {}) {
|
||||||
<Panel.Layout>
|
<Panel.Layout>
|
||||||
<Panel.Header side="right">
|
<Panel.Header side="right">
|
||||||
<h3>Style</h3>
|
<h3>Style</h3>
|
||||||
<IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
||||||
|
>
|
||||||
<X />
|
<X />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Panel.Header>
|
</Panel.Header>
|
||||||
<Content>
|
<Content>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
colors={strokeColors}
|
color={commonStyle.color}
|
||||||
onChange={(color) =>
|
onChange={(color) => state.send('CHANGED_STYLE', { color })}
|
||||||
state.send('CHANGED_STYLE', {
|
/>
|
||||||
stroke: strokeColors[color],
|
<IsFilledPicker
|
||||||
fill: getFillColor(color),
|
isFilled={commonStyle.isFilled}
|
||||||
})
|
onChange={(isFilled) => state.send('CHANGED_STYLE', { isFilled })}
|
||||||
}
|
/>
|
||||||
>
|
|
||||||
<CurrentColor>
|
|
||||||
<label>Color</label>
|
|
||||||
<ColorIcon color={commonStyle.stroke} />
|
|
||||||
</CurrentColor>
|
|
||||||
</ColorPicker>
|
|
||||||
{/* <Row>
|
|
||||||
<label htmlFor="filled">Filled</label>
|
|
||||||
<StyledCheckbox
|
|
||||||
checked={commonStyle.isFilled}
|
|
||||||
onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
console.log(e.target.value)
|
|
||||||
state.send('CHANGED_STYLE', {
|
|
||||||
isFilled: e.target.value === 'on',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Checkbox.Indicator as={CheckIcon} />
|
|
||||||
</StyledCheckbox>
|
|
||||||
</Row>*/}
|
|
||||||
<Row>
|
<Row>
|
||||||
<label htmlFor="width">Width</label>
|
<label htmlFor="size">Size</label>
|
||||||
<WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
|
<SizePicker size={commonStyle.size} />
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<label htmlFor="dash">Dash</label>
|
<label htmlFor="dash">Dash</label>
|
||||||
|
@ -159,30 +122,35 @@ function SelectedShapeStyles({}: {}) {
|
||||||
<ButtonsRow>
|
<ButtonsRow>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('DUPLICATED')}
|
onClick={() => state.send('DUPLICATED')}
|
||||||
>
|
>
|
||||||
<CopyIcon />
|
<CopyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('ROTATED_CCW')}
|
onClick={() => state.send('ROTATED_CCW')}
|
||||||
>
|
>
|
||||||
<RotateCounterClockwiseIcon />
|
<RotateCounterClockwiseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
|
onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
|
||||||
>
|
>
|
||||||
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
|
onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
|
||||||
>
|
>
|
||||||
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
|
onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
|
||||||
>
|
>
|
||||||
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
||||||
|
@ -191,30 +159,35 @@ function SelectedShapeStyles({}: {}) {
|
||||||
<ButtonsRow>
|
<ButtonsRow>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
|
onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
|
||||||
>
|
>
|
||||||
<PinBottomIcon />
|
<PinBottomIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.Backward })}
|
onClick={() => state.send('MOVED', { type: MoveType.Backward })}
|
||||||
>
|
>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.Forward })}
|
onClick={() => state.send('MOVED', { type: MoveType.Forward })}
|
||||||
>
|
>
|
||||||
<ArrowUpIcon />
|
<ArrowUpIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
|
onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
|
||||||
>
|
>
|
||||||
<PinTopIcon />
|
<PinTopIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
size="small"
|
||||||
onClick={() => state.send('DELETED')}
|
onClick={() => state.send('DELETED')}
|
||||||
>
|
>
|
||||||
<Trash2 />
|
<Trash2 />
|
||||||
|
@ -236,7 +209,7 @@ const StylePanelRoot = styled(Panel.Root, {
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
border: '1px solid $panel',
|
border: '1px solid $panel',
|
||||||
boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
|
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
|
@ -262,7 +235,6 @@ const Row = styled('div', {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
background: 'none',
|
background: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
cursor: 'pointer',
|
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
@ -293,22 +265,3 @@ const ButtonsRow = styled('div', {
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
padding: 4,
|
padding: 4,
|
||||||
})
|
})
|
||||||
|
|
||||||
const StyledCheckbox = styled(Checkbox.Root, {
|
|
||||||
appearance: 'none',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
padding: 0,
|
|
||||||
boxShadow: 'inset 0 0 0 1px gainsboro',
|
|
||||||
width: 15,
|
|
||||||
height: 15,
|
|
||||||
borderRadius: 2,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
|
|
||||||
'&:focus': {
|
|
||||||
outline: 'none',
|
|
||||||
boxShadow: 'inset 0 0 0 1px dodgerblue, 0 0 0 1px dodgerblue',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { Group, RadioItem } from './shared'
|
|
||||||
import { ChangeEvent } from 'react'
|
|
||||||
import { Circle } from 'react-feather'
|
|
||||||
import state from 'state'
|
|
||||||
|
|
||||||
function handleChange(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={handleChange}>
|
|
||||||
<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={22} />
|
|
||||||
</RadioItem>
|
|
||||||
</Group>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -106,7 +106,7 @@ export default function ToolsPanel() {
|
||||||
>
|
>
|
||||||
<CircleIcon />
|
<CircleIcon />
|
||||||
</IconButton> */}
|
</IconButton> */}
|
||||||
<IconButton
|
{/* <IconButton
|
||||||
name={ShapeType.Line}
|
name={ShapeType.Line}
|
||||||
size={{ '@sm': 'small', '@md': 'large' }}
|
size={{ '@sm': 'small', '@md': 'large' }}
|
||||||
onClick={selectLineTool}
|
onClick={selectLineTool}
|
||||||
|
@ -129,7 +129,7 @@ export default function ToolsPanel() {
|
||||||
isActive={activeTool === ShapeType.Dot}
|
isActive={activeTool === ShapeType.Dot}
|
||||||
>
|
>
|
||||||
<DotIcon />
|
<DotIcon />
|
||||||
</IconButton>
|
</IconButton> */}
|
||||||
</Container>
|
</Container>
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { CircleShape, ShapeType } from 'types'
|
import { CircleShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Circle extends CodeShape<CircleShape> {
|
export default class Circle extends CodeShape<CircleShape> {
|
||||||
constructor(props = {} as Partial<CircleShape>) {
|
constructor(props = {} as Partial<CircleShape>) {
|
||||||
|
@ -20,11 +21,7 @@ export default class Circle extends CodeShape<CircleShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { DotShape, ShapeType } from 'types'
|
import { DotShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Dot extends CodeShape<DotShape> {
|
export default class Dot extends CodeShape<DotShape> {
|
||||||
constructor(props = {} as Partial<DotShape>) {
|
constructor(props = {} as Partial<DotShape>) {
|
||||||
|
@ -19,12 +20,12 @@ export default class Dot extends CodeShape<DotShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
|
style: {
|
||||||
|
...defaultStyle,
|
||||||
|
...props.style,
|
||||||
|
isFilled: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { EllipseShape, ShapeType } from 'types'
|
import { EllipseShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Ellipse extends CodeShape<EllipseShape> {
|
export default class Ellipse extends CodeShape<EllipseShape> {
|
||||||
constructor(props = {} as Partial<EllipseShape>) {
|
constructor(props = {} as Partial<EllipseShape>) {
|
||||||
|
@ -21,11 +22,7 @@ export default class Ellipse extends CodeShape<EllipseShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { LineShape, ShapeType } from 'types'
|
import { LineShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Line extends CodeShape<LineShape> {
|
export default class Line extends CodeShape<LineShape> {
|
||||||
constructor(props = {} as Partial<LineShape>) {
|
constructor(props = {} as Partial<LineShape>) {
|
||||||
|
@ -21,12 +22,12 @@ export default class Line extends CodeShape<LineShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
|
style: {
|
||||||
|
...defaultStyle,
|
||||||
|
...props.style,
|
||||||
|
isFilled: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { PolylineShape, ShapeType } from 'types'
|
import { PolylineShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Polyline extends CodeShape<PolylineShape> {
|
export default class Polyline extends CodeShape<PolylineShape> {
|
||||||
constructor(props = {} as Partial<PolylineShape>) {
|
constructor(props = {} as Partial<PolylineShape>) {
|
||||||
|
@ -21,11 +22,7 @@ export default class Polyline extends CodeShape<PolylineShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: 'none',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { RayShape, ShapeType } from 'types'
|
import { RayShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Ray extends CodeShape<RayShape> {
|
export default class Ray extends CodeShape<RayShape> {
|
||||||
constructor(props = {} as Partial<RayShape>) {
|
constructor(props = {} as Partial<RayShape>) {
|
||||||
|
@ -21,12 +22,12 @@ export default class Ray extends CodeShape<RayShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
|
style: {
|
||||||
|
...defaultStyle,
|
||||||
|
...props.style,
|
||||||
|
isFilled: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { RectangleShape, ShapeType } from 'types'
|
import { RectangleShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
export default class Rectangle extends CodeShape<RectangleShape> {
|
export default class Rectangle extends CodeShape<RectangleShape> {
|
||||||
constructor(props = {} as Partial<RectangleShape>) {
|
constructor(props = {} as Partial<RectangleShape>) {
|
||||||
|
@ -22,11 +23,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
export const shades = {
|
|
||||||
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)',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fills = {
|
|
||||||
lime: 'rgba(243, 252, 227, 1.000)',
|
|
||||||
green: 'rgba(235, 251, 238, 1.000)',
|
|
||||||
teal: 'rgba(230, 252, 245, 1.000)',
|
|
||||||
cyan: 'rgba(227, 250, 251, 1.000)',
|
|
||||||
blue: 'rgba(231, 245, 255, 1.000)',
|
|
||||||
indigo: 'rgba(237, 242, 255, 1.000)',
|
|
||||||
violet: 'rgba(242, 240, 255, 1.000)',
|
|
||||||
grape: 'rgba(249, 240, 252, 1.000)',
|
|
||||||
pink: 'rgba(254, 241, 246, 1.000)',
|
|
||||||
red: 'rgba(255, 245, 245, 1.000)',
|
|
||||||
orange: 'rgba(255, 244, 229, 1.000)',
|
|
||||||
yellow: 'rgba(255, 249, 219, 1.000)',
|
|
||||||
}
|
|
83
lib/shape-styles.ts
Normal file
83
lib/shape-styles.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import { SVGProps } from 'react'
|
||||||
|
import { ColorStyle, DashStyle, Shape, ShapeStyles, SizeStyle } from 'types'
|
||||||
|
|
||||||
|
export const strokes: Record<ColorStyle, string> = {
|
||||||
|
[ColorStyle.White]: 'rgba(248, 249, 250, 1.000)',
|
||||||
|
[ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
|
||||||
|
[ColorStyle.Gray]: 'rgba(172, 181, 189, 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.Teal]: 'rgba(9, 167, 120, 1.000)',
|
||||||
|
[ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
|
||||||
|
[ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
|
||||||
|
[ColorStyle.Indigo]: 'rgba(66, 99, 235, 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.Orange]: 'rgba(247, 103, 6, 1.000)',
|
||||||
|
[ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fills = {
|
||||||
|
[ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
|
||||||
|
[ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
|
||||||
|
[ColorStyle.Gray]: 'rgba(224, 226, 230, 1.000)',
|
||||||
|
[ColorStyle.Black]: 'rgba(224, 226, 230, 1.000)',
|
||||||
|
[ColorStyle.Lime]: 'rgba(243, 252, 227, 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.Blue]: 'rgba(231, 245, 255, 1.000)',
|
||||||
|
[ColorStyle.Indigo]: 'rgba(237, 242, 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.Orange]: 'rgba(255, 244, 229, 1.000)',
|
||||||
|
[ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',
|
||||||
|
}
|
||||||
|
|
||||||
|
const strokeWidths = {
|
||||||
|
[SizeStyle.Small]: 2,
|
||||||
|
[SizeStyle.Medium]: 4,
|
||||||
|
[SizeStyle.Large]: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
const dashArrays = {
|
||||||
|
[DashStyle.Solid]: () => 'none',
|
||||||
|
[DashStyle.Dashed]: (sw: number) => `${sw} ${sw * 2}`,
|
||||||
|
[DashStyle.Dotted]: (sw: number) => `0 ${sw * 1.5}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStrokeWidth(size: SizeStyle) {
|
||||||
|
return strokeWidths[size]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStrokeDashArray(dash: DashStyle, strokeWidth: number) {
|
||||||
|
return dashArrays[dash](strokeWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShapeStyle(
|
||||||
|
style: ShapeStyles
|
||||||
|
): Partial<SVGProps<SVGUseElement>> {
|
||||||
|
const { color, size, dash, isFilled } = style
|
||||||
|
|
||||||
|
const strokeWidth = getStrokeWidth(size)
|
||||||
|
const strokeDasharray = getStrokeDashArray(dash, strokeWidth)
|
||||||
|
|
||||||
|
return {
|
||||||
|
stroke: strokes[color],
|
||||||
|
fill: isFilled ? fills[color] : 'none',
|
||||||
|
strokeWidth,
|
||||||
|
strokeDasharray,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultStyle = {
|
||||||
|
color: ColorStyle.Black,
|
||||||
|
size: SizeStyle.Medium,
|
||||||
|
isFilled: false,
|
||||||
|
dash: DashStyle.Solid,
|
||||||
|
}
|
|
@ -1,7 +1,14 @@
|
||||||
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 * as svg from 'utils/svg'
|
import * as svg from 'utils/svg'
|
||||||
import { ArrowShape, ShapeHandle, ShapeType } from 'types'
|
import {
|
||||||
|
ArrowShape,
|
||||||
|
ColorStyle,
|
||||||
|
DashStyle,
|
||||||
|
ShapeHandle,
|
||||||
|
ShapeType,
|
||||||
|
SizeStyle,
|
||||||
|
} from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { circleFromThreePoints, clamp, isAngleBetween } from 'utils/utils'
|
import { circleFromThreePoints, clamp, isAngleBetween } from 'utils/utils'
|
||||||
import { pointInBounds } from 'utils/bounds'
|
import { pointInBounds } from 'utils/bounds'
|
||||||
|
@ -11,6 +18,7 @@ import {
|
||||||
} from 'utils/intersections'
|
} from 'utils/intersections'
|
||||||
import { getBoundsFromPoints, translateBounds } from 'utils/utils'
|
import { getBoundsFromPoints, translateBounds } from 'utils/utils'
|
||||||
import { pointInCircle } from 'utils/hitTests'
|
import { pointInCircle } from 'utils/hitTests'
|
||||||
|
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
|
const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
|
||||||
|
|
||||||
|
@ -77,21 +85,23 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
style: {
|
style: {
|
||||||
strokeWidth: 2,
|
...defaultStyle,
|
||||||
...props.style,
|
...props.style,
|
||||||
fill: 'none',
|
isFilled: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render(shape) {
|
render(shape) {
|
||||||
const { id, bend, points, handles, style } = shape
|
const { id, bend, points, handles } = shape
|
||||||
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 bendDist = arrowDist * bend
|
||||||
const showCircle = Math.abs(bendDist) > 20
|
const showCircle = Math.abs(bendDist) > 20
|
||||||
|
|
||||||
|
const style = getShapeStyle(shape.style)
|
||||||
|
|
||||||
// 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 angle = showCircle ? bend * (Math.PI * 0.48) : 0
|
||||||
|
@ -145,7 +155,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
|
|
||||||
applyStyles(shape, style) {
|
applyStyles(shape, style) {
|
||||||
Object.assign(shape.style, style)
|
Object.assign(shape.style, style)
|
||||||
shape.style.fill = 'none'
|
shape.style.isFilled = false
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
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 { CircleShape, ShapeType } from 'types'
|
import { CircleShape, ColorStyle, DashStyle, ShapeType, SizeStyle } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { boundsContained } from 'utils/bounds'
|
import { boundsContained } from 'utils/bounds'
|
||||||
import { intersectCircleBounds } from 'utils/intersections'
|
import { intersectCircleBounds } from 'utils/intersections'
|
||||||
import { pointInCircle } from 'utils/hitTests'
|
import { pointInCircle } from 'utils/hitTests'
|
||||||
import { translateBounds } from 'utils/utils'
|
import { translateBounds } from 'utils/utils'
|
||||||
|
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const circle = registerShapeUtils<CircleShape>({
|
const circle = registerShapeUtils<CircleShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -24,21 +25,20 @@ const circle = registerShapeUtils<CircleShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render({ id, radius, style }) {
|
render({ id, radius, style }) {
|
||||||
|
const styles = getShapeStyle(style)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<circle
|
<circle
|
||||||
id={id}
|
id={id}
|
||||||
cx={radius}
|
cx={radius}
|
||||||
cy={radius}
|
cy={radius}
|
||||||
r={Math.max(0, radius - Number(style.strokeWidth) / 2)}
|
r={Math.max(0, radius - Number(styles.strokeWidth) / 2)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { boundsContained } from 'utils/bounds'
|
||||||
import { intersectCircleBounds } from 'utils/intersections'
|
import { intersectCircleBounds } from 'utils/intersections'
|
||||||
import { DotCircle } from 'components/canvas/misc'
|
import { DotCircle } from 'components/canvas/misc'
|
||||||
import { translateBounds } from 'utils/utils'
|
import { translateBounds } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const dot = registerShapeUtils<DotShape>({
|
const dot = registerShapeUtils<DotShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -23,11 +24,12 @@ const dot = registerShapeUtils<DotShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
|
||||||
fill: '#c6cacb',
|
|
||||||
strokeWidth: '0',
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
|
style: {
|
||||||
|
...defaultStyle,
|
||||||
|
...props.style,
|
||||||
|
isFilled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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 { DrawShape, ShapeType } from 'types'
|
import { DashStyle, DrawShape, 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'
|
||||||
|
@ -12,6 +12,7 @@ import {
|
||||||
translateBounds,
|
translateBounds,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
|
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const pathCache = new WeakMap<DrawShape['points'], string>([])
|
const pathCache = new WeakMap<DrawShape['points'], string>([])
|
||||||
|
|
||||||
|
@ -34,11 +35,9 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
...props,
|
...props,
|
||||||
style: {
|
style: {
|
||||||
strokeWidth: 2,
|
...defaultStyle,
|
||||||
strokeLinecap: 'round',
|
|
||||||
strokeLinejoin: 'round',
|
|
||||||
...props.style,
|
...props.style,
|
||||||
fill: props.style.stroke,
|
isFilled: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -46,12 +45,14 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
render(shape) {
|
render(shape) {
|
||||||
const { id, points, style } = shape
|
const { id, points, style } = shape
|
||||||
|
|
||||||
|
const styles = getShapeStyle(style)
|
||||||
|
|
||||||
if (!pathCache.has(points)) {
|
if (!pathCache.has(points)) {
|
||||||
pathCache.set(
|
pathCache.set(
|
||||||
points,
|
points,
|
||||||
getSvgPathFromStroke(
|
getSvgPathFromStroke(
|
||||||
getStroke(points, {
|
getStroke(points, {
|
||||||
size: +style.strokeWidth * 2,
|
size: +styles.strokeWidth * 2,
|
||||||
thinning: 0.9,
|
thinning: 0.9,
|
||||||
end: { taper: 100 },
|
end: { taper: 100 },
|
||||||
start: { taper: 40 },
|
start: { taper: 40 },
|
||||||
|
@ -61,15 +62,18 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (points.length < 2) {
|
if (points.length < 2) {
|
||||||
return <circle id={id} r={+style.strokeWidth * 0.618} />
|
return (
|
||||||
|
<circle id={id} r={+styles.strokeWidth * 0.618} fill={styles.stroke} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <path id={id} d={pathCache.get(points)} />
|
return <path id={id} d={pathCache.get(points)} fill={styles.stroke} />
|
||||||
},
|
},
|
||||||
|
|
||||||
applyStyles(shape, style) {
|
applyStyles(shape, style) {
|
||||||
Object.assign(shape.style, style)
|
Object.assign(shape.style, style)
|
||||||
shape.style.fill = shape.style.stroke
|
shape.style.isFilled = false
|
||||||
|
shape.style.dash = DashStyle.Solid
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -106,19 +110,11 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
|
|
||||||
hitTest(shape, point) {
|
hitTest(shape, point) {
|
||||||
let pt = vec.sub(point, shape.point)
|
let pt = vec.sub(point, shape.point)
|
||||||
let prev = shape.points[0]
|
const min = +getShapeStyle(shape.style).strokeWidth
|
||||||
|
return shape.points.some(
|
||||||
for (let i = 1; i < shape.points.length; i++) {
|
(curr, i) =>
|
||||||
let curr = shape.points[i]
|
i > 0 && vec.distanceToLineSegment(shape.points[i - 1], curr, pt) < min
|
||||||
if (
|
)
|
||||||
vec.distanceToLineSegment(prev, curr, pt) < +shape.style.strokeWidth
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
prev = curr
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hitTestBounds(this, shape, brushBounds) {
|
hitTestBounds(this, shape, brushBounds) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
rotateBounds,
|
rotateBounds,
|
||||||
translateBounds,
|
translateBounds,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
|
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const ellipse = registerShapeUtils<EllipseShape>({
|
const ellipse = registerShapeUtils<EllipseShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -30,22 +31,20 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render({ id, radiusX, radiusY, style }) {
|
render({ id, radiusX, radiusY, style }) {
|
||||||
|
const styles = getShapeStyle(style)
|
||||||
return (
|
return (
|
||||||
<ellipse
|
<ellipse
|
||||||
id={id}
|
id={id}
|
||||||
cx={radiusX}
|
cx={radiusX}
|
||||||
cy={radiusY}
|
cy={radiusY}
|
||||||
rx={Math.max(0, radiusX - Number(style.strokeWidth) / 2)}
|
rx={Math.max(0, radiusX - Number(styles.strokeWidth) / 2)}
|
||||||
ry={Math.max(0, radiusY - Number(style.strokeWidth) / 2)}
|
ry={Math.max(0, radiusY - Number(styles.strokeWidth) / 2)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -48,7 +48,7 @@ export interface ShapeUtility<K extends Readonly<Shape>> {
|
||||||
applyStyles(
|
applyStyles(
|
||||||
this: ShapeUtility<K>,
|
this: ShapeUtility<K>,
|
||||||
shape: K,
|
shape: K,
|
||||||
style: ShapeStyles
|
style: Partial<ShapeStyles>
|
||||||
): ShapeUtility<K>
|
): ShapeUtility<K>
|
||||||
|
|
||||||
// Set the shape's point.
|
// Set the shape's point.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { intersectCircleBounds } from 'utils/intersections'
|
||||||
import { DotCircle, ThinLine } from 'components/canvas/misc'
|
import { DotCircle, ThinLine } from 'components/canvas/misc'
|
||||||
import { translateBounds } from 'utils/utils'
|
import { translateBounds } from 'utils/utils'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const line = registerShapeUtils<LineShape>({
|
const line = registerShapeUtils<LineShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -25,11 +26,12 @@ const line = registerShapeUtils<LineShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
|
style: {
|
||||||
|
...defaultStyle,
|
||||||
|
...props.style,
|
||||||
|
isFilled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { registerShapeUtils } from './index'
|
||||||
import { intersectPolylineBounds } from 'utils/intersections'
|
import { intersectPolylineBounds } from 'utils/intersections'
|
||||||
import { boundsContainPolygon } from 'utils/bounds'
|
import { boundsContainPolygon } from 'utils/bounds'
|
||||||
import { getBoundsFromPoints, translateBounds } from 'utils/utils'
|
import { getBoundsFromPoints, translateBounds } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const polyline = registerShapeUtils<PolylineShape>({
|
const polyline = registerShapeUtils<PolylineShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -23,11 +24,7 @@ const polyline = registerShapeUtils<PolylineShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
strokeWidth: 2,
|
|
||||||
strokeLinecap: 'round',
|
|
||||||
strokeLinejoin: 'round',
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { boundsContained } from 'utils/bounds'
|
||||||
import { intersectCircleBounds } from 'utils/intersections'
|
import { intersectCircleBounds } from 'utils/intersections'
|
||||||
import { DotCircle, ThinLine } from 'components/canvas/misc'
|
import { DotCircle, ThinLine } from 'components/canvas/misc'
|
||||||
import { translateBounds } from 'utils/utils'
|
import { translateBounds } from 'utils/utils'
|
||||||
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const ray = registerShapeUtils<RayShape>({
|
const ray = registerShapeUtils<RayShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -24,12 +25,12 @@ const ray = registerShapeUtils<RayShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
|
style: {
|
||||||
|
...defaultStyle,
|
||||||
|
...props.style,
|
||||||
|
isFilled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
getRotatedCorners,
|
getRotatedCorners,
|
||||||
translateBounds,
|
translateBounds,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
|
import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
|
||||||
const rectangle = registerShapeUtils<RectangleShape>({
|
const rectangle = registerShapeUtils<RectangleShape>({
|
||||||
boundsCache: new WeakMap([]),
|
boundsCache: new WeakMap([]),
|
||||||
|
@ -27,23 +28,21 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
||||||
isAspectRatioLocked: false,
|
isAspectRatioLocked: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
style: {
|
style: defaultStyle,
|
||||||
fill: '#c6cacb',
|
|
||||||
stroke: '#000',
|
|
||||||
},
|
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render({ id, size, radius, style }) {
|
render({ id, size, radius, style }) {
|
||||||
|
const styles = getShapeStyle(style)
|
||||||
return (
|
return (
|
||||||
<g id={id}>
|
<g id={id}>
|
||||||
<rect
|
<rect
|
||||||
id={id}
|
id={id}
|
||||||
rx={radius}
|
rx={radius}
|
||||||
ry={radius}
|
ry={radius}
|
||||||
width={Math.max(0, size[0] - Number(style.strokeWidth) / 2)}
|
width={Math.max(0, size[0] - Number(styles.strokeWidth) / 2)}
|
||||||
height={Math.max(0, size[1] - Number(style.strokeWidth) / 2)}
|
height={Math.max(0, size[1] - Number(styles.strokeWidth) / 2)}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import Command from "./command"
|
import Command from './command'
|
||||||
import history from "../history"
|
import history from '../history'
|
||||||
import { Data, ShapeStyles } from "types"
|
import { Data, ShapeStyles } from 'types'
|
||||||
import { getPage, getSelectedShapes } from "utils/utils"
|
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||||
import { getShapeUtils } from "lib/shape-utils"
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import { current } from "immer"
|
import { current } from 'immer'
|
||||||
|
|
||||||
export default function styleCommand(data: Data, styles: ShapeStyles) {
|
export default function styleCommand(data: Data, styles: Partial<ShapeStyles>) {
|
||||||
const { currentPageId } = data
|
const { currentPageId } = data
|
||||||
const initialShapes = getSelectedShapes(current(data))
|
const initialShapes = getSelectedShapes(current(data))
|
||||||
|
|
||||||
history.execute(
|
history.execute(
|
||||||
data,
|
data,
|
||||||
new Command({
|
new Command({
|
||||||
name: "changed_style",
|
name: 'changed_style',
|
||||||
category: "canvas",
|
category: 'canvas',
|
||||||
manualSelection: true,
|
manualSelection: true,
|
||||||
do(data) {
|
do(data) {
|
||||||
const { shapes } = getPage(data, currentPageId)
|
const { shapes } = getPage(data, currentPageId)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Data, ShapeType } from 'types'
|
import { Data, ShapeType } from 'types'
|
||||||
import shapeUtils from 'lib/shape-utils'
|
import shapeUtils from 'lib/shape-utils'
|
||||||
import { shades } from 'lib/colors'
|
|
||||||
|
|
||||||
export const defaultDocument: Data['document'] = {
|
export const defaultDocument: Data['document'] = {
|
||||||
pages: {
|
pages: {
|
||||||
|
@ -10,23 +9,22 @@ export const defaultDocument: Data['document'] = {
|
||||||
name: 'Page 0',
|
name: 'Page 0',
|
||||||
childIndex: 0,
|
childIndex: 0,
|
||||||
shapes: {
|
shapes: {
|
||||||
arrowShape0: shapeUtils[ShapeType.Arrow].create({
|
// arrowShape0: shapeUtils[ShapeType.Arrow].create({
|
||||||
id: 'arrowShape0',
|
// id: 'arrowShape0',
|
||||||
point: [200, 200],
|
// point: [200, 200],
|
||||||
points: [
|
// points: [
|
||||||
[0, 0],
|
// [0, 0],
|
||||||
[200, 200],
|
// [200, 200],
|
||||||
],
|
// ],
|
||||||
}),
|
// }),
|
||||||
arrowShape1: shapeUtils[ShapeType.Arrow].create({
|
// arrowShape1: shapeUtils[ShapeType.Arrow].create({
|
||||||
id: 'arrowShape1',
|
// id: 'arrowShape1',
|
||||||
point: [100, 100],
|
// point: [100, 100],
|
||||||
points: [
|
// points: [
|
||||||
[0, 0],
|
// [0, 0],
|
||||||
[300, 0],
|
// [300, 0],
|
||||||
],
|
// ],
|
||||||
}),
|
// }),
|
||||||
|
|
||||||
// shape3: shapeUtils[ShapeType.Dot].create({
|
// shape3: shapeUtils[ShapeType.Dot].create({
|
||||||
// id: 'shape3',
|
// id: 'shape3',
|
||||||
// name: 'Shape 3',
|
// name: 'Shape 3',
|
||||||
|
|
|
@ -117,11 +117,11 @@ export default class BrushSession extends BaseSession {
|
||||||
|
|
||||||
export function getDrawSnapshot(data: Data, shapeId: string) {
|
export function getDrawSnapshot(data: Data, shapeId: string) {
|
||||||
const page = getPage(current(data))
|
const page = getPage(current(data))
|
||||||
const { points, style } = page.shapes[shapeId] as DrawShape
|
const { points } = page.shapes[shapeId] as DrawShape
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: shapeId,
|
id: shapeId,
|
||||||
points,
|
points,
|
||||||
strokeWidth: style.strokeWidth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { createSelectorHook, createState } from '@state-designer/react'
|
||||||
import * as vec from 'utils/vec'
|
import * as vec from 'utils/vec'
|
||||||
import inputs from './inputs'
|
import inputs from './inputs'
|
||||||
import { defaultDocument } from './data'
|
import { defaultDocument } from './data'
|
||||||
import { shades } from 'lib/colors'
|
|
||||||
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
||||||
import history from 'state/history'
|
import history from 'state/history'
|
||||||
import * as Sessions from './sessions'
|
import * as Sessions from './sessions'
|
||||||
|
@ -15,7 +14,6 @@ import {
|
||||||
getCurrent,
|
getCurrent,
|
||||||
getPage,
|
getPage,
|
||||||
getSelectedBounds,
|
getSelectedBounds,
|
||||||
getSelectedShapes,
|
|
||||||
getShape,
|
getShape,
|
||||||
screenToWorld,
|
screenToWorld,
|
||||||
setZoomCSS,
|
setZoomCSS,
|
||||||
|
@ -34,6 +32,8 @@ import {
|
||||||
AlignType,
|
AlignType,
|
||||||
StretchType,
|
StretchType,
|
||||||
DashStyle,
|
DashStyle,
|
||||||
|
SizeStyle,
|
||||||
|
ColorStyle,
|
||||||
} from 'types'
|
} from 'types'
|
||||||
|
|
||||||
const initialData: Data = {
|
const initialData: Data = {
|
||||||
|
@ -49,10 +49,10 @@ const initialData: Data = {
|
||||||
nudgeDistanceSmall: 1,
|
nudgeDistanceSmall: 1,
|
||||||
},
|
},
|
||||||
currentStyle: {
|
currentStyle: {
|
||||||
fill: shades.lightGray,
|
size: SizeStyle.Medium,
|
||||||
stroke: shades.darkGray,
|
color: ColorStyle.Black,
|
||||||
strokeWidth: 2,
|
|
||||||
dash: DashStyle.Solid,
|
dash: DashStyle.Solid,
|
||||||
|
isFilled: false,
|
||||||
},
|
},
|
||||||
camera: {
|
camera: {
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
|
@ -131,7 +131,7 @@ const state = createState({
|
||||||
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
||||||
TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
|
TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
|
||||||
TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
|
TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
|
||||||
TOUCHED_CANVAS: 'closeStylePanel',
|
POINTED_CANVAS: 'closeStylePanel',
|
||||||
CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
|
CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
|
||||||
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
||||||
NUDGED: { do: 'nudgeSelection' },
|
NUDGED: { do: 'nudgeSelection' },
|
||||||
|
@ -1261,7 +1261,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
|
|
||||||
restoreSavedData(data) {
|
restoreSavedData(data) {
|
||||||
history.load(data)
|
// history.load(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
clearBoundsRotation(data) {
|
clearBoundsRotation(data) {
|
||||||
|
@ -1309,7 +1309,7 @@ const state = createState({
|
||||||
const page = getPage(data)
|
const page = getPage(data)
|
||||||
const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
|
const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
|
||||||
|
|
||||||
const commonStyle: Partial<ShapeStyles> = {}
|
const commonStyle: ShapeStyles = {} as ShapeStyles
|
||||||
|
|
||||||
const overrides = new Set<string>([])
|
const overrides = new Set<string>([])
|
||||||
|
|
||||||
|
|
52
types.ts
52
types.ts
|
@ -67,11 +67,49 @@ export enum ShapeType {
|
||||||
// Cubic = "cubic",
|
// Cubic = "cubic",
|
||||||
// Conic = "conic",
|
// Conic = "conic",
|
||||||
|
|
||||||
export type ShapeStyles = Partial<
|
export enum ColorStyle {
|
||||||
React.SVGProps<SVGUseElement> & {
|
White = 'White',
|
||||||
|
LightGray = 'LightGray',
|
||||||
|
Gray = 'Gray',
|
||||||
|
Black = 'Black',
|
||||||
|
Lime = 'Lime',
|
||||||
|
Green = 'Green',
|
||||||
|
Teal = 'Teal',
|
||||||
|
Cyan = 'Cyan',
|
||||||
|
Blue = 'Blue',
|
||||||
|
Indigo = 'Indigo',
|
||||||
|
Violet = 'Violet',
|
||||||
|
Grape = 'Grape',
|
||||||
|
Pink = 'Pink',
|
||||||
|
Red = 'Red',
|
||||||
|
Orange = 'Orange',
|
||||||
|
Yellow = 'Yellow',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SizeStyle {
|
||||||
|
Small = 'Small',
|
||||||
|
Medium = 'Medium',
|
||||||
|
Large = 'Large',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DashStyle {
|
||||||
|
Solid = 'Solid',
|
||||||
|
Dashed = 'Dashed',
|
||||||
|
Dotted = 'Dotted',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ShapeStyles = {
|
||||||
|
color: ColorStyle
|
||||||
|
size: SizeStyle
|
||||||
dash: DashStyle
|
dash: DashStyle
|
||||||
}
|
isFilled: boolean
|
||||||
>
|
}
|
||||||
|
|
||||||
|
// export type ShapeStyles = Partial<
|
||||||
|
// React.SVGProps<SVGUseElement> & {
|
||||||
|
// dash: DashStyle
|
||||||
|
// }
|
||||||
|
// >
|
||||||
|
|
||||||
export interface BaseShape {
|
export interface BaseShape {
|
||||||
id: string
|
id: string
|
||||||
|
@ -180,12 +218,6 @@ export enum Decoration {
|
||||||
Arrow = 'Arrow',
|
Arrow = 'Arrow',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DashStyle {
|
|
||||||
Solid = 'Solid',
|
|
||||||
Dashed = 'Dashed',
|
|
||||||
Dotted = 'Dotted',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShapeBinding {
|
export interface ShapeBinding {
|
||||||
id: string
|
id: string
|
||||||
index: number
|
index: number
|
||||||
|
|
Loading…
Reference in a new issue