2021-05-28 20:30:27 +00:00
|
|
|
import styled from 'styles'
|
|
|
|
import state, { useSelector } from 'state'
|
|
|
|
import * as Panel from 'components/panel'
|
|
|
|
import { useRef } from 'react'
|
|
|
|
import { IconButton } from 'components/shared'
|
2021-06-03 12:06:39 +00:00
|
|
|
import { ChevronDown, Trash2, X } from 'react-feather'
|
2021-06-07 11:18:50 +00:00
|
|
|
import {
|
|
|
|
deepCompare,
|
|
|
|
deepCompareArrays,
|
|
|
|
getPage,
|
|
|
|
getSelectedIds,
|
|
|
|
setToArray,
|
2021-06-24 08:18:14 +00:00
|
|
|
} from 'utils'
|
2021-05-28 20:30:27 +00:00
|
|
|
import AlignDistribute from './align-distribute'
|
2021-06-02 11:50:34 +00:00
|
|
|
import { MoveType } from 'types'
|
|
|
|
import SizePicker from './size-picker'
|
2021-05-29 10:12:28 +00:00
|
|
|
import {
|
2021-05-29 22:27:19 +00:00
|
|
|
ArrowDownIcon,
|
|
|
|
ArrowUpIcon,
|
2021-05-29 10:12:28 +00:00
|
|
|
AspectRatioIcon,
|
|
|
|
BoxIcon,
|
|
|
|
CopyIcon,
|
|
|
|
EyeClosedIcon,
|
|
|
|
EyeOpenIcon,
|
|
|
|
LockClosedIcon,
|
|
|
|
LockOpen1Icon,
|
2021-05-29 22:27:19 +00:00
|
|
|
PinBottomIcon,
|
|
|
|
PinTopIcon,
|
2021-05-29 10:12:28 +00:00
|
|
|
RotateCounterClockwiseIcon,
|
|
|
|
} from '@radix-ui/react-icons'
|
2021-06-01 21:49:32 +00:00
|
|
|
import DashPicker from './dash-picker'
|
2021-06-02 11:50:34 +00:00
|
|
|
import QuickColorSelect from './quick-color-select'
|
|
|
|
import ColorPicker from './color-picker'
|
|
|
|
import IsFilledPicker from './is-filled-picker'
|
|
|
|
import QuickSizeSelect from './quick-size-select'
|
|
|
|
import QuickdashSelect from './quick-dash-select'
|
2021-06-02 21:17:38 +00:00
|
|
|
import Tooltip from 'components/tooltip'
|
2021-05-26 10:34:10 +00:00
|
|
|
|
2021-06-27 19:19:57 +00:00
|
|
|
const breakpoints = { '@initial': 'mobile', '@sm': 'small' } as any
|
|
|
|
const handleStylePanelOpen = () => state.send('TOGGLED_STYLE_PANEL_OPEN')
|
|
|
|
const handleColorChange = (color) => state.send('CHANGED_STYLE', { color })
|
|
|
|
const handleRotateCcw = () => () => state.send('ROTATED_CCW')
|
|
|
|
const handleIsFilledChange = (dash) => state.send('CHANGED_STYLE', { dash })
|
|
|
|
const handleDuplicate = () => state.send('DUPLICATED')
|
|
|
|
const handleHide = () => state.send('TOGGLED_SHAPE_HIDE')
|
|
|
|
const handleLock = () => state.send('TOGGLED_SHAPE_LOCK')
|
|
|
|
const handleAspectLock = () => state.send('TOGGLED_SHAPE_ASPECT_LOCK')
|
|
|
|
const handleMoveToBack = () => state.send('MOVED', { type: MoveType.ToBack })
|
|
|
|
const handleMoveBackward = () =>
|
|
|
|
state.send('MOVED', { type: MoveType.Backward })
|
|
|
|
const handleMoveForward = () => state.send('MOVED', { type: MoveType.Forward })
|
|
|
|
const handleMoveToFront = () => state.send('MOVED', { type: MoveType.ToFront })
|
|
|
|
const handleDelete = () => state.send('DELETED')
|
|
|
|
|
2021-06-21 21:35:28 +00:00
|
|
|
export default function StylePanel(): JSX.Element {
|
2021-05-26 10:34:10 +00:00
|
|
|
const rContainer = useRef<HTMLDivElement>(null)
|
|
|
|
const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
|
|
|
|
|
|
|
|
return (
|
2021-06-27 19:19:57 +00:00
|
|
|
<StylePanelRoot dir="ltr" ref={rContainer} isOpen={isOpen}>
|
2021-05-26 10:34:10 +00:00
|
|
|
{isOpen ? (
|
|
|
|
<SelectedShapeStyles />
|
|
|
|
) : (
|
2021-06-01 21:49:32 +00:00
|
|
|
<>
|
2021-06-02 11:50:34 +00:00
|
|
|
<QuickColorSelect />
|
|
|
|
<QuickSizeSelect />
|
|
|
|
<QuickdashSelect />
|
2021-06-01 21:49:32 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-06-01 21:49:32 +00:00
|
|
|
title="Style"
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleStylePanelOpen}
|
2021-06-01 21:49:32 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="More">
|
|
|
|
<ChevronDown />
|
|
|
|
</Tooltip>
|
2021-06-01 21:49:32 +00:00
|
|
|
</IconButton>
|
|
|
|
</>
|
2021-05-26 10:34:10 +00:00
|
|
|
)}
|
|
|
|
</StylePanelRoot>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// track of this data manually within our state.
|
|
|
|
|
2021-06-21 21:35:28 +00:00
|
|
|
function SelectedShapeStyles(): JSX.Element {
|
2021-05-26 10:34:10 +00:00
|
|
|
const selectedIds = useSelector(
|
2021-06-07 11:18:50 +00:00
|
|
|
(s) => setToArray(getSelectedIds(s.data)),
|
2021-05-26 10:34:10 +00:00
|
|
|
deepCompareArrays
|
|
|
|
)
|
|
|
|
|
2021-05-29 10:12:28 +00:00
|
|
|
const isAllLocked = useSelector((s) => {
|
|
|
|
const page = getPage(s.data)
|
|
|
|
return selectedIds.every((id) => page.shapes[id].isLocked)
|
|
|
|
})
|
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
|
2021-06-01 21:49:32 +00:00
|
|
|
const commonStyle = useSelector((s) => s.values.selectedStyle, deepCompare)
|
2021-05-26 10:34:10 +00:00
|
|
|
|
2021-05-26 21:47:46 +00:00
|
|
|
const hasSelection = selectedIds.length > 0
|
|
|
|
|
2021-05-26 10:34:10 +00:00
|
|
|
return (
|
|
|
|
<Panel.Layout>
|
2021-05-28 20:30:27 +00:00
|
|
|
<Panel.Header side="right">
|
2021-05-26 10:34:10 +00:00
|
|
|
<h3>Style</h3>
|
2021-06-02 11:50:34 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleStylePanelOpen}
|
2021-06-02 11:50:34 +00:00
|
|
|
>
|
2021-05-27 17:59:40 +00:00
|
|
|
<X />
|
|
|
|
</IconButton>
|
2021-05-26 10:34:10 +00:00
|
|
|
</Panel.Header>
|
2021-05-26 19:20:52 +00:00
|
|
|
<Content>
|
2021-06-27 19:19:57 +00:00
|
|
|
<ColorPicker color={commonStyle.color} onChange={handleColorChange} />
|
2021-06-02 11:50:34 +00:00
|
|
|
<IsFilledPicker
|
|
|
|
isFilled={commonStyle.isFilled}
|
2021-06-27 19:19:57 +00:00
|
|
|
onChange={handleIsFilledChange}
|
2021-06-02 11:50:34 +00:00
|
|
|
/>
|
2021-05-28 20:30:27 +00:00
|
|
|
<Row>
|
2021-06-02 11:50:34 +00:00
|
|
|
<label htmlFor="size">Size</label>
|
|
|
|
<SizePicker size={commonStyle.size} />
|
2021-05-28 20:30:27 +00:00
|
|
|
</Row>
|
2021-06-01 21:49:32 +00:00
|
|
|
<Row>
|
|
|
|
<label htmlFor="dash">Dash</label>
|
|
|
|
<DashPicker dash={commonStyle.dash} />
|
|
|
|
</Row>
|
2021-05-28 20:30:27 +00:00
|
|
|
<ButtonsRow>
|
2021-05-29 10:12:28 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 10:12:28 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleDuplicate}
|
2021-05-29 10:12:28 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Duplicate">
|
|
|
|
<CopyIcon />
|
|
|
|
</Tooltip>
|
2021-05-29 10:12:28 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 10:12:28 +00:00
|
|
|
<IconButton
|
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleRotateCcw}
|
2021-05-29 10:12:28 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Rotate">
|
|
|
|
<RotateCounterClockwiseIcon />
|
|
|
|
</Tooltip>
|
2021-05-29 10:12:28 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-28 20:30:27 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-28 20:30:27 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleHide}
|
2021-05-28 20:30:27 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Toogle Hidden">
|
|
|
|
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
|
|
|
</Tooltip>
|
2021-05-28 20:30:27 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 10:12:28 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 10:12:28 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleLock}
|
2021-05-29 10:12:28 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Toogle Locked">
|
|
|
|
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
|
|
|
</Tooltip>
|
2021-05-29 10:12:28 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 10:12:28 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 10:12:28 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleAspectLock}
|
2021-05-29 10:12:28 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Toogle Aspect Ratio Lock">
|
|
|
|
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
|
|
|
</Tooltip>
|
2021-05-28 20:30:27 +00:00
|
|
|
</IconButton>
|
2021-05-29 22:27:19 +00:00
|
|
|
</ButtonsRow>
|
|
|
|
<ButtonsRow>
|
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 22:27:19 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleMoveToBack}
|
2021-05-29 22:27:19 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Move to Back">
|
|
|
|
<PinBottomIcon />
|
|
|
|
</Tooltip>
|
2021-05-29 22:27:19 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 22:27:19 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 22:27:19 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleMoveBackward}
|
2021-05-29 22:27:19 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Move Backward">
|
|
|
|
<ArrowDownIcon />
|
|
|
|
</Tooltip>
|
2021-05-29 22:27:19 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 22:27:19 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 22:27:19 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleMoveForward}
|
2021-05-29 22:27:19 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Move Forward">
|
|
|
|
<ArrowUpIcon />
|
|
|
|
</Tooltip>
|
2021-05-29 22:27:19 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 22:27:19 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 22:27:19 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleMoveToFront}
|
2021-05-29 22:27:19 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="More to Front">
|
|
|
|
<PinTopIcon />
|
|
|
|
</Tooltip>
|
2021-05-29 22:27:19 +00:00
|
|
|
</IconButton>
|
2021-06-02 21:17:38 +00:00
|
|
|
|
2021-05-29 13:59:11 +00:00
|
|
|
<IconButton
|
2021-06-27 19:19:57 +00:00
|
|
|
bp={breakpoints}
|
2021-05-29 13:59:11 +00:00
|
|
|
disabled={!hasSelection}
|
2021-06-02 11:50:34 +00:00
|
|
|
size="small"
|
2021-06-27 19:19:57 +00:00
|
|
|
onClick={handleDelete}
|
2021-05-29 13:59:11 +00:00
|
|
|
>
|
2021-06-02 21:17:38 +00:00
|
|
|
<Tooltip label="Delete">
|
|
|
|
<Trash2 size="15" />
|
|
|
|
</Tooltip>
|
2021-05-29 13:59:11 +00:00
|
|
|
</IconButton>
|
2021-05-28 20:30:27 +00:00
|
|
|
</ButtonsRow>
|
2021-05-29 22:27:19 +00:00
|
|
|
<AlignDistribute
|
|
|
|
hasTwoOrMore={selectedIds.length > 1}
|
|
|
|
hasThreeOrMore={selectedIds.length > 2}
|
|
|
|
/>
|
2021-05-26 19:20:52 +00:00
|
|
|
</Content>
|
2021-05-26 10:34:10 +00:00
|
|
|
</Panel.Layout>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const StylePanelRoot = styled(Panel.Root, {
|
2021-05-26 19:20:52 +00:00
|
|
|
minWidth: 1,
|
|
|
|
width: 184,
|
|
|
|
maxWidth: 184,
|
2021-05-28 20:30:27 +00:00
|
|
|
overflow: 'hidden',
|
|
|
|
position: 'relative',
|
2021-05-30 13:49:33 +00:00
|
|
|
border: '1px solid $panel',
|
2021-06-02 11:50:34 +00:00
|
|
|
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
2021-06-01 21:49:32 +00:00
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
pointerEvents: 'all',
|
2021-05-26 10:34:10 +00:00
|
|
|
|
|
|
|
variants: {
|
|
|
|
isOpen: {
|
2021-05-26 19:20:52 +00:00
|
|
|
true: {},
|
2021-05-26 10:34:10 +00:00
|
|
|
false: {
|
2021-05-30 13:49:33 +00:00
|
|
|
padding: 2,
|
2021-06-01 21:49:32 +00:00
|
|
|
width: 'fit-content',
|
2021-05-26 10:34:10 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2021-05-26 19:20:52 +00:00
|
|
|
const Content = styled(Panel.Content, {
|
|
|
|
padding: 8,
|
2021-05-26 10:34:10 +00:00
|
|
|
})
|
2021-05-28 20:30:27 +00:00
|
|
|
|
|
|
|
const Row = styled('div', {
|
|
|
|
position: 'relative',
|
|
|
|
display: 'flex',
|
|
|
|
width: '100%',
|
|
|
|
background: 'none',
|
|
|
|
border: 'none',
|
|
|
|
outline: 'none',
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
padding: '4px 2px 4px 12px',
|
|
|
|
|
|
|
|
'& label': {
|
|
|
|
fontFamily: '$ui',
|
2021-06-03 12:20:34 +00:00
|
|
|
fontSize: '$1',
|
2021-05-28 20:30:27 +00:00
|
|
|
fontWeight: '$1',
|
|
|
|
margin: 0,
|
|
|
|
padding: 0,
|
|
|
|
},
|
|
|
|
|
|
|
|
'& > svg': {
|
|
|
|
position: 'relative',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
const ButtonsRow = styled('div', {
|
|
|
|
position: 'relative',
|
|
|
|
display: 'flex',
|
|
|
|
width: '100%',
|
|
|
|
background: 'none',
|
|
|
|
border: 'none',
|
|
|
|
cursor: 'pointer',
|
|
|
|
outline: 'none',
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'flex-start',
|
|
|
|
padding: 4,
|
|
|
|
})
|