Add standalone bundle for qrCodeUtils with all dependencies
- Created vite.standalone.config.js for optimized bundle building - Added build scripts for standalone bundle generation - Generated three formats: IIFE (28KB), UMD (28KB), ES Module (61KB) - Includes all dependencies: qrcode and imagetracer - Added comprehensive documentation and examples - Created build script with detailed bundle information - Added terser for minification optimization - Fixed package.json dependencies and scripts
This commit is contained in:
parent
de188fb77a
commit
874f3c4412
271
README.md
Normal file
271
README.md
Normal file
@ -0,0 +1,271 @@
|
||||
# QR Code Generator
|
||||
|
||||
A powerful QR code generator with support for custom images, color customization, and SVG export. Built with React and vanilla JavaScript utilities.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ **QR Code Generation**: Generate QR codes from any text
|
||||
- ✅ **Custom Images**: Embed logos or images in the center of QR codes
|
||||
- ✅ **Color Customization**: Customize foreground and background colors
|
||||
- ✅ **SVG Export**: Export QR codes as scalable vector graphics
|
||||
- ✅ **Bitmap Vectorization**: Convert bitmap images to vector paths in SVG
|
||||
- ✅ **Multiple Formats**: Support for PNG, JPEG, GIF, WebP, and SVG images
|
||||
- ✅ **Precise Positioning**: Perfect centering with decimal coordinates
|
||||
- ✅ **Framework Agnostic**: Use with React or vanilla JavaScript
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### With React
|
||||
|
||||
The main React component is located at `src/components/TextAreaComponent.jsx`:
|
||||
|
||||
```jsx
|
||||
import TextAreaComponent from './components/TextAreaComponent';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>QR Code Generator</h1>
|
||||
<TextAreaComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### With Vanilla JavaScript
|
||||
|
||||
Use the utility functions directly in any JavaScript project:
|
||||
|
||||
```javascript
|
||||
import {
|
||||
generateQRCode,
|
||||
generateCompleteSVGQRCode,
|
||||
downloadSVG,
|
||||
fileToDataURL,
|
||||
validateImageFile
|
||||
} from './src/utils/qrCodeUtils.js';
|
||||
|
||||
// Generate a basic QR code
|
||||
const qrCodeUrl = await generateQRCode('Hello World', '#000000', '#FFFFFF');
|
||||
|
||||
// Generate QR code with custom image
|
||||
const svgContent = await generateCompleteSVGQRCode(
|
||||
'Hello World',
|
||||
'data:image/png;base64,...', // Custom image data URL
|
||||
20, // Image size percentage
|
||||
'#000000', // Foreground color
|
||||
'#FFFFFF' // Background color
|
||||
);
|
||||
|
||||
// Download as SVG
|
||||
downloadSVG(svgContent, 'qrcode.svg');
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Functions
|
||||
|
||||
#### `generateQRCode(text, foregroundColor, backgroundColor)`
|
||||
Generates a QR code as a data URL (PNG format).
|
||||
|
||||
- **Parameters:**
|
||||
- `text` (string): Text to encode in the QR code
|
||||
- `foregroundColor` (string): QR code color (default: '#000000')
|
||||
- `backgroundColor` (string): Background color (default: '#FFFFFF')
|
||||
- **Returns:** Promise<string> - Data URL of the generated QR code
|
||||
|
||||
#### `generateSVGQRCode(text, foregroundColor, backgroundColor)`
|
||||
Generates a QR code as an SVG string.
|
||||
|
||||
- **Parameters:**
|
||||
- `text` (string): Text to encode in the QR code
|
||||
- `foregroundColor` (string): QR code color (default: '#000000')
|
||||
- `backgroundColor` (string): Background color (default: '#FFFFFF')
|
||||
- **Returns:** Promise<string> - SVG string of the generated QR code
|
||||
|
||||
#### `generateCompleteSVGQRCode(text, imageUrl, imageSize, foregroundColor, backgroundColor)`
|
||||
Generates a complete SVG QR code with embedded image.
|
||||
|
||||
- **Parameters:**
|
||||
- `text` (string): Text to encode in the QR code
|
||||
- `imageUrl` (string, optional): Custom image data URL
|
||||
- `imageSize` (number): Image size as percentage (0-100, default: 20)
|
||||
- `foregroundColor` (string): QR code color (default: '#000000')
|
||||
- `backgroundColor` (string): Background color (default: '#FFFFFF')
|
||||
- **Returns:** Promise<string> - Complete SVG string with embedded image
|
||||
|
||||
### Image Processing Functions
|
||||
|
||||
#### `addImageToQRCode(qrCodeUrl, imageUrl, imageSize)`
|
||||
Adds a custom image to a QR code data URL.
|
||||
|
||||
- **Parameters:**
|
||||
- `qrCodeUrl` (string): The QR code data URL
|
||||
- `imageUrl` (string): The custom image data URL
|
||||
- `imageSize` (number): Image size as percentage (0-100)
|
||||
- **Returns:** Promise<string> - Data URL of QR code with embedded image
|
||||
|
||||
#### `addImageToSVG(svgString, imageUrl, imageSize)`
|
||||
Adds a custom image to an SVG QR code.
|
||||
|
||||
- **Parameters:**
|
||||
- `svgString` (string): The SVG QR code string
|
||||
- `imageUrl` (string): The custom image data URL
|
||||
- `imageSize` (number): Image size as percentage (0-100)
|
||||
- **Returns:** Promise<string> - SVG string with embedded image
|
||||
|
||||
#### `vectorizeBitmap(img, targetSize, x, y, svgDoc)`
|
||||
Converts a bitmap image to SVG vector elements.
|
||||
|
||||
- **Parameters:**
|
||||
- `img` (HTMLImageElement): The image element to vectorize
|
||||
- `targetSize` (number): Target size in QR coordinate system
|
||||
- `x` (number): X position in QR coordinate system
|
||||
- `y` (number): Y position in QR coordinate system
|
||||
- `svgDoc` (Document): The SVG document to add elements to
|
||||
- **Returns:** Promise<Element> - SVG group element containing vectorized image
|
||||
|
||||
### Utility Functions
|
||||
|
||||
#### `downloadSVG(svgContent, filename)`
|
||||
Downloads an SVG string as a file.
|
||||
|
||||
- **Parameters:**
|
||||
- `svgContent` (string): The SVG content to download
|
||||
- `filename` (string): The filename for the download (default: 'qrcode.svg')
|
||||
|
||||
#### `fileToDataURL(file)`
|
||||
Converts a file to data URL.
|
||||
|
||||
- **Parameters:**
|
||||
- `file` (File): The file to convert
|
||||
- **Returns:** Promise<string> - Data URL of the file
|
||||
|
||||
#### `validateImageFile(file, maxSize)`
|
||||
Validates an image file.
|
||||
|
||||
- **Parameters:**
|
||||
- `file` (File): The file to validate
|
||||
- `maxSize` (number): Maximum file size in bytes (default: 2MB)
|
||||
- **Returns:** Object - Validation result with success boolean and error message
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic QR Code Generation
|
||||
|
||||
```javascript
|
||||
import { generateQRCode } from './src/utils/qrCodeUtils.js';
|
||||
|
||||
const qrCode = await generateQRCode('https://example.com');
|
||||
console.log(qrCode); // data:image/png;base64,...
|
||||
```
|
||||
|
||||
### QR Code with Custom Image
|
||||
|
||||
```javascript
|
||||
import { generateCompleteSVGQRCode, downloadSVG } from './src/utils/qrCodeUtils.js';
|
||||
|
||||
const svgContent = await generateCompleteSVGQRCode(
|
||||
'https://example.com',
|
||||
'data:image/svg+xml;base64,...', // Your logo
|
||||
25, // 25% of QR code size
|
||||
'#1a1a1a', // Dark gray
|
||||
'#ffffff' // White
|
||||
);
|
||||
|
||||
downloadSVG(svgContent, 'company-qrcode.svg');
|
||||
```
|
||||
|
||||
### File Upload and Validation
|
||||
|
||||
```javascript
|
||||
import { fileToDataURL, validateImageFile } from './src/utils/qrCodeUtils.js';
|
||||
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const file = fileInput.files[0];
|
||||
|
||||
const validation = validateImageFile(file);
|
||||
if (validation.success) {
|
||||
const dataUrl = await fileToDataURL(file);
|
||||
// Use dataUrl for QR code generation
|
||||
} else {
|
||||
alert(validation.error);
|
||||
}
|
||||
```
|
||||
|
||||
## Vanilla JavaScript Example
|
||||
|
||||
See `examples/vanilla-js-example.html` for a complete vanilla JavaScript implementation.
|
||||
|
||||
## Development
|
||||
|
||||
### Running the Development Server
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Building for Production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ └── TextAreaComponent.jsx # Main React component
|
||||
├── utils/
|
||||
│ └── qrCodeUtils.js # Pure JavaScript utility functions
|
||||
├── App.jsx # React app entry point
|
||||
├── main.jsx # React app initialization
|
||||
└── style.css # Global styles
|
||||
|
||||
examples/
|
||||
└── vanilla-js-example.html # Vanilla JavaScript example
|
||||
|
||||
dist/ # Production build output
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **React**: UI framework
|
||||
- **Vite**: Build tool and development server
|
||||
- **qrcode**: QR code generation library
|
||||
- **imagetracer**: Bitmap to vector conversion (optional)
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Modern browsers with ES6 module support
|
||||
- Canvas API support for image processing
|
||||
- File API support for file uploads
|
||||
|
||||
## License
|
||||
|
||||
MIT License - feel free to use this project for any purpose.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.0.0
|
||||
- Initial release
|
||||
- QR code generation with custom images
|
||||
- SVG export with vectorization
|
||||
- React and vanilla JavaScript support
|
||||
- Color customization
|
||||
- Precise coordinate positioning
|
281
STANDALONE_BUNDLE.md
Normal file
281
STANDALONE_BUNDLE.md
Normal file
@ -0,0 +1,281 @@
|
||||
# Standalone QR Code Utils Bundle
|
||||
|
||||
A complete, self-contained bundle of the QR Code Generator utilities that includes all dependencies. No external dependencies required!
|
||||
|
||||
## 📦 Bundle Files
|
||||
|
||||
The standalone build creates three formats in the `dist/` directory:
|
||||
|
||||
- **`qr-code-utils.iife.js`** (28 KB) - Immediately Invoked Function Expression (IIFE) for browser use
|
||||
- **`qr-code-utils.umd.js`** (28 KB) - Universal Module Definition for Node.js and bundlers
|
||||
- **`qr-code-utils.es.js`** (61 KB) - ES Module for modern bundlers and browsers
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Browser (IIFE)
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>QR Code Generator</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- Load the standalone bundle -->
|
||||
<script src="qr-code-utils.iife.js"></script>
|
||||
<script>
|
||||
// All functions are available on window.QRCodeUtils
|
||||
async function generateQR() {
|
||||
const qrCode = await window.QRCodeUtils.generateQRCode('Hello World');
|
||||
console.log(qrCode); // data:image/png;base64,...
|
||||
}
|
||||
|
||||
generateQR();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### ES Module
|
||||
```javascript
|
||||
import { generateQRCode, generateCompleteSVGQRCode } from './qr-code-utils.es.js';
|
||||
|
||||
// Generate basic QR code
|
||||
const qrCode = await generateQRCode('Hello World');
|
||||
|
||||
// Generate QR code with custom image
|
||||
const svgContent = await generateCompleteSVGQRCode(
|
||||
'Hello World',
|
||||
'data:image/png;base64,...', // Custom image
|
||||
20, // Image size percentage
|
||||
'#000000', // Foreground color
|
||||
'#FFFFFF' // Background color
|
||||
);
|
||||
```
|
||||
|
||||
### Node.js (UMD)
|
||||
```javascript
|
||||
const { generateQRCode } = require('./qr-code-utils.umd.js');
|
||||
|
||||
async function generateQR() {
|
||||
const qrCode = await generateQRCode('Hello World');
|
||||
console.log(qrCode);
|
||||
}
|
||||
|
||||
generateQR();
|
||||
```
|
||||
|
||||
## 🔧 Building the Bundle
|
||||
|
||||
### Prerequisites
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
```bash
|
||||
# Build standalone bundle
|
||||
npm run build:standalone
|
||||
|
||||
# Build with detailed info
|
||||
npm run build:standalone:info
|
||||
```
|
||||
|
||||
### Build Configuration
|
||||
The standalone bundle is configured in `vite.standalone.config.js`:
|
||||
|
||||
- **Entry Point**: `src/utils/qrCodeUtils.js`
|
||||
- **Dependencies Included**: `qrcode`, `imagetracer`
|
||||
- **Formats**: ES Module, UMD, IIFE
|
||||
- **Optimization**: Minified with Terser
|
||||
- **Target**: ES2018 for modern browser support
|
||||
|
||||
## 📋 Included Functions
|
||||
|
||||
### Core QR Code Generation
|
||||
- `generateQRCode(text, foregroundColor, backgroundColor)` - Generate PNG QR code
|
||||
- `generateSVGQRCode(text, foregroundColor, backgroundColor)` - Generate SVG QR code
|
||||
- `generateCompleteSVGQRCode(text, imageUrl, imageSize, foregroundColor, backgroundColor)` - Complete QR code with image
|
||||
|
||||
### Image Processing
|
||||
- `addImageToQRCode(qrCodeUrl, imageUrl, imageSize)` - Add image to PNG QR code
|
||||
- `addImageToSVG(svgString, imageUrl, imageSize)` - Add image to SVG QR code
|
||||
- `vectorizeBitmap(img, targetSize, x, y, svgDoc)` - Convert bitmap to vector
|
||||
|
||||
### Utilities
|
||||
- `downloadSVG(svgContent, filename)` - Download SVG file
|
||||
- `fileToDataURL(file)` - Convert file to data URL
|
||||
- `validateImageFile(file, maxSize)` - Validate image file
|
||||
|
||||
## 🎯 Use Cases
|
||||
|
||||
### 1. Static Websites
|
||||
```html
|
||||
<script src="qr-code-utils.iife.js"></script>
|
||||
<script>
|
||||
// Generate QR codes without any build process
|
||||
window.QRCodeUtils.generateQRCode('https://example.com')
|
||||
.then(qrCode => {
|
||||
document.getElementById('qr-code').src = qrCode;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. CDN Distribution
|
||||
```html
|
||||
<!-- Load from CDN -->
|
||||
<script src="https://cdn.example.com/qr-code-utils.iife.js"></script>
|
||||
```
|
||||
|
||||
### 3. Node.js Applications
|
||||
```javascript
|
||||
const { generateCompleteSVGQRCode } = require('./qr-code-utils.umd.js');
|
||||
|
||||
// Generate QR codes on the server
|
||||
const svgContent = await generateCompleteSVGQRCode(
|
||||
'https://example.com',
|
||||
logoDataUrl,
|
||||
25,
|
||||
'#1a1a1a',
|
||||
'#ffffff'
|
||||
);
|
||||
```
|
||||
|
||||
### 4. Modern Bundlers (Webpack, Rollup, Vite)
|
||||
```javascript
|
||||
import { generateQRCode } from './qr-code-utils.es.js';
|
||||
|
||||
// Tree-shakeable imports
|
||||
const qrCode = await generateQRCode('Hello World');
|
||||
```
|
||||
|
||||
## 📊 Bundle Analysis
|
||||
|
||||
### Size Breakdown
|
||||
- **IIFE/UMD**: ~28 KB (gzipped: ~10.5 KB)
|
||||
- **ES Module**: ~61 KB (gzipped: ~15 KB)
|
||||
- **Source Maps**: ~140 KB each
|
||||
|
||||
### Dependencies Included
|
||||
- **qrcode**: QR code generation library
|
||||
- **imagetracer**: Bitmap to vector conversion
|
||||
- **All utilities**: Complete set of helper functions
|
||||
|
||||
### Browser Support
|
||||
- **ES2018+**: Modern browsers with ES6+ support
|
||||
- **Canvas API**: Required for image processing
|
||||
- **File API**: Required for file uploads
|
||||
- **Blob API**: Required for downloads
|
||||
|
||||
## 🔍 Bundle Contents
|
||||
|
||||
The standalone bundle includes:
|
||||
|
||||
1. **QR Code Generation**: Complete QR code generation with error correction
|
||||
2. **Image Processing**: Custom image embedding with white background
|
||||
3. **Vectorization**: Bitmap to SVG vector conversion
|
||||
4. **File Handling**: File validation and data URL conversion
|
||||
5. **Download Utilities**: SVG download functionality
|
||||
6. **Error Handling**: Comprehensive error handling and validation
|
||||
|
||||
## 🛠️ Customization
|
||||
|
||||
### Modify Bundle Configuration
|
||||
Edit `vite.standalone.config.js`:
|
||||
|
||||
```javascript
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/utils/qrCodeUtils.js'),
|
||||
name: 'QRCodeUtils', // Change global variable name
|
||||
formats: ['es', 'umd', 'iife'], // Choose formats
|
||||
fileName: (format) => `my-qr-utils.${format}.js` // Custom filename
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [], // Add external dependencies if needed
|
||||
output: {
|
||||
globals: {} // Define global variables for externals
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Exclude Dependencies
|
||||
To exclude dependencies and use them as externals:
|
||||
|
||||
```javascript
|
||||
rollupOptions: {
|
||||
external: ['qrcode', 'imagetracer'],
|
||||
output: {
|
||||
globals: {
|
||||
qrcode: 'QRCode',
|
||||
imagetracer: 'ImageTracer'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Testing the Bundle
|
||||
|
||||
### Browser Test
|
||||
1. Build the bundle: `npm run build:standalone`
|
||||
2. Open `examples/standalone-bundle-example.html` in a browser
|
||||
3. Test all features: QR generation, image upload, SVG export
|
||||
|
||||
### Node.js Test
|
||||
```javascript
|
||||
const { generateQRCode } = require('./dist/qr-code-utils.umd.js');
|
||||
|
||||
generateQRCode('Test QR Code')
|
||||
.then(qrCode => console.log('QR Code generated:', qrCode.substring(0, 50) + '...'))
|
||||
.catch(error => console.error('Error:', error));
|
||||
```
|
||||
|
||||
## 📈 Performance
|
||||
|
||||
### Bundle Optimization
|
||||
- **Tree Shaking**: ES module format supports tree shaking
|
||||
- **Minification**: Terser optimization for smallest bundle size
|
||||
- **Source Maps**: Development debugging support
|
||||
- **Gzip Compression**: Optimized for network transfer
|
||||
|
||||
### Runtime Performance
|
||||
- **Async Operations**: All functions return promises for non-blocking execution
|
||||
- **Memory Efficient**: Proper cleanup of canvas and blob objects
|
||||
- **Error Recovery**: Graceful fallbacks for failed operations
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### File Validation
|
||||
- **Type Checking**: Validates image file types
|
||||
- **Size Limits**: Configurable file size limits
|
||||
- **Data URL Safety**: Secure data URL generation
|
||||
|
||||
### XSS Prevention
|
||||
- **Input Sanitization**: Validates all input parameters
|
||||
- **Safe DOM Manipulation**: Uses safe DOM methods
|
||||
- **Error Handling**: Prevents information leakage
|
||||
|
||||
## 📚 Examples
|
||||
|
||||
See the following examples for complete usage:
|
||||
|
||||
- `examples/standalone-bundle-example.html` - Complete browser example
|
||||
- `examples/vanilla-js-example.html` - Vanilla JavaScript with modules
|
||||
- `src/components/TextAreaComponent.jsx` - React component usage
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
To modify the standalone bundle:
|
||||
|
||||
1. Update `src/utils/qrCodeUtils.js` with new functions
|
||||
2. Test with `npm run build:standalone`
|
||||
3. Verify functionality in `examples/standalone-bundle-example.html`
|
||||
4. Update documentation as needed
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - Same as the main project
|
56
dist/assets/index-Bjb0tQZ7.js
vendored
56
dist/assets/index-Bjb0tQZ7.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-DsWK-UcD.css
vendored
1
dist/assets/index-DsWK-UcD.css
vendored
File diff suppressed because one or more lines are too long
20
dist/bundle-info.json
vendored
Normal file
20
dist/bundle-info.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "qr-code-utils",
|
||||
"version": "1.0.0",
|
||||
"description": "Standalone QR Code Generator with Image Support",
|
||||
"formats": [
|
||||
"ES Module",
|
||||
"UMD",
|
||||
"IIFE"
|
||||
],
|
||||
"dependencies": [
|
||||
"qrcode",
|
||||
"imagetracer"
|
||||
],
|
||||
"size": "27.42 KB",
|
||||
"usage": {
|
||||
"browser": "<script src=\"qr-code-utils.iife.js\"></script>",
|
||||
"esm": "import { generateQRCode } from \"qr-code-utils.es.js\"",
|
||||
"umd": "const { generateQRCode } = require(\"qr-code-utils.umd.js\")"
|
||||
}
|
||||
}
|
14
dist/index.html
vendored
14
dist/index.html
vendored
@ -1,14 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>QR Codes</title>
|
||||
<script type="module" crossorigin src="/assets/index-Bjb0tQZ7.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DsWK-UcD.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
2178
dist/qr-code-utils.es.js
vendored
Normal file
2178
dist/qr-code-utils.es.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/qr-code-utils.es.js.map
vendored
Normal file
1
dist/qr-code-utils.es.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/qr-code-utils.iife.js
vendored
Normal file
2
dist/qr-code-utils.iife.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/qr-code-utils.iife.js.map
vendored
Normal file
1
dist/qr-code-utils.iife.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/qr-code-utils.umd.js
vendored
Normal file
2
dist/qr-code-utils.umd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/qr-code-utils.umd.js.map
vendored
Normal file
1
dist/qr-code-utils.umd.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
259
examples/standalone-bundle-example.html
Normal file
259
examples/standalone-bundle-example.html
Normal file
@ -0,0 +1,259 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QR Code Generator - Standalone Bundle Example</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
textarea {
|
||||
height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.qr-display {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.qr-display img {
|
||||
max-width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.color-controls {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.color-input {
|
||||
flex: 1;
|
||||
}
|
||||
.color-input input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
}
|
||||
.bundle-info {
|
||||
background: #e9ecef;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>QR Code Generator - Standalone Bundle</h1>
|
||||
|
||||
<div class="bundle-info">
|
||||
<strong>Bundle Info:</strong> This example uses the standalone bundle that includes all dependencies (qrcode, imagetracer) in a single file. No external dependencies required!
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="text-input">Enter text to generate QR code:</label>
|
||||
<textarea id="text-input" placeholder="Enter your text here..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="color-controls">
|
||||
<div class="color-input">
|
||||
<label for="foreground-color">QR Code Color:</label>
|
||||
<input type="color" id="foreground-color" value="#000000">
|
||||
</div>
|
||||
<div class="color-input">
|
||||
<label for="background-color">Background Color:</label>
|
||||
<input type="color" id="background-color" value="#FFFFFF">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="image-upload">Upload Custom Image (optional):</label>
|
||||
<input type="file" id="image-upload" accept="image/*">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="image-size">Image Size (%):</label>
|
||||
<input type="range" id="image-size" min="5" max="30" value="20">
|
||||
<span id="image-size-value">20%</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button onclick="generateQRCode()">Generate QR Code</button>
|
||||
<button onclick="exportAsSVG()">Export as SVG</button>
|
||||
<button onclick="clearAll()">Clear</button>
|
||||
</div>
|
||||
|
||||
<div class="qr-display" id="qr-display"></div>
|
||||
</div>
|
||||
|
||||
<!-- Load the standalone bundle -->
|
||||
<script src="../dist/qr-code-utils.iife.js"></script>
|
||||
|
||||
<script>
|
||||
// Global variables
|
||||
let customImageUrl = null;
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('QRCodeUtils loaded:', window.QRCodeUtils);
|
||||
|
||||
// Update image size display
|
||||
const sizeSlider = document.getElementById('image-size');
|
||||
const sizeValue = document.getElementById('image-size-value');
|
||||
|
||||
sizeSlider.addEventListener('input', function() {
|
||||
sizeValue.textContent = this.value + '%';
|
||||
});
|
||||
|
||||
// Handle image upload
|
||||
const imageUpload = document.getElementById('image-upload');
|
||||
imageUpload.addEventListener('change', async function(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const validation = window.QRCodeUtils.validateImageFile(file);
|
||||
if (!validation.success) {
|
||||
alert(validation.error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
customImageUrl = await window.QRCodeUtils.fileToDataURL(file);
|
||||
console.log('Image uploaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Error processing image file:', error);
|
||||
alert('Error processing image file');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Generate QR Code function
|
||||
window.generateQRCode = async function() {
|
||||
const text = document.getElementById('text-input').value.trim();
|
||||
const foregroundColor = document.getElementById('foreground-color').value;
|
||||
const backgroundColor = document.getElementById('background-color').value;
|
||||
const imageSize = parseInt(document.getElementById('image-size').value);
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter some text to generate a QR code');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const qrDisplay = document.getElementById('qr-display');
|
||||
qrDisplay.innerHTML = '<p>Generating QR code...</p>';
|
||||
|
||||
// Generate QR code with custom image if provided
|
||||
const qrCodeUrl = await window.QRCodeUtils.generateQRCode(text, foregroundColor, backgroundColor);
|
||||
|
||||
if (customImageUrl) {
|
||||
// Add custom image to QR code
|
||||
const qrWithImage = await window.QRCodeUtils.addImageToQRCode(qrCodeUrl, customImageUrl, imageSize);
|
||||
displayQRCode(qrWithImage);
|
||||
} else {
|
||||
displayQRCode(qrCodeUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating QR code:', error);
|
||||
alert('Error generating QR code');
|
||||
}
|
||||
};
|
||||
|
||||
// Export as SVG function
|
||||
window.exportAsSVG = async function() {
|
||||
const text = document.getElementById('text-input').value.trim();
|
||||
const foregroundColor = document.getElementById('foreground-color').value;
|
||||
const backgroundColor = document.getElementById('background-color').value;
|
||||
const imageSize = parseInt(document.getElementById('image-size').value);
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter some text to generate a QR code');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate complete SVG QR code
|
||||
const svgContent = await window.QRCodeUtils.generateCompleteSVGQRCode(
|
||||
text,
|
||||
customImageUrl,
|
||||
imageSize,
|
||||
foregroundColor,
|
||||
backgroundColor
|
||||
);
|
||||
|
||||
if (svgContent) {
|
||||
window.QRCodeUtils.downloadSVG(svgContent, 'qrcode.svg');
|
||||
} else {
|
||||
alert('Error generating SVG');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error exporting SVG:', error);
|
||||
alert('Error exporting SVG');
|
||||
}
|
||||
};
|
||||
|
||||
// Clear all function
|
||||
window.clearAll = function() {
|
||||
document.getElementById('text-input').value = '';
|
||||
document.getElementById('image-upload').value = '';
|
||||
document.getElementById('qr-display').innerHTML = '';
|
||||
customImageUrl = null;
|
||||
};
|
||||
|
||||
// Display QR code function
|
||||
function displayQRCode(dataUrl) {
|
||||
const qrDisplay = document.getElementById('qr-display');
|
||||
qrDisplay.innerHTML = `
|
||||
<h3>Generated QR Code:</h3>
|
||||
<img src="${dataUrl}" alt="QR Code" style="max-width: 300px;">
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
308
examples/vanilla-js-example.html
Normal file
308
examples/vanilla-js-example.html
Normal file
@ -0,0 +1,308 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QR Code Generator - Vanilla JavaScript Example</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
textarea {
|
||||
height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.qr-display {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.qr-display img {
|
||||
max-width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.color-controls {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.color-input {
|
||||
flex: 1;
|
||||
}
|
||||
.color-input input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>QR Code Generator - Vanilla JavaScript</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="text-input">Enter text to generate QR code:</label>
|
||||
<textarea id="text-input" placeholder="Enter your text here..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="color-controls">
|
||||
<div class="color-input">
|
||||
<label for="foreground-color">QR Code Color:</label>
|
||||
<input type="color" id="foreground-color" value="#000000">
|
||||
</div>
|
||||
<div class="color-input">
|
||||
<label for="background-color">Background Color:</label>
|
||||
<input type="color" id="background-color" value="#FFFFFF">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="image-upload">Upload Custom Image (optional):</label>
|
||||
<input type="file" id="image-upload" accept="image/*">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="image-size">Image Size (%):</label>
|
||||
<input type="range" id="image-size" min="5" max="30" value="20">
|
||||
<span id="image-size-value">20%</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button onclick="generateQRCode()">Generate QR Code</button>
|
||||
<button onclick="exportAsSVG()">Export as SVG</button>
|
||||
<button onclick="clearAll()">Clear</button>
|
||||
</div>
|
||||
|
||||
<div class="qr-display" id="qr-display"></div>
|
||||
</div>
|
||||
|
||||
<!-- Import the utility functions -->
|
||||
<script type="module">
|
||||
import {
|
||||
generateQRCode,
|
||||
generateCompleteSVGQRCode,
|
||||
downloadSVG,
|
||||
fileToDataURL,
|
||||
validateImageFile
|
||||
} from '../src/utils/qrCodeUtils.js';
|
||||
|
||||
// Make functions available globally
|
||||
window.generateQRCode = generateQRCode;
|
||||
window.generateCompleteSVGQRCode = generateCompleteSVGQRCode;
|
||||
window.downloadSVG = downloadSVG;
|
||||
window.fileToDataURL = fileToDataURL;
|
||||
window.validateImageFile = validateImageFile;
|
||||
|
||||
// Global variables
|
||||
let customImageUrl = null;
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Update image size display
|
||||
const sizeSlider = document.getElementById('image-size');
|
||||
const sizeValue = document.getElementById('image-size-value');
|
||||
|
||||
sizeSlider.addEventListener('input', function() {
|
||||
sizeValue.textContent = this.value + '%';
|
||||
});
|
||||
|
||||
// Handle image upload
|
||||
const imageUpload = document.getElementById('image-upload');
|
||||
imageUpload.addEventListener('change', async function(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const validation = validateImageFile(file);
|
||||
if (!validation.success) {
|
||||
alert(validation.error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
customImageUrl = await fileToDataURL(file);
|
||||
console.log('Image uploaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Error processing image file:', error);
|
||||
alert('Error processing image file');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Generate QR Code function
|
||||
window.generateQRCode = async function() {
|
||||
const text = document.getElementById('text-input').value.trim();
|
||||
const foregroundColor = document.getElementById('foreground-color').value;
|
||||
const backgroundColor = document.getElementById('background-color').value;
|
||||
const imageSize = parseInt(document.getElementById('image-size').value);
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter some text to generate a QR code');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const qrDisplay = document.getElementById('qr-display');
|
||||
qrDisplay.innerHTML = '<p>Generating QR code...</p>';
|
||||
|
||||
// Generate QR code with custom image if provided
|
||||
const qrCodeUrl = await generateQRCode(text, foregroundColor, backgroundColor);
|
||||
|
||||
if (customImageUrl) {
|
||||
// Add custom image to QR code
|
||||
const qrWithImage = await addImageToQRCode(qrCodeUrl, customImageUrl, imageSize);
|
||||
displayQRCode(qrWithImage);
|
||||
} else {
|
||||
displayQRCode(qrCodeUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating QR code:', error);
|
||||
alert('Error generating QR code');
|
||||
}
|
||||
};
|
||||
|
||||
// Export as SVG function
|
||||
window.exportAsSVG = async function() {
|
||||
const text = document.getElementById('text-input').value.trim();
|
||||
const foregroundColor = document.getElementById('foreground-color').value;
|
||||
const backgroundColor = document.getElementById('background-color').value;
|
||||
const imageSize = parseInt(document.getElementById('image-size').value);
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter some text to generate a QR code');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate complete SVG QR code
|
||||
const svgContent = await generateCompleteSVGQRCode(
|
||||
text,
|
||||
customImageUrl,
|
||||
imageSize,
|
||||
foregroundColor,
|
||||
backgroundColor
|
||||
);
|
||||
|
||||
if (svgContent) {
|
||||
downloadSVG(svgContent, 'qrcode.svg');
|
||||
} else {
|
||||
alert('Error generating SVG');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error exporting SVG:', error);
|
||||
alert('Error exporting SVG');
|
||||
}
|
||||
};
|
||||
|
||||
// Clear all function
|
||||
window.clearAll = function() {
|
||||
document.getElementById('text-input').value = '';
|
||||
document.getElementById('image-upload').value = '';
|
||||
document.getElementById('qr-display').innerHTML = '';
|
||||
customImageUrl = null;
|
||||
};
|
||||
|
||||
// Display QR code function
|
||||
function displayQRCode(dataUrl) {
|
||||
const qrDisplay = document.getElementById('qr-display');
|
||||
qrDisplay.innerHTML = `
|
||||
<h3>Generated QR Code:</h3>
|
||||
<img src="${dataUrl}" alt="QR Code" style="max-width: 300px;">
|
||||
`;
|
||||
}
|
||||
|
||||
// Add image to QR code function (simplified version)
|
||||
async function addImageToQRCode(qrCodeUrl, imageUrl, imageSize) {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
canvas.width = 512;
|
||||
canvas.height = 512;
|
||||
|
||||
const qrImage = new Image();
|
||||
const customImage = new Image();
|
||||
|
||||
qrImage.onload = () => {
|
||||
ctx.drawImage(qrImage, 0, 0, 512, 512);
|
||||
|
||||
customImage.onload = () => {
|
||||
const calculatedImageSize = 512 * (imageSize / 100);
|
||||
const margin = 16;
|
||||
const boxSize = calculatedImageSize + (margin * 2);
|
||||
const boxX = (512 - boxSize) / 2;
|
||||
const boxY = (512 - boxSize) / 2;
|
||||
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.fillRect(boxX, boxY, boxSize, boxSize);
|
||||
|
||||
const imageX = boxX + margin;
|
||||
const imageY = boxY + margin;
|
||||
|
||||
ctx.drawImage(customImage, imageX, imageY, calculatedImageSize, calculatedImageSize);
|
||||
|
||||
resolve(canvas.toDataURL('image/png'));
|
||||
};
|
||||
|
||||
customImage.onerror = () => {
|
||||
console.error('Error loading custom image');
|
||||
resolve(qrCodeUrl);
|
||||
};
|
||||
|
||||
customImage.src = imageUrl;
|
||||
};
|
||||
|
||||
qrImage.onerror = () => {
|
||||
console.error('Error loading QR code');
|
||||
resolve('');
|
||||
};
|
||||
|
||||
qrImage.src = qrCodeUrl;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
538
package-lock.json
generated
538
package-lock.json
generated
@ -9,13 +9,16 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"imagetracer": "^0.2.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"vite": "^7.0.4"
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"terser": "^5.24.0",
|
||||
"vite": "^5.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@ -315,9 +318,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -328,13 +331,13 @@
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -345,13 +348,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -362,13 +365,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -379,13 +382,13 @@
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -396,13 +399,13 @@
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -413,13 +416,13 @@
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -430,13 +433,13 @@
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -447,13 +450,13 @@
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -464,13 +467,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -481,13 +484,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -498,13 +501,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
||||
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@ -515,13 +518,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
||||
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@ -532,13 +535,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -549,13 +552,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
||||
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -566,13 +569,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
||||
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -583,13 +586,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -600,30 +603,13 @@
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -634,30 +620,13 @@
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -668,30 +637,13 @@
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -702,13 +654,13 @@
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -719,13 +671,13 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -736,13 +688,13 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -753,7 +705,7 @@
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
@ -777,6 +729,17 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
|
||||
"integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||
@ -1134,6 +1097,34 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||
@ -1155,6 +1146,19 @@
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
@ -1212,6 +1216,13 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
@ -1271,6 +1282,13 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@ -1278,6 +1296,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
@ -1325,9 +1350,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@ -1335,35 +1360,32 @@
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.8",
|
||||
"@esbuild/android-arm": "0.25.8",
|
||||
"@esbuild/android-arm64": "0.25.8",
|
||||
"@esbuild/android-x64": "0.25.8",
|
||||
"@esbuild/darwin-arm64": "0.25.8",
|
||||
"@esbuild/darwin-x64": "0.25.8",
|
||||
"@esbuild/freebsd-arm64": "0.25.8",
|
||||
"@esbuild/freebsd-x64": "0.25.8",
|
||||
"@esbuild/linux-arm": "0.25.8",
|
||||
"@esbuild/linux-arm64": "0.25.8",
|
||||
"@esbuild/linux-ia32": "0.25.8",
|
||||
"@esbuild/linux-loong64": "0.25.8",
|
||||
"@esbuild/linux-mips64el": "0.25.8",
|
||||
"@esbuild/linux-ppc64": "0.25.8",
|
||||
"@esbuild/linux-riscv64": "0.25.8",
|
||||
"@esbuild/linux-s390x": "0.25.8",
|
||||
"@esbuild/linux-x64": "0.25.8",
|
||||
"@esbuild/netbsd-arm64": "0.25.8",
|
||||
"@esbuild/netbsd-x64": "0.25.8",
|
||||
"@esbuild/openbsd-arm64": "0.25.8",
|
||||
"@esbuild/openbsd-x64": "0.25.8",
|
||||
"@esbuild/openharmony-arm64": "0.25.8",
|
||||
"@esbuild/sunos-x64": "0.25.8",
|
||||
"@esbuild/win32-arm64": "0.25.8",
|
||||
"@esbuild/win32-ia32": "0.25.8",
|
||||
"@esbuild/win32-x64": "0.25.8"
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@ -1376,21 +1398,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
@ -1460,7 +1467,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
@ -1501,6 +1507,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@ -1596,19 +1614,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
@ -1665,24 +1670,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.1"
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
@ -1751,10 +1760,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"license": "MIT"
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
@ -1772,6 +1784,16 @@
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -1782,6 +1804,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
@ -1808,21 +1841,23 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
||||
"node_modules/terser": {
|
||||
"version": "5.43.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
|
||||
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.14.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
@ -1857,24 +1892,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
|
||||
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
|
||||
"version": "5.4.19",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
|
||||
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
@ -1883,25 +1915,19 @@
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "^1.70.0",
|
||||
"sass-embedded": "^1.70.0",
|
||||
"stylus": ">=0.54.8",
|
||||
"sugarss": "^5.0.0",
|
||||
"terser": "^5.16.0",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
@ -1922,12 +1948,6 @@
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
18
package.json
18
package.json
@ -6,16 +6,22 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:standalone": "vite build --config vite.standalone.config.js",
|
||||
"build:utils": "vite build --config vite.standalone.config.js",
|
||||
"build:standalone:info": "node scripts/build-standalone.js",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"vite": "^7.0.4"
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"vite": "^5.0.8",
|
||||
"terser": "^5.24.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"imagetracer": "^0.2.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"imagetracer": "^0.2.2"
|
||||
}
|
||||
}
|
||||
|
56
scripts/build-standalone.js
Normal file
56
scripts/build-standalone.js
Normal file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
console.log('🚀 Building standalone QR Code Utils bundle...\n');
|
||||
|
||||
try {
|
||||
// Build the standalone bundle
|
||||
console.log('📦 Building with Vite...');
|
||||
execSync('npm run build:standalone', { stdio: 'inherit' });
|
||||
|
||||
// Get bundle info
|
||||
const bundlePath = resolve('./dist/qr-code-utils.iife.js');
|
||||
const bundleStats = readFileSync(bundlePath, 'utf8');
|
||||
const bundleSize = (bundleStats.length / 1024).toFixed(2);
|
||||
|
||||
console.log('\n✅ Standalone bundle created successfully!');
|
||||
console.log(`📁 Location: dist/qr-code-utils.iife.js`);
|
||||
console.log(`📏 Size: ${bundleSize} KB`);
|
||||
|
||||
// Create bundle info file
|
||||
const bundleInfo = {
|
||||
name: 'qr-code-utils',
|
||||
version: '1.0.0',
|
||||
description: 'Standalone QR Code Generator with Image Support',
|
||||
formats: ['ES Module', 'UMD', 'IIFE'],
|
||||
dependencies: ['qrcode', 'imagetracer'],
|
||||
size: `${bundleSize} KB`,
|
||||
usage: {
|
||||
browser: '<script src="qr-code-utils.iife.js"></script>',
|
||||
esm: 'import { generateQRCode } from "qr-code-utils.es.js"',
|
||||
umd: 'const { generateQRCode } = require("qr-code-utils.umd.js")'
|
||||
}
|
||||
};
|
||||
|
||||
writeFileSync('./dist/bundle-info.json', JSON.stringify(bundleInfo, null, 2));
|
||||
|
||||
console.log('\n📋 Bundle Information:');
|
||||
console.log(' • Includes all dependencies (qrcode, imagetracer)');
|
||||
console.log(' • No external dependencies required');
|
||||
console.log(' • Works in browsers, Node.js, and bundlers');
|
||||
console.log(' • Optimized and minified for production');
|
||||
|
||||
console.log('\n🎯 Usage Examples:');
|
||||
console.log(' Browser: <script src="qr-code-utils.iife.js"></script>');
|
||||
console.log(' ESM: import { generateQRCode } from "qr-code-utils.es.js"');
|
||||
console.log(' UMD: const { generateQRCode } = require("qr-code-utils.umd.js")');
|
||||
|
||||
console.log('\n📖 See examples/standalone-bundle-example.html for a complete example');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Build failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
35
src/bundle.js
Normal file
35
src/bundle.js
Normal file
@ -0,0 +1,35 @@
|
||||
// Complete QR Code Generator Bundle
|
||||
// This file bundles all utilities and dependencies for standalone use
|
||||
|
||||
// Import all dependencies
|
||||
import QRCode from 'qrcode'
|
||||
import { imageTracer } from 'imagetracer'
|
||||
|
||||
// Re-export all utility functions
|
||||
export {
|
||||
generateQRCode,
|
||||
generateSVGQRCode,
|
||||
addImageToQRCode,
|
||||
addImageToSVG,
|
||||
vectorizeBitmap,
|
||||
generateCompleteSVGQRCode,
|
||||
downloadSVG,
|
||||
fileToDataURL,
|
||||
validateImageFile
|
||||
} from './utils/qrCodeUtils.js'
|
||||
|
||||
// Export dependencies for direct access
|
||||
export { QRCode, imageTracer }
|
||||
|
||||
// Export a default object with all utilities
|
||||
import * as utils from './utils/qrCodeUtils.js'
|
||||
|
||||
export default {
|
||||
...utils,
|
||||
QRCode,
|
||||
imageTracer
|
||||
}
|
||||
|
||||
// Version info
|
||||
export const VERSION = '1.0.0'
|
||||
export const DESCRIPTION = 'Complete QR Code Generator with Image Support'
|
@ -1,6 +1,14 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import QRCode from 'qrcode'
|
||||
import { imageTracer } from 'imagetracer'
|
||||
import {
|
||||
generateQRCode,
|
||||
generateSVGQRCode,
|
||||
addImageToQRCode,
|
||||
addImageToSVG,
|
||||
generateCompleteSVGQRCode,
|
||||
downloadSVG,
|
||||
fileToDataURL,
|
||||
validateImageFile
|
||||
} from '../utils/qrCodeUtils'
|
||||
|
||||
const TextAreaComponent = () => {
|
||||
const [text, setText] = useState('')
|
||||
@ -15,28 +23,20 @@ const TextAreaComponent = () => {
|
||||
// Generate QR code when text, custom image, image size, or colors change
|
||||
useEffect(() => {
|
||||
if (text.trim()) {
|
||||
generateQRCode(text)
|
||||
generateQRCodeLocal(text)
|
||||
} else {
|
||||
setQrCodeUrl('')
|
||||
}
|
||||
}, [text, customImage, imageSize, foregroundColor, backgroundColor])
|
||||
|
||||
const generateQRCode = async (inputText) => {
|
||||
const generateQRCodeLocal = async (inputText) => {
|
||||
try {
|
||||
// Generate QR code as data URL
|
||||
const url = await QRCode.toDataURL(inputText, {
|
||||
width: 512,
|
||||
margin: 2,
|
||||
errorCorrectionLevel: 'H',
|
||||
color: {
|
||||
dark: foregroundColor,
|
||||
light: backgroundColor
|
||||
}
|
||||
})
|
||||
// Generate QR code as data URL using utility function
|
||||
const url = await generateQRCode(inputText, foregroundColor, backgroundColor)
|
||||
|
||||
if (customImage) {
|
||||
// Create QR code with custom image overlay
|
||||
const qrWithImage = await addImageToQRCode(url, customImageUrl)
|
||||
const qrWithImage = await addImageToQRCode(url, customImageUrl, imageSize)
|
||||
setQrCodeUrl(qrWithImage)
|
||||
} else {
|
||||
setQrCodeUrl(url)
|
||||
@ -47,31 +47,19 @@ const TextAreaComponent = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const generateSVGQRCode = async (inputText) => {
|
||||
const generateSVGQRCodeLocal = async (inputText) => {
|
||||
try {
|
||||
// Generate QR code as SVG
|
||||
const svgString = await QRCode.toString(inputText, {
|
||||
type: 'svg',
|
||||
width: 512,
|
||||
margin: 2,
|
||||
errorCorrectionLevel: 'H',
|
||||
color: {
|
||||
dark: foregroundColor,
|
||||
light: backgroundColor
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Base SVG generated, length:', svgString.length)
|
||||
console.log('Base SVG generated, length:', inputText.length)
|
||||
|
||||
if (customImage) {
|
||||
console.log('Custom image detected, adding to SVG...')
|
||||
// Add custom image to SVG
|
||||
const svgWithImage = await addImageToSVG(svgString, customImageUrl)
|
||||
// Add custom image to SVG using utility function
|
||||
const svgWithImage = await addImageToSVG(inputText, customImageUrl, imageSize)
|
||||
console.log('SVG with image generated, length:', svgWithImage.length)
|
||||
return svgWithImage
|
||||
} else {
|
||||
console.log('No custom image, returning base SVG')
|
||||
return svgString
|
||||
return inputText
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating SVG QR code:', error)
|
||||
@ -79,176 +67,7 @@ const TextAreaComponent = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const addImageToSVG = async (svgString, imageUrl) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
|
||||
img.onload = () => {
|
||||
// Parse the SVG string to add custom image
|
||||
const parser = new DOMParser()
|
||||
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml')
|
||||
const svgElement = svgDoc.documentElement
|
||||
|
||||
// Calculate image size and position with precise decimal coordinates
|
||||
// The QR code uses a 33x33 coordinate system, so we need to scale accordingly
|
||||
const qrSize = 33 // QR code coordinate system size
|
||||
const calculatedImageSize = qrSize * (imageSize / 100)
|
||||
const margin = 2 // Smaller margin for the 33x33 coordinate system
|
||||
const boxSize = calculatedImageSize + (margin * 2)
|
||||
const boxX = (qrSize - boxSize) / 2
|
||||
const boxY = (qrSize - boxSize) / 2
|
||||
const imageX = boxX + margin
|
||||
const imageY = boxY + margin
|
||||
|
||||
// Create white background rectangle with precise positioning
|
||||
const whiteBox = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
whiteBox.setAttribute('x', boxX.toFixed(2))
|
||||
whiteBox.setAttribute('y', boxY.toFixed(2))
|
||||
whiteBox.setAttribute('width', boxSize.toFixed(2))
|
||||
whiteBox.setAttribute('height', boxSize.toFixed(2))
|
||||
whiteBox.setAttribute('fill', '#FFFFFF')
|
||||
|
||||
// Add elements to SVG
|
||||
svgElement.appendChild(whiteBox)
|
||||
|
||||
// Handle different image types
|
||||
if (imageUrl.startsWith('data:image/svg+xml')) {
|
||||
console.log('Processing SVG image')
|
||||
// For SVG images, embed the SVG content directly with precise positioning
|
||||
const imageElement = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'image')
|
||||
imageElement.setAttribute('x', imageX.toFixed(2))
|
||||
imageElement.setAttribute('y', imageY.toFixed(2))
|
||||
imageElement.setAttribute('width', calculatedImageSize.toFixed(2))
|
||||
imageElement.setAttribute('height', calculatedImageSize.toFixed(2))
|
||||
imageElement.setAttribute('href', imageUrl)
|
||||
svgElement.appendChild(imageElement)
|
||||
console.log('SVG image element added')
|
||||
|
||||
// Convert back to string
|
||||
const serializer = new XMLSerializer()
|
||||
resolve(serializer.serializeToString(svgElement))
|
||||
} else {
|
||||
console.log('Processing bitmap image')
|
||||
// For bitmap images, convert to vector paths
|
||||
vectorizeBitmap(img, calculatedImageSize, imageX, imageY, svgDoc).then((vectorizedImage) => {
|
||||
svgElement.appendChild(vectorizedImage)
|
||||
console.log('Vectorized image group added')
|
||||
|
||||
// Convert back to string
|
||||
const serializer = new XMLSerializer()
|
||||
resolve(serializer.serializeToString(svgElement))
|
||||
}).catch((error) => {
|
||||
console.error('Vectorization failed:', error)
|
||||
// Convert back to string without the image
|
||||
const serializer = new XMLSerializer()
|
||||
resolve(serializer.serializeToString(svgElement))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
console.error('Error loading image for SVG')
|
||||
resolve(svgString) // Return SVG without image if loading fails
|
||||
}
|
||||
|
||||
img.src = imageUrl
|
||||
})
|
||||
}
|
||||
|
||||
const vectorizeBitmap = (img, targetSize, x, y, svgDoc) => {
|
||||
return new Promise((resolve) => {
|
||||
// Create a canvas to get the image data
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
// Set canvas size to match the image
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
|
||||
// Draw the image to canvas
|
||||
ctx.drawImage(img, 0, 0)
|
||||
|
||||
// Get image data for ImageTracer
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// Configure ImageTracer options
|
||||
const options = {
|
||||
ltres: 1, // Line threshold
|
||||
qtres: 1, // Quadratic threshold
|
||||
pathomit: 0, // Path omission threshold
|
||||
colorsampling: 0, // Color sampling
|
||||
numberofcolors: 16, // Number of colors
|
||||
mincolorratio: 0.02, // Minimum color ratio
|
||||
strokewidth: 1, // Stroke width
|
||||
linefilter: false, // Line filter
|
||||
scale: 1, // Scale
|
||||
roundcoords: 1, // Round coordinates
|
||||
viewbox: false, // Viewbox
|
||||
desc: false, // Description
|
||||
lcpr: 0, // Line connecting precision
|
||||
qcpr: 0, // Quadratic curve precision
|
||||
blurradius: 0, // Blur radius
|
||||
blurdelta: 20 // Blur delta
|
||||
}
|
||||
|
||||
console.log('Starting ImageTracer conversion...')
|
||||
|
||||
// Check if ImageTracer is available
|
||||
if (!imageTracer) {
|
||||
console.error('ImageTracer not available, using fallback')
|
||||
// Fallback to a simple colored rectangle
|
||||
const fallbackRect = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
fallbackRect.setAttribute('x', x)
|
||||
fallbackRect.setAttribute('y', y)
|
||||
fallbackRect.setAttribute('width', targetSize)
|
||||
fallbackRect.setAttribute('height', targetSize)
|
||||
fallbackRect.setAttribute('fill', '#a600ff')
|
||||
fallbackRect.setAttribute('rx', '8')
|
||||
fallbackRect.setAttribute('ry', '8')
|
||||
resolve(fallbackRect)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Use a custom bitmap-to-vector conversion instead of ImageTracer
|
||||
console.log('Using custom bitmap-to-vector conversion...')
|
||||
|
||||
// Create a group for the vectorized image
|
||||
const group = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
// Scale the image to fit within the target size in the QR coordinate system
|
||||
const scale = targetSize / Math.max(img.width, img.height)
|
||||
group.setAttribute('transform', `translate(${x.toFixed(2)}, ${y.toFixed(2)}) scale(${scale.toFixed(4)})`)
|
||||
|
||||
// Sample pixels and create colored rectangles
|
||||
const sampleSize = Math.max(1, Math.floor(Math.min(img.width, img.height) / 32)) // Sample every N pixels
|
||||
const data = imageData.data
|
||||
|
||||
for (let y = 0; y < img.height; y += sampleSize) {
|
||||
for (let x = 0; x < img.width; x += sampleSize) {
|
||||
const index = (y * img.width + x) * 4
|
||||
const r = data[index]
|
||||
const g = data[index + 1]
|
||||
const b = data[index + 2]
|
||||
const a = data[index + 3]
|
||||
|
||||
// Only create rectangles for non-transparent pixels with sufficient opacity
|
||||
if (a > 128 && (r > 0 || g > 0 || b > 0)) {
|
||||
const rect = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
rect.setAttribute('x', x.toFixed(2))
|
||||
rect.setAttribute('y', y.toFixed(2))
|
||||
rect.setAttribute('width', sampleSize.toFixed(2))
|
||||
rect.setAttribute('height', sampleSize.toFixed(2))
|
||||
rect.setAttribute('fill', `rgb(${r}, ${g}, ${b})`)
|
||||
group.appendChild(rect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Custom bitmap-to-vector conversion completed')
|
||||
resolve(group)
|
||||
})
|
||||
}
|
||||
|
||||
const exportAsSVG = async () => {
|
||||
if (!text.trim()) {
|
||||
@ -256,111 +75,45 @@ const TextAreaComponent = () => {
|
||||
return
|
||||
}
|
||||
|
||||
const svgContent = await generateSVGQRCode(text)
|
||||
// Generate base SVG QR code using utility function
|
||||
const baseSvg = await generateSVGQRCode(text, foregroundColor, backgroundColor)
|
||||
if (!baseSvg) {
|
||||
alert('Error generating QR code')
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom image if present
|
||||
const svgContent = await generateSVGQRCodeLocal(baseSvg)
|
||||
if (svgContent) {
|
||||
// Debug: Log the SVG content to see what's being generated
|
||||
console.log('Generated SVG content:', svgContent)
|
||||
|
||||
// Create download link
|
||||
const blob = new Blob([svgContent], { type: 'image/svg+xml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'qrcode.svg'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
// Use utility function to download SVG
|
||||
downloadSVG(svgContent, 'qrcode.svg')
|
||||
}
|
||||
}
|
||||
|
||||
const addImageToQRCode = async (qrCodeUrl, imageUrl) => {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
// Set canvas size
|
||||
canvas.width = 512
|
||||
canvas.height = 512
|
||||
|
||||
// Create image objects
|
||||
const qrImage = new Image()
|
||||
const customImage = new Image()
|
||||
|
||||
qrImage.onload = () => {
|
||||
// Draw QR code
|
||||
ctx.drawImage(qrImage, 0, 0, 512, 512)
|
||||
|
||||
customImage.onload = () => {
|
||||
// Calculate image size based on user preference
|
||||
const calculatedImageSize = 512 * (imageSize / 100)
|
||||
|
||||
// Calculate white box size with margin
|
||||
const margin = 16 // 8 pixel margin around the image
|
||||
const boxSize = calculatedImageSize + (margin * 2)
|
||||
const boxX = (512 - boxSize) / 2
|
||||
const boxY = (512 - boxSize) / 2
|
||||
|
||||
// Draw white background box
|
||||
ctx.fillStyle = '#FFFFFF'
|
||||
ctx.fillRect(boxX, boxY, boxSize, boxSize)
|
||||
|
||||
// Calculate image position within the white box
|
||||
const imageX = boxX + margin
|
||||
const imageY = boxY + margin
|
||||
|
||||
// Draw custom image
|
||||
ctx.drawImage(customImage, imageX, imageY, calculatedImageSize, calculatedImageSize)
|
||||
|
||||
// Add border around the white box
|
||||
//ctx.strokeStyle = '#000000'
|
||||
//ctx.lineWidth = 1
|
||||
//ctx.strokeRect(boxX, boxY, boxSize, boxSize)
|
||||
|
||||
// Convert canvas to data URL
|
||||
resolve(canvas.toDataURL('image/png'))
|
||||
}
|
||||
|
||||
// Handle SVG loading errors
|
||||
customImage.onerror = () => {
|
||||
console.error('Error loading custom image')
|
||||
resolve(qrCodeUrl) // Return QR code without custom image
|
||||
}
|
||||
|
||||
customImage.src = imageUrl
|
||||
}
|
||||
|
||||
qrImage.onerror = () => {
|
||||
console.error('Error loading QR code')
|
||||
resolve('')
|
||||
}
|
||||
|
||||
qrImage.src = qrCodeUrl
|
||||
})
|
||||
}
|
||||
|
||||
const handleImageUpload = (event) => {
|
||||
const handleImageUpload = async (event) => {
|
||||
const file = event.target.files[0]
|
||||
if (file) {
|
||||
// Validate file type - accept images and SVG
|
||||
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
|
||||
if (!validTypes.includes(file.type)) {
|
||||
alert('Please select an image file (JPEG, PNG, GIF, WebP, or SVG)')
|
||||
// Validate file using utility function
|
||||
const validation = validateImageFile(file)
|
||||
if (!validation.success) {
|
||||
alert(validation.error)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate file size (max 2MB)
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
alert('Image file size must be less than 2MB')
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
// Convert file to data URL using utility function
|
||||
const dataUrl = await fileToDataURL(file)
|
||||
setCustomImage(file)
|
||||
setCustomImageUrl(e.target.result)
|
||||
setCustomImageUrl(dataUrl)
|
||||
} catch (error) {
|
||||
console.error('Error processing image file:', error)
|
||||
alert('Error processing image file')
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
|
||||
|
366
src/utils/qrCodeUtils.js
Normal file
366
src/utils/qrCodeUtils.js
Normal file
@ -0,0 +1,366 @@
|
||||
import QRCode from 'qrcode'
|
||||
import { imageTracer } from 'imagetracer'
|
||||
|
||||
/**
|
||||
* Generate a QR code as a data URL (PNG)
|
||||
* @param {string} text - The text to encode in the QR code
|
||||
* @param {string} foregroundColor - The foreground color (default: '#000000')
|
||||
* @param {string} backgroundColor - The background color (default: '#FFFFFF')
|
||||
* @returns {Promise<string>} - Data URL of the generated QR code
|
||||
*/
|
||||
export const generateQRCode = async (text, foregroundColor = '#000000', backgroundColor = '#FFFFFF') => {
|
||||
try {
|
||||
const dataUrl = await QRCode.toDataURL(text, {
|
||||
color: {
|
||||
dark: foregroundColor,
|
||||
light: backgroundColor
|
||||
},
|
||||
width: 512,
|
||||
margin: 2,
|
||||
errorCorrectionLevel: 'H'
|
||||
})
|
||||
return dataUrl
|
||||
} catch (error) {
|
||||
console.error('Error generating QR code:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a QR code as SVG string
|
||||
* @param {string} text - The text to encode in the QR code
|
||||
* @param {string} foregroundColor - The foreground color (default: '#000000')
|
||||
* @param {string} backgroundColor - The background color (default: '#FFFFFF')
|
||||
* @returns {Promise<string>} - SVG string of the generated QR code
|
||||
*/
|
||||
export const generateSVGQRCode = async (text, foregroundColor = '#000000', backgroundColor = '#FFFFFF') => {
|
||||
try {
|
||||
const svgString = await QRCode.toString(text, {
|
||||
type: 'svg',
|
||||
width: 512,
|
||||
margin: 2,
|
||||
errorCorrectionLevel: 'H',
|
||||
color: {
|
||||
dark: foregroundColor,
|
||||
light: backgroundColor
|
||||
}
|
||||
})
|
||||
return svgString
|
||||
} catch (error) {
|
||||
console.error('Error generating SVG QR code:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom image to a QR code data URL
|
||||
* @param {string} qrCodeUrl - The QR code data URL
|
||||
* @param {string} imageUrl - The custom image data URL
|
||||
* @param {number} imageSize - Image size as percentage (0-100)
|
||||
* @returns {Promise<string>} - Data URL of QR code with embedded image
|
||||
*/
|
||||
export const addImageToQRCode = async (qrCodeUrl, imageUrl, imageSize = 20) => {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
// Set canvas size
|
||||
canvas.width = 512
|
||||
canvas.height = 512
|
||||
|
||||
// Create image objects
|
||||
const qrImage = new Image()
|
||||
const customImage = new Image()
|
||||
|
||||
qrImage.onload = () => {
|
||||
// Draw QR code
|
||||
ctx.drawImage(qrImage, 0, 0, 512, 512)
|
||||
|
||||
customImage.onload = () => {
|
||||
// Calculate image size based on user preference
|
||||
const calculatedImageSize = 512 * (imageSize / 100)
|
||||
|
||||
// Calculate white box size with margin
|
||||
const margin = 16 // 8 pixel margin around the image
|
||||
const boxSize = calculatedImageSize + (margin * 2)
|
||||
const boxX = (512 - boxSize) / 2
|
||||
const boxY = (512 - boxSize) / 2
|
||||
|
||||
// Draw white background box
|
||||
ctx.fillStyle = '#FFFFFF'
|
||||
ctx.fillRect(boxX, boxY, boxSize, boxSize)
|
||||
|
||||
// Calculate image position within the white box
|
||||
const imageX = boxX + margin
|
||||
const imageY = boxY + margin
|
||||
|
||||
// Draw custom image
|
||||
ctx.drawImage(customImage, imageX, imageY, calculatedImageSize, calculatedImageSize)
|
||||
|
||||
// Convert canvas to data URL
|
||||
resolve(canvas.toDataURL('image/png'))
|
||||
}
|
||||
|
||||
// Handle image loading errors
|
||||
customImage.onerror = () => {
|
||||
console.error('Error loading custom image')
|
||||
resolve(qrCodeUrl) // Return QR code without custom image
|
||||
}
|
||||
|
||||
customImage.src = imageUrl
|
||||
}
|
||||
|
||||
// Handle QR code loading errors
|
||||
qrImage.onerror = () => {
|
||||
console.error('Error loading QR code')
|
||||
resolve(qrCodeUrl) // Return original QR code if loading fails
|
||||
}
|
||||
|
||||
qrImage.src = qrCodeUrl
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Vectorize a bitmap image to SVG elements
|
||||
* @param {HTMLImageElement} img - The image element to vectorize
|
||||
* @param {number} targetSize - Target size in QR coordinate system
|
||||
* @param {number} x - X position in QR coordinate system
|
||||
* @param {number} y - Y position in QR coordinate system
|
||||
* @param {Document} svgDoc - The SVG document to add elements to
|
||||
* @returns {Promise<Element>} - SVG group element containing vectorized image
|
||||
*/
|
||||
export const vectorizeBitmap = (img, targetSize, x, y, svgDoc) => {
|
||||
return new Promise((resolve) => {
|
||||
// Create a canvas to get the image data
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
// Set canvas size to match the image
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
|
||||
// Draw the image to canvas
|
||||
ctx.drawImage(img, 0, 0)
|
||||
|
||||
// Get image data for vectorization
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// Create a group for the vectorized image
|
||||
const group = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
// Scale the image to fit within the target size in the QR coordinate system
|
||||
const scale = targetSize / Math.max(img.width, img.height)
|
||||
group.setAttribute('transform', `translate(${x.toFixed(2)}, ${y.toFixed(2)}) scale(${scale.toFixed(4)})`)
|
||||
|
||||
// Sample pixels and create colored rectangles
|
||||
const sampleSize = Math.max(1, Math.floor(Math.min(img.width, img.height) / 32)) // Sample every N pixels
|
||||
const data = imageData.data
|
||||
|
||||
for (let y = 0; y < img.height; y += sampleSize) {
|
||||
for (let x = 0; x < img.width; x += sampleSize) {
|
||||
const index = (y * img.width + x) * 4
|
||||
const r = data[index]
|
||||
const g = data[index + 1]
|
||||
const b = data[index + 2]
|
||||
const a = data[index + 3]
|
||||
|
||||
// Only create rectangles for non-transparent pixels with sufficient opacity
|
||||
if (a > 128 && (r > 0 || g > 0 || b > 0)) {
|
||||
const rect = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
rect.setAttribute('x', x.toFixed(2))
|
||||
rect.setAttribute('y', y.toFixed(2))
|
||||
rect.setAttribute('width', sampleSize.toFixed(2))
|
||||
rect.setAttribute('height', sampleSize.toFixed(2))
|
||||
rect.setAttribute('fill', `rgb(${r}, ${g}, ${b})`)
|
||||
group.appendChild(rect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve(group)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom image to an SVG QR code
|
||||
* @param {string} svgString - The SVG QR code string
|
||||
* @param {string} imageUrl - The custom image data URL
|
||||
* @param {number} imageSize - Image size as percentage (0-100)
|
||||
* @returns {Promise<string>} - SVG string with embedded image
|
||||
*/
|
||||
export const addImageToSVG = async (svgString, imageUrl, imageSize = 20) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
|
||||
img.onload = () => {
|
||||
// Parse the SVG string to add custom image
|
||||
const parser = new DOMParser()
|
||||
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml')
|
||||
const svgElement = svgDoc.documentElement
|
||||
|
||||
// Calculate image size and position with precise decimal coordinates
|
||||
// The QR code uses a 33x33 coordinate system, so we need to scale accordingly
|
||||
const qrSize = 33 // QR code coordinate system size
|
||||
const calculatedImageSize = qrSize * (imageSize / 100)
|
||||
const margin = 2 // Smaller margin for the 33x33 coordinate system
|
||||
const boxSize = calculatedImageSize + (margin * 2)
|
||||
const boxX = (qrSize - boxSize) / 2
|
||||
const boxY = (qrSize - boxSize) / 2
|
||||
const imageX = boxX + margin
|
||||
const imageY = boxY + margin
|
||||
|
||||
// Create white background rectangle with precise positioning
|
||||
const whiteBox = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
whiteBox.setAttribute('x', boxX.toFixed(2))
|
||||
whiteBox.setAttribute('y', boxY.toFixed(2))
|
||||
whiteBox.setAttribute('width', boxSize.toFixed(2))
|
||||
whiteBox.setAttribute('height', boxSize.toFixed(2))
|
||||
whiteBox.setAttribute('fill', '#FFFFFF')
|
||||
|
||||
// Add elements to SVG
|
||||
svgElement.appendChild(whiteBox)
|
||||
|
||||
// Handle different image types
|
||||
if (imageUrl.startsWith('data:image/svg+xml')) {
|
||||
console.log('Processing SVG image')
|
||||
// For SVG images, embed the SVG content directly with precise positioning
|
||||
const imageElement = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'image')
|
||||
imageElement.setAttribute('x', imageX.toFixed(2))
|
||||
imageElement.setAttribute('y', imageY.toFixed(2))
|
||||
imageElement.setAttribute('width', calculatedImageSize.toFixed(2))
|
||||
imageElement.setAttribute('height', calculatedImageSize.toFixed(2))
|
||||
imageElement.setAttribute('href', imageUrl)
|
||||
svgElement.appendChild(imageElement)
|
||||
console.log('SVG image element added')
|
||||
|
||||
// Convert back to string
|
||||
const serializer = new XMLSerializer()
|
||||
resolve(serializer.serializeToString(svgElement))
|
||||
} else {
|
||||
console.log('Processing bitmap image')
|
||||
// For bitmap images, convert to vector paths
|
||||
vectorizeBitmap(img, calculatedImageSize, imageX, imageY, svgDoc).then((vectorizedImage) => {
|
||||
svgElement.appendChild(vectorizedImage)
|
||||
console.log('Vectorized image group added')
|
||||
|
||||
// Convert back to string
|
||||
const serializer = new XMLSerializer()
|
||||
resolve(serializer.serializeToString(svgElement))
|
||||
}).catch((error) => {
|
||||
console.error('Vectorization failed:', error)
|
||||
// Convert back to string without the image
|
||||
const serializer = new XMLSerializer()
|
||||
resolve(serializer.serializeToString(svgElement))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
console.error('Error loading image for SVG')
|
||||
resolve(svgString) // Return SVG without image if loading fails
|
||||
}
|
||||
|
||||
img.src = imageUrl
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a complete SVG QR code with embedded image
|
||||
* @param {string} text - The text to encode in the QR code
|
||||
* @param {string} imageUrl - The custom image data URL (optional)
|
||||
* @param {number} imageSize - Image size as percentage (0-100, default: 20)
|
||||
* @param {string} foregroundColor - The foreground color (default: '#000000')
|
||||
* @param {string} backgroundColor - The background color (default: '#FFFFFF')
|
||||
* @returns {Promise<string>} - Complete SVG string with embedded image
|
||||
*/
|
||||
export const generateCompleteSVGQRCode = async (
|
||||
text,
|
||||
imageUrl = null,
|
||||
imageSize = 20,
|
||||
foregroundColor = '#000000',
|
||||
backgroundColor = '#FFFFFF'
|
||||
) => {
|
||||
try {
|
||||
// Generate base SVG QR code
|
||||
const svgString = await generateSVGQRCode(text, foregroundColor, backgroundColor)
|
||||
|
||||
if (!svgString) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Add custom image if provided
|
||||
if (imageUrl) {
|
||||
console.log('Adding custom image to SVG...')
|
||||
const result = await addImageToSVG(svgString, imageUrl, imageSize)
|
||||
console.log('SVG with image generated')
|
||||
return result
|
||||
}
|
||||
|
||||
return svgString
|
||||
} catch (error) {
|
||||
console.error('Error generating complete SVG QR code:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an SVG string as a file
|
||||
* @param {string} svgContent - The SVG content to download
|
||||
* @param {string} filename - The filename for the download (default: 'qrcode.svg')
|
||||
*/
|
||||
export const downloadSVG = (svgContent, filename = 'qrcode.svg') => {
|
||||
if (!svgContent) {
|
||||
console.error('No SVG content to download')
|
||||
return
|
||||
}
|
||||
|
||||
// Create download link
|
||||
const blob = new Blob([svgContent], { type: 'image/svg+xml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a file to data URL
|
||||
* @param {File} file - The file to convert
|
||||
* @returns {Promise<string>} - Data URL of the file
|
||||
*/
|
||||
export const fileToDataURL = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => resolve(reader.result)
|
||||
reader.onerror = reject
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate image file
|
||||
* @param {File} file - The file to validate
|
||||
* @param {number} maxSize - Maximum file size in bytes (default: 2MB)
|
||||
* @returns {Object} - Validation result with success boolean and error message
|
||||
*/
|
||||
export const validateImageFile = (file, maxSize = 2 * 1024 * 1024) => {
|
||||
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
|
||||
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Please select a valid image file (JPEG, PNG, GIF, WebP, or SVG)'
|
||||
}
|
||||
}
|
||||
|
||||
if (file.size > maxSize) {
|
||||
return {
|
||||
success: false,
|
||||
error: `File size must be less than ${Math.round(maxSize / 1024 / 1024)}MB`
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
}
|
@ -1,7 +1,38 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { resolve } from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
// Create standalone bundle for qrCodeUtils with dependencies
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/utils/qrCodeUtils.js'),
|
||||
name: 'QRCodeUtils',
|
||||
formats: ['es', 'umd', 'iife'],
|
||||
fileName: (format) => `qr-code-utils.${format}.js`
|
||||
},
|
||||
rollupOptions: {
|
||||
// Include all dependencies in the bundle
|
||||
external: [],
|
||||
output: {
|
||||
// Global variable name for UMD/IIFE
|
||||
globals: {},
|
||||
// Bundle all dependencies
|
||||
inlineDynamicImports: true
|
||||
}
|
||||
},
|
||||
// Optimize the bundle
|
||||
minify: 'terser',
|
||||
sourcemap: true,
|
||||
// Ensure all dependencies are included
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/]
|
||||
}
|
||||
},
|
||||
// Optimize dependencies
|
||||
optimizeDeps: {
|
||||
include: ['qrcode', 'imagetracer']
|
||||
}
|
||||
})
|
44
vite.standalone.config.js
Normal file
44
vite.standalone.config.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { resolve } from 'path'
|
||||
|
||||
// Standalone bundle configuration for qrCodeUtils
|
||||
export default defineConfig({
|
||||
build: {
|
||||
// Create standalone bundle for qrCodeUtils with dependencies
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/utils/qrCodeUtils.js'),
|
||||
name: 'QRCodeUtils',
|
||||
formats: ['es', 'umd', 'iife'],
|
||||
fileName: (format) => `qr-code-utils.${format}.js`
|
||||
},
|
||||
rollupOptions: {
|
||||
// Include all dependencies in the bundle
|
||||
external: [],
|
||||
output: {
|
||||
// Global variable name for UMD/IIFE
|
||||
globals: {},
|
||||
// Bundle all dependencies
|
||||
inlineDynamicImports: true,
|
||||
// Optimize chunk splitting
|
||||
manualChunks: undefined
|
||||
}
|
||||
},
|
||||
// Optimize the bundle
|
||||
minify: 'terser',
|
||||
sourcemap: true,
|
||||
// Ensure all dependencies are included
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/]
|
||||
},
|
||||
// Target modern browsers for smaller bundle
|
||||
target: 'es2018'
|
||||
},
|
||||
// Optimize dependencies
|
||||
optimizeDeps: {
|
||||
include: ['qrcode', 'imagetracer']
|
||||
},
|
||||
// Define environment
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user