PrintToPDF integration
This commit is contained in:
parent
5d17bf7795
commit
10c191212c
|
|
@ -20,11 +20,14 @@
|
|||
"@tldraw/sync-core": "^3.6.0",
|
||||
"@tldraw/tldraw": "^3.6.0",
|
||||
"@tldraw/tlschema": "^3.6.0",
|
||||
"@types/jspdf": "^2.0.0",
|
||||
"@types/markdown-it": "^14.1.1",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"cloudflare-workers-unfurl": "^0.0.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
"html2canvas": "^1.4.1",
|
||||
"itty-router": "^5.0.17",
|
||||
"jspdf": "^2.5.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { EmbedShape } from "./shapes/EmbedShapeUtil"
|
|||
import { MarkdownShape } from "./shapes/MarkdownShapeUtil"
|
||||
import { MarkdownTool } from "./tools/MarkdownTool"
|
||||
import { createRoot } from "react-dom/client"
|
||||
import { handleInitialPageLoad } from "@/utils/handleInitialPageLoad"
|
||||
import { handleInitialPageLoad } from "./utils/handleInitialPageLoad"
|
||||
|
||||
inject()
|
||||
|
||||
|
|
@ -47,11 +47,10 @@ export default function InteractiveShapeExample() {
|
|||
)
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />)
|
||||
//createRoot(document.getElementById("root")!).render(<App />)
|
||||
|
||||
function App() {
|
||||
return (
|
||||
// <React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Default />} />
|
||||
|
|
@ -60,6 +59,7 @@ function App() {
|
|||
<Route path="/inbox" element={<Inbox />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
// </React.StrictMode>
|
||||
)
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
zoomToSelection,
|
||||
} from "./cameraUtils"
|
||||
import { useState, useEffect } from "react"
|
||||
import { saveToPdf } from "../utils/pdfUtils"
|
||||
|
||||
export function CustomContextMenu(props: TLUiContextMenuProps) {
|
||||
const editor = useEditor()
|
||||
|
|
@ -71,6 +72,14 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
|||
disabled={!hasCameraHistory}
|
||||
onSelect={() => revertCamera(editor)}
|
||||
/>
|
||||
<TldrawUiMenuItem
|
||||
id="save-to-pdf"
|
||||
label="Save Selection as PDF"
|
||||
icon="file"
|
||||
kbd="alt+p"
|
||||
disabled={!hasSelection}
|
||||
onSelect={() => saveToPdf(editor)}
|
||||
/>
|
||||
</TldrawUiMenuGroup>
|
||||
|
||||
{/* Creation Tools Group */}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,20 @@ import { TldrawUiMenuItem } from "tldraw"
|
|||
import { DefaultToolbar, DefaultToolbarContent } from "tldraw"
|
||||
import { useTools } from "tldraw"
|
||||
import { useEditor } from "tldraw"
|
||||
import { useState, useEffect } from "react"
|
||||
|
||||
export function CustomToolbar() {
|
||||
const editor = useEditor()
|
||||
const tools = useTools()
|
||||
const [isReady, setIsReady] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (editor && tools) {
|
||||
setIsReady(true)
|
||||
}
|
||||
}, [editor, tools])
|
||||
|
||||
if (!isReady) return null
|
||||
|
||||
return (
|
||||
<DefaultToolbar>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
revertCamera,
|
||||
zoomToSelection,
|
||||
} from "./cameraUtils"
|
||||
import { saveToPdf } from "../utils/pdfUtils"
|
||||
|
||||
export const overrides: TLUiOverrides = {
|
||||
tools(editor, tools) {
|
||||
|
|
@ -85,6 +86,17 @@ export const overrides: TLUiOverrides = {
|
|||
kbd: "shift+l",
|
||||
onSelect: () => lockCameraToFrame(editor),
|
||||
},
|
||||
saveToPdf: {
|
||||
id: "save-to-pdf",
|
||||
label: "Save Selection as PDF",
|
||||
kbd: "alt+p",
|
||||
onSelect: () => {
|
||||
if (editor.getSelectedShapeIds().length > 0) {
|
||||
saveToPdf(editor)
|
||||
}
|
||||
},
|
||||
readonlyOk: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import { Editor } from "tldraw"
|
||||
import { Editor, TLEventMap, TLInstancePresence } from "tldraw"
|
||||
|
||||
export const handleInitialPageLoad = (editor: Editor) => {
|
||||
if (!editor.store || !editor.getInstanceState().isFocused) {
|
||||
setTimeout(() => handleInitialPageLoad(editor), 100)
|
||||
return
|
||||
export const handleInitialPageLoad = async (editor: Editor) => {
|
||||
// Wait for editor to be ready
|
||||
while (!editor.store || !editor.getInstanceState().isFocused) {
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve))
|
||||
}
|
||||
|
||||
editor.setCurrentTool("hand")
|
||||
try {
|
||||
// Set initial tool
|
||||
editor.setCurrentTool("hand")
|
||||
|
||||
// Force a re-render of the toolbar
|
||||
editor.emit("toolsChange" as keyof TLEventMap)
|
||||
} catch (error) {
|
||||
console.error("Error during initial page load:", error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import { Editor, TLShapeId } from "tldraw"
|
||||
import { jsPDF } from "jspdf"
|
||||
import { exportToBlob } from "tldraw"
|
||||
|
||||
export const saveToPdf = async (editor: Editor) => {
|
||||
const selectedIds = editor.getSelectedShapeIds()
|
||||
if (selectedIds.length === 0) return
|
||||
|
||||
try {
|
||||
// Get common bounds of selected shapes
|
||||
const selectionBounds = editor.getSelectionPageBounds()
|
||||
if (!selectionBounds) return
|
||||
|
||||
// Get blob using the editor's export functionality
|
||||
const blob = await exportToBlob({
|
||||
editor,
|
||||
ids: selectedIds,
|
||||
format: "svg",
|
||||
opts: {
|
||||
scale: 2,
|
||||
background: true,
|
||||
padding: 10,
|
||||
preserveAspectRatio: "xMidYMid slice",
|
||||
},
|
||||
})
|
||||
|
||||
if (!blob) return
|
||||
|
||||
// Convert blob to data URL
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
// Create image from blob
|
||||
const img = new Image()
|
||||
img.src = url
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onload = resolve
|
||||
img.onerror = reject
|
||||
})
|
||||
|
||||
// Create PDF with proper dimensions
|
||||
const pdf = new jsPDF({
|
||||
orientation: selectionBounds.width > selectionBounds.height ? "l" : "p",
|
||||
unit: "px",
|
||||
format: [selectionBounds.width, selectionBounds.height],
|
||||
})
|
||||
|
||||
// Add the image to the PDF
|
||||
pdf.addImage(
|
||||
img,
|
||||
"SVG",
|
||||
0,
|
||||
0,
|
||||
selectionBounds.width,
|
||||
selectionBounds.height,
|
||||
)
|
||||
pdf.save("canvas-selection.pdf")
|
||||
|
||||
// Cleanup
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error("Failed to generate PDF:", error)
|
||||
alert("Failed to generate PDF. Please try again.")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue