feat(ui): mesh header "Flash LoRa" button opens the in-app flash flow
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m9s
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m9s
Replaces the external flasher.meshcore.co.uk link with a button that opens the global device-setup modal directly at its flash step, via a new manual entry point in the mesh store (flashFlowPath). Manual opens target the connected radio (else the first detected stick), skip the read-only probe — the port is held by the live session and a second tty opener corrupts it; the backend flash job stops the listener itself — and close the modal instead of stepping back to the detection screen. Button is disabled with a hint when no radio is present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c99f1c7b77
commit
6ba39041d0
@ -340,7 +340,7 @@ function stopProbeProgress(done = false) {
|
||||
if (done) probeProgress.value = 100
|
||||
}
|
||||
|
||||
const devicePath = computed(() => mesh.undismissedDetectedDevices[0] ?? '')
|
||||
const devicePath = computed(() => mesh.flashFlowPath ?? mesh.undismissedDetectedDevices[0] ?? '')
|
||||
const show = computed(() => !!devicePath.value)
|
||||
const imageFailed = ref(false)
|
||||
const deviceImage = computed(() =>
|
||||
@ -402,6 +402,23 @@ watch([show, devicePath], async ([visible]) => {
|
||||
stopFlashPoll()
|
||||
return
|
||||
}
|
||||
if (mesh.flashFlowPath) {
|
||||
// Manual "Flash LoRa" entry: jump straight to the flash step. Skip the
|
||||
// read-only probe — the port is usually the live session's, and a second
|
||||
// opener on the tty corrupts the running connection; status already
|
||||
// knows the firmware kind for the connected radio.
|
||||
probe.value = null
|
||||
probeError.value = ''
|
||||
probing.value = false
|
||||
stopProbeProgress()
|
||||
imageFailed.value = false
|
||||
openFlashStep()
|
||||
const t = (mesh.status?.device_type ?? '').toLowerCase()
|
||||
if (t === 'meshcore' || t === 'meshtastic' || t === 'reticulum') {
|
||||
flashFamily.value = t as FlashFirmwareFamily
|
||||
}
|
||||
return
|
||||
}
|
||||
step.value = 1
|
||||
error.value = ''
|
||||
imageFailed.value = false
|
||||
@ -428,6 +445,10 @@ watch([show, devicePath], async ([visible]) => {
|
||||
}, { immediate: false })
|
||||
|
||||
function dismiss() {
|
||||
if (mesh.flashFlowPath) {
|
||||
mesh.closeFlashFlow()
|
||||
return
|
||||
}
|
||||
if (devicePath.value) mesh.dismissDetectedDevice(devicePath.value)
|
||||
}
|
||||
|
||||
@ -598,6 +619,11 @@ async function cancelFlash() {
|
||||
|
||||
function closeFlashStep() {
|
||||
stopFlashPoll()
|
||||
if (mesh.flashFlowPath) {
|
||||
// Manual entry has no detection step to go back to — close the modal.
|
||||
mesh.closeFlashFlow()
|
||||
return
|
||||
}
|
||||
step.value = 1
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -377,6 +377,16 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
}
|
||||
localStorage.setItem(DETECT_DISMISS_KEY, JSON.stringify(dismissedDetected.value))
|
||||
}
|
||||
// Manual "Flash LoRa" entry (Mesh header button): forces the global setup
|
||||
// modal open at the flash step for this port — including the live session's
|
||||
// port, which detection-driven opening deliberately excludes.
|
||||
const flashFlowPath = ref<string | null>(null)
|
||||
function openFlashFlow(path: string) {
|
||||
flashFlowPath.value = path
|
||||
}
|
||||
function closeFlashFlow() {
|
||||
flashFlowPath.value = null
|
||||
}
|
||||
/** Read-only firmware/config probe of a detected port (hot-swap modal). */
|
||||
async function probeDevice(path: string): Promise<MeshDeviceProbe> {
|
||||
return rpcClient.call<MeshDeviceProbe>({
|
||||
@ -1066,6 +1076,9 @@ export const useMeshStore = defineStore('mesh', () => {
|
||||
fetchStatus,
|
||||
undismissedDetectedDevices,
|
||||
dismissDetectedDevice,
|
||||
flashFlowPath,
|
||||
openFlashFlow,
|
||||
closeFlashFlow,
|
||||
probeDevice,
|
||||
flashListFirmware,
|
||||
flashDevice,
|
||||
|
||||
@ -21,6 +21,18 @@ const mesh = useMeshStore()
|
||||
const transport = useTransportStore()
|
||||
const route = useRoute()
|
||||
|
||||
// "Flash LoRa" header button target: the connected radio wins, else any
|
||||
// detected-but-unconnected stick. Opens the global setup modal at its
|
||||
// flash step (backend stops the listener and frees the port itself).
|
||||
const flashLoraPath = computed(() =>
|
||||
(mesh.status?.device_connected && mesh.status.device_path) ||
|
||||
mesh.status?.detected_devices?.[0] ||
|
||||
''
|
||||
)
|
||||
function openFlashLora() {
|
||||
if (flashLoraPath.value) mesh.openFlashFlow(flashLoraPath.value)
|
||||
}
|
||||
|
||||
// Responsive layout breakpoints
|
||||
const isWideDesktop = ref(window.innerWidth >= 1536)
|
||||
const isVeryWideDesktop = ref(window.innerWidth >= 2560 && window.innerHeight >= 1200)
|
||||
@ -1860,14 +1872,14 @@ async function downloadAttachment(payload: MeshAttachmentPayload) {
|
||||
<span v-else-if="mesh.status?.device_present" class="mesh-subtitle-badge">Mesh device detected</span>
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="https://flasher.meshcore.co.uk/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<button
|
||||
class="glass-button mesh-flasher-btn"
|
||||
:disabled="!flashLoraPath"
|
||||
:title="flashLoraPath ? '' : 'Plug in a LoRa radio first'"
|
||||
@click="openFlashLora"
|
||||
>
|
||||
Flash Meshcore <span class="mesh-flasher-sep">|</span> Choose Companion USB
|
||||
</a>
|
||||
Flash LoRa
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error banner -->
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
.mesh-subtitle { color: rgba(255, 255, 255, 0.5); font-size: 0.85rem; margin: 2px 0 0; display: flex; align-items: center; gap: 8px; }
|
||||
.mesh-subtitle-badge { font-size: 0.65rem; font-weight: 600; color: #4ade80; background: rgba(74, 222, 128, 0.12); padding: 1px 6px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.mesh-flasher-btn { display: inline-flex; align-items: center; gap: 0; padding: 8px 16px; font-size: 0.9rem; text-decoration: none; white-space: nowrap; flex-shrink: 0; }
|
||||
.mesh-flasher-btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
||||
.mesh-flasher-sep { margin: 0 8px; color: rgba(255, 255, 255, 0.2); }
|
||||
.mesh-error { color: #ef4444; font-size: 0.85rem; padding: 8px 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border: 1px solid rgba(239, 68, 68, 0.2); flex-shrink: 0; }
|
||||
.mesh-columns { display: flex; gap: 16px; flex: 1; min-height: 0; overflow: hidden; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user