feat: add BLE/USB toggle with auto-detect in DeployTab
This commit is contained in:
parent
369897529d
commit
185fed636e
|
|
@ -1,13 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { BLEHardwareDeployer } from '$services/ble-deployer';
|
import { BLEHardwareDeployer } from '$services/ble-deployer';
|
||||||
|
import { USBHardwareDeployer } from '$services/usb-deployer';
|
||||||
import { getHexContent } from '$services/api';
|
import { getHexContent } from '$services/api';
|
||||||
import { getVelxioState } from '$services/velxio-manager';
|
import { getVelxioState } from '$services/velxio-manager';
|
||||||
import {
|
import {
|
||||||
SUPPORTED_BAUD_RATES,
|
SUPPORTED_BAUD_RATES,
|
||||||
DEFAULT_BAUD_RATE,
|
DEFAULT_BAUD_RATE,
|
||||||
type DeployState,
|
type DeployState,
|
||||||
type DeployProgress
|
type DeployProgress,
|
||||||
|
type HardwareDeployer
|
||||||
} from '$types/deployer';
|
} from '$types/deployer';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
@ -22,7 +24,7 @@
|
||||||
authLoggedIn: boolean;
|
authLoggedIn: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let deployer = $state<BLEHardwareDeployer | null>(null);
|
let deployer = $state<HardwareDeployer | null>(null);
|
||||||
let isPaired = $state(false);
|
let isPaired = $state(false);
|
||||||
let deployState = $state<DeployState>('idle');
|
let deployState = $state<DeployState>('idle');
|
||||||
let deployMessage = $state('');
|
let deployMessage = $state('');
|
||||||
|
|
@ -30,6 +32,11 @@
|
||||||
let completedChunks = $state(0);
|
let completedChunks = $state(0);
|
||||||
let progressPercent = $derived(totalChunks > 0 ? Math.round((completedChunks / totalChunks) * 100) : 0);
|
let progressPercent = $derived(totalChunks > 0 ? Math.round((completedChunks / totalChunks) * 100) : 0);
|
||||||
let bleSupported = $state(true);
|
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 serialLog = $state<string[]>([]);
|
||||||
let serialInput = $state('');
|
let serialInput = $state('');
|
||||||
let serialActive = $state(false);
|
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() {
|
function initDeployer() {
|
||||||
deployer = new BLEHardwareDeployer();
|
detectMode();
|
||||||
|
deployer = createDeployer(deployMode);
|
||||||
bleSupported = deployer.checkSupport();
|
bleSupported = deployer.checkSupport();
|
||||||
if (!bleSupported) {
|
if (!bleSupported) {
|
||||||
deployState = 'error';
|
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(() => {
|
deployer.onDisconnected(() => {
|
||||||
deployState = 'error';
|
deployState = 'error';
|
||||||
errorMessage = 'Koneksi BLE terputus.';
|
errorMessage = deployMode === 'usb' ? 'Koneksi USB terputus.' : 'Koneksi BLE terputus.';
|
||||||
serialActive = false;
|
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() {
|
function resetState() {
|
||||||
deployState = 'idle';
|
deployState = 'idle';
|
||||||
deployMessage = '';
|
deployMessage = '';
|
||||||
|
|
@ -148,7 +189,11 @@
|
||||||
completedChunks = p.completedChunks;
|
completedChunks = p.completedChunks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (deployMode === 'usb' && res.binary_content) {
|
||||||
|
await deployer.deployBinary!(res.binary_content, onProgress);
|
||||||
|
} else {
|
||||||
await deployer.deployHex(res.hex_content, onProgress);
|
await deployer.deployHex(res.hex_content, onProgress);
|
||||||
|
}
|
||||||
|
|
||||||
deployState = 'success';
|
deployState = 'success';
|
||||||
deployMessage = 'Upload berhasil! LED Hijau.';
|
deployMessage = 'Upload berhasil! LED Hijau.';
|
||||||
|
|
@ -239,17 +284,33 @@
|
||||||
|
|
||||||
<div class="deploy-tab">
|
<div class="deploy-tab">
|
||||||
<div class="deploy-toolbar">
|
<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}>
|
<div class="deploy-status" class:connected={isPaired}>
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
{isPaired ? 'Terhubung' : 'Terputus'}
|
{isPaired ? (deployer?.deviceName ? `Terhubung (${deployer.deviceName})` : 'Terhubung') : 'Terputus'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="deploy-connection">
|
<div class="deploy-connection">
|
||||||
{#if !isPaired}
|
{#if !isPaired}
|
||||||
<button class="btn btn-primary" onclick={handlePair} disabled={deployState === 'pairing'}>
|
<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>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="deploy-actions">
|
<div class="deploy-actions">
|
||||||
|
|
@ -327,7 +388,7 @@
|
||||||
|
|
||||||
{#if !bleSupported && deployState !== 'error'}
|
{#if !bleSupported && deployState !== 'error'}
|
||||||
<div class="deploy-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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -522,4 +583,30 @@
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #991b1b;
|
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>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue