perf improvements around selected / hovered shapes

pull/3/head
Steve Ruiz 2021-06-28 13:13:34 +01:00
rodzic 2cfeea0449
commit 8ff8b87a9e
26 zmienionych plików z 643 dodań i 612 usunięć

Wyświetl plik

@ -2,11 +2,9 @@ import * as React from 'react'
import { Edge, Corner } from 'types'
import { useSelector } from 'state'
import {
deepCompareArrays,
getBoundsCenter,
getCurrentCamera,
getPage,
getSelectedIds,
getSelectedShapes,
isMobile,
} from 'utils'
@ -24,25 +22,22 @@ export default function Bounds(): JSX.Element {
const bounds = useSelector((s) => s.values.selectedBounds)
const selectedIds = useSelector(
(s) => Array.from(s.values.selectedIds.values()),
deepCompareArrays
)
const rotation = useSelector(({ data }) =>
getSelectedIds(data).size === 1 ? getSelectedShapes(data)[0].rotation : 0
const rotation = useSelector((s) =>
s.values.selectedIds.length === 1
? getSelectedShapes(s.data)[0].rotation
: 0
)
const isAllLocked = useSelector((s) => {
const page = getPage(s.data)
return selectedIds.every((id) => page.shapes[id]?.isLocked)
return s.values.selectedIds.every((id) => page.shapes[id]?.isLocked)
})
const isSingleHandles = useSelector((s) => {
const page = getPage(s.data)
return (
selectedIds.length === 1 &&
page.shapes[selectedIds[0]]?.handles !== undefined
s.values.selectedIds.length === 1 &&
page.shapes[s.values.selectedIds[0]]?.handles !== undefined
)
})

Wyświetl plik

@ -2,7 +2,7 @@ import { useRef } from 'react'
import state, { useSelector } from 'state'
import inputs from 'state/inputs'
import styled from 'styles'
import { deepCompareArrays, getPage } from 'utils'
import { getPage } from 'utils'
function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
if (!inputs.canAccept(e.pointerId)) return
@ -31,28 +31,30 @@ export default function BoundsBg(): JSX.Element {
const isSelecting = useSelector((s) => s.isIn('selecting'))
const selectedIds = useSelector(
(s) => Array.from(s.values.selectedIds.values()),
deepCompareArrays
)
const rotation = useSelector((s) => {
const selectedIds = s.values.selectedIds
if (selectedIds.length === 1) {
const { shapes } = getPage(s.data)
const selected = Array.from(s.values.selectedIds.values())[0]
return shapes[selected]?.rotation
const selected = selectedIds[0]
const page = getPage(s.data)
return page.shapes[selected]?.rotation
} else {
return 0
}
})
const isAllHandles = useSelector((s) => {
const page = getPage(s.data)
const selectedIds = Array.from(s.values.selectedIds.values())
return (
selectedIds.length === 1 &&
page.shapes[selectedIds[0]]?.handles !== undefined
)
const selectedIds = s.values.selectedIds
if (selectedIds.length === 1) {
const page = getPage(s.data)
const selected = selectedIds[0]
return (
selectedIds.length === 1 && page.shapes[selected]?.handles !== undefined
)
}
})
if (isAllHandles) return null

Wyświetl plik

@ -3,18 +3,14 @@ import { getShapeUtils } from 'state/shape-utils'
import { useRef } from 'react'
import { useSelector } from 'state'
import styled from 'styles'
import { deepCompareArrays, getPage } from 'utils'
import { getPage } from 'utils'
import vec from 'utils/vec'
export default function Handles(): JSX.Element {
const selectedIds = useSelector(
(s) => Array.from(s.values.selectedIds.values()),
deepCompareArrays
)
const shape = useSelector(
({ data }) =>
selectedIds.length === 1 && getPage(data).shapes[selectedIds[0]]
(s) =>
s.values.selectedIds.length === 1 &&
getPage(s.data).shapes[s.values.selectedIds[0]]
)
const isSelecting = useSelector((s) =>

Wyświetl plik

@ -13,6 +13,10 @@ import Handles from './bounds/handles'
import useCanvasEvents from 'hooks/useCanvasEvents'
import ContextMenu from './context-menu/context-menu'
function resetError() {
null
}
export default function Canvas(): JSX.Element {
const rCanvas = useRef<SVGSVGElement>(null)
const rGroup = useRef<SVGGElement>(null)
@ -28,12 +32,7 @@ export default function Canvas(): JSX.Element {
return (
<ContextMenu>
<MainSVG ref={rCanvas} {...events}>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={resetError}>
<Defs />
{isReady && (
<g ref={rGroup} id="shapes">

Wyświetl plik

@ -5,14 +5,7 @@ import {
IconButton as _IconButton,
RowButton,
} from 'components/shared'
import {
commandKey,
deepCompareArrays,
getSelectedIds,
getShape,
isMobile,
setToArray,
} from 'utils'
import { commandKey, deepCompareArrays, getShape, isMobile } from 'utils'
import state, { useSelector } from 'state'
import {
AlignType,
@ -82,7 +75,7 @@ export default function ContextMenu({
children: React.ReactNode
}): JSX.Element {
const selectedShapeIds = useSelector(
(s) => setToArray(getSelectedIds(s.data)),
(s) => s.values.selectedIds,
deepCompareArrays
)

Wyświetl plik

@ -1,6 +1,6 @@
import { getShapeStyle } from 'state/shape-styles'
import { getShapeUtils } from 'state/shape-utils'
import React, { memo } from 'react'
import React from 'react'
import { useSelector } from 'state'
import { getCurrentCamera } from 'utils'
import { DotCircle, Handle } from './misc'
@ -12,28 +12,32 @@ export default function Defs(): JSX.Element {
return (
<defs>
{shapeIdsToRender.map((id) => (
<Def key={id} id={id} />
))}
<DotCircle id="dot" r={4} />
<Handle id="handle" r={4} />
<ExpandDef />
{shapeIdsToRender.map((id) => (
<Def key={id} id={id} />
))}
</defs>
)
}
const Def = memo(function Def({ id }: { id: string }) {
function Def({ id }: { id: string }) {
const shape = useShapeDef(id)
if (!shape) return null
const style = getShapeStyle(shape.style)
return React.cloneElement(
getShapeUtils(shape).render(shape, { isEditing: false }),
{ id, ...style }
return (
<>
{React.cloneElement(
getShapeUtils(shape).render(shape, { isEditing: false }),
{ id, ...style, strokeWidth: undefined }
)}
</>
)
})
}
function ExpandDef() {
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)

Wyświetl plik

@ -0,0 +1,43 @@
import { memo } from 'react'
import { getShape } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
import vec from 'utils/vec'
import styled from 'styles'
import { useSelector } from 'state'
import { getShapeStyle } from 'state/shape-styles'
function HoveredShape({ id }: { id: string }) {
const transform = useSelector((s) => {
const shape = getShape(s.data, id)
const center = getShapeUtils(shape).getCenter(shape)
const rotation = shape.rotation * (180 / Math.PI)
const parentPoint = getShape(s.data, shape.parentId)?.point || [0, 0]
return `
translate(${vec.neg(parentPoint)})
rotate(${rotation}, ${center})
translate(${shape.point})
`
})
const strokeWidth = useSelector((s) => {
const shape = getShape(s.data, id)
const style = getShapeStyle(shape.style)
return +style.strokeWidth
})
return (
<g transform={transform}>
<StyledHoverShape href={'#' + id} strokeWidth={strokeWidth + 8} />
<text>hello</text>
</g>
)
}
const StyledHoverShape = styled('use', {
stroke: '$selected',
filter: 'url(#expand)',
opacity: 0.1,
})
export default memo(HoveredShape)

Wyświetl plik

@ -1,5 +1,6 @@
import { useSelector } from 'state'
import Shape from './shape'
import HoveredShape from './hovered-shape'
import usePageShapes from 'hooks/usePageShapes'
/*
@ -8,22 +9,22 @@ on the current page. Kind of expensive but only happens
here; and still cheaper than any other pattern I've found.
*/
const noOffset = [0, 0]
export default function Page(): JSX.Element {
const currentPageShapeIds = usePageShapes()
const isSelecting = useSelector((s) => s.isIn('selecting'))
const visiblePageShapeIds = usePageShapes()
const hoveredShapeId = useSelector((s) => {
return visiblePageShapeIds.find((id) => id === s.data.hoveredId)
})
return (
<g pointerEvents={isSelecting ? 'all' : 'none'}>
{currentPageShapeIds.map((shapeId) => (
<Shape
key={shapeId}
id={shapeId}
isSelecting={isSelecting}
parentPoint={noOffset}
/>
{isSelecting && hoveredShapeId && (
<HoveredShape key={hoveredShapeId} id={hoveredShapeId} />
)}
{visiblePageShapeIds.map((id) => (
<Shape key={id} id={id} isSelecting={isSelecting} />
))}
</g>
)

Wyświetl plik

@ -1,12 +1,12 @@
import styled from 'styles'
import { useSelector } from 'state'
import { deepCompareArrays, getPage, getSelectedIds, setToArray } from 'utils'
import { deepCompareArrays, getPage } from 'utils'
import { getShapeUtils } from 'state/shape-utils'
import { memo } from 'react'
export default function Selected(): JSX.Element {
const currentSelectedShapeIds = useSelector(
({ data }) => setToArray(getSelectedIds(data)),
(s) => s.values.selectedIds,
deepCompareArrays
)

Wyświetl plik

@ -2,31 +2,150 @@ import React, { useRef, memo, useEffect } from 'react'
import { useSelector } from 'state'
import styled from 'styles'
import { getShapeUtils } from 'state/shape-utils'
import { getPage, getSelectedIds, isMobile } from 'utils'
import { deepCompareArrays, getPage, getShape } from 'utils'
import useShapeEvents from 'hooks/useShapeEvents'
import { Shape as _Shape } from 'types'
import vec from 'utils/vec'
import { getShapeStyle } from 'state/shape-styles'
const isMobileDevice = isMobile()
import useShapeDef from 'hooks/useShape'
interface ShapeProps {
id: string
isSelecting: boolean
parentPoint: number[]
}
function Shape({ id, isSelecting, parentPoint }: ShapeProps): JSX.Element {
function Shape({ id, isSelecting }: ShapeProps): JSX.Element {
const rGroup = useRef<SVGGElement>(null)
const shapeUtils = useSelector((s) => {
const shape = getShape(s.data, id)
return getShapeUtils(shape)
})
const isHidden = useSelector((s) => {
const shape = getShape(s.data, id)
return shape.isHidden
})
const children = useSelector((s) => {
const shape = getShape(s.data, id)
return shape.children
}, deepCompareArrays)
const isParent = shapeUtils.isParent
const isForeignObject = shapeUtils.isForeignObject
const strokeWidth = useSelector((s) => {
const shape = getShape(s.data, id)
const style = getShapeStyle(shape.style)
return +style.strokeWidth
})
const transform = useSelector((s) => {
const shape = getShape(s.data, id)
const center = shapeUtils.getCenter(shape)
const rotation = shape.rotation * (180 / Math.PI)
const parentPoint = getShape(s.data, shape.parentId)?.point || [0, 0]
return `
translate(${vec.neg(parentPoint)})
rotate(${rotation}, ${center})
translate(${shape.point})
`
})
const events = useShapeEvents(id, isParent, rGroup)
return (
<StyledGroup
id={id + '-group'}
ref={rGroup}
transform={transform}
{...events}
>
{isSelecting &&
(isForeignObject ? (
<ForeignObjectHover id={id} />
) : (
<EventSoak
as="use"
href={'#' + id}
strokeWidth={strokeWidth + 8}
variant={shapeUtils.canStyleFill ? 'filled' : 'hollow'}
/>
))}
{!isHidden &&
(isForeignObject ? (
<ForeignObjectRender id={id} />
) : (
<RealShape id={id} isParent={isParent} strokeWidth={strokeWidth} />
))}
{isParent &&
children.map((shapeId) => (
<Shape key={shapeId} id={shapeId} isSelecting={isSelecting} />
))}
</StyledGroup>
)
}
interface RealShapeProps {
id: string
isParent: boolean
strokeWidth: number
}
const RealShape = memo(function RealShape({
id,
isParent,
strokeWidth,
}: RealShapeProps) {
return (
<StyledShape
as="use"
data-shy={isParent}
href={'#' + id}
strokeWidth={strokeWidth}
/>
)
})
const ForeignObjectHover = memo(function ForeignObjectHover({
id,
}: {
id: string
}) {
const size = useSelector((s) => {
const shape = getPage(s.data).shapes[id]
const bounds = getShapeUtils(shape).getBounds(shape)
return [bounds.width, bounds.height]
}, deepCompareArrays)
return (
<EventSoak
as="rect"
width={size[0]}
height={size[1]}
strokeWidth={1.5}
variant={'ghost'}
/>
)
})
const ForeignObjectRender = memo(function ForeignObjectRender({
id,
}: {
id: string
}) {
const shape = useShapeDef(id)
const rFocusable = useRef<HTMLTextAreaElement>(null)
const isEditing = useSelector((s) => s.data.editingId === id)
const isSelected = useSelector((s) => getSelectedIds(s.data).has(id))
const shape = useSelector((s) => getPage(s.data).shapes[id])
const events = useShapeEvents(id, getShapeUtils(shape)?.isParent, rGroup)
const shapeUtils = getShapeUtils(shape)
useEffect(() => {
if (isEditing) {
@ -38,85 +157,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps): JSX.Element {
}
}, [isEditing])
// This is a problem with deleted shapes. The hooks in this component
// may sometimes run before the hook in the Page component, which means
// a deleted shape will still be pulled here before the page component
// detects the change and pulls this component.
if (!shape) return null
const style = getShapeStyle(shape.style)
const shapeUtils = getShapeUtils(shape)
const { isShy, isParent, isForeignObject } = shapeUtils
const bounds = shapeUtils.getBounds(shape)
const center = shapeUtils.getCenter(shape)
const rotation = shape.rotation * (180 / Math.PI)
const transform = `
translate(${vec.neg(parentPoint)})
rotate(${rotation}, ${center})
translate(${shape.point})
`
return (
<StyledGroup
id={id + '-group'}
ref={rGroup}
transform={transform}
isSelected={isSelected}
device={isMobileDevice ? 'mobile' : 'desktop'}
{...events}
>
{isSelecting && !isShy && (
<>
{isForeignObject ? (
<HoverIndicator
as="rect"
width={bounds.width}
height={bounds.height}
strokeWidth={1.5}
variant={'ghost'}
/>
) : (
<HoverIndicator
as="use"
href={'#' + id}
strokeWidth={+style.strokeWidth + 5}
variant={shapeUtils.canStyleFill ? 'filled' : 'hollow'}
/>
)}
</>
)}
{!shape.isHidden &&
(isForeignObject ? (
shapeUtils.render(shape, { isEditing, ref: rFocusable })
) : (
<RealShape id={id} isParent={isParent} shape={shape} />
))}
{isParent &&
shape.children.map((shapeId) => (
<Shape
key={shapeId}
id={shapeId}
isSelecting={isSelecting}
parentPoint={shape.point}
/>
))}
</StyledGroup>
)
}
interface RealShapeProps {
id: string
shape: _Shape
isParent: boolean
}
const RealShape = memo(function RealShape({ id, isParent }: RealShapeProps) {
return <StyledShape as="use" data-shy={isParent} href={'#' + id} />
return shapeUtils.render(shape, { isEditing, ref: rFocusable })
})
const StyledShape = styled('path', {
@ -125,12 +166,10 @@ const StyledShape = styled('path', {
pointerEvents: 'none',
})
const HoverIndicator = styled('path', {
stroke: '$selected',
const EventSoak = styled('use', {
opacity: 0,
strokeLinecap: 'round',
strokeLinejoin: 'round',
fill: 'transparent',
filter: 'url(#expand)',
variants: {
variant: {
ghost: {
@ -150,81 +189,6 @@ const HoverIndicator = styled('path', {
const StyledGroup = styled('g', {
outline: 'none',
[`& *[data-shy="true"]`]: {
opacity: '0',
},
[`& ${HoverIndicator}`]: {
opacity: '0',
},
variants: {
device: {
mobile: {},
desktop: {},
},
isSelected: {
true: {
[`& *[data-shy="true"]`]: {
opacity: '1',
},
[`& ${HoverIndicator}`]: {
opacity: '0.2',
},
},
false: {
[`& ${HoverIndicator}`]: {
opacity: '0',
},
},
},
},
compoundVariants: [
{
device: 'desktop',
isSelected: 'false',
css: {
[`&:hover ${HoverIndicator}`]: {
opacity: '0.16',
},
[`&:hover *[data-shy="true"]`]: {
opacity: '1',
},
},
},
{
device: 'desktop',
isSelected: 'true',
css: {
[`&:hover ${HoverIndicator}`]: {
opacity: '0.25',
},
[`&:active ${HoverIndicator}`]: {
opacity: '0.25',
},
},
},
],
})
// function Label({ children }: { children: React.ReactNode }) {
// return (
// <text
// y={4}
// x={4}
// fontSize={12}
// fill="black"
// stroke="none"
// alignmentBaseline="text-before-edge"
// pointerEvents="none"
// >
// {children}
// </text>
// )
// }
// function pp(n: number[]) {
// return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
// }
export { HoverIndicator }
export default memo(Shape)

Wyświetl plik

@ -3,40 +3,44 @@ import styled from 'styles'
import React, { useRef } from 'react'
import state, { useSelector } from 'state'
import { X, Code } from 'react-feather'
import { IconButton } from 'components/shared'
import { breakpoints, IconButton } from 'components/shared'
import * as Panel from '../panel'
import Control from './control'
import { deepCompareArrays } from 'utils'
function openCodePanel() {
state.send('CLOSED_CODE_PANEL')
}
function closeCodePanel() {
state.send('OPENED_CODE_PANEL')
}
const stopKeyboardPropagation = (e: KeyboardEvent | React.KeyboardEvent) =>
e.stopPropagation()
export default function ControlPanel(): JSX.Element {
const rContainer = useRef<HTMLDivElement>(null)
const isOpen = useSelector((s) => Object.keys(s.data.codeControls).length > 0)
const codeControls = useSelector(
(state) => Object.keys(state.data.codeControls),
deepCompareArrays
)
const isOpen = useSelector((s) => Object.keys(s.data.codeControls).length > 0)
return (
<Panel.Root
ref={rContainer}
dir="ltr"
data-bp-desktop
ref={rContainer}
isOpen={isOpen}
variant="controls"
isOpen={isOpen}
onKeyDown={stopKeyboardPropagation}
onKeyUp={stopKeyboardPropagation}
>
{isOpen ? (
<Panel.Layout>
<Panel.Header>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size="small"
onClick={() => state.send('CLOSED_CODE_PANEL')}
>
<IconButton bp={breakpoints} size="small" onClick={closeCodePanel}>
<X />
</IconButton>
<h3>Controls</h3>
@ -48,11 +52,7 @@ export default function ControlPanel(): JSX.Element {
</ControlsList>
</Panel.Layout>
) : (
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size="small"
onClick={() => state.send('OPENED_CODE_PANEL')}
>
<IconButton bp={breakpoints} size="small" onClick={openCodePanel}>
<Code />
</IconButton>
)}

Wyświetl plik

@ -2,7 +2,7 @@ import styled from 'styles'
import * as ContextMenu from '@radix-ui/react-context-menu'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { IconWrapper, RowButton } from 'components/shared'
import { breakpoints, IconWrapper, RowButton } from 'components/shared'
import { CheckIcon, PlusIcon } from '@radix-ui/react-icons'
import * as Panel from '../panel'
import state, { useSelector } from 'state'
@ -40,8 +40,8 @@ export default function PagePanel(): JSX.Element {
<PanelRoot dir="ltr">
<DropdownMenu.Trigger
as={RowButton}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
css={{ paddingRight: 12 }}
bp={breakpoints}
variant="pageButton"
>
<span>{documentPages[currentPageId].name}</span>
</DropdownMenu.Trigger>
@ -58,11 +58,7 @@ export default function PagePanel(): JSX.Element {
{sorted.map(({ id, name }) => (
<ContextMenu.Root dir="ltr" key={id}>
<ContextMenu.Trigger>
<StyledRadioItem
key={id}
value={id}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
>
<StyledRadioItem key={id} value={id} bp={breakpoints}>
<span>{name}</span>
<DropdownMenu.ItemIndicator as={IconWrapper} size="small">
<CheckIcon />
@ -91,7 +87,7 @@ export default function PagePanel(): JSX.Element {
</DropdownMenu.RadioGroup>
<DropdownMenu.Separator />
<RowButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
onClick={() => {
setIsOpen(false)
state.send('CREATED_PAGE')

Wyświetl plik

@ -3,6 +3,8 @@ import * as RadioGroup from '@radix-ui/react-radio-group'
import * as Panel from './panel'
import styled from 'styles'
export const breakpoints: any = { '@initial': 'mobile', '@sm': 'small' }
export const IconButton = styled('button', {
height: '32px',
width: '32px',
@ -124,6 +126,11 @@ export const RowButton = styled('button', {
width: 'auto',
},
},
variant: {
pageButton: {
paddingRight: 12,
},
},
},
})

Wyświetl plik

@ -10,7 +10,8 @@ import {
StretchHorizontallyIcon,
StretchVerticallyIcon,
} from '@radix-ui/react-icons'
import { IconButton } from 'components/shared'
import { breakpoints, IconButton } from 'components/shared'
import { memo } from 'react'
import state from 'state'
import styled from 'styles'
import { AlignType, DistributeType, StretchType } from 'types'
@ -55,7 +56,7 @@ function distributeHorizontally() {
state.send('DISTRIBUTED', { type: DistributeType.Horizontal })
}
export default function AlignDistribute({
function AlignDistribute({
hasTwoOrMore,
hasThreeOrMore,
}: {
@ -65,7 +66,7 @@ export default function AlignDistribute({
return (
<Container>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={alignLeft}
@ -73,7 +74,7 @@ export default function AlignDistribute({
<AlignLeftIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={alignCenterHorizontal}
@ -81,7 +82,7 @@ export default function AlignDistribute({
<AlignCenterHorizontallyIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={alignRight}
@ -89,7 +90,7 @@ export default function AlignDistribute({
<AlignRightIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={stretchHorizontally}
@ -97,7 +98,7 @@ export default function AlignDistribute({
<StretchHorizontallyIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasThreeOrMore}
onClick={distributeHorizontally}
@ -105,7 +106,7 @@ export default function AlignDistribute({
<SpaceEvenlyHorizontallyIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={alignTop}
@ -113,7 +114,7 @@ export default function AlignDistribute({
<AlignTopIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={alignCenterVertical}
@ -121,7 +122,7 @@ export default function AlignDistribute({
<AlignCenterVerticallyIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={alignBottom}
@ -129,7 +130,7 @@ export default function AlignDistribute({
<AlignBottomIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasTwoOrMore}
onClick={stretchVertically}
@ -137,7 +138,7 @@ export default function AlignDistribute({
<StretchVerticallyIcon />
</IconButton>
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
size="small"
disabled={!hasThreeOrMore}
onClick={distributeVertically}
@ -148,6 +149,8 @@ export default function AlignDistribute({
)
}
export default memo(AlignDistribute)
const Container = styled('div', {
display: 'grid',
padding: 4,

Wyświetl plik

@ -4,12 +4,16 @@ import { ColorStyle } from 'types'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { Square } from 'react-feather'
import { DropdownContent } from '../shared'
import { memo } from 'react'
import state from 'state'
export default function ColorContent({
onChange,
}: {
onChange: (color: ColorStyle) => void
}): JSX.Element {
function handleColorChange(
e: Event & { currentTarget: { value: ColorStyle } }
): void {
state.send('CHANGED_STYLE', { color: e.currentTarget.value })
}
function ColorContent(): JSX.Element {
return (
<DropdownContent sideOffset={8} side="bottom">
{Object.keys(strokes).map((color: ColorStyle) => (
@ -17,7 +21,8 @@ export default function ColorContent({
as={IconButton}
key={color}
title={color}
onSelect={() => onChange(color)}
value={color}
onSelect={handleColorChange}
>
<Square fill={strokes[color]} stroke="none" size="22" />
</DropdownMenu.DropdownMenuItem>
@ -25,3 +30,5 @@ export default function ColorContent({
</DropdownContent>
)
}
export default memo(ColorContent)

Wyświetl plik

@ -1,28 +1,25 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { strokes } from 'state/shape-styles'
import { ColorStyle } from 'types'
import { RowButton, IconWrapper } from '../shared'
import { RowButton, IconWrapper, breakpoints } from '../shared'
import { Square } from 'react-feather'
import ColorContent from './color-content'
import { memo } from 'react'
import { useSelector } from 'state'
interface Props {
color: ColorStyle
onChange: (color: ColorStyle) => void
}
function ColorPicker(): JSX.Element {
const color = useSelector((s) => s.values.selectedStyle.color)
export default function ColorPicker({ color, onChange }: Props): JSX.Element {
return (
<DropdownMenu.Root dir="ltr">
<DropdownMenu.Trigger
as={RowButton}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
>
<DropdownMenu.Trigger as={RowButton} bp={breakpoints}>
<label htmlFor="color">Color</label>
<IconWrapper>
<Square fill={strokes[color]} />
</IconWrapper>
</DropdownMenu.Trigger>
<ColorContent onChange={onChange} />
<ColorContent />
</DropdownMenu.Root>
)
}
export default memo(ColorPicker)

Wyświetl plik

@ -7,40 +7,36 @@ import {
} from '../shared'
import * as RadioGroup from '@radix-ui/react-radio-group'
import { DashStyle } from 'types'
import state from 'state'
import state, { useSelector } from 'state'
import { memo } from 'react'
function handleChange(dash: string) {
state.send('CHANGED_STYLE', { dash })
}
interface Props {
dash: DashStyle
const dashes = {
[DashStyle.Solid]: <DashSolidIcon />,
[DashStyle.Dashed]: <DashDashedIcon />,
[DashStyle.Dotted]: <DashDottedIcon />,
}
export default function DashPicker({ dash }: Props): JSX.Element {
function DashPicker(): JSX.Element {
const dash = useSelector((s) => s.values.selectedStyle.dash)
return (
<Group name="Dash" onValueChange={handleChange}>
<Item
as={RadioGroup.RadioGroupItem}
value={DashStyle.Solid}
isActive={dash === DashStyle.Solid}
>
<DashSolidIcon />
</Item>
<Item
as={RadioGroup.RadioGroupItem}
value={DashStyle.Dashed}
isActive={dash === DashStyle.Dashed}
>
<DashDashedIcon />
</Item>
<Item
as={RadioGroup.RadioGroupItem}
value={DashStyle.Dotted}
isActive={dash === DashStyle.Dotted}
>
<DashDottedIcon />
</Item>
{Object.keys(DashStyle).map((dashStyle: DashStyle) => (
<RadioGroup.RadioGroupItem
as={Item}
key={dashStyle}
isActive={dash === dashStyle}
value={dashStyle}
>
{dashes[dashStyle]}
</RadioGroup.RadioGroupItem>
))}
</Group>
)
}
export default memo(DashPicker)

Wyświetl plik

@ -2,24 +2,23 @@ import * as Checkbox from '@radix-ui/react-checkbox'
import { CheckIcon } from '@radix-ui/react-icons'
import { strokes } from 'state/shape-styles'
import { Square } from 'react-feather'
import { IconWrapper, RowButton } from '../shared'
import { breakpoints, IconWrapper, RowButton } from '../shared'
import state, { useSelector } from 'state'
interface Props {
isFilled: boolean
onChange: (isFilled: boolean | string) => void
function handleIsFilledChange(isFilled: boolean) {
state.send('CHANGED_STYLE', { isFilled })
}
export default function IsFilledPicker({
isFilled,
onChange,
}: Props): JSX.Element {
export default function IsFilledPicker(): JSX.Element {
const isFilled = useSelector((s) => s.values.selectedStyle.isFilled)
return (
<Checkbox.Root
dir="ltr"
as={RowButton}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
bp={breakpoints}
checked={isFilled}
onCheckedChange={onChange}
onCheckedChange={handleIsFilledChange}
>
<label htmlFor="fill">Fill</label>
<IconWrapper>

Wyświetl plik

@ -1,9 +1,9 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { IconButton } from 'components/shared'
import { breakpoints, IconButton } from 'components/shared'
import Tooltip from 'components/tooltip'
import { strokes } from 'state/shape-styles'
import { Square } from 'react-feather'
import state, { useSelector } from 'state'
import { useSelector } from 'state'
import ColorContent from './color-content'
export default function QuickColorSelect(): JSX.Element {
@ -11,17 +11,12 @@ export default function QuickColorSelect(): JSX.Element {
return (
<DropdownMenu.Root dir="ltr">
<DropdownMenu.Trigger
as={IconButton}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
>
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
<Tooltip label="Color">
<Square fill={strokes[color]} stroke={strokes[color]} />
</Tooltip>
</DropdownMenu.Trigger>
<ColorContent
onChange={(color) => state.send('CHANGED_STYLE', { color })}
/>
<ColorContent />
</DropdownMenu.Root>
)
}

Wyświetl plik

@ -1,6 +1,7 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { IconButton } from 'components/shared'
import { breakpoints, IconButton } from 'components/shared'
import Tooltip from 'components/tooltip'
import { memo } from 'react'
import state, { useSelector } from 'state'
import { DashStyle } from 'types'
import {
@ -17,40 +18,35 @@ const dashes = {
[DashStyle.Dotted]: <DashDottedIcon />,
}
export default function QuickdashSelect(): JSX.Element {
function changeDashStyle(
e: Event & { currentTarget: { value: DashStyle } }
): void {
state.send('CHANGED_STYLE', { dash: e.currentTarget.value })
}
function QuickdashSelect(): JSX.Element {
const dash = useSelector((s) => s.values.selectedStyle.dash)
return (
<DropdownMenu.Root dir="ltr">
<DropdownMenu.Trigger
as={IconButton}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
>
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
<Tooltip label="Dash">{dashes[dash]}</Tooltip>
</DropdownMenu.Trigger>
<DropdownContent sideOffset={8} 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}
/>
{Object.keys(DashStyle).map((dashStyle: DashStyle) => (
<DropdownMenu.DropdownMenuItem
as={Item}
key={dashStyle}
isActive={dash === dashStyle}
onSelect={changeDashStyle}
value={dashStyle}
>
{dashes[dashStyle]}
</DropdownMenu.DropdownMenuItem>
))}
</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>
)
}
export default memo(QuickdashSelect)

Wyświetl plik

@ -1,6 +1,7 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { IconButton } from 'components/shared'
import { breakpoints, IconButton } from 'components/shared'
import Tooltip from 'components/tooltip'
import { memo } from 'react'
import { Circle } from 'react-feather'
import state, { useSelector } from 'state'
import { SizeStyle } from 'types'
@ -12,39 +13,37 @@ const sizes = {
[SizeStyle.Large]: 22,
}
export default function QuickSizeSelect(): JSX.Element {
function handleSizeChange(
e: Event & { currentTarget: { value: SizeStyle } }
): void {
state.send('CHANGED_STYLE', { size: e.currentTarget.value })
}
function QuickSizeSelect(): JSX.Element {
const size = useSelector((s) => s.values.selectedStyle.size)
return (
<DropdownMenu.Root dir="ltr">
<DropdownMenu.Trigger
as={IconButton}
bp={{ '@initial': 'mobile', '@sm': 'small' }}
>
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
<Tooltip label="Size">
<Circle size={sizes[size]} stroke="none" fill="currentColor" />
</Tooltip>
</DropdownMenu.Trigger>
<DropdownContent sideOffset={8} 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} />
{Object.keys(SizeStyle).map((sizeStyle: SizeStyle) => (
<DropdownMenu.DropdownMenuItem
key={sizeStyle}
as={Item}
isActive={size === sizeStyle}
value={sizeStyle}
onSelect={handleSizeChange}
>
<Circle size={sizes[sizeStyle]} />
</DropdownMenu.DropdownMenuItem>
))}
</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>
)
}
export default memo(QuickSizeSelect)

Wyświetl plik

@ -0,0 +1,217 @@
import { IconButton, breakpoints } from 'components/shared'
import { memo } from 'react'
import styled from 'styles'
import { MoveType } from 'types'
import { Trash2 } from 'react-feather'
import state, { useSelector } from 'state'
import Tooltip from 'components/tooltip'
import {
ArrowDownIcon,
ArrowUpIcon,
AspectRatioIcon,
BoxIcon,
CopyIcon,
EyeClosedIcon,
EyeOpenIcon,
LockClosedIcon,
LockOpen1Icon,
PinBottomIcon,
PinTopIcon,
RotateCounterClockwiseIcon,
} from '@radix-ui/react-icons'
import { getPage, getSelectedIds } from 'utils'
function handleRotateCcw() {
state.send('ROTATED_CCW')
}
function handleDuplicate() {
state.send('DUPLICATED')
}
function handleHide() {
state.send('TOGGLED_SHAPE_HIDE')
}
function handleLock() {
state.send('TOGGLED_SHAPE_LOCK')
}
function handleAspectLock() {
state.send('TOGGLED_SHAPE_ASPECT_LOCK')
}
function handleMoveToBack() {
state.send('MOVED', { type: MoveType.ToBack })
}
function handleMoveBackward() {
state.send('MOVED', { type: MoveType.Backward })
}
function handleMoveForward() {
state.send('MOVED', { type: MoveType.Forward })
}
function handleMoveToFront() {
state.send('MOVED', { type: MoveType.ToFront })
}
function handleDelete() {
state.send('DELETED')
}
function ShapesFunctions() {
const isAllLocked = useSelector((s) => {
const page = getPage(s.data)
return s.values.selectedIds.every((id) => page.shapes[id].isLocked)
})
const isAllAspectLocked = useSelector((s) => {
const page = getPage(s.data)
return s.values.selectedIds.every(
(id) => page.shapes[id].isAspectRatioLocked
)
})
const isAllHidden = useSelector((s) => {
const page = getPage(s.data)
return s.values.selectedIds.every((id) => page.shapes[id].isHidden)
})
const hasSelection = useSelector((s) => {
return getSelectedIds(s.data).size > 0
})
return (
<>
<ButtonsRow>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleDuplicate}
>
<Tooltip label="Duplicate">
<CopyIcon />
</Tooltip>
</IconButton>
<IconButton
disabled={!hasSelection}
size="small"
onClick={handleRotateCcw}
>
<Tooltip label="Rotate">
<RotateCounterClockwiseIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleHide}
>
<Tooltip label="Toogle Hidden">
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleLock}
>
<Tooltip label="Toogle Locked">
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleAspectLock}
>
<Tooltip label="Toogle Aspect Ratio Lock">
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
</Tooltip>
</IconButton>
</ButtonsRow>
<ButtonsRow>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveToBack}
>
<Tooltip label="Move to Back">
<PinBottomIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveBackward}
>
<Tooltip label="Move Backward">
<ArrowDownIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveForward}
>
<Tooltip label="Move Forward">
<ArrowUpIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveToFront}
>
<Tooltip label="More to Front">
<PinTopIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleDelete}
>
<Tooltip label="Delete">
<Trash2 size="15" />
</Tooltip>
</IconButton>
</ButtonsRow>
</>
)
}
export default memo(ShapesFunctions)
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,
})

Wyświetl plik

@ -1,37 +1,37 @@
import { Group, Item } from '../shared'
import * as RadioGroup from '@radix-ui/react-radio-group'
import { Circle } from 'react-feather'
import state from 'state'
import state, { useSelector } from 'state'
import { SizeStyle } from 'types'
import { memo } from 'react'
const sizes = {
[SizeStyle.Small]: 6,
[SizeStyle.Medium]: 12,
[SizeStyle.Large]: 22,
}
function handleChange(size: string) {
state.send('CHANGED_STYLE', { size })
}
export default function SizePicker({ size }: { size: SizeStyle }): JSX.Element {
function SizePicker(): JSX.Element {
const size = useSelector((s) => s.values.selectedStyle.size)
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>
{Object.keys(SizeStyle).map((sizeStyle: SizeStyle) => (
<RadioGroup.Item
key={sizeStyle}
as={Item}
isActive={size === sizeStyle}
value={sizeStyle}
>
<Circle size={sizes[sizeStyle]} />
</RadioGroup.Item>
))}
</Group>
)
}
export default memo(SizePicker)

Wyświetl plik

@ -3,31 +3,10 @@ import state, { useSelector } from 'state'
import * as Panel from 'components/panel'
import { useRef } from 'react'
import { IconButton } from 'components/shared'
import { ChevronDown, Trash2, X } from 'react-feather'
import {
deepCompare,
deepCompareArrays,
getPage,
getSelectedIds,
setToArray,
} from 'utils'
import { ChevronDown, X } from 'react-feather'
import ShapesFunctions from './shapes-functions'
import AlignDistribute from './align-distribute'
import { MoveType } from 'types'
import SizePicker from './size-picker'
import {
ArrowDownIcon,
ArrowUpIcon,
AspectRatioIcon,
BoxIcon,
CopyIcon,
EyeClosedIcon,
EyeOpenIcon,
LockClosedIcon,
LockOpen1Icon,
PinBottomIcon,
PinTopIcon,
RotateCounterClockwiseIcon,
} from '@radix-ui/react-icons'
import DashPicker from './dash-picker'
import QuickColorSelect from './quick-color-select'
import ColorPicker from './color-picker'
@ -37,23 +16,12 @@ import QuickdashSelect from './quick-dash-select'
import Tooltip from 'components/tooltip'
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')
export default function StylePanel(): JSX.Element {
const rContainer = useRef<HTMLDivElement>(null)
const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
return (
@ -86,29 +54,7 @@ export default function StylePanel(): JSX.Element {
// track of this data manually within our state.
function SelectedShapeStyles(): JSX.Element {
const selectedIds = useSelector(
(s) => setToArray(getSelectedIds(s.data)),
deepCompareArrays
)
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)
})
const commonStyle = useSelector((s) => s.values.selectedStyle, deepCompare)
const hasSelection = selectedIds.length > 0
const selectedShapesCount = useSelector((s) => s.values.selectedIds.length)
return (
<Panel.Layout>
@ -123,133 +69,20 @@ function SelectedShapeStyles(): JSX.Element {
</IconButton>
</Panel.Header>
<Content>
<ColorPicker color={commonStyle.color} onChange={handleColorChange} />
<IsFilledPicker
isFilled={commonStyle.isFilled}
onChange={handleIsFilledChange}
/>
<ColorPicker />
<IsFilledPicker />
<Row>
<label htmlFor="size">Size</label>
<SizePicker size={commonStyle.size} />
<SizePicker />
</Row>
<Row>
<label htmlFor="dash">Dash</label>
<DashPicker dash={commonStyle.dash} />
<DashPicker />
</Row>
<ButtonsRow>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleDuplicate}
>
<Tooltip label="Duplicate">
<CopyIcon />
</Tooltip>
</IconButton>
<IconButton
disabled={!hasSelection}
size="small"
onClick={handleRotateCcw}
>
<Tooltip label="Rotate">
<RotateCounterClockwiseIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleHide}
>
<Tooltip label="Toogle Hidden">
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleLock}
>
<Tooltip label="Toogle Locked">
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleAspectLock}
>
<Tooltip label="Toogle Aspect Ratio Lock">
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
</Tooltip>
</IconButton>
</ButtonsRow>
<ButtonsRow>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveToBack}
>
<Tooltip label="Move to Back">
<PinBottomIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveBackward}
>
<Tooltip label="Move Backward">
<ArrowDownIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveForward}
>
<Tooltip label="Move Forward">
<ArrowUpIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleMoveToFront}
>
<Tooltip label="More to Front">
<PinTopIcon />
</Tooltip>
</IconButton>
<IconButton
bp={breakpoints}
disabled={!hasSelection}
size="small"
onClick={handleDelete}
>
<Tooltip label="Delete">
<Trash2 size="15" />
</Tooltip>
</IconButton>
</ButtonsRow>
<ShapesFunctions />
<AlignDistribute
hasTwoOrMore={selectedIds.length > 1}
hasThreeOrMore={selectedIds.length > 2}
hasTwoOrMore={selectedShapesCount > 1}
hasThreeOrMore={selectedShapesCount > 2}
/>
</Content>
</Panel.Layout>
@ -306,16 +139,3 @@ const Row = styled('div', {
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,
})

Wyświetl plik

@ -26,7 +26,7 @@ export default function usePageShapes(): string[] {
}, [])
// Get the shapes that fit into the current window
return useSelector((s) => {
const visiblePageShapeIds = useSelector((s) => {
const pageState = getPageState(s.data)
if (!viewportCache.has(pageState)) {
@ -46,4 +46,6 @@ export default function usePageShapes(): string[] {
})
.map((shape) => shape.id)
}, deepCompareArrays)
return visiblePageShapeIds
}

Wyświetl plik

@ -1870,9 +1870,17 @@ const state = createState({
data.boundsRotation = 0
},
},
asyncs: {
async getUpdatedShapes(data) {
return updateFromCode(
data,
data.document.code[data.currentCodeFileId].code
)
},
},
values: {
selectedIds(data) {
return new Set(getSelectedIds(data))
return setToArray(getSelectedIds(data))
},
selectedBounds(data) {
return getSelectionBounds(data)
@ -1915,14 +1923,6 @@ const state = createState({
return commonStyle
},
},
asyncs: {
async getUpdatedShapes(data) {
return updateFromCode(
data,
data.document.code[data.currentCodeFileId].code
)
},
},
})
export default state