mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2025-06-22 15:35:04 +00:00
update
This commit is contained in:
parent
d9a42a6bc7
commit
a8a69caf76
@ -31,7 +31,7 @@
|
|||||||
<!-- Viewer Mode -->
|
<!-- Viewer Mode -->
|
||||||
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4">
|
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4">
|
||||||
<!-- Context Pills -->
|
<!-- Context Pills -->
|
||||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide min-h-[25px]"></div>
|
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide"></div>
|
||||||
|
|
||||||
<!-- Main Prompt Interface - Full Height -->
|
<!-- Main Prompt Interface - Full Height -->
|
||||||
<div class="flex-1 flex flex-col">
|
<div class="flex-1 flex flex-col">
|
||||||
@ -46,13 +46,6 @@
|
|||||||
<!-- Settings Pills -->
|
<!-- Settings Pills -->
|
||||||
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
||||||
|
|
||||||
<!-- Download Button -->
|
|
||||||
<button id="download-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-muted text-dynamic-foreground rounded-full flex items-center justify-center hover:bg-dynamic-muted/80 transition-colors focus-ring flex-shrink-0 shadow-lg touch-target mr-2" title="Download as PNG">
|
|
||||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
||||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Send Button (circular with arrow up) -->
|
<!-- Send Button (circular with arrow up) -->
|
||||||
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
|
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
|
||||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
@ -177,11 +177,6 @@ class EmbedPreview {
|
|||||||
if (copyButton) {
|
if (copyButton) {
|
||||||
copyButton.addEventListener('click', () => this.handleCopy());
|
copyButton.addEventListener('click', () => this.handleCopy());
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadButton = document.getElementById('download-button');
|
|
||||||
if (downloadButton) {
|
|
||||||
downloadButton.addEventListener('click', () => this.handleDownload());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -376,253 +371,6 @@ class EmbedPreview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDownload() {
|
|
||||||
try {
|
|
||||||
// Get computed styles
|
|
||||||
const computedStyles = getComputedStyle(document.documentElement);
|
|
||||||
const bgColor = `rgb(${computedStyles.getPropertyValue('--background').trim()})`;
|
|
||||||
const fgColor = `rgb(${computedStyles.getPropertyValue('--foreground').trim()})`;
|
|
||||||
const mutedColor = `rgb(${computedStyles.getPropertyValue('--muted').trim()})`;
|
|
||||||
const mutedFgColor = `rgb(${computedStyles.getPropertyValue('--muted-foreground').trim()})`;
|
|
||||||
const primaryColor = `rgb(${computedStyles.getPropertyValue('--primary').trim()})`;
|
|
||||||
const borderColor = `rgb(${computedStyles.getPropertyValue('--border').trim()})`;
|
|
||||||
|
|
||||||
// Create SVG representation
|
|
||||||
const { svg, scale } = this.createSVG({
|
|
||||||
bgColor,
|
|
||||||
fgColor,
|
|
||||||
mutedColor,
|
|
||||||
mutedFgColor,
|
|
||||||
primaryColor,
|
|
||||||
borderColor
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert SVG to PNG and download (scale will make it retina-compatible)
|
|
||||||
await this.downloadSVGasPNG(svg, 'prompt-preview.png', scale);
|
|
||||||
this.showNotification('Downloaded as PNG!');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Download failed:', error);
|
|
||||||
this.showNotification('Download failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createSVG(colors) {
|
|
||||||
const scale = 2; // 2x for retina displays
|
|
||||||
const width = 800 * scale;
|
|
||||||
const padding = 16 * scale; // Match sm:p-4 (1rem = 16px)
|
|
||||||
const mobileGap = 4 * scale; // Match gap-1 on mobile
|
|
||||||
const desktopGap = 8 * scale; // Match sm:gap-2
|
|
||||||
|
|
||||||
// Start calculating height dynamically
|
|
||||||
let yOffset = padding;
|
|
||||||
let svgContent = '';
|
|
||||||
|
|
||||||
// Context pills area with min-height of 25px
|
|
||||||
const contextPills = document.getElementById('context-pills');
|
|
||||||
const contextPillsHeight = 25 * scale; // min-h-[25px]
|
|
||||||
const contextPillsMargin = 8 * scale; // mb-0 sm:mb-2 (using desktop margin)
|
|
||||||
|
|
||||||
if (contextPills && contextPills.children.length > 0) {
|
|
||||||
let xOffset = padding;
|
|
||||||
const pillHeight = 20 * scale; // Smaller pills in context area
|
|
||||||
const pillPadding = 8 * scale;
|
|
||||||
const fontSize = 10.4 * scale;
|
|
||||||
|
|
||||||
for (const pill of contextPills.children) {
|
|
||||||
const pillText = pill.textContent.trim();
|
|
||||||
const pillWidth = Math.max(60 * scale, pillText.length * 7 * scale + pillPadding * 2);
|
|
||||||
|
|
||||||
// Pill background with proper opacity
|
|
||||||
const pillY = yOffset + (contextPillsHeight - pillHeight) / 2;
|
|
||||||
svgContent += `<rect x="${xOffset}" y="${pillY}" width="${pillWidth}" height="${pillHeight}" rx="${10 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.3" stroke-width="${scale}"/>`;
|
|
||||||
|
|
||||||
// Pill text - fix vertical alignment using dominant-baseline
|
|
||||||
const textY = pillY + pillHeight / 2;
|
|
||||||
svgContent += `<text x="${xOffset + pillWidth/2}" y="${textY}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-weight="500">${this.escapeXML(pillText)}</text>`;
|
|
||||||
|
|
||||||
xOffset += pillWidth + desktopGap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yOffset += contextPillsHeight + contextPillsMargin;
|
|
||||||
|
|
||||||
// Main prompt container - calculate needed height
|
|
||||||
const promptText = this.config.prompt || '← Enter your prompt on designer...';
|
|
||||||
const isPlaceholder = !this.config.prompt;
|
|
||||||
const containerPadding = 24 * scale; // p-3 sm:p-6 (using desktop padding)
|
|
||||||
const lineHeight = 22 * scale; // leading-relaxed
|
|
||||||
const fontSize = 16 * scale; // text-sm sm:text-base (using desktop size)
|
|
||||||
|
|
||||||
// Calculate wrapped text lines
|
|
||||||
const maxTextWidth = width - padding * 2 - containerPadding * 2;
|
|
||||||
const lines = this.wrapText(promptText, maxTextWidth / scale, 16); // Use unscaled values for text wrapping
|
|
||||||
const textHeight = Math.max(100 * scale, lines.length * lineHeight + containerPadding * 2);
|
|
||||||
|
|
||||||
// Prompt container background
|
|
||||||
svgContent += `<rect x="${padding}" y="${yOffset}" width="${width - padding * 2}" height="${textHeight}" rx="${12 * scale}" fill="${colors.mutedColor}" stroke="${colors.borderColor}" stroke-width="${scale}"/>`;
|
|
||||||
|
|
||||||
// Prompt text
|
|
||||||
const textColor = isPlaceholder ? colors.mutedFgColor : colors.fgColor;
|
|
||||||
const fontStyle = isPlaceholder ? 'italic' : 'normal';
|
|
||||||
let textY = yOffset + containerPadding + fontSize;
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
svgContent += `<text x="${padding + containerPadding}" y="${textY}" fill="${textColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-style="${fontStyle}">${this.escapeXML(line)}</text>`;
|
|
||||||
textY += lineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
yOffset += textHeight + desktopGap; // mt-0 sm:mt-2
|
|
||||||
|
|
||||||
// Bottom bar with settings pills and buttons
|
|
||||||
const bottomBarHeight = 40 * scale; // h-10 for buttons
|
|
||||||
const settingsY = yOffset + (bottomBarHeight - 28 * scale) / 2; // Center pills vertically
|
|
||||||
let settingsX = padding;
|
|
||||||
|
|
||||||
// Mode pill (with background)
|
|
||||||
if (this.config.mode) {
|
|
||||||
const modeText = this.capitalizeFirst(this.config.mode);
|
|
||||||
const modePadding = 12 * scale; // px-3
|
|
||||||
const modeHeight = 32 * scale; // py-2
|
|
||||||
const modeWidth = modeText.length * 8 * scale + modePadding * 2;
|
|
||||||
const modeFontSize = 12 * scale;
|
|
||||||
|
|
||||||
svgContent += `<rect x="${settingsX}" y="${settingsY}" width="${modeWidth}" height="${modeHeight}" rx="${16 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.2" stroke-width="${scale}"/>`;
|
|
||||||
svgContent += `<text x="${settingsX + modeWidth/2}" y="${settingsY + modeHeight/2}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modeFontSize}" font-weight="500">${modeText}</text>`;
|
|
||||||
|
|
||||||
settingsX += modeWidth + desktopGap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model pill (text only)
|
|
||||||
if (this.config.model) {
|
|
||||||
const modelText = this.config.model;
|
|
||||||
const modelFontSize = 12 * scale;
|
|
||||||
svgContent += `<text x="${settingsX + 4 * scale}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modelFontSize}">${modelText}</text>`;
|
|
||||||
settingsX += modelText.length * 7 * scale + 12 * scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thinking pill
|
|
||||||
if (this.config.thinking) {
|
|
||||||
const thinkingFontSize = 12 * scale;
|
|
||||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${thinkingFontSize}">🧠 Thinking</text>`;
|
|
||||||
settingsX += 70 * scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAX pill
|
|
||||||
if (this.config.max) {
|
|
||||||
const maxFontSize = 12 * scale;
|
|
||||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${maxFontSize}">⚡ MAX</text>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watermark on the right
|
|
||||||
const watermarkText = "prompts.chat/embed";
|
|
||||||
const watermarkFontSize = 14 * scale;
|
|
||||||
const watermarkX = width - padding - watermarkText.length * 7 * scale;
|
|
||||||
const watermarkY = yOffset + bottomBarHeight / 2;
|
|
||||||
|
|
||||||
// Add watermark text with subtle styling
|
|
||||||
svgContent += `<text x="${watermarkX}" y="${watermarkY}" dominant-baseline="middle" fill="${colors.mutedFgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${watermarkFontSize}" font-weight="400" opacity="0.7">${watermarkText}</text>`;
|
|
||||||
|
|
||||||
// Calculate final height
|
|
||||||
const totalHeight = yOffset + bottomBarHeight + padding;
|
|
||||||
|
|
||||||
// Create SVG with calculated height
|
|
||||||
let svg = `<svg width="${width}" height="${totalHeight}" viewBox="0 0 ${width} ${totalHeight}" xmlns="http://www.w3.org/2000/svg">`;
|
|
||||||
|
|
||||||
// Background
|
|
||||||
svg += `<rect width="${width}" height="${totalHeight}" fill="${colors.bgColor}"/>`;
|
|
||||||
|
|
||||||
// Add all content
|
|
||||||
svg += svgContent;
|
|
||||||
|
|
||||||
svg += '</svg>';
|
|
||||||
|
|
||||||
return { svg, scale };
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapText(text, maxWidth, fontSize) {
|
|
||||||
const words = text.split(' ');
|
|
||||||
const lines = [];
|
|
||||||
let currentLine = '';
|
|
||||||
// More accurate character width calculation based on font size
|
|
||||||
const avgCharWidth = fontSize * 0.55; // Adjusted for typical character width
|
|
||||||
|
|
||||||
for (const word of words) {
|
|
||||||
const testLine = currentLine ? currentLine + ' ' + word : word;
|
|
||||||
const testWidth = testLine.length * avgCharWidth;
|
|
||||||
|
|
||||||
if (testWidth > maxWidth && currentLine) {
|
|
||||||
lines.push(currentLine);
|
|
||||||
currentLine = word;
|
|
||||||
} else {
|
|
||||||
currentLine = testLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentLine) {
|
|
||||||
lines.push(currentLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
escapeXML(text) {
|
|
||||||
return text
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadSVGasPNG(svgString, filename, scale = 1) {
|
|
||||||
// Create a blob from the SVG string
|
|
||||||
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
// Create an image element
|
|
||||||
const img = new Image();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
img.onload = () => {
|
|
||||||
// Create canvas with the actual pixel dimensions
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Enable high quality rendering for retina
|
|
||||||
ctx.imageSmoothingEnabled = true;
|
|
||||||
ctx.imageSmoothingQuality = 'high';
|
|
||||||
|
|
||||||
// Draw the image at full resolution
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
// Convert to PNG and download
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = URL.createObjectURL(blob);
|
|
||||||
a.download = filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(a.href);
|
|
||||||
}
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
resolve();
|
|
||||||
}, 'image/png', 1.0); // Maximum quality
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = () => {
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
reject(new Error('Failed to load SVG'));
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = url;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showNotification(message) {
|
showNotification(message) {
|
||||||
const notification = document.getElementById('notification');
|
const notification = document.getElementById('notification');
|
||||||
if (!notification) return;
|
if (!notification) return;
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<!-- Viewer Mode -->
|
<!-- Viewer Mode -->
|
||||||
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4">
|
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4">
|
||||||
<!-- Context Pills -->
|
<!-- Context Pills -->
|
||||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide min-h-[25px]"></div>
|
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden mb-0 sm:mb-2 pb-1 scrollbar-hide"></div>
|
||||||
|
|
||||||
<!-- Main Prompt Interface - Full Height -->
|
<!-- Main Prompt Interface - Full Height -->
|
||||||
<div class="flex-1 flex flex-col">
|
<div class="flex-1 flex flex-col">
|
||||||
@ -46,13 +46,6 @@
|
|||||||
<!-- Settings Pills -->
|
<!-- Settings Pills -->
|
||||||
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
||||||
|
|
||||||
<!-- Download Button -->
|
|
||||||
<button id="download-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-muted text-dynamic-foreground rounded-full flex items-center justify-center hover:bg-dynamic-muted/80 transition-colors focus-ring flex-shrink-0 shadow-lg touch-target mr-2" title="Download as PNG">
|
|
||||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
||||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Send Button (circular with arrow up) -->
|
<!-- Send Button (circular with arrow up) -->
|
||||||
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
|
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
|
||||||
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
@ -177,11 +177,6 @@ class EmbedPreview {
|
|||||||
if (copyButton) {
|
if (copyButton) {
|
||||||
copyButton.addEventListener('click', () => this.handleCopy());
|
copyButton.addEventListener('click', () => this.handleCopy());
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadButton = document.getElementById('download-button');
|
|
||||||
if (downloadButton) {
|
|
||||||
downloadButton.addEventListener('click', () => this.handleDownload());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -376,253 +371,6 @@ class EmbedPreview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDownload() {
|
|
||||||
try {
|
|
||||||
// Get computed styles
|
|
||||||
const computedStyles = getComputedStyle(document.documentElement);
|
|
||||||
const bgColor = `rgb(${computedStyles.getPropertyValue('--background').trim()})`;
|
|
||||||
const fgColor = `rgb(${computedStyles.getPropertyValue('--foreground').trim()})`;
|
|
||||||
const mutedColor = `rgb(${computedStyles.getPropertyValue('--muted').trim()})`;
|
|
||||||
const mutedFgColor = `rgb(${computedStyles.getPropertyValue('--muted-foreground').trim()})`;
|
|
||||||
const primaryColor = `rgb(${computedStyles.getPropertyValue('--primary').trim()})`;
|
|
||||||
const borderColor = `rgb(${computedStyles.getPropertyValue('--border').trim()})`;
|
|
||||||
|
|
||||||
// Create SVG representation
|
|
||||||
const { svg, scale } = this.createSVG({
|
|
||||||
bgColor,
|
|
||||||
fgColor,
|
|
||||||
mutedColor,
|
|
||||||
mutedFgColor,
|
|
||||||
primaryColor,
|
|
||||||
borderColor
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert SVG to PNG and download (scale will make it retina-compatible)
|
|
||||||
await this.downloadSVGasPNG(svg, 'prompt-preview.png', scale);
|
|
||||||
this.showNotification('Downloaded as PNG!');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Download failed:', error);
|
|
||||||
this.showNotification('Download failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createSVG(colors) {
|
|
||||||
const scale = 2; // 2x for retina displays
|
|
||||||
const width = 800 * scale;
|
|
||||||
const padding = 16 * scale; // Match sm:p-4 (1rem = 16px)
|
|
||||||
const mobileGap = 4 * scale; // Match gap-1 on mobile
|
|
||||||
const desktopGap = 8 * scale; // Match sm:gap-2
|
|
||||||
|
|
||||||
// Start calculating height dynamically
|
|
||||||
let yOffset = padding;
|
|
||||||
let svgContent = '';
|
|
||||||
|
|
||||||
// Context pills area with min-height of 25px
|
|
||||||
const contextPills = document.getElementById('context-pills');
|
|
||||||
const contextPillsHeight = 25 * scale; // min-h-[25px]
|
|
||||||
const contextPillsMargin = 8 * scale; // mb-0 sm:mb-2 (using desktop margin)
|
|
||||||
|
|
||||||
if (contextPills && contextPills.children.length > 0) {
|
|
||||||
let xOffset = padding;
|
|
||||||
const pillHeight = 20 * scale; // Smaller pills in context area
|
|
||||||
const pillPadding = 8 * scale;
|
|
||||||
const fontSize = 10.4 * scale;
|
|
||||||
|
|
||||||
for (const pill of contextPills.children) {
|
|
||||||
const pillText = pill.textContent.trim();
|
|
||||||
const pillWidth = Math.max(60 * scale, pillText.length * 7 * scale + pillPadding * 2);
|
|
||||||
|
|
||||||
// Pill background with proper opacity
|
|
||||||
const pillY = yOffset + (contextPillsHeight - pillHeight) / 2;
|
|
||||||
svgContent += `<rect x="${xOffset}" y="${pillY}" width="${pillWidth}" height="${pillHeight}" rx="${10 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.3" stroke-width="${scale}"/>`;
|
|
||||||
|
|
||||||
// Pill text - fix vertical alignment using dominant-baseline
|
|
||||||
const textY = pillY + pillHeight / 2;
|
|
||||||
svgContent += `<text x="${xOffset + pillWidth/2}" y="${textY}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-weight="500">${this.escapeXML(pillText)}</text>`;
|
|
||||||
|
|
||||||
xOffset += pillWidth + desktopGap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yOffset += contextPillsHeight + contextPillsMargin;
|
|
||||||
|
|
||||||
// Main prompt container - calculate needed height
|
|
||||||
const promptText = this.config.prompt || '← Enter your prompt on designer...';
|
|
||||||
const isPlaceholder = !this.config.prompt;
|
|
||||||
const containerPadding = 24 * scale; // p-3 sm:p-6 (using desktop padding)
|
|
||||||
const lineHeight = 22 * scale; // leading-relaxed
|
|
||||||
const fontSize = 16 * scale; // text-sm sm:text-base (using desktop size)
|
|
||||||
|
|
||||||
// Calculate wrapped text lines
|
|
||||||
const maxTextWidth = width - padding * 2 - containerPadding * 2;
|
|
||||||
const lines = this.wrapText(promptText, maxTextWidth / scale, 16); // Use unscaled values for text wrapping
|
|
||||||
const textHeight = Math.max(100 * scale, lines.length * lineHeight + containerPadding * 2);
|
|
||||||
|
|
||||||
// Prompt container background
|
|
||||||
svgContent += `<rect x="${padding}" y="${yOffset}" width="${width - padding * 2}" height="${textHeight}" rx="${12 * scale}" fill="${colors.mutedColor}" stroke="${colors.borderColor}" stroke-width="${scale}"/>`;
|
|
||||||
|
|
||||||
// Prompt text
|
|
||||||
const textColor = isPlaceholder ? colors.mutedFgColor : colors.fgColor;
|
|
||||||
const fontStyle = isPlaceholder ? 'italic' : 'normal';
|
|
||||||
let textY = yOffset + containerPadding + fontSize;
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
svgContent += `<text x="${padding + containerPadding}" y="${textY}" fill="${textColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${fontSize}" font-style="${fontStyle}">${this.escapeXML(line)}</text>`;
|
|
||||||
textY += lineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
yOffset += textHeight + desktopGap; // mt-0 sm:mt-2
|
|
||||||
|
|
||||||
// Bottom bar with settings pills and buttons
|
|
||||||
const bottomBarHeight = 40 * scale; // h-10 for buttons
|
|
||||||
const settingsY = yOffset + (bottomBarHeight - 28 * scale) / 2; // Center pills vertically
|
|
||||||
let settingsX = padding;
|
|
||||||
|
|
||||||
// Mode pill (with background)
|
|
||||||
if (this.config.mode) {
|
|
||||||
const modeText = this.capitalizeFirst(this.config.mode);
|
|
||||||
const modePadding = 12 * scale; // px-3
|
|
||||||
const modeHeight = 32 * scale; // py-2
|
|
||||||
const modeWidth = modeText.length * 8 * scale + modePadding * 2;
|
|
||||||
const modeFontSize = 12 * scale;
|
|
||||||
|
|
||||||
svgContent += `<rect x="${settingsX}" y="${settingsY}" width="${modeWidth}" height="${modeHeight}" rx="${16 * scale}" fill="${colors.primaryColor}" fill-opacity="0.1" stroke="${colors.primaryColor}" stroke-opacity="0.2" stroke-width="${scale}"/>`;
|
|
||||||
svgContent += `<text x="${settingsX + modeWidth/2}" y="${settingsY + modeHeight/2}" text-anchor="middle" dominant-baseline="middle" fill="${colors.fgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modeFontSize}" font-weight="500">${modeText}</text>`;
|
|
||||||
|
|
||||||
settingsX += modeWidth + desktopGap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model pill (text only)
|
|
||||||
if (this.config.model) {
|
|
||||||
const modelText = this.config.model;
|
|
||||||
const modelFontSize = 12 * scale;
|
|
||||||
svgContent += `<text x="${settingsX + 4 * scale}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${modelFontSize}">${modelText}</text>`;
|
|
||||||
settingsX += modelText.length * 7 * scale + 12 * scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thinking pill
|
|
||||||
if (this.config.thinking) {
|
|
||||||
const thinkingFontSize = 12 * scale;
|
|
||||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${thinkingFontSize}">🧠 Thinking</text>`;
|
|
||||||
settingsX += 70 * scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAX pill
|
|
||||||
if (this.config.max) {
|
|
||||||
const maxFontSize = 12 * scale;
|
|
||||||
svgContent += `<text x="${settingsX}" y="${yOffset + bottomBarHeight/2}" dominant-baseline="middle" fill="${colors.primaryColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${maxFontSize}">⚡ MAX</text>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watermark on the right
|
|
||||||
const watermarkText = "prompts.chat/embed";
|
|
||||||
const watermarkFontSize = 14 * scale;
|
|
||||||
const watermarkX = width - padding - watermarkText.length * 7 * scale;
|
|
||||||
const watermarkY = yOffset + bottomBarHeight / 2;
|
|
||||||
|
|
||||||
// Add watermark text with subtle styling
|
|
||||||
svgContent += `<text x="${watermarkX}" y="${watermarkY}" dominant-baseline="middle" fill="${colors.mutedFgColor}" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif" font-size="${watermarkFontSize}" font-weight="400" opacity="0.7">${watermarkText}</text>`;
|
|
||||||
|
|
||||||
// Calculate final height
|
|
||||||
const totalHeight = yOffset + bottomBarHeight + padding;
|
|
||||||
|
|
||||||
// Create SVG with calculated height
|
|
||||||
let svg = `<svg width="${width}" height="${totalHeight}" viewBox="0 0 ${width} ${totalHeight}" xmlns="http://www.w3.org/2000/svg">`;
|
|
||||||
|
|
||||||
// Background
|
|
||||||
svg += `<rect width="${width}" height="${totalHeight}" fill="${colors.bgColor}"/>`;
|
|
||||||
|
|
||||||
// Add all content
|
|
||||||
svg += svgContent;
|
|
||||||
|
|
||||||
svg += '</svg>';
|
|
||||||
|
|
||||||
return { svg, scale };
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapText(text, maxWidth, fontSize) {
|
|
||||||
const words = text.split(' ');
|
|
||||||
const lines = [];
|
|
||||||
let currentLine = '';
|
|
||||||
// More accurate character width calculation based on font size
|
|
||||||
const avgCharWidth = fontSize * 0.55; // Adjusted for typical character width
|
|
||||||
|
|
||||||
for (const word of words) {
|
|
||||||
const testLine = currentLine ? currentLine + ' ' + word : word;
|
|
||||||
const testWidth = testLine.length * avgCharWidth;
|
|
||||||
|
|
||||||
if (testWidth > maxWidth && currentLine) {
|
|
||||||
lines.push(currentLine);
|
|
||||||
currentLine = word;
|
|
||||||
} else {
|
|
||||||
currentLine = testLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentLine) {
|
|
||||||
lines.push(currentLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
escapeXML(text) {
|
|
||||||
return text
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadSVGasPNG(svgString, filename, scale = 1) {
|
|
||||||
// Create a blob from the SVG string
|
|
||||||
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
// Create an image element
|
|
||||||
const img = new Image();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
img.onload = () => {
|
|
||||||
// Create canvas with the actual pixel dimensions
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Enable high quality rendering for retina
|
|
||||||
ctx.imageSmoothingEnabled = true;
|
|
||||||
ctx.imageSmoothingQuality = 'high';
|
|
||||||
|
|
||||||
// Draw the image at full resolution
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
|
|
||||||
// Convert to PNG and download
|
|
||||||
canvas.toBlob((blob) => {
|
|
||||||
if (blob) {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = URL.createObjectURL(blob);
|
|
||||||
a.download = filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(a.href);
|
|
||||||
}
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
resolve();
|
|
||||||
}, 'image/png', 1.0); // Maximum quality
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = () => {
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
reject(new Error('Failed to load SVG'));
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = url;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showNotification(message) {
|
showNotification(message) {
|
||||||
const notification = document.getElementById('notification');
|
const notification = document.getElementById('notification');
|
||||||
if (!notification) return;
|
if (!notification) return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user