2021-11-03 16:46:33 +00:00
|
|
|
import * as React from 'react'
|
2021-11-06 11:16:30 +00:00
|
|
|
import { styled } from '~styles'
|
2021-11-03 16:46:33 +00:00
|
|
|
import * as RadixContextMenu from '@radix-ui/react-context-menu'
|
2021-11-16 16:01:29 +00:00
|
|
|
import { useTldrawApp } from '~hooks'
|
2022-05-11 13:25:08 +00:00
|
|
|
import { TDSnapshot, AlignType, DistributeType, StretchType, TDExportType } from '~types'
|
2021-11-03 16:46:33 +00:00
|
|
|
import {
|
|
|
|
AlignBottomIcon,
|
|
|
|
AlignCenterHorizontallyIcon,
|
|
|
|
AlignCenterVerticallyIcon,
|
|
|
|
AlignLeftIcon,
|
|
|
|
AlignRightIcon,
|
|
|
|
AlignTopIcon,
|
|
|
|
SpaceEvenlyHorizontallyIcon,
|
|
|
|
SpaceEvenlyVerticallyIcon,
|
|
|
|
StretchHorizontallyIcon,
|
|
|
|
StretchVerticallyIcon,
|
|
|
|
} from '@radix-ui/react-icons'
|
2021-11-19 10:19:06 +00:00
|
|
|
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 numberOfSelectedIdsSelector = (s: TDSnapshot) => {
|
|
|
|
return s.document.pageStates[s.appState.currentPageId].selectedIds.length
|
2021-11-03 16:46:33 +00:00
|
|
|
}
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const isDebugModeSelector = (s: TDSnapshot) => {
|
2021-11-03 16:46:33 +00:00
|
|
|
return s.settings.isDebugMode
|
|
|
|
}
|
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const hasGroupSelectedSelector = (s: TDSnapshot) => {
|
2021-11-03 16:46:33 +00:00
|
|
|
return s.document.pageStates[s.appState.currentPageId].selectedIds.some(
|
|
|
|
(id) => s.document.pages[s.appState.currentPageId].shapes[id].children !== undefined
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-11-05 14:13:14 +00:00
|
|
|
const preventDefault = (e: Event) => e.stopPropagation()
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
interface ContextMenuProps {
|
2022-01-06 14:00:23 +00:00
|
|
|
onBlur?: React.FocusEventHandler
|
2021-11-03 16:46:33 +00:00
|
|
|
children: React.ReactNode
|
|
|
|
}
|
|
|
|
|
2022-05-14 13:15:55 +00:00
|
|
|
export const ContextMenu = ({ onBlur, children }: ContextMenuProps) => {
|
2022-01-06 14:00:23 +00:00
|
|
|
return (
|
|
|
|
<RadixContextMenu.Root dir="ltr">
|
|
|
|
<RadixContextMenu.Trigger dir="ltr">{children}</RadixContextMenu.Trigger>
|
|
|
|
<InnerMenu onBlur={onBlur} />
|
|
|
|
</RadixContextMenu.Root>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
interface InnerContextMenuProps {
|
|
|
|
onBlur?: React.FocusEventHandler
|
|
|
|
}
|
|
|
|
|
|
|
|
const InnerMenu = React.memo(function InnerMenu({ onBlur }: InnerContextMenuProps) {
|
2021-11-16 16:01:29 +00:00
|
|
|
const app = useTldrawApp()
|
2021-11-19 10:19:06 +00:00
|
|
|
const numberOfSelectedIds = app.useStore(numberOfSelectedIdsSelector)
|
2021-11-16 16:01:29 +00:00
|
|
|
const isDebugMode = app.useStore(isDebugModeSelector)
|
|
|
|
const hasGroupSelected = app.useStore(hasGroupSelectedSelector)
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const rContent = React.useRef<HTMLDivElement>(null)
|
|
|
|
|
|
|
|
const handleFlipHorizontal = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.flipHorizontal()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleFlipVertical = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.flipVertical()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleDuplicate = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.duplicate()
|
|
|
|
}, [app])
|
|
|
|
|
|
|
|
const handleLock = React.useCallback(() => {
|
|
|
|
app.toggleLocked()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleGroup = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.group()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleMoveToBack = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.moveToBack()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleMoveBackward = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.moveBackward()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleMoveForward = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.moveForward()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleMoveToFront = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.moveToFront()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleDelete = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.delete()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
2021-11-27 16:26:15 +00:00
|
|
|
const handleCut = React.useCallback(() => {
|
|
|
|
app.cut()
|
|
|
|
}, [app])
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
const handleCopy = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.copy()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handlePaste = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.paste()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
2022-05-11 13:25:08 +00:00
|
|
|
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 })
|
2021-11-16 16:01:29 +00:00
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleUndo = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.undo()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const handleRedo = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.redo()
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
2022-01-10 16:36:28 +00:00
|
|
|
const handleExportPNG = React.useCallback(async () => {
|
2022-05-11 13:25:08 +00:00
|
|
|
app.exportImage(TDExportType.PNG, { scale: 2, quality: 1, transparentBackground: true })
|
2022-01-10 16:36:28 +00:00
|
|
|
}, [app])
|
|
|
|
|
|
|
|
const handleExportJPG = React.useCallback(async () => {
|
2022-05-11 13:25:08 +00:00
|
|
|
app.exportImage(TDExportType.JPG, { scale: 2, quality: 1, transparentBackground: false })
|
2022-01-10 16:36:28 +00:00
|
|
|
}, [app])
|
|
|
|
|
|
|
|
const handleExportWEBP = React.useCallback(async () => {
|
2022-05-11 13:25:08 +00:00
|
|
|
app.exportImage(TDExportType.WEBP, { scale: 2, quality: 1, transparentBackground: false })
|
2022-01-10 16:36:28 +00:00
|
|
|
}, [app])
|
|
|
|
|
|
|
|
const handleExportSVG = React.useCallback(async () => {
|
2022-05-11 13:25:08 +00:00
|
|
|
app.exportImage(TDExportType.SVG, { scale: 1, quality: 1, transparentBackground: false })
|
|
|
|
}, [app])
|
|
|
|
|
|
|
|
const handleCopyJSON = React.useCallback(async () => {
|
|
|
|
app.copyJson()
|
2022-01-10 16:36:28 +00:00
|
|
|
}, [app])
|
|
|
|
|
|
|
|
const handleExportJSON = React.useCallback(async () => {
|
2022-05-11 13:25:08 +00:00
|
|
|
app.exportJson()
|
2022-01-10 16:36:28 +00:00
|
|
|
}, [app])
|
|
|
|
|
2021-11-19 10:19:06 +00:00
|
|
|
const hasSelection = numberOfSelectedIds > 0
|
|
|
|
const hasTwoOrMore = numberOfSelectedIds > 1
|
|
|
|
const hasThreeOrMore = numberOfSelectedIds > 2
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
return (
|
2022-01-06 14:00:23 +00:00
|
|
|
<RadixContextMenu.Content
|
|
|
|
dir="ltr"
|
|
|
|
ref={rContent}
|
|
|
|
onEscapeKeyDown={preventDefault}
|
|
|
|
asChild
|
|
|
|
tabIndex={-1}
|
|
|
|
onBlur={onBlur}
|
|
|
|
>
|
2022-01-21 11:44:18 +00:00
|
|
|
<MenuContent id="TD-ContextMenu">
|
2022-01-06 14:00:23 +00:00
|
|
|
{hasSelection ? (
|
|
|
|
<>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleDuplicate} kbd="#D" id="TD-ContextMenu-Duplicate">
|
2022-01-06 14:00:23 +00:00
|
|
|
Duplicate
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton
|
|
|
|
onClick={handleFlipHorizontal}
|
|
|
|
kbd="⇧H"
|
|
|
|
id="TD-ContextMenu-Flip_Horizontal"
|
|
|
|
>
|
2022-01-06 14:00:23 +00:00
|
|
|
Flip Horizontal
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleFlipVertical} kbd="⇧V" id="TD-ContextMenu-Flip_Vertical">
|
2022-01-06 14:00:23 +00:00
|
|
|
Flip Vertical
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleLock} kbd="#⇧L" id="TD-ContextMenu- Lock_Unlock">
|
2022-01-06 14:00:23 +00:00
|
|
|
Lock / Unlock
|
|
|
|
</CMRowButton>
|
|
|
|
{(hasTwoOrMore || hasGroupSelected) && <Divider />}
|
|
|
|
{hasTwoOrMore && (
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Group">
|
2022-01-06 14:00:23 +00:00
|
|
|
Group
|
2021-11-27 16:26:15 +00:00
|
|
|
</CMRowButton>
|
2022-01-06 14:00:23 +00:00
|
|
|
)}
|
|
|
|
{hasGroupSelected && (
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleGroup} kbd="#G" id="TD-ContextMenu-Ungroup">
|
2022-01-06 14:00:23 +00:00
|
|
|
Ungroup
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMRowButton>
|
2022-01-06 14:00:23 +00:00
|
|
|
)}
|
|
|
|
<Divider />
|
2022-01-21 11:44:18 +00:00
|
|
|
<ContextMenuSubMenu label="Move" id="TD-ContextMenu-Move">
|
|
|
|
<CMRowButton onClick={handleMoveToFront} kbd="⇧]" id="TD-ContextMenu-Move-To_Front">
|
2022-01-06 14:00:23 +00:00
|
|
|
To Front
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleMoveForward} kbd="]" id="TD-ContextMenu-Move-Forward">
|
2022-01-06 14:00:23 +00:00
|
|
|
Forward
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleMoveBackward} kbd="[" id="TD-ContextMenu-Move-Backward">
|
2022-01-06 14:00:23 +00:00
|
|
|
Backward
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleMoveToBack} kbd="⇧[" id="TD-ContextMenu-Move-To_Back">
|
2022-01-06 14:00:23 +00:00
|
|
|
To Back
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMRowButton>
|
2022-01-06 14:00:23 +00:00
|
|
|
</ContextMenuSubMenu>
|
|
|
|
<MoveToPageMenu />
|
|
|
|
{hasTwoOrMore && (
|
|
|
|
<AlignDistributeSubMenu hasTwoOrMore={hasTwoOrMore} hasThreeOrMore={hasThreeOrMore} />
|
|
|
|
)}
|
|
|
|
<Divider />
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleCut} kbd="#X" id="TD-ContextMenu-Cut">
|
2022-01-06 14:00:23 +00:00
|
|
|
Cut
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleCopy} kbd="#C" id="TD-ContextMenu-Copy">
|
2022-01-06 14:00:23 +00:00
|
|
|
Copy
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
2022-01-06 14:00:23 +00:00
|
|
|
Paste
|
|
|
|
</CMRowButton>
|
2022-05-11 13:25:08 +00:00
|
|
|
<Divider />
|
|
|
|
<ContextMenuSubMenu label="Copy as..." size="small" id="TD-ContextMenu-Copy-As">
|
|
|
|
<CMRowButton onClick={handleCopySVG} id="TD-ContextMenu-Copy-as-SVG">
|
|
|
|
SVG
|
|
|
|
</CMRowButton>
|
|
|
|
<CMRowButton onClick={handleCopyPNG} id="TD-ContextMenu-Copy-As-PNG">
|
|
|
|
PNG
|
|
|
|
</CMRowButton>
|
|
|
|
{isDebugMode && (
|
|
|
|
<CMRowButton onClick={handleCopyJSON} id="TD-ContextMenu-Copy_as_JSON">
|
|
|
|
JSON
|
|
|
|
</CMRowButton>
|
|
|
|
)}
|
|
|
|
</ContextMenuSubMenu>
|
|
|
|
<ContextMenuSubMenu label="Export as..." size="small" id="TD-ContextMenu-Export">
|
|
|
|
<CMRowButton onClick={handleExportSVG} id="TD-ContextMenu-Export-SVG">
|
|
|
|
SVG
|
|
|
|
</CMRowButton>
|
|
|
|
<CMRowButton onClick={handleExportPNG} id="TD-ContextMenu-Export-PNG">
|
|
|
|
PNG
|
|
|
|
</CMRowButton>
|
|
|
|
<CMRowButton onClick={handleExportJPG} id="TD-ContextMenu-Export-JPG">
|
|
|
|
JPG
|
|
|
|
</CMRowButton>
|
|
|
|
<CMRowButton onClick={handleExportWEBP} id="TD-ContextMenu-Export-WEBP">
|
|
|
|
WEBP
|
|
|
|
</CMRowButton>
|
|
|
|
{isDebugMode && (
|
|
|
|
<CMRowButton onClick={handleExportJSON} id="TD-ContextMenu-Export-JSON">
|
|
|
|
JSON
|
|
|
|
</CMRowButton>
|
|
|
|
)}
|
|
|
|
</ContextMenuSubMenu>
|
2022-01-06 14:00:23 +00:00
|
|
|
<Divider />
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleDelete} kbd="⌫" id="TD-ContextMenu-Delete">
|
2022-01-06 14:00:23 +00:00
|
|
|
Delete
|
|
|
|
</CMRowButton>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handlePaste} kbd="#V" id="TD-ContextMenu-Paste">
|
2022-01-06 14:00:23 +00:00
|
|
|
Paste
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleUndo} kbd="#Z" id="TD-ContextMenu-Undo">
|
2022-01-06 14:00:23 +00:00
|
|
|
Undo
|
|
|
|
</CMRowButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMRowButton onClick={handleRedo} kbd="#⇧Z" id="TD-ContextMenu-Redo">
|
2022-01-06 14:00:23 +00:00
|
|
|
Redo
|
|
|
|
</CMRowButton>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</MenuContent>
|
|
|
|
</RadixContextMenu.Content>
|
2021-11-03 16:46:33 +00:00
|
|
|
)
|
2022-01-06 14:00:23 +00:00
|
|
|
})
|
2021-11-03 16:46:33 +00:00
|
|
|
|
2021-11-19 10:19:06 +00:00
|
|
|
/* ---------- Align and Distribute Sub Menu --------- */
|
|
|
|
|
2021-11-03 16:46:33 +00:00
|
|
|
function AlignDistributeSubMenu({
|
|
|
|
hasThreeOrMore,
|
|
|
|
}: {
|
|
|
|
hasTwoOrMore: boolean
|
|
|
|
hasThreeOrMore: boolean
|
|
|
|
}) {
|
2021-11-16 16:01:29 +00:00
|
|
|
const app = useTldrawApp()
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const alignTop = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.align(AlignType.Top)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const alignCenterVertical = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.align(AlignType.CenterVertical)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const alignBottom = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.align(AlignType.Bottom)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const stretchVertically = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.stretch(StretchType.Vertical)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const distributeVertically = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.distribute(DistributeType.Vertical)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const alignLeft = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.align(AlignType.Left)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const alignCenterHorizontal = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.align(AlignType.CenterHorizontal)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const alignRight = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.align(AlignType.Right)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const stretchHorizontally = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.stretch(StretchType.Horizontal)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
const distributeHorizontally = React.useCallback(() => {
|
2021-11-16 16:01:29 +00:00
|
|
|
app.distribute(DistributeType.Horizontal)
|
|
|
|
}, [app])
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
return (
|
2022-01-21 11:44:18 +00:00
|
|
|
<span id="TD-ContextMenu-Align_Duplicate">
|
|
|
|
<RadixContextMenu.Root dir="ltr">
|
|
|
|
<CMTriggerButton isSubmenu>Align / Distribute</CMTriggerButton>
|
|
|
|
<RadixContextMenu.Content asChild sideOffset={2} alignOffset={-2}>
|
|
|
|
<StyledGridContent numberOfSelected={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}>
|
|
|
|
<CMIconButton onClick={alignLeft} id="TD-ContextMenu-Align_Duplicate-AlignLeft">
|
|
|
|
<AlignLeftIcon />
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMIconButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMIconButton
|
|
|
|
onClick={alignCenterHorizontal}
|
|
|
|
id="TD-ContextMenu-Align_Duplicate-AlignCenterHorizontal"
|
|
|
|
>
|
|
|
|
<AlignCenterHorizontallyIcon />
|
2021-11-03 16:46:33 +00:00
|
|
|
</CMIconButton>
|
2022-01-21 11:44:18 +00:00
|
|
|
<CMIconButton onClick={alignRight} id="TD-ContextMenu-Align_Duplicate-AlignRight">
|
|
|
|
<AlignRightIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
<CMIconButton
|
|
|
|
onClick={stretchHorizontally}
|
|
|
|
id="TD-ContextMenu-Align_Duplicate-StretchHorizontal"
|
|
|
|
>
|
|
|
|
<StretchHorizontallyIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
{hasThreeOrMore && (
|
|
|
|
<CMIconButton
|
|
|
|
onClick={distributeHorizontally}
|
|
|
|
id="TD-ContextMenu-Align_Duplicate-SpaceEvenlyHorizontal"
|
|
|
|
>
|
|
|
|
<SpaceEvenlyHorizontallyIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
)}
|
|
|
|
<CMIconButton onClick={alignTop} id="TD-ContextMenu-Align_Duplicate-AlignTop">
|
|
|
|
<AlignTopIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
<CMIconButton
|
|
|
|
onClick={alignCenterVertical}
|
|
|
|
id="TD-ContextMenu-Align_Duplicate-AlignCenterVertical"
|
|
|
|
>
|
|
|
|
<AlignCenterVerticallyIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
<CMIconButton onClick={alignBottom} id="TD-ContextMenu-Align_Duplicate-AlignBottom">
|
|
|
|
<AlignBottomIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
<CMIconButton
|
|
|
|
onClick={stretchVertically}
|
|
|
|
id="TD-ContextMenu-Align_Duplicate-StretchVertical"
|
|
|
|
>
|
|
|
|
<StretchVerticallyIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
{hasThreeOrMore && (
|
|
|
|
<CMIconButton
|
|
|
|
onClick={distributeVertically}
|
|
|
|
id="TD-ContextMenu-Align_Duplicate-SpaceEvenlyVertical"
|
|
|
|
>
|
|
|
|
<SpaceEvenlyVerticallyIcon />
|
|
|
|
</CMIconButton>
|
|
|
|
)}
|
|
|
|
<CMArrow offset={13} />
|
|
|
|
</StyledGridContent>
|
|
|
|
</RadixContextMenu.Content>
|
|
|
|
</RadixContextMenu.Root>
|
|
|
|
</span>
|
2021-11-03 16:46:33 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const StyledGridContent = styled(MenuContent, {
|
|
|
|
display: 'grid',
|
|
|
|
variants: {
|
2021-11-19 10:19:06 +00:00
|
|
|
numberOfSelected: {
|
2021-11-03 16:46:33 +00:00
|
|
|
threeOrMore: {
|
|
|
|
gridTemplateColumns: 'repeat(5, auto)',
|
|
|
|
},
|
|
|
|
twoOrMore: {
|
|
|
|
gridTemplateColumns: 'repeat(4, auto)',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2021-11-19 10:19:06 +00:00
|
|
|
/* -------------- Move to Page Sub Menu ------------- */
|
2021-11-03 16:46:33 +00:00
|
|
|
|
2021-11-16 16:01:29 +00:00
|
|
|
const currentPageIdSelector = (s: TDSnapshot) => s.appState.currentPageId
|
|
|
|
const documentPagesSelector = (s: TDSnapshot) => s.document.pages
|
2021-11-03 16:46:33 +00:00
|
|
|
|
2022-05-14 13:15:55 +00:00
|
|
|
function MoveToPageMenu() {
|
2021-11-16 16:01:29 +00:00
|
|
|
const app = useTldrawApp()
|
|
|
|
const currentPageId = app.useStore(currentPageIdSelector)
|
|
|
|
const documentPages = app.useStore(documentPagesSelector)
|
2021-11-03 16:46:33 +00:00
|
|
|
|
|
|
|
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 (
|
|
|
|
<RadixContextMenu.Root dir="ltr">
|
|
|
|
<CMTriggerButton isSubmenu>Move To Page</CMTriggerButton>
|
|
|
|
<RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild>
|
|
|
|
<MenuContent>
|
|
|
|
{sorted.map(({ id, name }, i) => (
|
|
|
|
<CMRowButton
|
|
|
|
key={id}
|
|
|
|
disabled={id === currentPageId}
|
2021-11-16 16:01:29 +00:00
|
|
|
onClick={() => app.moveToPage(id)}
|
2021-11-03 16:46:33 +00:00
|
|
|
>
|
|
|
|
{name || `Page ${i}`}
|
|
|
|
</CMRowButton>
|
|
|
|
))}
|
|
|
|
<CMArrow offset={13} />
|
|
|
|
</MenuContent>
|
|
|
|
</RadixContextMenu.Content>
|
|
|
|
</RadixContextMenu.Root>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------- Submenu -------------------- */
|
|
|
|
|
|
|
|
export interface ContextMenuSubMenuProps {
|
|
|
|
label: string
|
2022-01-10 16:36:28 +00:00
|
|
|
size?: 'small'
|
2021-11-03 16:46:33 +00:00
|
|
|
children: React.ReactNode
|
2022-01-21 11:44:18 +00:00
|
|
|
id?: string
|
2021-11-03 16:46:33 +00:00
|
|
|
}
|
|
|
|
|
2022-05-14 13:15:55 +00:00
|
|
|
export function ContextMenuSubMenu({ children, label, size, id }: ContextMenuSubMenuProps) {
|
2021-11-03 16:46:33 +00:00
|
|
|
return (
|
2022-01-21 11:44:18 +00:00
|
|
|
<span id={id}>
|
|
|
|
<RadixContextMenu.Root dir="ltr">
|
|
|
|
<CMTriggerButton isSubmenu>{label}</CMTriggerButton>
|
|
|
|
<RadixContextMenu.Content dir="ltr" sideOffset={2} alignOffset={-2} asChild>
|
|
|
|
<MenuContent size={size}>
|
|
|
|
{children}
|
|
|
|
<CMArrow offset={13} />
|
|
|
|
</MenuContent>
|
|
|
|
</RadixContextMenu.Content>
|
|
|
|
</RadixContextMenu.Root>
|
|
|
|
</span>
|
2021-11-03 16:46:33 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------- Arrow --------------------- */
|
|
|
|
|
|
|
|
const CMArrow = styled(RadixContextMenu.ContextMenuArrow, {
|
|
|
|
fill: '$panel',
|
|
|
|
})
|
2021-11-19 10:19:06 +00:00
|
|
|
|
|
|
|
/* ------------------- IconButton ------------------- */
|
|
|
|
|
2022-05-14 13:15:55 +00:00
|
|
|
function CMIconButton({ onSelect, ...rest }: ToolButtonProps) {
|
2021-11-19 10:19:06 +00:00
|
|
|
return (
|
|
|
|
<RadixContextMenu.ContextMenuItem dir="ltr" onSelect={onSelect} asChild>
|
|
|
|
<ToolButton {...rest} />
|
|
|
|
</RadixContextMenu.ContextMenuItem>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------- RowButton ------------------- */
|
|
|
|
|
2022-01-21 11:44:18 +00:00
|
|
|
const CMRowButton = ({ id, ...rest }: RowButtonProps) => {
|
2021-11-19 10:19:06 +00:00
|
|
|
return (
|
2022-01-21 11:44:18 +00:00
|
|
|
<RadixContextMenu.ContextMenuItem asChild id={id}>
|
2021-11-19 10:19:06 +00:00
|
|
|
<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>
|
|
|
|
)
|
|
|
|
}
|