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

View file

@ -1,6 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { CheckboxItem } from '@radix-ui/react-dropdown-menu' 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' import { preventEvent } from '~components/preventEvent'
interface DMCheckboxItemProps { interface DMCheckboxItemProps {

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { Trigger } from '@radix-ui/react-dropdown-menu' 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 { interface DMTriggerIconProps extends ToolButtonProps {
children: React.ReactNode 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 { ChevronRightIcon, CheckIcon } from '@radix-ui/react-icons'
import * as React from 'react' import * as React from 'react'
import { breakpoints } from '~components/breakpoints' import { breakpoints } from '~components/breakpoints'
import { Kbd } from '~components/Kbd' import { Kbd } from '~components/Primitives/Kbd'
import { SmallIcon } from '~components/SmallIcon' import { SmallIcon } from '~components/Primitives/SmallIcon'
import { styled } from '~styles' import { styled } from '~styles'
export interface RowButtonProps { export interface RowButtonProps {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,11 @@
import * as React from 'react' import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { Panel } from '~components/Panel' import { Panel } from '~components/Primitives/Panel'
import { ToolButton } from '~components/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
import { TDShapeType, TDToolType } from '~types' import { TDShapeType, TDToolType } from '~types'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { SquareIcon, CircleIcon } from '@radix-ui/react-icons' import { SquareIcon, CircleIcon } from '@radix-ui/react-icons'
import { Tooltip } from '~components/Tooltip' import { Tooltip } from '~components/Primitives/Tooltip'
interface ShapesMenuProps { interface ShapesMenuProps {
activeTool: TDToolType 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 { ExitIcon, GitHubLogoIcon, HamburgerMenuIcon, TwitterLogoIcon } from '@radix-ui/react-icons'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { PreferencesMenu } from './PreferencesMenu' import { PreferencesMenu } from '../PreferencesMenu'
import { DMItem, DMContent, DMDivider, DMSubMenu, DMTriggerIcon } from '~components/DropdownMenu' import {
import { SmallIcon } from '~components/SmallIcon' DMItem,
DMContent,
DMDivider,
DMSubMenu,
DMTriggerIcon,
} from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/Primitives/SmallIcon'
import { useFileSystemHandlers } from '~hooks' import { useFileSystemHandlers } from '~hooks'
import { HeartIcon } from '~components/icons/HeartIcon' import { HeartIcon } from '~components/Primitives/icons/HeartIcon'
import { preventEvent } from '~components/preventEvent' import { preventEvent } from '~components/preventEvent'
interface MenuProps { interface MenuProps {

View file

@ -2,9 +2,9 @@ import * as React from 'react'
import { CheckIcon, ClipboardIcon } from '@radix-ui/react-icons' import { CheckIcon, ClipboardIcon } from '@radix-ui/react-icons'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { DMItem, DMContent, DMDivider, DMTriggerIcon } from '~components/DropdownMenu' import { DMItem, DMContent, DMDivider, DMTriggerIcon } from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/SmallIcon' import { SmallIcon } from '~components/Primitives/SmallIcon'
import { MultiplayerIcon } from '~components/icons' import { MultiplayerIcon } from '~components/Primitives/icons'
import type { TDSnapshot } from '~types' import type { TDSnapshot } from '~types'
import { TLDR } from '~state/TLDR' 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 React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { PlusIcon, CheckIcon } from '@radix-ui/react-icons' import { PlusIcon, CheckIcon } from '@radix-ui/react-icons'
import { PageOptionsDialog } from './PageOptionsDialog' import { PageOptionsDialog } from '../PageOptionsDialog'
import { styled } from '~styles' import { styled } from '~styles'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import type { TDSnapshot } from '~types' import type { TDSnapshot } from '~types'
import { DMContent, DMDivider } from '~components/DropdownMenu' import { DMContent, DMDivider } from '~components/Primitives/DropdownMenu'
import { SmallIcon } from '~components/SmallIcon' import { SmallIcon } from '~components/Primitives/SmallIcon'
import { RowButton } from '~components/RowButton' import { RowButton } from '~components/Primitives/RowButton'
import { ToolButton } from '~components/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
const sortedSelector = (s: TDSnapshot) => const sortedSelector = (s: TDSnapshot) =>
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0)) 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 { MixerVerticalIcon } from '@radix-ui/react-icons'
import type { TDSnapshot, TDPage } from '~types' import type { TDSnapshot, TDPage } from '~types'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { RowButton, RowButtonProps } from '~components/RowButton' import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
import { styled } from '~styles' import { styled } from '~styles'
import { Divider } from '~components/Divider' import { Divider } from '~components/Primitives/Divider'
import { IconButton } from '~components/IconButton/IconButton' import { IconButton } from '~components/Primitives/IconButton/IconButton'
import { SmallIcon } from '~components/SmallIcon' import { SmallIcon } from '~components/Primitives/SmallIcon'
import { breakpoints } from '~components/breakpoints' import { breakpoints } from '~components/breakpoints'
const canDeleteSelector = (s: TDSnapshot) => { const canDeleteSelector = (s: TDSnapshot) => {

View file

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

View file

@ -1,5 +1,5 @@
import * as React from 'react' 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 { useTldrawApp } from '~hooks'
import type { TDSnapshot } from '~types' 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 React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' 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 { useTldrawApp } from '~hooks'
import { DMCheckboxItem, DMContent, DMRadioItem, DMTriggerIcon } from '~components/DropdownMenu' import {
DMCheckboxItem,
DMContent,
DMRadioItem,
DMTriggerIcon,
} from '~components/Primitives/DropdownMenu'
import { import {
CircleIcon, CircleIcon,
DashDashedIcon, DashDashedIcon,
@ -12,24 +17,28 @@ import {
SizeLargeIcon, SizeLargeIcon,
SizeMediumIcon, SizeMediumIcon,
SizeSmallIcon, SizeSmallIcon,
} from '~components/icons' } from '~components/Primitives/icons'
import { ToolButton } from '~components/ToolButton' import { ToolButton } from '~components/Primitives/ToolButton'
import { TDSnapshot, ColorStyle, DashStyle, SizeStyle } from '~types' import { TDSnapshot, ColorStyle, DashStyle, SizeStyle, ShapeStyles } from '~types'
import { styled } from '~styles' import { styled } from '~styles'
import { breakpoints } from '~components/breakpoints' import { breakpoints } from '~components/breakpoints'
import { Divider } from '~components/Divider' import { Divider } from '~components/Primitives/Divider'
import { preventEvent } from '~components/preventEvent' 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.Draw]: <DashDrawIcon />,
[DashStyle.Solid]: <DashSolidIcon />, [DashStyle.Solid]: <DashSolidIcon />,
[DashStyle.Dashed]: <DashDashedIcon />, [DashStyle.Dashed]: <DashDashedIcon />,
[DashStyle.Dotted]: <DashDottedIcon />, [DashStyle.Dotted]: <DashDottedIcon />,
} }
const sizes = { const SIZES = {
[SizeStyle.Small]: <SizeSmallIcon />, [SizeStyle.Small]: <SizeSmallIcon />,
[SizeStyle.Medium]: <SizeMediumIcon />, [SizeStyle.Medium]: <SizeMediumIcon />,
[SizeStyle.Large]: <SizeLargeIcon />, [SizeStyle.Large]: <SizeLargeIcon />,
@ -42,7 +51,53 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
const theme = app.useStore(themeSelector) 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) => { const handleToggleFilled = React.useCallback((checked: boolean) => {
app.style({ isFilled: checked }) app.style({ isFilled: checked })
@ -61,13 +116,17 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<DMTriggerIcon> <DMTriggerIcon>
<OverlapIcons <OverlapIcons
style={{ style={{
color: strokes[theme][style.color as ColorStyle], color: strokes[theme][displayedStyle.color as ColorStyle],
}} }}
> >
{style.isFilled && ( {displayedStyle.isFilled && (
<CircleIcon size={16} stroke="none" fill={fills[theme][style.color as ColorStyle]} /> <CircleIcon
size={16}
stroke="none"
fill={fills[theme][displayedStyle.color as ColorStyle]}
/>
)} )}
{dashes[style.dash]} {DASHES[displayedStyle.dash]}
</OverlapIcons> </OverlapIcons>
</DMTriggerIcon> </DMTriggerIcon>
<DMContent> <DMContent>
@ -78,13 +137,17 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<DropdownMenu.Item key={colorStyle} onSelect={preventEvent} asChild> <DropdownMenu.Item key={colorStyle} onSelect={preventEvent} asChild>
<ToolButton <ToolButton
variant="icon" variant="icon"
isActive={style.color === colorStyle} isActive={displayedStyle.color === colorStyle}
onClick={() => app.style({ color: colorStyle as ColorStyle })} onClick={() => app.style({ color: colorStyle as ColorStyle })}
> >
<CircleIcon <CircleIcon
size={18} size={18}
strokeWidth={2.5} 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]} stroke={strokes.light[colorStyle as ColorStyle]}
/> />
</ToolButton> </ToolButton>
@ -95,16 +158,16 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<Divider /> <Divider />
<StyledRow> <StyledRow>
Dash Dash
<StyledGroup dir="ltr" value={style.dash} onValueChange={handleDashChange}> <StyledGroup dir="ltr" value={displayedStyle.dash} onValueChange={handleDashChange}>
{Object.values(DashStyle).map((dashStyle) => ( {Object.values(DashStyle).map((dashStyle) => (
<DMRadioItem <DMRadioItem
key={dashStyle} key={dashStyle}
isActive={dashStyle === style.dash} isActive={dashStyle === displayedStyle.dash}
value={dashStyle} value={dashStyle}
onSelect={preventEvent} onSelect={preventEvent}
bp={breakpoints} bp={breakpoints}
> >
{dashes[dashStyle as DashStyle]} {DASHES[dashStyle as DashStyle]}
</DMRadioItem> </DMRadioItem>
))} ))}
</StyledGroup> </StyledGroup>
@ -112,22 +175,22 @@ export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
<Divider /> <Divider />
<StyledRow> <StyledRow>
Size Size
<StyledGroup dir="ltr" value={style.size} onValueChange={handleSizeChange}> <StyledGroup dir="ltr" value={displayedStyle.size} onValueChange={handleSizeChange}>
{Object.values(SizeStyle).map((sizeStyle) => ( {Object.values(SizeStyle).map((sizeStyle) => (
<DMRadioItem <DMRadioItem
key={sizeStyle} key={sizeStyle}
isActive={sizeStyle === style.size} isActive={sizeStyle === displayedStyle.size}
value={sizeStyle} value={sizeStyle}
onSelect={preventEvent} onSelect={preventEvent}
bp={breakpoints} bp={breakpoints}
> >
{sizes[sizeStyle as SizeStyle]} {SIZES[sizeStyle as SizeStyle]}
</DMRadioItem> </DMRadioItem>
))} ))}
</StyledGroup> </StyledGroup>
</StyledRow> </StyledRow>
<Divider /> <Divider />
<DMCheckboxItem checked={!!style.isFilled} onCheckedChange={handleToggleFilled}> <DMCheckboxItem checked={!!displayedStyle.isFilled} onCheckedChange={handleToggleFilled}>
Fill Fill
</DMCheckboxItem> </DMCheckboxItem>
</DMContent> </DMContent>

View file

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

View file

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

View file

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

View file

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

View file

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