[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:
parent
e0b607e512
commit
eb20f1c816
76 changed files with 210 additions and 187 deletions
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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,
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
|
@ -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'
|
||||
|
|
@ -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'
|
||||
|
||||
/* -------------------------------------------------- */
|
|
@ -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
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export function HelpMenu() {
|
||||
return <div />
|
||||
}
|
|
@ -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 {
|
0
packages/tldraw/src/components/TopPanel/Menu/index.ts
Normal file
0
packages/tldraw/src/components/TopPanel/Menu/index.ts
Normal 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'
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './MultiplayerMenu'
|
|
@ -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))
|
|
@ -0,0 +1 @@
|
|||
export * from './PageMenu'
|
|
@ -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) => {
|
|
@ -0,0 +1 @@
|
|||
export * from './PageOptionsDialog'
|
|
@ -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'
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './PreferencesMenu'
|
|
@ -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')
|
||||
})
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
export * from './StyleMenu'
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
export * from './ZoomMenu'
|
|
@ -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 */
|
||||
/* -------------------------------------------------- */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -92,7 +92,6 @@ export interface TDSnapshot {
|
|||
showCloneHandles: boolean
|
||||
}
|
||||
appState: {
|
||||
selectedStyle: ShapeStyles
|
||||
currentStyle: ShapeStyles
|
||||
currentPageId: string
|
||||
pages: Pick<TLPage<TDShape, TDBinding>, 'id' | 'name' | 'childIndex'>[]
|
||||
|
|
Loading…
Reference in a new issue