import { defineMessages, useEditor, useValue } from '@tldraw/editor'
import { useActions } from '../context/actions'
import { useUiEvents } from '../context/events'
import { useToasts } from '../context/toasts'
import {
useAnySelectedShapesCount,
useHasShapesOnPage,
useUnlockedSelectedShapesCount,
} from '../hooks/menu-hooks'
import { useSomeActionsEnabled, useSomeActionsAvailable } from '../hooks/useActionState'
import { useReadonly } from '../hooks/useReadonly'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { TldrawUiMenuActionCheckboxItem } from './primitives/menus/TldrawUiMenuActionCheckboxItem'
import { TldrawUiMenuActionItem } from './primitives/menus/TldrawUiMenuActionItem'
import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
import { TldrawUiMenuItem } from './primitives/menus/TldrawUiMenuItem'
import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
const messages = defineMessages({
contextMenuArrange: { id: 'context-menu.arrange', defaultMessage: 'Arrange' },
contextMenuCopyAs: { id: 'context-menu.copy-as', defaultMessage: 'Copy as' },
contextMenuEdit: { id: 'context-menu.edit', defaultMessage: 'Edit' },
contextMenuExportAs: { id: 'context-menu.export-as', defaultMessage: 'Export as' },
contextMenuMoveToPage: { id: 'context-menu.move-to-page', defaultMessage: 'Move to page' },
movedToPageAction: { id: 'toast.moved-to-page.action', defaultMessage: 'Go back' },
movedToPageDesc: { id: 'toast.moved-to-page.desc', defaultMessage: 'Moved to {page}.' },
movedToPageTitle: { id: 'toast.moved-to-page.title', defaultMessage: 'Changed page' },
})
/* -------------------- Selection ------------------- */
/** @public @react */
export function ToggleAutoSizeMenuItem() {
return
}
/** @public @react */
export function EditLinkMenuItem() {
return
}
/** @public @react */
export function DuplicateMenuItem() {
return
}
/** @public @react */
export function FlattenMenuItem() {
return
}
/** @public @react */
export function DownloadOriginalMenuItem() {
return
}
/** @public @react */
export function GroupMenuItem() {
return
}
/** @public @react */
export function UngroupMenuItem() {
return
}
/** @public @react */
export function FrameSelectionMenuItem() {
return
}
/** @public @react */
export function RemoveFrameMenuItem() {
return
}
/** @public @react */
export function FitFrameToContentMenuItem() {
return
}
/** @public @react */
export function ToggleLockMenuItem() {
return
}
/** @public @react */
export function ToggleTransparentBgMenuItem() {
return
}
/** @public @react */
export function UnlockAllMenuItem() {
return
}
/* ---------------------- Zoom ---------------------- */
/** @public @react */
export function ZoomTo100MenuItem() {
return
}
/** @public @react */
export function ZoomToFitMenuItem() {
return (
)
}
/** @public @react */
export function ZoomToSelectionMenuItem() {
return (
)
}
/* -------------------- Clipboard ------------------- */
/** @public @react */
export function ClipboardMenuGroup() {
return (
)
}
const COPY_AS_ACTIONS = ['copy-as-svg', 'copy-as-png', 'copy-as-json']
/** @public @react */
export function CopyAsMenuGroup() {
const atLeastOneShapeOnPage = useHasShapesOnPage()
const hasCopyActions = useSomeActionsAvailable(COPY_AS_ACTIONS)
if (!hasCopyActions) return null
return (
{COPY_AS_ACTIONS.map((id) => (
))}
)
}
/** @public @react */
export function ExportAsMenuGroup() {
const actions = useActions()
// If a consumer has removed the export actions via `overrides`, don't render an empty submenu.
if (!actions['export-as-svg'] && !actions['export-as-png']) return null
return (
)
}
/** @public @react */
export function CutMenuItem() {
return
}
/** @public @react */
export function CopyMenuItem() {
return
}
/** @public @react */
export function PasteMenuItem() {
return
}
/* ------------------- Conversions ------------------ */
/** @public @react */
export function ConversionsMenuGroup() {
const atLeastOneShapeOnPage = useHasShapesOnPage()
if (!atLeastOneShapeOnPage) return null
return (
)
}
/* ------------------ Set Selection ----------------- */
/** @public @react */
export function SelectAllMenuItem() {
return
}
/* ------------------ Delete Group ------------------ */
/** @public @react */
export function DeleteMenuItem() {
return
}
/* --------------------- Modify --------------------- */
/** @internal */
export const EDIT_ACTIONS = [
'group',
'ungroup',
'flatten-to-image',
'frame-selection',
'edit-link',
'fit-frame-to-content',
'remove-frame',
'convert-to-embed',
'convert-to-bookmark',
'toggle-auto-size',
'toggle-lock',
]
/** @public @react */
export function EditMenuSubmenu() {
// The selection check keeps an action replaced without isEnabled from opening the submenu on
// an empty canvas.
const hasSelection = useAnySelectedShapesCount(1)
const show = useSomeActionsEnabled(EDIT_ACTIONS)
if (!hasSelection || !show) return null
return (
{EDIT_ACTIONS.map((id) => (
))}
)
}
const ARRANGE_GROUPS: { id: string; actionIds: string[] }[] = [
{
id: 'align',
actionIds: [
'align-left',
'align-center-horizontal',
'align-right',
'align-top',
'align-center-vertical',
'align-bottom',
],
},
{ id: 'distribute', actionIds: ['distribute-horizontal', 'distribute-vertical'] },
{ id: 'stretch', actionIds: ['stretch-horizontal', 'stretch-vertical'] },
{ id: 'flip', actionIds: ['flip-horizontal', 'flip-vertical'] },
{ id: 'order', actionIds: ['pack', 'stack-horizontal', 'stack-vertical'] },
]
/** @internal */
export const ARRANGE_ACTIONS = ARRANGE_GROUPS.flatMap((group) => group.actionIds)
function ActionGroup({ id, actionIds }: { id: string; actionIds: string[] }) {
const show = useSomeActionsEnabled(actionIds)
if (!show) return null
return (
{actionIds.map((actionId) => (
))}
)
}
/** @public @react */
export function ArrangeMenuSubmenu() {
const hasSelection = useUnlockedSelectedShapesCount(1)
const show = useSomeActionsEnabled(ARRANGE_ACTIONS)
if (!hasSelection || !show) return null
return (
{ARRANGE_GROUPS.map((group) => (
))}
)
}
/** @internal */
export const REORDER_ACTIONS = ['bring-to-front', 'bring-forward', 'send-backward', 'send-to-back']
/** @public @react */
export function ReorderMenuSubmenu() {
const hasSelection = useUnlockedSelectedShapesCount(1)
const show = useSomeActionsEnabled(REORDER_ACTIONS)
if (!hasSelection || !show) return null
return (
)
}
/** @public @react */
export function MoveToPageMenu() {
const editor = useEditor()
const msg = useTranslation()
const pages = useValue('pages', () => editor.getPages(), [editor])
const currentPageId = useValue('current page id', () => editor.getCurrentPageId(), [editor])
const { addToast } = useToasts()
const trackEvent = useUiEvents()
const isReadonlyMode = useReadonly()
const oneSelected = useUnlockedSelectedShapesCount(1)
if (!oneSelected) return null
if (isReadonlyMode) return null
return (
{pages.map((page) => (
30 ? `${page.name.slice(0, 30)}…` : page.name}
onSelect={() => {
editor.markHistoryStoppingPoint('move_shapes_to_page')
editor.moveShapesToPage(editor.getSelectedShapeIds(), page.id)
const toPage = editor.getPage(page.id)
if (toPage) {
addToast({
title: msg(messages.movedToPageTitle),
description: msg(messages.movedToPageDesc, { page: toPage.name }),
actions: [
{
label: msg(messages.movedToPageAction),
type: 'primary',
onClick: () => {
editor.markHistoryStoppingPoint('change-page')
editor.setCurrentPage(currentPageId)
},
},
],
})
}
trackEvent('move-to-page', { source: 'context-menu' })
}}
/>
))}
)
}
/** @public @react */
export function ConvertToBookmarkMenuItem() {
return
}
/** @public @react */
export function ConvertToEmbedMenuItem() {
return
}
/* ------------------- Preferences ------------------ */
/** @public @react */
export function ToggleSnapModeItem() {
return
}
/** @public @react */
export function ToggleToolLockItem() {
return
}
/** @public @react */
export function ToggleGridItem() {
return
}
/** @public @react */
export function ToggleWrapModeItem() {
return
}
/** @public @react */
export function ToggleDarkModeItem() {
return
}
/** @public @react */
export function ToggleFocusModeItem() {
return
}
/** @public @react */
export function ToggleEdgeScrollingItem() {
return
}
/** @public @react */
export function ToggleInvertZoomItem() {
return
}
/** @public @react */
export function ToggleReduceMotionItem() {
return
}
/** @public @react */
export function ToggleKeyboardShortcutsItem() {
return
}
/** @public @react */
export function ToggleEnhancedA11yModeItem() {
return
}
/** @public @react */
export function ToggleDebugModeItem() {
return
}
/** @public @react */
export function ToggleDynamicSizeModeItem() {
return
}
/** @public @react */
export function TogglePasteAtCursorItem() {
return
}
/* ---------------------- Print --------------------- */
/** @public @react */
export function PrintItem() {
return
}
/* ---------------------- Multiplayer --------------------- */
/** @public @react */
export function CursorChatItem() {
return
}