Repository navigation
Expand file tree
/
Copy pathuseExportImage.ts
More file actions
127 lines (107 loc) · 3.71 KB
/
Copy pathuseExportImage.ts
File metadata and controls
127 lines (107 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { useCallback, useState } from 'react';
export type ExportFormat = 'png' | 'svg' | 'pdf';
interface UseExportImageOptions {
targetSelector?: string;
filename?: string;
scale?: number;
}
interface UseExportImageReturn {
exportImage: (format: ExportFormat) => Promise<void>;
isExporting: boolean;
error: string | null;
}
export function useExportImage({
targetSelector = '[data-export-target]',
filename = 'commitpulse-comparison',
scale = 2,
}: UseExportImageOptions = {}): UseExportImageReturn {
const [isExporting, setIsExporting] = useState(false);
const [error, setError] = useState<string | null>(null);
const exportImage = useCallback(
async (format: ExportFormat) => {
setIsExporting(true);
setError(null);
try {
const target = document.querySelector<HTMLElement>(targetSelector);
if (!target) {
throw new Error(
`Export target not found. Add data-export-target attribute to the comparison card.`
);
}
if (format === 'png') {
await exportAsPng(target, filename, scale);
} else if (format === 'svg') {
await exportAsSvg(target, filename);
} else {
await exportAsPdf(target, filename);
}
} catch (err) {
const message = err instanceof Error ? err.message : 'Export failed. Please try again.';
setError(message);
console.error('[useExportImage]', err);
} finally {
setIsExporting(false);
}
},
[targetSelector, filename, scale]
);
return { exportImage, isExporting, error };
}
async function scrollAndWait(): Promise<void> {
window.scrollTo(0, document.body.scrollHeight);
await new Promise((r) => setTimeout(r, 800));
window.scrollTo(0, 0);
await new Promise((r) => setTimeout(r, 300));
}
async function exportAsPng(element: HTMLElement, filename: string, scale: number): Promise<void> {
const { toPng } = await import('html-to-image');
await scrollAndWait();
const dataUrl = await toPng(element, {
pixelRatio: scale,
backgroundColor: '#ffffff',
});
triggerDownload(dataUrl, `${filename}.png`);
}
async function exportAsSvg(element: HTMLElement, filename: string): Promise<void> {
const { toSvg } = await import('html-to-image');
await scrollAndWait();
const dataUrl = await toSvg(element, {
backgroundColor: '#ffffff',
});
triggerDownload(dataUrl, `${filename}.svg`);
}
async function exportAsPdf(element: HTMLElement, filename: string): Promise<void> {
// Convert the rendered UI to an SVG string and then reuse the existing PDF exporter.
// This keeps the output consistent with the SVG that users already see/download.
const { toSvg } = await import('html-to-image');
const { default: JsPDF } = await import('jspdf');
const { exportSvgToPdf } = await import('@/lib/pdf-export');
await scrollAndWait();
const dataUrl = await toSvg(element, {
backgroundColor: '#ffffff',
});
// html-to-image returns a data URL. Extract the raw SVG markup.
const match = dataUrl.match(/^data:image\/svg\+xml;charset=utf-8,(.*)$/);
if (!match) {
throw new Error('Failed to export SVG markup for PDF');
}
const svgMarkup = decodeURIComponent(match[1]);
const pdf = new JsPDF({
orientation: 'landscape',
unit: 'pt',
format: 'a4',
});
await exportSvgToPdf(svgMarkup, `${filename}.pdf`, pdf);
}
function triggerDownload(href: string, filename: string): void {
const link = document.createElement('a');
link.href = href;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
if (href.startsWith('blob:')) {
setTimeout(() => URL.revokeObjectURL(href), 10_000);
}
}