feat(mesh): device-detected setup modal with board images + full radio settings
Plugging in a LoRa radio now pops a global two-step setup modal on any page: step 1 shows the ACTUAL detected board (25 board SVGs vendored from the Meshtastic web-flasher, GPL-3.0; matched by USB product string on native-USB boards, vid:pid heuristics for bridge chips, animated fallback otherwise); step 2 configures with Archipelago presets — the LoRa region pre-selected from the node's location (new lat/lon→region mapping), channel 'archipelago', node name, and an advanced firmware pin. Options adapt per protocol: region+channel program Meshtastic; MeshCore shows its community frequency plan (firmware owns RF); RNode defers to the Reticulum daemon config. Backend: mesh.configure now accepts lora_region (validated against the driver's region table) and device_kind (probe pin); mesh.status returns the persisted values plus per-port USB identity (sysfs vid/pid/product) for board identification. The Mesh Device tab gains a full settings form (region with duty-cycle/legality hints, firmware pin, channel, name, identity broadcast) replacing the read-only panel; the old Mesh-page-only onboarding modal is superseded by the global flow. Mock backend: stateful mesh config so the dev UI demos the full detect→configure round-trip (disable mesh → modal fires with Heltec V3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c014c4b3ee
commit
7b36b5cc34
@@ -40,13 +40,7 @@ const sendError = ref('')
|
||||
const broadcasting = ref(false)
|
||||
const configuring = ref(false)
|
||||
const connectingDevice = ref<string | null>(null)
|
||||
// Onboarding modal (#6): guides a first-time connect for a freshly-detected,
|
||||
// not-yet-connected device — a friendlier wrapper around the same Connect
|
||||
// action the "Detected USB devices" list already offers, not a new setup
|
||||
// engine. `onboardingDismissed` remembers paths the user closed without
|
||||
// connecting, so it doesn't reappear every poll tick for the same device.
|
||||
const showOnboardingModal = ref(false)
|
||||
const onboardingDismissed = ref<Set<string>>(new Set())
|
||||
// Device-detected onboarding now lives in the global MeshDeviceSetupModal (App.vue).
|
||||
const chatScrollEl = ref<HTMLElement | null>(null)
|
||||
const messageInputRef = ref<HTMLInputElement | null>(null)
|
||||
const mobileShowChat = ref(false)
|
||||
@@ -1041,35 +1035,12 @@ async function handleToggleEnabled() {
|
||||
async function handleConnectDevice(devicePath: string) {
|
||||
connectingDevice.value = devicePath
|
||||
try {
|
||||
await mesh.configure({ enabled: true, device_path: devicePath } as Partial<import('@/stores/mesh').MeshStatus>)
|
||||
showOnboardingModal.value = false
|
||||
await mesh.configure({ enabled: true, device_path: devicePath })
|
||||
} finally {
|
||||
connectingDevice.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const undismissedDetectedDevices = computed(() =>
|
||||
(mesh.status?.detected_devices ?? []).filter((d) => !onboardingDismissed.value.has(d))
|
||||
)
|
||||
|
||||
function dismissOnboarding() {
|
||||
for (const d of undismissedDetectedDevices.value) onboardingDismissed.value.add(d)
|
||||
showOnboardingModal.value = false
|
||||
}
|
||||
|
||||
// Pop the onboarding modal the moment a device is detected but not yet
|
||||
// connected — same trigger condition the inline "Detected USB devices" list
|
||||
// already uses (mesh.status.detected_devices non-empty + not connected),
|
||||
// just surfaced as a guided prompt instead of requiring the user to notice
|
||||
// the collapsed Device card.
|
||||
watch(
|
||||
() => [mesh.status?.device_connected, undismissedDetectedDevices.value.length] as const,
|
||||
([connected, count]) => {
|
||||
if (!connected && count > 0) showOnboardingModal.value = true
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function signalBars(rssi: number | null, snr: number | null = null): number {
|
||||
if (rssi !== null) {
|
||||
if (rssi > -60) return 4
|
||||
@@ -2448,31 +2419,9 @@ async function downloadAttachment(payload: MeshAttachmentPayload) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Onboarding modal (#6): guided first-connect prompt for a freshly
|
||||
detected, not-yet-connected mesh device — wraps the same Connect
|
||||
action the inline "Detected USB devices" list already offers. -->
|
||||
<div v-if="showOnboardingModal" class="mesh-transport-modal-backdrop" @click.self="dismissOnboarding">
|
||||
<div class="glass-card mesh-transport-modal">
|
||||
<h3 class="mesh-transport-title">📡 Mesh Device Found</h3>
|
||||
<p class="mesh-transport-sub">
|
||||
A radio was detected but isn't connected yet. Connect it to start using off-grid mesh chat.
|
||||
</p>
|
||||
<div class="mesh-transport-options">
|
||||
<button
|
||||
v-for="dev in undismissedDetectedDevices"
|
||||
:key="dev"
|
||||
class="mesh-transport-option"
|
||||
:disabled="connectingDevice !== null"
|
||||
@click="handleConnectDevice(dev)"
|
||||
>
|
||||
<span class="mesh-transport-icon">📡</span>
|
||||
<span class="mesh-transport-label">{{ dev }}</span>
|
||||
<span class="mesh-transport-meta">{{ connectingDevice === dev ? 'Connecting…' : 'Connect' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="mesh-transport-cancel" @click="dismissOnboarding">Not now</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- The "mesh device detected" setup flow is now the global
|
||||
MeshDeviceSetupModal mounted in App.vue (fires on every page,
|
||||
board image + region presets), so no local modal here. -->
|
||||
|
||||
<MediaLightbox
|
||||
:items="lightboxItems"
|
||||
|
||||
Reference in New Issue
Block a user