Improves layout, code panel, tweaks draw
This commit is contained in:
parent
062f446339
commit
475d04e3d0
23 changed files with 308 additions and 182 deletions
|
@ -11,7 +11,7 @@ import Bounds from './bounds/bounding-box'
|
|||
import BoundsBg from './bounds/bounds-bg'
|
||||
import Selected from './selected'
|
||||
import Handles from './bounds/handles'
|
||||
import { isMobile } from 'utils/utils'
|
||||
import { isMobile, throttle } from 'utils/utils'
|
||||
|
||||
export default function Canvas() {
|
||||
const rCanvas = useRef<SVGSVGElement>(null)
|
||||
|
@ -34,25 +34,13 @@ export default function Canvas() {
|
|||
} else {
|
||||
if (isMobile()) {
|
||||
state.send('TOUCHED_CANVAS')
|
||||
// state.send('POINTED_CANVAS', inputs.touchStart(e, 'canvas'))
|
||||
// e.preventDefault()
|
||||
// e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
||||
// if (!inputs.canAccept(e.touches[0].identifier)) return
|
||||
// if (inputs.canAccept(e.touches[0].identifier)) {
|
||||
// state.send('MOVED_POINTER', inputs.touchMove(e))
|
||||
// }
|
||||
// }, [])
|
||||
|
||||
const handlePointerMove = useCallback((e: React.PointerEvent) => {
|
||||
if (!inputs.canAccept(e.pointerId)) return
|
||||
if (inputs.canAccept(e.pointerId)) {
|
||||
state.send('MOVED_POINTER', inputs.pointerMove(e))
|
||||
}
|
||||
throttledPointerMove(inputs.pointerMove(e))
|
||||
}, [])
|
||||
|
||||
const handlePointerUp = useCallback((e: React.PointerEvent) => {
|
||||
|
@ -94,8 +82,17 @@ const MainSVG = styled('svg', {
|
|||
touchAction: 'none',
|
||||
zIndex: 100,
|
||||
backgroundColor: '$canvas',
|
||||
pointerEvents: 'all',
|
||||
|
||||
'& *': {
|
||||
userSelect: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
// const throttledPointerMove = throttle((payload: any) => {
|
||||
// state.send('MOVED_POINTER', payload)
|
||||
// }, 16)
|
||||
|
||||
const throttledPointerMove = (payload: any) => {
|
||||
state.send('MOVED_POINTER', payload)
|
||||
}
|
||||
|
|
|
@ -208,6 +208,11 @@ const EditorContainer = styled('div', {
|
|||
pointerEvents: 'all',
|
||||
userSelect: 'all',
|
||||
|
||||
'& > *': {
|
||||
userSelect: 'all',
|
||||
pointerEvents: 'all',
|
||||
},
|
||||
|
||||
'.editorLineError': {
|
||||
backgroundColor: '$lineError',
|
||||
},
|
||||
|
|
|
@ -115,11 +115,18 @@ export default function CodePanel() {
|
|||
const { error } = local.data
|
||||
|
||||
return (
|
||||
<Panel.Root data-bp-desktop ref={rContainer} isOpen={isOpen}>
|
||||
<Panel.Root
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
data-bp-desktop
|
||||
ref={rContainer}
|
||||
isOpen={isOpen}
|
||||
variant="code"
|
||||
>
|
||||
{isOpen ? (
|
||||
<Panel.Layout>
|
||||
<Panel.Header side="left">
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
|
||||
>
|
||||
|
@ -129,6 +136,7 @@ export default function CodePanel() {
|
|||
<ButtonsGroup>
|
||||
<FontSizeButtons>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!local.isIn('editingCode')}
|
||||
onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
|
||||
|
@ -144,12 +152,14 @@ export default function CodePanel() {
|
|||
</IconButton>
|
||||
</FontSizeButtons>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
onClick={() => local.send('TOGGLED_DOCS')}
|
||||
>
|
||||
<Info />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!local.isIn('editingCode')}
|
||||
onClick={() => local.send('SAVED_CODE')}
|
||||
|
@ -179,6 +189,7 @@ export default function CodePanel() {
|
|||
</Panel.Layout>
|
||||
) : (
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
|
||||
>
|
||||
|
|
|
@ -22,6 +22,7 @@ export default function ControlPanel() {
|
|||
<Panel.Layout>
|
||||
<Panel.Header>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
onClick={() => state.send('CLOSED_CODE_PANEL')}
|
||||
>
|
||||
|
@ -37,6 +38,7 @@ export default function ControlPanel() {
|
|||
</Panel.Layout>
|
||||
) : (
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
onClick={() => state.send('OPENED_CODE_PANEL')}
|
||||
>
|
||||
|
|
|
@ -20,21 +20,26 @@ export default function Editor() {
|
|||
|
||||
return (
|
||||
<Layout>
|
||||
<Canvas />
|
||||
<CodePanel />
|
||||
<PagePanel />
|
||||
<LeftPanels>
|
||||
<Spacer />
|
||||
<StylePanel />
|
||||
<Canvas />
|
||||
|
||||
{/* <LeftPanels>
|
||||
<CodePanel />
|
||||
{hasControls && <ControlsPanel />}
|
||||
</LeftPanels>
|
||||
<RightPanels>
|
||||
<StylePanel />
|
||||
</RightPanels>
|
||||
</LeftPanels> */}
|
||||
<ToolsPanel />
|
||||
<StatusBar />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const Spacer = styled('div', {
|
||||
flexGrow: 2,
|
||||
})
|
||||
|
||||
const Layout = styled('main', {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
|
@ -43,34 +48,13 @@ const Layout = styled('main', {
|
|||
right: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateRows: '1fr auto 144px',
|
||||
gridTemplateColumns: 'minmax(0, 720px) 1fr auto',
|
||||
gridTemplateAreas: `
|
||||
"leftPanels main rightPanels"
|
||||
"tools tools tools"
|
||||
"statusbar statusbar statusbar"
|
||||
`,
|
||||
})
|
||||
|
||||
const LeftPanels = styled('div', {
|
||||
display: 'grid',
|
||||
gridArea: 'leftPanels',
|
||||
gridTemplateRows: '1fr auto',
|
||||
padding: 8,
|
||||
gap: 8,
|
||||
zIndex: 250,
|
||||
pointerEvents: 'none',
|
||||
})
|
||||
|
||||
const RightPanels = styled('div', {
|
||||
gridArea: 'rightPanels',
|
||||
padding: 8,
|
||||
display: 'grid',
|
||||
gridTemplateRows: 'auto',
|
||||
height: 'fit-content',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 8,
|
||||
zIndex: 300,
|
||||
padding: '8px 8px 0 8px',
|
||||
zIndex: 200,
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
pointerEvents: 'none',
|
||||
'& > *': {
|
||||
PointerEvent: 'all',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -29,89 +29,89 @@ export default function PagePanel() {
|
|||
)
|
||||
|
||||
return (
|
||||
<OuterContainer>
|
||||
<DropdownMenu.Root
|
||||
open={isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (rIsOpen.current !== isOpen) {
|
||||
setIsOpen(isOpen)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PanelRoot>
|
||||
<DropdownMenu.Trigger as={RowButton}>
|
||||
<span>{documentPages[currentPageId].name}</span>
|
||||
<IconWrapper size="small">
|
||||
<ChevronDownIcon />
|
||||
</IconWrapper>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content sideOffset={8}>
|
||||
<PanelRoot>
|
||||
<DropdownMenu.RadioGroup
|
||||
as={Content}
|
||||
value={currentPageId}
|
||||
onValueChange={(id) => {
|
||||
setIsOpen(false)
|
||||
state.send('CHANGED_CURRENT_PAGE', { id })
|
||||
}}
|
||||
>
|
||||
{sorted.map(({ id, name }) => (
|
||||
<ContextMenu.Root key={id}>
|
||||
<ContextMenu.Trigger>
|
||||
<StyledRadioItem key={id} value={id}>
|
||||
<span>{name}</span>
|
||||
<DropdownMenu.ItemIndicator
|
||||
as={IconWrapper}
|
||||
size="small"
|
||||
>
|
||||
<CheckIcon />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
</StyledRadioItem>
|
||||
</ContextMenu.Trigger>
|
||||
<StyledContextMenuContent>
|
||||
<ContextMenu.Group>
|
||||
<StyledContextMenuItem
|
||||
onSelect={() => state.send('RENAMED_PAGE', { id })}
|
||||
>
|
||||
Rename
|
||||
</StyledContextMenuItem>
|
||||
<StyledContextMenuItem
|
||||
onSelect={() => {
|
||||
setIsOpen(false)
|
||||
state.send('DELETED_PAGE', { id })
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</StyledContextMenuItem>
|
||||
</ContextMenu.Group>
|
||||
</StyledContextMenuContent>
|
||||
</ContextMenu.Root>
|
||||
))}
|
||||
</DropdownMenu.RadioGroup>
|
||||
<DropdownMenu.Separator />
|
||||
<RowButton
|
||||
onClick={() => {
|
||||
setIsOpen(false)
|
||||
state.send('CREATED_PAGE')
|
||||
}}
|
||||
>
|
||||
<span>Create Page</span>
|
||||
<IconWrapper size="small">
|
||||
<PlusIcon />
|
||||
</IconWrapper>
|
||||
</RowButton>
|
||||
</PanelRoot>
|
||||
</DropdownMenu.Content>
|
||||
</PanelRoot>
|
||||
</DropdownMenu.Root>
|
||||
</OuterContainer>
|
||||
<DropdownMenu.Root
|
||||
open={isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (rIsOpen.current !== isOpen) {
|
||||
setIsOpen(isOpen)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PanelRoot>
|
||||
<DropdownMenu.Trigger
|
||||
as={RowButton}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
css={{ paddingRight: 12 }}
|
||||
>
|
||||
<span>{documentPages[currentPageId].name}</span>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content sideOffset={8}>
|
||||
<PanelRoot>
|
||||
<DropdownMenu.RadioGroup
|
||||
as={Content}
|
||||
value={currentPageId}
|
||||
onValueChange={(id) => {
|
||||
setIsOpen(false)
|
||||
state.send('CHANGED_CURRENT_PAGE', { id })
|
||||
}}
|
||||
>
|
||||
{sorted.map(({ id, name }) => (
|
||||
<ContextMenu.Root key={id}>
|
||||
<ContextMenu.Trigger>
|
||||
<StyledRadioItem
|
||||
key={id}
|
||||
value={id}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
>
|
||||
<span>{name}</span>
|
||||
<DropdownMenu.ItemIndicator as={IconWrapper} size="small">
|
||||
<CheckIcon />
|
||||
</DropdownMenu.ItemIndicator>
|
||||
</StyledRadioItem>
|
||||
</ContextMenu.Trigger>
|
||||
<StyledContextMenuContent>
|
||||
<ContextMenu.Group>
|
||||
<StyledContextMenuItem
|
||||
onSelect={() => state.send('RENAMED_PAGE', { id })}
|
||||
>
|
||||
Rename
|
||||
</StyledContextMenuItem>
|
||||
<StyledContextMenuItem
|
||||
onSelect={() => {
|
||||
setIsOpen(false)
|
||||
state.send('DELETED_PAGE', { id })
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</StyledContextMenuItem>
|
||||
</ContextMenu.Group>
|
||||
</StyledContextMenuContent>
|
||||
</ContextMenu.Root>
|
||||
))}
|
||||
</DropdownMenu.RadioGroup>
|
||||
<DropdownMenu.Separator />
|
||||
<RowButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
onClick={() => {
|
||||
setIsOpen(false)
|
||||
state.send('CREATED_PAGE')
|
||||
}}
|
||||
>
|
||||
<span>Create Page</span>
|
||||
<IconWrapper size="small">
|
||||
<PlusIcon />
|
||||
</IconWrapper>
|
||||
</RowButton>
|
||||
</PanelRoot>
|
||||
</DropdownMenu.Content>
|
||||
</PanelRoot>
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const PanelRoot = styled('div', {
|
||||
minWidth: 1,
|
||||
width: 184,
|
||||
maxWidth: 184,
|
||||
marginLeft: 8,
|
||||
zIndex: 200,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
|
@ -128,11 +128,12 @@ const PanelRoot = styled('div', {
|
|||
|
||||
const Content = styled(Panel.Content, {
|
||||
width: '100%',
|
||||
minWidth: 128,
|
||||
})
|
||||
|
||||
const StyledRadioItem = styled(DropdownMenu.RadioItem, {
|
||||
height: 32,
|
||||
width: '100%',
|
||||
width: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
|
@ -143,24 +144,19 @@ const StyledRadioItem = styled(DropdownMenu.RadioItem, {
|
|||
fontFamily: '$ui',
|
||||
backgroundColor: 'transparent',
|
||||
outline: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: '$hover',
|
||||
variants: {
|
||||
bp: {
|
||||
mobile: {},
|
||||
small: {
|
||||
'&:hover': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
'&:focus-within': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'&:focus-within': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
})
|
||||
|
||||
const OuterContainer = styled('div', {
|
||||
position: 'fixed',
|
||||
top: 8,
|
||||
left: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
zIndex: 200,
|
||||
height: 44,
|
||||
})
|
||||
|
||||
const StyledContextMenuContent = styled(ContextMenu.Content, {
|
||||
|
@ -184,8 +180,13 @@ const StyledContextMenuItem = styled(ContextMenu.Item, {
|
|||
fontFamily: '$ui',
|
||||
backgroundColor: 'transparent',
|
||||
outline: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: '$hover',
|
||||
bp: {
|
||||
mobile: {},
|
||||
small: {
|
||||
'&:hover:not(:disabled)': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -12,6 +12,13 @@ export const Root = styled('div', {
|
|||
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
||||
|
||||
variants: {
|
||||
bp: {
|
||||
mobile: {},
|
||||
small: {},
|
||||
},
|
||||
variant: {
|
||||
code: {},
|
||||
},
|
||||
isOpen: {
|
||||
true: {},
|
||||
false: {
|
||||
|
@ -21,6 +28,35 @@ export const Root = styled('div', {
|
|||
},
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
isOpen: true,
|
||||
variant: 'code',
|
||||
css: {
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom: 48,
|
||||
maxWidth: 720,
|
||||
zIndex: 1000,
|
||||
},
|
||||
},
|
||||
{
|
||||
isOpen: true,
|
||||
variant: 'code',
|
||||
bp: 'small',
|
||||
css: {
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom: 128,
|
||||
maxWidth: 720,
|
||||
zIndex: 1000,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const Layout = styled('div', {
|
||||
|
|
|
@ -23,15 +23,19 @@ export const IconButton = styled('button', {
|
|||
gridColumn: 1,
|
||||
},
|
||||
|
||||
'&:hover:not(:disabled)': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
opacity: '0.5',
|
||||
},
|
||||
|
||||
variants: {
|
||||
bp: {
|
||||
mobile: {},
|
||||
small: {
|
||||
'&:hover:not(:disabled)': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
},
|
||||
},
|
||||
size: {
|
||||
small: {
|
||||
'& svg': {
|
||||
|
@ -80,10 +84,6 @@ export const RowButton = styled('button', {
|
|||
padding: '4px 6px 4px 12px',
|
||||
borderRadius: 4,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
|
||||
'& label': {
|
||||
fontWeight: '$1',
|
||||
margin: 0,
|
||||
|
@ -98,6 +98,14 @@ export const RowButton = styled('button', {
|
|||
},
|
||||
|
||||
variants: {
|
||||
bp: {
|
||||
mobile: {},
|
||||
small: {
|
||||
'&:hover:not(:disabled)': {
|
||||
backgroundColor: '$hover',
|
||||
},
|
||||
},
|
||||
},
|
||||
size: {
|
||||
icon: {
|
||||
padding: '4px ',
|
||||
|
|
|
@ -32,6 +32,7 @@ const StatusBarContainer = styled('div', {
|
|||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
zIndex: 300,
|
||||
height: 40,
|
||||
userSelect: 'none',
|
||||
borderTop: '1px solid black',
|
||||
|
@ -43,7 +44,6 @@ const StatusBarContainer = styled('div', {
|
|||
gap: 8,
|
||||
fontSize: '$0',
|
||||
padding: '0 16px',
|
||||
zIndex: 200,
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
|
|
|
@ -64,20 +64,32 @@ export default function AlignDistribute({
|
|||
}) {
|
||||
return (
|
||||
<Container>
|
||||
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignLeft}>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={alignLeft}
|
||||
>
|
||||
<AlignLeftIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={alignCenterHorizontal}
|
||||
>
|
||||
<AlignCenterHorizontallyIcon />
|
||||
</IconButton>
|
||||
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignRight}>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={alignRight}
|
||||
>
|
||||
<AlignRightIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={stretchHorizontally}
|
||||
|
@ -85,26 +97,39 @@ export default function AlignDistribute({
|
|||
<StretchHorizontallyIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasThreeOrMore}
|
||||
onClick={distributeHorizontally}
|
||||
>
|
||||
<SpaceEvenlyHorizontallyIcon />
|
||||
</IconButton>
|
||||
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignTop}>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={alignTop}
|
||||
>
|
||||
<AlignTopIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={alignCenterVertical}
|
||||
>
|
||||
<AlignCenterVerticallyIcon />
|
||||
</IconButton>
|
||||
<IconButton size="small" disabled={!hasTwoOrMore} onClick={alignBottom}>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={alignBottom}
|
||||
>
|
||||
<AlignBottomIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasTwoOrMore}
|
||||
onClick={stretchVertically}
|
||||
|
@ -112,6 +137,7 @@ export default function AlignDistribute({
|
|||
<StretchVerticallyIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
disabled={!hasThreeOrMore}
|
||||
onClick={distributeVertically}
|
||||
|
|
|
@ -12,7 +12,7 @@ export default function ColorContent({
|
|||
onChange: (color: ColorStyle) => void
|
||||
}) {
|
||||
return (
|
||||
<DropdownContent sideOffset={0} side="bottom">
|
||||
<DropdownContent sideOffset={8} side="bottom">
|
||||
{Object.keys(strokes).map((color: ColorStyle) => (
|
||||
<DropdownMenu.DropdownMenuItem
|
||||
as={IconButton}
|
||||
|
|
|
@ -13,7 +13,10 @@ interface Props {
|
|||
export default function ColorPicker({ color, onChange }: Props) {
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger as={RowButton}>
|
||||
<DropdownMenu.Trigger
|
||||
as={RowButton}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
>
|
||||
<label htmlFor="color">Color</label>
|
||||
<IconWrapper>
|
||||
<Square fill={strokes[color]} />
|
||||
|
|
|
@ -13,6 +13,7 @@ export default function IsFilledPicker({ isFilled, onChange }: Props) {
|
|||
return (
|
||||
<Checkbox.Root
|
||||
as={RowButton}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
checked={isFilled}
|
||||
onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChange(e.currentTarget.checked)
|
||||
|
|
|
@ -11,7 +11,10 @@ export default function QuickColorSelect() {
|
|||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger as={IconButton}>
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
>
|
||||
<Tooltip label="Color">
|
||||
<Square fill={strokes[color]} stroke={strokes[color]} />
|
||||
</Tooltip>
|
||||
|
|
|
@ -22,10 +22,13 @@ export default function QuickdashSelect() {
|
|||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger as={IconButton}>
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
>
|
||||
<Tooltip label="Dash">{dashes[dash]}</Tooltip>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownContent direction="vertical">
|
||||
<DropdownContent sideOffset={8} direction="vertical">
|
||||
<DashItem isActive={dash === DashStyle.Solid} dash={DashStyle.Solid} />
|
||||
<DashItem
|
||||
isActive={dash === DashStyle.Dashed}
|
||||
|
|
|
@ -17,12 +17,15 @@ export default function QuickSizeSelect() {
|
|||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger as={IconButton}>
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
>
|
||||
<Tooltip label="Size">
|
||||
<Circle size={sizes[size]} stroke="none" fill="currentColor" />
|
||||
</Tooltip>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownContent direction="vertical">
|
||||
<DropdownContent sideOffset={8} direction="vertical">
|
||||
<SizeItem isActive={size === SizeStyle.Small} size={SizeStyle.Small} />
|
||||
<SizeItem
|
||||
isActive={size === SizeStyle.Medium}
|
||||
|
|
|
@ -44,6 +44,7 @@ export default function StylePanel() {
|
|||
<QuickSizeSelect />
|
||||
<QuickdashSelect />
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
title="Style"
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
||||
|
@ -92,6 +93,7 @@ function SelectedShapeStyles() {
|
|||
<Panel.Header side="right">
|
||||
<h3>Style</h3>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
|
||||
>
|
||||
|
@ -117,6 +119,7 @@ function SelectedShapeStyles() {
|
|||
</Row>
|
||||
<ButtonsRow>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('DUPLICATED')}
|
||||
|
@ -137,6 +140,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
|
||||
|
@ -147,6 +151,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
|
||||
|
@ -157,6 +162,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
|
||||
|
@ -168,6 +174,7 @@ function SelectedShapeStyles() {
|
|||
</ButtonsRow>
|
||||
<ButtonsRow>
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
|
||||
|
@ -178,6 +185,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('MOVED', { type: MoveType.Backward })}
|
||||
|
@ -188,6 +196,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('MOVED', { type: MoveType.Forward })}
|
||||
|
@ -198,6 +207,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
|
||||
|
@ -208,6 +218,7 @@ function SelectedShapeStyles() {
|
|||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
disabled={!hasSelection}
|
||||
size="small"
|
||||
onClick={() => state.send('DELETED')}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
Pencil2Icon,
|
||||
SewingPinIcon,
|
||||
SquareIcon,
|
||||
TextIcon,
|
||||
} from '@radix-ui/react-icons'
|
||||
import { IconButton } from 'components/shared'
|
||||
import React from 'react'
|
||||
|
@ -27,6 +28,7 @@ const selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
|
|||
const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_TOOL')
|
||||
const selectLineTool = () => state.send('SELECTED_LINE_TOOL')
|
||||
const selectPolylineTool = () => state.send('SELECTED_POLYLINE_TOOL')
|
||||
const selectTextTool = () => state.send('SELECTED_TEXT_TOOL')
|
||||
const selectRayTool = () => state.send('SELECTED_RAY_TOOL')
|
||||
const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
|
||||
const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
|
||||
|
@ -47,6 +49,7 @@ export default function ToolsPanel() {
|
|||
<Tooltip label="Select">
|
||||
<IconButton
|
||||
name="select"
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectSelectTool}
|
||||
isActive={activeTool === 'select'}
|
||||
|
@ -59,6 +62,7 @@ export default function ToolsPanel() {
|
|||
<Tooltip label="Draw">
|
||||
<IconButton
|
||||
name={ShapeType.Draw}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectDrawTool}
|
||||
isActive={activeTool === ShapeType.Draw}
|
||||
|
@ -69,6 +73,7 @@ export default function ToolsPanel() {
|
|||
<Tooltip label="Rectangle">
|
||||
<IconButton
|
||||
name={ShapeType.Rectangle}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectRectangleTool}
|
||||
isActive={activeTool === ShapeType.Rectangle}
|
||||
|
@ -79,6 +84,7 @@ export default function ToolsPanel() {
|
|||
<Tooltip label="Ellipse">
|
||||
<IconButton
|
||||
name={ShapeType.Circle}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectEllipseTool}
|
||||
isActive={activeTool === ShapeType.Ellipse}
|
||||
|
@ -89,6 +95,7 @@ export default function ToolsPanel() {
|
|||
<Tooltip label="Arrow">
|
||||
<IconButton
|
||||
name={ShapeType.Arrow}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectArrowTool}
|
||||
isActive={activeTool === ShapeType.Arrow}
|
||||
|
@ -96,6 +103,17 @@ export default function ToolsPanel() {
|
|||
<ArrowTopRightIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip label="Text">
|
||||
<IconButton
|
||||
name={ShapeType.Arrow}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectTextTool}
|
||||
isActive={activeTool === ShapeType.Text}
|
||||
>
|
||||
<TextIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{/* <IconButton
|
||||
name={ShapeType.Circle}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
|
@ -132,6 +150,7 @@ export default function ToolsPanel() {
|
|||
<Container>
|
||||
<Tooltip label="Lock Tool">
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectToolLock}
|
||||
>
|
||||
|
@ -141,6 +160,7 @@ export default function ToolsPanel() {
|
|||
{isPenLocked && (
|
||||
<Tooltip label="Unlock Pen">
|
||||
<IconButton
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectToolLock}
|
||||
>
|
||||
|
|
|
@ -164,10 +164,10 @@ function renderPath(shape: DrawShape, style: ShapeStyles) {
|
|||
shape.points,
|
||||
getSvgPathFromStroke(
|
||||
getStroke(shape.points, {
|
||||
size: +styles.strokeWidth * 2,
|
||||
thinning: 0.9,
|
||||
end: { taper: 100 },
|
||||
start: { taper: 40 },
|
||||
size: 1 + +styles.strokeWidth * 2,
|
||||
thinning: 0.83,
|
||||
end: { taper: +styles.strokeWidth * 16 },
|
||||
start: { taper: +styles.strokeWidth * 16 },
|
||||
})
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AppProps } from "next/app"
|
||||
import { globalStyles } from "styles"
|
||||
import "styles/globals.css"
|
||||
import { AppProps } from 'next/app'
|
||||
import { globalStyles } from 'styles'
|
||||
import 'styles/globals.css'
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
globalStyles()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import NextDocument, { Html, Head, Main, NextScript } from "next/document"
|
||||
import { dark, getCssString } from "styles"
|
||||
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import { dark, getCssString } from 'styles'
|
||||
|
||||
class MyDocument extends NextDocument {
|
||||
static async getInitialProps(ctx) {
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
// import Editor from "components/editor"
|
||||
import dynamic from "next/dynamic"
|
||||
const Editor = dynamic(() => import("components/editor"), { ssr: false })
|
||||
import Head from 'next/head'
|
||||
import dynamic from 'next/dynamic'
|
||||
const Editor = dynamic(() => import('components/editor'), { ssr: false })
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Head>
|
||||
<title>tldraw</title>
|
||||
</Head>
|
||||
<Editor />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
8
types.ts
8
types.ts
|
@ -65,6 +65,7 @@ export enum ShapeType {
|
|||
Rectangle = 'rectangle',
|
||||
Draw = 'draw',
|
||||
Arrow = 'arrow',
|
||||
Text = 'text',
|
||||
}
|
||||
|
||||
// Consider:
|
||||
|
@ -183,6 +184,11 @@ export interface ArrowShape extends BaseShape {
|
|||
}
|
||||
}
|
||||
|
||||
export interface TextShape extends BaseShape {
|
||||
type: ShapeType.Text
|
||||
text: string
|
||||
}
|
||||
|
||||
export type MutableShape =
|
||||
| DotShape
|
||||
| CircleShape
|
||||
|
@ -193,6 +199,7 @@ export type MutableShape =
|
|||
| DrawShape
|
||||
| RectangleShape
|
||||
| ArrowShape
|
||||
| TextShape
|
||||
|
||||
export type Shape = Readonly<MutableShape>
|
||||
|
||||
|
@ -206,6 +213,7 @@ export interface Shapes {
|
|||
[ShapeType.Draw]: Readonly<DrawShape>
|
||||
[ShapeType.Rectangle]: Readonly<RectangleShape>
|
||||
[ShapeType.Arrow]: Readonly<ArrowShape>
|
||||
[ShapeType.Text]: Readonly<TextShape>
|
||||
}
|
||||
|
||||
export type ShapeByType<T extends ShapeType> = Shapes[T]
|
||||
|
|
Loading…
Reference in a new issue