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
JavaScript
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