[fix] current style and selected style (#298)

* Fix selectedStyles from being new on each update

* Fix again

* Update TldrawApp.ts

* Fix log around current style and selected style

* Add stub test, move style menu into folder

* Cleanup repo

* cleanup context menu
This commit is contained in:
Steve Ruiz 2021-11-19 10:19:06 +00:00 committed by GitHub
parent e0b607e512
commit eb20f1c816
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 210 additions and 187 deletions

View file

@ -1,11 +0,0 @@
import * as React from 'react'
import { ContextMenuItem } from '@radix-ui/react-context-menu'
import { ToolButton, ToolButtonProps } from '~components/ToolButton'
export function CMIconButton({ onSelect, ...rest }: ToolButtonProps): JSX.Element {
return (
<ContextMenuItem dir="ltr" onSelect={onSelect} asChild>
<ToolButton {...rest} />
</ContextMenuItem>
)
}

View file

@ -1,11 +0,0 @@
import * as React from 'react'
import { ContextMenuItem } from '@radix-ui/react-context-menu'
import { RowButton, RowButtonProps } from '~components/RowButton'
export const CMRowButton = ({ ...rest }: RowButtonProps) => {
return (
<ContextMenuItem asChild>
<RowButton {...rest} />
</ContextMenuItem>
)
}

View file

@ -1,15 +0,0 @@
import * as React from 'react'
import { ContextMenuTriggerItem } from '@radix-ui/react-context-menu'
import { RowButton, RowButtonProps } from '~components/RowButton'
interface CMTriggerButtonProps extends RowButtonProps {
isSubmenu?: boolean
}
export const CMTriggerButton = ({ isSubmenu, ...rest }: CMTriggerButtonProps) => {
return (
<ContextMenuTriggerItem asChild>
<RowButton hasArrow={isSubmenu} {...rest} />
</ContextMenuTriggerItem>
)
}

View file

@ -15,20 +15,13 @@ import {
StretchHorizontallyIcon,
StretchVerticallyIcon,
} from '@radix-ui/react-icons'
import { CMRowButton } from './CMRowButton'
import { CMIconButton } from './CMIconButton'
import { CMTriggerButton } from './CMTriggerButton'
import { Divider } from '~components/Divider'
import { MenuContent } from '~components/MenuContent'
import { Divider } from '~components/Primitives/Divider'
import { MenuContent } from '~components/Primitives/MenuContent'
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
const has1SelectedIdsSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 0
}
const has2SelectedIdsSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 1
}
const has3SelectedIdsSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.length > 2
const numberOfSelectedIdsSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.length
}
const isDebugModeSelector = (s: TDSnapshot) => {
@ -50,9 +43,7 @@ interface ContextMenuProps {
export const ContextMenu = ({ onBlur, children }: ContextMenuProps): JSX.Element => {
const app = useTldrawApp()
const hasSelection = app.useStore(has1SelectedIdsSelector)
const hasTwoOrMore = app.useStore(has2SelectedIdsSelector)
const hasThreeOrMore = app.useStore(has3SelectedIdsSelector)
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
const isDebugMode = app.useStore(isDebugModeSelector)
const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
@ -122,6 +113,10 @@ export const ContextMenu = ({ onBlur, children }: ContextMenuProps): JSX.Element
app.redo()
}, [app])
const hasSelection = numberOfSelectedIds > 0
const hasTwoOrMore = numberOfSelectedIds > 1
const hasThreeOrMore = numberOfSelectedIds > 2
return (
<RadixContextMenu.Root dir="ltr">
<RadixContextMenu.Trigger dir="ltr">{children}</RadixContextMenu.Trigger>
@ -216,6 +211,8 @@ export const ContextMenu = ({ onBlur, children }: ContextMenuProps): JSX.Element
)
}
/* ---------- Align and Distribute Sub Menu --------- */
function AlignDistributeSubMenu({
hasThreeOrMore,
}: {
@ -268,7 +265,7 @@ function AlignDistributeSubMenu({
<RadixContextMenu.Root dir="ltr">
<CMTriggerButton isSubmenu>Align / Distribute</CMTriggerButton>
<RadixContextMenu.Content asChild sideOffset={2} alignOffset={-2}>
<StyledGridContent selectedStyle={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}>
<StyledGridContent numberOfSelected={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}>
<CMIconButton onClick={alignLeft}>
<AlignLeftIcon />
</CMIconButton>
@ -313,7 +310,7 @@ function AlignDistributeSubMenu({
const StyledGridContent = styled(MenuContent, {
display: 'grid',
variants: {
selectedStyle: {
numberOfSelected: {
threeOrMore: {
gridTemplateColumns: 'repeat(5, auto)',
},
@ -324,7 +321,7 @@ const StyledGridContent = styled(MenuContent, {
},
})
/* ------------------ Move to Page ------------------ */
/* -------------- Move to Page Sub Menu ------------- */
const currentPageIdSelector = (s: TDSnapshot) => s.appState.currentPageId
const documentPagesSelector = (s: TDSnapshot) => s.document.pages
@ -387,3 +384,37 @@ export function ContextMenuSubMenu({ children, label }: ContextMenuSubMenuProps)
const CMArrow = styled(RadixContextMenu.ContextMenuArrow, {
fill: '$panel',
})
/* ------------------- IconButton ------------------- */
function CMIconButton({ onSelect, ...rest }: ToolButtonProps): JSX.Element {
return (
<RadixContextMenu.ContextMenuItem dir="ltr" onSelect={onSelect} asChild>
<ToolButton {...rest} />
</RadixContextMenu.ContextMenuItem>
)
}
/* -------------------- RowButton ------------------- */
const CMRowButton = ({ ...rest }: RowButtonProps) => {
return (
<RadixContextMenu.ContextMenuItem asChild>
<RowButton {...rest} />
</RadixContextMenu.ContextMenuItem>
)
}
/* ----------------- Trigger Button ----------------- */
interface CMTriggerButtonProps extends RowButtonProps {
isSubmenu?: boolean
}
export const CMTriggerButton = ({ isSubmenu, ...rest }: CMTriggerButtonProps) => {
return (
<RadixContextMenu.ContextMenuTriggerItem asChild>
<RowButton hasArrow={isSubmenu} {...rest} />
</RadixContextMenu.ContextMenuTriggerItem>
)
}

View file

@ -1,6 +1,6 @@
import { DotFilledIcon } from '@radix-ui/react-icons'
import * as React from 'react'
import { IconButton } from '~components/IconButton/IconButton'
import { IconButton } from '~components/Primitives/IconButton/IconButton'
import { styled } from '~styles'
interface FocusButtonProps {

View file

@ -1,6 +1,6 @@
import * as React from 'react'
import { CheckboxItem } from '@radix-ui/react-dropdown-menu'
import { RowButton, RowButtonProps } from '~components/RowButton'
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
import { preventEvent } from '~components/preventEvent'
interface DMCheckboxItemProps {

View file

@ -1,7 +1,7 @@
import * as React from 'react'
import { Content } from '@radix-ui/react-dropdown-menu'
import { styled } from '~styles/stitches.config'
import { MenuContent } from '~components/MenuContent'
import { MenuContent } from '~components/Primitives/MenuContent'
import { stopPropagation } from '~components/stopPropagation'
export interface DMContentProps {

View file

@ -1,6 +1,6 @@
import * as React from 'react'
import { Item } from '@radix-ui/react-dropdown-menu'
import { RowButton, RowButtonProps } from '~components/RowButton'
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
export function DMItem({
onSelect,

View file

@ -1,7 +1,7 @@
import * as React from 'react'
import { Root, TriggerItem, Content, Arrow } from '@radix-ui/react-dropdown-menu'
import { RowButton } from '~components/RowButton'
import { MenuContent } from '~components/MenuContent'
import { RowButton } from '~components/Primitives/RowButton'
import { MenuContent } from '~components/Primitives/MenuContent'
export interface DMSubMenuProps {
label: string

View file

@ -1,6 +1,6 @@
import * as React from 'react'
import { Trigger } from '@radix-ui/react-dropdown-menu'
import { ToolButton, ToolButtonProps } from '~components/ToolButton'
import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
interface DMTriggerIconProps extends ToolButtonProps {
children: React.ReactNode

View file

@ -2,8 +2,8 @@ import { ItemIndicator } from '@radix-ui/react-dropdown-menu'
import { ChevronRightIcon, CheckIcon } from '@radix-ui/react-icons'
import * as React from 'react'
import { breakpoints } from '~components/breakpoints'
import { Kbd } from '~components/Kbd'
import { SmallIcon } from '~components/SmallIcon'
import { Kbd } from '~components/Primitives/Kbd'
import { SmallIcon } from '~components/Primitives/SmallIcon'
import { styled } from '~styles'
export interface RowButtonProps {

View file

@ -1,6 +1,6 @@
import * as React from 'react'
import { breakpoints } from '~components/breakpoints'
import { Tooltip } from '~components/Tooltip'
import { Tooltip } from '~components/Primitives/Tooltip'
import { useTldrawApp } from '~hooks'
import { styled } from '~styles'

View file

@ -1,6 +1,6 @@
import * as RadixTooltip from '@radix-ui/react-tooltip'
import * as React from 'react'
import { Kbd } from '~components/Kbd'
import { Kbd } from '~components/Primitives/Kbd'
import { styled } from '~styles'
/* -------------------------------------------------- */

View file

@ -1,5 +1,5 @@
import * as React from 'react'
import { Tooltip } from '~components/Tooltip/Tooltip'
import { Tooltip } from '~components/Primitives/Tooltip/Tooltip'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useTldrawApp } from '~hooks'
import { styled } from '~styles'
@ -28,10 +28,10 @@ import {
StretchVerticallyIcon,
BoxIcon,
} from '@radix-ui/react-icons'
import { DMContent } from '~components/DropdownMenu'
import { Divider } from '~components/Divider'
import { TrashIcon } from '~components/icons'
import { ToolButton } from '~components/ToolButton'
import { DMContent } from '~components/Primitives/DropdownMenu'
import { Divider } from '~components/Primitives/Divider'
import { TrashIcon } from '~components/Primitives/icons'
import { ToolButton } from '~components/Primitives/ToolButton'
const selectedShapesCountSelector = (s: TDSnapshot) =>
s.document.pageStates[s.appState.currentPageId].selectedIds.length

View file

@ -2,8 +2,8 @@ import * as React from 'react'
import { styled } from '~styles'
import type { TDSnapshot } from '~types'
import { useTldrawApp } from '~hooks'
import { RowButton } from '~components/RowButton'
import { MenuContent } from '~components/MenuContent'
import { RowButton } from '~components/Primitives/RowButton'
import { MenuContent } from '~components/Primitives/MenuContent'
const isEmptyCanvasSelector = (s: TDSnapshot) =>
Object.keys(s.document.pages[s.appState.currentPageId].shapes).length > 0 &&

View file

@ -1,8 +1,8 @@
import * as React from 'react'
import { Tooltip } from '~components/Tooltip'
import { Tooltip } from '~components/Primitives/Tooltip'
import { useTldrawApp } from '~hooks'
import { ToolButton } from '~components/ToolButton'
import { TrashIcon } from '~components/icons'
import { ToolButton } from '~components/Primitives/ToolButton'
import { TrashIcon } from '~components/Primitives/icons'
export function DeleteButton(): JSX.Element {
const app = useTldrawApp()

View file

@ -1,8 +1,8 @@
import * as React from 'react'
import { LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons'
import { Tooltip } from '~components/Tooltip'
import { Tooltip } from '~components/Primitives/Tooltip'
import { useTldrawApp } from '~hooks'
import { ToolButton } from '~components/ToolButton'
import { ToolButton } from '~components/Primitives/ToolButton'
import type { TDSnapshot } from '~types'
const isToolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked

View file

@ -1,11 +1,11 @@
import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { Panel } from '~components/Panel'
import { ToolButton } from '~components/ToolButton'
import { Panel } from '~components/Primitives/Panel'
import { ToolButton } from '~components/Primitives/ToolButton'
import { TDShapeType, TDToolType } from '~types'
import { useTldrawApp } from '~hooks'
import { Pencil1Icon } from '@radix-ui/react-icons'
import { Tooltip } from '~components/Tooltip'
import { Tooltip } from '~components/Primitives/Tooltip'
interface ShapesMenuProps {
activeTool: TDToolType

View file

@ -8,10 +8,10 @@ import {
} from '@radix-ui/react-icons'
import { TDSnapshot, TDShapeType } from '~types'
import { useTldrawApp } from '~hooks'
import { ToolButtonWithTooltip } from '~components/ToolButton'
import { Panel } from '~components/Panel'
import { ToolButtonWithTooltip } from '~components/Primitives/ToolButton'
import { Panel } from '~components/Primitives/Panel'
import { ShapesMenu } from './ShapesMenu'
import { EraserIcon } from '~components/icons'
import { EraserIcon } from '~components/Primitives/icons'
const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool
const toolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked

View file

@ -1,11 +1,11 @@
import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { Panel } from '~components/Panel'
import { ToolButton } from '~components/ToolButton'
import { Panel } from '~components/Primitives/Panel'
import { ToolButton } from '~components/Primitives/ToolButton'
import { TDShapeType, TDToolType } from '~types'
import { useTldrawApp } from '~hooks'
import { SquareIcon, CircleIcon } from '@radix-ui/react-icons'
import { Tooltip } from '~components/Tooltip'
import { Tooltip } from '~components/Primitives/Tooltip'
interface ShapesMenuProps {
activeTool: TDToolType

View file

@ -1,3 +0,0 @@
export function HelpMenu() {
return <div />
}

View file

@ -2,11 +2,17 @@ import * as React from 'react'
import { ExitIcon, GitHubLogoIcon, HamburgerMenuIcon, TwitterLogoIcon } from '@radix-ui/react-icons'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useTldrawApp } from '~hooks'
import { PreferencesMenu } from './PreferencesMenu'
import { DMItem, DMContent, DMDivider, DMSubMenu, DMTriggerIcon } from '~components/DropdownMenu'
import { SmallIcon } from '~components/SmallIcon'
import { PreferencesMenu } from '../PreferencesMenu'
import {
DMItem,
DMContent,
DMDivider,
DMSubMenu,
DMTriggerIcon,
} from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/Primitives/SmallIcon'
import { useFileSystemHandlers } from '~hooks'
import { HeartIcon } from '~components/icons/HeartIcon'
import { HeartIcon } from '~components/Primitives/icons/HeartIcon'
import { preventEvent } from '~components/preventEvent'
interface MenuProps {

View file

@ -2,9 +2,9 @@ import * as React from 'react'
import { CheckIcon, ClipboardIcon } from '@radix-ui/react-icons'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useTldrawApp } from '~hooks'
import { DMItem, DMContent, DMDivider, DMTriggerIcon } from '~components/DropdownMenu'
import { SmallIcon } from '~components/SmallIcon'
import { MultiplayerIcon } from '~components/icons'
import { DMItem, DMContent, DMDivider, DMTriggerIcon } from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/Primitives/SmallIcon'
import { MultiplayerIcon } from '~components/Primitives/icons'
import type { TDSnapshot } from '~types'
import { TLDR } from '~state/TLDR'

View file

@ -0,0 +1 @@
export * from './MultiplayerMenu'

View file

@ -1,14 +1,14 @@
import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { PlusIcon, CheckIcon } from '@radix-ui/react-icons'
import { PageOptionsDialog } from './PageOptionsDialog'
import { PageOptionsDialog } from '../PageOptionsDialog'
import { styled } from '~styles'
import { useTldrawApp } from '~hooks'
import type { TDSnapshot } from '~types'
import { DMContent, DMDivider } from '~components/DropdownMenu'
import { SmallIcon } from '~components/SmallIcon'
import { RowButton } from '~components/RowButton'
import { ToolButton } from '~components/ToolButton'
import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/Primitives/SmallIcon'
import { RowButton } from '~components/Primitives/RowButton'
import { ToolButton } from '~components/Primitives/ToolButton'
const sortedSelector = (s: TDSnapshot) =>
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))

View file

@ -0,0 +1 @@
export * from './PageMenu'

View file

@ -3,11 +3,11 @@ import * as Dialog from '@radix-ui/react-alert-dialog'
import { MixerVerticalIcon } from '@radix-ui/react-icons'
import type { TDSnapshot, TDPage } from '~types'
import { useTldrawApp } from '~hooks'
import { RowButton, RowButtonProps } from '~components/RowButton'
import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
import { styled } from '~styles'
import { Divider } from '~components/Divider'
import { IconButton } from '~components/IconButton/IconButton'
import { SmallIcon } from '~components/SmallIcon'
import { Divider } from '~components/Primitives/Divider'
import { IconButton } from '~components/Primitives/IconButton/IconButton'
import { SmallIcon } from '~components/Primitives/SmallIcon'
import { breakpoints } from '~components/breakpoints'
const canDeleteSelector = (s: TDSnapshot) => {

View file

@ -0,0 +1 @@
export * from './PageOptionsDialog'

View file

@ -1,5 +1,5 @@
import * as React from 'react'
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/DropdownMenu'
import { DMCheckboxItem, DMDivider, DMSubMenu } from '~components/Primitives/DropdownMenu'
import { useTldrawApp } from '~hooks'
import type { TDSnapshot } from '~types'

View file

@ -0,0 +1 @@
export * from './PreferencesMenu'

View file

@ -0,0 +1,4 @@
describe('the style menu', () => {
test.todo('Correctly sets the style properties when shapes are selected')
test.todo('Correctly sets the style properties when nothing is selected')
})

View file

@ -1,8 +1,13 @@
import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { strokes, fills } from '~state/shapes/shared/shape-styles'
import { strokes, fills, defaultStyle } from '~state/shapes/shared/shape-styles'
import { useTldrawApp } from '~hooks'
import { DMCheckboxItem, DMContent, DMRadioItem, DMTriggerIcon } from '~components/DropdownMenu'
import {
DMCheckboxItem,
DMContent,
DMRadioItem,
DMTriggerIcon,
} from '~components/Primitives/DropdownMenu'
import {
CircleIcon,
DashDashedIcon,
@ -12,24 +17,28 @@ import {
SizeLargeIcon,
SizeMediumIcon,
SizeSmallIcon,
} from '~components/icons'
import { ToolButton } from '~components/ToolButton'
import { TDSnapshot, ColorStyle, DashStyle, SizeStyle } from '~types'
} from '~components/Primitives/icons'
import { ToolButton } from '~components/Primitives/ToolButton'
import { TDSnapshot, ColorStyle, DashStyle, SizeStyle, ShapeStyles } from '~types'
import { styled } from '~styles'
import { breakpoints } from '~components/breakpoints'
import { Divider } from '~components/Divider'
import { Divider } from '~components/Primitives/Divider'
import { preventEvent } from '~components/preventEvent'
const selectedStyleSelector = (s: TDSnapshot) => s.appState.selectedStyle
const currentStyleSelector = (s: TDSnapshot) => s.appState.currentStyle
const selectedIdsSelector = (s: TDSnapshot) =>
s.document.pageStates[s.appState.currentPageId].selectedIds
const dashes = {
const STYLE_KEYS = Object.keys(defaultStyle) as (keyof ShapeStyles)[]
const DASHES = {
[DashStyle.Draw]: <DashDrawIcon />,
[DashStyle.Solid]: <DashSolidIcon />,
[DashStyle.Dashed]: <DashDashedIcon />,
[DashStyle.Dotted]: <DashDottedIcon />,
}
const sizes = {
const SIZES = {
[SizeStyle.Small]: <SizeSmallIcon />,
[SizeStyle.Medium]: <SizeMediumIcon />,
[SizeStyle.Large]: <SizeLargeIcon />,
@ -42,7 +51,53 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
const theme = app.useStore(themeSelector)
const style = app.useStore(selectedStyleSelector)
const currentStyle = app.useStore(currentStyleSelector)
const selectedIds = app.useStore(selectedIdsSelector)
const [displayedStyle, setDisplayedStyle] = React.useState(currentStyle)
const rDisplayedStyle = React.useRef(currentStyle)
React.useEffect(() => {
const {
appState: { currentStyle },
page,
selectedIds,
} = app
let commonStyle = {} as ShapeStyles
if (selectedIds.length <= 0) {
commonStyle = currentStyle
} else {
const overrides = new Set<string>([])
app.selectedIds
.map((id) => page.shapes[id])
.forEach((shape) => {
STYLE_KEYS.forEach((key) => {
if (overrides.has(key)) return
if (commonStyle[key] === undefined) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
commonStyle[key] = shape.style[key]
} else {
if (commonStyle[key] === shape.style[key]) return
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
commonStyle[key] = shape.style[key]
overrides.add(key)
}
})
})
}
// Until we can work out the correct logic for deciding whether or not to
// update the selected style, do a string comparison. Yuck!
if (JSON.stringify(commonStyle) !== JSON.stringify(rDisplayedStyle.current)) {
rDisplayedStyle.current = commonStyle
setDisplayedStyle(commonStyle)
}
}, [currentStyle, selectedIds])
const handleToggleFilled = React.useCallback((checked: boolean) => {
app.style({ isFilled: checked })
@ -61,13 +116,17 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<DMTriggerIcon>
<OverlapIcons
style={{
color: strokes[theme][style.color as ColorStyle],
color: strokes[theme][displayedStyle.color as ColorStyle],
}}
>
{style.isFilled && (
<CircleIcon size={16} stroke="none" fill={fills[theme][style.color as ColorStyle]} />
{displayedStyle.isFilled && (
<CircleIcon
size={16}
stroke="none"
fill={fills[theme][displayedStyle.color as ColorStyle]}
/>
)}
{dashes[style.dash]}
{DASHES[displayedStyle.dash]}
</OverlapIcons>
</DMTriggerIcon>
<DMContent>
@ -78,13 +137,17 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<DropdownMenu.Item key={colorStyle} onSelect={preventEvent} asChild>
<ToolButton
variant="icon"
isActive={style.color === colorStyle}
isActive={displayedStyle.color === colorStyle}
onClick={() => app.style({ color: colorStyle as ColorStyle })}
>
<CircleIcon
size={18}
strokeWidth={2.5}
fill={style.isFilled ? fills.light[colorStyle as ColorStyle] : 'transparent'}
fill={
displayedStyle.isFilled
? fills.light[colorStyle as ColorStyle]
: 'transparent'
}
stroke={strokes.light[colorStyle as ColorStyle]}
/>
</ToolButton>
@ -95,16 +158,16 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<Divider />
<StyledRow>
Dash
<StyledGroup dir="ltr" value={style.dash} onValueChange={handleDashChange}>
<StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
{Object.values(DashStyle).map((dashStyle) => (
<DMRadioItem
key={dashStyle}
isActive={dashStyle === style.dash}
isActive={dashStyle === displayedStyle.dash}
value={dashStyle}
onSelect={preventEvent}
bp={breakpoints}
>
{dashes[dashStyle as DashStyle]}
{DASHES[dashStyle as DashStyle]}
</DMRadioItem>
))}
</StyledGroup>
@ -112,22 +175,22 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<Divider />
<StyledRow>
Size
<StyledGroup dir="ltr" value={style.size} onValueChange={handleSizeChange}>
<StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
{Object.values(SizeStyle).map((sizeStyle) => (
<DMRadioItem
key={sizeStyle}
isActive={sizeStyle === style.size}
isActive={sizeStyle === displayedStyle.size}
value={sizeStyle}
onSelect={preventEvent}
bp={breakpoints}
>
{sizes[sizeStyle as SizeStyle]}
{SIZES[sizeStyle as SizeStyle]}
</DMRadioItem>
))}
</StyledGroup>
</StyledRow>
<Divider />
<DMCheckboxItem checked={!!style.isFilled} onCheckedChange={handleToggleFilled}>
<DMCheckboxItem checked={!!displayedStyle.isFilled} onCheckedChange={handleToggleFilled}>
Fill
</DMCheckboxItem>
</DMContent>

View file

@ -0,0 +1 @@
export * from './StyleMenu'

View file

@ -1,10 +1,10 @@
import * as React from 'react'
import { Menu } from './Menu'
import { Menu } from './Menu/Menu'
import { styled } from '~styles'
import { PageMenu } from './PageMenu'
import { ZoomMenu } from './ZoomMenu'
import { StyleMenu } from './StyleMenu'
import { Panel } from '~components/Panel'
import { Panel } from '~components/Primitives/Panel'
interface TopPanelProps {
readOnly: boolean

View file

@ -3,8 +3,8 @@ import { useTldrawApp } from '~hooks'
import type { TDSnapshot } from '~types'
import { styled } from '~styles'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { DMItem, DMContent } from '~components/DropdownMenu'
import { ToolButton } from '~components/ToolButton'
import { DMItem, DMContent } from '~components/Primitives/DropdownMenu'
import { ToolButton } from '~components/Primitives/ToolButton'
import { preventEvent } from '~components/preventEvent'
const zoomSelector = (s: TDSnapshot) => s.document.pageStates[s.appState.currentPageId].camera.zoom

View file

@ -0,0 +1 @@
export * from './ZoomMenu'

View file

@ -735,44 +735,6 @@ export class TLDR {
TLDR.updateParents(data, pageId, parentToUpdateIds)
}
static getSelectedStyle(data: TDSnapshot, pageId: string): ShapeStyles | false {
const { currentStyle } = data.appState
const page = data.document.pages[pageId]
const pageState = data.document.pageStates[pageId]
if (pageState.selectedIds.length === 0) {
return currentStyle
}
const shapeStyles = pageState.selectedIds.map((id) => page.shapes[id].style)
const commonStyle = {} as ShapeStyles
const overrides = new Set<string>([])
for (const shapeStyle of shapeStyles) {
const styles = Object.keys(currentStyle) as (keyof ShapeStyles)[]
styles.forEach((key) => {
if (overrides.has(key)) return
if (commonStyle[key] === undefined) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
commonStyle[key] = shapeStyle[key]
} else {
if (commonStyle[key] === shapeStyle[key]) return
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
commonStyle[key] = currentStyle[key]
overrides.add(key)
}
})
}
return commonStyle
}
/* -------------------------------------------------- */
/* Bindings */
/* -------------------------------------------------- */

View file

@ -247,7 +247,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
* @protected
* @returns The final state
*/
protected cleanup = (state: TDSnapshot, prev: TDSnapshot): TDSnapshot => {
protected cleanup = (
state: TDSnapshot,
prev: TDSnapshot,
patch: Patch<TDSnapshot>
): TDSnapshot => {
const next = { ...state }
// Remove deleted shapes and bindings (in Commands, these will be set to undefined)
@ -415,17 +419,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
}
}
// Apply selected style change, if any
const newSelectedStyle = TLDR.getSelectedStyle(next, currentPageId)
if (newSelectedStyle) {
next.appState = {
...next.appState,
selectedStyle: newSelectedStyle,
}
}
// Temporary block on editing pages while in readonly mode.
// This is a broad solution but not a very good one: the UX
// for interacting with a readOnly document will be more nuanced.
@ -2855,7 +2848,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
currentPageId: 'page',
pages: [{ id: 'page', name: 'page', childIndex: 1 }],
currentStyle: defaultStyle,
selectedStyle: defaultStyle,
isToolLocked: false,
isStyleOpen: false,
isEmptyCanvas: false,

View file

@ -92,7 +92,6 @@ export interface TDSnapshot {
showCloneHandles: boolean
}
appState: {
selectedStyle: ShapeStyles
currentStyle: ShapeStyles
currentPageId: string
pages: Pick<TLPage<TDShape, TDBinding>, 'id' | 'name' | 'childIndex'>[]