import * as React from 'react'
import { styled } from '~styles'
import * as RadixContextMenu from '@radix-ui/react-context-menu'
import { useTldrawApp } from '~hooks'
import { TDSnapshot, AlignType, DistributeType, StretchType, TDExportType } from '~types'
import {
AlignBottomIcon,
AlignCenterHorizontallyIcon,
AlignCenterVerticallyIcon,
AlignLeftIcon,
AlignRightIcon,
AlignTopIcon,
SpaceEvenlyHorizontallyIcon,
SpaceEvenlyVerticallyIcon,
StretchHorizontallyIcon,
StretchVerticallyIcon,
} from '@radix-ui/react-icons'
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'
import { FormattedMessage, useIntl } from 'react-intl'
const numberOfSelectedIdsSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.length
}
const isDebugModeSelector = (s: TDSnapshot) => {
return s.settings.isDebugMode
}
const hasGroupSelectedSelector = (s: TDSnapshot) => {
return s.document.pageStates[s.appState.currentPageId].selectedIds.some(
(id) => s.document.pages[s.appState.currentPageId].shapes[id].children !== undefined
)
}
const preventDefault = (e: Event) => e.stopPropagation()
interface ContextMenuProps {
onBlur?: React.FocusEventHandler
children: React.ReactNode
}
export const ContextMenu = ({ onBlur, children }: ContextMenuProps) => {
return (
{children}
)
}
interface InnerContextMenuProps {
onBlur?: React.FocusEventHandler
}
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
const app = useTldrawApp()
const intl = useIntl()
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
const isDebugMode = app.useStore(isDebugModeSelector)
const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
const rContent = React.useRef(null)
const handleFlipHorizontal = React.useCallback(() => {
app.flipHorizontal()
}, [app])
const handleFlipVertical = React.useCallback(() => {
app.flipVertical()
}, [app])
const handleDuplicate = React.useCallback(() => {
app.duplicate()
}, [app])
const handleLock = React.useCallback(() => {
app.toggleLocked()
}, [app])
const handleGroup = React.useCallback(() => {
app.group()
}, [app])
const handleMoveToBack = React.useCallback(() => {
app.moveToBack()
}, [app])
const handleMoveBackward = React.useCallback(() => {
app.moveBackward()
}, [app])
const handleMoveForward = React.useCallback(() => {
app.moveForward()
}, [app])
const handleMoveToFront = React.useCallback(() => {
app.moveToFront()
}, [app])
const handleDelete = React.useCallback(() => {
app.delete()
}, [app])
const handleCut = React.useCallback(() => {
app.cut()
}, [app])
const handleCopy = React.useCallback(() => {
app.copy()
}, [app])
const handlePaste = React.useCallback(() => {
app.paste()
}, [app])
const handleCopySVG = React.useCallback(() => {
app.copyImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
}, [app])
const handleCopyPNG = React.useCallback(() => {
app.copyImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleUndo = React.useCallback(() => {
app.undo()
}, [app])
const handleRedo = React.useCallback(() => {
app.redo()
}, [app])
const handleExportPNG = React.useCallback(async () => {
app.exportImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
}, [app])
const handleExportJPG = React.useCallback(async () => {
app.exportImage(TDExportType.JPG, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleExportWEBP = React.useCallback(async () => {
app.exportImage(TDExportType.WEBP, { scale: 2, quality: 1, transparentBackground: false })
}, [app])
const handleExportSVG = React.useCallback(async () => {
app.exportImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
}, [app])
const handleCopyJSON = React.useCallback(async () => {
app.copyJson()
}, [app])
const handleExportJSON = React.useCallback(async () => {
app.exportJson()
}, [app])
const hasSelection = numberOfSelectedIds > 0
const hasTwoOrMore = numberOfSelectedIds > 1
const hasThreeOrMore = numberOfSelectedIds > 2
return (
)
})
/* ---------- Align and Distribute Sub Menu --------- */
function AlignDistributeSubMenu({
hasThreeOrMore,
}: {
hasTwoOrMore: boolean
hasThreeOrMore: boolean
}) {
const app = useTldrawApp()
const alignTop = React.useCallback(() => {
app.align(AlignType.Top)
}, [app])
const alignCenterVertical = React.useCallback(() => {
app.align(AlignType.CenterVertical)
}, [app])
const alignBottom = React.useCallback(() => {
app.align(AlignType.Bottom)
}, [app])
const stretchVertically = React.useCallback(() => {
app.stretch(StretchType.Vertical)
}, [app])
const distributeVertically = React.useCallback(() => {
app.distribute(DistributeType.Vertical)
}, [app])
const alignLeft = React.useCallback(() => {
app.align(AlignType.Left)
}, [app])
const alignCenterHorizontal = React.useCallback(() => {
app.align(AlignType.CenterHorizontal)
}, [app])
const alignRight = React.useCallback(() => {
app.align(AlignType.Right)
}, [app])
const stretchHorizontally = React.useCallback(() => {
app.stretch(StretchType.Horizontal)
}, [app])
const distributeHorizontally = React.useCallback(() => {
app.distribute(DistributeType.Horizontal)
}, [app])
return (
)
}
const StyledGridContent = styled(MenuContent, {
display: 'grid',
variants: {
numberOfSelected: {
threeOrMore: {
gridTemplateColumns: 'repeat(5, auto)',
},
twoOrMore: {
gridTemplateColumns: 'repeat(4, auto)',
},
},
},
})
/* -------------- Move to Page Sub Menu ------------- */
const currentPageIdSelector = (s: TDSnapshot) => s.appState.currentPageId
const documentPagesSelector = (s: TDSnapshot) => s.document.pages
function MoveToPageMenu() {
const app = useTldrawApp()
const currentPageId = app.useStore(currentPageIdSelector)
const documentPages = app.useStore(documentPagesSelector)
const sorted = Object.values(documentPages)
.sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
.filter((a) => a.id !== currentPageId)
if (sorted.length === 0) return null
return (
{sorted.map(({ id, name }, i) => (
app.moveToPage(id)}
>
{name || `Page ${i}`}
))}
)
}
/* --------------------- Submenu -------------------- */
export interface ContextMenuSubMenuProps {
label: string
size?: 'small'
children: React.ReactNode
id?: string
}
export function ContextMenuSubMenu({ children, label, size, id }: ContextMenuSubMenuProps) {
return (
{label}
{children}
)
}
/* ---------------------- Arrow --------------------- */
const CMArrow = styled(RadixContextMenu.ContextMenuArrow, {
fill: '$panel',
})
/* ------------------- IconButton ------------------- */
function CMIconButton({ onSelect, ...rest }: ToolButtonProps) {
return (
)
}
/* -------------------- RowButton ------------------- */
const CMRowButton = ({ id, ...rest }: RowButtonProps) => {
return (
)
}
/* ----------------- Trigger Button ----------------- */
interface CMTriggerButtonProps extends RowButtonProps {
isSubmenu?: boolean
}
export const CMTriggerButton = ({ isSubmenu, ...rest }: CMTriggerButtonProps) => {
return (
)
}