Adds boolean props for lock, hide, aspect lock
This commit is contained in:
parent
b369aef7fc
commit
3329c16e57
29 changed files with 601 additions and 366 deletions
|
@ -1,15 +1,15 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import styled from "styles"
|
||||
import { useStateDesigner } from "@state-designer/react"
|
||||
import React, { useEffect, useRef } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
import state, { useSelector } from "state"
|
||||
import { CodeFile } from "types"
|
||||
import CodeDocs from "./code-docs"
|
||||
import CodeEditor from "./code-editor"
|
||||
import { generateFromCode } from "lib/code/generate"
|
||||
import * as Panel from "../panel"
|
||||
import { IconButton } from "../shared"
|
||||
import styled from 'styles'
|
||||
import { useStateDesigner } from '@state-designer/react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import state, { useSelector } from 'state'
|
||||
import { CodeFile } from 'types'
|
||||
import CodeDocs from './code-docs'
|
||||
import CodeEditor from './code-editor'
|
||||
import { generateFromCode } from 'lib/code/generate'
|
||||
import * as Panel from '../panel'
|
||||
import { IconButton } from '../shared'
|
||||
import {
|
||||
X,
|
||||
Code,
|
||||
|
@ -17,10 +17,10 @@ import {
|
|||
PlayCircle,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
} from "react-feather"
|
||||
} from 'react-feather'
|
||||
|
||||
const getErrorLineAndColumn = (e: any) => {
|
||||
if ("line" in e) {
|
||||
if ('line' in e) {
|
||||
return { line: Number(e.line), column: e.column }
|
||||
}
|
||||
|
||||
|
@ -46,23 +46,23 @@ export default function CodePanel() {
|
|||
error: null as { message: string; line: number; column: number } | null,
|
||||
},
|
||||
on: {
|
||||
MOUNTED: "setCode",
|
||||
CHANGED_FILE: "loadFile",
|
||||
MOUNTED: 'setCode',
|
||||
CHANGED_FILE: 'loadFile',
|
||||
},
|
||||
initial: "editingCode",
|
||||
initial: 'editingCode',
|
||||
states: {
|
||||
editingCode: {
|
||||
on: {
|
||||
RAN_CODE: ["saveCode", "runCode"],
|
||||
SAVED_CODE: ["saveCode", "runCode"],
|
||||
CHANGED_CODE: { secretlyDo: "setCode" },
|
||||
CLEARED_ERROR: { if: "hasError", do: "clearError" },
|
||||
TOGGLED_DOCS: { to: "viewingDocs" },
|
||||
RAN_CODE: ['saveCode', 'runCode'],
|
||||
SAVED_CODE: ['saveCode', 'runCode'],
|
||||
CHANGED_CODE: { secretlyDo: 'setCode' },
|
||||
CLEARED_ERROR: { if: 'hasError', do: 'clearError' },
|
||||
TOGGLED_DOCS: { to: 'viewingDocs' },
|
||||
},
|
||||
},
|
||||
viewingDocs: {
|
||||
on: {
|
||||
TOGGLED_DOCS: { to: "editingCode" },
|
||||
TOGGLED_DOCS: { to: 'editingCode' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -83,7 +83,7 @@ export default function CodePanel() {
|
|||
|
||||
try {
|
||||
const { shapes, controls } = generateFromCode(data.code)
|
||||
state.send("GENERATED_FROM_CODE", { shapes, controls })
|
||||
state.send('GENERATED_FROM_CODE', { shapes, controls })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
error = { message: e.message, ...getErrorLineAndColumn(e) }
|
||||
|
@ -93,7 +93,7 @@ export default function CodePanel() {
|
|||
},
|
||||
saveCode(data) {
|
||||
const { code } = data
|
||||
state.send("SAVED_CODE", { code })
|
||||
state.send('SAVED_CODE', { code })
|
||||
},
|
||||
clearError(data) {
|
||||
data.error = null
|
||||
|
@ -102,13 +102,13 @@ export default function CodePanel() {
|
|||
})
|
||||
|
||||
useEffect(() => {
|
||||
local.send("CHANGED_FILE", { file })
|
||||
local.send('CHANGED_FILE', { file })
|
||||
}, [file])
|
||||
|
||||
useEffect(() => {
|
||||
local.send("MOUNTED", { code: state.data.document.code[fileId].code })
|
||||
local.send('MOUNTED', { code: state.data.document.code[fileId].code })
|
||||
return () => {
|
||||
state.send("CHANGED_CODE", { fileId, code: local.data.code })
|
||||
state.send('CHANGED_CODE', { fileId, code: local.data.code })
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -118,32 +118,32 @@ export default function CodePanel() {
|
|||
<Panel.Root data-bp-desktop ref={rContainer} isOpen={isOpen}>
|
||||
{isOpen ? (
|
||||
<Panel.Layout>
|
||||
<Panel.Header>
|
||||
<IconButton onClick={() => state.send("TOGGLED_CODE_PANEL_OPEN")}>
|
||||
<Panel.Header side="left">
|
||||
<IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
|
||||
<X />
|
||||
</IconButton>
|
||||
<h3>Code</h3>
|
||||
<ButtonsGroup>
|
||||
<FontSizeButtons>
|
||||
<IconButton
|
||||
disabled={!local.isIn("editingCode")}
|
||||
onClick={() => state.send("INCREASED_CODE_FONT_SIZE")}
|
||||
disabled={!local.isIn('editingCode')}
|
||||
onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
|
||||
>
|
||||
<ChevronUp />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!local.isIn("editingCode")}
|
||||
onClick={() => state.send("DECREASED_CODE_FONT_SIZE")}
|
||||
disabled={!local.isIn('editingCode')}
|
||||
onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
|
||||
>
|
||||
<ChevronDown />
|
||||
</IconButton>
|
||||
</FontSizeButtons>
|
||||
<IconButton onClick={() => local.send("TOGGLED_DOCS")}>
|
||||
<IconButton onClick={() => local.send('TOGGLED_DOCS')}>
|
||||
<Info />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!local.isIn("editingCode")}
|
||||
onClick={() => local.send("SAVED_CODE")}
|
||||
disabled={!local.isIn('editingCode')}
|
||||
onClick={() => local.send('SAVED_CODE')}
|
||||
>
|
||||
<PlayCircle />
|
||||
</IconButton>
|
||||
|
@ -155,11 +155,11 @@ export default function CodePanel() {
|
|||
readOnly={isReadOnly}
|
||||
value={file.code}
|
||||
error={error}
|
||||
onChange={(code) => local.send("CHANGED_CODE", { code })}
|
||||
onSave={() => local.send("SAVED_CODE")}
|
||||
onKey={() => local.send("CLEARED_ERROR")}
|
||||
onChange={(code) => local.send('CHANGED_CODE', { code })}
|
||||
onSave={() => local.send('SAVED_CODE')}
|
||||
onKey={() => local.send('CLEARED_ERROR')}
|
||||
/>
|
||||
<CodeDocs isHidden={!local.isIn("viewingDocs")} />
|
||||
<CodeDocs isHidden={!local.isIn('viewingDocs')} />
|
||||
</Panel.Content>
|
||||
<Panel.Footer>
|
||||
{error &&
|
||||
|
@ -169,7 +169,7 @@ export default function CodePanel() {
|
|||
</Panel.Footer>
|
||||
</Panel.Layout>
|
||||
) : (
|
||||
<IconButton onClick={() => state.send("TOGGLED_CODE_PANEL_OPEN")}>
|
||||
<IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
|
||||
<Code />
|
||||
</IconButton>
|
||||
)}
|
||||
|
@ -177,28 +177,28 @@ export default function CodePanel() {
|
|||
)
|
||||
}
|
||||
|
||||
const ButtonsGroup = styled("div", {
|
||||
gridRow: "1",
|
||||
gridColumn: "3",
|
||||
display: "flex",
|
||||
const ButtonsGroup = styled('div', {
|
||||
gridRow: '1',
|
||||
gridColumn: '3',
|
||||
display: 'flex',
|
||||
})
|
||||
|
||||
const FontSizeButtons = styled("div", {
|
||||
const FontSizeButtons = styled('div', {
|
||||
paddingRight: 4,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
"& > button": {
|
||||
height: "50%",
|
||||
"&:nth-of-type(1)": {
|
||||
alignItems: "flex-end",
|
||||
'& > button': {
|
||||
height: '50%',
|
||||
'&:nth-of-type(1)': {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
|
||||
"&:nth-of-type(2)": {
|
||||
alignItems: "flex-start",
|
||||
'&:nth-of-type(2)': {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
'& svg': {
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import useKeyboardEvents from "hooks/useKeyboardEvents"
|
||||
import useLoadOnMount from "hooks/useLoadOnMount"
|
||||
import Canvas from "./canvas/canvas"
|
||||
import StatusBar from "./status-bar"
|
||||
import Toolbar from "./toolbar"
|
||||
import CodePanel from "./code-panel/code-panel"
|
||||
import ControlsPanel from "./controls-panel/controls-panel"
|
||||
import styled from "styles"
|
||||
import StylePanel from "./style-panel/style-panel"
|
||||
import { useSelector } from "state"
|
||||
import useKeyboardEvents from 'hooks/useKeyboardEvents'
|
||||
import useLoadOnMount from 'hooks/useLoadOnMount'
|
||||
import Canvas from './canvas/canvas'
|
||||
import StatusBar from './status-bar'
|
||||
import Toolbar from './toolbar'
|
||||
import CodePanel from './code-panel/code-panel'
|
||||
import ControlsPanel from './controls-panel/controls-panel'
|
||||
import ToolsPanel from './tools-panel/tools-panel'
|
||||
import StylePanel from './style-panel/style-panel'
|
||||
import { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
|
||||
export default function Editor() {
|
||||
useKeyboardEvents()
|
||||
|
@ -19,9 +20,8 @@ export default function Editor() {
|
|||
|
||||
return (
|
||||
<Layout>
|
||||
<Canvas />
|
||||
<StatusBar />
|
||||
<Toolbar />
|
||||
<Canvas />
|
||||
<LeftPanels>
|
||||
<CodePanel />
|
||||
{hasControls && <ControlsPanel />}
|
||||
|
@ -29,40 +29,43 @@ export default function Editor() {
|
|||
<RightPanels>
|
||||
<StylePanel />
|
||||
</RightPanels>
|
||||
<ToolsPanel />
|
||||
<StatusBar />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const Layout = styled("div", {
|
||||
position: "fixed",
|
||||
const Layout = styled('div', {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
display: "grid",
|
||||
gridTemplateRows: "40px 1fr 40px",
|
||||
gridTemplateColumns: "minmax(50%, 400px) 1fr auto",
|
||||
display: 'grid',
|
||||
gridTemplateRows: '40px 1fr auto 40px',
|
||||
gridTemplateColumns: 'minmax(50%, 400px) 1fr auto',
|
||||
gridTemplateAreas: `
|
||||
"toolbar toolbar toolbar"
|
||||
"leftPanels main rightPanels"
|
||||
"tools tools tools"
|
||||
"statusbar statusbar statusbar"
|
||||
`,
|
||||
})
|
||||
|
||||
const LeftPanels = styled("main", {
|
||||
display: "grid",
|
||||
gridArea: "leftPanels",
|
||||
gridTemplateRows: "1fr auto",
|
||||
const LeftPanels = styled('main', {
|
||||
display: 'grid',
|
||||
gridArea: 'leftPanels',
|
||||
gridTemplateRows: '1fr auto',
|
||||
padding: 8,
|
||||
gap: 8,
|
||||
})
|
||||
|
||||
const RightPanels = styled("main", {
|
||||
display: "grid",
|
||||
gridArea: "rightPanels",
|
||||
gridTemplateRows: "auto",
|
||||
height: "fit-content",
|
||||
justifyContent: "flex-end",
|
||||
const RightPanels = styled('main', {
|
||||
display: 'grid',
|
||||
gridArea: 'rightPanels',
|
||||
gridTemplateRows: 'auto',
|
||||
height: 'fit-content',
|
||||
justifyContent: 'flex-end',
|
||||
padding: 8,
|
||||
gap: 8,
|
||||
})
|
||||
|
|
|
@ -1,32 +1,60 @@
|
|||
import styled from "styles"
|
||||
import styled from 'styles'
|
||||
|
||||
export const IconButton = styled("button", {
|
||||
height: "32px",
|
||||
width: "32px",
|
||||
backgroundColor: "$panel",
|
||||
borderRadius: "4px",
|
||||
padding: "0",
|
||||
margin: "0",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
outline: "none",
|
||||
border: "none",
|
||||
pointerEvents: "all",
|
||||
cursor: "pointer",
|
||||
export const IconButton = styled('button', {
|
||||
height: '32px',
|
||||
width: '32px',
|
||||
backgroundColor: '$panel',
|
||||
borderRadius: '4px',
|
||||
padding: '0',
|
||||
margin: '0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
pointerEvents: 'all',
|
||||
cursor: 'pointer',
|
||||
|
||||
"&:hover:not(:disabled)": {
|
||||
backgroundColor: "$hover",
|
||||
'&:hover:not(:disabled)': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
|
||||
"&:disabled": {
|
||||
opacity: "0.5",
|
||||
'&:disabled': {
|
||||
opacity: '0.5',
|
||||
},
|
||||
|
||||
svg: {
|
||||
height: "16px",
|
||||
width: "16px",
|
||||
strokeWidth: "2px",
|
||||
stroke: "$text",
|
||||
'& > svg': {
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
// strokeWidth: '2px',
|
||||
// stroke: '$text',
|
||||
},
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
medium: {
|
||||
height: 44,
|
||||
width: 44,
|
||||
'& svg': {
|
||||
height: 16,
|
||||
width: 16,
|
||||
strokeWidth: 0,
|
||||
},
|
||||
},
|
||||
large: {
|
||||
height: 44,
|
||||
width: 44,
|
||||
'& svg': {
|
||||
height: 24,
|
||||
width: 24,
|
||||
strokeWidth: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
isActive: {
|
||||
true: {
|
||||
color: '$selected',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -4,14 +4,29 @@ import * as Panel from 'components/panel'
|
|||
import { useRef } from 'react'
|
||||
import { IconButton } from 'components/shared'
|
||||
import { Circle, Copy, Lock, Trash, Unlock, X } from 'react-feather'
|
||||
import { deepCompare, deepCompareArrays, getSelectedShapes } from 'utils/utils'
|
||||
import {
|
||||
deepCompare,
|
||||
deepCompareArrays,
|
||||
getPage,
|
||||
getSelectedShapes,
|
||||
} from 'utils/utils'
|
||||
import { shades, fills, strokes } from 'lib/colors'
|
||||
|
||||
import ColorPicker from './color-picker'
|
||||
import AlignDistribute from './align-distribute'
|
||||
import { ShapeStyles } from 'types'
|
||||
import WidthPicker from './width-picker'
|
||||
import { CopyIcon } from '@radix-ui/react-icons'
|
||||
import {
|
||||
AspectRatioIcon,
|
||||
BoxIcon,
|
||||
CopyIcon,
|
||||
EyeClosedIcon,
|
||||
EyeOpenIcon,
|
||||
LockClosedIcon,
|
||||
LockOpen1Icon,
|
||||
RotateCounterClockwiseIcon,
|
||||
TrashIcon,
|
||||
} from '@radix-ui/react-icons'
|
||||
|
||||
const fillColors = { ...shades, ...fills }
|
||||
const strokeColors = { ...shades, ...strokes }
|
||||
|
@ -43,31 +58,47 @@ function SelectedShapeStyles({}: {}) {
|
|||
deepCompareArrays
|
||||
)
|
||||
|
||||
const shapesStyle = useSelector((s) => {
|
||||
const { currentStyle } = s.data
|
||||
const shapes = getSelectedShapes(s.data)
|
||||
const isAllLocked = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return selectedIds.every((id) => page.shapes[id].isLocked)
|
||||
})
|
||||
|
||||
if (shapes.length === 0) {
|
||||
const isAllAspectLocked = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return selectedIds.every((id) => page.shapes[id].isAspectRatioLocked)
|
||||
})
|
||||
|
||||
const isAllHidden = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
return selectedIds.every((id) => page.shapes[id].isHidden)
|
||||
})
|
||||
|
||||
const commonStyle = useSelector((s) => {
|
||||
const { currentStyle } = s.data
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
return currentStyle
|
||||
}
|
||||
const page = getPage(s.data)
|
||||
const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
|
||||
|
||||
const style: Partial<ShapeStyles> = {}
|
||||
const commonStyle: Partial<ShapeStyles> = {}
|
||||
const overrides = new Set<string>([])
|
||||
|
||||
for (const shape of shapes) {
|
||||
for (const shapeStyle of shapeStyles) {
|
||||
for (let key in currentStyle) {
|
||||
if (overrides.has(key)) continue
|
||||
if (style[key] === undefined) {
|
||||
style[key] = shape.style[key]
|
||||
if (commonStyle[key] === undefined) {
|
||||
commonStyle[key] = shapeStyle[key]
|
||||
} else {
|
||||
if (style[key] === shape.style[key]) continue
|
||||
style[key] = currentStyle[key]
|
||||
if (commonStyle[key] === shapeStyle[key]) continue
|
||||
commonStyle[key] = currentStyle[key]
|
||||
overrides.add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
return commonStyle
|
||||
}, deepCompare)
|
||||
|
||||
const hasSelection = selectedIds.length > 0
|
||||
|
@ -83,19 +114,19 @@ function SelectedShapeStyles({}: {}) {
|
|||
<Content>
|
||||
<ColorPicker
|
||||
label="Fill"
|
||||
color={shapesStyle.fill}
|
||||
color={commonStyle.fill}
|
||||
colors={fillColors}
|
||||
onChange={(color) => state.send('CHANGED_STYLE', { fill: color })}
|
||||
/>
|
||||
<ColorPicker
|
||||
label="Stroke"
|
||||
color={shapesStyle.stroke}
|
||||
color={commonStyle.stroke}
|
||||
colors={strokeColors}
|
||||
onChange={(color) => state.send('CHANGED_STYLE', { stroke: color })}
|
||||
/>
|
||||
<Row>
|
||||
<label htmlFor="width">Width</label>
|
||||
<WidthPicker strokeWidth={Number(shapesStyle.strokeWidth)} />
|
||||
<WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
|
||||
</Row>
|
||||
<AlignDistribute
|
||||
hasTwoOrMore={selectedIds.length > 1}
|
||||
|
@ -104,19 +135,39 @@ function SelectedShapeStyles({}: {}) {
|
|||
<ButtonsRow>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('DELETED')}
|
||||
onClick={() => state.send('DUPLICATED')}
|
||||
>
|
||||
<Trash />
|
||||
<CopyIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('DUPLICATED')}
|
||||
onClick={() => state.send('ROTATED_CCW')}
|
||||
>
|
||||
<Copy />
|
||||
<RotateCounterClockwiseIcon />
|
||||
</IconButton>
|
||||
|
||||
<IconButton>
|
||||
<Unlock />
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('DELETED')}
|
||||
>
|
||||
<TrashIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
|
||||
>
|
||||
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
|
||||
>
|
||||
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!hasSelection}
|
||||
onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
|
||||
>
|
||||
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
||||
</IconButton>
|
||||
</ButtonsRow>
|
||||
</Content>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { DotsHorizontalIcon } from '@radix-ui/react-icons'
|
||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||
import { IconButton } from 'components/shared'
|
||||
import { ChangeEvent } from 'react'
|
||||
import { Circle } from 'react-feather'
|
||||
import state from 'state'
|
||||
|
@ -26,7 +24,7 @@ export default function WidthPicker({
|
|||
<Circle size={12} />
|
||||
</RadioItem>
|
||||
<RadioItem value="8" isActive={strokeWidth === 8}>
|
||||
<Circle size={18} />
|
||||
<Circle size={22} />
|
||||
</RadioItem>
|
||||
</Group>
|
||||
)
|
||||
|
|
|
@ -26,63 +26,6 @@ export default function Toolbar() {
|
|||
<Button>
|
||||
<Menu />
|
||||
</Button>
|
||||
<Button onClick={() => state.send('TOGGLED_TOOL_LOCK')}>
|
||||
{isToolLocked ? <Lock /> : <Unlock />}
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'select'}
|
||||
onClick={() => state.send('SELECTED_SELECT_TOOL')}
|
||||
>
|
||||
Select
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'draw'}
|
||||
onClick={() => state.send('SELECTED_DRAW_TOOL')}
|
||||
>
|
||||
Draw
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'dot'}
|
||||
onClick={() => state.send('SELECTED_DOT_TOOL')}
|
||||
>
|
||||
Dot
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'circle'}
|
||||
onClick={() => state.send('SELECTED_CIRCLE_TOOL')}
|
||||
>
|
||||
Circle
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'ellipse'}
|
||||
onClick={() => state.send('SELECTED_ELLIPSE_TOOL')}
|
||||
>
|
||||
Ellipse
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'ray'}
|
||||
onClick={() => state.send('SELECTED_RAY_TOOL')}
|
||||
>
|
||||
Ray
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'line'}
|
||||
onClick={() => state.send('SELECTED_LINE_TOOL')}
|
||||
>
|
||||
Line
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'polyline'}
|
||||
onClick={() => state.send('SELECTED_POLYLINE_TOOL')}
|
||||
>
|
||||
Polyline
|
||||
</Button>
|
||||
<Button
|
||||
isSelected={activeTool === 'rectangle'}
|
||||
onClick={() => state.send('SELECTED_RECTANGLE_TOOL')}
|
||||
>
|
||||
Rectangle
|
||||
</Button>
|
||||
<Button onClick={() => state.send('RESET_CAMERA')}>Reset Camera</Button>
|
||||
</Section>
|
||||
<Section>
|
||||
|
|
162
components/tools-panel/tools-panel.tsx
Normal file
162
components/tools-panel/tools-panel.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
import {
|
||||
CircleIcon,
|
||||
CursorArrowIcon,
|
||||
DividerHorizontalIcon,
|
||||
DotIcon,
|
||||
LineHeightIcon,
|
||||
LockClosedIcon,
|
||||
LockOpen1Icon,
|
||||
Pencil1Icon,
|
||||
Pencil2Icon,
|
||||
SewingPinIcon,
|
||||
SquareIcon,
|
||||
} from '@radix-ui/react-icons'
|
||||
import { IconButton } from 'components/shared'
|
||||
import React from 'react'
|
||||
import state, { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { ShapeType } from 'types'
|
||||
|
||||
const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
|
||||
const selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
|
||||
const selectDotTool = () => state.send('SELECTED_DOT_TOOL')
|
||||
const selectCircleTool = () => state.send('SELECTED_CIRCLE_TOOL')
|
||||
const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_TOOL')
|
||||
const selectRayTool = () => state.send('SELECTED_RAY_TOOL')
|
||||
const selectLineTool = () => state.send('SELECTED_LINE_TOOL')
|
||||
const selectPolylineTool = () => state.send('SELECTED_POLYLINE_TOOL')
|
||||
const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
|
||||
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
|
||||
|
||||
export default function ToolsPanel() {
|
||||
const activeTool = useSelector((state) =>
|
||||
state.whenIn({
|
||||
selecting: 'select',
|
||||
dot: ShapeType.Dot,
|
||||
circle: ShapeType.Circle,
|
||||
ellipse: ShapeType.Ellipse,
|
||||
ray: ShapeType.Ray,
|
||||
line: ShapeType.Line,
|
||||
polyline: ShapeType.Polyline,
|
||||
rectangle: ShapeType.Rectangle,
|
||||
draw: ShapeType.Draw,
|
||||
})
|
||||
)
|
||||
|
||||
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
|
||||
|
||||
const isPenLocked = useSelector((s) => s.data.settings.isPenLocked)
|
||||
|
||||
return (
|
||||
<OuterContainer>
|
||||
<Container>
|
||||
<IconButton
|
||||
name="select"
|
||||
size="large"
|
||||
onClick={selectSelectTool}
|
||||
isActive={activeTool === 'select'}
|
||||
>
|
||||
<CursorArrowIcon />
|
||||
</IconButton>
|
||||
</Container>
|
||||
<Container>
|
||||
<IconButton
|
||||
name={ShapeType.Draw}
|
||||
size="large"
|
||||
onClick={selectDrawTool}
|
||||
isActive={activeTool === ShapeType.Draw}
|
||||
>
|
||||
<Pencil1Icon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
name={ShapeType.Rectangle}
|
||||
size="large"
|
||||
onClick={selectRectangleTool}
|
||||
isActive={activeTool === ShapeType.Rectangle}
|
||||
>
|
||||
<SquareIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
name={ShapeType.Circle}
|
||||
size="large"
|
||||
onClick={selectCircleTool}
|
||||
isActive={activeTool === ShapeType.Circle}
|
||||
>
|
||||
<CircleIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
name={ShapeType.Ellipse}
|
||||
size="large"
|
||||
onClick={selectEllipseTool}
|
||||
isActive={activeTool === ShapeType.Ellipse}
|
||||
>
|
||||
<CircleIcon transform="rotate(-45) scale(1, .8)" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
name={ShapeType.Line}
|
||||
size="large"
|
||||
onClick={selectLineTool}
|
||||
isActive={activeTool === ShapeType.Line}
|
||||
>
|
||||
<DividerHorizontalIcon transform="rotate(-45)" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
name={ShapeType.Ray}
|
||||
size="large"
|
||||
onClick={selectRayTool}
|
||||
isActive={activeTool === ShapeType.Ray}
|
||||
>
|
||||
<SewingPinIcon transform="rotate(-135)" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
name={ShapeType.Dot}
|
||||
size="large"
|
||||
onClick={selectDotTool}
|
||||
isActive={activeTool === ShapeType.Dot}
|
||||
>
|
||||
<DotIcon />
|
||||
</IconButton>
|
||||
</Container>
|
||||
<Container>
|
||||
<IconButton size="medium" onClick={selectToolLock}>
|
||||
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||
</IconButton>
|
||||
{isPenLocked && (
|
||||
<IconButton size="medium" onClick={selectToolLock}>
|
||||
<Pencil2Icon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Container>
|
||||
</OuterContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const OuterContainer = styled('div', {
|
||||
gridArea: 'tools',
|
||||
padding: '0 8px 12px 8px',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 16,
|
||||
})
|
||||
|
||||
const Container = styled('div', {
|
||||
position: 'relative',
|
||||
backgroundColor: '$panel',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid $border',
|
||||
pointerEvents: 'all',
|
||||
userSelect: 'none',
|
||||
zIndex: 200,
|
||||
boxShadow: '0px 2px 25px rgba(0,0,0,.16)',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
padding: 4,
|
||||
|
||||
'& svg': {
|
||||
strokeWidth: 0,
|
||||
},
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { CircleShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { CircleShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Circle extends CodeShape<CircleShape> {
|
||||
constructor(props = {} as Partial<CircleShape>) {
|
||||
|
@ -11,15 +11,18 @@ export default class Circle extends CodeShape<CircleShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Circle,
|
||||
isGenerated: true,
|
||||
name: "Circle",
|
||||
parentId: "page0",
|
||||
name: 'Circle',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
radius: 20,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { DotShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { DotShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Dot extends CodeShape<DotShape> {
|
||||
constructor(props = {} as Partial<DotShape>) {
|
||||
|
@ -11,14 +11,17 @@ export default class Dot extends CodeShape<DotShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Dot,
|
||||
isGenerated: true,
|
||||
name: "Dot",
|
||||
parentId: "page0",
|
||||
name: 'Dot',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { EllipseShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { EllipseShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Ellipse extends CodeShape<EllipseShape> {
|
||||
constructor(props = {} as Partial<EllipseShape>) {
|
||||
|
@ -11,16 +11,19 @@ export default class Ellipse extends CodeShape<EllipseShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Ellipse,
|
||||
isGenerated: true,
|
||||
name: "Ellipse",
|
||||
parentId: "page0",
|
||||
name: 'Ellipse',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
radiusX: 20,
|
||||
radiusY: 20,
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { LineShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { LineShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Line extends CodeShape<LineShape> {
|
||||
constructor(props = {} as Partial<LineShape>) {
|
||||
|
@ -12,15 +12,18 @@ export default class Line extends CodeShape<LineShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Line,
|
||||
isGenerated: true,
|
||||
name: "Line",
|
||||
parentId: "page0",
|
||||
name: 'Line',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
direction: [-0.5, 0.5],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { PolylineShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { PolylineShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Polyline extends CodeShape<PolylineShape> {
|
||||
constructor(props = {} as Partial<PolylineShape>) {
|
||||
|
@ -12,15 +12,18 @@ export default class Polyline extends CodeShape<PolylineShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Polyline,
|
||||
isGenerated: true,
|
||||
name: "Polyline",
|
||||
parentId: "page0",
|
||||
name: 'Polyline',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
points: [[0, 0]],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "none",
|
||||
stroke: "#000",
|
||||
fill: 'none',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { RayShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { RayShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Ray extends CodeShape<RayShape> {
|
||||
constructor(props = {} as Partial<RayShape>) {
|
||||
|
@ -12,15 +12,18 @@ export default class Ray extends CodeShape<RayShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Ray,
|
||||
isGenerated: true,
|
||||
name: "Ray",
|
||||
parentId: "page0",
|
||||
name: 'Ray',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
direction: [0, 1],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { RectangleShape, ShapeType } from "types"
|
||||
import { vectorToPoint } from "utils/utils"
|
||||
import CodeShape from './index'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { RectangleShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
|
||||
export default class Rectangle extends CodeShape<RectangleShape> {
|
||||
constructor(props = {} as Partial<RectangleShape>) {
|
||||
|
@ -12,16 +12,19 @@ export default class Rectangle extends CodeShape<RectangleShape> {
|
|||
id: uuid(),
|
||||
type: ShapeType.Rectangle,
|
||||
isGenerated: true,
|
||||
name: "Rectangle",
|
||||
parentId: "page0",
|
||||
name: 'Rectangle',
|
||||
parentId: 'page0',
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
size: [100, 100],
|
||||
rotation: 0,
|
||||
radius: 2,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: "#c6cacb",
|
||||
stroke: "#000",
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
...props,
|
||||
|
|
|
@ -21,6 +21,9 @@ const circle = registerShapeUtils<CircleShape>({
|
|||
point: [0, 0],
|
||||
rotation: 0,
|
||||
radius: 1,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
|
@ -125,13 +128,8 @@ const circle = registerShapeUtils<CircleShape>({
|
|||
return this
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@ const dot = registerShapeUtils<DotShape>({
|
|||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: '#c6cacb',
|
||||
strokeWidth: '0',
|
||||
|
@ -94,13 +97,8 @@ const dot = registerShapeUtils<DotShape>({
|
|||
return this
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
point: [0, 0],
|
||||
points: [[0, 0]],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
...props,
|
||||
style: {
|
||||
strokeWidth: 2,
|
||||
|
@ -169,25 +172,8 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
return this
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
return this
|
||||
},
|
||||
|
||||
setPoints(shape, points) {
|
||||
// const bounds = getBoundsFromPoints(points)
|
||||
// const corner = [bounds.minX, bounds.minY]
|
||||
// const nudged = points.map((point) => vec.sub(point, corner))
|
||||
// this.boundsCache.set(shape, translategetBoundsFromPoints(nudged))
|
||||
// shape.point = vec.add(shape.point, corner)
|
||||
|
||||
shape.points = points
|
||||
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
|||
radiusX: 1,
|
||||
radiusY: 1,
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
|
@ -137,13 +140,8 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
|||
return this.transform(shape, bounds, info)
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -8,15 +8,16 @@ import {
|
|||
Edge,
|
||||
ShapeByType,
|
||||
ShapeStyles,
|
||||
} from "types"
|
||||
import circle from "./circle"
|
||||
import dot from "./dot"
|
||||
import polyline from "./polyline"
|
||||
import rectangle from "./rectangle"
|
||||
import ellipse from "./ellipse"
|
||||
import line from "./line"
|
||||
import ray from "./ray"
|
||||
import draw from "./draw"
|
||||
PropsOfType,
|
||||
} from 'types'
|
||||
import circle from './circle'
|
||||
import dot from './dot'
|
||||
import polyline from './polyline'
|
||||
import rectangle from './rectangle'
|
||||
import ellipse from './ellipse'
|
||||
import line from './line'
|
||||
import ray from './ray'
|
||||
import draw from './draw'
|
||||
|
||||
/*
|
||||
Shape Utiliies
|
||||
|
@ -82,21 +83,11 @@ export interface ShapeUtility<K extends Readonly<Shape>> {
|
|||
}
|
||||
): ShapeUtility<K>
|
||||
|
||||
// Move a shape to a new parent.
|
||||
setParent(this: ShapeUtility<K>, shape: K, parentId: string): ShapeUtility<K>
|
||||
|
||||
// Change the child index of a shape
|
||||
setChildIndex(
|
||||
setProperty<P extends keyof K>(
|
||||
this: ShapeUtility<K>,
|
||||
shape: K,
|
||||
childIndex: number
|
||||
): ShapeUtility<K>
|
||||
|
||||
// Add a point
|
||||
setPoints?(
|
||||
this: ShapeUtility<K>,
|
||||
shape: K,
|
||||
points: number[][]
|
||||
prop: P,
|
||||
value: K[P]
|
||||
): ShapeUtility<K>
|
||||
|
||||
// Render a shape to JSX.
|
||||
|
@ -151,7 +142,7 @@ export function registerShapeUtils<T extends Shape>(
|
|||
}
|
||||
|
||||
export function createShape<T extends Shape>(
|
||||
type: T["type"],
|
||||
type: T['type'],
|
||||
props: Partial<T>
|
||||
) {
|
||||
return shapeUtilityMap[type].create(props) as T
|
||||
|
|
|
@ -21,6 +21,9 @@ const line = registerShapeUtils<LineShape>({
|
|||
point: [0, 0],
|
||||
direction: [0, 0],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
|
@ -102,13 +105,8 @@ const line = registerShapeUtils<LineShape>({
|
|||
return this.transform(shape, bounds, info)
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@ const polyline = registerShapeUtils<PolylineShape>({
|
|||
point: [0, 0],
|
||||
points: [[0, 0]],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
|
@ -127,13 +130,8 @@ const polyline = registerShapeUtils<PolylineShape>({
|
|||
return this
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@ const ray = registerShapeUtils<RayShape>({
|
|||
point: [0, 0],
|
||||
direction: [0, 1],
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
|
@ -102,13 +105,8 @@ const ray = registerShapeUtils<RayShape>({
|
|||
return this.transform(shape, bounds, info)
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
size: [1, 1],
|
||||
radius: 2,
|
||||
rotation: 0,
|
||||
isAspectRatioLocked: false,
|
||||
isLocked: false,
|
||||
isHidden: false,
|
||||
style: {
|
||||
fill: '#c6cacb',
|
||||
stroke: '#000',
|
||||
|
@ -140,13 +143,8 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
return this
|
||||
},
|
||||
|
||||
setParent(shape, parentId) {
|
||||
shape.parentId = parentId
|
||||
return this
|
||||
},
|
||||
|
||||
setChildIndex(shape, childIndex) {
|
||||
shape.childIndex = childIndex
|
||||
setProperty(shape, prop, value) {
|
||||
shape[prop] = value
|
||||
return this
|
||||
},
|
||||
|
||||
|
|
|
@ -9,16 +9,16 @@ interface Core {
|
|||
interface Instance extends Props, Core {}
|
||||
|
||||
const defaults: Props = {
|
||||
name: "Spot",
|
||||
name: 'Spot',
|
||||
}
|
||||
|
||||
const core: Core = {
|
||||
id: "0",
|
||||
id: '0',
|
||||
}
|
||||
|
||||
class ClassInstance<T extends object = {}> implements Instance {
|
||||
id = "0"
|
||||
name = "Spot"
|
||||
id = '0'
|
||||
name = 'Spot'
|
||||
|
||||
constructor(
|
||||
props: Partial<Props> &
|
||||
|
@ -51,7 +51,7 @@ function getInstance<T extends object = {}>(
|
|||
}
|
||||
|
||||
const instance = getInstance({
|
||||
name: "Steve",
|
||||
name: 'Steve',
|
||||
age: 93,
|
||||
wag(this: Instance) {
|
||||
return this.name
|
||||
|
@ -76,29 +76,29 @@ const getAnimal = <T extends object>(
|
|||
): Animal & T => {
|
||||
return {
|
||||
// Defaults
|
||||
name: "Animal",
|
||||
name: 'Animal',
|
||||
greet(name) {
|
||||
return "Hey " + name
|
||||
return 'Hey ' + name
|
||||
},
|
||||
// Overrides
|
||||
...props,
|
||||
// Core
|
||||
id: "hi",
|
||||
id: 'hi',
|
||||
sleep() {},
|
||||
}
|
||||
}
|
||||
|
||||
const dog = getAnimal({
|
||||
name: "doggo",
|
||||
name: 'doggo',
|
||||
greet(name) {
|
||||
return "Woof " + this.name
|
||||
return 'Woof ' + this.name
|
||||
},
|
||||
wag() {
|
||||
return "wagging..."
|
||||
return 'wagging...'
|
||||
},
|
||||
})
|
||||
|
||||
dog.greet("steve")
|
||||
dog.greet('steve')
|
||||
dog.wag()
|
||||
dog.sleep()
|
||||
|
||||
|
@ -111,5 +111,5 @@ export default shapeTest
|
|||
type Greet = (name: string) => string
|
||||
|
||||
const greet: Greet = (name: string | number) => {
|
||||
return "hello " + name
|
||||
return 'hello ' + name
|
||||
}
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import Command from "./command"
|
||||
import history from "../history"
|
||||
import { Data } from "types"
|
||||
import { getPage } from "utils/utils"
|
||||
import { getShapeUtils } from "lib/shape-utils"
|
||||
import { current } from "immer"
|
||||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data, DrawShape } from 'types'
|
||||
import { getPage } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { current } from 'immer'
|
||||
|
||||
export default function drawCommand(
|
||||
data: Data,
|
||||
id: string,
|
||||
before: number[][],
|
||||
after: number[][]
|
||||
) {
|
||||
const restoreShape = current(getPage(data).shapes[id])
|
||||
getShapeUtils(restoreShape).setPoints!(restoreShape, after)
|
||||
export default function drawCommand(data: Data, id: string, after: number[][]) {
|
||||
const restoreShape = current(getPage(data)).shapes[id] as DrawShape
|
||||
|
||||
getShapeUtils(restoreShape).setProperty!(restoreShape, 'points', after)
|
||||
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: "set_points",
|
||||
category: "canvas",
|
||||
name: 'set_points',
|
||||
category: 'canvas',
|
||||
manualSelection: true,
|
||||
do(data, initial) {
|
||||
if (!initial) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import transform from './transform'
|
|||
import transformSingle from './transform-single'
|
||||
import translate from './translate'
|
||||
import nudge from './nudge'
|
||||
import toggle from './toggle'
|
||||
|
||||
const commands = {
|
||||
align,
|
||||
|
@ -30,6 +31,7 @@ const commands = {
|
|||
transformSingle,
|
||||
translate,
|
||||
nudge,
|
||||
toggle,
|
||||
}
|
||||
|
||||
export default commands
|
||||
|
|
46
state/commands/toggle.ts
Normal file
46
state/commands/toggle.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data, Shape } from 'types'
|
||||
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { PropsOfType } from 'types'
|
||||
|
||||
export default function toggleCommand(
|
||||
data: Data,
|
||||
prop: PropsOfType<Shape, boolean>
|
||||
) {
|
||||
const { currentPageId } = data
|
||||
const selectedShapes = getSelectedShapes(data)
|
||||
const isAllToggled = selectedShapes.every((shape) => shape[prop])
|
||||
const initialShapes = Object.fromEntries(
|
||||
selectedShapes.map((shape) => [shape.id, shape[prop]])
|
||||
)
|
||||
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: 'hide_shapes',
|
||||
category: 'canvas',
|
||||
do(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
for (const id in initialShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).setProperty(
|
||||
shape,
|
||||
prop,
|
||||
isAllToggled ? false : true
|
||||
)
|
||||
}
|
||||
},
|
||||
undo(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
|
||||
for (const id in initialShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).setProperty(shape, prop, initialShapes[id])
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
|
@ -75,6 +75,12 @@ const state = createState({
|
|||
PANNED_CAMERA: {
|
||||
do: 'panCamera',
|
||||
},
|
||||
TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
|
||||
TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
|
||||
TOGGLED_SHAPE_ASPECT_LOCK: {
|
||||
if: 'hasSelection',
|
||||
do: 'aspectLockSelection',
|
||||
},
|
||||
SELECTED_SELECT_TOOL: { to: 'selecting' },
|
||||
SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
|
||||
SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
|
||||
|
@ -638,7 +644,7 @@ const state = createState({
|
|||
? siblings[siblings.length - 1].childIndex + 1
|
||||
: 1
|
||||
|
||||
getShapeUtils(shape).setChildIndex(shape, childIndex)
|
||||
getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex)
|
||||
|
||||
getPage(data).shapes[shape.id] = shape
|
||||
|
||||
|
@ -834,6 +840,15 @@ const state = createState({
|
|||
duplicateSelection(data) {
|
||||
commands.duplicate(data)
|
||||
},
|
||||
lockSelection(data) {
|
||||
commands.toggle(data, 'isLocked')
|
||||
},
|
||||
hideSelection(data) {
|
||||
commands.toggle(data, 'isHidden')
|
||||
},
|
||||
aspectLockSelection(data) {
|
||||
commands.toggle(data, 'isAspectRatioLocked')
|
||||
},
|
||||
|
||||
/* --------------------- Camera --------------------- */
|
||||
|
||||
|
|
7
types.ts
7
types.ts
|
@ -78,6 +78,9 @@ export interface BaseShape {
|
|||
point: number[]
|
||||
rotation: number
|
||||
style: ShapeStyles
|
||||
isLocked: boolean
|
||||
isHidden: boolean
|
||||
isAspectRatioLocked: boolean
|
||||
}
|
||||
|
||||
export interface DotShape extends BaseShape {
|
||||
|
@ -313,3 +316,7 @@ export type CodeControl =
|
|||
| VectorCodeControl
|
||||
| TextCodeControl
|
||||
| SelectCodeControl
|
||||
|
||||
export type PropsOfType<T extends object, K> = {
|
||||
[K in keyof T]: T[K] extends boolean ? K : never
|
||||
}[keyof T]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue