mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
vip tab restyling
This commit is contained in:
@@ -50,17 +50,10 @@ export default function VipPanel() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="panel-section space-y-0.5 text-base">
|
||||
<div className="grid gap-0.5 grid-cols-1 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,0.9fr)]">
|
||||
<div className="lg:col-span-2">
|
||||
{isVerified ? (
|
||||
<section className="surface w-full">
|
||||
<div className="mx-auto flex w-full max-w-md flex-col items-center space-y-0.5 text-center">
|
||||
<p className="text-sm text-emerald-300">Verification complete</p>
|
||||
<p className="text-xs text-slate-400">VIP controls are unlocked for your assigned rover.</p>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className="space-y-2 text-base">
|
||||
<div className="grid gap-2 grid-cols-1 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,0.9fr)]">
|
||||
{!isVerified ? (
|
||||
<div className="lg:col-span-2">
|
||||
<VipVerificationCard
|
||||
pendingRequestId={pendingRequestId}
|
||||
currentStoredKey={currentStoredKey}
|
||||
@@ -70,12 +63,12 @@ export default function VipPanel() {
|
||||
onMessage={setMessage}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="lg:col-span-2">
|
||||
{isVerified ? (
|
||||
<div className="space-y-0.5">
|
||||
<div className="space-y-2">
|
||||
<VipNeatoCard
|
||||
neato={session?.neato || null}
|
||||
onStart={neatoStart}
|
||||
|
||||
@@ -739,127 +739,130 @@ export default function VipAudioUploadCard({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid gap-0.5">
|
||||
<section className="surface">
|
||||
<div className="flex items-center justify-center gap-0.5 py-0.25 text-xs text-slate-300">
|
||||
<span>Push-to-Talk Key</span>
|
||||
<KeyPill label={pttKeyLabel} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid gap-0.5 grid-cols-1 lg:grid-cols-2">
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_1fr]">
|
||||
<p className="text-sm text-slate-200 text-center">PTT Mode</p>
|
||||
<p className="text-xs text-slate-400 text-center">
|
||||
Choose how holding your PTT key behaves: live mic stream, or record-and-send clip. If the live stream doesn't work, switch to clip mode.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-0.5">
|
||||
<button
|
||||
type="button"
|
||||
className={`button-dark w-full text-sm ${!clipMode ? 'bg-emerald-500 text-white hover:bg-emerald-500' : ''}`}
|
||||
onClick={() => setPttMode('live')}
|
||||
>
|
||||
Live Mic (WHIP)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`button-dark w-full text-sm ${clipMode ? 'bg-emerald-500 text-white hover:bg-emerald-500' : ''}`}
|
||||
onClick={() => setPttMode('clip')}
|
||||
>
|
||||
Record Clip
|
||||
</button>
|
||||
</div>
|
||||
<section className="surface">
|
||||
<div className="grid gap-1">
|
||||
<p className="text-sm text-slate-100 text-center">Audio Controls</p>
|
||||
<section className="surface">
|
||||
<div className="flex items-center justify-center gap-0.5 py-0.25 text-xs text-slate-300">
|
||||
<span>Push-to-Talk Key</span>
|
||||
<KeyPill label={pttKeyLabel} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_1fr]">
|
||||
<p className="text-sm text-slate-200 text-center">PTT Clip Status</p>
|
||||
<div className="grid gap-0.5 content-start">
|
||||
<StatusIndicator label="Mode" active={clipMode} detail={clipMode ? 'record clip' : 'live mic'} />
|
||||
<StatusIndicator label="PTT hold" active={pttActive} detail={pttActive ? 'held' : 'released'} />
|
||||
<StatusIndicator label="Recording" active={clipRecording} detail={clipRecording ? 'capturing' : 'idle'} />
|
||||
<StatusIndicator label="Playback" active={clipMode && uploadPlaying} detail={clipMode && uploadPlaying ? 'playing' : 'idle'} />
|
||||
<div className="grid gap-0.5 grid-cols-1 lg:grid-cols-2">
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_1fr]">
|
||||
<p className="text-sm text-slate-200 text-center">PTT Mode</p>
|
||||
<p className="text-xs text-slate-400 text-center">
|
||||
Choose how holding your PTT key behaves: live mic stream, or record-and-send clip. If the live stream doesn't work, switch to clip mode.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-0.5">
|
||||
<button
|
||||
type="button"
|
||||
className={`button-dark w-full text-sm ${!clipMode ? 'bg-emerald-500 text-white hover:bg-emerald-500' : ''}`}
|
||||
onClick={() => setPttMode('live')}
|
||||
>
|
||||
Live Mic (WHIP)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`button-dark w-full text-sm ${clipMode ? 'bg-emerald-500 text-white hover:bg-emerald-500' : ''}`}
|
||||
onClick={() => setPttMode('clip')}
|
||||
>
|
||||
Record Clip
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_1fr]">
|
||||
<p className="text-sm text-slate-200 text-center">PTT Clip Status</p>
|
||||
<div className="grid gap-0.5 content-start">
|
||||
<StatusIndicator label="Mode" active={clipMode} detail={clipMode ? 'record clip' : 'live mic'} />
|
||||
<StatusIndicator label="PTT hold" active={pttActive} detail={pttActive ? 'held' : 'released'} />
|
||||
<StatusIndicator label="Recording" active={clipRecording} detail={clipRecording ? 'capturing' : 'idle'} />
|
||||
<StatusIndicator label="Playback" active={clipMode && uploadPlaying} detail={clipMode && uploadPlaying ? 'playing' : 'idle'} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-0.5 grid-cols-1 lg:grid-cols-2">
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_1fr_auto]">
|
||||
<p className="text-sm text-slate-200 text-center">Audio Upload</p>
|
||||
|
||||
<label className="mx-auto grid w-full max-w-sm gap-0.5 text-xs text-slate-300 text-center">
|
||||
<span>Audio file (mp3 / wav / ogg)</span>
|
||||
<input
|
||||
className={`${fieldClass} text-center`}
|
||||
type="file"
|
||||
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg"
|
||||
disabled={working || !roverId}
|
||||
onChange={(event) => setSelectedUpload(event.target.files?.[0] || null)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="mx-auto w-full max-w-sm text-center">
|
||||
{selectedUpload ? (
|
||||
<div className="surface-muted text-xs text-slate-300">
|
||||
{selectedUpload.name} ({selectedUpload.size} bytes)
|
||||
</div>
|
||||
) : (
|
||||
<div className="surface-muted text-xs text-slate-500">No file selected</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-0.5">
|
||||
<button type="button" className="button-dark text-sm" disabled={working || !roverId} onClick={handleUploadPlay}>
|
||||
{working ? 'Working...' : 'Play Upload'}
|
||||
</button>
|
||||
<button type="button" className="button-dark text-sm" disabled={working || !roverId} onClick={handleUploadStop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_1fr]">
|
||||
<p className="text-sm text-slate-200 text-center">Live Microphone</p>
|
||||
<label className="surface-muted mx-auto flex w-full max-w-sm items-center justify-center gap-0.5 px-0.5 py-0.5 text-xs text-slate-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={openMicEnabled}
|
||||
disabled={!roverId || clipMode}
|
||||
onChange={(event) =>
|
||||
saveVipAudio((current) => ({ ...(current || {}), openMicEnabled: Boolean(event.target.checked) }))
|
||||
}
|
||||
/>
|
||||
<span>{clipMode ? 'Open mic (live mode only)' : 'Open mic'}</span>
|
||||
</label>
|
||||
<div className="grid gap-0.5 content-start">
|
||||
<StatusIndicator label="WHIP link" active={whipLinkActive} detail={micState} />
|
||||
<StatusIndicator label="Mic hot" active={micHot} detail={micHot ? 'transmitting' : 'muted'} />
|
||||
<div className="surface-muted text-center text-xs text-slate-400">Hold <KeyPill label={pttKeyLabel} /> to talk</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="surface">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm text-slate-200 text-center">Audio Status</p>
|
||||
<div className="grid gap-0.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-6">
|
||||
<StatusIndicator label="Forward pipe" active={pipelineConnected} detail={pipelineConnected ? 'connected' : 'offline'} />
|
||||
<StatusIndicator label="Upload playback" active={uploadPlaying} detail={uploadPlaying ? 'playing' : 'idle'} />
|
||||
<StatusIndicator label="Mic relay" active={micRelayActive} detail={micRelayActive ? 'active' : 'idle'} />
|
||||
<StatusIndicator label="WHIP transport" active={whipLinkActive} detail={micState} />
|
||||
<StatusIndicator label="Clip recording" active={clipRecording} detail={clipRecording ? 'recording' : 'idle'} />
|
||||
<StatusIndicator label="Clip sending" active={clipSending} detail={clipSending ? 'sending' : 'idle'} />
|
||||
</div>
|
||||
{message ? <div className="text-xs text-slate-400 text-center">{message}</div> : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-0.5 grid-cols-1 lg:grid-cols-2">
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_1fr_auto]">
|
||||
<p className="text-sm text-slate-200 text-center">Audio Upload</p>
|
||||
|
||||
<label className="mx-auto grid w-full max-w-sm gap-0.5 text-xs text-slate-300 text-center">
|
||||
<span>Audio file (mp3 / wav / ogg)</span>
|
||||
<input
|
||||
className={`${fieldClass} text-center`}
|
||||
type="file"
|
||||
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg"
|
||||
disabled={working || !roverId}
|
||||
onChange={(event) => setSelectedUpload(event.target.files?.[0] || null)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="mx-auto w-full max-w-sm text-center">
|
||||
{selectedUpload ? (
|
||||
<div className="surface-muted text-xs text-slate-300">
|
||||
{selectedUpload.name} ({selectedUpload.size} bytes)
|
||||
</div>
|
||||
) : (
|
||||
<div className="surface-muted text-xs text-slate-500">No file selected</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-0.5">
|
||||
<button type="button" className="button-dark text-sm" disabled={working || !roverId} onClick={handleUploadPlay}>
|
||||
{working ? 'Working...' : 'Play Upload'}
|
||||
</button>
|
||||
<button type="button" className="button-dark text-sm" disabled={working || !roverId} onClick={handleUploadStop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="surface h-full">
|
||||
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_1fr]">
|
||||
<p className="text-sm text-slate-200 text-center">Live Microphone</p>
|
||||
<label className="surface-muted mx-auto flex w-full max-w-sm items-center justify-center gap-0.5 px-0.5 py-0.5 text-xs text-slate-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={openMicEnabled}
|
||||
disabled={!roverId || clipMode}
|
||||
onChange={(event) =>
|
||||
saveVipAudio((current) => ({ ...(current || {}), openMicEnabled: Boolean(event.target.checked) }))
|
||||
}
|
||||
/>
|
||||
<span>{clipMode ? 'Open mic (live mode only)' : 'Open mic'}</span>
|
||||
</label>
|
||||
<div className="grid gap-0.5 content-start">
|
||||
<StatusIndicator label="WHIP link" active={whipLinkActive} detail={micState} />
|
||||
<StatusIndicator label="Mic hot" active={micHot} detail={micHot ? 'transmitting' : 'muted'} />
|
||||
<div className="surface-muted text-center text-xs text-slate-400">Hold <KeyPill label={pttKeyLabel} /> to talk</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="surface">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm text-slate-200 text-center">Audio Status</p>
|
||||
<div className="grid gap-0.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-6">
|
||||
<StatusIndicator label="Forward pipe" active={pipelineConnected} detail={pipelineConnected ? 'connected' : 'offline'} />
|
||||
<StatusIndicator label="Upload playback" active={uploadPlaying} detail={uploadPlaying ? 'playing' : 'idle'} />
|
||||
<StatusIndicator label="Mic relay" active={micRelayActive} detail={micRelayActive ? 'active' : 'idle'} />
|
||||
<StatusIndicator label="WHIP transport" active={whipLinkActive} detail={micState} />
|
||||
<StatusIndicator label="Clip recording" active={clipRecording} detail={clipRecording ? 'recording' : 'idle'} />
|
||||
<StatusIndicator label="Clip sending" active={clipSending} detail={clipSending ? 'sending' : 'idle'} />
|
||||
</div>
|
||||
{message ? <div className="text-xs text-slate-400 text-center">{message}</div> : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user