mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
mobile swappy controls
This commit is contained in:
@@ -8,6 +8,8 @@ This means that locking / unlocking will act a little different than standard ro
|
|||||||
- private status is defined in the roverd config
|
- private status is defined in the roverd config
|
||||||
- needs to never leak through access to anyone while locked
|
- needs to never leak through access to anyone while locked
|
||||||
- unlocking a private rover is a big deal for verified users (opening up a rover in the main living space for a special event)
|
- unlocking a private rover is a big deal for verified users (opening up a rover in the main living space for a special event)
|
||||||
|
- not included in LLM events system
|
||||||
|
- basically needs to be online but completely hidden when its not open
|
||||||
|
|
||||||
## locking / unlocking:
|
## locking / unlocking:
|
||||||
- private rovers start locked
|
- private rovers start locked
|
||||||
|
|||||||
+2
-1
@@ -9,7 +9,8 @@
|
|||||||
- audio files first [x]
|
- audio files first [x]
|
||||||
- then voice chat [x]
|
- then voice chat [x]
|
||||||
8. mobile controls column swapping (optional joystick on left)
|
8. mobile controls column swapping (optional joystick on left)
|
||||||
9. home assistant rover mute switch
|
9. fix fullscreen on mobile so that you can re-enter it
|
||||||
|
10. home assistant rover mute switch
|
||||||
|
|
||||||
# relative pipe dreams:
|
# relative pipe dreams:
|
||||||
1. VPS video forwarding
|
1. VPS video forwarding
|
||||||
|
|||||||
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-8-JHgslZ.js"></script>
|
<script type="module" crossorigin src="/assets/index-Cf-6drTv.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-B-TLXecO.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B-TLXecO.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+20
-10
@@ -3,8 +3,8 @@ import TelemetryPanel from './components/TelemetryPanel.jsx';
|
|||||||
import ReplaySourcesPanel from './components/ReplaySourcesPanel.jsx';
|
import ReplaySourcesPanel from './components/ReplaySourcesPanel.jsx';
|
||||||
import AlertFeed from './components/AlertFeed.jsx';
|
import AlertFeed from './components/AlertFeed.jsx';
|
||||||
import MobileControls, {
|
import MobileControls, {
|
||||||
MobileLeftColumn,
|
MobileActionsColumn,
|
||||||
MobileRightColumn,
|
MobileDriveColumn,
|
||||||
} from './components/MobileControls.jsx';
|
} from './components/MobileControls.jsx';
|
||||||
import {
|
import {
|
||||||
ControlSystemProvider,
|
ControlSystemProvider,
|
||||||
@@ -160,11 +160,11 @@ function MobileFeatureTabs({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MobilePortraitLayout({ onOpenHelpOverlay }) {
|
function MobilePortraitLayout({ onOpenHelpOverlay, swapMobileControlColumns = false }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<DriverVideoPanel layoutFormat="mobile-portrait" />
|
<DriverVideoPanel layoutFormat="mobile-portrait" />
|
||||||
<MobileControls />
|
<MobileControls swapColumns={swapMobileControlColumns} />
|
||||||
<div className="grid gap-0.5 grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">
|
<div className="grid gap-0.5 grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">
|
||||||
<ReplaySourcesPanel panelId="replay-sources-mobile-portrait" />
|
<ReplaySourcesPanel panelId="replay-sources-mobile-portrait" />
|
||||||
<RoverQueuesPanel />
|
<RoverQueuesPanel />
|
||||||
@@ -179,12 +179,18 @@ function MobilePortraitLayout({ onOpenHelpOverlay }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MobileLandscapeLayout({ onOpenHelpOverlay }) {
|
function MobileLandscapeLayout({ onOpenHelpOverlay, swapMobileControlColumns = false }) {
|
||||||
const columnClass = 'self-start h-[min(100svh,32rem)]';
|
const columnClass = 'self-start h-[min(100svh,32rem)]';
|
||||||
|
const firstColumn = swapMobileControlColumns
|
||||||
|
? <MobileDriveColumn layout="landscape" className={columnClass} />
|
||||||
|
: <MobileActionsColumn layout="landscape" className={columnClass} />;
|
||||||
|
const secondColumn = swapMobileControlColumns
|
||||||
|
? <MobileActionsColumn layout="landscape" className={columnClass} />
|
||||||
|
: <MobileDriveColumn layout="landscape" className={columnClass} />;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<section className="grid min-h-screen grid-cols-[minmax(0,0.7fr)_minmax(0,2.1fr)_minmax(0,0.7fr)] gap-0.5">
|
<section className="grid min-h-screen grid-cols-[minmax(0,0.7fr)_minmax(0,2.1fr)_minmax(0,0.7fr)] gap-0.5">
|
||||||
<MobileLeftColumn layout="landscape" className={columnClass} />
|
{firstColumn}
|
||||||
<div>
|
<div>
|
||||||
<DriverVideoPanel layoutFormat="mobile-landscape" />
|
<DriverVideoPanel layoutFormat="mobile-landscape" />
|
||||||
<div className="grid gap-0.5 grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">
|
<div className="grid gap-0.5 grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)]">
|
||||||
@@ -193,7 +199,7 @@ function MobileLandscapeLayout({ onOpenHelpOverlay }) {
|
|||||||
</div>
|
</div>
|
||||||
{/* <TelemetryPanel /> */}
|
{/* <TelemetryPanel /> */}
|
||||||
</div>
|
</div>
|
||||||
<MobileRightColumn layout="landscape" className={columnClass} />
|
{secondColumn}
|
||||||
</section>
|
</section>
|
||||||
<div className="flex flex-col gap-0.5 pb-0">
|
<div className="flex flex-col gap-0.5 pb-0">
|
||||||
<MobileFeatureTabs
|
<MobileFeatureTabs
|
||||||
@@ -234,6 +240,10 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
|||||||
status: helpStatus,
|
status: helpStatus,
|
||||||
save: saveHelpSettings,
|
save: saveHelpSettings,
|
||||||
} = useSettingsNamespace('help', { showOnLoad: true });
|
} = useSettingsNamespace('help', { showOnLoad: true });
|
||||||
|
const { value: pageSettings } = useSettingsNamespace('page', {
|
||||||
|
swapMobileControlColumns: false,
|
||||||
|
});
|
||||||
|
const swapMobileControlColumns = Boolean(pageSettings?.swapMobileControlColumns);
|
||||||
const [helpVisible, setHelpVisible] = useState(false);
|
const [helpVisible, setHelpVisible] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -260,9 +270,9 @@ function AppWithProviders({ layout, isDesktop, fullscreen }) {
|
|||||||
isDesktop
|
isDesktop
|
||||||
? <DesktopLayout layout={layout} onOpenHelpOverlay={openHelp} />
|
? <DesktopLayout layout={layout} onOpenHelpOverlay={openHelp} />
|
||||||
: layout === 'mobile-landscape'
|
: layout === 'mobile-landscape'
|
||||||
? <MobileLandscapeLayout onOpenHelpOverlay={openHelp} />
|
? <MobileLandscapeLayout onOpenHelpOverlay={openHelp} swapMobileControlColumns={swapMobileControlColumns} />
|
||||||
: <MobilePortraitLayout onOpenHelpOverlay={openHelp} />,
|
: <MobilePortraitLayout onOpenHelpOverlay={openHelp} swapMobileControlColumns={swapMobileControlColumns} />,
|
||||||
[isDesktop, layout, openHelp],
|
[isDesktop, layout, openHelp, swapMobileControlColumns],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ function MobileAuxButton({ id, label, values, color, disabled, onPress, onReleas
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MobileLeftColumnContent({ layout }) {
|
function MobileActionsColumnContent({ layout }) {
|
||||||
const {
|
const {
|
||||||
state: { roverId, camera, horn },
|
state: { roverId, camera, horn },
|
||||||
pipeline,
|
pipeline,
|
||||||
@@ -347,15 +347,15 @@ function MobileLeftColumnContent({ layout }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MobileLeftColumn({ layout, className = '' }) {
|
export function MobileActionsColumn({ layout, className = '' }) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col gap-0.5 ${className}`.trim()}>
|
<div className={`flex flex-col gap-0.5 ${className}`.trim()}>
|
||||||
<MobileLeftColumnContent layout={layout} />
|
<MobileActionsColumnContent layout={layout} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MobileRightColumn({ layout, className = '' }) {
|
export function MobileDriveColumn({ layout, className = '' }) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col gap-0.5 ${className}`.trim()}>
|
<div className={`flex flex-col gap-0.5 ${className}`.trim()}>
|
||||||
<MobileJoystickPanel layout={layout === 'landscape' ? 'landscape' : 'portrait'} />
|
<MobileJoystickPanel layout={layout === 'landscape' ? 'landscape' : 'portrait'} />
|
||||||
@@ -363,13 +363,19 @@ export function MobileRightColumn({ layout, className = '' }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MobilePortraitControls() {
|
export default function MobilePortraitControls({ swapColumns = false }) {
|
||||||
const columnHeight = 'h-[min(60svh,24rem)]';
|
const columnHeight = 'h-[min(60svh,24rem)]';
|
||||||
|
const firstColumn = swapColumns
|
||||||
|
? <MobileDriveColumn layout="portrait" className={columnHeight} />
|
||||||
|
: <MobileActionsColumn layout="portrait" className={columnHeight} />;
|
||||||
|
const secondColumn = swapColumns
|
||||||
|
? <MobileActionsColumn layout="portrait" className={columnHeight} />
|
||||||
|
: <MobileDriveColumn layout="portrait" className={columnHeight} />;
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
<div className="grid grid-cols-2 gap-0.5 items-stretch">
|
<div className="grid grid-cols-2 gap-0.5 items-stretch">
|
||||||
<MobileLeftColumn layout="portrait" className={columnHeight} />
|
{firstColumn}
|
||||||
<MobileRightColumn layout="portrait" className={columnHeight} />
|
{secondColumn}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ export default function SettingsPanel() {
|
|||||||
const { value: pageSettings, save: savePageSettings } = useSettingsNamespace('page', {
|
const { value: pageSettings, save: savePageSettings } = useSettingsNamespace('page', {
|
||||||
hudMapDesktop: false,
|
hudMapDesktop: false,
|
||||||
connectionTransport: 'websocket',
|
connectionTransport: 'websocket',
|
||||||
|
swapMobileControlColumns: false,
|
||||||
});
|
});
|
||||||
const { value: audioSettings, save: saveAudioSettings } = useSettingsNamespace('audio', AUDIO_SETTINGS_DEFAULTS);
|
const { value: audioSettings, save: saveAudioSettings } = useSettingsNamespace('audio', AUDIO_SETTINGS_DEFAULTS);
|
||||||
const connectionTransport = pageSettings?.connectionTransport || 'websocket';
|
const connectionTransport = pageSettings?.connectionTransport || 'websocket';
|
||||||
|
const swapMobileControlColumns = Boolean(pageSettings?.swapMobileControlColumns);
|
||||||
const masterVolume = Number.isFinite(audioSettings?.masterVolume) ? audioSettings.masterVolume : AUDIO_SETTINGS_DEFAULTS.masterVolume;
|
const masterVolume = Number.isFinite(audioSettings?.masterVolume) ? audioSettings.masterVolume : AUDIO_SETTINGS_DEFAULTS.masterVolume;
|
||||||
const alertVolume = Number.isFinite(audioSettings?.alertVolume) ? audioSettings.alertVolume : AUDIO_SETTINGS_DEFAULTS.alertVolume;
|
const alertVolume = Number.isFinite(audioSettings?.alertVolume) ? audioSettings.alertVolume : AUDIO_SETTINGS_DEFAULTS.alertVolume;
|
||||||
const roverVolume = Number.isFinite(audioSettings?.roverVolume) ? audioSettings.roverVolume : AUDIO_SETTINGS_DEFAULTS.roverVolume;
|
const roverVolume = Number.isFinite(audioSettings?.roverVolume) ? audioSettings.roverVolume : AUDIO_SETTINGS_DEFAULTS.roverVolume;
|
||||||
@@ -80,6 +82,11 @@ export default function SettingsPanel() {
|
|||||||
const checked = Boolean(event.target.checked);
|
const checked = Boolean(event.target.checked);
|
||||||
saveAudioSettings((current) => ({ ...(current ?? {}), mainBrushDuckEnabled: checked }));
|
saveAudioSettings((current) => ({ ...(current ?? {}), mainBrushDuckEnabled: checked }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSwapMobileControlColumns = (event) => {
|
||||||
|
const checked = Boolean(event.target.checked);
|
||||||
|
savePageSettings((current) => ({ ...(current ?? {}), swapMobileControlColumns: checked }));
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Tabs defaultTab="keybindings">
|
<Tabs defaultTab="keybindings">
|
||||||
<TabList>
|
<TabList>
|
||||||
@@ -114,6 +121,18 @@ export default function SettingsPanel() {
|
|||||||
</label>
|
</label>
|
||||||
<p className="text-xs text-slate-500">Mobile HUD keeps the map on by default.</p>
|
<p className="text-xs text-slate-500">Mobile HUD keeps the map on by default.</p>
|
||||||
</section>
|
</section>
|
||||||
|
<section className="panel-section space-y-0.5 text-sm">
|
||||||
|
<p className="text-slate-400">Mobile controls</p>
|
||||||
|
<label className="flex items-center gap-0.5 text-slate-200">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="accent-emerald-500"
|
||||||
|
checked={swapMobileControlColumns}
|
||||||
|
onChange={handleSwapMobileControlColumns}
|
||||||
|
/>
|
||||||
|
<span>Swap control columns (put joystick on the left)</span>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
<section className="panel-section space-y-0.5 text-sm">
|
<section className="panel-section space-y-0.5 text-sm">
|
||||||
<p className="text-slate-400">Audio</p>
|
<p className="text-slate-400">Audio</p>
|
||||||
<label className="grid gap-0.5 text-slate-200">
|
<label className="grid gap-0.5 text-slate-200">
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const HELP_CONTENT = {
|
|||||||
title: 'Driving the rover',
|
title: 'Driving the rover',
|
||||||
items: [
|
items: [
|
||||||
{ segments: ['Press the "Start Driving" button onscreen to put the rover into driving mode.'] },
|
{ segments: ['Press the "Start Driving" button onscreen to put the rover into driving mode.'] },
|
||||||
'Look below the rover video. Use the joystick on the right to move the rover, and hold the buttons on the left to run the aux motors.'
|
'Look below the rover video. Use the joystick column to move the rover, and hold the aux buttons in the other control column.'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -154,7 +154,7 @@ export const HELP_CONTENT = {
|
|||||||
title: 'Driving the rover',
|
title: 'Driving the rover',
|
||||||
items: [
|
items: [
|
||||||
{ segments: ['Press the "Start Driving" button, or the "Drive" button to put the rover into driving mode.'] },
|
{ segments: ['Press the "Start Driving" button, or the "Drive" button to put the rover into driving mode.'] },
|
||||||
'Use the joystick to the right of the video feed to move the rover, and hold the buttons on the left to run the aux motors.'
|
'Use the joystick column beside the video feed to move the rover, and hold the aux buttons in the other control column.'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user