UNPKG

code-craft-studio

Version:

A comprehensive QR code and barcode scanning/generation library for React. Works with or without Capacitor. Supports 22+ QR data types and 14+ barcode formats (EAN, UPC, Code 128, etc.), with customizable designs, analytics, and React components. Provider

1,028 lines 46.1 kB
import { WebPlugin } from '@capacitor/core'; import QRCode from 'qrcode'; import QrScanner from 'qr-scanner'; import { BrowserMultiFormatReader, DecodeHintType, BarcodeFormat as ZXingFormat } from '@zxing/library'; import JsBarcode from 'jsbarcode'; import { QRType, BarcodeFormat } from './definitions'; export class QRCodeStudioWeb extends WebPlugin { constructor() { super(...arguments); this.scanner = null; this.barcodeReader = null; this.videoElement = null; this.scanListeners = []; this.errorListeners = []; this.torchListeners = []; this.history = []; this.currentStream = null; // private torchEnabled: boolean = false; // Not used in web implementation this.scanInterval = null; } async checkPermissions() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { return { camera: 'denied' }; } try { const permissionStatus = await navigator.permissions.query({ name: 'camera' }); return { camera: permissionStatus.state === 'granted' ? 'granted' : permissionStatus.state === 'denied' ? 'denied' : 'prompt' }; } catch (_a) { // Fallback for browsers that don't support permissions API try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); stream.getTracks().forEach(track => track.stop()); return { camera: 'granted' }; } catch (_b) { return { camera: 'prompt' }; } } } async requestPermissions() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); stream.getTracks().forEach(track => track.stop()); return { camera: 'granted' }; } catch (_a) { return { camera: 'denied' }; } } async startScan(options) { try { // Create video element if not exists if (!this.videoElement) { this.videoElement = document.createElement('video'); // Apply custom video styles or defaults const videoStyle = (options === null || options === void 0 ? void 0 : options.videoStyle) || {}; this.videoElement.style.position = videoStyle.position || 'fixed'; this.videoElement.style.top = videoStyle.top || '0'; this.videoElement.style.left = videoStyle.left || '0'; this.videoElement.style.width = videoStyle.width || '100%'; this.videoElement.style.height = videoStyle.height || '100%'; this.videoElement.style.objectFit = videoStyle.objectFit || 'cover'; this.videoElement.style.zIndex = videoStyle.zIndex || '999999'; document.body.appendChild(this.videoElement); } // Determine which formats to scan const requestedFormats = (options === null || options === void 0 ? void 0 : options.targetedFormats) || (options === null || options === void 0 ? void 0 : options.formats) || Object.values(BarcodeFormat); const hasOnlyQR = requestedFormats.length === 1 && requestedFormats[0] === BarcodeFormat.QR_CODE; if (hasOnlyQR) { // Use QrScanner for QR-only scanning (better performance) this.scanner = new QrScanner(this.videoElement, (result) => { const scanResult = { content: result.data, format: BarcodeFormat.QR_CODE, timestamp: Date.now(), type: this.detectQRType(result.data), parsedData: this.parseQRData(result.data), cornerPoints: result.cornerPoints, }; this.scanListeners.forEach(listener => listener(scanResult)); }, { preferredCamera: (options === null || options === void 0 ? void 0 : options.camera) || 'environment', maxScansPerSecond: (options === null || options === void 0 ? void 0 : options.maxScansPerSecond) !== undefined ? options.maxScansPerSecond : ((options === null || options === void 0 ? void 0 : options.scanDelay) ? 1000 / options.scanDelay : 5), calculateScanRegion: options === null || options === void 0 ? void 0 : options.calculateScanRegion, overlay: options === null || options === void 0 ? void 0 : options.overlay, highlightCodeOutline: (options === null || options === void 0 ? void 0 : options.highlightCodeOutline) !== false, highlightScanRegion: (options === null || options === void 0 ? void 0 : options.highlightScanRegion) !== false, }); await this.scanner.start(); // Enable torch if requested if ((options === null || options === void 0 ? void 0 : options.torch) && await this.scanner.hasFlash()) { await this.scanner.turnFlashOn(); // this.torchEnabled = true; } } else { // Use ZXing for multi-format scanning this.barcodeReader = new BrowserMultiFormatReader(); // Configure hints for better performance const hints = new Map(); if ((options === null || options === void 0 ? void 0 : options.targetedFormats) || (options === null || options === void 0 ? void 0 : options.formats)) { const zxingFormats = this.mapToZXingFormats(requestedFormats); hints.set(DecodeHintType.POSSIBLE_FORMATS, zxingFormats); } this.barcodeReader.hints = hints; // Start video stream const constraints = { video: { facingMode: (options === null || options === void 0 ? void 0 : options.camera) === 'front' ? 'user' : 'environment', } }; this.currentStream = await navigator.mediaDevices.getUserMedia(constraints); this.videoElement.srcObject = this.currentStream; await this.videoElement.play(); // Enable torch if requested if (options === null || options === void 0 ? void 0 : options.torch) { await this.enableTorch(); } // Start continuous scanning const scanDelay = (options === null || options === void 0 ? void 0 : options.scanDelay) || (1000 / ((options === null || options === void 0 ? void 0 : options.maxScansPerSecond) || 5)); this.scanInterval = setInterval(async () => { try { const result = await this.barcodeReader.decodeFromVideoElement(this.videoElement); if (result) { const scanResult = { content: result.getText(), format: this.mapFromZXingFormat(result.getBarcodeFormat()), timestamp: Date.now(), type: this.detectQRType(result.getText()), parsedData: this.parseQRData(result.getText()), productInfo: await this.getProductInfo(result.getText(), this.mapFromZXingFormat(result.getBarcodeFormat())), }; if (!(options === null || options === void 0 ? void 0 : options.multiple)) { clearInterval(this.scanInterval); } this.scanListeners.forEach(listener => listener(scanResult)); } } catch (_a) { // Ignore decode errors (no barcode found) } }, scanDelay); } } catch (error) { const scanError = { code: 'SCAN_ERROR', message: error instanceof Error ? error.message : 'Failed to start scanner', }; this.errorListeners.forEach(listener => listener(scanError)); throw error; } } async stopScan() { if (this.scanner) { await this.scanner.stop(); this.scanner.destroy(); this.scanner = null; } if (this.barcodeReader) { this.barcodeReader.reset(); this.barcodeReader = null; } if (this.scanInterval) { clearInterval(this.scanInterval); this.scanInterval = null; } if (this.currentStream) { this.currentStream.getTracks().forEach(track => track.stop()); this.currentStream = null; } if (this.videoElement) { this.videoElement.remove(); this.videoElement = null; } // this.torchEnabled = false; } async generate(options) { var _a, _b, _c, _d, _e; try { const qrData = this.formatQRData(options.type, options.data); const size = options.size || 300; // Generate QR code options with all user-provided options const qrOptions = { // Use user-provided width or size, with size as fallback width: options.width || size, // Use user-provided margin or design margin, with default of 4 margin: options.margin !== undefined ? options.margin : (((_a = options.design) === null || _a === void 0 ? void 0 : _a.margin) || 4), // Use user-provided scale if width is not specified scale: options.width ? undefined : (options.scale || 4), color: { dark: ((_c = (_b = options.design) === null || _b === void 0 ? void 0 : _b.colors) === null || _c === void 0 ? void 0 : _c.dark) || '#000000', light: ((_e = (_d = options.design) === null || _d === void 0 ? void 0 : _d.colors) === null || _e === void 0 ? void 0 : _e.light) || '#FFFFFF', }, errorCorrectionLevel: options.errorCorrectionLevel || 'M', // Pass through additional options version: options.version, maskPattern: options.maskPattern, toSJISFunc: options.toSJISFunc, }; // Generate base64 data URL const dataUrl = await QRCode.toDataURL(qrData, qrOptions); // Generate SVG with same options const svg = await QRCode.toString(qrData, Object.assign(Object.assign({}, qrOptions), { type: 'svg' })); // Generate unique ID const id = this.generateId(); // Store in history const historyItem = { id, type: options.type, data: options.data, createdAt: Date.now(), qrCode: { id, dataUrl, svg }, scanCount: 0, isFavorite: false, }; this.history.push(historyItem); this.saveHistory(); return { id, dataUrl, svg, base64: dataUrl.split(',')[1], }; } catch (error) { throw new Error(`Failed to generate QR code: ${error}`); } } async saveQRCode(options) { try { const { qrCode, fileName = 'qrcode', format = 'png' } = options; if (!qrCode.dataUrl && !qrCode.svg) { throw new Error('No QR code data to save'); } // Create download link const link = document.createElement('a'); link.download = `${fileName}.${format}`; switch (format) { case 'svg': if (qrCode.svg) { const blob = new Blob([qrCode.svg], { type: 'image/svg+xml' }); link.href = URL.createObjectURL(blob); } else { throw new Error('SVG data not available'); } break; case 'json': // Export QR code data as JSON const jsonData = { id: qrCode.id, dataUrl: qrCode.dataUrl, svg: qrCode.svg, timestamp: Date.now() }; const jsonBlob = new Blob([JSON.stringify(jsonData, null, 2)], { type: 'application/json' }); link.href = URL.createObjectURL(jsonBlob); break; case 'jpg': // Convert PNG to JPG using canvas if (qrCode.dataUrl) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = qrCode.dataUrl; }); canvas.width = img.width; canvas.height = img.height; ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); link.href = canvas.toDataURL('image/jpeg', 0.95); } break; case 'webp': // Convert to WebP if supported if (qrCode.dataUrl) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = qrCode.dataUrl; }); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); link.href = canvas.toDataURL('image/webp', 0.95); } break; case 'pdf': // For PDF, we would need a library like jsPDF // For now, throw an informative error throw new Error('PDF export requires additional libraries. Please use PNG or SVG format.'); case 'gif': case 'eps': case 'wmf': // These formats require specialized libraries throw new Error(`${format.toUpperCase()} export is not yet implemented. Please use PNG, JPG, or SVG format.`); case 'png': default: if (qrCode.dataUrl) { link.href = qrCode.dataUrl; } else { throw new Error('PNG data not available'); } break; } // Trigger download document.body.appendChild(link); link.click(); document.body.removeChild(link); return { uri: link.href, path: `${fileName}.${format}`, }; } catch (error) { throw new Error(`Failed to save QR code: ${error}`); } } async getHistory(options) { this.loadHistory(); let filteredHistory = [...this.history]; // Apply filters if (options === null || options === void 0 ? void 0 : options.type) { filteredHistory = filteredHistory.filter(item => item.type === options.type); } if (options === null || options === void 0 ? void 0 : options.dateRange) { const start = options.dateRange.start.getTime(); const end = options.dateRange.end.getTime(); filteredHistory = filteredHistory.filter(item => item.createdAt >= start && item.createdAt <= end); } // Sort by date (newest first) filteredHistory.sort((a, b) => b.createdAt - a.createdAt); // Apply pagination const offset = (options === null || options === void 0 ? void 0 : options.offset) || 0; const limit = (options === null || options === void 0 ? void 0 : options.limit) || 50; const paginatedHistory = filteredHistory.slice(offset, offset + limit); return { items: paginatedHistory, total: filteredHistory.length, }; } async clearHistory() { this.history = []; localStorage.removeItem('qrcode_studio_history'); } async getAnalytics(options) { // Web implementation would need a backend service for real analytics // This is a mock implementation const qrCode = this.history.find(item => item.id === options.qrCodeId); if (!qrCode) { throw new Error('QR code not found'); } return { totalScans: qrCode.scanCount || 0, uniqueScans: Math.floor((qrCode.scanCount || 0) * 0.7), locations: [ { country: 'United States', region: 'California', city: 'San Francisco', count: 45 }, { country: 'United Kingdom', region: 'England', city: 'London', count: 23 }, ], devices: [ { type: 'mobile', os: 'iOS', count: 42 }, { type: 'mobile', os: 'Android', count: 26 }, ], timeDistribution: this.generateMockTimeDistribution(), }; } async scan(options) { return new Promise((resolve, reject) => { const handleScan = (result) => { this.stopScan(); resolve(result); }; const handleError = (error) => { this.stopScan(); reject(new Error(error.message)); }; this.addListener('scanResult', handleScan); this.addListener('scanError', handleError); this.startScan(options).catch(reject); }); } async readBarcodesFromImage(options) { try { const reader = new BrowserMultiFormatReader(); // Configure formats if specified if (options.formats) { const hints = new Map(); hints.set(DecodeHintType.POSSIBLE_FORMATS, this.mapToZXingFormats(options.formats)); reader.hints = hints; } let imageElement; if (options.path) { // Load from path imageElement = new Image(); await new Promise((resolve, reject) => { imageElement.onload = resolve; imageElement.onerror = reject; imageElement.src = options.path; }); } else if (options.base64) { // Load from base64 imageElement = new Image(); await new Promise((resolve, reject) => { imageElement.onload = resolve; imageElement.onerror = reject; imageElement.src = `data:image/png;base64,${options.base64}`; }); } else { throw new Error('Either path or base64 must be provided'); } const result = await reader.decodeFromImageElement(imageElement); return [{ content: result.getText(), format: this.mapFromZXingFormat(result.getBarcodeFormat()), timestamp: Date.now(), type: this.detectQRType(result.getText()), parsedData: this.parseQRData(result.getText()), productInfo: await this.getProductInfo(result.getText(), this.mapFromZXingFormat(result.getBarcodeFormat())), }]; } catch (error) { throw new Error(`Failed to read barcode from image: ${error}`); } } async getSupportedFormats() { // Web platform supports all formats through ZXing return Object.values(BarcodeFormat); } async enableTorch() { try { if (this.scanner && await this.scanner.hasFlash()) { await this.scanner.turnFlashOn(); // this.torchEnabled = true; this.torchListeners.forEach(listener => listener({ isEnabled: true })); } else if (this.currentStream) { const videoTrack = this.currentStream.getVideoTracks()[0]; const capabilities = videoTrack.getCapabilities(); if (capabilities.torch) { await videoTrack.applyConstraints({ advanced: [{ torch: true }], }); // this.torchEnabled = true; this.torchListeners.forEach(listener => listener({ isEnabled: true })); } else { throw new Error('Torch not available on this device'); } } } catch (error) { throw new Error(`Failed to enable torch: ${error}`); } } async disableTorch() { try { if (this.scanner && await this.scanner.hasFlash()) { await this.scanner.turnFlashOff(); // this.torchEnabled = false; this.torchListeners.forEach(listener => listener({ isEnabled: false })); } else if (this.currentStream) { const videoTrack = this.currentStream.getVideoTracks()[0]; const capabilities = videoTrack.getCapabilities(); if (capabilities.torch) { await videoTrack.applyConstraints({ advanced: [{ torch: false }], }); // this.torchEnabled = false; this.torchListeners.forEach(listener => listener({ isEnabled: false })); } } } catch (error) { throw new Error(`Failed to disable torch: ${error}`); } } async isTorchAvailable() { try { if (this.scanner) { return { available: await this.scanner.hasFlash() }; } const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); const videoTrack = stream.getVideoTracks()[0]; const capabilities = videoTrack.getCapabilities(); stream.getTracks().forEach(track => track.stop()); return { available: !!capabilities.torch }; } catch (_a) { return { available: false }; } } async setZoomRatio(options) { try { if (this.currentStream) { const videoTrack = this.currentStream.getVideoTracks()[0]; const capabilities = videoTrack.getCapabilities(); if (capabilities.zoom) { const minZoom = capabilities.zoom.min || 1; const maxZoom = capabilities.zoom.max || 1; const zoom = Math.min(maxZoom, Math.max(minZoom, options.ratio)); await videoTrack.applyConstraints({ advanced: [{ zoom }], }); } else { throw new Error('Zoom not available on this device'); } } else { throw new Error('No active video stream'); } } catch (error) { throw new Error(`Failed to set zoom: ${error}`); } } async generateBarcode(options) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; try { // Create canvas element const canvas = document.createElement('canvas'); // Generate barcode JsBarcode(canvas, options.data, { format: this.mapToJSBarcodeFormat(options.format), width: options.width ? options.width / 100 : 2, height: options.height || 100, displayValue: options.displayText !== false, text: options.text || options.data, textAlign: ((_a = options.fontOptions) === null || _a === void 0 ? void 0 : _a.textAlign) || 'center', textPosition: ((_b = options.fontOptions) === null || _b === void 0 ? void 0 : _b.textPosition) || 'bottom', textMargin: ((_c = options.fontOptions) === null || _c === void 0 ? void 0 : _c.textMargin) || 2, fontSize: ((_d = options.fontOptions) === null || _d === void 0 ? void 0 : _d.size) || 20, font: ((_e = options.fontOptions) === null || _e === void 0 ? void 0 : _e.font) || 'monospace', background: ((_f = options.colors) === null || _f === void 0 ? void 0 : _f.background) || '#FFFFFF', lineColor: ((_g = options.colors) === null || _g === void 0 ? void 0 : _g.lineColor) || '#000000', margin: options.margins ? Math.min(options.margins.top || 10, options.margins.right || 10, options.margins.bottom || 10, options.margins.left || 10) : 10, }); // Convert to requested format let result = { format: options.format, data: options.data, }; switch (options.outputFormat) { case 'svg': // For SVG, we need to regenerate using SVG element const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); JsBarcode(svgElement, options.data, { format: this.mapToJSBarcodeFormat(options.format), width: options.width ? options.width / 100 : 2, height: options.height || 100, displayValue: options.displayText !== false, text: options.text || options.data, textAlign: ((_h = options.fontOptions) === null || _h === void 0 ? void 0 : _h.textAlign) || 'center', textPosition: ((_j = options.fontOptions) === null || _j === void 0 ? void 0 : _j.textPosition) || 'bottom', textMargin: ((_k = options.fontOptions) === null || _k === void 0 ? void 0 : _k.textMargin) || 2, fontSize: ((_l = options.fontOptions) === null || _l === void 0 ? void 0 : _l.size) || 20, font: ((_m = options.fontOptions) === null || _m === void 0 ? void 0 : _m.font) || 'monospace', background: ((_o = options.colors) === null || _o === void 0 ? void 0 : _o.background) || '#FFFFFF', lineColor: ((_p = options.colors) === null || _p === void 0 ? void 0 : _p.lineColor) || '#000000', margin: options.margins ? Math.min(options.margins.top || 10, options.margins.right || 10, options.margins.bottom || 10, options.margins.left || 10) : 10, }); result.svg = new XMLSerializer().serializeToString(svgElement); break; case 'jpg': result.dataUrl = canvas.toDataURL('image/jpeg', 0.95); result.base64 = result.dataUrl.split(',')[1]; break; case 'png': default: result.dataUrl = canvas.toDataURL('image/png'); result.base64 = result.dataUrl.split(',')[1]; break; } return result; } catch (error) { throw new Error(`Failed to generate barcode: ${error}`); } } async addListener(eventName, listenerFunc) { if (eventName === 'scanResult') { this.scanListeners.push(listenerFunc); } else if (eventName === 'scanError') { this.errorListeners.push(listenerFunc); } else if (eventName === 'torchStateChanged') { this.torchListeners.push(listenerFunc); } return { remove: async () => { if (eventName === 'scanResult') { this.scanListeners = this.scanListeners.filter(l => l !== listenerFunc); } else if (eventName === 'scanError') { this.errorListeners = this.errorListeners.filter(l => l !== listenerFunc); } else if (eventName === 'torchStateChanged') { this.torchListeners = this.torchListeners.filter(l => l !== listenerFunc); } }, }; } async removeAllListeners() { this.scanListeners = []; this.errorListeners = []; this.torchListeners = []; } // Helper methods detectQRType(content) { // URL patterns if (/^https?:\/\//i.test(content)) { if (content.includes('youtube.com') || content.includes('youtu.be')) { return QRType.VIDEO; } if (content.includes('facebook.com')) { return QRType.FACEBOOK; } if (content.includes('instagram.com')) { return QRType.INSTAGRAM; } if (content.includes('.pdf')) { return QRType.PDF; } return QRType.WEBSITE; } // WiFi pattern if (/^WIFI:/i.test(content)) { return QRType.WIFI; } // vCard pattern if (/^BEGIN:VCARD/i.test(content)) { return QRType.VCARD; } // Email pattern if (/^mailto:/i.test(content)) { return QRType.EMAIL; } // Phone pattern if (/^tel:/i.test(content)) { return QRType.PHONE; } // SMS pattern if (/^sms:/i.test(content)) { return QRType.SMS; } // WhatsApp pattern if (/^https:\/\/wa\.me\//i.test(content)) { return QRType.WHATSAPP; } // Location pattern if (/^geo:/i.test(content)) { return QRType.LOCATION; } // Event pattern if (/^BEGIN:VEVENT/i.test(content)) { return QRType.EVENT; } // Default to text return QRType.TEXT; } parseQRData(content) { const type = this.detectQRType(content); switch (type) { case QRType.WEBSITE: case QRType.PDF: case QRType.VIDEO: return { url: content }; case QRType.WIFI: const wifiMatch = content.match(/WIFI:T:([^;]+);S:([^;]+);P:([^;]+);/); if (wifiMatch) { return { security: wifiMatch[1], ssid: wifiMatch[2], password: wifiMatch[3], }; } break; case QRType.EMAIL: const emailMatch = content.match(/mailto:([^?]+)\??(.*)$/); if (emailMatch) { const params = new URLSearchParams(emailMatch[2]); return { to: emailMatch[1], subject: params.get('subject') || undefined, body: params.get('body') || undefined, }; } break; case QRType.PHONE: return { phoneNumber: content.replace('tel:', '') }; case QRType.SMS: const smsMatch = content.match(/sms:([^?]+)\??(.*)$/); if (smsMatch) { const params = new URLSearchParams(smsMatch[2]); return { phoneNumber: smsMatch[1], message: params.get('body') || undefined, }; } break; case QRType.TEXT: default: return { text: content }; } return { text: content }; } formatQRData(type, data) { switch (type) { case QRType.WEBSITE: case QRType.PDF: case QRType.VIDEO: return data.url; case QRType.WIFI: return `WIFI:T:${data.security};S:${data.ssid};P:${data.password || ''};H:${data.hidden ? 'true' : 'false'};`; case QRType.VCARD: return this.generateVCard(data); case QRType.EMAIL: let emailUrl = `mailto:${data.to}`; const emailParams = new URLSearchParams(); if (data.subject) emailParams.append('subject', data.subject); if (data.body) emailParams.append('body', data.body); if (data.cc) emailParams.append('cc', data.cc); if (data.bcc) emailParams.append('bcc', data.bcc); if (emailParams.toString()) emailUrl += `?${emailParams.toString()}`; return emailUrl; case QRType.PHONE: return `tel:${data.phoneNumber}`; case QRType.SMS: let smsUrl = `sms:${data.phoneNumber}`; if (data.message) smsUrl += `?body=${encodeURIComponent(data.message)}`; return smsUrl; case QRType.WHATSAPP: let whatsappUrl = `https://wa.me/${data.phoneNumber.replace(/[^0-9]/g, '')}`; if (data.message) whatsappUrl += `?text=${encodeURIComponent(data.message)}`; return whatsappUrl; case QRType.LOCATION: return `geo:${data.latitude},${data.longitude}`; case QRType.EVENT: return this.generateVEvent(data); case QRType.FACEBOOK: return data.pageUrl; case QRType.INSTAGRAM: return data.profileUrl; case QRType.IMAGES: // For images, create a landing page URL or JSON representation return JSON.stringify({ type: 'images', title: data.title, description: data.description, images: data.images }); case QRType.MENU: // For menu, create a structured JSON representation return JSON.stringify({ type: 'menu', restaurant: data.restaurantName, currency: data.currency || 'USD', categories: data.categories }); case QRType.BUSINESS: // Business card format const businessLines = ['BEGIN:BUSINESS']; if (data.name) businessLines.push(`NAME:${data.name}`); if (data.industry) businessLines.push(`INDUSTRY:${data.industry}`); if (data.phone) businessLines.push(`PHONE:${data.phone}`); if (data.email) businessLines.push(`EMAIL:${data.email}`); if (data.website) businessLines.push(`URL:${data.website}`); if (data.address) businessLines.push(`ADDRESS:${data.address}`); if (data.hours) businessLines.push(`HOURS:${data.hours}`); if (data.description) businessLines.push(`DESC:${data.description}`); businessLines.push('END:BUSINESS'); return businessLines.join('\n'); case QRType.MP3: // For MP3, create a structured representation return JSON.stringify({ type: 'mp3', url: data.url, title: data.title, artist: data.artist, album: data.album, coverArt: data.coverArt }); case QRType.APPS: // For apps, create a multi-platform URL or JSON if (data.customUrl) return data.customUrl; if (data.appStoreUrl) return data.appStoreUrl; if (data.playStoreUrl) return data.playStoreUrl; if (data.windowsStoreUrl) return data.windowsStoreUrl; return JSON.stringify({ type: 'apps', appName: data.appName, description: data.description, stores: { appStore: data.appStoreUrl, playStore: data.playStoreUrl, windowsStore: data.windowsStoreUrl } }); case QRType.LINKS_LIST: // For links list, create a JSON representation return JSON.stringify({ type: 'links', title: data.title, links: data.links }); case QRType.COUPON: // For coupon, create a structured format return JSON.stringify({ type: 'coupon', code: data.code, description: data.description, discount: data.discount, validUntil: data.validUntil, terms: data.terms }); case QRType.SOCIAL_MEDIA: // For social media, create a multi-link format const socialLinks = Object.entries(data) .filter(([key, value]) => value && key !== 'type') .map(([network, url]) => `${network}: ${url}`) .join('\n'); return socialLinks || JSON.stringify(data); case QRType.TEXT: default: return data.text || JSON.stringify(data); } } generateVCard(data) { const vcard = [ 'BEGIN:VCARD', 'VERSION:3.0', ]; if (data.firstName || data.lastName) { vcard.push(`FN:${data.firstName || ''} ${data.lastName || ''}`.trim()); vcard.push(`N:${data.lastName || ''};${data.firstName || ''};;;`); } if (data.organization) vcard.push(`ORG:${data.organization}`); if (data.title) vcard.push(`TITLE:${data.title}`); if (data.phone) vcard.push(`TEL:${data.phone}`); if (data.mobile) vcard.push(`TEL;TYPE=CELL:${data.mobile}`); if (data.email) vcard.push(`EMAIL:${data.email}`); if (data.website) vcard.push(`URL:${data.website}`); if (data.address) vcard.push(`ADR:;;${data.address};;;;`); if (data.note) vcard.push(`NOTE:${data.note}`); vcard.push('END:VCARD'); return vcard.join('\n'); } generateVEvent(data) { const vevent = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Code Craft Studio//EN', 'BEGIN:VEVENT', ]; const dtstart = new Date(data.startDate).toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, ''); vevent.push(`DTSTART:${dtstart}`); if (data.endDate) { const dtend = new Date(data.endDate).toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, ''); vevent.push(`DTEND:${dtend}`); } vevent.push(`SUMMARY:${data.title}`); if (data.location) vevent.push(`LOCATION:${data.location}`); if (data.description) vevent.push(`DESCRIPTION:${data.description}`); if (data.url) vevent.push(`URL:${data.url}`); vevent.push('END:VEVENT'); vevent.push('END:VCALENDAR'); return vevent.join('\n'); } generateId() { return `qr_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } saveHistory() { localStorage.setItem('qrcode_studio_history', JSON.stringify(this.history)); } loadHistory() { const saved = localStorage.getItem('qrcode_studio_history'); if (saved) { try { this.history = JSON.parse(saved); } catch (_a) { this.history = []; } } } generateMockTimeDistribution() { const distribution = []; const today = new Date(); for (let i = 6; i >= 0; i--) { const date = new Date(today); date.setDate(date.getDate() - i); distribution.push({ date: date.toISOString().split('T')[0], count: Math.floor(Math.random() * 20) + 5, }); } return distribution; } mapToZXingFormats(formats) { return formats.map(format => { switch (format) { case BarcodeFormat.QR_CODE: return ZXingFormat.QR_CODE; case BarcodeFormat.DATA_MATRIX: return ZXingFormat.DATA_MATRIX; case BarcodeFormat.AZTEC: return ZXingFormat.AZTEC; case BarcodeFormat.PDF_417: return ZXingFormat.PDF_417; case BarcodeFormat.MAXICODE: return ZXingFormat.MAXICODE; case BarcodeFormat.EAN_13: return ZXingFormat.EAN_13; case BarcodeFormat.EAN_8: return ZXingFormat.EAN_8; case BarcodeFormat.UPC_A: return ZXingFormat.UPC_A; case BarcodeFormat.UPC_E: return ZXingFormat.UPC_E; case BarcodeFormat.CODE_128: return ZXingFormat.CODE_128; case BarcodeFormat.CODE_39: return ZXingFormat.CODE_39; case BarcodeFormat.CODE_93: return ZXingFormat.CODE_93; case BarcodeFormat.CODABAR: return ZXingFormat.CODABAR; case BarcodeFormat.ITF: return ZXingFormat.ITF; case BarcodeFormat.RSS_14: return ZXingFormat.RSS_14; case BarcodeFormat.RSS_EXPANDED: return ZXingFormat.RSS_EXPANDED; default: return ZXingFormat.QR_CODE; } }); } mapFromZXingFormat(format) { switch (format) { case ZXingFormat.QR_CODE: return BarcodeFormat.QR_CODE; case ZXingFormat.DATA_MATRIX: return BarcodeFormat.DATA_MATRIX; case ZXingFormat.AZTEC: return BarcodeFormat.AZTEC; case ZXingFormat.PDF_417: return BarcodeFormat.PDF_417; case ZXingFormat.MAXICODE: return BarcodeFormat.MAXICODE; case ZXingFormat.EAN_13: return BarcodeFormat.EAN_13; case ZXingFormat.EAN_8: return BarcodeFormat.EAN_8; case ZXingFormat.UPC_A: return BarcodeFormat.UPC_A; case ZXingFormat.UPC_E: return BarcodeFormat.UPC_E; case ZXingFormat.CODE_128: return BarcodeFormat.CODE_128; case ZXingFormat.CODE_39: return BarcodeFormat.CODE_39; case ZXingFormat.CODE_93: return BarcodeFormat.CODE_93; case ZXingFormat.CODABAR: return BarcodeFormat.CODABAR; case ZXingFormat.ITF: return BarcodeFormat.ITF; case ZXingFormat.RSS_14: return BarcodeFormat.RSS_14; case ZXingFormat.RSS_EXPANDED: return BarcodeFormat.RSS_EXPANDED; default: return BarcodeFormat.QR_CODE; } } mapToJSBarcodeFormat(format) { switch (format) { case BarcodeFormat.CODE_128: return 'CODE128'; case BarcodeFormat.CODE_39: return 'CODE39'; case BarcodeFormat.EAN_13: return 'EAN13'; case BarcodeFormat.EAN_8: return 'EAN8'; case BarcodeFormat.UPC_A: return 'UPC'; case BarcodeFormat.UPC_E: return 'UPC_E'; case BarcodeFormat.ITF: return 'ITF'; case BarcodeFormat.ITF_14: return 'ITF14'; case BarcodeFormat.MSI: return 'MSI'; case BarcodeFormat.MSI_PLESSEY: return 'MSI'; case BarcodeFormat.PHARMACODE: return 'pharmacode'; case BarcodeFormat.CODABAR: return 'codabar'; default: throw new Error(`Unsupported barcode format for generation: ${format}`); } } async getProductInfo(content, format) { // Only process product codes if (![BarcodeFormat.UPC_A, BarcodeFormat.UPC_E, BarcodeFormat.EAN_13, BarcodeFormat.EAN_8].includes(format)) { return undefined; } // In a real implementation, this would call a product API service // For now, return mock data for demonstration return { name: 'Sample Product', brand: 'Sample Brand', category: 'General', description: 'This is a sample product description', metadata: { barcode: content, format: format, } }; } } //# sourceMappingURL=web.js.map