feat: add BLE/USB toggle with auto-detect in DeployTab

This commit is contained in:
a2nr 2026-07-17 08:17:54 +07:00
parent 369897529d
commit 185fed636e
1 changed files with 97 additions and 10 deletions

View File

@ -1,13 +1,15 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { BLEHardwareDeployer } from '$services/ble-deployer';
import { USBHardwareDeployer } from '$services/usb-deployer';
import { getHexContent } from '$services/api';
import { getVelxioState } from '$services/velxio-manager';
import {
SUPPORTED_BAUD_RATES,
DEFAULT_BAUD_RATE,
type DeployState,
type DeployProgress
type DeployProgress,
type HardwareDeployer
} from '$types/deployer';
let {
@ -22,7 +24,7 @@
authLoggedIn: boolean;
} = $props();
let deployer = $state<BLEHardwareDeployer | null>(null);
let deployer = $state<HardwareDeployer | null>(null);
let isPaired = $state(false);
let deployState = $state<DeployState>('idle');
let deployMessage = $state('');
@ -30,6 +32,11 @@
let completedChunks = $state(0);
let progressPercent = $derived(totalChunks > 0 ? Math.round((completedChunks / totalChunks) * 100) : 0);
let bleSupported = $state(true);
type DeployMode = 'ble' | 'usb';
let deployMode: DeployMode = $state('ble');
let usbSupported = $state(false);
let isDesktop = $state(false);
let serialLog = $state<string[]>([]);
let serialInput = $state('');
let serialActive = $state(false);
@ -44,20 +51,54 @@
}
});
function detectMode() {
usbSupported = typeof navigator !== 'undefined' && 'serial' in navigator;
isDesktop = window.matchMedia('(min-width: 1024px)').matches;
if (usbSupported && isDesktop) {
deployMode = 'usb';
} else {
deployMode = 'ble';
}
}
function createDeployer(mode: DeployMode): HardwareDeployer {
if (mode === 'usb') return new USBHardwareDeployer();
return new BLEHardwareDeployer();
}
function initDeployer() {
deployer = new BLEHardwareDeployer();
detectMode();
deployer = createDeployer(deployMode);
bleSupported = deployer.checkSupport();
if (!bleSupported) {
deployState = 'error';
errorMessage = 'Web Bluetooth tidak didukung. Gunakan Chrome Android.';
errorMessage = deployMode === 'usb'
? 'Web Serial tidak didukung. Gunakan Chrome desktop.'
: 'Web Bluetooth tidak didukung. Gunakan Chrome Android.';
}
deployer.onDisconnected(() => {
deployState = 'error';
errorMessage = 'Koneksi BLE terputus.';
errorMessage = deployMode === 'usb' ? 'Koneksi USB terputus.' : 'Koneksi BLE terputus.';
serialActive = false;
});
}
function handleModeChange(mode: DeployMode) {
if (deployer?.isConnected) {
deployer.disconnect();
}
resetState();
deployMode = mode;
deployer = createDeployer(mode);
bleSupported = deployer.checkSupport();
if (!bleSupported) {
deployState = 'error';
errorMessage = mode === 'usb'
? 'Web Serial tidak didukung. Gunakan Chrome desktop.'
: 'Web Bluetooth tidak didukung. Gunakan Chrome Android.';
}
}
function resetState() {
deployState = 'idle';
deployMessage = '';
@ -148,7 +189,11 @@
completedChunks = p.completedChunks;
};
if (deployMode === 'usb' && res.binary_content) {
await deployer.deployBinary!(res.binary_content, onProgress);
} else {
await deployer.deployHex(res.hex_content, onProgress);
}
deployState = 'success';
deployMessage = 'Upload berhasil! LED Hijau.';
@ -239,17 +284,33 @@
<div class="deploy-tab">
<div class="deploy-toolbar">
<h3 class="deploy-title">BLE Deployer</h3>
<h3 class="deploy-title">
{deployMode === 'usb' ? 'USB Deployer' : 'BLE Deployer'}
</h3>
{#if usbSupported && isDesktop}
<div class="deploy-mode-toggle">
<button
class="mode-btn"
class:active={deployMode === 'ble'}
onclick={() => handleModeChange('ble')}
>BLE</button>
<button
class="mode-btn"
class:active={deployMode === 'usb'}
onclick={() => handleModeChange('usb')}
>USB</button>
</div>
{/if}
<div class="deploy-status" class:connected={isPaired}>
<span class="status-dot"></span>
{isPaired ? 'Terhubung' : 'Terputus'}
{isPaired ? (deployer?.deviceName ? `Terhubung (${deployer.deviceName})` : 'Terhubung') : 'Terputus'}
</div>
</div>
<div class="deploy-connection">
{#if !isPaired}
<button class="btn btn-primary" onclick={handlePair} disabled={deployState === 'pairing'}>
{deployState === 'pairing' ? 'Menghubungkan...' : 'Pair Device'}
{deployState === 'pairing' ? 'Menghubungkan...' : (deployMode === 'usb' ? 'Pilih Port USB' : 'Pair Device')}
</button>
{:else}
<div class="deploy-actions">
@ -327,7 +388,7 @@
{#if !bleSupported && deployState !== 'error'}
<div class="deploy-error">
<div class="error-message">Web Bluetooth tidak didukung di browser ini. Gunakan Chrome Android.</div>
<div class="error-message">{deployMode === 'usb' ? 'Web Serial tidak didukung di browser ini. Gunakan Chrome desktop.' : 'Web Bluetooth tidak didukung di browser ini. Gunakan Chrome Android.'}</div>
</div>
{/if}
</div>
@ -522,4 +583,30 @@
font-size: 0.9rem;
color: #991b1b;
}
.deploy-mode-toggle {
display: flex;
gap: 0;
border-radius: 6px;
overflow: hidden;
border: 1px solid #d1d5db;
}
.mode-btn {
padding: 4px 12px;
font-size: 0.8rem;
background: #f9fafb;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.mode-btn.active {
background: #3b82f6;
color: white;
}
.mode-btn:not(.active):hover {
background: #e5e7eb;
}
</style>