import { Editor, TldrawUiMenuActionItem, TldrawUiMenuItem, TldrawUiMenuSubmenu, TLGeoShape, TLShape, } from "tldraw" import { TldrawUiMenuGroup } from "tldraw" import { DefaultContextMenu } from "tldraw" import { TLUiContextMenuProps, useEditor } from "tldraw" import { cameraHistory, copyLinkToCurrentView, lockCameraToFrame, revertCamera, zoomToSelection, } from "./cameraUtils" import { useState, useEffect } from "react" import { saveToPdf } from "../utils/pdfUtils" import { TLFrameShape } from "tldraw" import { searchText } from "../utils/searchUtils" import { llm } from "../utils/llmUtils" import { getEdge } from "@/propagators/tlgraph" const getAllFrames = (editor: Editor) => { return editor .getCurrentPageShapes() .filter((shape): shape is TLFrameShape => shape.type === "frame") .map((frame) => ({ id: frame.id, title: frame.props.name || "Untitled Frame", })) } export function CustomContextMenu(props: TLUiContextMenuProps) { const editor = useEditor() const [selectedShapes, setSelectedShapes] = useState([]) const [selectedIds, setSelectedIds] = useState([]) // Update selection state more frequently useEffect(() => { const updateSelection = () => { setSelectedShapes(editor.getSelectedShapes()) setSelectedIds(editor.getSelectedShapeIds()) } // Initial update updateSelection() // Subscribe to selection changes const unsubscribe = editor.addListener("change", updateSelection) return () => { if (typeof unsubscribe === "function") { ;(unsubscribe as () => void)() } } }, [editor]) const hasSelection = selectedIds.length > 0 const hasCameraHistory = cameraHistory.length > 0 // Check if exactly one frame is selected const hasFrameSelected = selectedShapes.length === 1 && selectedShapes[0].type === "frame" return ( {/* Camera Controls Group */} zoomToSelection(editor)} /> copyLinkToCurrentView(editor)} /> revertCamera(editor)} /> saveToPdf(editor)} /> { const selectedShape = editor.getSelectedShapes()[0]; if (!selectedShape || selectedShape.type !== 'arrow') return; const edge = getEdge(selectedShape, editor); if (!edge) return; const sourceShape = editor.getShape(edge.from); const sourceText = sourceShape && sourceShape.type === "geo" ? (sourceShape as TLGeoShape).props.text : ""; llm( `Instruction: ${edge.text} ${sourceText ? `Context: ${sourceText}` : ""}`, localStorage.getItem("openai_api_key") || "", (partialResponse: string) => { editor.updateShape({ id: edge.to, type: "geo", props: { ...(editor.getShape(edge.to) as TLGeoShape).props, text: partialResponse } }); } ) }} /> {/* Creation Tools Group */} { editor.setCurrentTool("VideoChat") }} /> { editor.setCurrentTool("ChatBox") }} /> { editor.setCurrentTool("Embed") }} /> { editor.setCurrentTool("Slide") }} /> { editor.setCurrentTool("MycrozineTemplate") }} /> { editor.setCurrentTool("Markdown") }} /> { editor.setCurrentTool("Prompt") }} /> {/* Frame Controls */} lockCameraToFrame(editor)} /> {getAllFrames(editor).map((frame) => ( { const shape = editor.getShape(frame.id) if (shape) { editor.zoomToBounds(editor.getShapePageBounds(shape)!, { animation: { duration: 400, easing: (t) => t * (2 - t) }, }) editor.select(frame.id) } }} /> ))} { const otherUsers = Array.from(editor.store.allRecords()).filter( (record) => record.typeName === "instance_presence" && record.id !== editor.user.getId(), ) otherUsers.forEach((user) => editor.startFollowingUser(user.id)) }} /> { const otherUsers = Array.from(editor.store.allRecords()).filter( (record) => record.typeName === "instance_presence" && record.id !== editor.user.getId(), ) otherUsers.forEach((_user) => editor.stopFollowingUser()) }} /> searchText(editor)} /> ) }