feat: Add Bluetooth printer connection functionality and UI alongside existing USB printer support.
This commit is contained in:
parent
823cb18311
commit
c4a66fcf9a
@ -4,19 +4,29 @@
|
||||
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
|
||||
</div>
|
||||
@can('create order')
|
||||
<div class="flex" x-data="{ isConnected: !!localStorage.getItem('printerDevice') }" @printer-status-change.window="isConnected = $event.detail.connected">
|
||||
<div class="flex" x-data="{
|
||||
isConnected: !!(localStorage.getItem('printerDevice') || localStorage.getItem('btPrinterDevice'))
|
||||
}" @printer-status-change.window="isConnected = $event.detail.connected">
|
||||
<flux:button href="{{ route('studio.manage.order.create') }}" variant="primary" wire:navigate class="text-sm">
|
||||
Tambah
|
||||
</flux:button>
|
||||
<template x-if="!isConnected">
|
||||
<flux:button variant="primary" color="cyan" class="ms-2" wire:click="$dispatch('connectUSB')">
|
||||
Connect USB
|
||||
</flux:button>
|
||||
<div class="flex gap-2 ms-2">
|
||||
<flux:button variant="primary" color="cyan" wire:click="$dispatch('connectUSB')">
|
||||
Connect USB
|
||||
</flux:button>
|
||||
<flux:button variant="primary" color="indigo" wire:click="$dispatch('connectBluetooth')">
|
||||
Connect Bluetooth
|
||||
</flux:button>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="isConnected">
|
||||
<div class="ms-2 flex items-center gap-2 text-green-600 bg-green-50 px-3 py-1 rounded-lg border border-green-200">
|
||||
<div
|
||||
class="ms-2 flex items-center gap-2 text-green-600 bg-green-50 px-3 py-1 rounded-lg border border-green-200">
|
||||
<flux:icon.check-circle class="size-4" />
|
||||
<span class="text-sm font-medium">Printer Connected</span>
|
||||
{{-- <flux:button variant="ghost" size="xs" color="danger" class="ms-1" icon="x-mark"
|
||||
x-on:click="localStorage.removeItem('printerDevice'); localStorage.removeItem('btPrinterDevice'); window.printerDevice = null; window.btCharacteristic = null; window.dispatchEvent(new CustomEvent('printer-status-change', { detail: { connected: false } }))" /> --}}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@ -67,11 +77,13 @@
|
||||
<script>
|
||||
document.addEventListener('livewire:navigated', () => {
|
||||
window.printerDevice = null;
|
||||
window.btCharacteristic = null;
|
||||
let currentOrder = null;
|
||||
|
||||
// Check if device is already stored
|
||||
const savedDevice = localStorage.getItem('printerDevice');
|
||||
if (savedDevice) {
|
||||
const savedUSB = localStorage.getItem('printerDevice');
|
||||
const savedBT = localStorage.getItem('btPrinterDevice');
|
||||
if (savedUSB || savedBT) {
|
||||
// Dispatch event to update UI
|
||||
setTimeout(() => window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
@ -100,13 +112,12 @@
|
||||
vendorId: device.vendorId,
|
||||
productId: device.productId,
|
||||
productName: device.productName,
|
||||
manufacturerName: device.manufacturerName,
|
||||
serialNumber: device.serialNumber
|
||||
};
|
||||
|
||||
localStorage.setItem('printerDevice', JSON.stringify(deviceInfo));
|
||||
localStorage.removeItem('btPrinterDevice'); // Clear BT if USB is connected
|
||||
|
||||
console.log('Connected to:', device.productName);
|
||||
console.log('Connected to USB:', device.productName);
|
||||
window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
connected: true
|
||||
@ -114,51 +125,138 @@
|
||||
}));
|
||||
|
||||
} catch (err) {
|
||||
console.error('Gagal connect:', err);
|
||||
console.error('Gagal connect USB:', err);
|
||||
}
|
||||
});
|
||||
|
||||
Livewire.on('connectBluetooth', async (event) => {
|
||||
try {
|
||||
const device = await navigator.bluetooth.requestDevice({
|
||||
filters: [{
|
||||
services: ['000018f0-0000-1000-8000-00805f9b34fb']
|
||||
},
|
||||
{
|
||||
services: ['0000ff00-0000-1000-8000-00805f9b34fb']
|
||||
},
|
||||
{
|
||||
services: ['49535343-fe7d-4ae5-8fa9-9fafd205e455']
|
||||
}, // microchip
|
||||
{
|
||||
namePrefix: 'TP'
|
||||
},
|
||||
{
|
||||
namePrefix: 'RP'
|
||||
},
|
||||
{
|
||||
namePrefix: 'MPT'
|
||||
},
|
||||
{
|
||||
namePrefix: 'InnerPrinter'
|
||||
},
|
||||
{
|
||||
namePrefix: 'Bluetooth'
|
||||
}
|
||||
],
|
||||
optionalServices: [
|
||||
'00001101-0000-1000-8000-00805f9b34fb',
|
||||
'000018f0-0000-1000-8000-00805f9b34fb',
|
||||
'0000ff00-0000-1000-8000-00805f9b34fb',
|
||||
'49535343-fe7d-4ae5-8fa9-9fafd205e455'
|
||||
]
|
||||
});
|
||||
|
||||
const server = await device.gatt.connect();
|
||||
|
||||
// Try to find the write characteristic
|
||||
const services = await server.getPrimaryServices();
|
||||
let characteristic = null;
|
||||
|
||||
for (const service of services) {
|
||||
const characteristics = await service.getCharacteristics();
|
||||
for (const char of characteristics) {
|
||||
if (char.properties.write || char.properties.writeWithoutResponse) {
|
||||
characteristic = char;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (characteristic) break;
|
||||
}
|
||||
|
||||
if (!characteristic) {
|
||||
throw new Error("Tidak menemukan characteristic untuk menulis data.");
|
||||
}
|
||||
|
||||
window.btCharacteristic = characteristic;
|
||||
|
||||
const deviceInfo = {
|
||||
name: device.name,
|
||||
id: device.id
|
||||
};
|
||||
|
||||
localStorage.setItem('btPrinterDevice', JSON.stringify(deviceInfo));
|
||||
localStorage.removeItem('printerDevice'); // Clear USB if BT is connected
|
||||
|
||||
console.log('Connected to Bluetooth:', device.name);
|
||||
window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
connected: true
|
||||
}
|
||||
}));
|
||||
|
||||
device.addEventListener('gattserverdisconnected', () => {
|
||||
console.log('Bluetooth disconnected');
|
||||
window.btCharacteristic = null;
|
||||
localStorage.removeItem('btPrinterDevice');
|
||||
window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
connected: false
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('Gagal connect Bluetooth:', err);
|
||||
alert('Gagal menghubungkan Bluetooth: ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
async function autoReconnect() {
|
||||
const saved = localStorage.getItem('printerDevice');
|
||||
const savedUSB = localStorage.getItem('printerDevice');
|
||||
const savedBT = localStorage.getItem('btPrinterDevice');
|
||||
|
||||
if (!saved) return;
|
||||
if (savedUSB) {
|
||||
const {
|
||||
vendorId,
|
||||
productId
|
||||
} = JSON.parse(savedUSB);
|
||||
try {
|
||||
const devices = await navigator.usb.getDevices();
|
||||
const target = devices.find(d => d.vendorId === vendorId && d.productId === productId);
|
||||
|
||||
const {
|
||||
vendorId,
|
||||
productId
|
||||
} = JSON.parse(saved);
|
||||
|
||||
const devices = await navigator.usb.getDevices();
|
||||
|
||||
const target = devices.find(d => d.vendorId === vendorId && d.productId === productId);
|
||||
|
||||
if (!target) return;
|
||||
|
||||
try {
|
||||
await target.open();
|
||||
if (target.configuration === null) {
|
||||
await target.selectConfiguration(1);
|
||||
if (target) {
|
||||
await target.open();
|
||||
if (target.configuration === null) {
|
||||
await target.selectConfiguration(1);
|
||||
}
|
||||
await target.claimInterface(0);
|
||||
window.printerDevice = target;
|
||||
console.log('Auto reconnected to USB:', target.productName);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Gagal autoreconnect USB:', err);
|
||||
}
|
||||
|
||||
await target.claimInterface(0);
|
||||
window.printerDevice = target;
|
||||
|
||||
console.log('Auto reconnected to:', target.productName);
|
||||
window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
connected: true
|
||||
}
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Gagal autoreconnect:', err);
|
||||
// If fail, remove storage
|
||||
localStorage.removeItem('printerDevice');
|
||||
window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
connected: false
|
||||
}
|
||||
}));
|
||||
} else if (savedBT) {
|
||||
// Bluetooth doesn't support silent reconnect in many browsers without user gesture
|
||||
// But we can check if it's still connected or just clear it if it needs manual action
|
||||
console.log('Bluetooth device saved, but may need manual reconnection if disconnected.');
|
||||
}
|
||||
|
||||
const isConnected = !!(window.printerDevice || window.btCharacteristic);
|
||||
window.dispatchEvent(new CustomEvent('printer-status-change', {
|
||||
detail: {
|
||||
connected: isConnected
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
autoReconnect();
|
||||
@ -218,25 +316,20 @@ function getMaxChar(widthMM) {
|
||||
});
|
||||
|
||||
async function printReceipt(order, paperSize = 58) {
|
||||
if (!window.printerDevice) {
|
||||
// Try auto reconnect if not connected but saved
|
||||
if (!window.printerDevice && !window.btCharacteristic) {
|
||||
// Try auto reconnect or prompt
|
||||
await autoReconnect();
|
||||
if (!window.printerDevice) {
|
||||
alert("Printer belum terkoneksi via USB.");
|
||||
if (!window.printerDevice && !window.btCharacteristic) {
|
||||
alert("Printer belum terkoneksi via USB atau Bluetooth.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const device = window.printerDevice;
|
||||
|
||||
// ESC/POS Commands
|
||||
const ESC = "\x1B";
|
||||
const GS = "\x1D";
|
||||
const reset = new Uint8Array([0x1B, 0x40]);
|
||||
const alignLeft = new Uint8Array([0x1B, 0x61, 0]);
|
||||
const alignCenter = new Uint8Array([0x1B, 0x61, 1]);
|
||||
const alignRight = new Uint8Array([0x1B, 0x61, 2]);
|
||||
const cut = new Uint8Array([0x1D, 0x56, 0x00]);
|
||||
|
||||
const MAX = getMaxChar(paperSize);
|
||||
@ -305,7 +398,7 @@ function getMaxChar(widthMM) {
|
||||
}
|
||||
|
||||
// Add extra newline if not packed tight
|
||||
text += "\n";
|
||||
text += "\n";
|
||||
});
|
||||
|
||||
text += line;
|
||||
@ -331,10 +424,29 @@ function getMaxChar(widthMM) {
|
||||
|
||||
const textBytes = encoder.encode(text);
|
||||
|
||||
// SEND
|
||||
await device.transferOut(1, reset);
|
||||
await device.transferOut(1, textBytes);
|
||||
await device.transferOut(1, cut);
|
||||
// SEND DATA FUNCTION
|
||||
const sendData = async (data) => {
|
||||
if (window.printerDevice) {
|
||||
await window.printerDevice.transferOut(1, data);
|
||||
} else if (window.btCharacteristic) {
|
||||
// Bluetooth often has a limit on packet size (typically 20-512 bytes)
|
||||
// Split data into chunks if necessary
|
||||
const chunkSize = 100;
|
||||
for (let i = 0; i < data.length; i += chunkSize) {
|
||||
const chunk = data.slice(i, i + chunkSize);
|
||||
await window.btCharacteristic.writeValue(chunk);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await sendData(reset);
|
||||
await sendData(textBytes);
|
||||
await sendData(cut);
|
||||
} catch (err) {
|
||||
console.error('Gagal mencetak:', err);
|
||||
alert('Gagal mencetak: ' + err.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user