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 }