mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
ui
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||||
<title>Multi Roomba Rover</title>
|
<title>Multi Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CIRCS_69.js"></script>
|
<script type="module" crossorigin src="/assets/index-D4q08ieD.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-WqYsCIRI.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-WqYsCIRI.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export default function VipPanel() {
|
|||||||
audioForwardByRover={session?.audioForward || {}}
|
audioForwardByRover={session?.audioForward || {}}
|
||||||
playUploadedAudio={playUploadedAudio}
|
playUploadedAudio={playUploadedAudio}
|
||||||
stopUploadedAudio={stopUploadedAudio}
|
stopUploadedAudio={stopUploadedAudio}
|
||||||
onMessage={setMessage}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<VipVerificationCard
|
<VipVerificationCard
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { MAX_UPLOAD_BYTES, bytesToBase64, fieldClass } from './constants.js';
|
import {
|
||||||
|
MAX_UPLOAD_BYTES,
|
||||||
|
bytesToBase64,
|
||||||
|
fieldClass,
|
||||||
|
flowWrapClass,
|
||||||
|
innerFlowClass,
|
||||||
|
} from './constants.js';
|
||||||
|
|
||||||
export default function VipAudioForwardingCard({
|
export default function VipAudioForwardingCard({
|
||||||
roster = [],
|
roster = [],
|
||||||
@@ -7,13 +13,12 @@ export default function VipAudioForwardingCard({
|
|||||||
audioForwardByRover = {},
|
audioForwardByRover = {},
|
||||||
playUploadedAudio,
|
playUploadedAudio,
|
||||||
stopUploadedAudio,
|
stopUploadedAudio,
|
||||||
onMessage,
|
|
||||||
}) {
|
}) {
|
||||||
const [selectedRoverId, setSelectedRoverId] = useState('');
|
|
||||||
const [selectedUpload, setSelectedUpload] = useState(null);
|
const [selectedUpload, setSelectedUpload] = useState(null);
|
||||||
const [working, setWorking] = useState(false);
|
const [working, setWorking] = useState(false);
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
const singleRoverId = roster.length === 1 ? roster[0].id : '';
|
const singleRoverId = roster.length === 1 ? roster[0].id : '';
|
||||||
const targetRoverId = String(selectedRoverId || singleRoverId || ownRoverId || '').trim();
|
const targetRoverId = String(singleRoverId || ownRoverId || '').trim();
|
||||||
const selectedForwardState = useMemo(
|
const selectedForwardState = useMemo(
|
||||||
() => (targetRoverId ? audioForwardByRover?.[targetRoverId] || null : null),
|
() => (targetRoverId ? audioForwardByRover?.[targetRoverId] || null : null),
|
||||||
[audioForwardByRover, targetRoverId],
|
[audioForwardByRover, targetRoverId],
|
||||||
@@ -22,19 +27,19 @@ export default function VipAudioForwardingCard({
|
|||||||
const handleUploadPlay = async () => {
|
const handleUploadPlay = async () => {
|
||||||
const roverId = targetRoverId;
|
const roverId = targetRoverId;
|
||||||
if (!roverId) {
|
if (!roverId) {
|
||||||
onMessage?.('Take control of a rover first.');
|
setMessage('Take control of a rover first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selectedUpload) {
|
if (!selectedUpload) {
|
||||||
onMessage?.('Select an audio file first.');
|
setMessage('Select an audio file first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedUpload.size > MAX_UPLOAD_BYTES) {
|
if (selectedUpload.size > MAX_UPLOAD_BYTES) {
|
||||||
onMessage?.(`File too large (max ${MAX_UPLOAD_BYTES} bytes).`);
|
setMessage(`File too large (max ${MAX_UPLOAD_BYTES} bytes).`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setWorking(true);
|
setWorking(true);
|
||||||
onMessage?.('');
|
setMessage('');
|
||||||
try {
|
try {
|
||||||
const buffer = await selectedUpload.arrayBuffer();
|
const buffer = await selectedUpload.arrayBuffer();
|
||||||
const base64 = bytesToBase64(new Uint8Array(buffer));
|
const base64 = bytesToBase64(new Uint8Array(buffer));
|
||||||
@@ -44,9 +49,9 @@ export default function VipAudioForwardingCard({
|
|||||||
mime: selectedUpload.type || '',
|
mime: selectedUpload.type || '',
|
||||||
dataBase64: base64,
|
dataBase64: base64,
|
||||||
});
|
});
|
||||||
onMessage?.(`Playing upload on ${roverId}.`);
|
setMessage(`Playing upload on ${roverId}.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onMessage?.(err.message || 'Failed to play upload.');
|
setMessage(err.message || 'Failed to play upload.');
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(false);
|
setWorking(false);
|
||||||
}
|
}
|
||||||
@@ -55,75 +60,56 @@ export default function VipAudioForwardingCard({
|
|||||||
const handleUploadStop = async () => {
|
const handleUploadStop = async () => {
|
||||||
const roverId = targetRoverId;
|
const roverId = targetRoverId;
|
||||||
if (!roverId) {
|
if (!roverId) {
|
||||||
onMessage?.('Take control of a rover first.');
|
setMessage('Take control of a rover first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setWorking(true);
|
setWorking(true);
|
||||||
onMessage?.('');
|
setMessage('');
|
||||||
try {
|
try {
|
||||||
await stopUploadedAudio?.(roverId);
|
await stopUploadedAudio?.(roverId);
|
||||||
onMessage?.(`Stopped upload on ${roverId}.`);
|
setMessage(`Stopped upload on ${roverId}.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onMessage?.(err.message || 'Failed to stop upload.');
|
setMessage(err.message || 'Failed to stop upload.');
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(false);
|
setWorking(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="surface space-y-0.5 text-sm text-slate-200">
|
<section className={`surface ${flowWrapClass}`}>
|
||||||
<p className="text-slate-300">VIP Audio Forwarding</p>
|
<div className={innerFlowClass}>
|
||||||
{roster.length > 1 ? (
|
<p className="text-sm text-slate-300">VIP Audio Forwarding</p>
|
||||||
<label className="grid gap-0.5 text-xs text-slate-300">
|
<label className="grid w-full gap-0.5 text-xs text-slate-300">
|
||||||
<span>Target rover</span>
|
<span>Audio file (mp3 / wav / ogg)</span>
|
||||||
<select
|
<input
|
||||||
className={fieldClass}
|
className={fieldClass}
|
||||||
value={selectedRoverId}
|
type="file"
|
||||||
onChange={(event) => setSelectedRoverId(event.target.value)}
|
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg"
|
||||||
disabled={working}
|
disabled={working || !targetRoverId}
|
||||||
>
|
onChange={(event) => setSelectedUpload(event.target.files?.[0] || null)}
|
||||||
<option value="">Select rover</option>
|
/>
|
||||||
{roster.map((rover) => (
|
|
||||||
<option key={rover.id} value={rover.id}>
|
|
||||||
{rover.name || rover.id}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
) : (
|
{selectedUpload ? (
|
||||||
<div className="surface-muted text-xs text-slate-300 text-center">
|
<div className="surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center">
|
||||||
Rover: {roster[0]?.name || roster[0]?.id || 'none selected'}
|
{selectedUpload.name} ({selectedUpload.size} bytes)
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="flex justify-center gap-0.5">
|
||||||
|
<button type="button" className="button-dark text-sm" disabled={working || !targetRoverId} onClick={handleUploadPlay}>
|
||||||
|
{working ? 'Working...' : 'Play Upload'}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="button-dark text-sm" disabled={working || !targetRoverId} onClick={handleUploadStop}>
|
||||||
|
Stop
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{selectedForwardState ? (
|
||||||
<label className="grid gap-0.5 text-xs text-slate-300">
|
<div className="surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center">
|
||||||
<span>Audio file (mp3 / wav / ogg)</span>
|
state: {selectedForwardState.state || 'idle'}
|
||||||
<input
|
{selectedForwardState.error ? ` | error: ${selectedForwardState.error}` : ''}
|
||||||
className={fieldClass}
|
</div>
|
||||||
type="file"
|
) : null}
|
||||||
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg"
|
{message ? <div className="text-xs text-slate-400 text-center">{message}</div> : null}
|
||||||
disabled={working || !targetRoverId}
|
|
||||||
onChange={(event) => setSelectedUpload(event.target.files?.[0] || null)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{selectedUpload ? (
|
|
||||||
<div className="surface-muted text-xs text-slate-300 text-center">
|
|
||||||
{selectedUpload.name} ({selectedUpload.size} bytes)
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="flex justify-center gap-0.5">
|
|
||||||
<button type="button" className="button-dark text-sm" disabled={working || !targetRoverId} onClick={handleUploadPlay}>
|
|
||||||
{working ? 'Working...' : 'Play Upload'}
|
|
||||||
</button>
|
|
||||||
<button type="button" className="button-dark text-sm" disabled={working || !targetRoverId} onClick={handleUploadStop}>
|
|
||||||
Stop
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{selectedForwardState ? (
|
|
||||||
<div className="surface-muted text-xs text-slate-300 text-center">
|
|
||||||
state: {selectedForwardState.state || 'idle'}
|
|
||||||
{selectedForwardState.error ? ` | error: ${selectedForwardState.error}` : ''}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user