feat: add QR code generator tool
Add a sixth tool with live SVG preview, customizable foreground/background colors, error correction level, margin control, and export as PNG (256–2048px) or SVG. URL params enable shareable state. All processing runs client-side via the qrcode package. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
19
app/(app)/qrcode/page.tsx
Normal file
19
app/(app)/qrcode/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { QRCodeGenerator } from '@/components/qrcode/QRCodeGenerator';
|
||||||
|
import { AppPage } from '@/components/layout/AppPage';
|
||||||
|
import { getToolByHref } from '@/lib/tools';
|
||||||
|
|
||||||
|
const tool = getToolByHref('/qrcode')!;
|
||||||
|
|
||||||
|
export const metadata: Metadata = { title: tool.title };
|
||||||
|
|
||||||
|
export default function QRCodePage() {
|
||||||
|
return (
|
||||||
|
<AppPage
|
||||||
|
title={tool.title}
|
||||||
|
description={tool.description}
|
||||||
|
>
|
||||||
|
<QRCodeGenerator />
|
||||||
|
</AppPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -36,3 +36,15 @@ export const FaviconIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const QRCodeIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg {...props} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<rect x="3" y="3" width="7" height="7" rx="1" strokeWidth={2} />
|
||||||
|
<rect x="14" y="3" width="7" height="7" rx="1" strokeWidth={2} />
|
||||||
|
<rect x="3" y="14" width="7" height="7" rx="1" strokeWidth={2} />
|
||||||
|
<rect x="14" y="14" width="3" height="3" strokeWidth={2} />
|
||||||
|
<rect x="18" y="18" width="3" height="3" strokeWidth={2} />
|
||||||
|
<line x1="14" y1="18" x2="17" y2="18" strokeWidth={2} />
|
||||||
|
<line x1="18" y1="14" x2="18" y2="17" strokeWidth={2} />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { addRecentFont } from '@/lib/storage/favorites';
|
|||||||
import { decodeFromUrl, updateUrl, getShareableUrl } from '@/lib/utils/urlSharing';
|
import { decodeFromUrl, updateUrl, getShareableUrl } from '@/lib/utils/urlSharing';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { ASCIIFont } from '@/types/ascii';
|
import type { ASCIIFont } from '@/types/ascii';
|
||||||
import { Card, CardContent } from '../ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||||
|
|
||||||
export function ASCIIConverter() {
|
export function ASCIIConverter() {
|
||||||
const [text, setText] = React.useState('ASCII');
|
const [text, setText] = React.useState('ASCII');
|
||||||
@@ -121,7 +121,11 @@ export function ASCIIConverter() {
|
|||||||
{/* Left Column - Input and Preview */}
|
{/* Left Column - Input and Preview */}
|
||||||
<div className="lg:col-span-2 space-y-6 overflow-y-auto custom-scrollbar">
|
<div className="lg:col-span-2 space-y-6 overflow-y-auto custom-scrollbar">
|
||||||
<Card>
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Text</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={text}
|
value={text}
|
||||||
onChange={setText}
|
onChange={setText}
|
||||||
|
|||||||
145
components/qrcode/QRCodeGenerator.tsx
Normal file
145
components/qrcode/QRCodeGenerator.tsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { QRInput } from './QRInput';
|
||||||
|
import { QRPreview } from './QRPreview';
|
||||||
|
import { QROptions } from './QROptions';
|
||||||
|
import { generateSvg, generateDataUrl } from '@/lib/qrcode/qrcodeService';
|
||||||
|
import { decodeQRFromUrl, updateQRUrl, getQRShareableUrl } from '@/lib/qrcode/urlSharing';
|
||||||
|
import { downloadBlob } from '@/lib/media/utils/fileUtils';
|
||||||
|
import { debounce } from '@/lib/utils/debounce';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import type { ErrorCorrectionLevel, ExportSize } from '@/types/qrcode';
|
||||||
|
|
||||||
|
export function QRCodeGenerator() {
|
||||||
|
const [text, setText] = React.useState('https://kit.pivoine.art');
|
||||||
|
const [errorCorrection, setErrorCorrection] = React.useState<ErrorCorrectionLevel>('M');
|
||||||
|
const [foregroundColor, setForegroundColor] = React.useState('#000000');
|
||||||
|
const [backgroundColor, setBackgroundColor] = React.useState('#ffffff');
|
||||||
|
const [margin, setMargin] = React.useState(4);
|
||||||
|
const [exportSize, setExportSize] = React.useState<ExportSize>(512);
|
||||||
|
const [svgString, setSvgString] = React.useState('');
|
||||||
|
const [isGenerating, setIsGenerating] = React.useState(false);
|
||||||
|
|
||||||
|
// Load state from URL on mount
|
||||||
|
React.useEffect(() => {
|
||||||
|
const urlState = decodeQRFromUrl();
|
||||||
|
if (urlState) {
|
||||||
|
if (urlState.text !== undefined) setText(urlState.text);
|
||||||
|
if (urlState.errorCorrection) setErrorCorrection(urlState.errorCorrection);
|
||||||
|
if (urlState.foregroundColor) setForegroundColor(urlState.foregroundColor);
|
||||||
|
if (urlState.backgroundColor) setBackgroundColor(urlState.backgroundColor);
|
||||||
|
if (urlState.margin !== undefined) setMargin(urlState.margin);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Debounced generation
|
||||||
|
const generate = React.useMemo(
|
||||||
|
() =>
|
||||||
|
debounce(async (t: string, ec: ErrorCorrectionLevel, fg: string, bg: string, m: number) => {
|
||||||
|
if (!t) {
|
||||||
|
setSvgString('');
|
||||||
|
setIsGenerating(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsGenerating(true);
|
||||||
|
try {
|
||||||
|
const svg = await generateSvg(t, ec, fg, bg, m);
|
||||||
|
setSvgString(svg);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('QR generation error:', error);
|
||||||
|
setSvgString('');
|
||||||
|
toast.error('Failed to generate QR code. Text may be too long.');
|
||||||
|
} finally {
|
||||||
|
setIsGenerating(false);
|
||||||
|
}
|
||||||
|
}, 200),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Regenerate on changes
|
||||||
|
React.useEffect(() => {
|
||||||
|
generate(text, errorCorrection, foregroundColor, backgroundColor, margin);
|
||||||
|
updateQRUrl(text, errorCorrection, foregroundColor, backgroundColor, margin);
|
||||||
|
}, [text, errorCorrection, foregroundColor, backgroundColor, margin, generate]);
|
||||||
|
|
||||||
|
// Export: PNG download
|
||||||
|
const handleDownloadPng = async () => {
|
||||||
|
if (!text) return;
|
||||||
|
try {
|
||||||
|
const dataUrl = await generateDataUrl(text, errorCorrection, foregroundColor, backgroundColor, margin, exportSize);
|
||||||
|
const res = await fetch(dataUrl);
|
||||||
|
const blob = await res.blob();
|
||||||
|
downloadBlob(blob, `qrcode-${Date.now()}.png`);
|
||||||
|
} catch {
|
||||||
|
toast.error('Failed to export PNG');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export: SVG download
|
||||||
|
const handleDownloadSvg = () => {
|
||||||
|
if (!svgString) return;
|
||||||
|
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||||
|
downloadBlob(blob, `qrcode-${Date.now()}.svg`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy image to clipboard
|
||||||
|
const handleCopyImage = async () => {
|
||||||
|
if (!text) return;
|
||||||
|
try {
|
||||||
|
const dataUrl = await generateDataUrl(text, errorCorrection, foregroundColor, backgroundColor, margin, exportSize);
|
||||||
|
const res = await fetch(dataUrl);
|
||||||
|
const blob = await res.blob();
|
||||||
|
await navigator.clipboard.write([
|
||||||
|
new ClipboardItem({ 'image/png': blob }),
|
||||||
|
]);
|
||||||
|
toast.success('Image copied to clipboard!');
|
||||||
|
} catch {
|
||||||
|
toast.error('Failed to copy image');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Share URL
|
||||||
|
const handleShare = async () => {
|
||||||
|
const shareUrl = getQRShareableUrl(text, errorCorrection, foregroundColor, backgroundColor, margin);
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(shareUrl);
|
||||||
|
toast.success('Shareable URL copied!');
|
||||||
|
} catch {
|
||||||
|
toast.error('Failed to copy URL');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch lg:max-h-[800px]">
|
||||||
|
{/* Left Column - Input and Options */}
|
||||||
|
<div className="lg:col-span-1 space-y-6 overflow-y-auto custom-scrollbar">
|
||||||
|
<QRInput value={text} onChange={setText} />
|
||||||
|
<QROptions
|
||||||
|
errorCorrection={errorCorrection}
|
||||||
|
foregroundColor={foregroundColor}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
margin={margin}
|
||||||
|
onErrorCorrectionChange={setErrorCorrection}
|
||||||
|
onForegroundColorChange={setForegroundColor}
|
||||||
|
onBackgroundColorChange={setBackgroundColor}
|
||||||
|
onMarginChange={setMargin}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Preview */}
|
||||||
|
<div className="lg:col-span-2 h-full">
|
||||||
|
<QRPreview
|
||||||
|
svgString={svgString}
|
||||||
|
isGenerating={isGenerating}
|
||||||
|
exportSize={exportSize}
|
||||||
|
onExportSizeChange={setExportSize}
|
||||||
|
onCopyImage={handleCopyImage}
|
||||||
|
onShare={handleShare}
|
||||||
|
onDownloadPng={handleDownloadPng}
|
||||||
|
onDownloadSvg={handleDownloadSvg}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
components/qrcode/QRInput.tsx
Normal file
34
components/qrcode/QRInput.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
|
||||||
|
interface QRInputProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_LENGTH = 2048;
|
||||||
|
|
||||||
|
export function QRInput({ value, onChange }: QRInputProps) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Text</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<Textarea
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
placeholder="Enter text or URL..."
|
||||||
|
maxLength={MAX_LENGTH}
|
||||||
|
rows={3}
|
||||||
|
className="resize-none font-mono text-sm"
|
||||||
|
/>
|
||||||
|
<div className="text-[10px] text-muted-foreground text-right">
|
||||||
|
{value.length} / {MAX_LENGTH}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
137
components/qrcode/QROptions.tsx
Normal file
137
components/qrcode/QROptions.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Slider } from '@/components/ui/slider';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import type { ErrorCorrectionLevel } from '@/types/qrcode';
|
||||||
|
|
||||||
|
interface QROptionsProps {
|
||||||
|
errorCorrection: ErrorCorrectionLevel;
|
||||||
|
foregroundColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
margin: number;
|
||||||
|
onErrorCorrectionChange: (ec: ErrorCorrectionLevel) => void;
|
||||||
|
onForegroundColorChange: (color: string) => void;
|
||||||
|
onBackgroundColorChange: (color: string) => void;
|
||||||
|
onMarginChange: (margin: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EC_OPTIONS: { value: ErrorCorrectionLevel; label: string }[] = [
|
||||||
|
{ value: 'L', label: 'Low (7%)' },
|
||||||
|
{ value: 'M', label: 'Medium (15%)' },
|
||||||
|
{ value: 'Q', label: 'Quartile (25%)' },
|
||||||
|
{ value: 'H', label: 'High (30%)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function QROptions({
|
||||||
|
errorCorrection,
|
||||||
|
foregroundColor,
|
||||||
|
backgroundColor,
|
||||||
|
margin,
|
||||||
|
onErrorCorrectionChange,
|
||||||
|
onForegroundColorChange,
|
||||||
|
onBackgroundColorChange,
|
||||||
|
onMarginChange,
|
||||||
|
}: QROptionsProps) {
|
||||||
|
const isTransparent = backgroundColor === '#00000000';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Options</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{/* Error Correction */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label className="text-xs">Error Correction</Label>
|
||||||
|
<Select value={errorCorrection} onValueChange={(v) => onErrorCorrectionChange(v as ErrorCorrectionLevel)}>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{EC_OPTIONS.map((opt) => (
|
||||||
|
<SelectItem key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Colors */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label className="text-xs">Foreground</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
type="color"
|
||||||
|
className="w-9 p-1 h-9 shrink-0"
|
||||||
|
value={foregroundColor}
|
||||||
|
onChange={(e) => onForegroundColorChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
className="font-mono text-xs"
|
||||||
|
value={foregroundColor}
|
||||||
|
onChange={(e) => onForegroundColorChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="text-xs">Background</Label>
|
||||||
|
<Button
|
||||||
|
variant={isTransparent ? 'default' : 'outline'}
|
||||||
|
size="xs"
|
||||||
|
className="h-5 text-[10px] px-1.5"
|
||||||
|
onClick={() =>
|
||||||
|
onBackgroundColorChange(isTransparent ? '#ffffff' : '#00000000')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Transparent
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
type="color"
|
||||||
|
className="w-9 p-1 h-9 shrink-0"
|
||||||
|
disabled={isTransparent}
|
||||||
|
value={backgroundColor}
|
||||||
|
onChange={(e) => onBackgroundColorChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
className="font-mono text-xs"
|
||||||
|
disabled={isTransparent}
|
||||||
|
value={backgroundColor}
|
||||||
|
onChange={(e) => onBackgroundColorChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Margin */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="text-xs">Margin</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">{margin}</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
value={[margin]}
|
||||||
|
onValueChange={([v]) => onMarginChange(v)}
|
||||||
|
min={0}
|
||||||
|
max={8}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
132
components/qrcode/QRPreview.tsx
Normal file
132
components/qrcode/QRPreview.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip';
|
||||||
|
import {
|
||||||
|
Empty,
|
||||||
|
EmptyDescription,
|
||||||
|
EmptyHeader,
|
||||||
|
EmptyMedia,
|
||||||
|
EmptyTitle,
|
||||||
|
} from '@/components/ui/empty';
|
||||||
|
import { Copy, Share2, Image as ImageIcon, FileCode, QrCode } from 'lucide-react';
|
||||||
|
import type { ExportSize } from '@/types/qrcode';
|
||||||
|
|
||||||
|
interface QRPreviewProps {
|
||||||
|
svgString: string;
|
||||||
|
isGenerating: boolean;
|
||||||
|
exportSize: ExportSize;
|
||||||
|
onExportSizeChange: (size: ExportSize) => void;
|
||||||
|
onCopyImage: () => void;
|
||||||
|
onShare: () => void;
|
||||||
|
onDownloadPng: () => void;
|
||||||
|
onDownloadSvg: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QRPreview({
|
||||||
|
svgString,
|
||||||
|
isGenerating,
|
||||||
|
exportSize,
|
||||||
|
onExportSizeChange,
|
||||||
|
onCopyImage,
|
||||||
|
onShare,
|
||||||
|
onDownloadPng,
|
||||||
|
onDownloadSvg,
|
||||||
|
}: QRPreviewProps) {
|
||||||
|
return (
|
||||||
|
<Card className="h-full flex flex-col">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
||||||
|
<CardTitle>Preview</CardTitle>
|
||||||
|
<div className="flex items-center gap-1.5 flex-wrap">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" size="xs" onClick={onCopyImage} disabled={!svgString}>
|
||||||
|
<Copy className="h-3 w-3 mr-1" />
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Copy image to clipboard</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" size="xs" onClick={onShare} disabled={!svgString}>
|
||||||
|
<Share2 className="h-3 w-3 mr-1" />
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Copy shareable URL</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" size="xs" onClick={onDownloadPng} disabled={!svgString}>
|
||||||
|
<ImageIcon className="h-3 w-3 mr-1" />
|
||||||
|
PNG
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Download as PNG</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
value={String(exportSize)}
|
||||||
|
onValueChange={(v) => v && onExportSizeChange(Number(v) as ExportSize)}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem value="256" className="h-6 px-1.5 min-w-0 text-[10px]">256</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="512" className="h-6 px-1.5 min-w-0 text-[10px]">512</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="1024" className="h-6 px-1.5 min-w-0 text-[10px]">1k</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="2048" className="h-6 px-1.5 min-w-0 text-[10px]">2k</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" size="xs" onClick={onDownloadSvg} disabled={!svgString}>
|
||||||
|
<FileCode className="h-3 w-3 mr-1" />
|
||||||
|
SVG
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Download as SVG</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-1 flex flex-col">
|
||||||
|
<div className="flex-1 min-h-[200px] rounded-lg p-4 flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'repeating-conic-gradient(hsl(var(--muted)) 0% 25%, transparent 0% 50%)',
|
||||||
|
backgroundSize: '16px 16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isGenerating ? (
|
||||||
|
<Skeleton className="h-[200px] w-[200px]" />
|
||||||
|
) : svgString ? (
|
||||||
|
<div
|
||||||
|
className="w-full max-w-[400px] aspect-square [&>svg]:w-full [&>svg]:h-full"
|
||||||
|
dangerouslySetInnerHTML={{ __html: svgString }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Empty>
|
||||||
|
<EmptyHeader>
|
||||||
|
<EmptyMedia variant="icon">
|
||||||
|
<QrCode />
|
||||||
|
</EmptyMedia>
|
||||||
|
<EmptyTitle>Enter text to generate a QR code</EmptyTitle>
|
||||||
|
<EmptyDescription>Type text or a URL in the input field above</EmptyDescription>
|
||||||
|
</EmptyHeader>
|
||||||
|
</Empty>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
39
lib/qrcode/qrcodeService.ts
Normal file
39
lib/qrcode/qrcodeService.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import QRCode from 'qrcode';
|
||||||
|
import type { ErrorCorrectionLevel } from '@/types/qrcode';
|
||||||
|
|
||||||
|
export async function generateSvg(
|
||||||
|
text: string,
|
||||||
|
errorCorrection: ErrorCorrectionLevel,
|
||||||
|
foregroundColor: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
margin: number,
|
||||||
|
): Promise<string> {
|
||||||
|
return QRCode.toString(text, {
|
||||||
|
type: 'svg',
|
||||||
|
errorCorrectionLevel: errorCorrection,
|
||||||
|
color: {
|
||||||
|
dark: foregroundColor,
|
||||||
|
light: backgroundColor,
|
||||||
|
},
|
||||||
|
margin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateDataUrl(
|
||||||
|
text: string,
|
||||||
|
errorCorrection: ErrorCorrectionLevel,
|
||||||
|
foregroundColor: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
margin: number,
|
||||||
|
size: number,
|
||||||
|
): Promise<string> {
|
||||||
|
return QRCode.toDataURL(text, {
|
||||||
|
errorCorrectionLevel: errorCorrection,
|
||||||
|
color: {
|
||||||
|
dark: foregroundColor,
|
||||||
|
light: backgroundColor,
|
||||||
|
},
|
||||||
|
margin,
|
||||||
|
width: size,
|
||||||
|
});
|
||||||
|
}
|
||||||
85
lib/qrcode/urlSharing.ts
Normal file
85
lib/qrcode/urlSharing.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import type { ErrorCorrectionLevel } from '@/types/qrcode';
|
||||||
|
|
||||||
|
export interface QRShareableState {
|
||||||
|
text?: string;
|
||||||
|
errorCorrection?: ErrorCorrectionLevel;
|
||||||
|
foregroundColor?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
margin?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULTS = {
|
||||||
|
errorCorrection: 'M' as ErrorCorrectionLevel,
|
||||||
|
foregroundColor: '#000000',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
margin: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function decodeQRFromUrl(): QRShareableState | null {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const text = params.get('text');
|
||||||
|
const ec = params.get('ec') as ErrorCorrectionLevel | null;
|
||||||
|
const fg = params.get('fg');
|
||||||
|
const bg = params.get('bg');
|
||||||
|
const margin = params.get('margin');
|
||||||
|
|
||||||
|
if (!text && !ec && !fg && !bg && !margin) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: text || undefined,
|
||||||
|
errorCorrection: ec || undefined,
|
||||||
|
foregroundColor: fg ? `#${fg}` : undefined,
|
||||||
|
backgroundColor: bg ? `#${bg}` : undefined,
|
||||||
|
margin: margin ? parseInt(margin, 10) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateQRUrl(
|
||||||
|
text: string,
|
||||||
|
errorCorrection: ErrorCorrectionLevel,
|
||||||
|
foregroundColor: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
margin: number,
|
||||||
|
): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (text) params.set('text', text);
|
||||||
|
if (errorCorrection !== DEFAULTS.errorCorrection) params.set('ec', errorCorrection);
|
||||||
|
if (foregroundColor !== DEFAULTS.foregroundColor) params.set('fg', foregroundColor.replace('#', ''));
|
||||||
|
if (backgroundColor !== DEFAULTS.backgroundColor) params.set('bg', backgroundColor.replace('#', ''));
|
||||||
|
if (margin !== DEFAULTS.margin) params.set('margin', String(margin));
|
||||||
|
|
||||||
|
const query = params.toString();
|
||||||
|
const newUrl = query
|
||||||
|
? `${window.location.pathname}?${query}`
|
||||||
|
: window.location.pathname;
|
||||||
|
|
||||||
|
window.history.replaceState({}, '', newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQRShareableUrl(
|
||||||
|
text: string,
|
||||||
|
errorCorrection: ErrorCorrectionLevel,
|
||||||
|
foregroundColor: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
margin: number,
|
||||||
|
): string {
|
||||||
|
if (typeof window === 'undefined') return '';
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (text) params.set('text', text);
|
||||||
|
if (errorCorrection !== DEFAULTS.errorCorrection) params.set('ec', errorCorrection);
|
||||||
|
if (foregroundColor !== DEFAULTS.foregroundColor) params.set('fg', foregroundColor.replace('#', ''));
|
||||||
|
if (backgroundColor !== DEFAULTS.backgroundColor) params.set('bg', backgroundColor.replace('#', ''));
|
||||||
|
if (margin !== DEFAULTS.margin) params.set('margin', String(margin));
|
||||||
|
|
||||||
|
const query = params.toString();
|
||||||
|
return `${window.location.origin}${window.location.pathname}${query ? `?${query}` : ''}`;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ColorIcon, UnitsIcon, ASCIIIcon, MediaIcon, FaviconIcon } from '@/components/AppIcons';
|
import { ColorIcon, UnitsIcon, ASCIIIcon, MediaIcon, FaviconIcon, QRCodeIcon } from '@/components/AppIcons';
|
||||||
|
|
||||||
export interface Tool {
|
export interface Tool {
|
||||||
/** Short display name (e.g. "Color") */
|
/** Short display name (e.g. "Color") */
|
||||||
@@ -15,10 +15,6 @@ export interface Tool {
|
|||||||
summary: string;
|
summary: string;
|
||||||
/** Icon component */
|
/** Icon component */
|
||||||
icon: React.ElementType;
|
icon: React.ElementType;
|
||||||
/** Tailwind gradient utility class for the landing card */
|
|
||||||
gradient: string;
|
|
||||||
/** Hex accent color for the landing card */
|
|
||||||
accentColor: string;
|
|
||||||
/** Badge labels for the landing card */
|
/** Badge labels for the landing card */
|
||||||
badges: string[];
|
badges: string[];
|
||||||
}
|
}
|
||||||
@@ -33,8 +29,6 @@ export const tools: Tool[] = [
|
|||||||
summary:
|
summary:
|
||||||
'Modern color manipulation toolkit with palette generation, accessibility testing, and format conversion. Supports hex, RGB, HSL, Lab, and more.',
|
'Modern color manipulation toolkit with palette generation, accessibility testing, and format conversion. Supports hex, RGB, HSL, Lab, and more.',
|
||||||
icon: ColorIcon,
|
icon: ColorIcon,
|
||||||
gradient: 'gradient-indigo-purple',
|
|
||||||
accentColor: '#a855f7',
|
|
||||||
badges: ['Open Source', 'WCAG', 'Free'],
|
badges: ['Open Source', 'WCAG', 'Free'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,8 +40,6 @@ export const tools: Tool[] = [
|
|||||||
summary:
|
summary:
|
||||||
'Smart unit converter with 187 units across 23 categories. Real-time bidirectional conversion with fuzzy search.',
|
'Smart unit converter with 187 units across 23 categories. Real-time bidirectional conversion with fuzzy search.',
|
||||||
icon: UnitsIcon,
|
icon: UnitsIcon,
|
||||||
gradient: 'gradient-cyan-purple',
|
|
||||||
accentColor: '#2dd4bf',
|
|
||||||
badges: ['Open Source', 'Real-time', 'Free'],
|
badges: ['Open Source', 'Real-time', 'Free'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -59,8 +51,6 @@ export const tools: Tool[] = [
|
|||||||
summary:
|
summary:
|
||||||
'ASCII art text generator with 373 fonts. Create stunning text banners, terminal art, and retro designs with live preview and multiple export formats.',
|
'ASCII art text generator with 373 fonts. Create stunning text banners, terminal art, and retro designs with live preview and multiple export formats.',
|
||||||
icon: ASCIIIcon,
|
icon: ASCIIIcon,
|
||||||
gradient: 'gradient-yellow-amber',
|
|
||||||
accentColor: '#eab308',
|
|
||||||
badges: ['Open Source', 'ASCII Art', 'Free'],
|
badges: ['Open Source', 'ASCII Art', 'Free'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -72,8 +62,6 @@ export const tools: Tool[] = [
|
|||||||
summary:
|
summary:
|
||||||
'Modern browser-based file converter powered by WebAssembly. Convert videos, images, and audio locally without server uploads. Privacy-first with no file size limits.',
|
'Modern browser-based file converter powered by WebAssembly. Convert videos, images, and audio locally without server uploads. Privacy-first with no file size limits.',
|
||||||
icon: MediaIcon,
|
icon: MediaIcon,
|
||||||
gradient: 'gradient-green-teal',
|
|
||||||
accentColor: '#10b981',
|
|
||||||
badges: ['Open Source', 'Converter', 'Free'],
|
badges: ['Open Source', 'Converter', 'Free'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -85,8 +73,17 @@ export const tools: Tool[] = [
|
|||||||
summary:
|
summary:
|
||||||
'Generate a complete set of favicons for your website. Includes PWA manifest and HTML embed code. All processing happens locally in your browser.',
|
'Generate a complete set of favicons for your website. Includes PWA manifest and HTML embed code. All processing happens locally in your browser.',
|
||||||
icon: FaviconIcon,
|
icon: FaviconIcon,
|
||||||
gradient: 'gradient-blue-cyan',
|
badges: ['Open Source', 'Generator', 'Free'],
|
||||||
accentColor: '#3b82f6',
|
},
|
||||||
|
{
|
||||||
|
shortTitle: 'QR Code',
|
||||||
|
title: 'QR Code Generator',
|
||||||
|
navTitle: 'QR Code Generator',
|
||||||
|
href: '/qrcode',
|
||||||
|
description: 'Generate QR codes with custom colors, error correction, and multi-format export.',
|
||||||
|
summary:
|
||||||
|
'Generate QR codes with live preview, customizable colors, error correction levels, and export as PNG or SVG. All processing happens locally in your browser.',
|
||||||
|
icon: QRCodeIcon,
|
||||||
badges: ['Open Source', 'Generator', 'Free'],
|
badges: ['Open Source', 'Generator', 'Free'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide-react": "^0.575.0",
|
"lucide-react": "^0.575.0",
|
||||||
"next": "^16.1.6",
|
"next": "^16.1.6",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
"@tailwindcss/postcss": "^4.2.0",
|
"@tailwindcss/postcss": "^4.2.0",
|
||||||
"@types/figlet": "^1.7.0",
|
"@types/figlet": "^1.7.0",
|
||||||
"@types/node": "^25.3.0",
|
"@types/node": "^25.3.0",
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.21.0",
|
||||||
|
|||||||
142
pnpm-lock.yaml
generated
142
pnpm-lock.yaml
generated
@@ -59,6 +59,9 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: ^16.1.6
|
specifier: ^16.1.6
|
||||||
version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
qrcode:
|
||||||
|
specifier: ^1.5.4
|
||||||
|
version: 1.5.4
|
||||||
radix-ui:
|
radix-ui:
|
||||||
specifier: ^1.4.3
|
specifier: ^1.4.3
|
||||||
version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
@@ -93,6 +96,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.3.0
|
specifier: ^25.3.0
|
||||||
version: 25.3.0
|
version: 25.3.0
|
||||||
|
'@types/qrcode':
|
||||||
|
specifier: ^1.5.6
|
||||||
|
version: 1.5.6
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.14
|
specifier: ^19.2.14
|
||||||
version: 19.2.14
|
version: 19.2.14
|
||||||
@@ -1520,6 +1526,9 @@ packages:
|
|||||||
'@types/prismjs@1.26.6':
|
'@types/prismjs@1.26.6':
|
||||||
resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==}
|
resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==}
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||||
|
|
||||||
'@types/react-dom@19.2.3':
|
'@types/react-dom@19.2.3':
|
||||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1861,6 +1870,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
camelcase@5.3.1:
|
||||||
|
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001772:
|
caniuse-lite@1.0.30001772:
|
||||||
resolution: {integrity: sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==}
|
resolution: {integrity: sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==}
|
||||||
|
|
||||||
@@ -1890,6 +1903,9 @@ packages:
|
|||||||
client-only@0.0.1:
|
client-only@0.0.1:
|
||||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
|
|
||||||
|
cliui@6.0.0:
|
||||||
|
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -2015,6 +2031,10 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decamelize@1.2.0:
|
||||||
|
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
dedent@1.7.1:
|
dedent@1.7.1:
|
||||||
resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==}
|
resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2065,6 +2085,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
|
resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
|
||||||
engines: {node: '>=0.3.1'}
|
engines: {node: '>=0.3.1'}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3:
|
||||||
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
|
|
||||||
doctrine@2.1.0:
|
doctrine@2.1.0:
|
||||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2366,6 +2389,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
||||||
engines: {node: '>= 18.0.0'}
|
engines: {node: '>= 18.0.0'}
|
||||||
|
|
||||||
|
find-up@4.1.0:
|
||||||
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0:
|
||||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2933,6 +2960,10 @@ packages:
|
|||||||
lines-and-columns@1.2.4:
|
lines-and-columns@1.2.4:
|
||||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
|
|
||||||
|
locate-path@5.0.0:
|
||||||
|
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
locate-path@6.0.0:
|
locate-path@6.0.0:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3224,14 +3255,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
p-limit@2.3.0:
|
||||||
|
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
p-limit@3.1.0:
|
p-limit@3.1.0:
|
||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-locate@4.1.0:
|
||||||
|
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
p-locate@5.0.0:
|
p-locate@5.0.0:
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-try@2.2.0:
|
||||||
|
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
package-manager-detector@1.6.0:
|
package-manager-detector@1.6.0:
|
||||||
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
|
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
|
||||||
|
|
||||||
@@ -3293,6 +3336,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
|
|
||||||
|
pngjs@5.0.0:
|
||||||
|
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0:
|
possible-typed-array-names@1.1.0:
|
||||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3344,6 +3391,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
qs@6.15.0:
|
qs@6.15.0:
|
||||||
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
|
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@@ -3443,6 +3495,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
require-main-filename@2.0.0:
|
||||||
|
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -3520,6 +3575,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
|
set-blocking@2.0.0:
|
||||||
|
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3886,6 +3944,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
|
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
which-module@2.0.1:
|
||||||
|
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||||
|
|
||||||
which-typed-array@1.1.20:
|
which-typed-array@1.1.20:
|
||||||
resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
|
resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3919,6 +3980,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
|
resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
y18n@4.0.3:
|
||||||
|
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||||
|
|
||||||
y18n@5.0.8:
|
y18n@5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3926,10 +3990,18 @@ packages:
|
|||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
|
yargs-parser@18.1.3:
|
||||||
|
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
yargs-parser@21.1.1:
|
yargs-parser@21.1.1:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.2:
|
||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -5410,6 +5482,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/prismjs@1.26.6': {}
|
'@types/prismjs@1.26.6': {}
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 25.3.0
|
||||||
|
|
||||||
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
@@ -5772,6 +5848,8 @@ snapshots:
|
|||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
|
camelcase@5.3.1: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001772: {}
|
caniuse-lite@1.0.30001772: {}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
@@ -5795,6 +5873,12 @@ snapshots:
|
|||||||
|
|
||||||
client-only@0.0.1: {}
|
client-only@0.0.1: {}
|
||||||
|
|
||||||
|
cliui@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
wrap-ansi: 6.2.0
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
@@ -5902,6 +5986,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
dedent@1.7.1: {}
|
dedent@1.7.1: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
@@ -5937,6 +6023,8 @@ snapshots:
|
|||||||
|
|
||||||
diff@8.0.3: {}
|
diff@8.0.3: {}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
doctrine@2.1.0:
|
doctrine@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
@@ -6423,6 +6511,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
find-up@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
locate-path: 5.0.0
|
||||||
|
path-exists: 4.0.0
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 6.0.0
|
locate-path: 6.0.0
|
||||||
@@ -6914,6 +7007,10 @@ snapshots:
|
|||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
|
|
||||||
|
locate-path@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
p-locate: 4.1.0
|
||||||
|
|
||||||
locate-path@6.0.0:
|
locate-path@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
@@ -7255,14 +7352,24 @@ snapshots:
|
|||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
safe-push-apply: 1.0.0
|
safe-push-apply: 1.0.0
|
||||||
|
|
||||||
|
p-limit@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
p-try: 2.2.0
|
||||||
|
|
||||||
p-limit@3.1.0:
|
p-limit@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
|
|
||||||
|
p-locate@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
p-limit: 2.3.0
|
||||||
|
|
||||||
p-locate@5.0.0:
|
p-locate@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
|
p-try@2.2.0: {}
|
||||||
|
|
||||||
package-manager-detector@1.6.0: {}
|
package-manager-detector@1.6.0: {}
|
||||||
|
|
||||||
pako@1.0.11: {}
|
pako@1.0.11: {}
|
||||||
@@ -7304,6 +7411,8 @@ snapshots:
|
|||||||
|
|
||||||
pkce-challenge@5.0.1: {}
|
pkce-challenge@5.0.1: {}
|
||||||
|
|
||||||
|
pngjs@5.0.0: {}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0: {}
|
possible-typed-array-names@1.1.0: {}
|
||||||
|
|
||||||
postcss-selector-parser@7.1.1:
|
postcss-selector-parser@7.1.1:
|
||||||
@@ -7357,6 +7466,12 @@ snapshots:
|
|||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
dependencies:
|
||||||
|
dijkstrajs: 1.0.3
|
||||||
|
pngjs: 5.0.0
|
||||||
|
yargs: 15.4.1
|
||||||
|
|
||||||
qs@6.15.0:
|
qs@6.15.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
@@ -7518,6 +7633,8 @@ snapshots:
|
|||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
|
require-main-filename@2.0.0: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
@@ -7616,6 +7733,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
set-blocking@2.0.0: {}
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
define-data-property: 1.1.4
|
define-data-property: 1.1.4
|
||||||
@@ -8105,6 +8224,8 @@ snapshots:
|
|||||||
is-weakmap: 2.0.2
|
is-weakmap: 2.0.2
|
||||||
is-weakset: 2.0.4
|
is-weakset: 2.0.4
|
||||||
|
|
||||||
|
which-module@2.0.1: {}
|
||||||
|
|
||||||
which-typed-array@1.1.20:
|
which-typed-array@1.1.20:
|
||||||
dependencies:
|
dependencies:
|
||||||
available-typed-arrays: 1.0.7
|
available-typed-arrays: 1.0.7
|
||||||
@@ -8144,12 +8265,33 @@ snapshots:
|
|||||||
is-wsl: 3.1.1
|
is-wsl: 3.1.1
|
||||||
powershell-utils: 0.1.0
|
powershell-utils: 0.1.0
|
||||||
|
|
||||||
|
y18n@4.0.3: {}
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
|
yargs-parser@18.1.3:
|
||||||
|
dependencies:
|
||||||
|
camelcase: 5.3.1
|
||||||
|
decamelize: 1.2.0
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
dependencies:
|
||||||
|
cliui: 6.0.0
|
||||||
|
decamelize: 1.2.0
|
||||||
|
find-up: 4.1.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
require-directory: 2.1.1
|
||||||
|
require-main-filename: 2.0.0
|
||||||
|
set-blocking: 2.0.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
which-module: 2.0.1
|
||||||
|
y18n: 4.0.3
|
||||||
|
yargs-parser: 18.1.3
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 8.0.1
|
cliui: 8.0.1
|
||||||
|
|||||||
12
types/qrcode.ts
Normal file
12
types/qrcode.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export type ErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H';
|
||||||
|
|
||||||
|
export type ExportSize = 256 | 512 | 1024 | 2048;
|
||||||
|
|
||||||
|
export interface QRCodeOptions {
|
||||||
|
text: string;
|
||||||
|
errorCorrection: ErrorCorrectionLevel;
|
||||||
|
foregroundColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
margin: number;
|
||||||
|
size: ExportSize;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user