feat(ui): mesh Refresh/Broadcast feedback, Set Recommended modal with probe progress bar, live list refresh
Demo images / Build & push demo images (push) Successful in 3m43s
Demo images / Build & push demo images (push) Successful in 3m43s
- Refresh button: real handler — calls the new mesh.refresh (radio
re-query) plus contacts/federation/outbox re-reads, disabled with a
spinner while running (was an unawaited cache repaint with no feedback
that skipped half the list's data sources).
- Broadcast button: success ('Sent ✓') and failure states with the error
in the tooltip; failures no longer vanish as unhandled rejections.
- The store's 5s status poll no longer wipes the error banner each tick.
- Contacts/aliases, federation nodes and the outbox badge refresh every
~30s (were mount-only and went permanently stale).
- Peer-list empty state keys on the merged list, so federation rows and
the channel rows still render with no radio attached.
- Device setup modal: 'Set Recommended' naming, probe progress bar with
stage labels instead of an anonymous spinner.
- Device panel: name save clears properly (empty = fall back to server
name) and the confirmation reflects the new live apply.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a8c4694c36
commit
3f76b4960a
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<BaseModal
|
||||
:show="show"
|
||||
:title="step === 1 ? 'Mesh Radio Detected' : 'Apply Archipelago Settings'"
|
||||
:title="step === 1 ? 'Mesh Radio Detected' : 'Set Recommended'"
|
||||
max-width="max-w-lg"
|
||||
content-class="max-h-[90vh] overflow-y-auto"
|
||||
@close="dismiss"
|
||||
@@ -35,9 +35,17 @@
|
||||
|
||||
<!-- What's currently flashed / configured on it -->
|
||||
<div class="mt-4 text-left rounded-xl bg-white/[0.05] border border-white/10 p-3">
|
||||
<div v-if="probing" class="flex items-center gap-2 text-white/60 text-sm py-1">
|
||||
<span class="inline-block w-3.5 h-3.5 rounded-full border-2 border-orange-300/70 border-t-transparent animate-spin"></span>
|
||||
Reading what's on the radio…
|
||||
<div v-if="probing" class="py-1">
|
||||
<div class="flex items-center justify-between text-white/60 text-sm mb-1.5">
|
||||
<span>{{ probeStage }}</span>
|
||||
<span class="text-white/40 text-xs tabular-nums">{{ Math.round(probeProgress) }}%</span>
|
||||
</div>
|
||||
<div class="h-1.5 rounded-full bg-white/10 overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded-full bg-orange-400/80 transition-[width] duration-500 ease-linear"
|
||||
:style="{ width: probeProgress + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="probe">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -94,7 +102,7 @@
|
||||
:disabled="!!connecting"
|
||||
@click="step = 2"
|
||||
>
|
||||
Set Up with Archipelago Settings
|
||||
Set Recommended
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-white/40 text-[11px] mt-3">
|
||||
@@ -107,8 +115,8 @@
|
||||
<!-- Step 2: our latest parameters, shown before anything is written -->
|
||||
<div v-else>
|
||||
<p class="text-white/60 text-xs mb-3">
|
||||
These are the latest Archipelago settings — nothing is written to the
|
||||
radio until you confirm.
|
||||
These are the recommended Archipelago settings — nothing is written to
|
||||
the radio until you confirm.
|
||||
</p>
|
||||
|
||||
<!-- Summary of what will be applied -->
|
||||
@@ -206,6 +214,30 @@ const probing = ref(false)
|
||||
const probe = ref<MeshDeviceProbe | null>(null)
|
||||
const probeError = ref('')
|
||||
|
||||
// Time-driven probe progress: the probe RPC is a single opaque call that can
|
||||
// take ~5-30s (boot settle + up to three firmware handshakes), so the bar
|
||||
// advances on a clock toward 92% and snaps to 100% when the result lands.
|
||||
const probeProgress = ref(0)
|
||||
const probeStage = ref('Waiting for the radio to boot…')
|
||||
let probeTicker: ReturnType<typeof setInterval> | null = null
|
||||
function startProbeProgress() {
|
||||
stopProbeProgress()
|
||||
probeProgress.value = 0
|
||||
probeStage.value = 'Waiting for the radio to boot…'
|
||||
const startedAt = Date.now()
|
||||
probeTicker = setInterval(() => {
|
||||
const elapsed = (Date.now() - startedAt) / 1000
|
||||
// ~92% at 30s, decelerating — never looks stuck, never lies "done".
|
||||
probeProgress.value = Math.min(92, 100 * (1 - Math.exp(-elapsed / 11)))
|
||||
if (elapsed >= 4) probeStage.value = 'Detecting firmware…'
|
||||
if (elapsed >= 18) probeStage.value = 'Still checking (radios can be slow to answer)…'
|
||||
}, 400)
|
||||
}
|
||||
function stopProbeProgress(done = false) {
|
||||
if (probeTicker) { clearInterval(probeTicker); probeTicker = null }
|
||||
if (done) probeProgress.value = 100
|
||||
}
|
||||
|
||||
const devicePath = computed(() => mesh.undismissedDetectedDevices[0] ?? '')
|
||||
const show = computed(() => !!devicePath.value)
|
||||
const imageFailed = ref(false)
|
||||
@@ -275,6 +307,7 @@ watch([show, devicePath], async ([visible]) => {
|
||||
probe.value = null
|
||||
probeError.value = ''
|
||||
probing.value = true
|
||||
startProbeProgress()
|
||||
const path = devicePath.value
|
||||
try {
|
||||
const res = await mesh.probeDevice(path)
|
||||
@@ -284,6 +317,7 @@ watch([show, devicePath], async ([visible]) => {
|
||||
probeError.value = e instanceof Error ? e.message : String(e)
|
||||
}
|
||||
} finally {
|
||||
stopProbeProgress(true)
|
||||
if (devicePath.value === path) probing.value = false
|
||||
}
|
||||
}, { immediate: false })
|
||||
|
||||
Reference in New Issue
Block a user