Feedback sweep: spinners on every async button, toasts for action outcomes, top activity bar, loading states on every page, terminal connecting overlay, dashboard save indicator, task pane pinned above the package list with auto-scroll and completion toast
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d08b246b5f
commit
724f30c02b
+15
-2
@@ -1,6 +1,7 @@
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { api, DashPrefs, Sample, Series, Temp } from './api'
|
||||
import Sparkline from './Sparkline'
|
||||
import { Loading, Spinner, toast } from './ui'
|
||||
|
||||
const MAX = 300
|
||||
const S1 = 'var(--s1)', S2 = 'var(--s2)'
|
||||
@@ -261,6 +262,8 @@ export default function Dashboard() {
|
||||
const [layout, setLayout] = useState<Layout>(() => normalize({}))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [saving, setSaving] = useState<'' | 'saving' | 'saved'>('')
|
||||
const savedTimer = useRef<number | undefined>(undefined)
|
||||
const hist = useRef<Series | null>(null)
|
||||
const saveTimer = useRef<number | undefined>(undefined)
|
||||
const intervalRef = useRef(DEFAULT_INTERVAL)
|
||||
@@ -271,7 +274,15 @@ export default function Dashboard() {
|
||||
setLayout((prev) => {
|
||||
const next = fn(prev)
|
||||
clearTimeout(saveTimer.current)
|
||||
saveTimer.current = window.setTimeout(() => { api.saveDashPrefs(toPrefs(next)).catch(() => setErr('could not save layout')) }, 300)
|
||||
setSaving('saving')
|
||||
saveTimer.current = window.setTimeout(async () => {
|
||||
try {
|
||||
await api.saveDashPrefs(toPrefs(next))
|
||||
setSaving('saved')
|
||||
clearTimeout(savedTimer.current)
|
||||
savedTimer.current = window.setTimeout(() => setSaving(''), 1500)
|
||||
} catch (e) { setSaving(''); toast(`layout not saved — ${e instanceof Error ? e.message : 'failed'}`, 'err') }
|
||||
}, 300)
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
@@ -317,7 +328,7 @@ export default function Dashboard() {
|
||||
return () => { stop = true; clearTimeout(timer); document.removeEventListener('visibilitychange', onVis) }
|
||||
}, [])
|
||||
|
||||
if (!latest) return <div className="dash"><div className="center muted">{err || '…'}</div></div>
|
||||
if (!latest) return <div className="dash">{err ? <div className="error">{err}</div> : <Loading label="Collecting metrics…" />}</div>
|
||||
|
||||
const edit: Edit = {
|
||||
on: editing,
|
||||
@@ -369,6 +380,8 @@ export default function Dashboard() {
|
||||
</label>
|
||||
) : <span className="hint">last {fmtWindow(latest.interval_ms || layout.intervalMs)} · {fmtMs(latest.interval_ms || layout.intervalMs)}</span>}
|
||||
<span className="spacer" />
|
||||
{saving === 'saving' && <span className="saved"><Spinner /> saving</span>}
|
||||
{saving === 'saved' && <span className="saved">saved ✓</span>}
|
||||
{editing && <button className="btn" onClick={reset}>Reset</button>}
|
||||
<button className={'btn' + (editing ? ' on' : '')} disabled={!loaded} title={loaded ? '' : 'layout not loaded'} onClick={() => setEditing(!editing)}>{editing ? 'Done' : 'Customize'}</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user