fix the width scaling error, fix the font scaling, removing debug logging
This commit is contained in:
parent
9483f369d7
commit
54729b1a51
@ -70,22 +70,22 @@ export class TimeoutError extends Error {
|
|||||||
export class BluetoothPrinterManager {
|
export class BluetoothPrinterManager {
|
||||||
constructor(errorNotificationService = null) {
|
constructor(errorNotificationService = null) {
|
||||||
// Debug logging
|
// Debug logging
|
||||||
this.debugMode = true; // Set to false to disable verbose logging
|
this.debugMode = false; // Set to false to disable verbose logging
|
||||||
|
|
||||||
// Connection state
|
// Connection state
|
||||||
this.device = null;
|
this.device = null;
|
||||||
this.server = null;
|
this.server = null;
|
||||||
this.service = null;
|
this.service = null;
|
||||||
this.characteristic = null;
|
this.characteristic = null;
|
||||||
this.connectionStatus = 'disconnected';
|
this.connectionStatus = 'disconnected';
|
||||||
|
|
||||||
// Reconnection state
|
// Reconnection state
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.maxReconnectAttempts = 3;
|
this.maxReconnectAttempts = 3;
|
||||||
this.reconnectDelays = [1000, 2000, 4000]; // Exponential backoff in milliseconds
|
this.reconnectDelays = [1000, 2000, 4000]; // Exponential backoff in milliseconds
|
||||||
this.isReconnecting = false;
|
this.isReconnecting = false;
|
||||||
this.autoReconnectEnabled = true;
|
this.autoReconnectEnabled = true;
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
this.eventListeners = {
|
this.eventListeners = {
|
||||||
'connection-status-changed': [],
|
'connection-status-changed': [],
|
||||||
@ -95,21 +95,21 @@ export class BluetoothPrinterManager {
|
|||||||
'reconnection-success': [],
|
'reconnection-success': [],
|
||||||
'reconnection-failure': []
|
'reconnection-failure': []
|
||||||
};
|
};
|
||||||
|
|
||||||
// Printer state
|
// Printer state
|
||||||
this.lastError = null;
|
this.lastError = null;
|
||||||
this.isPrinting = false;
|
this.isPrinting = false;
|
||||||
|
|
||||||
// Error notification service
|
// Error notification service
|
||||||
this.errorNotificationService = errorNotificationService;
|
this.errorNotificationService = errorNotificationService;
|
||||||
|
|
||||||
// Bluetooth service UUID for serial port profile (commonly used by thermal printers)
|
// Bluetooth service UUID for serial port profile (commonly used by thermal printers)
|
||||||
// Using the standard Serial Port Profile UUID (SPP)
|
// Using the standard Serial Port Profile UUID (SPP)
|
||||||
// Most thermal printers use SPP for communication
|
// Most thermal printers use SPP for communication
|
||||||
this.serviceUUID = '00001101-0000-1000-8000-00805f9b34fb'; // Serial Port Profile
|
this.serviceUUID = '00001101-0000-1000-8000-00805f9b34fb'; // Serial Port Profile
|
||||||
this.characteristicUUID = '00002af1-0000-1000-8000-00805f9b34fb';
|
this.characteristicUUID = '00002af1-0000-1000-8000-00805f9b34fb';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set error notification service
|
* Set error notification service
|
||||||
* @param {Object} errorNotificationService - Error notification service instance
|
* @param {Object} errorNotificationService - Error notification service instance
|
||||||
@ -139,8 +139,8 @@ export class BluetoothPrinterManager {
|
|||||||
* @returns {boolean} True if available
|
* @returns {boolean} True if available
|
||||||
*/
|
*/
|
||||||
isBluetoothAvailable() {
|
isBluetoothAvailable() {
|
||||||
const available = typeof navigator !== 'undefined' &&
|
const available = typeof navigator !== 'undefined' &&
|
||||||
navigator.bluetooth !== undefined;
|
navigator.bluetooth !== undefined;
|
||||||
this._log(`Bluetooth API available: ${available}`);
|
this._log(`Bluetooth API available: ${available}`);
|
||||||
return available;
|
return available;
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ export class BluetoothPrinterManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this._log('Starting device scan...');
|
this._log('Starting device scan...');
|
||||||
|
|
||||||
// Request bluetooth device with filters for thermal printers
|
// Request bluetooth device with filters for thermal printers
|
||||||
// Many thermal printers advertise with specific name patterns
|
// Many thermal printers advertise with specific name patterns
|
||||||
const device = await navigator.bluetooth.requestDevice({
|
const device = await navigator.bluetooth.requestDevice({
|
||||||
@ -190,7 +190,7 @@ export class BluetoothPrinterManager {
|
|||||||
return [device];
|
return [device];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._log('Filtered scan failed, trying acceptAllDevices fallback', error.name);
|
this._log('Filtered scan failed, trying acceptAllDevices fallback', error.name);
|
||||||
|
|
||||||
// If filtered search fails, try acceptAllDevices as fallback
|
// If filtered search fails, try acceptAllDevices as fallback
|
||||||
if (error.name === 'NotFoundError') {
|
if (error.name === 'NotFoundError') {
|
||||||
try {
|
try {
|
||||||
@ -219,7 +219,7 @@ export class BluetoothPrinterManager {
|
|||||||
}
|
}
|
||||||
throw cancelError;
|
throw cancelError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log unexpected errors
|
// Log unexpected errors
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.logError(error, { operation: 'scanDevices' });
|
this.errorNotificationService.logError(error, { operation: 'scanDevices' });
|
||||||
@ -247,7 +247,7 @@ export class BluetoothPrinterManager {
|
|||||||
try {
|
try {
|
||||||
this._log('Attempting to connect to printer...', deviceId);
|
this._log('Attempting to connect to printer...', deviceId);
|
||||||
this._setConnectionStatus('connecting');
|
this._setConnectionStatus('connecting');
|
||||||
|
|
||||||
// If deviceId is actually a device object from scanDevices, use it directly
|
// If deviceId is actually a device object from scanDevices, use it directly
|
||||||
let device;
|
let device;
|
||||||
if (typeof deviceId === 'object' && deviceId.gatt) {
|
if (typeof deviceId === 'object' && deviceId.gatt) {
|
||||||
@ -259,15 +259,15 @@ export class BluetoothPrinterManager {
|
|||||||
const devices = await navigator.bluetooth.getDevices();
|
const devices = await navigator.bluetooth.getDevices();
|
||||||
this._log(`Found ${devices.length} previously paired devices`);
|
this._log(`Found ${devices.length} previously paired devices`);
|
||||||
device = devices.find(d => d.id === deviceId || d.name === deviceId);
|
device = devices.find(d => d.id === deviceId || d.name === deviceId);
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
this._log('Device not found in paired devices');
|
this._log('Device not found in paired devices');
|
||||||
|
|
||||||
const error = new DeviceNotFoundError(`Device ${deviceId} not found`);
|
const error = new DeviceNotFoundError(`Device ${deviceId} not found`);
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleError(error, {
|
this.errorNotificationService.handleError(error, {
|
||||||
operation: 'connectToPrinter',
|
operation: 'connectToPrinter',
|
||||||
deviceId: deviceId
|
deviceId: deviceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@ -276,7 +276,7 @@ export class BluetoothPrinterManager {
|
|||||||
|
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this._log('Device selected', { name: this.device.name, id: this.device.id });
|
this._log('Device selected', { name: this.device.name, id: this.device.id });
|
||||||
|
|
||||||
// Set up disconnect handler
|
// Set up disconnect handler
|
||||||
this.device.addEventListener('gattserverdisconnected', () => {
|
this.device.addEventListener('gattserverdisconnected', () => {
|
||||||
this._log('GATT server disconnected event fired');
|
this._log('GATT server disconnected event fired');
|
||||||
@ -287,7 +287,7 @@ export class BluetoothPrinterManager {
|
|||||||
this._log('Connecting to GATT server...');
|
this._log('Connecting to GATT server...');
|
||||||
this.server = await this.device.gatt.connect();
|
this.server = await this.device.gatt.connect();
|
||||||
this._log('GATT server connected successfully');
|
this._log('GATT server connected successfully');
|
||||||
|
|
||||||
// Try to get the printer service - try multiple common UUIDs
|
// Try to get the printer service - try multiple common UUIDs
|
||||||
const serviceUUIDs = [
|
const serviceUUIDs = [
|
||||||
this.serviceUUID, // Serial Port Profile
|
this.serviceUUID, // Serial Port Profile
|
||||||
@ -296,7 +296,7 @@ export class BluetoothPrinterManager {
|
|||||||
'0000ffe0-0000-1000-8000-00805f9b34fb', // Common serial service
|
'0000ffe0-0000-1000-8000-00805f9b34fb', // Common serial service
|
||||||
'6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART Service
|
'6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART Service
|
||||||
];
|
];
|
||||||
|
|
||||||
const characteristicUUIDs = [
|
const characteristicUUIDs = [
|
||||||
this.characteristicUUID, // Default characteristic
|
this.characteristicUUID, // Default characteristic
|
||||||
'00002af1-0000-1000-8000-00805f9b34fb', // Write characteristic
|
'00002af1-0000-1000-8000-00805f9b34fb', // Write characteristic
|
||||||
@ -304,19 +304,19 @@ export class BluetoothPrinterManager {
|
|||||||
'0000ffe1-0000-1000-8000-00805f9b34fb', // Common write characteristic
|
'0000ffe1-0000-1000-8000-00805f9b34fb', // Common write characteristic
|
||||||
'6e400002-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART TX
|
'6e400002-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART TX
|
||||||
];
|
];
|
||||||
|
|
||||||
let serviceFound = false;
|
let serviceFound = false;
|
||||||
|
|
||||||
for (const serviceUUID of serviceUUIDs) {
|
for (const serviceUUID of serviceUUIDs) {
|
||||||
try {
|
try {
|
||||||
console.log(`Trying service UUID: ${serviceUUID}`);
|
this._log(`Trying service UUID: ${serviceUUID}`);
|
||||||
this.service = await this.server.getPrimaryService(serviceUUID);
|
this.service = await this.server.getPrimaryService(serviceUUID);
|
||||||
|
|
||||||
// Try to find a writable characteristic
|
// Try to find a writable characteristic
|
||||||
for (const charUUID of characteristicUUIDs) {
|
for (const charUUID of characteristicUUIDs) {
|
||||||
try {
|
try {
|
||||||
this.characteristic = await this.service.getCharacteristic(charUUID);
|
this.characteristic = await this.service.getCharacteristic(charUUID);
|
||||||
console.log(`Found characteristic: ${charUUID}`);
|
this._log(`Found characteristic: ${charUUID}`);
|
||||||
serviceFound = true;
|
serviceFound = true;
|
||||||
break;
|
break;
|
||||||
} catch (charError) {
|
} catch (charError) {
|
||||||
@ -324,7 +324,7 @@ export class BluetoothPrinterManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceFound) {
|
if (serviceFound) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -333,10 +333,10 @@ export class BluetoothPrinterManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serviceFound) {
|
if (!serviceFound) {
|
||||||
console.warn('Standard printer services not found, attempting to use any available writable characteristic');
|
console.warn('Standard printer services not found, attempting to use any available writable characteristic');
|
||||||
|
|
||||||
// Last resort: try to find any writable characteristic
|
// Last resort: try to find any writable characteristic
|
||||||
try {
|
try {
|
||||||
const services = await this.server.getPrimaryServices();
|
const services = await this.server.getPrimaryServices();
|
||||||
@ -346,7 +346,7 @@ export class BluetoothPrinterManager {
|
|||||||
if (char.properties.write || char.properties.writeWithoutResponse) {
|
if (char.properties.write || char.properties.writeWithoutResponse) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.characteristic = char;
|
this.characteristic = char;
|
||||||
console.log(`Using fallback characteristic: ${char.uuid}`);
|
this._log(`Using fallback characteristic: ${char.uuid}`);
|
||||||
serviceFound = true;
|
serviceFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -356,9 +356,9 @@ export class BluetoothPrinterManager {
|
|||||||
} catch (fallbackError) {
|
} catch (fallbackError) {
|
||||||
console.error('Failed to find any writable characteristic:', fallbackError);
|
console.error('Failed to find any writable characteristic:', fallbackError);
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.logError(fallbackError, {
|
this.errorNotificationService.logError(fallbackError, {
|
||||||
operation: 'findWritableCharacteristic',
|
operation: 'findWritableCharacteristic',
|
||||||
deviceName: this.device.name
|
deviceName: this.device.name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,16 +376,16 @@ export class BluetoothPrinterManager {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._setConnectionStatus('error');
|
this._setConnectionStatus('error');
|
||||||
this.lastError = error.message;
|
this.lastError = error.message;
|
||||||
|
|
||||||
if (error instanceof DeviceNotFoundError) {
|
if (error instanceof DeviceNotFoundError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectionError = new ConnectionFailedError(`Failed to connect: ${error.message}`);
|
const connectionError = new ConnectionFailedError(`Failed to connect: ${error.message}`);
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleError(connectionError, {
|
this.errorNotificationService.handleError(connectionError, {
|
||||||
operation: 'connectToPrinter',
|
operation: 'connectToPrinter',
|
||||||
originalError: error.message
|
originalError: error.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw connectionError;
|
throw connectionError;
|
||||||
@ -398,7 +398,7 @@ export class BluetoothPrinterManager {
|
|||||||
*/
|
*/
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
this.autoReconnectEnabled = false;
|
this.autoReconnectEnabled = false;
|
||||||
|
|
||||||
if (this.server && this.server.connected) {
|
if (this.server && this.server.connected) {
|
||||||
try {
|
try {
|
||||||
await this.server.disconnect();
|
await this.server.disconnect();
|
||||||
@ -411,7 +411,7 @@ export class BluetoothPrinterManager {
|
|||||||
this.server = null;
|
this.server = null;
|
||||||
this.service = null;
|
this.service = null;
|
||||||
this.characteristic = null;
|
this.characteristic = null;
|
||||||
|
|
||||||
this._setConnectionStatus('disconnected');
|
this._setConnectionStatus('disconnected');
|
||||||
this.lastError = null;
|
this.lastError = null;
|
||||||
}
|
}
|
||||||
@ -444,9 +444,9 @@ export class BluetoothPrinterManager {
|
|||||||
if (!this.characteristic) {
|
if (!this.characteristic) {
|
||||||
const error = new TransmissionError('Printer characteristic not available');
|
const error = new TransmissionError('Printer characteristic not available');
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleError(error, {
|
this.errorNotificationService.handleError(error, {
|
||||||
operation: 'sendData',
|
operation: 'sendData',
|
||||||
reason: 'characteristic_unavailable'
|
reason: 'characteristic_unavailable'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@ -455,36 +455,36 @@ export class BluetoothPrinterManager {
|
|||||||
try {
|
try {
|
||||||
this.isPrinting = true;
|
this.isPrinting = true;
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
// OPTIMIZED: Use larger chunks for graphics data (faster transmission)
|
// OPTIMIZED: Use larger chunks for graphics data (faster transmission)
|
||||||
// Graphics data can handle larger chunks than text commands
|
// Graphics data can handle larger chunks than text commands
|
||||||
const isLargeData = escposData.length > 1000;
|
const isLargeData = escposData.length > 1000;
|
||||||
const chunkSize = isLargeData ? 512 : 20; // Much larger chunks for graphics
|
const chunkSize = isLargeData ? 512 : 20; // Much larger chunks for graphics
|
||||||
|
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
for (let i = 0; i < escposData.length; i += chunkSize) {
|
for (let i = 0; i < escposData.length; i += chunkSize) {
|
||||||
chunks.push(escposData.slice(i, i + chunkSize));
|
chunks.push(escposData.slice(i, i + chunkSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[Bluetooth] Sending ${chunks.length} chunks (${escposData.length} bytes, ${chunkSize} bytes/chunk)`);
|
this._log(`Sending ${chunks.length} chunks (${escposData.length} bytes, ${chunkSize} bytes/chunk)`);
|
||||||
|
|
||||||
// Determine write method based on characteristic properties
|
// Determine write method based on characteristic properties
|
||||||
const useWriteWithoutResponse = this.characteristic.properties.writeWithoutResponse;
|
const useWriteWithoutResponse = this.characteristic.properties.writeWithoutResponse;
|
||||||
const useWrite = this.characteristic.properties.write;
|
const useWrite = this.characteristic.properties.write;
|
||||||
|
|
||||||
if (!useWrite && !useWriteWithoutResponse) {
|
if (!useWrite && !useWriteWithoutResponse) {
|
||||||
throw new Error('Characteristic does not support write operations');
|
throw new Error('Characteristic does not support write operations');
|
||||||
}
|
}
|
||||||
|
|
||||||
// OPTIMIZED: Reduce delays for faster transmission
|
// OPTIMIZED: Reduce delays for faster transmission
|
||||||
const delay = isLargeData ?
|
const delay = isLargeData ?
|
||||||
(useWriteWithoutResponse ? 10 : 5) : // Much shorter delays for graphics
|
(useWriteWithoutResponse ? 10 : 5) : // Much shorter delays for graphics
|
||||||
(useWriteWithoutResponse ? 50 : 25); // Normal delays for text
|
(useWriteWithoutResponse ? 50 : 25); // Normal delays for text
|
||||||
|
|
||||||
// Send each chunk
|
// Send each chunk
|
||||||
for (let i = 0; i < chunks.length; i++) {
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
const chunk = chunks[i];
|
const chunk = chunks[i];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (useWriteWithoutResponse) {
|
if (useWriteWithoutResponse) {
|
||||||
// Faster but no acknowledgment
|
// Faster but no acknowledgment
|
||||||
@ -493,16 +493,16 @@ export class BluetoothPrinterManager {
|
|||||||
// Slower but with acknowledgment
|
// Slower but with acknowledgment
|
||||||
await this.characteristic.writeValue(chunk);
|
await this.characteristic.writeValue(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OPTIMIZED: Minimal delay between chunks
|
// OPTIMIZED: Minimal delay between chunks
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
await this._sleep(delay);
|
await this._sleep(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress logging every 20%
|
// Progress logging every 20%
|
||||||
if (i % Math.ceil(chunks.length / 5) === 0) {
|
if (i % Math.ceil(chunks.length / 5) === 0) {
|
||||||
const progress = Math.round((i / chunks.length) * 100);
|
const progress = Math.round((i / chunks.length) * 100);
|
||||||
console.log(`[Bluetooth] Progress: ${progress}%`);
|
this._log(`Progress: ${progress}%`);
|
||||||
}
|
}
|
||||||
} catch (chunkError) {
|
} catch (chunkError) {
|
||||||
console.error(`Failed to send chunk ${i + 1}/${chunks.length}:`, chunkError);
|
console.error(`Failed to send chunk ${i + 1}/${chunks.length}:`, chunkError);
|
||||||
@ -513,22 +513,22 @@ export class BluetoothPrinterManager {
|
|||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
||||||
const speed = (escposData.length / 1024 / (duration || 1)).toFixed(2);
|
const speed = (escposData.length / 1024 / (duration || 1)).toFixed(2);
|
||||||
|
|
||||||
console.log(`[Bluetooth] Transmission complete in ${duration}s (${speed} KB/s)`);
|
this._log(`Transmission complete in ${duration}s (${speed} KB/s)`);
|
||||||
this.isPrinting = false;
|
this.isPrinting = false;
|
||||||
this._emit('print-completed', { success: true });
|
this._emit('print-completed', { success: true });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.isPrinting = false;
|
this.isPrinting = false;
|
||||||
this._emit('print-failed', { error: error.message });
|
this._emit('print-failed', { error: error.message });
|
||||||
|
|
||||||
const transmissionError = new TransmissionError(`Failed to send data: ${error.message}`);
|
const transmissionError = new TransmissionError(`Failed to send data: ${error.message}`);
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleError(transmissionError, {
|
this.errorNotificationService.handleError(transmissionError, {
|
||||||
operation: 'sendData',
|
operation: 'sendData',
|
||||||
dataSize: escposData.length,
|
dataSize: escposData.length,
|
||||||
originalError: error.message
|
originalError: error.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw transmissionError;
|
throw transmissionError;
|
||||||
@ -578,13 +578,13 @@ export class BluetoothPrinterManager {
|
|||||||
|
|
||||||
for (let attempt = 0; attempt < this.maxReconnectAttempts; attempt++) {
|
for (let attempt = 0; attempt < this.maxReconnectAttempts; attempt++) {
|
||||||
this.reconnectAttempts = attempt + 1;
|
this.reconnectAttempts = attempt + 1;
|
||||||
|
|
||||||
// Emit reconnection attempt event
|
// Emit reconnection attempt event
|
||||||
this._emit('reconnection-attempt', {
|
this._emit('reconnection-attempt', {
|
||||||
attempt: this.reconnectAttempts,
|
attempt: this.reconnectAttempts,
|
||||||
maxAttempts: this.maxReconnectAttempts
|
maxAttempts: this.maxReconnectAttempts
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify via error service
|
// Notify via error service
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleReconnectionAttempt(
|
this.errorNotificationService.handleReconnectionAttempt(
|
||||||
@ -592,32 +592,32 @@ export class BluetoothPrinterManager {
|
|||||||
this.maxReconnectAttempts
|
this.maxReconnectAttempts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
this._log(`Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
||||||
|
|
||||||
// Try to reconnect
|
// Try to reconnect
|
||||||
await this.connectToPrinter(this.device);
|
await this.connectToPrinter(this.device);
|
||||||
|
|
||||||
this.isReconnecting = false;
|
this.isReconnecting = false;
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
|
|
||||||
console.log('Reconnection successful');
|
this._log('Reconnection successful');
|
||||||
|
|
||||||
// Emit reconnection success event
|
// Emit reconnection success event
|
||||||
this._emit('reconnection-success', {
|
this._emit('reconnection-success', {
|
||||||
deviceName: this.device.name
|
deviceName: this.device.name
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify via error service
|
// Notify via error service
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleReconnectionSuccess(this.device.name);
|
this.errorNotificationService.handleReconnectionSuccess(this.device.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error);
|
console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error);
|
||||||
|
|
||||||
// Log the error
|
// Log the error
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.logError(error, {
|
this.errorNotificationService.logError(error, {
|
||||||
@ -626,11 +626,11 @@ export class BluetoothPrinterManager {
|
|||||||
maxAttempts: this.maxReconnectAttempts
|
maxAttempts: this.maxReconnectAttempts
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait before next attempt (exponential backoff)
|
// Wait before next attempt (exponential backoff)
|
||||||
if (attempt < this.maxReconnectAttempts - 1) {
|
if (attempt < this.maxReconnectAttempts - 1) {
|
||||||
const delay = this.reconnectDelays[attempt];
|
const delay = this.reconnectDelays[attempt];
|
||||||
console.log(`Waiting ${delay}ms before next attempt`);
|
this._log(`Waiting ${delay}ms before next attempt`);
|
||||||
await this._sleep(delay);
|
await this._sleep(delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,19 +640,19 @@ export class BluetoothPrinterManager {
|
|||||||
this.isReconnecting = false;
|
this.isReconnecting = false;
|
||||||
this._setConnectionStatus('error');
|
this._setConnectionStatus('error');
|
||||||
this.lastError = 'Reconnection failed after maximum attempts';
|
this.lastError = 'Reconnection failed after maximum attempts';
|
||||||
|
|
||||||
console.error('All reconnection attempts failed');
|
console.error('All reconnection attempts failed');
|
||||||
|
|
||||||
// Emit reconnection failure event
|
// Emit reconnection failure event
|
||||||
this._emit('reconnection-failure', {
|
this._emit('reconnection-failure', {
|
||||||
attempts: this.maxReconnectAttempts
|
attempts: this.maxReconnectAttempts
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify via error service
|
// Notify via error service
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleReconnectionFailure();
|
this.errorNotificationService.handleReconnectionFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,12 +694,12 @@ export class BluetoothPrinterManager {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onDisconnected() {
|
_onDisconnected() {
|
||||||
console.log('Bluetooth device disconnected');
|
this._log('Bluetooth device disconnected');
|
||||||
this._setConnectionStatus('disconnected');
|
this._setConnectionStatus('disconnected');
|
||||||
|
|
||||||
// Attempt auto-reconnection if enabled
|
// Attempt auto-reconnection if enabled
|
||||||
if (this.autoReconnectEnabled) {
|
if (this.autoReconnectEnabled) {
|
||||||
console.log('Starting auto-reconnection...');
|
this._log('Starting auto-reconnection...');
|
||||||
this.autoReconnect().catch(error => {
|
this.autoReconnect().catch(error => {
|
||||||
console.error('Auto-reconnection failed:', error);
|
console.error('Auto-reconnection failed:', error);
|
||||||
});
|
});
|
||||||
@ -714,7 +714,7 @@ export class BluetoothPrinterManager {
|
|||||||
_setConnectionStatus(status) {
|
_setConnectionStatus(status) {
|
||||||
const oldStatus = this.connectionStatus;
|
const oldStatus = this.connectionStatus;
|
||||||
this.connectionStatus = status;
|
this.connectionStatus = status;
|
||||||
|
|
||||||
if (oldStatus !== status) {
|
if (oldStatus !== status) {
|
||||||
const statusData = {
|
const statusData = {
|
||||||
oldStatus,
|
oldStatus,
|
||||||
@ -722,9 +722,9 @@ export class BluetoothPrinterManager {
|
|||||||
deviceName: this.device ? this.device.name : null,
|
deviceName: this.device ? this.device.name : null,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
this._emit('connection-status-changed', statusData);
|
this._emit('connection-status-changed', statusData);
|
||||||
|
|
||||||
// Notify error service about status change
|
// Notify error service about status change
|
||||||
if (this.errorNotificationService) {
|
if (this.errorNotificationService) {
|
||||||
this.errorNotificationService.handleStatusChange(statusData);
|
this.errorNotificationService.handleStatusChange(statusData);
|
||||||
|
|||||||
@ -35,34 +35,39 @@ export class EscPosGraphics {
|
|||||||
* @returns {Uint8Array} Complete ESC/POS command sequence
|
* @returns {Uint8Array} Complete ESC/POS command sequence
|
||||||
*/
|
*/
|
||||||
generateBitmapCommands(bitmap) {
|
generateBitmapCommands(bitmap) {
|
||||||
console.log('[EscPosGraphics] Generating bitmap commands (optimized)...');
|
|
||||||
console.log('[EscPosGraphics] Original dimensions:', bitmap.width, 'x', bitmap.height);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
// OPTIMIZATION: Remove blank lines from top and bottom
|
// OPTIMIZATION: Remove blank lines from top and bottom
|
||||||
const optimizedBitmap = this._removeBlankLines(bitmap);
|
const optimizedBitmap = this._removeBlankLines(bitmap);
|
||||||
console.log('[EscPosGraphics] Optimized dimensions:', optimizedBitmap.width, 'x', optimizedBitmap.height);
|
|
||||||
console.log('[EscPosGraphics] Saved', bitmap.height - optimizedBitmap.height, 'blank lines');
|
|
||||||
|
|
||||||
const commands = [];
|
|
||||||
|
|
||||||
// Initialize printer
|
// Initialize printer
|
||||||
commands.push(...this.initialize());
|
const initCmd = this.initialize();
|
||||||
|
|
||||||
// Print bitmap using raster graphics mode
|
// Print bitmap using raster graphics mode
|
||||||
const rasterCommands = this._generateRasterGraphics(optimizedBitmap);
|
const rasterCommands = this._generateRasterGraphics(optimizedBitmap);
|
||||||
commands.push(...rasterCommands);
|
|
||||||
|
|
||||||
// Feed paper and cut
|
// Feed paper and cut
|
||||||
commands.push(...this._feedAndCut(4));
|
const feedCmd = this._feedAndCut(4);
|
||||||
|
|
||||||
const result = new Uint8Array(commands);
|
// Combine all commands safely without spreading large arrays
|
||||||
|
const totalLength = initCmd.length + rasterCommands.length + feedCmd.length;
|
||||||
|
const result = new Uint8Array(totalLength);
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
result.set(initCmd, offset);
|
||||||
|
offset += initCmd.length;
|
||||||
|
|
||||||
|
result.set(rasterCommands, offset);
|
||||||
|
offset += rasterCommands.length;
|
||||||
|
|
||||||
|
result.set(new Uint8Array(feedCmd), offset);
|
||||||
|
|
||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
console.log('[EscPosGraphics] Command generation took:', (endTime - startTime).toFixed(2), 'ms');
|
|
||||||
console.log('[EscPosGraphics] Generated', result.length, 'bytes of commands');
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,14 +80,14 @@ export class EscPosGraphics {
|
|||||||
*/
|
*/
|
||||||
_removeBlankLines(bitmap) {
|
_removeBlankLines(bitmap) {
|
||||||
const { data, width, height, bytesPerLine } = bitmap;
|
const { data, width, height, bytesPerLine } = bitmap;
|
||||||
|
|
||||||
// Find first non-blank line from top
|
// Find first non-blank line from top
|
||||||
let firstLine = 0;
|
let firstLine = 0;
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < height; y++) {
|
||||||
const lineStart = y * bytesPerLine;
|
const lineStart = y * bytesPerLine;
|
||||||
const lineEnd = lineStart + bytesPerLine;
|
const lineEnd = lineStart + bytesPerLine;
|
||||||
const lineData = data.slice(lineStart, lineEnd);
|
const lineData = data.slice(lineStart, lineEnd);
|
||||||
|
|
||||||
// Check if line has any black pixels
|
// Check if line has any black pixels
|
||||||
const hasContent = lineData.some(byte => byte !== 0);
|
const hasContent = lineData.some(byte => byte !== 0);
|
||||||
if (hasContent) {
|
if (hasContent) {
|
||||||
@ -90,14 +95,14 @@ export class EscPosGraphics {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find last non-blank line from bottom
|
// Find last non-blank line from bottom
|
||||||
let lastLine = height - 1;
|
let lastLine = height - 1;
|
||||||
for (let y = height - 1; y >= firstLine; y--) {
|
for (let y = height - 1; y >= firstLine; y--) {
|
||||||
const lineStart = y * bytesPerLine;
|
const lineStart = y * bytesPerLine;
|
||||||
const lineEnd = lineStart + bytesPerLine;
|
const lineEnd = lineStart + bytesPerLine;
|
||||||
const lineData = data.slice(lineStart, lineEnd);
|
const lineData = data.slice(lineStart, lineEnd);
|
||||||
|
|
||||||
// Check if line has any black pixels
|
// Check if line has any black pixels
|
||||||
const hasContent = lineData.some(byte => byte !== 0);
|
const hasContent = lineData.some(byte => byte !== 0);
|
||||||
if (hasContent) {
|
if (hasContent) {
|
||||||
@ -105,18 +110,18 @@ export class EscPosGraphics {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract only the content lines
|
// Extract only the content lines
|
||||||
const newHeight = lastLine - firstLine + 1;
|
const newHeight = lastLine - firstLine + 1;
|
||||||
const newData = new Uint8Array(bytesPerLine * newHeight);
|
const newData = new Uint8Array(bytesPerLine * newHeight);
|
||||||
|
|
||||||
for (let y = 0; y < newHeight; y++) {
|
for (let y = 0; y < newHeight; y++) {
|
||||||
const srcStart = (firstLine + y) * bytesPerLine;
|
const srcStart = (firstLine + y) * bytesPerLine;
|
||||||
const srcEnd = srcStart + bytesPerLine;
|
const srcEnd = srcStart + bytesPerLine;
|
||||||
const dstStart = y * bytesPerLine;
|
const dstStart = y * bytesPerLine;
|
||||||
newData.set(data.slice(srcStart, srcEnd), dstStart);
|
newData.set(data.slice(srcStart, srcEnd), dstStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: newData,
|
data: newData,
|
||||||
width: width,
|
width: width,
|
||||||
@ -134,37 +139,39 @@ export class EscPosGraphics {
|
|||||||
* @returns {Array} Command bytes
|
* @returns {Array} Command bytes
|
||||||
*/
|
*/
|
||||||
_generateRasterGraphics(bitmap) {
|
_generateRasterGraphics(bitmap) {
|
||||||
const commands = [];
|
|
||||||
const { data, width, height, bytesPerLine } = bitmap;
|
const { data, width, height, bytesPerLine } = bitmap;
|
||||||
|
|
||||||
// Calculate dimensions
|
// Calculate dimensions
|
||||||
const widthBytes = bytesPerLine;
|
const widthBytes = bytesPerLine;
|
||||||
const widthLow = widthBytes & 0xFF;
|
const widthLow = widthBytes & 0xFF;
|
||||||
const widthHigh = (widthBytes >> 8) & 0xFF;
|
const widthHigh = (widthBytes >> 8) & 0xFF;
|
||||||
const heightLow = height & 0xFF;
|
const heightLow = height & 0xFF;
|
||||||
const heightHigh = (height >> 8) & 0xFF;
|
const heightHigh = (height >> 8) & 0xFF;
|
||||||
|
|
||||||
console.log('[EscPosGraphics] Bitmap width:', width, 'pixels');
|
|
||||||
console.log('[EscPosGraphics] Bitmap height:', height, 'lines');
|
|
||||||
console.log('[EscPosGraphics] Bytes per line:', bytesPerLine);
|
|
||||||
console.log('[EscPosGraphics] Width bytes (xL xH):', widthLow, widthHigh, '=', widthBytes);
|
|
||||||
console.log('[EscPosGraphics] Height (yL yH):', heightLow, heightHigh, '=', height);
|
|
||||||
console.log('[EscPosGraphics] Total data size:', data.length, 'bytes');
|
|
||||||
console.log('[EscPosGraphics] Expected data size:', bytesPerLine * height, 'bytes');
|
|
||||||
|
|
||||||
// GS v 0 - Print raster bitmap
|
// GS v 0 - Print raster bitmap
|
||||||
// Format: GS v 0 m xL xH yL yH d1...dk
|
// Format: GS v 0 m xL xH yL yH d1...dk
|
||||||
// m = mode (0 = normal, 1 = double width, 2 = double height, 3 = quadruple)
|
// Header length: 8 bytes
|
||||||
commands.push(GS, 0x76, 0x30, 0x00); // GS v 0 m (m=0 for normal)
|
const header = [
|
||||||
commands.push(widthLow, widthHigh); // xL xH (width in bytes)
|
GS, 0x76, 0x30, 0x00, // GS v 0 m (m=0 for normal)
|
||||||
commands.push(heightLow, heightHigh); // yL yH (height in dots)
|
widthLow, widthHigh, // xL xH (width in bytes)
|
||||||
|
heightLow, heightHigh // yL yH (height in dots)
|
||||||
// Add bitmap data
|
];
|
||||||
commands.push(...data);
|
|
||||||
|
// Create Uint8Array for the complete command
|
||||||
// Add line feed after image
|
const totalLength = header.length + data.length + 1; // +1 for LF
|
||||||
commands.push(LF);
|
const commands = new Uint8Array(totalLength);
|
||||||
|
|
||||||
|
// Set header
|
||||||
|
commands.set(header, 0);
|
||||||
|
|
||||||
|
// Set bitmap data
|
||||||
|
commands.set(data, header.length);
|
||||||
|
|
||||||
|
// Set LF
|
||||||
|
commands[totalLength - 1] = LF;
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +186,7 @@ export class EscPosGraphics {
|
|||||||
_generateBitImageCommands(bitmap) {
|
_generateBitImageCommands(bitmap) {
|
||||||
const commands = [];
|
const commands = [];
|
||||||
const { data, width, height, bytesPerLine } = bitmap;
|
const { data, width, height, bytesPerLine } = bitmap;
|
||||||
|
|
||||||
// Print line by line using ESC * command
|
// Print line by line using ESC * command
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < height; y++) {
|
||||||
// ESC * m nL nH d1...dk
|
// ESC * m nL nH d1...dk
|
||||||
@ -187,18 +194,18 @@ export class EscPosGraphics {
|
|||||||
const mode = 33;
|
const mode = 33;
|
||||||
const nL = width & 0xFF;
|
const nL = width & 0xFF;
|
||||||
const nH = (width >> 8) & 0xFF;
|
const nH = (width >> 8) & 0xFF;
|
||||||
|
|
||||||
commands.push(ESC, 0x2A, mode, nL, nH);
|
commands.push(ESC, 0x2A, mode, nL, nH);
|
||||||
|
|
||||||
// Add line data
|
// Add line data
|
||||||
const lineStart = y * bytesPerLine;
|
const lineStart = y * bytesPerLine;
|
||||||
const lineEnd = lineStart + bytesPerLine;
|
const lineEnd = lineStart + bytesPerLine;
|
||||||
commands.push(...data.slice(lineStart, lineEnd));
|
commands.push(...data.slice(lineStart, lineEnd));
|
||||||
|
|
||||||
// Line feed
|
// Line feed
|
||||||
commands.push(LF);
|
commands.push(LF);
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,16 +218,16 @@ export class EscPosGraphics {
|
|||||||
*/
|
*/
|
||||||
_feedAndCut(lines = 3) {
|
_feedAndCut(lines = 3) {
|
||||||
const commands = [];
|
const commands = [];
|
||||||
|
|
||||||
// Feed lines
|
// Feed lines
|
||||||
for (let i = 0; i < lines; i++) {
|
for (let i = 0; i < lines; i++) {
|
||||||
commands.push(LF);
|
commands.push(LF);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cut paper (GS V m)
|
// Cut paper (GS V m)
|
||||||
// m = 0 (full cut), 1 (partial cut)
|
// m = 0 (full cut), 1 (partial cut)
|
||||||
commands.push(GS, 0x56, 0x00);
|
commands.push(GS, 0x56, 0x00);
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,13 +241,13 @@ export class EscPosGraphics {
|
|||||||
*/
|
*/
|
||||||
splitIntoChunks(commands, chunkSize = 1024) {
|
splitIntoChunks(commands, chunkSize = 1024) {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
|
|
||||||
for (let i = 0; i < commands.length; i += chunkSize) {
|
for (let i = 0; i < commands.length; i += chunkSize) {
|
||||||
const chunk = commands.slice(i, i + chunkSize);
|
const chunk = commands.slice(i, i + chunkSize);
|
||||||
chunks.push(chunk);
|
chunks.push(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[EscPosGraphics] Split into', chunks.length, 'chunks');
|
|
||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,12 +261,12 @@ export class EscPosGraphics {
|
|||||||
const height = 200;
|
const height = 200;
|
||||||
const bytesPerLine = Math.ceil(width / 8);
|
const bytesPerLine = Math.ceil(width / 8);
|
||||||
const data = new Uint8Array(bytesPerLine * height);
|
const data = new Uint8Array(bytesPerLine * height);
|
||||||
|
|
||||||
// Create a test pattern (checkerboard)
|
// Create a test pattern (checkerboard)
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < height; y++) {
|
||||||
for (let x = 0; x < width; x++) {
|
for (let x = 0; x < width; x++) {
|
||||||
const isBlack = ((Math.floor(x / 8) + Math.floor(y / 8)) % 2) === 0;
|
const isBlack = ((Math.floor(x / 8) + Math.floor(y / 8)) % 2) === 0;
|
||||||
|
|
||||||
if (isBlack) {
|
if (isBlack) {
|
||||||
const byteIndex = y * bytesPerLine + Math.floor(x / 8);
|
const byteIndex = y * bytesPerLine + Math.floor(x / 8);
|
||||||
const bitIndex = 7 - (x % 8);
|
const bitIndex = 7 - (x % 8);
|
||||||
@ -267,14 +274,14 @@ export class EscPosGraphics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bitmap = {
|
const bitmap = {
|
||||||
data: data,
|
data: data,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
bytesPerLine: bytesPerLine
|
bytesPerLine: bytesPerLine
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.generateBitmapCommands(bitmap);
|
return this.generateBitmapCommands(bitmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,12 +31,11 @@ export class HtmlToImageConverter {
|
|||||||
* @returns {Promise<HTMLCanvasElement>} Canvas with rendered HTML
|
* @returns {Promise<HTMLCanvasElement>} Canvas with rendered HTML
|
||||||
*/
|
*/
|
||||||
async htmlToCanvas(element) {
|
async htmlToCanvas(element) {
|
||||||
console.log('[HtmlToImage] Converting HTML to canvas...');
|
|
||||||
console.log('[HtmlToImage] Paper width:', this.paperWidth, 'pixels');
|
|
||||||
|
|
||||||
// Clone the element to avoid modifying the original
|
// Clone the element to avoid modifying the original
|
||||||
const clone = element.cloneNode(true);
|
const clone = element.cloneNode(true);
|
||||||
|
|
||||||
// Apply receipt styling to clone for proper rendering
|
// Apply receipt styling to clone for proper rendering
|
||||||
clone.style.width = `${this.paperWidth}px`;
|
clone.style.width = `${this.paperWidth}px`;
|
||||||
clone.style.maxWidth = `${this.paperWidth}px`;
|
clone.style.maxWidth = `${this.paperWidth}px`;
|
||||||
@ -47,47 +46,12 @@ export class HtmlToImageConverter {
|
|||||||
clone.style.backgroundColor = 'white';
|
clone.style.backgroundColor = 'white';
|
||||||
clone.style.color = 'black';
|
clone.style.color = 'black';
|
||||||
clone.style.overflow = 'visible';
|
clone.style.overflow = 'visible';
|
||||||
|
|
||||||
// Scale all fonts by 100% (2x) to match test print readability
|
// Scale all fonts by 150% (3x) to match test print readability
|
||||||
// Test print uses ESC/POS text mode which has larger fonts
|
// Test print uses ESC/POS text mode which has larger fonts
|
||||||
const fontScale = 2.0;
|
const fontScale = 1.2;
|
||||||
console.log('[HtmlToImage] Scaling fonts by', fontScale, 'x to match test print readability');
|
|
||||||
|
|
||||||
const allElements = clone.querySelectorAll('*');
|
|
||||||
allElements.forEach(element => {
|
|
||||||
const style = window.getComputedStyle(element);
|
|
||||||
const currentFontSize = parseFloat(style.fontSize);
|
|
||||||
|
|
||||||
if (currentFontSize > 0) {
|
|
||||||
const newFontSize = currentFontSize * fontScale;
|
|
||||||
element.style.fontSize = `${newFontSize}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also reduce padding to prevent overflow and cropping
|
|
||||||
const paddingLeft = parseFloat(style.paddingLeft) || 0;
|
|
||||||
const paddingRight = parseFloat(style.paddingRight) || 0;
|
|
||||||
|
|
||||||
// If padding is excessive, reduce it
|
|
||||||
if (paddingLeft > 8) {
|
|
||||||
element.style.paddingLeft = '4px';
|
|
||||||
}
|
|
||||||
if (paddingRight > 8) {
|
|
||||||
element.style.paddingRight = '4px';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also scale the root element font size
|
|
||||||
const rootFontSize = parseFloat(window.getComputedStyle(clone).fontSize);
|
|
||||||
if (rootFontSize > 0) {
|
|
||||||
clone.style.fontSize = `${rootFontSize * fontScale}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce root padding to prevent cropping
|
|
||||||
clone.style.paddingLeft = '4px';
|
|
||||||
clone.style.paddingRight = '4px';
|
|
||||||
|
|
||||||
console.log('[HtmlToImage] Fonts scaled 2x, padding reduced, rendering at paper width:', this.paperWidth, 'px');
|
|
||||||
|
|
||||||
// Create a temporary container to measure height
|
// Create a temporary container to measure height
|
||||||
// Container must be large enough to hold the scaled content
|
// Container must be large enough to hold the scaled content
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
@ -101,14 +65,66 @@ export class HtmlToImageConverter {
|
|||||||
container.style.overflow = 'visible';
|
container.style.overflow = 'visible';
|
||||||
container.style.zIndex = '-1000';
|
container.style.zIndex = '-1000';
|
||||||
container.style.boxSizing = 'border-box';
|
container.style.boxSizing = 'border-box';
|
||||||
|
|
||||||
container.appendChild(clone);
|
container.appendChild(clone);
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
const allElements = clone.querySelectorAll('*');
|
||||||
|
allElements.forEach(element => {
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
const currentFontSize = parseFloat(style.fontSize);
|
||||||
|
|
||||||
|
if (currentFontSize > 0) {
|
||||||
|
const newFontSize = currentFontSize * fontScale;
|
||||||
|
element.style.fontSize = `${newFontSize}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also reduce padding to prevent overflow and cropping
|
||||||
|
const paddingLeft = parseFloat(style.paddingLeft) || 0;
|
||||||
|
const paddingRight = parseFloat(style.paddingRight) || 0;
|
||||||
|
|
||||||
|
// If padding is excessive, reduce it
|
||||||
|
if (paddingLeft > 8) {
|
||||||
|
element.style.paddingLeft = '4px';
|
||||||
|
}
|
||||||
|
if (paddingRight > 8) {
|
||||||
|
element.style.paddingRight = '4px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also scale the root element font size
|
||||||
|
const rootFontSize = parseFloat(window.getComputedStyle(clone).fontSize);
|
||||||
|
if (rootFontSize > 0) {
|
||||||
|
clone.style.fontSize = `${rootFontSize * fontScale}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce root padding to prevent cropping
|
||||||
|
clone.style.paddingLeft = '4px';
|
||||||
|
clone.style.paddingRight = '4px';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Create a temporary container to measure height
|
||||||
|
// Container must be large enough to hold the scaled content
|
||||||
|
// Duplicate container declaration removed
|
||||||
|
container.style.position = 'fixed';
|
||||||
|
container.style.left = '-9999px'; // Move off-screen instead of using opacity
|
||||||
|
container.style.top = '0';
|
||||||
|
container.style.width = `${this.paperWidth}px`;
|
||||||
|
container.style.maxWidth = `${this.paperWidth}px`;
|
||||||
|
container.style.minWidth = `${this.paperWidth}px`;
|
||||||
|
container.style.backgroundColor = 'white';
|
||||||
|
container.style.overflow = 'visible';
|
||||||
|
container.style.zIndex = '-1000';
|
||||||
|
container.style.boxSizing = 'border-box';
|
||||||
|
|
||||||
|
container.appendChild(clone);
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for layout to settle, fonts to load, and images to load
|
// Wait for layout to settle, fonts to load, and images to load
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
|
||||||
// Load all images in the clone
|
// Load all images in the clone
|
||||||
const images = clone.querySelectorAll('img');
|
const images = clone.querySelectorAll('img');
|
||||||
await Promise.all(Array.from(images).map(img => {
|
await Promise.all(Array.from(images).map(img => {
|
||||||
@ -123,36 +139,32 @@ export class HtmlToImageConverter {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Get the actual rendered dimensions
|
// Get the actual rendered dimensions
|
||||||
const rect = clone.getBoundingClientRect();
|
const rect = clone.getBoundingClientRect();
|
||||||
const width = this.paperWidth;
|
const width = this.paperWidth;
|
||||||
const height = Math.ceil(rect.height);
|
const height = Math.ceil(rect.height);
|
||||||
|
|
||||||
console.log('[HtmlToImage] Rendered dimensions:', rect.width, 'x', rect.height);
|
|
||||||
console.log('[HtmlToImage] Final canvas size:', width, 'x', height);
|
|
||||||
console.log('[HtmlToImage] Container rect:', rect);
|
|
||||||
console.log('[HtmlToImage] Receipt HTML preview (first 1000 chars):', clone.outerHTML.substring(0, 1000));
|
|
||||||
console.log('[HtmlToImage] Receipt text content:', clone.textContent.substring(0, 500));
|
|
||||||
|
|
||||||
// Create canvas with exact dimensions
|
// Create canvas with exact dimensions
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
const ctx = canvas.getContext('2d', { alpha: false, willReadFrequently: false });
|
const ctx = canvas.getContext('2d', { alpha: false, willReadFrequently: false });
|
||||||
|
|
||||||
// Fill with white background
|
// Fill with white background
|
||||||
ctx.fillStyle = 'white';
|
ctx.fillStyle = 'white';
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
// Render the DOM to canvas using improved method
|
// Render the DOM to canvas using improved method
|
||||||
// Content is already at the correct width, no scaling needed
|
// Content is already at the correct width, no scaling needed
|
||||||
console.log('[HtmlToImage] Rendering DOM to canvas...');
|
|
||||||
await this._renderDomToCanvasImproved(clone, ctx, 0, 0, width, height);
|
await this._renderDomToCanvasImproved(clone, ctx, 0, 0, width, height);
|
||||||
|
|
||||||
console.log('[HtmlToImage] Canvas rendering complete');
|
|
||||||
return canvas;
|
return canvas;
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up
|
// Clean up
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container);
|
||||||
@ -173,17 +185,17 @@ export class HtmlToImageConverter {
|
|||||||
* @returns {Promise<number>} Height used
|
* @returns {Promise<number>} Height used
|
||||||
*/
|
*/
|
||||||
async _renderDomToCanvasImproved(element, ctx, offsetX, offsetY, canvasWidth, canvasHeight) {
|
async _renderDomToCanvasImproved(element, ctx, offsetX, offsetY, canvasWidth, canvasHeight) {
|
||||||
console.log('[HtmlToImage] Rendering DOM to canvas (improved method)...');
|
|
||||||
|
|
||||||
// Background is already filled, no need to fill again
|
// Background is already filled, no need to fill again
|
||||||
|
|
||||||
// Get the bounding rect of the root element to calculate offsets
|
// Get the bounding rect of the root element to calculate offsets
|
||||||
const rootRect = element.getBoundingClientRect();
|
const rootRect = element.getBoundingClientRect();
|
||||||
|
|
||||||
// Render element recursively with proper offset calculation
|
// Render element recursively with proper offset calculation
|
||||||
await this._renderElement(element, ctx, -rootRect.left, -rootRect.top, rootRect);
|
await this._renderElement(element, ctx, -rootRect.left, -rootRect.top, rootRect);
|
||||||
|
|
||||||
console.log('[HtmlToImage] Rendering complete');
|
|
||||||
return canvasHeight;
|
return canvasHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,50 +213,114 @@ export class HtmlToImageConverter {
|
|||||||
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = window.getComputedStyle(element);
|
const style = window.getComputedStyle(element);
|
||||||
|
|
||||||
// Skip hidden elements
|
// Skip hidden elements
|
||||||
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
const x = rect.left + offsetX;
|
const x = rect.left + offsetX;
|
||||||
const y = rect.top + offsetY;
|
const y = rect.top + offsetY;
|
||||||
|
|
||||||
// Render background
|
// Render background
|
||||||
const bgColor = style.backgroundColor;
|
const bgColor = style.backgroundColor;
|
||||||
if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
|
if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
|
||||||
ctx.fillStyle = bgColor;
|
ctx.fillStyle = bgColor;
|
||||||
ctx.fillRect(x, y, rect.width, rect.height);
|
ctx.fillRect(x, y, rect.width, rect.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render borders
|
// Render borders
|
||||||
this._renderBorders(ctx, x, y, rect.width, rect.height, style);
|
this._renderBorders(ctx, x, y, rect.width, rect.height, style);
|
||||||
|
|
||||||
// Render images
|
// Render images
|
||||||
if (element.tagName === 'IMG') {
|
if (element.tagName === 'IMG') {
|
||||||
await this._renderImage(element, ctx, x, y, rect.width, rect.height);
|
await this._renderImage(element, ctx, x, y, rect.width, rect.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render text content
|
// Render children (text and elements)
|
||||||
if (element.childNodes.length > 0) {
|
if (element.childNodes.length > 0) {
|
||||||
|
// Check if this is a specialized text-only element (optimization + wrapping handling)
|
||||||
|
// We keep this optimization for simple blocks, but improve the check
|
||||||
|
// Actually, to fix mixed content, we should just iterate childNodes.
|
||||||
|
// But we need to handle wrapping for long text nodes.
|
||||||
|
// For now, let's assume standard recursion is safer for mixed content.
|
||||||
|
|
||||||
const hasOnlyText = Array.from(element.childNodes).every(
|
const hasOnlyText = Array.from(element.childNodes).every(
|
||||||
node => node.nodeType === Node.TEXT_NODE || (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BR')
|
node => node.nodeType === Node.TEXT_NODE || (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'BR')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Use the block text renderer mainly for simple text blocks which might need wrapping logic
|
||||||
|
// that _renderText provides (it handles word wrapping based on width).
|
||||||
|
// However, relying ONLY on this means mixed content fails.
|
||||||
|
|
||||||
if (hasOnlyText) {
|
if (hasOnlyText) {
|
||||||
this._renderText(element, ctx, x, y, rect.width, rect.height, style);
|
this._renderText(element, ctx, x, y, rect.width, rect.height, style);
|
||||||
} else {
|
} else {
|
||||||
// Render children recursively
|
// Mixed content or complex structure
|
||||||
for (const child of element.children) {
|
for (const node of element.childNodes) {
|
||||||
await this._renderElement(child, ctx, offsetX, offsetY, rootRect);
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
await this._renderElement(node, ctx, offsetX, offsetY, rootRect);
|
||||||
|
} else if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
const text = node.textContent.trim();
|
||||||
|
if (text) {
|
||||||
|
// Text node in mixed content
|
||||||
|
// We need its exact position
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNode(node);
|
||||||
|
const textRect = range.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Calculate relative position
|
||||||
|
const textX = textRect.left + offsetX;
|
||||||
|
const textY = textRect.top + offsetY;
|
||||||
|
|
||||||
|
// Draw the text node using parent's style
|
||||||
|
// Note: Text nodes don't have padding/margin themselves effectively (handled by layout)
|
||||||
|
this._drawTextNode(ctx, text, textX, textY, textRect.width, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a single text node
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
||||||
|
* @param {string} text - Text to draw
|
||||||
|
* @param {number} x - X position
|
||||||
|
* @param {number} y - Y position
|
||||||
|
* @param {number} width - Node width
|
||||||
|
* @param {CSSStyleDeclaration} style - Computed style of parent
|
||||||
|
*/
|
||||||
|
_drawTextNode(ctx, text, x, y, width, style) {
|
||||||
|
// Set font
|
||||||
|
const fontSize = parseFloat(style.fontSize) || 12;
|
||||||
|
const fontWeight = style.fontWeight;
|
||||||
|
const fontFamily = style.fontFamily.split(',')[0].replace(/['"]/g, '') || 'monospace';
|
||||||
|
const isBold = fontWeight === 'bold' || parseInt(fontWeight) >= 600;
|
||||||
|
|
||||||
|
ctx.font = `${isBold ? 'bold' : 'normal'} ${fontSize}px ${fontFamily}`;
|
||||||
|
ctx.fillStyle = style.color || 'black';
|
||||||
|
ctx.textBaseline = 'top'; // Align to top of rect
|
||||||
|
|
||||||
|
// Handle alignment - though for a text node range rect, the rect SHOULD already be aligned by layout
|
||||||
|
// So we can arguably just draw at x.
|
||||||
|
// However, range rects can be tricky.
|
||||||
|
|
||||||
|
// Simple draw for now - rely on DOM layout for positioning
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
|
||||||
|
// Text nodes usually don't need wrapping if they are part of a laid-out line
|
||||||
|
// (the browser already wrapped them into lines, and if it spans lines, we might get a big rect)
|
||||||
|
// If it spans lines, this simple fillText might be issue, but for missing words like "Total", it's fine.
|
||||||
|
ctx.fillText(text, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render text content of an element
|
* Render text content of an element
|
||||||
*
|
*
|
||||||
@ -260,25 +336,25 @@ export class HtmlToImageConverter {
|
|||||||
_renderText(element, ctx, x, y, width, height, style) {
|
_renderText(element, ctx, x, y, width, height, style) {
|
||||||
const text = element.textContent.trim();
|
const text = element.textContent.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
// Set font
|
// Set font
|
||||||
const fontSize = parseFloat(style.fontSize) || 12;
|
const fontSize = parseFloat(style.fontSize) || 12;
|
||||||
const fontWeight = style.fontWeight;
|
const fontWeight = style.fontWeight;
|
||||||
const fontFamily = style.fontFamily.split(',')[0].replace(/['"]/g, '') || 'monospace';
|
const fontFamily = style.fontFamily.split(',')[0].replace(/['"]/g, '') || 'monospace';
|
||||||
const isBold = fontWeight === 'bold' || parseInt(fontWeight) >= 600;
|
const isBold = fontWeight === 'bold' || parseInt(fontWeight) >= 600;
|
||||||
|
|
||||||
ctx.font = `${isBold ? 'bold' : 'normal'} ${fontSize}px ${fontFamily}`;
|
ctx.font = `${isBold ? 'bold' : 'normal'} ${fontSize}px ${fontFamily}`;
|
||||||
ctx.fillStyle = style.color || 'black';
|
ctx.fillStyle = style.color || 'black';
|
||||||
ctx.textBaseline = 'top';
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
// Handle text alignment
|
// Handle text alignment
|
||||||
const textAlign = style.textAlign || 'left';
|
const textAlign = style.textAlign || 'left';
|
||||||
const paddingLeft = parseFloat(style.paddingLeft || 0);
|
const paddingLeft = parseFloat(style.paddingLeft || 0);
|
||||||
const paddingRight = parseFloat(style.paddingRight || 0);
|
const paddingRight = parseFloat(style.paddingRight || 0);
|
||||||
const paddingTop = parseFloat(style.paddingTop || 0);
|
const paddingTop = parseFloat(style.paddingTop || 0);
|
||||||
|
|
||||||
let textX = x + paddingLeft;
|
let textX = x + paddingLeft;
|
||||||
|
|
||||||
if (textAlign === 'center') {
|
if (textAlign === 'center') {
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
textX = x + width / 2;
|
textX = x + width / 2;
|
||||||
@ -288,15 +364,15 @@ export class HtmlToImageConverter {
|
|||||||
} else {
|
} else {
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
}
|
}
|
||||||
|
|
||||||
const textY = y + paddingTop;
|
const textY = y + paddingTop;
|
||||||
|
|
||||||
// Word wrap if needed
|
// Word wrap if needed
|
||||||
const maxWidth = width - paddingLeft - paddingRight;
|
const maxWidth = width - paddingLeft - paddingRight;
|
||||||
const lineHeight = parseFloat(style.lineHeight) || fontSize * 1.4;
|
const lineHeight = parseFloat(style.lineHeight) || fontSize * 1.4;
|
||||||
|
|
||||||
this._wrapAndDrawText(ctx, text, textX, textY, maxWidth, lineHeight);
|
this._wrapAndDrawText(ctx, text, textX, textY, maxWidth, lineHeight);
|
||||||
|
|
||||||
// Reset text align
|
// Reset text align
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
}
|
}
|
||||||
@ -316,11 +392,11 @@ export class HtmlToImageConverter {
|
|||||||
const words = text.split(' ');
|
const words = text.split(' ');
|
||||||
let line = '';
|
let line = '';
|
||||||
let currentY = y;
|
let currentY = y;
|
||||||
|
|
||||||
for (let i = 0; i < words.length; i++) {
|
for (let i = 0; i < words.length; i++) {
|
||||||
const testLine = line + (line ? ' ' : '') + words[i];
|
const testLine = line + (line ? ' ' : '') + words[i];
|
||||||
const metrics = ctx.measureText(testLine);
|
const metrics = ctx.measureText(testLine);
|
||||||
|
|
||||||
if (metrics.width > maxWidth && line) {
|
if (metrics.width > maxWidth && line) {
|
||||||
ctx.fillText(line, x, currentY);
|
ctx.fillText(line, x, currentY);
|
||||||
line = words[i];
|
line = words[i];
|
||||||
@ -329,7 +405,7 @@ export class HtmlToImageConverter {
|
|||||||
line = testLine;
|
line = testLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line) {
|
if (line) {
|
||||||
ctx.fillText(line, x, currentY);
|
ctx.fillText(line, x, currentY);
|
||||||
}
|
}
|
||||||
@ -351,7 +427,7 @@ export class HtmlToImageConverter {
|
|||||||
const borderRight = parseFloat(style.borderRightWidth) || 0;
|
const borderRight = parseFloat(style.borderRightWidth) || 0;
|
||||||
const borderBottom = parseFloat(style.borderBottomWidth) || 0;
|
const borderBottom = parseFloat(style.borderBottomWidth) || 0;
|
||||||
const borderLeft = parseFloat(style.borderLeftWidth) || 0;
|
const borderLeft = parseFloat(style.borderLeftWidth) || 0;
|
||||||
|
|
||||||
if (borderTop > 0) {
|
if (borderTop > 0) {
|
||||||
ctx.strokeStyle = style.borderTopColor || 'black';
|
ctx.strokeStyle = style.borderTopColor || 'black';
|
||||||
ctx.lineWidth = borderTop;
|
ctx.lineWidth = borderTop;
|
||||||
@ -360,7 +436,7 @@ export class HtmlToImageConverter {
|
|||||||
ctx.lineTo(x + width, y);
|
ctx.lineTo(x + width, y);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (borderRight > 0) {
|
if (borderRight > 0) {
|
||||||
ctx.strokeStyle = style.borderRightColor || 'black';
|
ctx.strokeStyle = style.borderRightColor || 'black';
|
||||||
ctx.lineWidth = borderRight;
|
ctx.lineWidth = borderRight;
|
||||||
@ -369,7 +445,7 @@ export class HtmlToImageConverter {
|
|||||||
ctx.lineTo(x + width, y + height);
|
ctx.lineTo(x + width, y + height);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (borderBottom > 0) {
|
if (borderBottom > 0) {
|
||||||
ctx.strokeStyle = style.borderBottomColor || 'black';
|
ctx.strokeStyle = style.borderBottomColor || 'black';
|
||||||
ctx.lineWidth = borderBottom;
|
ctx.lineWidth = borderBottom;
|
||||||
@ -378,7 +454,7 @@ export class HtmlToImageConverter {
|
|||||||
ctx.lineTo(x + width, y + height);
|
ctx.lineTo(x + width, y + height);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (borderLeft > 0) {
|
if (borderLeft > 0) {
|
||||||
ctx.strokeStyle = style.borderLeftColor || 'black';
|
ctx.strokeStyle = style.borderLeftColor || 'black';
|
||||||
ctx.lineWidth = borderLeft;
|
ctx.lineWidth = borderLeft;
|
||||||
@ -421,30 +497,30 @@ export class HtmlToImageConverter {
|
|||||||
* @param {number} height - Canvas height
|
* @param {number} height - Canvas height
|
||||||
*/
|
*/
|
||||||
async _renderDomToCanvas(element, ctx, width, height) {
|
async _renderDomToCanvas(element, ctx, width, height) {
|
||||||
console.log('[HtmlToImage] Rendering DOM to canvas (simple fallback method)...');
|
|
||||||
|
|
||||||
// Set default styles
|
// Set default styles
|
||||||
ctx.fillStyle = 'white';
|
ctx.fillStyle = 'white';
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
ctx.fillStyle = 'black';
|
ctx.fillStyle = 'black';
|
||||||
ctx.textBaseline = 'top';
|
ctx.textBaseline = 'top';
|
||||||
ctx.font = '12px monospace';
|
ctx.font = '12px monospace';
|
||||||
|
|
||||||
const padding = 10;
|
const padding = 10;
|
||||||
const lineHeight = 16;
|
const lineHeight = 16;
|
||||||
const maxWidth = width - (padding * 2);
|
const maxWidth = width - (padding * 2);
|
||||||
let y = padding;
|
let y = padding;
|
||||||
|
|
||||||
// Helper function to wrap text
|
// Helper function to wrap text
|
||||||
const wrapText = (text, maxWidth) => {
|
const wrapText = (text, maxWidth) => {
|
||||||
const words = text.split(' ');
|
const words = text.split(' ');
|
||||||
const lines = [];
|
const lines = [];
|
||||||
let currentLine = '';
|
let currentLine = '';
|
||||||
|
|
||||||
for (const word of words) {
|
for (const word of words) {
|
||||||
const testLine = currentLine + (currentLine ? ' ' : '') + word;
|
const testLine = currentLine + (currentLine ? ' ' : '') + word;
|
||||||
const metrics = ctx.measureText(testLine);
|
const metrics = ctx.measureText(testLine);
|
||||||
|
|
||||||
if (metrics.width > maxWidth && currentLine) {
|
if (metrics.width > maxWidth && currentLine) {
|
||||||
lines.push(currentLine);
|
lines.push(currentLine);
|
||||||
currentLine = word;
|
currentLine = word;
|
||||||
@ -452,36 +528,36 @@ export class HtmlToImageConverter {
|
|||||||
currentLine = testLine;
|
currentLine = testLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLine) {
|
if (currentLine) {
|
||||||
lines.push(currentLine);
|
lines.push(currentLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to draw text with alignment
|
// Helper to draw text with alignment
|
||||||
const drawText = (text, align = 'left', bold = false) => {
|
const drawText = (text, align = 'left', bold = false) => {
|
||||||
if (!text || !text.trim()) return;
|
if (!text || !text.trim()) return;
|
||||||
|
|
||||||
ctx.font = `${bold ? 'bold' : 'normal'} 12px monospace`;
|
ctx.font = `${bold ? 'bold' : 'normal'} 12px monospace`;
|
||||||
const lines = wrapText(text, maxWidth);
|
const lines = wrapText(text, maxWidth);
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
let x = padding;
|
let x = padding;
|
||||||
const textWidth = ctx.measureText(line).width;
|
const textWidth = ctx.measureText(line).width;
|
||||||
|
|
||||||
if (align === 'center') {
|
if (align === 'center') {
|
||||||
x = (width - textWidth) / 2;
|
x = (width - textWidth) / 2;
|
||||||
} else if (align === 'right') {
|
} else if (align === 'right') {
|
||||||
x = width - textWidth - padding;
|
x = width - textWidth - padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fillText(line, x, y);
|
ctx.fillText(line, x, y);
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to draw a line
|
// Helper to draw a line
|
||||||
const drawLine = () => {
|
const drawLine = () => {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@ -492,40 +568,40 @@ export class HtmlToImageConverter {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract and render content recursively
|
// Extract and render content recursively
|
||||||
const processElement = (el) => {
|
const processElement = (el) => {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
const display = style.display;
|
const display = style.display;
|
||||||
|
|
||||||
// Skip hidden elements
|
// Skip hidden elements
|
||||||
if (display === 'none' || style.visibility === 'hidden') {
|
if (display === 'none' || style.visibility === 'hidden') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagName = el.tagName;
|
const tagName = el.tagName;
|
||||||
const textAlign = style.textAlign || 'left';
|
const textAlign = style.textAlign || 'left';
|
||||||
const fontWeight = style.fontWeight;
|
const fontWeight = style.fontWeight;
|
||||||
const isBold = fontWeight === 'bold' || parseInt(fontWeight) >= 600;
|
const isBold = fontWeight === 'bold' || parseInt(fontWeight) >= 600;
|
||||||
|
|
||||||
// Handle special elements
|
// Handle special elements
|
||||||
if (tagName === 'BR') {
|
if (tagName === 'BR') {
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagName === 'HR') {
|
if (tagName === 'HR') {
|
||||||
drawLine();
|
drawLine();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if element has only text content (no child elements)
|
// Check if element has only text content (no child elements)
|
||||||
const hasOnlyText = Array.from(el.childNodes).every(
|
const hasOnlyText = Array.from(el.childNodes).every(
|
||||||
node => node.nodeType === Node.TEXT_NODE
|
node => node.nodeType === Node.TEXT_NODE
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasOnlyText) {
|
if (hasOnlyText) {
|
||||||
const text = el.textContent.trim();
|
const text = el.textContent.trim();
|
||||||
if (text) {
|
if (text) {
|
||||||
@ -544,17 +620,17 @@ export class HtmlToImageConverter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add spacing after block elements
|
// Add spacing after block elements
|
||||||
if (display === 'block' || tagName === 'DIV' || tagName === 'P' || tagName === 'TABLE') {
|
if (display === 'block' || tagName === 'DIV' || tagName === 'P' || tagName === 'TABLE') {
|
||||||
y += lineHeight / 2;
|
y += lineHeight / 2;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start processing
|
// Start processing
|
||||||
try {
|
try {
|
||||||
processElement(element);
|
processElement(element);
|
||||||
console.log('[HtmlToImage] Rendering complete, height used:', y);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[HtmlToImage] Error during rendering:', error);
|
console.error('[HtmlToImage] Error during rendering:', error);
|
||||||
// Ultimate fallback - just print all text
|
// Ultimate fallback - just print all text
|
||||||
@ -574,55 +650,55 @@ export class HtmlToImageConverter {
|
|||||||
* @returns {Uint8Array} Bitmap data
|
* @returns {Uint8Array} Bitmap data
|
||||||
*/
|
*/
|
||||||
canvasToBitmap(canvas) {
|
canvasToBitmap(canvas) {
|
||||||
console.log('[HtmlToImage] Converting canvas to bitmap (optimized)...');
|
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||||
const width = canvas.width;
|
const width = canvas.width;
|
||||||
const height = canvas.height;
|
const height = canvas.height;
|
||||||
|
|
||||||
// Get image data
|
// Get image data
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
const imageData = ctx.getImageData(0, 0, width, height);
|
||||||
const pixels = imageData.data;
|
const pixels = imageData.data;
|
||||||
|
|
||||||
// Convert to monochrome bitmap
|
// Convert to monochrome bitmap
|
||||||
// Each byte represents 8 pixels (1 bit per pixel)
|
// Each byte represents 8 pixels (1 bit per pixel)
|
||||||
const bytesPerLine = Math.ceil(width / 8);
|
const bytesPerLine = Math.ceil(width / 8);
|
||||||
const bitmapData = new Uint8Array(bytesPerLine * height);
|
const bitmapData = new Uint8Array(bytesPerLine * height);
|
||||||
|
|
||||||
// Optimized conversion using lookup table and bitwise operations
|
// Optimized conversion using lookup table and bitwise operations
|
||||||
// Pre-calculate grayscale weights for faster conversion
|
// Pre-calculate grayscale weights for faster conversion
|
||||||
const rWeight = 0.299;
|
const rWeight = 0.299;
|
||||||
const gWeight = 0.587;
|
const gWeight = 0.587;
|
||||||
const bWeight = 0.114;
|
const bWeight = 0.114;
|
||||||
|
|
||||||
let byteIndex = 0;
|
let byteIndex = 0;
|
||||||
let currentByte = 0;
|
let currentByte = 0;
|
||||||
let bitPosition = 7;
|
let bitPosition = 7;
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < height; y++) {
|
||||||
for (let x = 0; x < width; x++) {
|
for (let x = 0; x < width; x++) {
|
||||||
const pixelIndex = (y * width + x) * 4;
|
const pixelIndex = (y * width + x) * 4;
|
||||||
|
|
||||||
// Fast grayscale conversion using weighted average
|
// Fast grayscale conversion using weighted average
|
||||||
const gray = pixels[pixelIndex] * rWeight +
|
const gray = pixels[pixelIndex] * rWeight +
|
||||||
pixels[pixelIndex + 1] * gWeight +
|
pixels[pixelIndex + 1] * gWeight +
|
||||||
pixels[pixelIndex + 2] * bWeight;
|
pixels[pixelIndex + 2] * bWeight;
|
||||||
|
|
||||||
// Threshold to black or white
|
// Threshold to black or white
|
||||||
if (gray < 128) {
|
if (gray < 128) {
|
||||||
currentByte |= (1 << bitPosition);
|
currentByte |= (1 << bitPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
bitPosition--;
|
bitPosition--;
|
||||||
|
|
||||||
if (bitPosition < 0) {
|
if (bitPosition < 0) {
|
||||||
bitmapData[byteIndex++] = currentByte;
|
bitmapData[byteIndex++] = currentByte;
|
||||||
currentByte = 0;
|
currentByte = 0;
|
||||||
bitPosition = 7;
|
bitPosition = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle remaining bits at end of line
|
// Handle remaining bits at end of line
|
||||||
if (bitPosition !== 7) {
|
if (bitPosition !== 7) {
|
||||||
bitmapData[byteIndex++] = currentByte;
|
bitmapData[byteIndex++] = currentByte;
|
||||||
@ -630,14 +706,10 @@ export class HtmlToImageConverter {
|
|||||||
bitPosition = 7;
|
bitPosition = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
console.log('[HtmlToImage] Bitmap conversion took:', (endTime - startTime).toFixed(2), 'ms');
|
|
||||||
console.log('[HtmlToImage] Bitmap dimensions:', width, 'pixels x', height, 'pixels');
|
|
||||||
console.log('[HtmlToImage] Bytes per line:', bytesPerLine, 'bytes');
|
|
||||||
console.log('[HtmlToImage] Total bitmap size:', bitmapData.length, 'bytes');
|
|
||||||
console.log('[HtmlToImage] Expected size:', bytesPerLine * height, 'bytes');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: bitmapData,
|
data: bitmapData,
|
||||||
width: width,
|
width: width,
|
||||||
@ -653,16 +725,16 @@ export class HtmlToImageConverter {
|
|||||||
* @returns {Promise<Object>} Bitmap data with dimensions
|
* @returns {Promise<Object>} Bitmap data with dimensions
|
||||||
*/
|
*/
|
||||||
async htmlToBitmap(element) {
|
async htmlToBitmap(element) {
|
||||||
console.log('[HtmlToImage] Converting HTML to bitmap...');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Convert HTML to canvas
|
// Convert HTML to canvas
|
||||||
const canvas = await this.htmlToCanvas(element);
|
const canvas = await this.htmlToCanvas(element);
|
||||||
|
|
||||||
// Convert canvas to bitmap
|
// Convert canvas to bitmap
|
||||||
const bitmap = this.canvasToBitmap(canvas);
|
const bitmap = this.canvasToBitmap(canvas);
|
||||||
|
|
||||||
console.log('[HtmlToImage] Conversion complete');
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[HtmlToImage] Conversion failed:', error);
|
console.error('[HtmlToImage] Conversion failed:', error);
|
||||||
@ -679,26 +751,29 @@ export class HtmlToImageConverter {
|
|||||||
setPaperWidth(widthMm, dpi = 203) {
|
setPaperWidth(widthMm, dpi = 203) {
|
||||||
this.paperWidthMm = widthMm;
|
this.paperWidthMm = widthMm;
|
||||||
this.dpi = dpi;
|
this.dpi = dpi;
|
||||||
|
|
||||||
// Calculate dots per mm based on DPI
|
// Calculate dots per mm based on DPI
|
||||||
// 203 DPI = 8 dots/mm (203 / 25.4)
|
// 203 DPI = 8 dots/mm (203 / 25.4)
|
||||||
// 304 DPI = 12 dots/mm (304 / 25.4)
|
// 304 DPI = 12 dots/mm (304 / 25.4)
|
||||||
const dotsPerMm = Math.round(dpi / 25.4);
|
const dotsPerMm = Math.round(dpi / 25.4);
|
||||||
|
|
||||||
// Calculate pixel width based on paper size and DPI
|
// Calculate pixel width based on PRINTABLE width (essential for thermal printers)
|
||||||
// Use FULL width without margins for maximum usage
|
// 58mm Paper -> ~48mm Printable
|
||||||
|
// 80mm Paper -> ~72mm Printable
|
||||||
|
let printableWidthMm;
|
||||||
|
|
||||||
if (widthMm === 58) {
|
if (widthMm === 58) {
|
||||||
// 58mm paper at selected DPI
|
printableWidthMm = 48; // Standard printable width for 58mm
|
||||||
this.paperWidth = widthMm * dotsPerMm;
|
|
||||||
} else if (widthMm === 80) {
|
} else if (widthMm === 80) {
|
||||||
// 80mm paper at selected DPI
|
printableWidthMm = 72; // Standard printable width for 80mm
|
||||||
this.paperWidth = widthMm * dotsPerMm;
|
|
||||||
} else {
|
} else {
|
||||||
// Custom width: use full width
|
// Custom width: subtract ~10mm margin
|
||||||
this.paperWidth = widthMm * dotsPerMm;
|
printableWidthMm = Math.max(widthMm - 10, widthMm * 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[HtmlToImage] Setting paper width to', widthMm, 'mm at', dpi, 'DPI (', dotsPerMm, 'dots/mm,', this.paperWidth, 'pixels)');
|
this.paperWidth = Math.floor(printableWidthMm * dotsPerMm);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -60,69 +60,63 @@ patch(PosPrinterService.prototype, {
|
|||||||
async printHtml(el) {
|
async printHtml(el) {
|
||||||
// Wrap entire method in try-catch to catch any errors
|
// Wrap entire method in try-catch to catch any errors
|
||||||
try {
|
try {
|
||||||
console.log('[BluetoothPrint] printHtml() called');
|
|
||||||
console.log('[BluetoothPrint] Element type:', el?.constructor?.name);
|
|
||||||
console.log('[BluetoothPrint] Element tag:', el?.tagName);
|
|
||||||
console.log('[BluetoothPrint] Element classes:', el?.className);
|
|
||||||
|
|
||||||
// Check if Web Bluetooth API is available
|
// Check if Web Bluetooth API is available
|
||||||
if (!navigator.bluetooth) {
|
if (!navigator.bluetooth) {
|
||||||
console.log('[BluetoothPrint] Web Bluetooth API not available, using browser print dialog');
|
|
||||||
await this._printViaBrowserDialog(el);
|
await this._printViaBrowserDialog(el);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
console.log('[BluetoothPrint] Web Bluetooth API available');
|
|
||||||
|
|
||||||
// Check if a Bluetooth printer is configured (from localStorage)
|
// Check if a Bluetooth printer is configured (from localStorage)
|
||||||
// We don't need POS config - we check if user has configured a printer
|
// We don't need POS config - we check if user has configured a printer
|
||||||
const storage = new BluetoothPrinterStorage();
|
const storage = new BluetoothPrinterStorage();
|
||||||
const config = storage.loadConfiguration(1); // Default POS config ID
|
const config = storage.loadConfiguration(1); // Default POS config ID
|
||||||
|
|
||||||
if (!config || !config.deviceId) {
|
if (!config || !config.deviceId) {
|
||||||
console.log('[BluetoothPrint] No Bluetooth printer configured, using standard print');
|
|
||||||
console.log('[BluetoothPrint] Calling originalPrintHtml with:', el);
|
|
||||||
try {
|
try {
|
||||||
const result = await originalPrintHtml.call(this, el);
|
const result = await originalPrintHtml.call(this, el);
|
||||||
console.log('[BluetoothPrint] originalPrintHtml returned:', result);
|
|
||||||
|
|
||||||
// If original method returned false, it didn't handle the print
|
// If original method returned false, it didn't handle the print
|
||||||
// So we need to handle it ourselves
|
// So we need to handle it ourselves
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
console.log('[BluetoothPrint] originalPrintHtml returned false, opening browser print dialog directly');
|
|
||||||
await this._printViaBrowserDialog(el);
|
await this._printViaBrowserDialog(el);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[BluetoothPrint] Error calling originalPrintHtml:', error);
|
console.error('[BluetoothPrint] Error calling originalPrintHtml:', error);
|
||||||
// Fallback to browser print dialog
|
// Fallback to browser print dialog
|
||||||
console.log('[BluetoothPrint] Falling back to browser print dialog');
|
|
||||||
await this._printViaBrowserDialog(el);
|
await this._printViaBrowserDialog(el);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Bluetooth printer configured:', config.deviceName);
|
|
||||||
|
|
||||||
// Initialize bluetooth services
|
// Initialize bluetooth services
|
||||||
console.log('[BluetoothPrint] Initializing bluetooth services...');
|
const services = initializeBluetoothPrinting(null); // Restored
|
||||||
const services = initializeBluetoothPrinting(null);
|
|
||||||
console.log('[BluetoothPrint] Bluetooth services initialized');
|
|
||||||
|
|
||||||
// Check if printer is actually connected
|
// Check if printer is actually connected
|
||||||
const connectionStatus = services.bluetoothManager.getConnectionStatus();
|
const connectionStatus = services.bluetoothManager.getConnectionStatus();
|
||||||
console.log('[BluetoothPrint] Current connection status:', connectionStatus);
|
|
||||||
|
|
||||||
if (connectionStatus !== 'connected') {
|
if (connectionStatus !== 'connected') {
|
||||||
console.log('[BluetoothPrint] Printer not connected, attempting to reconnect...');
|
|
||||||
// Try to reconnect
|
// Try to reconnect
|
||||||
try {
|
try {
|
||||||
await services.bluetoothManager.autoReconnect();
|
await services.bluetoothManager.autoReconnect();
|
||||||
console.log('[BluetoothPrint] Reconnection successful');
|
|
||||||
} catch (reconnectError) {
|
} catch (reconnectError) {
|
||||||
console.error('[BluetoothPrint] Reconnection failed:', reconnectError);
|
console.error('[BluetoothPrint] Reconnection failed:', reconnectError);
|
||||||
console.log('[BluetoothPrint] Falling back to browser print dialog');
|
|
||||||
await this._printViaBrowserDialog(el);
|
await this._printViaBrowserDialog(el);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -130,17 +124,17 @@ patch(PosPrinterService.prototype, {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Attempt bluetooth printing
|
// Attempt bluetooth printing
|
||||||
console.log('[BluetoothPrint] Attempting bluetooth print...');
|
|
||||||
await this._printViaBluetoothFromHtml(el, services, config);
|
await this._printViaBluetoothFromHtml(el, services, config);
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Print completed successfully');
|
|
||||||
return true;
|
return true;
|
||||||
} catch (printError) {
|
} catch (printError) {
|
||||||
console.error('[BluetoothPrint] Bluetooth print failed:', printError);
|
console.error('[BluetoothPrint] Bluetooth print failed:', printError);
|
||||||
console.error('[BluetoothPrint] Error stack:', printError.stack);
|
console.error('[BluetoothPrint] Error stack:', printError.stack);
|
||||||
|
|
||||||
// Fallback to browser print dialog
|
// Fallback to browser print dialog
|
||||||
console.log('[BluetoothPrint] Falling back to browser print dialog after error');
|
|
||||||
await this._printViaBrowserDialog(el);
|
await this._printViaBrowserDialog(el);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -149,7 +143,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
console.error('[BluetoothPrint] Error name:', error.name);
|
console.error('[BluetoothPrint] Error name:', error.name);
|
||||||
console.error('[BluetoothPrint] Error message:', error.message);
|
console.error('[BluetoothPrint] Error message:', error.message);
|
||||||
console.error('[BluetoothPrint] Error stack:', error.stack);
|
console.error('[BluetoothPrint] Error stack:', error.stack);
|
||||||
console.log('[BluetoothPrint] Falling back to browser print dialog due to critical error');
|
|
||||||
try {
|
try {
|
||||||
await this._printViaBrowserDialog(el);
|
await this._printViaBrowserDialog(el);
|
||||||
return true;
|
return true;
|
||||||
@ -169,8 +163,8 @@ patch(PosPrinterService.prototype, {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async _printViaBrowserDialog(el) {
|
async _printViaBrowserDialog(el) {
|
||||||
console.log('[BluetoothPrint] Opening browser print dialog...');
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Create a hidden iframe for printing
|
// Create a hidden iframe for printing
|
||||||
const printFrame = document.createElement('iframe');
|
const printFrame = document.createElement('iframe');
|
||||||
@ -181,32 +175,32 @@ patch(PosPrinterService.prototype, {
|
|||||||
printFrame.style.height = '0';
|
printFrame.style.height = '0';
|
||||||
printFrame.style.border = '0';
|
printFrame.style.border = '0';
|
||||||
document.body.appendChild(printFrame);
|
document.body.appendChild(printFrame);
|
||||||
|
|
||||||
let printTriggered = false;
|
let printTriggered = false;
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (timeoutId) {
|
if (timeoutId) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
if (printFrame.parentNode) {
|
if (printFrame.parentNode) {
|
||||||
document.body.removeChild(printFrame);
|
document.body.removeChild(printFrame);
|
||||||
console.log('[BluetoothPrint] Iframe cleaned up');
|
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
const triggerPrint = () => {
|
const triggerPrint = () => {
|
||||||
if (printTriggered) {
|
if (printTriggered) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
printTriggered = true;
|
printTriggered = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
printFrame.contentWindow.focus();
|
printFrame.contentWindow.focus();
|
||||||
printFrame.contentWindow.print();
|
printFrame.contentWindow.print();
|
||||||
console.log('[BluetoothPrint] Print dialog opened');
|
|
||||||
|
|
||||||
// Clean up after a delay
|
// Clean up after a delay
|
||||||
setTimeout(cleanup, 1000);
|
setTimeout(cleanup, 1000);
|
||||||
} catch (printError) {
|
} catch (printError) {
|
||||||
@ -214,12 +208,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Write receipt content to iframe
|
// Write receipt content to iframe
|
||||||
const frameDoc = printFrame.contentWindow.document;
|
const frameDoc = printFrame.contentWindow.document;
|
||||||
frameDoc.open();
|
frameDoc.open();
|
||||||
|
|
||||||
// Add basic styling for print
|
// Add basic styling for print
|
||||||
const printHtml = `
|
const printHtml = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -262,27 +256,27 @@ patch(PosPrinterService.prototype, {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
frameDoc.write(printHtml);
|
frameDoc.write(printHtml);
|
||||||
frameDoc.close();
|
frameDoc.close();
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Receipt content written to iframe');
|
|
||||||
|
|
||||||
// Wait for content to load
|
// Wait for content to load
|
||||||
printFrame.contentWindow.addEventListener('load', () => {
|
printFrame.contentWindow.addEventListener('load', () => {
|
||||||
console.log('[BluetoothPrint] Iframe loaded, triggering print...');
|
|
||||||
// Small delay to ensure rendering is complete
|
// Small delay to ensure rendering is complete
|
||||||
setTimeout(triggerPrint, 100);
|
setTimeout(triggerPrint, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fallback if load event doesn't fire within 2 seconds
|
// Fallback if load event doesn't fire within 2 seconds
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
if (!printTriggered) {
|
if (!printTriggered) {
|
||||||
console.log('[BluetoothPrint] Load timeout, attempting print anyway...');
|
|
||||||
triggerPrint();
|
triggerPrint();
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[BluetoothPrint] Error in _printViaBrowserDialog:', error);
|
console.error('[BluetoothPrint] Error in _printViaBrowserDialog:', error);
|
||||||
cleanup();
|
cleanup();
|
||||||
@ -303,65 +297,46 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
async _printViaBluetoothFromHtml(el, services, config) {
|
async _printViaBluetoothFromHtml(el, services, config) {
|
||||||
const { bluetoothManager } = services;
|
const { bluetoothManager } = services;
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Starting bluetooth print from HTML...');
|
|
||||||
console.log('[BluetoothPrint] Using GRAPHICS MODE for exact HTML layout');
|
|
||||||
console.log('[BluetoothPrint] Element:', el);
|
|
||||||
console.log('[BluetoothPrint] Config:', config);
|
|
||||||
|
|
||||||
// Check connection status
|
// Check connection status
|
||||||
const status = bluetoothManager.getConnectionStatus();
|
const status = bluetoothManager.getConnectionStatus();
|
||||||
console.log('[BluetoothPrint] Connection status:', status);
|
|
||||||
|
|
||||||
if (status !== 'connected') {
|
if (status !== 'connected') {
|
||||||
throw new PrinterNotConnectedError('Bluetooth printer is not connected');
|
throw new PrinterNotConnectedError('Bluetooth printer is not connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Convert HTML to bitmap image
|
// Convert HTML to bitmap image
|
||||||
console.log('[BluetoothPrint] Converting HTML to bitmap...');
|
|
||||||
console.log('[BluetoothPrint] Creating HtmlToImageConverter...');
|
|
||||||
const converter = new HtmlToImageConverter();
|
const converter = new HtmlToImageConverter();
|
||||||
console.log('[BluetoothPrint] HtmlToImageConverter created successfully');
|
|
||||||
|
// Get paper width and DPI from configuration
|
||||||
|
|
||||||
|
|
||||||
// Get paper width and DPI from configuration
|
// Get paper width and DPI from configuration
|
||||||
const paperWidthMm = config?.settings?.paperWidthMm || 58;
|
const paperWidthMm = config?.settings?.paperWidthMm || 58;
|
||||||
const dpi = config?.settings?.dpi || 203;
|
const dpi = config?.settings?.dpi || 203;
|
||||||
console.log('[BluetoothPrint] Using paper width:', paperWidthMm, 'mm');
|
|
||||||
console.log('[BluetoothPrint] Using printer DPI:', dpi);
|
|
||||||
console.log('[BluetoothPrint] Setting paper width and DPI on converter...');
|
|
||||||
converter.setPaperWidth(paperWidthMm, dpi);
|
converter.setPaperWidth(paperWidthMm, dpi);
|
||||||
console.log('[BluetoothPrint] Paper width and DPI set successfully');
|
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Converting HTML to bitmap...');
|
|
||||||
const bitmap = await converter.htmlToBitmap(el);
|
const bitmap = await converter.htmlToBitmap(el);
|
||||||
console.log('[BluetoothPrint] Bitmap created:', bitmap.width, 'x', bitmap.height);
|
|
||||||
console.log('[BluetoothPrint] Bitmap data length:', bitmap.data.length);
|
|
||||||
|
|
||||||
// Generate ESC/POS graphics commands
|
// Generate ESC/POS graphics commands
|
||||||
console.log('[BluetoothPrint] Generating ESC/POS graphics commands...');
|
|
||||||
console.log('[BluetoothPrint] Creating EscPosGraphics...');
|
|
||||||
const graphicsGenerator = new EscPosGraphics();
|
const graphicsGenerator = new EscPosGraphics();
|
||||||
console.log('[BluetoothPrint] EscPosGraphics created successfully');
|
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Generating bitmap commands...');
|
|
||||||
const escposData = graphicsGenerator.generateBitmapCommands(bitmap);
|
const escposData = graphicsGenerator.generateBitmapCommands(bitmap);
|
||||||
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of graphics data');
|
|
||||||
|
|
||||||
// Send data to printer
|
// Send data to printer
|
||||||
console.log('[BluetoothPrint] Sending graphics to printer...');
|
|
||||||
const startTime = performance.now();
|
|
||||||
await bluetoothManager.sendData(escposData);
|
await bluetoothManager.sendData(escposData);
|
||||||
const endTime = performance.now();
|
|
||||||
console.log('[BluetoothPrint] Graphics print completed successfully in', (endTime - startTime).toFixed(0), 'ms');
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[BluetoothPrint] Graphics mode failed:', error);
|
console.error('[BluetoothPrint] Graphics mode failed:', error);
|
||||||
console.error('[BluetoothPrint] Error name:', error.name);
|
console.error('[BluetoothPrint] Error name:', error.name);
|
||||||
console.error('[BluetoothPrint] Error message:', error.message);
|
console.error('[BluetoothPrint] Error message:', error.message);
|
||||||
console.error('[BluetoothPrint] Error stack:', error.stack);
|
console.error('[BluetoothPrint] Error stack:', error.stack);
|
||||||
console.log('[BluetoothPrint] Falling back to text mode...');
|
|
||||||
|
|
||||||
// Fallback to text mode if graphics fails
|
// Fallback to text mode if graphics fails
|
||||||
await this._printViaBluetoothTextMode(el, services);
|
await this._printViaBluetoothTextMode(el, services);
|
||||||
}
|
}
|
||||||
@ -378,23 +353,17 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
async _printViaBluetoothTextMode(el, services) {
|
async _printViaBluetoothTextMode(el, services) {
|
||||||
const { bluetoothManager, escposGenerator } = services;
|
const { bluetoothManager, escposGenerator } = services;
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Using TEXT MODE (fallback)');
|
|
||||||
|
|
||||||
// Parse receipt data from HTML element
|
// Parse receipt data from HTML element
|
||||||
console.log('[BluetoothPrint] Parsing receipt data from HTML...');
|
|
||||||
const receiptData = this._parseReceiptDataFromHtml(el);
|
const receiptData = this._parseReceiptDataFromHtml(el);
|
||||||
console.log('[BluetoothPrint] Parsed receipt data:', JSON.stringify(receiptData, null, 2));
|
|
||||||
|
|
||||||
// Generate ESC/POS commands
|
// Generate ESC/POS commands
|
||||||
console.log('[BluetoothPrint] Generating ESC/POS text commands...');
|
|
||||||
const escposData = escposGenerator.generateReceipt(receiptData);
|
const escposData = escposGenerator.generateReceipt(receiptData);
|
||||||
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of text data');
|
|
||||||
|
|
||||||
// Send data to printer
|
// Send data to printer
|
||||||
console.log('[BluetoothPrint] Sending text to printer...');
|
|
||||||
await bluetoothManager.sendData(escposData);
|
await bluetoothManager.sendData(escposData);
|
||||||
console.log('[BluetoothPrint] Text print completed successfully');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -407,34 +376,25 @@ patch(PosPrinterService.prototype, {
|
|||||||
* @throws {Error} If printing fails
|
* @throws {Error} If printing fails
|
||||||
*/
|
*/
|
||||||
async _printViaBluetooth(el, pos) {
|
async _printViaBluetooth(el, pos) {
|
||||||
|
|
||||||
const services = initializeBluetoothPrinting();
|
const services = initializeBluetoothPrinting();
|
||||||
const { bluetoothManager, escposGenerator } = services;
|
const { bluetoothManager, escposGenerator } = services;
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Starting bluetooth print...');
|
|
||||||
console.log('[BluetoothPrint] Element:', el);
|
|
||||||
|
|
||||||
// Check connection status
|
// Check connection status
|
||||||
const status = bluetoothManager.getConnectionStatus();
|
const status = bluetoothManager.getConnectionStatus();
|
||||||
console.log('[BluetoothPrint] Connection status:', status);
|
|
||||||
|
|
||||||
if (status !== 'connected') {
|
if (status !== 'connected') {
|
||||||
throw new PrinterNotConnectedError('Bluetooth printer is not connected');
|
throw new PrinterNotConnectedError('Bluetooth printer is not connected');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse receipt data from POS order
|
// Parse receipt data from POS order
|
||||||
console.log('[BluetoothPrint] Parsing receipt data...');
|
|
||||||
const receiptData = this._parseReceiptDataFromPos(pos);
|
const receiptData = this._parseReceiptDataFromPos(pos);
|
||||||
console.log('[BluetoothPrint] Receipt data:', receiptData);
|
|
||||||
|
|
||||||
// Generate ESC/POS commands
|
// Generate ESC/POS commands
|
||||||
console.log('[BluetoothPrint] Generating ESC/POS commands...');
|
|
||||||
const escposData = escposGenerator.generateReceipt(receiptData);
|
const escposData = escposGenerator.generateReceipt(receiptData);
|
||||||
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of ESC/POS data');
|
|
||||||
|
|
||||||
// Send data to printer
|
// Send data to printer
|
||||||
console.log('[BluetoothPrint] Sending to printer...');
|
|
||||||
await bluetoothManager.sendData(escposData);
|
await bluetoothManager.sendData(escposData);
|
||||||
console.log('[BluetoothPrint] Print completed successfully');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -447,12 +407,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
async _printViaFallback(receipt) {
|
async _printViaFallback(receipt) {
|
||||||
// Use the standard Odoo POS printing mechanism
|
// Use the standard Odoo POS printing mechanism
|
||||||
// This typically opens the browser print dialog
|
// This typically opens the browser print dialog
|
||||||
|
|
||||||
// Create a hidden iframe for printing
|
// Create a hidden iframe for printing
|
||||||
const printFrame = document.createElement('iframe');
|
const printFrame = document.createElement('iframe');
|
||||||
printFrame.style.display = 'none';
|
printFrame.style.display = 'none';
|
||||||
document.body.appendChild(printFrame);
|
document.body.appendChild(printFrame);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Write receipt content to iframe
|
// Write receipt content to iframe
|
||||||
const frameDoc = printFrame.contentWindow.document;
|
const frameDoc = printFrame.contentWindow.document;
|
||||||
@ -481,7 +441,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
</html>
|
</html>
|
||||||
`);
|
`);
|
||||||
frameDoc.close();
|
frameDoc.close();
|
||||||
|
|
||||||
// Wait for content to load
|
// Wait for content to load
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
if (printFrame.contentWindow.document.readyState === 'complete') {
|
if (printFrame.contentWindow.document.readyState === 'complete') {
|
||||||
@ -490,10 +450,10 @@ patch(PosPrinterService.prototype, {
|
|||||||
printFrame.contentWindow.addEventListener('load', resolve);
|
printFrame.contentWindow.addEventListener('load', resolve);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger print dialog
|
// Trigger print dialog
|
||||||
printFrame.contentWindow.print();
|
printFrame.contentWindow.print();
|
||||||
|
|
||||||
// Clean up after a delay
|
// Clean up after a delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.removeChild(printFrame);
|
document.body.removeChild(printFrame);
|
||||||
@ -520,7 +480,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
setTimeout(() => reject(new TimeoutError(`Print operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
setTimeout(() => reject(new TimeoutError(`Print operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.race([sendFunction(), timeoutPromise]);
|
await Promise.race([sendFunction(), timeoutPromise]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -545,25 +505,19 @@ patch(PosPrinterService.prototype, {
|
|||||||
* @returns {Object} Structured receipt data
|
* @returns {Object} Structured receipt data
|
||||||
*/
|
*/
|
||||||
_parseReceiptDataFromHtml(el) {
|
_parseReceiptDataFromHtml(el) {
|
||||||
console.log('[BluetoothPrint] _parseReceiptDataFromHtml called');
|
|
||||||
console.log('[BluetoothPrint] Receipt HTML structure:', el.outerHTML.substring(0, 500));
|
|
||||||
console.log('[BluetoothPrint] Receipt classes:', el.className);
|
|
||||||
console.log('[BluetoothPrint] Receipt children count:', el.children.length);
|
|
||||||
|
|
||||||
// Extract text content from HTML
|
// Extract text content from HTML
|
||||||
const getText = (selector) => {
|
const getText = (selector) => {
|
||||||
const element = el.querySelector(selector);
|
const element = el.querySelector(selector);
|
||||||
const text = element ? element.textContent.trim() : '';
|
const text = element ? element.textContent.trim() : '';
|
||||||
console.log(`[BluetoothPrint] getText('${selector}'):`, text || '(empty)');
|
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAll = (selector) => {
|
const getAll = (selector) => {
|
||||||
const elements = Array.from(el.querySelectorAll(selector));
|
const elements = Array.from(el.querySelectorAll(selector));
|
||||||
console.log(`[BluetoothPrint] getAll('${selector}'):`, elements.length, 'elements found');
|
|
||||||
return elements;
|
return elements;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse header (company info)
|
// Parse header (company info)
|
||||||
const headerData = {
|
const headerData = {
|
||||||
companyName: getText('.pos-receipt-company-name') || getText('h2') || getText('h3') || 'Receipt',
|
companyName: getText('.pos-receipt-company-name') || getText('h2') || getText('h3') || 'Receipt',
|
||||||
@ -571,7 +525,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
phone: getText('.pos-receipt-phone') || '',
|
phone: getText('.pos-receipt-phone') || '',
|
||||||
taxId: getText('.pos-receipt-tax-id') || ''
|
taxId: getText('.pos-receipt-tax-id') || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse order info
|
// Parse order info
|
||||||
const orderData = {
|
const orderData = {
|
||||||
orderName: getText('.pos-receipt-order-name') || getText('.order-name') || getText('.pos-receipt-order-data') || '',
|
orderName: getText('.pos-receipt-order-name') || getText('.order-name') || getText('.pos-receipt-order-data') || '',
|
||||||
@ -579,9 +533,8 @@ patch(PosPrinterService.prototype, {
|
|||||||
cashier: getText('.pos-receipt-cashier') || getText('.cashier') || '',
|
cashier: getText('.pos-receipt-cashier') || getText('.cashier') || '',
|
||||||
customer: getText('.pos-receipt-customer') || getText('.customer') || null
|
customer: getText('.pos-receipt-customer') || getText('.customer') || null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse order lines - try multiple selectors
|
// Parse order lines - try multiple selectors
|
||||||
console.log('[BluetoothPrint] Searching for order lines...');
|
|
||||||
let lineElements = getAll('.orderline');
|
let lineElements = getAll('.orderline');
|
||||||
if (lineElements.length === 0) {
|
if (lineElements.length === 0) {
|
||||||
lineElements = getAll('.pos-receipt-orderline');
|
lineElements = getAll('.pos-receipt-orderline');
|
||||||
@ -596,72 +549,66 @@ patch(PosPrinterService.prototype, {
|
|||||||
// Try to find any table rows
|
// Try to find any table rows
|
||||||
lineElements = getAll('tbody tr');
|
lineElements = getAll('tbody tr');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Found', lineElements.length, 'line elements');
|
|
||||||
|
|
||||||
const lines = lineElements.map((line, index) => {
|
const lines = lineElements.map((line, index) => {
|
||||||
console.log(`[BluetoothPrint] Processing line ${index}:`, line.outerHTML.substring(0, 200));
|
|
||||||
|
|
||||||
const productName = line.querySelector('.product-name, td:first-child, .pos-receipt-left-align')?.textContent.trim() || '';
|
const productName = line.querySelector('.product-name, td:first-child, .pos-receipt-left-align')?.textContent.trim() || '';
|
||||||
const qtyText = line.querySelector('.qty, .quantity')?.textContent.trim() || '1';
|
const qtyText = line.querySelector('.qty, .quantity')?.textContent.trim() || '1';
|
||||||
const priceText = line.querySelector('.price, .price-unit')?.textContent.trim() || '0';
|
const priceText = line.querySelector('.price, .price-unit')?.textContent.trim() || '0';
|
||||||
const totalText = line.querySelector('.price-total, .total, td:last-child, .pos-receipt-right-align')?.textContent.trim() || '0';
|
const totalText = line.querySelector('.price-total, .total, td:last-child, .pos-receipt-right-align')?.textContent.trim() || '0';
|
||||||
|
|
||||||
console.log(`[BluetoothPrint] Line ${index} raw data:`, { productName, qtyText, priceText, totalText });
|
|
||||||
|
|
||||||
// Parse numbers (remove currency symbols and commas)
|
// Parse numbers (remove currency symbols and commas)
|
||||||
const parseNumber = (str) => {
|
const parseNumber = (str) => {
|
||||||
const cleaned = str.replace(/[^0-9.-]/g, '');
|
const cleaned = str.replace(/[^0-9.-]/g, '');
|
||||||
return parseFloat(cleaned) || 0;
|
return parseFloat(cleaned) || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsedLine = {
|
const parsedLine = {
|
||||||
productName: productName,
|
productName: productName,
|
||||||
quantity: parseNumber(qtyText),
|
quantity: parseNumber(qtyText),
|
||||||
price: parseNumber(priceText),
|
price: parseNumber(priceText),
|
||||||
total: parseNumber(totalText)
|
total: parseNumber(totalText)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[BluetoothPrint] Line ${index} parsed:`, parsedLine);
|
|
||||||
|
|
||||||
return parsedLine;
|
return parsedLine;
|
||||||
}).filter(line => line.productName); // Filter out empty lines
|
}).filter(line => line.productName); // Filter out empty lines
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Parsed', lines.length, 'lines from HTML');
|
|
||||||
console.log('[BluetoothPrint] All lines:', JSON.stringify(lines, null, 2));
|
|
||||||
|
|
||||||
// Parse totals
|
// Parse totals
|
||||||
console.log('[BluetoothPrint] Parsing totals...');
|
|
||||||
const totals = {
|
const totals = {
|
||||||
subtotal: this._parseAmount(getText('.pos-receipt-subtotal, .subtotal')),
|
subtotal: this._parseAmount(getText('.pos-receipt-subtotal, .subtotal')),
|
||||||
tax: this._parseAmount(getText('.pos-receipt-tax, .tax')),
|
tax: this._parseAmount(getText('.pos-receipt-tax, .tax')),
|
||||||
discount: this._parseAmount(getText('.pos-receipt-discount, .discount')),
|
discount: this._parseAmount(getText('.pos-receipt-discount, .discount')),
|
||||||
total: this._parseAmount(getText('.pos-receipt-total, .total, .amount-total'))
|
total: this._parseAmount(getText('.pos-receipt-total, .total, .amount-total'))
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Parsed totals:', totals);
|
|
||||||
|
|
||||||
// If totals not found in specific elements, calculate from lines
|
// If totals not found in specific elements, calculate from lines
|
||||||
if (totals.total === 0 && lines.length > 0) {
|
if (totals.total === 0 && lines.length > 0) {
|
||||||
console.log('[BluetoothPrint] Total is 0, calculating from lines...');
|
|
||||||
totals.total = lines.reduce((sum, line) => sum + line.total, 0);
|
totals.total = lines.reduce((sum, line) => sum + line.total, 0);
|
||||||
totals.subtotal = totals.total;
|
totals.subtotal = totals.total;
|
||||||
console.log('[BluetoothPrint] Calculated totals:', totals);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse payment info
|
// Parse payment info
|
||||||
const paymentData = {
|
const paymentData = {
|
||||||
method: getText('.pos-receipt-payment-method, .payment-method') || 'Cash',
|
method: getText('.pos-receipt-payment-method, .payment-method') || 'Cash',
|
||||||
amount: this._parseAmount(getText('.pos-receipt-payment-amount, .payment-amount')) || totals.total,
|
amount: this._parseAmount(getText('.pos-receipt-payment-amount, .payment-amount')) || totals.total,
|
||||||
change: this._parseAmount(getText('.pos-receipt-change, .change')) || 0
|
change: this._parseAmount(getText('.pos-receipt-change, .change')) || 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
const footerData = {
|
const footerData = {
|
||||||
message: getText('.pos-receipt-footer, .receipt-footer') || 'Thank you for your business!',
|
message: getText('.pos-receipt-footer, .receipt-footer') || 'Thank you for your business!',
|
||||||
barcode: orderData.orderName || null
|
barcode: orderData.orderName || null
|
||||||
};
|
};
|
||||||
|
|
||||||
const receiptData = {
|
const receiptData = {
|
||||||
headerData,
|
headerData,
|
||||||
orderData,
|
orderData,
|
||||||
@ -670,8 +617,8 @@ patch(PosPrinterService.prototype, {
|
|||||||
paymentData,
|
paymentData,
|
||||||
footerData
|
footerData
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Parsed receipt data from HTML:', receiptData);
|
|
||||||
return receiptData;
|
return receiptData;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -697,35 +644,28 @@ patch(PosPrinterService.prototype, {
|
|||||||
* @returns {Object} Structured receipt data
|
* @returns {Object} Structured receipt data
|
||||||
*/
|
*/
|
||||||
_parseReceiptDataFromPos(pos) {
|
_parseReceiptDataFromPos(pos) {
|
||||||
console.log('[BluetoothPrint] _parseReceiptDataFromPos called');
|
|
||||||
console.log('[BluetoothPrint] POS object keys:', Object.keys(pos));
|
|
||||||
|
|
||||||
// In Odoo 18, get current order from POS
|
// In Odoo 18, get current order from POS
|
||||||
// Try multiple ways to access the order
|
// Try multiple ways to access the order
|
||||||
let order = null;
|
let order = null;
|
||||||
|
|
||||||
if (typeof pos.get_order === 'function') {
|
if (typeof pos.get_order === 'function') {
|
||||||
order = pos.get_order();
|
order = pos.get_order();
|
||||||
console.log('[BluetoothPrint] Got order via get_order()');
|
|
||||||
} else if (pos.selectedOrder) {
|
} else if (pos.selectedOrder) {
|
||||||
order = pos.selectedOrder;
|
order = pos.selectedOrder;
|
||||||
console.log('[BluetoothPrint] Got order via selectedOrder');
|
|
||||||
} else if (pos.orders && pos.orders.length > 0) {
|
} else if (pos.orders && pos.orders.length > 0) {
|
||||||
order = pos.orders[pos.orders.length - 1];
|
order = pos.orders[pos.orders.length - 1];
|
||||||
console.log('[BluetoothPrint] Got order via orders array');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Order:', order);
|
|
||||||
console.log('[BluetoothPrint] Order keys:', order ? Object.keys(order) : 'null');
|
|
||||||
|
|
||||||
if (!order) {
|
if (!order) {
|
||||||
throw new Error('No active order found');
|
throw new Error('No active order found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get company info
|
// Get company info
|
||||||
const company = pos.company || {};
|
const company = pos.company || {};
|
||||||
console.log('[BluetoothPrint] Company:', company);
|
|
||||||
|
|
||||||
// Get cashier info - try multiple ways
|
// Get cashier info - try multiple ways
|
||||||
let cashierName = '';
|
let cashierName = '';
|
||||||
if (typeof pos.get_cashier === 'function') {
|
if (typeof pos.get_cashier === 'function') {
|
||||||
@ -735,8 +675,8 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (pos.user) {
|
} else if (pos.user) {
|
||||||
cashierName = pos.user.name || '';
|
cashierName = pos.user.name || '';
|
||||||
}
|
}
|
||||||
console.log('[BluetoothPrint] Cashier name:', cashierName);
|
|
||||||
|
|
||||||
// Get customer info - try multiple ways
|
// Get customer info - try multiple ways
|
||||||
let customerName = null;
|
let customerName = null;
|
||||||
if (typeof order.get_partner === 'function') {
|
if (typeof order.get_partner === 'function') {
|
||||||
@ -746,7 +686,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (order.partner_id) {
|
} else if (order.partner_id) {
|
||||||
customerName = order.partner_id[1] || null;
|
customerName = order.partner_id[1] || null;
|
||||||
}
|
}
|
||||||
console.log('[BluetoothPrint] Customer name:', customerName);
|
|
||||||
|
|
||||||
// Build receipt data structure
|
// Build receipt data structure
|
||||||
const receiptData = {
|
const receiptData = {
|
||||||
@ -771,7 +711,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Parsed receipt data:', receiptData);
|
|
||||||
return receiptData;
|
return receiptData;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -784,19 +724,19 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
_formatAddress(company) {
|
_formatAddress(company) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
|
||||||
if (company.street) parts.push(company.street);
|
if (company.street) parts.push(company.street);
|
||||||
if (company.street2) parts.push(company.street2);
|
if (company.street2) parts.push(company.street2);
|
||||||
|
|
||||||
const cityLine = [];
|
const cityLine = [];
|
||||||
if (company.zip) cityLine.push(company.zip);
|
if (company.zip) cityLine.push(company.zip);
|
||||||
if (company.city) cityLine.push(company.city);
|
if (company.city) cityLine.push(company.city);
|
||||||
if (cityLine.length > 0) parts.push(cityLine.join(' '));
|
if (cityLine.length > 0) parts.push(cityLine.join(' '));
|
||||||
|
|
||||||
if (company.country_id && company.country_id[1]) {
|
if (company.country_id && company.country_id[1]) {
|
||||||
parts.push(company.country_id[1]);
|
parts.push(company.country_id[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join(', ');
|
return parts.join(', ');
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -820,12 +760,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
_formatTotals(order) {
|
_formatTotals(order) {
|
||||||
console.log('[BluetoothPrint] Formatting totals...');
|
console.log('[BluetoothPrint] Formatting totals...');
|
||||||
|
|
||||||
let subtotal = 0;
|
let subtotal = 0;
|
||||||
let tax = 0;
|
let tax = 0;
|
||||||
let discount = 0;
|
let discount = 0;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
|
||||||
// Try multiple ways to get totals
|
// Try multiple ways to get totals
|
||||||
if (typeof order.get_total_without_tax === 'function') {
|
if (typeof order.get_total_without_tax === 'function') {
|
||||||
subtotal = order.get_total_without_tax();
|
subtotal = order.get_total_without_tax();
|
||||||
@ -846,9 +786,9 @@ patch(PosPrinterService.prototype, {
|
|||||||
});
|
});
|
||||||
subtotal = total;
|
subtotal = total;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Totals:', { subtotal, tax, discount, total });
|
console.log('[BluetoothPrint] Totals:', { subtotal, tax, discount, total });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subtotal: subtotal,
|
subtotal: subtotal,
|
||||||
tax: tax,
|
tax: tax,
|
||||||
@ -884,18 +824,18 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
_formatOrderLines(order) {
|
_formatOrderLines(order) {
|
||||||
console.log('[BluetoothPrint] Formatting order lines...');
|
console.log('[BluetoothPrint] Formatting order lines...');
|
||||||
|
|
||||||
const lines = this._getOrderLines(order);
|
const lines = this._getOrderLines(order);
|
||||||
console.log('[BluetoothPrint] Found', lines.length, 'lines');
|
console.log('[BluetoothPrint] Found', lines.length, 'lines');
|
||||||
|
|
||||||
return lines.map((line, index) => {
|
return lines.map((line, index) => {
|
||||||
console.log(`[BluetoothPrint] Processing line ${index}:`, line);
|
console.log(`[BluetoothPrint] Processing line ${index}:`, line);
|
||||||
|
|
||||||
let productName = '';
|
let productName = '';
|
||||||
let quantity = 0;
|
let quantity = 0;
|
||||||
let price = 0;
|
let price = 0;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
|
||||||
// Get product name
|
// Get product name
|
||||||
if (typeof line.get_product === 'function') {
|
if (typeof line.get_product === 'function') {
|
||||||
const product = line.get_product();
|
const product = line.get_product();
|
||||||
@ -905,7 +845,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (line.full_product_name) {
|
} else if (line.full_product_name) {
|
||||||
productName = line.full_product_name;
|
productName = line.full_product_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get quantity
|
// Get quantity
|
||||||
if (typeof line.get_quantity === 'function') {
|
if (typeof line.get_quantity === 'function') {
|
||||||
quantity = line.get_quantity();
|
quantity = line.get_quantity();
|
||||||
@ -914,7 +854,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (line.quantity !== undefined) {
|
} else if (line.quantity !== undefined) {
|
||||||
quantity = line.quantity;
|
quantity = line.quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get price
|
// Get price
|
||||||
if (typeof line.get_unit_price === 'function') {
|
if (typeof line.get_unit_price === 'function') {
|
||||||
price = line.get_unit_price();
|
price = line.get_unit_price();
|
||||||
@ -923,7 +863,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (line.price !== undefined) {
|
} else if (line.price !== undefined) {
|
||||||
price = line.price;
|
price = line.price;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get total
|
// Get total
|
||||||
if (typeof line.get_price_with_tax === 'function') {
|
if (typeof line.get_price_with_tax === 'function') {
|
||||||
total = line.get_price_with_tax();
|
total = line.get_price_with_tax();
|
||||||
@ -934,9 +874,9 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else {
|
} else {
|
||||||
total = quantity * price;
|
total = quantity * price;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[BluetoothPrint] Line ${index} formatted:`, { productName, quantity, price, total });
|
console.log(`[BluetoothPrint] Line ${index} formatted:`, { productName, quantity, price, total });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
productName: productName,
|
productName: productName,
|
||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
@ -955,9 +895,9 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
_formatPaymentData(order) {
|
_formatPaymentData(order) {
|
||||||
console.log('[BluetoothPrint] Formatting payment data...');
|
console.log('[BluetoothPrint] Formatting payment data...');
|
||||||
|
|
||||||
let paymentlines = [];
|
let paymentlines = [];
|
||||||
|
|
||||||
// Get payment lines
|
// Get payment lines
|
||||||
if (typeof order.get_paymentlines === 'function') {
|
if (typeof order.get_paymentlines === 'function') {
|
||||||
paymentlines = order.get_paymentlines();
|
paymentlines = order.get_paymentlines();
|
||||||
@ -966,9 +906,9 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (order.payment_ids) {
|
} else if (order.payment_ids) {
|
||||||
paymentlines = order.payment_ids;
|
paymentlines = order.payment_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Found', paymentlines.length, 'payment lines');
|
console.log('[BluetoothPrint] Found', paymentlines.length, 'payment lines');
|
||||||
|
|
||||||
if (paymentlines.length === 0) {
|
if (paymentlines.length === 0) {
|
||||||
return {
|
return {
|
||||||
method: 'Unknown',
|
method: 'Unknown',
|
||||||
@ -979,7 +919,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
|
|
||||||
// Use first payment method (or combine if multiple)
|
// Use first payment method (or combine if multiple)
|
||||||
const payment = paymentlines[0];
|
const payment = paymentlines[0];
|
||||||
|
|
||||||
// Get payment method name
|
// Get payment method name
|
||||||
let methodName = 'Cash';
|
let methodName = 'Cash';
|
||||||
if (payment.payment_method) {
|
if (payment.payment_method) {
|
||||||
@ -987,12 +927,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (payment.name) {
|
} else if (payment.name) {
|
||||||
methodName = payment.name;
|
methodName = payment.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total paid
|
// Calculate total paid
|
||||||
const totalPaid = paymentlines.reduce((sum, p) => {
|
const totalPaid = paymentlines.reduce((sum, p) => {
|
||||||
return sum + (p.amount || 0);
|
return sum + (p.amount || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Get order total
|
// Get order total
|
||||||
let orderTotal = 0;
|
let orderTotal = 0;
|
||||||
if (typeof order.get_total_with_tax === 'function') {
|
if (typeof order.get_total_with_tax === 'function') {
|
||||||
@ -1000,9 +940,9 @@ patch(PosPrinterService.prototype, {
|
|||||||
} else if (order.amount_total !== undefined) {
|
} else if (order.amount_total !== undefined) {
|
||||||
orderTotal = order.amount_total;
|
orderTotal = order.amount_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = Math.max(0, totalPaid - orderTotal);
|
const change = Math.max(0, totalPaid - orderTotal);
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Payment data:', { methodName, totalPaid, orderTotal, change });
|
console.log('[BluetoothPrint] Payment data:', { methodName, totalPaid, orderTotal, change });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1038,7 +978,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
*/
|
*/
|
||||||
_showFallbackNotification(error) {
|
_showFallbackNotification(error) {
|
||||||
let message = 'Bluetooth printer unavailable. Receipt sent to default printer.';
|
let message = 'Bluetooth printer unavailable. Receipt sent to default printer.';
|
||||||
|
|
||||||
if (error instanceof TimeoutError) {
|
if (error instanceof TimeoutError) {
|
||||||
message = 'Bluetooth printer timeout. Receipt sent to default printer.';
|
message = 'Bluetooth printer timeout. Receipt sent to default printer.';
|
||||||
} else if (error instanceof PrinterNotConnectedError) {
|
} else if (error instanceof PrinterNotConnectedError) {
|
||||||
@ -1069,9 +1009,9 @@ patch(PosPrinterService.prototype, {
|
|||||||
errorMessage: error.message || 'Unknown error',
|
errorMessage: error.message || 'Unknown error',
|
||||||
stack: error.stack
|
stack: error.stack
|
||||||
};
|
};
|
||||||
|
|
||||||
console.error('Print error details:', errorInfo);
|
console.error('Print error details:', errorInfo);
|
||||||
|
|
||||||
// Could send to server for logging if needed
|
// Could send to server for logging if needed
|
||||||
// this.env.services.rpc(...);
|
// this.env.services.rpc(...);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ patch(PosStore.prototype, {
|
|||||||
*/
|
*/
|
||||||
async setup() {
|
async setup() {
|
||||||
await super.setup(...arguments);
|
await super.setup(...arguments);
|
||||||
|
|
||||||
// Initialize bluetooth printing if enabled
|
// Initialize bluetooth printing if enabled
|
||||||
if (this.config.bluetooth_printer_enabled) {
|
if (this.config.bluetooth_printer_enabled) {
|
||||||
await this._initializeBluetoothPrinter();
|
await this._initializeBluetoothPrinter();
|
||||||
@ -36,14 +36,14 @@ patch(PosStore.prototype, {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async _initializeBluetoothPrinter() {
|
async _initializeBluetoothPrinter() {
|
||||||
console.log('Initializing bluetooth printer for POS session...');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const notificationService = this.env?.services?.notification || null;
|
const notificationService = this.env?.services?.notification || null;
|
||||||
const errorService = getErrorNotificationService(notificationService);
|
const errorService = getErrorNotificationService(notificationService);
|
||||||
const services = getBluetoothPrintingServices(notificationService);
|
const services = getBluetoothPrintingServices(notificationService);
|
||||||
const { bluetoothManager, storageManager } = services;
|
const { bluetoothManager, storageManager } = services;
|
||||||
|
|
||||||
// Check if Web Bluetooth API is available
|
// Check if Web Bluetooth API is available
|
||||||
if (!bluetoothManager.isBluetoothAvailable()) {
|
if (!bluetoothManager.isBluetoothAvailable()) {
|
||||||
console.warn('Web Bluetooth API not available in this browser');
|
console.warn('Web Bluetooth API not available in this browser');
|
||||||
@ -56,23 +56,23 @@ patch(PosStore.prototype, {
|
|||||||
|
|
||||||
// Load printer configuration from local storage
|
// Load printer configuration from local storage
|
||||||
const config = storageManager.loadConfiguration(this.config.id);
|
const config = storageManager.loadConfiguration(this.config.id);
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
// No printer configured for this device
|
// No printer configured for this device
|
||||||
console.log('No bluetooth printer configured for this device');
|
// console.log('No bluetooth printer configured for this device');
|
||||||
this._promptPrinterConfiguration();
|
this._promptPrinterConfiguration();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Found printer configuration:', config.deviceName);
|
// console.log('Found printer configuration:', config.deviceName);
|
||||||
|
|
||||||
// Enable auto-reconnect based on saved settings
|
// Enable auto-reconnect based on saved settings
|
||||||
const autoReconnect = config.settings?.autoReconnect !== false;
|
const autoReconnect = config.settings?.autoReconnect !== false;
|
||||||
bluetoothManager.setAutoReconnect(autoReconnect);
|
bluetoothManager.setAutoReconnect(autoReconnect);
|
||||||
|
|
||||||
// Attempt to connect to the configured printer
|
// Attempt to connect to the configured printer
|
||||||
await this._connectToConfiguredPrinter(config);
|
await this._connectToConfiguredPrinter(config);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize bluetooth printer:', error);
|
console.error('Failed to initialize bluetooth printer:', error);
|
||||||
const errorService = getErrorNotificationService();
|
const errorService = getErrorNotificationService();
|
||||||
@ -92,17 +92,17 @@ patch(PosStore.prototype, {
|
|||||||
const errorService = getErrorNotificationService(notificationService);
|
const errorService = getErrorNotificationService(notificationService);
|
||||||
const services = getBluetoothPrintingServices(notificationService);
|
const services = getBluetoothPrintingServices(notificationService);
|
||||||
const { bluetoothManager } = services;
|
const { bluetoothManager } = services;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Attempting to connect to printer:', config.deviceName);
|
// console.log('Attempting to connect to printer:', config.deviceName);
|
||||||
|
|
||||||
// Try to get the previously paired device
|
// Try to get the previously paired device
|
||||||
const devices = await navigator.bluetooth.getDevices();
|
const devices = await navigator.bluetooth.getDevices();
|
||||||
const device = devices.find(d =>
|
const device = devices.find(d =>
|
||||||
d.id === config.deviceId ||
|
d.id === config.deviceId ||
|
||||||
d.name === config.deviceName
|
d.name === config.deviceName
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
console.warn('Previously configured printer not found');
|
console.warn('Previously configured printer not found');
|
||||||
errorService.showNotification(
|
errorService.showNotification(
|
||||||
@ -115,13 +115,13 @@ patch(PosStore.prototype, {
|
|||||||
|
|
||||||
// Connect to the printer
|
// Connect to the printer
|
||||||
await bluetoothManager.connectToPrinter(device);
|
await bluetoothManager.connectToPrinter(device);
|
||||||
|
|
||||||
console.log('Successfully connected to bluetooth printer');
|
// console.log('Successfully connected to bluetooth printer');
|
||||||
// Success notification is now handled by error service in bluetooth manager
|
// Success notification is now handled by error service in bluetooth manager
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to connect to bluetooth printer:', error);
|
console.error('Failed to connect to bluetooth printer:', error);
|
||||||
|
|
||||||
// Error is now handled by error notification service in bluetooth manager
|
// Error is now handled by error notification service in bluetooth manager
|
||||||
// Just log additional context here
|
// Just log additional context here
|
||||||
errorService.logError(error, {
|
errorService.logError(error, {
|
||||||
@ -139,13 +139,13 @@ patch(PosStore.prototype, {
|
|||||||
_promptPrinterConfiguration() {
|
_promptPrinterConfiguration() {
|
||||||
const notificationService = this.env?.services?.notification || null;
|
const notificationService = this.env?.services?.notification || null;
|
||||||
const errorService = getErrorNotificationService(notificationService);
|
const errorService = getErrorNotificationService(notificationService);
|
||||||
|
|
||||||
// Show notification prompting user to configure printer
|
// Show notification prompting user to configure printer
|
||||||
errorService.showNotification(
|
errorService.showNotification(
|
||||||
'No bluetooth printer configured. Please configure a printer in POS settings.',
|
'No bluetooth printer configured. Please configure a printer in POS settings.',
|
||||||
'info'
|
'info'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Could trigger opening the configuration dialog here
|
// Could trigger opening the configuration dialog here
|
||||||
// For now, we just notify the user
|
// For now, we just notify the user
|
||||||
},
|
},
|
||||||
@ -160,7 +160,7 @@ patch(PosStore.prototype, {
|
|||||||
if (this.config.bluetooth_printer_enabled) {
|
if (this.config.bluetooth_printer_enabled) {
|
||||||
await this._cleanupBluetoothPrinter();
|
await this._cleanupBluetoothPrinter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call parent closePos
|
// Call parent closePos
|
||||||
return super.closePos(...arguments);
|
return super.closePos(...arguments);
|
||||||
},
|
},
|
||||||
@ -172,19 +172,19 @@ patch(PosStore.prototype, {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async _cleanupBluetoothPrinter() {
|
async _cleanupBluetoothPrinter() {
|
||||||
console.log('Cleaning up bluetooth printer connection...');
|
// console.log('Cleaning up bluetooth printer connection...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const services = getBluetoothPrintingServices();
|
const services = getBluetoothPrintingServices();
|
||||||
const { bluetoothManager } = services;
|
const { bluetoothManager } = services;
|
||||||
|
|
||||||
// Check if there's an active connection
|
// Check if there's an active connection
|
||||||
const status = bluetoothManager.getConnectionStatus();
|
const status = bluetoothManager.getConnectionStatus();
|
||||||
|
|
||||||
if (status === 'connected' || status === 'connecting') {
|
if (status === 'connected' || status === 'connecting') {
|
||||||
console.log('Disconnecting bluetooth printer...');
|
// console.log('Disconnecting bluetooth printer...');
|
||||||
await bluetoothManager.disconnect();
|
await bluetoothManager.disconnect();
|
||||||
console.log('Bluetooth printer disconnected');
|
// console.log('Bluetooth printer disconnected');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error cleaning up bluetooth printer:', error);
|
console.error('Error cleaning up bluetooth printer:', error);
|
||||||
@ -208,7 +208,7 @@ patch(PosStore.prototype, {
|
|||||||
try {
|
try {
|
||||||
const services = getBluetoothPrintingServices();
|
const services = getBluetoothPrintingServices();
|
||||||
const { bluetoothManager } = services;
|
const { bluetoothManager } = services;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
...bluetoothManager.getConnectionInfo()
|
...bluetoothManager.getConnectionInfo()
|
||||||
@ -258,7 +258,7 @@ patch(PosStore.prototype, {
|
|||||||
sticky: false
|
sticky: false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
// console.log(`[${type.toUpperCase()}] ${message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user