Update src/App.jsx
Browse files- src/App.jsx +156 -51
src/App.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
| 2 |
import QRCode from 'qrcode';
|
| 3 |
import jsQR from 'jsqr';
|
| 4 |
|
| 5 |
-
// Custom Alert Modal Component
|
| 6 |
const CustomAlert = ({ message, onClose }) => {
|
| 7 |
if (!message) return null;
|
| 8 |
|
|
@@ -21,24 +21,27 @@ const CustomAlert = ({ message, onClose }) => {
|
|
| 21 |
);
|
| 22 |
};
|
| 23 |
|
| 24 |
-
// --- QRGeneratorTab Component
|
| 25 |
const QRGeneratorTab = ({
|
| 26 |
content, setContent, errorLevel, setErrorLevel, qrColor, setQrColor,
|
| 27 |
isSuggestingIdeas, suggestIdeas, downloadQrCode, qrCanvasRef,
|
| 28 |
isInputFocused, setIsInputFocused, currentThemeClasses,
|
| 29 |
qrStyles, selectedStyleIndex, setSelectedStyleIndex, styleCanvasRefs, drawQrCode,
|
| 30 |
-
suggestedIdeas, showSuggestedIdeas, setShowSuggestedIdeas
|
|
|
|
| 31 |
}) => {
|
| 32 |
// Effect to generate QR code on the main canvas (Generator Tab)
|
| 33 |
useEffect(() => {
|
| 34 |
-
|
|
|
|
| 35 |
}, [content, qrColor, errorLevel, drawQrCode, qrCanvasRef]);
|
| 36 |
|
| 37 |
// Effect to generate QR codes for style previews (Style Selection Panel)
|
| 38 |
useEffect(() => {
|
| 39 |
qrStyles.forEach((style, index) => {
|
| 40 |
const canvas = styleCanvasRefs.current[index];
|
| 41 |
-
|
|
|
|
| 42 |
});
|
| 43 |
}, [qrStyles, drawQrCode, styleCanvasRefs]);
|
| 44 |
|
|
@@ -67,15 +70,15 @@ const QRGeneratorTab = ({
|
|
| 67 |
<div className="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 mt-4">
|
| 68 |
<button
|
| 69 |
className={`w-full sm:w-1/2 py-3 px-6 rounded-xl font-bold text-lg bg-gradient-to-r ${currentThemeClasses.primaryAccent} text-white shadow-lg ${currentThemeClasses.buttonGlow} transform hover:-translate-y-1 transition-all duration-300 ease-in-out flex items-center justify-center`}
|
| 70 |
-
onClick={suggestIdeas}
|
| 71 |
-
disabled={isSuggestingIdeas}
|
| 72 |
>
|
| 73 |
{isSuggestingIdeas ? (
|
| 74 |
<i className="fas fa-spinner fa-spin mr-2"></i>
|
| 75 |
) : (
|
| 76 |
-
<i className="fas fa-lightbulb mr-2"></i>
|
| 77 |
)}
|
| 78 |
-
💡 {isSuggestingIdeas ? 'Suggesting Ideas...' : 'Suggest Ideas'}
|
| 79 |
</button>
|
| 80 |
<button
|
| 81 |
className={`w-full sm:w-1/2 py-3 px-6 rounded-xl font-bold text-lg bg-gradient-to-r ${currentThemeClasses.secondaryAccent} text-white shadow-lg ${currentThemeClasses.buttonGlow} transform hover:-translate-y-1 transition-all duration-300 ease-in-out flex items-center justify-center`}
|
|
@@ -85,10 +88,10 @@ const QRGeneratorTab = ({
|
|
| 85 |
</button>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
-
{suggestedIdeas && (
|
| 89 |
<div className={`mt-4 p-4 rounded-xl border ${currentThemeClasses.glassBorder} ${currentThemeClasses.glassBg} shadow-inner transition-all duration-300 overflow-hidden`}>
|
| 90 |
<div className="flex justify-between items-center cursor-pointer" onClick={() => setShowSuggestedIdeas(!showSuggestedIdeas)}>
|
| 91 |
-
<label className={`font-semibold ${currentThemeClasses.labelColor} font-inter`}>Suggested QR Ideas:</label>
|
| 92 |
<i className={`fas ${showSuggestedIdeas ? 'fa-chevron-up' : 'fa-chevron-down'} transition-transform duration-300`}></i>
|
| 93 |
</div>
|
| 94 |
<div className={`overflow-hidden transition-all duration-500 ease-in-out ${showSuggestedIdeas ? 'max-h-96 opacity-100 mt-2' : 'max-h-0 opacity-0'}`}>
|
|
@@ -118,6 +121,36 @@ const QRGeneratorTab = ({
|
|
| 118 |
onChange={(e) => setQrColor(e.target.value)}
|
| 119 |
/>
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
<label className={`mt-6 mb-2 font-semibold ${currentThemeClasses.labelColor} font-inter`}>QR Preview:</label>
|
| 122 |
<div
|
| 123 |
className={`relative flex justify-center items-center w-full min-h-[300px] rounded-2xl border-2 ${currentThemeClasses.qrPreviewBorder} ${currentThemeClasses.qrPreviewBg} p-4 shadow-inner overflow-hidden transform transition-all duration-300 hover:scale-[1.01] hover:-translate-y-1 ${currentThemeClasses.glow}`}
|
|
@@ -157,7 +190,7 @@ const QRGeneratorTab = ({
|
|
| 157 |
);
|
| 158 |
};
|
| 159 |
|
| 160 |
-
// --- QRDecoderTab Component
|
| 161 |
const QRDecoderTab = ({
|
| 162 |
decodedContent, setDecodedContent,
|
| 163 |
isSummarizing, summarizeContent, showAlert, decodeQrCode,
|
|
@@ -196,22 +229,22 @@ const QRDecoderTab = ({
|
|
| 196 |
|
| 197 |
<button
|
| 198 |
className={`mt-4 w-full py-3 px-6 rounded-xl font-bold text-lg bg-gradient-to-r ${currentThemeClasses.secondaryAccent} text-white shadow-lg ${currentThemeClasses.buttonGlow} transform hover:-translate-y-1 transition-all duration-300 ease-in-out flex items-center justify-center`}
|
| 199 |
-
onClick={summarizeContent}
|
| 200 |
disabled={isSummarizing || !decodedContent.trim() || decodedContent === 'No QR code detected.'}
|
| 201 |
>
|
| 202 |
{isSummarizing ? (
|
| 203 |
<i className="fas fa-spinner fa-spin mr-2"></i>
|
| 204 |
) : (
|
| 205 |
-
<i className="fas fa-file-alt mr-2"></i>
|
| 206 |
)}
|
| 207 |
-
📝 {isSummarizing ? 'Summarizing Content...' : 'Summarize Content'}
|
| 208 |
</button>
|
| 209 |
</div>
|
| 210 |
</div>
|
| 211 |
);
|
| 212 |
};
|
| 213 |
|
| 214 |
-
// --- SettingsTab Component
|
| 215 |
const SettingsTab = ({ currentTheme, setCurrentTheme, currentThemeClasses, themes }) => {
|
| 216 |
return (
|
| 217 |
<div className={`flex flex-col p-4 md:p-6 space-y-6 h-full ${currentThemeClasses.bg} ${currentThemeClasses.text}`}>
|
|
@@ -246,37 +279,53 @@ const App = () => {
|
|
| 246 |
const [activeTab, setActiveTab] = useState('generator');
|
| 247 |
const [content, setContent] = useState('');
|
| 248 |
const [errorLevel, setErrorLevel] = useState('L');
|
| 249 |
-
const [qrColor, setQrColor] = useState('#
|
| 250 |
const [decodedContent, setDecodedContent] = useState('');
|
| 251 |
const [selectedStyleIndex, setSelectedStyleIndex] = useState(0);
|
| 252 |
-
const [currentTheme, setCurrentTheme] = useState('dark');
|
| 253 |
const [isSummarizing, setIsSummarizing] = useState(false);
|
| 254 |
-
const [suggestedIdeas, setSuggestedIdeas] = useState('');
|
| 255 |
-
const [isSuggestingIdeas, setIsSuggestingIdeas] = useState(false);
|
| 256 |
-
const [showSuggestedIdeas, setShowSuggestedIdeas] = useState(false);
|
| 257 |
const [alertMessage, setAlertMessage] = useState(null);
|
| 258 |
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
|
|
|
|
|
| 259 |
|
| 260 |
const qrCanvasRef = useRef(null);
|
| 261 |
const styleCanvasRefs = useRef(Array(12).fill(null));
|
| 262 |
|
| 263 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
const qrStyles = [
|
| 265 |
-
{ name: "
|
| 266 |
-
{ name: "
|
| 267 |
-
{ name: "
|
| 268 |
-
{ name: "
|
| 269 |
-
{ name: "
|
| 270 |
-
{ name: "
|
| 271 |
-
{ name: "
|
| 272 |
-
{ name: "
|
| 273 |
-
{ name: "
|
| 274 |
-
{ name: "
|
| 275 |
-
{ name: "
|
| 276 |
-
{ name: "
|
| 277 |
];
|
| 278 |
|
| 279 |
-
// Define themes
|
| 280 |
const themes = {
|
| 281 |
dark: {
|
| 282 |
bg: 'bg-gradient-to-br from-[#0A0A0A] via-[#1A0A2A] to-[#0A0A0A] animate-gradient-shift',
|
|
@@ -436,13 +485,14 @@ const App = () => {
|
|
| 436 |
|
| 437 |
const currentThemeClasses = themes[currentTheme];
|
| 438 |
|
| 439 |
-
// Function to show custom alert
|
| 440 |
const showAlert = (message) => {
|
| 441 |
setAlertMessage(message);
|
| 442 |
};
|
| 443 |
|
| 444 |
-
// Callback to generate QR code on a given canvas
|
| 445 |
-
|
|
|
|
| 446 |
if (!canvas) {
|
| 447 |
console.warn("Canvas element is null, cannot draw QR code.");
|
| 448 |
return;
|
|
@@ -465,8 +515,8 @@ const App = () => {
|
|
| 465 |
errorCorrectionLevel: errorLevel,
|
| 466 |
width: size,
|
| 467 |
color: {
|
| 468 |
-
dark: color,
|
| 469 |
-
light:
|
| 470 |
}
|
| 471 |
}, function (error) {
|
| 472 |
if (error) console.error("QR Code drawing error:", error);
|
|
@@ -474,25 +524,75 @@ const App = () => {
|
|
| 474 |
}
|
| 475 |
}, []);
|
| 476 |
|
| 477 |
-
// Function to download the generated QR code (
|
| 478 |
-
const downloadQrCode = () => {
|
| 479 |
if (content.trim() === '') {
|
| 480 |
showAlert('Please enter content to generate QR code.');
|
| 481 |
return;
|
| 482 |
}
|
| 483 |
-
|
| 484 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
const pngUrl = canvas.toDataURL("image/png");
|
| 486 |
const downloadLink = document.createElement('a');
|
| 487 |
downloadLink.href = pngUrl;
|
| 488 |
-
downloadLink.download =
|
| 489 |
document.body.appendChild(downloadLink);
|
| 490 |
downloadLink.click();
|
| 491 |
document.body.removeChild(downloadLink);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
}
|
| 493 |
};
|
| 494 |
|
| 495 |
-
// Function to decode QR code from an uploaded image
|
| 496 |
const decodeQrCode = (event) => {
|
| 497 |
const file = event.target.files[0];
|
| 498 |
if (!file) return;
|
|
@@ -512,6 +612,7 @@ const App = () => {
|
|
| 512 |
if (jsQR) {
|
| 513 |
let code = jsQR(imageData.data, imageData.width, imageData.height);
|
| 514 |
|
|
|
|
| 515 |
if (!code) {
|
| 516 |
for (let i = 0; i < imageData.data.length; i += 4) {
|
| 517 |
imageData.data[i] = 255 - imageData.data[i];
|
|
@@ -541,7 +642,7 @@ const App = () => {
|
|
| 541 |
reader.readAsDataURL(file);
|
| 542 |
};
|
| 543 |
|
| 544 |
-
// Function to summarize content using Gemini API
|
| 545 |
const summarizeContent = async () => {
|
| 546 |
if (!decodedContent.trim() || decodedContent === 'No QR code detected.') {
|
| 547 |
showAlert('Please decode a QR code first to summarize its content.');
|
|
@@ -573,6 +674,7 @@ const App = () => {
|
|
| 573 |
setContent(text); // Update the generator's input field with summarized content
|
| 574 |
showAlert('Content summarized and moved to QR Generator tab!');
|
| 575 |
} else {
|
|
|
|
| 576 |
showAlert("Failed to summarize content. Please try again.");
|
| 577 |
}
|
| 578 |
} catch (error) {
|
|
@@ -583,7 +685,7 @@ const App = () => {
|
|
| 583 |
}
|
| 584 |
};
|
| 585 |
|
| 586 |
-
// Function to suggest ideas for QR code generation
|
| 587 |
const suggestIdeas = async () => {
|
| 588 |
setIsSuggestingIdeas(true);
|
| 589 |
setSuggestedIdeas('Thinking of ideas...');
|
|
@@ -621,6 +723,7 @@ const App = () => {
|
|
| 621 |
const text = result.candidates[0].content.parts[0].text;
|
| 622 |
setSuggestedIdeas(text);
|
| 623 |
} else {
|
|
|
|
| 624 |
setSuggestedIdeas("Could not suggest ideas. Please try again.");
|
| 625 |
}
|
| 626 |
} catch (error) {
|
|
@@ -670,20 +773,22 @@ const App = () => {
|
|
| 670 |
content={content} setContent={setContent}
|
| 671 |
errorLevel={errorLevel} setErrorLevel={setErrorLevel}
|
| 672 |
qrColor={qrColor} setQrColor={setQrColor}
|
| 673 |
-
isSuggestingIdeas={isSuggestingIdeas} suggestIdeas={suggestIdeas}
|
| 674 |
downloadQrCode={downloadQrCode} qrCanvasRef={qrCanvasRef}
|
| 675 |
isInputFocused={isInputFocused} setIsInputFocused={setIsInputFocused}
|
| 676 |
currentThemeClasses={currentThemeClasses}
|
| 677 |
qrStyles={qrStyles} selectedStyleIndex={selectedStyleIndex}
|
| 678 |
setSelectedStyleIndex={setSelectedStyleIndex} styleCanvasRefs={styleCanvasRefs}
|
| 679 |
drawQrCode={drawQrCode}
|
| 680 |
-
suggestedIdeas={suggestedIdeas} showSuggestedIdeas={showSuggestedIdeas} setShowSuggestedIdeas={setShowSuggestedIdeas}
|
|
|
|
|
|
|
| 681 |
/>
|
| 682 |
)}
|
| 683 |
{activeTab === 'decoder' && (
|
| 684 |
<QRDecoderTab
|
| 685 |
decodedContent={decodedContent} setDecodedContent={setDecodedContent}
|
| 686 |
-
isSummarizing={isSummarizing} summarizeContent={summarizeContent}
|
| 687 |
showAlert={showAlert} decodeQrCode={decodeQrCode}
|
| 688 |
currentThemeClasses={currentThemeClasses}
|
| 689 |
/>
|
|
|
|
| 2 |
import QRCode from 'qrcode';
|
| 3 |
import jsQR from 'jsqr';
|
| 4 |
|
| 5 |
+
// Custom Alert Modal Component
|
| 6 |
const CustomAlert = ({ message, onClose }) => {
|
| 7 |
if (!message) return null;
|
| 8 |
|
|
|
|
| 21 |
);
|
| 22 |
};
|
| 23 |
|
| 24 |
+
// --- QRGeneratorTab Component ---
|
| 25 |
const QRGeneratorTab = ({
|
| 26 |
content, setContent, errorLevel, setErrorLevel, qrColor, setQrColor,
|
| 27 |
isSuggestingIdeas, suggestIdeas, downloadQrCode, qrCanvasRef,
|
| 28 |
isInputFocused, setIsInputFocused, currentThemeClasses,
|
| 29 |
qrStyles, selectedStyleIndex, setSelectedStyleIndex, styleCanvasRefs, drawQrCode,
|
| 30 |
+
suggestedIdeas, showSuggestedIdeas, setShowSuggestedIdeas,
|
| 31 |
+
downloadFormat, setDownloadFormat, qrResolution, setQrResolution
|
| 32 |
}) => {
|
| 33 |
// Effect to generate QR code on the main canvas (Generator Tab)
|
| 34 |
useEffect(() => {
|
| 35 |
+
// Always use black background for the main QR code preview
|
| 36 |
+
drawQrCode(qrCanvasRef.current, content, qrColor, errorLevel, 256, '#000000');
|
| 37 |
}, [content, qrColor, errorLevel, drawQrCode, qrCanvasRef]);
|
| 38 |
|
| 39 |
// Effect to generate QR codes for style previews (Style Selection Panel)
|
| 40 |
useEffect(() => {
|
| 41 |
qrStyles.forEach((style, index) => {
|
| 42 |
const canvas = styleCanvasRefs.current[index];
|
| 43 |
+
// Use black background for style previews as well
|
| 44 |
+
drawQrCode(canvas, "Sample", style.fg, 'H', 80, '#000000');
|
| 45 |
});
|
| 46 |
}, [qrStyles, drawQrCode, styleCanvasRefs]);
|
| 47 |
|
|
|
|
| 70 |
<div className="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 mt-4">
|
| 71 |
<button
|
| 72 |
className={`w-full sm:w-1/2 py-3 px-6 rounded-xl font-bold text-lg bg-gradient-to-r ${currentThemeClasses.primaryAccent} text-white shadow-lg ${currentThemeClasses.buttonGlow} transform hover:-translate-y-1 transition-all duration-300 ease-in-out flex items-center justify-center`}
|
| 73 |
+
onClick={suggestIdeas}
|
| 74 |
+
disabled={isSuggestingIdeas}
|
| 75 |
>
|
| 76 |
{isSuggestingIdeas ? (
|
| 77 |
<i className="fas fa-spinner fa-spin mr-2"></i>
|
| 78 |
) : (
|
| 79 |
+
<i className="fas fa-lightbulb mr-2"></i>
|
| 80 |
)}
|
| 81 |
+
💡 {isSuggestingIdeas ? 'Suggesting Ideas...' : 'Suggest Ideas'}
|
| 82 |
</button>
|
| 83 |
<button
|
| 84 |
className={`w-full sm:w-1/2 py-3 px-6 rounded-xl font-bold text-lg bg-gradient-to-r ${currentThemeClasses.secondaryAccent} text-white shadow-lg ${currentThemeClasses.buttonGlow} transform hover:-translate-y-1 transition-all duration-300 ease-in-out flex items-center justify-center`}
|
|
|
|
| 88 |
</button>
|
| 89 |
</div>
|
| 90 |
|
| 91 |
+
{suggestedIdeas && (
|
| 92 |
<div className={`mt-4 p-4 rounded-xl border ${currentThemeClasses.glassBorder} ${currentThemeClasses.glassBg} shadow-inner transition-all duration-300 overflow-hidden`}>
|
| 93 |
<div className="flex justify-between items-center cursor-pointer" onClick={() => setShowSuggestedIdeas(!showSuggestedIdeas)}>
|
| 94 |
+
<label className={`font-semibold ${currentThemeClasses.labelColor} font-inter`}>Suggested QR Ideas:</label>
|
| 95 |
<i className={`fas ${showSuggestedIdeas ? 'fa-chevron-up' : 'fa-chevron-down'} transition-transform duration-300`}></i>
|
| 96 |
</div>
|
| 97 |
<div className={`overflow-hidden transition-all duration-500 ease-in-out ${showSuggestedIdeas ? 'max-h-96 opacity-100 mt-2' : 'max-h-0 opacity-0'}`}>
|
|
|
|
| 121 |
onChange={(e) => setQrColor(e.target.value)}
|
| 122 |
/>
|
| 123 |
|
| 124 |
+
{/* New: Download Format and Resolution Selection */}
|
| 125 |
+
<div className="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 mt-4">
|
| 126 |
+
<div className="w-full sm:w-1/2">
|
| 127 |
+
<label className={`mb-2 font-semibold ${currentThemeClasses.labelColor} font-inter block`}>Download Format:</label>
|
| 128 |
+
<select
|
| 129 |
+
className={`w-full p-3 rounded-xl border ${currentThemeClasses.inputBorder} ${currentThemeClasses.inputBg} ${currentThemeClasses.text} focus:outline-none focus:ring-2 focus:ring-blue-500 appearance-none transition-all duration-300 font-inter`}
|
| 130 |
+
value={downloadFormat}
|
| 131 |
+
onChange={(e) => setDownloadFormat(e.target.value)}
|
| 132 |
+
>
|
| 133 |
+
<option value="png">PNG (High Resolution)</option>
|
| 134 |
+
<option value="svg">SVG (Vector)</option>
|
| 135 |
+
</select>
|
| 136 |
+
</div>
|
| 137 |
+
{downloadFormat === 'png' && (
|
| 138 |
+
<div className="w-full sm:w-1/2">
|
| 139 |
+
<label className={`mb-2 font-semibold ${currentThemeClasses.labelColor} font-inter block`}>PNG Resolution:</label>
|
| 140 |
+
<select
|
| 141 |
+
className={`w-full p-3 rounded-xl border ${currentThemeClasses.inputBorder} ${currentThemeClasses.inputBg} ${currentThemeClasses.text} focus:outline-none focus:ring-2 focus:ring-blue-500 appearance-none transition-all duration-300 font-inter`}
|
| 142 |
+
value={qrResolution}
|
| 143 |
+
onChange={(e) => setQrResolution(parseInt(e.target.value))}
|
| 144 |
+
>
|
| 145 |
+
<option value={1024}>1024x1024</option>
|
| 146 |
+
<option value={2048}>2048x2048</option>
|
| 147 |
+
<option value={4096}>4096x4096</option>
|
| 148 |
+
</select>
|
| 149 |
+
</div>
|
| 150 |
+
)}
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
|
| 154 |
<label className={`mt-6 mb-2 font-semibold ${currentThemeClasses.labelColor} font-inter`}>QR Preview:</label>
|
| 155 |
<div
|
| 156 |
className={`relative flex justify-center items-center w-full min-h-[300px] rounded-2xl border-2 ${currentThemeClasses.qrPreviewBorder} ${currentThemeClasses.qrPreviewBg} p-4 shadow-inner overflow-hidden transform transition-all duration-300 hover:scale-[1.01] hover:-translate-y-1 ${currentThemeClasses.glow}`}
|
|
|
|
| 190 |
);
|
| 191 |
};
|
| 192 |
|
| 193 |
+
// --- QRDecoderTab Component ---
|
| 194 |
const QRDecoderTab = ({
|
| 195 |
decodedContent, setDecodedContent,
|
| 196 |
isSummarizing, summarizeContent, showAlert, decodeQrCode,
|
|
|
|
| 229 |
|
| 230 |
<button
|
| 231 |
className={`mt-4 w-full py-3 px-6 rounded-xl font-bold text-lg bg-gradient-to-r ${currentThemeClasses.secondaryAccent} text-white shadow-lg ${currentThemeClasses.buttonGlow} transform hover:-translate-y-1 transition-all duration-300 ease-in-out flex items-center justify-center`}
|
| 232 |
+
onClick={summarizeContent}
|
| 233 |
disabled={isSummarizing || !decodedContent.trim() || decodedContent === 'No QR code detected.'}
|
| 234 |
>
|
| 235 |
{isSummarizing ? (
|
| 236 |
<i className="fas fa-spinner fa-spin mr-2"></i>
|
| 237 |
) : (
|
| 238 |
+
<i className="fas fa-file-alt mr-2"></i>
|
| 239 |
)}
|
| 240 |
+
📝 {isSummarizing ? 'Summarizing Content...' : 'Summarize Content'}
|
| 241 |
</button>
|
| 242 |
</div>
|
| 243 |
</div>
|
| 244 |
);
|
| 245 |
};
|
| 246 |
|
| 247 |
+
// --- SettingsTab Component ---
|
| 248 |
const SettingsTab = ({ currentTheme, setCurrentTheme, currentThemeClasses, themes }) => {
|
| 249 |
return (
|
| 250 |
<div className={`flex flex-col p-4 md:p-6 space-y-6 h-full ${currentThemeClasses.bg} ${currentThemeClasses.text}`}>
|
|
|
|
| 279 |
const [activeTab, setActiveTab] = useState('generator');
|
| 280 |
const [content, setContent] = useState('');
|
| 281 |
const [errorLevel, setErrorLevel] = useState('L');
|
| 282 |
+
const [qrColor, setQrColor] = useState('#00FFCC'); // Default to a vibrant neon cyan
|
| 283 |
const [decodedContent, setDecodedContent] = useState('');
|
| 284 |
const [selectedStyleIndex, setSelectedStyleIndex] = useState(0);
|
| 285 |
+
const [currentTheme, setCurrentTheme] = useState('dark'); // Initial state, will be updated by useEffect
|
| 286 |
const [isSummarizing, setIsSummarizing] = useState(false);
|
| 287 |
+
const [suggestedIdeas, setSuggestedIdeas] = useState('');
|
| 288 |
+
const [isSuggestingIdeas, setIsSuggestingIdeas] = useState(false);
|
| 289 |
+
const [showSuggestedIdeas, setShowSuggestedIdeas] = useState(false);
|
| 290 |
const [alertMessage, setAlertMessage] = useState(null);
|
| 291 |
const [isInputFocused, setIsInputFocused] = useState(false);
|
| 292 |
+
const [downloadFormat, setDownloadFormat] = useState('png'); // Default download format
|
| 293 |
+
const [qrResolution, setQrResolution] = useState(2048); // Default high resolution for PNG
|
| 294 |
|
| 295 |
const qrCanvasRef = useRef(null);
|
| 296 |
const styleCanvasRefs = useRef(Array(12).fill(null));
|
| 297 |
|
| 298 |
+
// Detect user's preferred color scheme and set initial theme
|
| 299 |
+
useEffect(() => {
|
| 300 |
+
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
| 301 |
+
setCurrentTheme(prefersDarkMode ? 'dark' : 'light');
|
| 302 |
+
|
| 303 |
+
// Listen for changes in the user's preferred color scheme
|
| 304 |
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
| 305 |
+
const handleChange = (e) => {
|
| 306 |
+
setCurrentTheme(e.matches ? 'dark' : 'light');
|
| 307 |
+
};
|
| 308 |
+
mediaQuery.addEventListener('change', handleChange);
|
| 309 |
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
| 310 |
+
}, []);
|
| 311 |
+
|
| 312 |
+
// Define QR code styles (updated with more neon colors)
|
| 313 |
const qrStyles = [
|
| 314 |
+
{ name: "Neon Cyan", fg: "#00FFCC" },
|
| 315 |
+
{ name: "Neon Green", fg: "#00FF00" },
|
| 316 |
+
{ name: "Neon Magenta", fg: "#FF00FF" },
|
| 317 |
+
{ name: "Electric Blue", fg: "#00BFFF" },
|
| 318 |
+
{ name: "Laser Red", fg: "#FF3333" },
|
| 319 |
+
{ name: "Vivid Yellow", fg: "#FFFF00" },
|
| 320 |
+
{ name: "Deep Purple", fg: "#8A2BE2" },
|
| 321 |
+
{ name: "Aqua", fg: "#00FFFF" },
|
| 322 |
+
{ name: "Lime", fg: "#32CD32" },
|
| 323 |
+
{ name: "Fuchsia", fg: "#FF00FF" },
|
| 324 |
+
{ name: "Orange Glow", fg: "#FFA500" },
|
| 325 |
+
{ name: "White Glow", fg: "#FFFFFF" }, // A white glow on black background
|
| 326 |
];
|
| 327 |
|
| 328 |
+
// Define themes
|
| 329 |
const themes = {
|
| 330 |
dark: {
|
| 331 |
bg: 'bg-gradient-to-br from-[#0A0A0A] via-[#1A0A2A] to-[#0A0A0A] animate-gradient-shift',
|
|
|
|
| 485 |
|
| 486 |
const currentThemeClasses = themes[currentTheme];
|
| 487 |
|
| 488 |
+
// Function to show custom alert
|
| 489 |
const showAlert = (message) => {
|
| 490 |
setAlertMessage(message);
|
| 491 |
};
|
| 492 |
|
| 493 |
+
// Callback to generate QR code on a given canvas
|
| 494 |
+
// Added 'bg' parameter for background color, defaulting to white for general use, but overridden to black for QR
|
| 495 |
+
const drawQrCode = useCallback((canvas, text, color, errorLevel, size = 256, bg = '#FFFFFF') => {
|
| 496 |
if (!canvas) {
|
| 497 |
console.warn("Canvas element is null, cannot draw QR code.");
|
| 498 |
return;
|
|
|
|
| 515 |
errorCorrectionLevel: errorLevel,
|
| 516 |
width: size,
|
| 517 |
color: {
|
| 518 |
+
dark: color, // QR code dots color
|
| 519 |
+
light: bg // QR code background color
|
| 520 |
}
|
| 521 |
}, function (error) {
|
| 522 |
if (error) console.error("QR Code drawing error:", error);
|
|
|
|
| 524 |
}
|
| 525 |
}, []);
|
| 526 |
|
| 527 |
+
// Function to download the generated QR code (now supports PNG and SVG)
|
| 528 |
+
const downloadQrCode = async () => {
|
| 529 |
if (content.trim() === '') {
|
| 530 |
showAlert('Please enter content to generate QR code.');
|
| 531 |
return;
|
| 532 |
}
|
| 533 |
+
|
| 534 |
+
if (downloadFormat === 'png') {
|
| 535 |
+
const canvas = document.createElement('canvas'); // Create an off-screen canvas for high-res
|
| 536 |
+
// Use a high resolution for the downloaded PNG
|
| 537 |
+
await new Promise(resolve => {
|
| 538 |
+
QRCode.toCanvas(canvas, content, {
|
| 539 |
+
errorCorrectionLevel: errorLevel,
|
| 540 |
+
width: qrResolution, // Use selected resolution
|
| 541 |
+
color: {
|
| 542 |
+
dark: qrColor,
|
| 543 |
+
light: '#000000' // Black background for downloaded PNG
|
| 544 |
+
}
|
| 545 |
+
}, function (error) {
|
| 546 |
+
if (error) {
|
| 547 |
+
console.error("QR Code PNG drawing error:", error);
|
| 548 |
+
showAlert("Failed to generate high-resolution PNG. Try again.");
|
| 549 |
+
}
|
| 550 |
+
resolve();
|
| 551 |
+
});
|
| 552 |
+
});
|
| 553 |
+
|
| 554 |
const pngUrl = canvas.toDataURL("image/png");
|
| 555 |
const downloadLink = document.createElement('a');
|
| 556 |
downloadLink.href = pngUrl;
|
| 557 |
+
downloadLink.download = `qrcode_${qrResolution}px.png`;
|
| 558 |
document.body.appendChild(downloadLink);
|
| 559 |
downloadLink.click();
|
| 560 |
document.body.removeChild(downloadLink);
|
| 561 |
+
showAlert(`QR Code downloaded as PNG (${qrResolution}x${qrResolution}px)!`);
|
| 562 |
+
|
| 563 |
+
} else if (downloadFormat === 'svg') {
|
| 564 |
+
try {
|
| 565 |
+
const svgString = await QRCode.toString(content, {
|
| 566 |
+
errorCorrectionLevel: errorLevel,
|
| 567 |
+
type: 'svg',
|
| 568 |
+
// Width here influences the SVG viewBox, but SVG itself scales losslessly
|
| 569 |
+
// We can set it to a reasonable base size, but it's not pixel resolution
|
| 570 |
+
width: qrResolution, // Reusing qrResolution for SVG viewBox consistency
|
| 571 |
+
color: {
|
| 572 |
+
dark: qrColor,
|
| 573 |
+
light: '#000000' // Black background for SVG
|
| 574 |
+
}
|
| 575 |
+
});
|
| 576 |
+
|
| 577 |
+
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
| 578 |
+
const url = URL.createObjectURL(blob);
|
| 579 |
+
const downloadLink = document.createElement('a');
|
| 580 |
+
downloadLink.href = url;
|
| 581 |
+
downloadLink.download = `qrcode.svg`;
|
| 582 |
+
document.body.appendChild(downloadLink);
|
| 583 |
+
downloadLink.click();
|
| 584 |
+
document.body.removeChild(downloadLink);
|
| 585 |
+
URL.revokeObjectURL(url); // Clean up the URL object
|
| 586 |
+
showAlert('QR Code downloaded as SVG!');
|
| 587 |
+
|
| 588 |
+
} catch (error) {
|
| 589 |
+
console.error("QR Code SVG generation error:", error);
|
| 590 |
+
showAlert("Failed to generate SVG. Please try again.");
|
| 591 |
+
}
|
| 592 |
}
|
| 593 |
};
|
| 594 |
|
| 595 |
+
// Function to decode QR code from an uploaded image
|
| 596 |
const decodeQrCode = (event) => {
|
| 597 |
const file = event.target.files[0];
|
| 598 |
if (!file) return;
|
|
|
|
| 612 |
if (jsQR) {
|
| 613 |
let code = jsQR(imageData.data, imageData.width, imageData.height);
|
| 614 |
|
| 615 |
+
// Attempt inversion if initial scan fails
|
| 616 |
if (!code) {
|
| 617 |
for (let i = 0; i < imageData.data.length; i += 4) {
|
| 618 |
imageData.data[i] = 255 - imageData.data[i];
|
|
|
|
| 642 |
reader.readAsDataURL(file);
|
| 643 |
};
|
| 644 |
|
| 645 |
+
// Function to summarize content using Gemini API
|
| 646 |
const summarizeContent = async () => {
|
| 647 |
if (!decodedContent.trim() || decodedContent === 'No QR code detected.') {
|
| 648 |
showAlert('Please decode a QR code first to summarize its content.');
|
|
|
|
| 674 |
setContent(text); // Update the generator's input field with summarized content
|
| 675 |
showAlert('Content summarized and moved to QR Generator tab!');
|
| 676 |
} else {
|
| 677 |
+
console.error("Gemini API response structure unexpected for summarizeContent:", result);
|
| 678 |
showAlert("Failed to summarize content. Please try again.");
|
| 679 |
}
|
| 680 |
} catch (error) {
|
|
|
|
| 685 |
}
|
| 686 |
};
|
| 687 |
|
| 688 |
+
// Function to suggest ideas for QR code generation
|
| 689 |
const suggestIdeas = async () => {
|
| 690 |
setIsSuggestingIdeas(true);
|
| 691 |
setSuggestedIdeas('Thinking of ideas...');
|
|
|
|
| 723 |
const text = result.candidates[0].content.parts[0].text;
|
| 724 |
setSuggestedIdeas(text);
|
| 725 |
} else {
|
| 726 |
+
console.error("Gemini API response structure unexpected for suggestIdeas:", result);
|
| 727 |
setSuggestedIdeas("Could not suggest ideas. Please try again.");
|
| 728 |
}
|
| 729 |
} catch (error) {
|
|
|
|
| 773 |
content={content} setContent={setContent}
|
| 774 |
errorLevel={errorLevel} setErrorLevel={setErrorLevel}
|
| 775 |
qrColor={qrColor} setQrColor={setQrColor}
|
| 776 |
+
isSuggestingIdeas={isSuggestingIdeas} suggestIdeas={suggestIdeas}
|
| 777 |
downloadQrCode={downloadQrCode} qrCanvasRef={qrCanvasRef}
|
| 778 |
isInputFocused={isInputFocused} setIsInputFocused={setIsInputFocused}
|
| 779 |
currentThemeClasses={currentThemeClasses}
|
| 780 |
qrStyles={qrStyles} selectedStyleIndex={selectedStyleIndex}
|
| 781 |
setSelectedStyleIndex={setSelectedStyleIndex} styleCanvasRefs={styleCanvasRefs}
|
| 782 |
drawQrCode={drawQrCode}
|
| 783 |
+
suggestedIdeas={suggestedIdeas} showSuggestedIdeas={showSuggestedIdeas} setShowSuggestedIdeas={setShowSuggestedIdeas}
|
| 784 |
+
downloadFormat={downloadFormat} setDownloadFormat={setDownloadFormat}
|
| 785 |
+
qrResolution={qrResolution} setQrResolution={setQrResolution}
|
| 786 |
/>
|
| 787 |
)}
|
| 788 |
{activeTab === 'decoder' && (
|
| 789 |
<QRDecoderTab
|
| 790 |
decodedContent={decodedContent} setDecodedContent={setDecodedContent}
|
| 791 |
+
isSummarizing={isSummarizing} summarizeContent={summarizeContent}
|
| 792 |
showAlert={showAlert} decodeQrCode={decodeQrCode}
|
| 793 |
currentThemeClasses={currentThemeClasses}
|
| 794 |
/>
|