Skip to content

Commit cd509e5

Browse files
Sushma-stack-hubSushma-stack-hub
authored andcommitted
feat: implement high-definition centered vector download for badges
1 parent e15a46a commit cd509e5

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

app/customize/components/ExportPanel.tsx

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react';
12
import type { ReactElement } from 'react';
23
import type { ExportFormat } from '../types';
34
import { getPlaceholderSnippet } from '../utils';
@@ -30,6 +31,95 @@ export function ExportPanel({
3031
? `Copy ${formatLabel} export snippet to clipboard`
3132
: `Add a GitHub username to enable copying the ${formatLabel} export snippet`;
3233

34+
// Track async server download states
35+
const [isDownloading, setIsDownloading] = useState(false);
36+
37+
const handleDownloadBadge = async () => {
38+
if (!hasUsername || !snippet) return;
39+
40+
try {
41+
setIsDownloading(true);
42+
43+
// 1. Extract the API URL source string from the template snippet container
44+
const urlMatch = snippet.match(/\((https?:\/\/[^)]+)\)/) || snippet.match(/src="([^"]+)"/);
45+
let targetUrl = urlMatch ? urlMatch[1] : '';
46+
47+
if (!targetUrl) {
48+
console.error('Could not parse the live API badge target URL from snippet.');
49+
return;
50+
}
51+
52+
// 2. Clear out HTML character entities if grabbed from HTML embed strings
53+
targetUrl = targetUrl.replace(/&/g, '&');
54+
55+
// 3. SECURE LOCAL WORKSPACE TESTING: Redirect backend calls to your local server instance
56+
if (targetUrl.includes('https://commitpulse.vercel.app')) {
57+
targetUrl = targetUrl.replace('https://commitpulse.vercel.app', window.location.origin);
58+
}
59+
60+
// 4. Append a cache-busting refresh query parameter to guarantee the latest custom colors
61+
if (targetUrl.includes('?')) {
62+
targetUrl += '&refresh=true';
63+
} else {
64+
targetUrl += '?refresh=true';
65+
}
66+
67+
// 5. Fetch the real, server-side generated raw XML text of the SVG from your local server
68+
const response = await fetch(targetUrl);
69+
if (!response.ok) throw new Error('Network response failed to retrieve badge data stream.');
70+
71+
let svgText = await response.text();
72+
73+
// 6. ABSOLUTE VIEWPORT CENTERING INJECTION
74+
// We attach absolute positioning properties directly into the root vector stylesheet.
75+
// This forces the standalone browser view to scale up and lock dead center in the viewport grid!
76+
const standaloneStyles = `
77+
<style id="standalone-canvas-centering">
78+
svg {
79+
display: block !important;
80+
margin: auto !important;
81+
position: absolute !important;
82+
top: 0 !important; bottom: 0 !important;
83+
left: 0 !important; right: 0 !important;
84+
max-width: 90vw !important;
85+
max-height: 85vh !important;
86+
width: 100% !important;
87+
height: 100% !important;
88+
}
89+
html, body {
90+
background-color: #0d1117 !important; /* Premium matching background void */
91+
margin: 0 !important;
92+
padding: 0 !important;
93+
overflow: hidden !important;
94+
}
95+
</style>
96+
`;
97+
98+
// Inject directly right after the opening tag to guarantee compilation matching
99+
svgText = svgText.replace(/<svg[^>]*>/, (match) => `${match}${standaloneStyles}`);
100+
101+
// 7. Convert the modified markup string into an optimal vector image blob buffer
102+
const blob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' });
103+
104+
// 8. Instantiate a virtual link and fire an automated native download with a unique timestamp
105+
const downloadUrl = URL.createObjectURL(blob);
106+
const downloadLink = document.createElement('a');
107+
downloadLink.href = downloadUrl;
108+
downloadLink.download = `perfect-centered-monolith-${Date.now()}.svg`;
109+
document.body.appendChild(downloadLink);
110+
downloadLink.click();
111+
112+
// 9. Housekeeping memory cleanup optimization
113+
document.body.removeChild(downloadLink);
114+
URL.revokeObjectURL(downloadUrl);
115+
} catch (error) {
116+
console.error('Failed to download custom vector badge image asset:', error);
117+
alert('Failed to download the badge asset directly from the server pipeline.');
118+
} finally {
119+
setIsDownloading(false);
120+
}
121+
};
122+
33123
return (
34124
<div className="bg-white/70 backdrop-blur-xl border border-black/10 dark:bg-black/35 dark:border-white/10 rounded-[1.75rem] p-6 shadow-[0_20px_60px_rgba(0,0,0,0.15)]">
35125
<div className="flex flex-col gap-4 mb-5 md:flex-row md:items-center md:justify-between">
@@ -64,6 +154,47 @@ export function ExportPanel({
64154
))}
65155
</div>
66156

157+
{/* Centered High-Definition Vector Download Button */}
158+
<button
159+
type="button"
160+
onClick={handleDownloadBadge}
161+
disabled={!hasUsername || isDownloading}
162+
aria-label={
163+
hasUsername
164+
? 'Download custom monolith layout as an image'
165+
: 'Add a GitHub username to enable image downloads'
166+
}
167+
className={`relative inline-flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-bold transition-all duration-200 ${
168+
!hasUsername || isDownloading
169+
? 'bg-gray-200/90 border border-black/10 text-gray-500 cursor-not-allowed dark:bg-white/10 dark:border-white/10 dark:text-white/35'
170+
: 'bg-emerald-500/10 border border-emerald-500/30 text-emerald-500 hover:bg-emerald-500/20 hover:scale-[1.03] active:scale-[0.97]'
171+
}`}
172+
>
173+
<svg
174+
xmlns="http://www.w3.org/2000/svg"
175+
className={`w-3.5 h-3.5 ${isDownloading ? 'animate-spin' : ''}`}
176+
viewBox="0 0 24 24"
177+
fill="none"
178+
stroke="currentColor"
179+
strokeWidth="2.5"
180+
strokeLinecap="round"
181+
strokeLinejoin="round"
182+
aria-hidden="true"
183+
>
184+
{isDownloading ? (
185+
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" />
186+
) : (
187+
<>
188+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v4" />
189+
<polyline points="7 10 12 15 17 10" />
190+
<line x1="12" y1="15" x2="12" y2="3" />
191+
</>
192+
)}
193+
</svg>
194+
{isDownloading ? 'Downloading...' : 'Download Badge'}
195+
</button>
196+
197+
{/* Clipboard Copy Button */}
67198
<button
68199
id="copy-markdown-btn"
69200
onClick={onCopy}

0 commit comments

Comments
 (0)