mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 17:40:46 -04:00
bebahba
This commit is contained in:
@@ -16,6 +16,7 @@ import { LinkButtonsPanel } from '../../../components/UserListPanel/index.jsx';
|
||||
import Tabs, { Tab, TabList, TabPanel, TabPanels } from '../../../components/Tabs/index.jsx';
|
||||
import ActivitiesTab from '../tabs/shared/ActivitiesTab/index.jsx';
|
||||
import VipTab from '../tabs/shared/VipTab/index.jsx';
|
||||
import VipTabButton from '../tabs/shared/VipTabButton/index.jsx';
|
||||
import HelpTab from '../tabs/shared/HelpTab/index.jsx';
|
||||
import SettingsTab from '../tabs/shared/SettingsTab/index.jsx';
|
||||
import { themeGapClass } from '../../../themes/index.js';
|
||||
@@ -26,7 +27,7 @@ function LeftColumnTabs() {
|
||||
<TabList className="sticky top-0 z-30 shrink-0 bg-neutral-950">
|
||||
<Tab id="room">Room</Tab>
|
||||
<Tab id="activities">Activities</Tab>
|
||||
<Tab id="vip">Vip</Tab>
|
||||
<VipTabButton />
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel id="room">
|
||||
|
||||
@@ -19,7 +19,6 @@ import GPIOToggleControl from '../../../components/GPIOToggleControl/index.jsx';
|
||||
import HornControl from '../../../components/HornControl/index.jsx';
|
||||
import CameraTiltControl from '../../../components/CameraTiltControl/index.jsx';
|
||||
import { useSessionSelector } from '../../../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../../../settings/index.js';
|
||||
import OverseerPreferencePanel from '../../../components/OverseerPreferencePanel/index.jsx';
|
||||
import CardFrame from '../../../components/CardFrame/index.jsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -27,6 +26,7 @@ import { useManualDockAssist } from '../../../features/manualDockAssist/useManua
|
||||
import { themeGapClass, themeStackClass } from '../../../themes/index.js';
|
||||
import ActivitiesTab from '../tabs/shared/ActivitiesTab/index.jsx';
|
||||
import VipTab from '../tabs/shared/VipTab/index.jsx';
|
||||
import VipTabButton from '../tabs/shared/VipTabButton/index.jsx';
|
||||
import HelpTab from '../tabs/shared/HelpTab/index.jsx';
|
||||
import SettingsTab from '../tabs/shared/SettingsTab/index.jsx';
|
||||
|
||||
@@ -214,29 +214,6 @@ export default function RightPaneTabs() {
|
||||
const [activeTab, setActiveTab] = useState('telemetry');
|
||||
const chatDockRef = useRef(null);
|
||||
const [chatDockHeight, setChatDockHeight] = useState(CHAT_DOCK_INITIAL_HEIGHT);
|
||||
const isVerified = useSessionSelector((state) => Boolean(state.session?.isVerified));
|
||||
const ownRoverId = useSessionSelector((state) => String(state.session?.assignment?.roverId || '').trim());
|
||||
const ownAudioForward = useSessionSelector((state) => {
|
||||
const roverId = String(state.session?.assignment?.roverId || '').trim();
|
||||
return roverId ? state.session?.audioForward?.[roverId] || null : null;
|
||||
});
|
||||
const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive));
|
||||
const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' });
|
||||
const vipDotClass = isVerified ? 'bg-emerald-400' : 'bg-red-600';
|
||||
const openMicEnabled = Boolean(vipAudio?.openMicEnabled);
|
||||
const pttMode = vipAudio?.pttMode === 'clip' ? 'clip' : 'live';
|
||||
const vipMicActive = Boolean(
|
||||
ownRoverId &&
|
||||
isVerified &&
|
||||
(pttMode === 'clip' ? pttActive : (openMicEnabled || pttActive)),
|
||||
);
|
||||
const vipClipPlaying = Boolean(
|
||||
ownRoverId &&
|
||||
isVerified &&
|
||||
pttMode === 'clip' &&
|
||||
ownAudioForward?.source === 'upload' &&
|
||||
ownAudioForward?.state === 'playing',
|
||||
);
|
||||
const showOverseerPreferencePanel = useSessionSelector((state) => {
|
||||
const vote = state.session?.overseerVote;
|
||||
|
||||
@@ -345,16 +322,7 @@ export default function RightPaneTabs() {
|
||||
<TabList>
|
||||
<Tab id="telemetry">Controls</Tab>
|
||||
<Tab id="activities">Activities</Tab>
|
||||
<Tab id="vip" highlight={vipClipPlaying ? 'green' : vipMicActive ? 'pink' : 'none'}>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span>VIP</span>
|
||||
<span
|
||||
className={`inline-block h-3 w-3 rounded-full ${vipDotClass}`}
|
||||
aria-hidden="true"
|
||||
title={isVerified ? 'Verified' : 'Not verified'}
|
||||
/>
|
||||
</span>
|
||||
</Tab>
|
||||
<VipTabButton />
|
||||
<Tab id="help">Help</Tab>
|
||||
<Tab id="settings">Settings</Tab>
|
||||
</TabList>
|
||||
|
||||
@@ -2,26 +2,16 @@
|
||||
// Purpose: Owns the shared mobile tab selector, state, and per-tab modules.
|
||||
import { useCallback, useState } from 'react';
|
||||
import Tabs, { Tab, TabList, TabPanels } from '../../../components/Tabs/index.jsx';
|
||||
import { useSessionSelector } from '../../../context/SessionContext.jsx';
|
||||
import { useControlSelector } from '../../../controls/index.js';
|
||||
import { useSettingsNamespace } from '../../../settings/index.js';
|
||||
import MobileChatTab from '../tabs/mobile/ChatTab/index.jsx';
|
||||
import RoomControlsTab from '../tabs/mobile/RoomControlsTab/index.jsx';
|
||||
import ActivitiesTab from '../tabs/shared/ActivitiesTab/index.jsx';
|
||||
import VipTab from '../tabs/shared/VipTab/index.jsx';
|
||||
import VipTabButton from '../tabs/shared/VipTabButton/index.jsx';
|
||||
import HelpTab from '../tabs/shared/HelpTab/index.jsx';
|
||||
import SettingsTab from '../tabs/shared/SettingsTab/index.jsx';
|
||||
|
||||
export default function MobileTabs() {
|
||||
const [activeTab, setActiveTab] = useState('chat');
|
||||
const isVerified = useSessionSelector((state) => Boolean(state.session?.isVerified));
|
||||
const ownRoverId = useSessionSelector((state) => String(state.session?.assignment?.roverId || '').trim());
|
||||
const ownAudioForward = useSessionSelector((state) => ownRoverId ? state.session?.audioForward?.[ownRoverId] || null : null);
|
||||
const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive));
|
||||
const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' });
|
||||
const pttMode = vipAudio?.pttMode === 'clip' ? 'clip' : 'live';
|
||||
const vipMicActive = Boolean(ownRoverId && isVerified && (pttMode === 'clip' ? pttActive : (vipAudio?.openMicEnabled || pttActive)));
|
||||
const vipClipPlaying = Boolean(ownRoverId && isVerified && pttMode === 'clip' && ownAudioForward?.source === 'upload' && ownAudioForward?.state === 'playing');
|
||||
const handleTabChange = useCallback((tab) => setActiveTab(tab), []);
|
||||
|
||||
return (
|
||||
@@ -30,12 +20,7 @@ export default function MobileTabs() {
|
||||
<TabList>
|
||||
<Tab id="chat">Chat</Tab>
|
||||
<Tab id="activities">Activities</Tab>
|
||||
<Tab id="vip" highlight={vipClipPlaying ? 'green' : vipMicActive ? 'pink' : 'none'}>
|
||||
<span className="inline-flex items-center gap-0.5">
|
||||
<span>VIP</span>
|
||||
<span className={`inline-block h-1.5 w-1.5 rounded-full ${isVerified ? 'bg-emerald-400' : 'bg-amber-400'}`} aria-hidden="true" title={isVerified ? 'Verified' : 'Not verified'} />
|
||||
</span>
|
||||
</Tab>
|
||||
<VipTabButton compact />
|
||||
<Tab id="roomcontrols">Room Controls</Tab>
|
||||
<Tab id="help">Help</Tab>
|
||||
<Tab id="settings">Settings</Tab>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Driver VIP Tab Button
|
||||
// Purpose: Keeps verification and active-audio status identical across every driver layout.
|
||||
import { Tab } from '../../../../../components/Tabs/index.jsx';
|
||||
import { useControlSelector } from '../../../../../controls/index.js';
|
||||
import { useSessionSelector } from '../../../../../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../../../../../settings/index.js';
|
||||
|
||||
export default function VipTabButton({ compact = false }) {
|
||||
const isVerified = useSessionSelector((state) => Boolean(state.session?.isVerified));
|
||||
const ownRoverId = useSessionSelector((state) => String(state.session?.assignment?.roverId || '').trim());
|
||||
const ownAudioForward = useSessionSelector((state) => (
|
||||
ownRoverId ? state.session?.audioForward?.[ownRoverId] || null : null
|
||||
));
|
||||
const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive));
|
||||
const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' });
|
||||
const clipMode = vipAudio?.pttMode === 'clip';
|
||||
|
||||
// Pink represents a live microphone path, while green means an uploaded clip
|
||||
// is actively forwarding. Keeping these conditions here prevents desktop and
|
||||
// mobile selectors from drifting away from the mounted VIP audio lifecycle.
|
||||
const micActive = Boolean(
|
||||
ownRoverId
|
||||
&& isVerified
|
||||
&& (clipMode ? pttActive : (vipAudio?.openMicEnabled || pttActive)),
|
||||
);
|
||||
const clipPlaying = Boolean(
|
||||
ownRoverId
|
||||
&& isVerified
|
||||
&& clipMode
|
||||
&& ownAudioForward?.source === 'upload'
|
||||
&& ownAudioForward?.state === 'playing',
|
||||
);
|
||||
const dotSizeClass = compact ? 'h-1.5 w-1.5' : 'h-3 w-3';
|
||||
const gapClass = compact ? 'gap-0.5' : 'gap-2';
|
||||
|
||||
return (
|
||||
<Tab id="vip" highlight={clipPlaying ? 'green' : micActive ? 'pink' : 'none'}>
|
||||
<span className={`inline-flex items-center ${gapClass}`}>
|
||||
<span>VIP</span>
|
||||
<span
|
||||
className={`inline-block rounded-full ${dotSizeClass} ${isVerified ? 'bg-emerald-400' : 'bg-red-600'}`}
|
||||
aria-hidden="true"
|
||||
title={isVerified ? 'Verified' : 'Not verified'}
|
||||
/>
|
||||
</span>
|
||||
</Tab>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user