Skip to content

Commit 3e7e4b3

Browse files
Merge branch 'main' into feat/selectable
2 parents 00893c9 + 1ac29ce commit 3e7e4b3

File tree

4 files changed

+59
-3
lines changed

4 files changed

+59
-3
lines changed

client/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@radix-ui/react-slider": "^1.3.6",
1515
"@tailwindcss/vite": "^4.1.14",
1616
"axios": "^1.12.2",
17+
"canvas2svg": "^1.0.16",
1718
"class-variance-authority": "^0.7.1",
1819
"clsx": "^2.1.1",
1920
"jspdf": "^3.0.3",

client/src/components/Canvas.jsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { StrokeControl } from "./StrokeControl";
55
import { toast } from "sonner";
66
import { io } from "socket.io-client";
77
import tinycolor from "tinycolor2";
8+
import { jsPDF } from "jspdf";
9+
import C2S from "canvas2svg";
810

911
// --- New Type Definitions for a Shape Object ---
1012
const SHAPE_TYPE = {
@@ -590,6 +592,48 @@ export const Canvas = () => {
590592
}
591593
};
592594

595+
const handleExport = (format) => {
596+
const canvas = canvasRef.current;
597+
if (!canvas) return;
598+
599+
switch (format) {
600+
case "png": {
601+
const link = document.createElement("a");
602+
link.download = "canvas.png";
603+
link.href = canvas.toDataURL("image/png");
604+
link.click();
605+
break;
606+
}
607+
608+
case "pdf": {
609+
const pdf = new jsPDF({
610+
orientation: "landscape",
611+
unit: "px",
612+
format: [canvas.width, canvas.height],
613+
});
614+
const imgData = canvas.toDataURL("image/png");
615+
pdf.addImage(imgData, "PNG", 0, 0, canvas.width, canvas.height);
616+
pdf.save("canvas.pdf");
617+
break;
618+
}
619+
620+
case "svg": {
621+
const svgCtx = new C2S(canvas.width, canvas.height);
622+
const svgData = svgCtx.getSerializedSvg();
623+
const blob = new Blob([svgData], { type: "image/svg+xml" });
624+
const link = document.createElement("a");
625+
link.download = "canvas.svg";
626+
link.href = URL.createObjectURL(blob);
627+
link.click();
628+
break;
629+
}
630+
631+
default:
632+
console.warn("Unsupported format:", format);
633+
}
634+
};
635+
636+
593637
return (
594638
<div className="relative w-full h-screen overflow-hidden bg-canvas">
595639
<div className="fixed top-4 left-4 z-[9999]">

client/src/components/Toolbar.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export const Toolbar = ({ activeTool, onToolChange, onClear, onExport }) => {
2828
{ type: "line", icon: Minus },
2929
];
3030

31+
const handleExport = (format) => {
32+
onExport(format);
33+
}
34+
3135
const brushTypes = [
3236
{
3337
id: "solid",
@@ -143,15 +147,15 @@ export const Toolbar = ({ activeTool, onToolChange, onClear, onExport }) => {
143147
</Button>
144148
}
145149
>
146-
<DropdownMenuItem onClick={() => onExport("png")}>
150+
<DropdownMenuItem onClick={() => handleExport("png")}>
147151
<FileImage className="h-4 w-4" />
148152
Export as PNG
149153
</DropdownMenuItem>
150-
<DropdownMenuItem onClick={() => onExport("svg")}>
154+
<DropdownMenuItem onClick={() => handleExport("svg")}>
151155
<FileType className="h-4 w-4" />
152156
Export as SVG
153157
</DropdownMenuItem>
154-
<DropdownMenuItem onClick={() => onExport("pdf")}>
158+
<DropdownMenuItem onClick={() => handleExport("pdf")}>
155159
<FileType className="h-4 w-4" />
156160
Export as PDF
157161
</DropdownMenuItem>

0 commit comments

Comments
 (0)