mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 17:40:46 -04:00
optional but always on transfer
This commit is contained in:
@@ -5,8 +5,9 @@ import { useMemo, useState } from 'react';
|
||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
import CardFrame from '../CardFrame/index.jsx';
|
||||
import RoverQueuesPanel from '../RoverQueuesPanel/index.jsx';
|
||||
import { openExternalRoverWithPrompt } from '../../lib/interInstanceTransfer.js';
|
||||
import { openExternalRover } from '../../lib/interInstanceTransfer.js';
|
||||
import { isFeatureEnabled } from '../../lib/features.js';
|
||||
import { useSettingsNamespace } from '../../settings/index.js';
|
||||
|
||||
function classNames(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
@@ -72,6 +73,8 @@ function InstancePanel({ remote, children = null }) {
|
||||
const features = featureEntries(instance.features);
|
||||
const color = instance.color || '#64748b';
|
||||
const online = Boolean(remote?.online);
|
||||
const { value: pageSettings } = useSettingsNamespace('page', { interInstanceTransferSettings: true });
|
||||
const includeSettings = pageSettings?.interInstanceTransferSettings !== false;
|
||||
return (
|
||||
<CardFrame
|
||||
title={instance.name || remote.url || 'External server'}
|
||||
@@ -92,7 +95,11 @@ function InstancePanel({ remote, children = null }) {
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-center gap-1 text-center">
|
||||
<InstanceStatus remote={remote} />
|
||||
<button type="button" className="button-dark" onClick={() => openExternalRoverWithPrompt(remote, '')}>
|
||||
<button
|
||||
type="button"
|
||||
className="button-dark"
|
||||
onClick={() => openExternalRover(remote, '', { includeSettings })}
|
||||
>
|
||||
Visit server
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,9 +7,10 @@ import { useSharedClock } from '../../hooks/useSharedClock.js';
|
||||
import CardFrame from '../CardFrame/index.jsx';
|
||||
import RoverLabel from '../RoverLabel/index.jsx';
|
||||
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||
import { openExternalRoverWithPrompt } from '../../lib/interInstanceTransfer.js';
|
||||
import { openExternalRover } from '../../lib/interInstanceTransfer.js';
|
||||
import { ExternalInstancesCompact } from '../InterInstancePanel/index.jsx';
|
||||
import { isFeatureEnabled } from '../../lib/features.js';
|
||||
import { useSettingsNamespace } from '../../settings/index.js';
|
||||
|
||||
function classNames(...values) {
|
||||
return values.filter(Boolean).join(' ');
|
||||
@@ -62,6 +63,7 @@ export default function RoverQueuesPanel({
|
||||
const localTurnQueues = useSessionSelector((state) => state.session?.turnQueues ?? {});
|
||||
const localUsers = useSessionSelector((state) => state.session?.users ?? []);
|
||||
const interInstanceEnabled = useSessionSelector((state) => isFeatureEnabled(state, 'interInstance'));
|
||||
const { value: pageSettings } = useSettingsNamespace('page', { interInstanceTransferSettings: true });
|
||||
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
||||
const assignedRoverId = useSessionSelector((state) => String(state.session?.assignment?.roverId || '').trim());
|
||||
const assignedRoverName = useSessionSelector((state) => {
|
||||
@@ -75,6 +77,7 @@ export default function RoverQueuesPanel({
|
||||
const [rebootPending, setRebootPending] = useState(false);
|
||||
const externalMode = Boolean(externalInstance);
|
||||
const externalBlocked = Boolean(externalMode && disabledOverlay);
|
||||
const includeInterInstanceSettings = pageSettings?.interInstanceTransferSettings !== false;
|
||||
const roster = Array.isArray(rosterOverride) ? rosterOverride : localRoster;
|
||||
const turnQueues = turnQueuesOverride && typeof turnQueuesOverride === 'object' ? turnQueuesOverride : localTurnQueues;
|
||||
const users = Array.isArray(usersOverride) ? usersOverride : localUsers;
|
||||
@@ -113,9 +116,10 @@ export default function RoverQueuesPanel({
|
||||
/*
|
||||
External queue cards deliberately reuse the local row layout, but their
|
||||
action cannot go through this Socket.IO server. The row opens the remote
|
||||
instance, optionally carrying settings after the source-page prompt.
|
||||
instance and follows the saved Page setting for cookie/settings transfer
|
||||
instead of interrupting each click with a confirmation popup.
|
||||
*/
|
||||
openExternalRoverWithPrompt(externalInstance, targetRoverId);
|
||||
openExternalRover(externalInstance, targetRoverId, { includeSettings: includeInterInstanceSettings });
|
||||
return;
|
||||
}
|
||||
setPending((prev) => ({ ...prev, [targetRoverId]: true }));
|
||||
|
||||
@@ -91,6 +91,18 @@ function RangeSetting({ label, value, disabled = false, onChange }) {
|
||||
);
|
||||
}
|
||||
|
||||
function reconnectSocketWithTransport(socket, transport) {
|
||||
if (!socket?.io?.opts) return;
|
||||
/*
|
||||
Socket.IO reads its manager options when reconnecting. Keep this mutation in
|
||||
one helper instead of inside the component body so the settings handler only
|
||||
expresses the user-facing action: save preference, then reconnect.
|
||||
*/
|
||||
socket.io.opts.transports = transport === 'polling' ? ['polling'] : ['websocket', 'polling'];
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
export default function SettingsPanel() {
|
||||
const keymap = useControlSelector((control) => control.state.keymap);
|
||||
const roverId = useControlSelector((control) => control.state.roverId);
|
||||
@@ -103,6 +115,7 @@ export default function SettingsPanel() {
|
||||
connectionTransport: 'websocket',
|
||||
swapMobileControlColumns: false,
|
||||
driveMacroBackoffEnabled: true,
|
||||
interInstanceTransferSettings: true,
|
||||
});
|
||||
const { value: audioSettings, save: saveAudioSettings } = useSettingsNamespace('audio', AUDIO_SETTINGS_DEFAULTS);
|
||||
const { value: videoSettings, save: saveVideoSettings } = useSettingsNamespace('video', VIDEO_SETTINGS_DEFAULTS);
|
||||
@@ -112,6 +125,7 @@ export default function SettingsPanel() {
|
||||
typeof pageSettings?.driveMacroBackoffEnabled === 'boolean'
|
||||
? pageSettings.driveMacroBackoffEnabled
|
||||
: true;
|
||||
const interInstanceTransferSettings = pageSettings?.interInstanceTransferSettings !== false;
|
||||
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 roverVolume = Number.isFinite(audioSettings?.roverVolume) ? audioSettings.roverVolume : AUDIO_SETTINGS_DEFAULTS.roverVolume;
|
||||
@@ -144,10 +158,7 @@ export default function SettingsPanel() {
|
||||
const next = event.target.value;
|
||||
savePageSettings((current) => ({ ...(current ?? {}), connectionTransport: next }));
|
||||
trackAnalyticsEvent('settings_change', { setting: 'connection_transport', value: next });
|
||||
if (!socket?.io?.opts) return;
|
||||
socket.io.opts.transports = next === 'polling' ? ['polling'] : ['websocket', 'polling'];
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
reconnectSocketWithTransport(socket, next);
|
||||
};
|
||||
|
||||
const handleAudioRange = (key) => (event) => {
|
||||
@@ -180,6 +191,17 @@ export default function SettingsPanel() {
|
||||
trackAnalyticsEvent('settings_change', { setting: 'driveMacroBackoffEnabled', value: checked });
|
||||
};
|
||||
|
||||
const handleInterInstanceTransferSettings = (event) => {
|
||||
const checked = Boolean(event.target.checked);
|
||||
/*
|
||||
This replaces the old per-click transfer confirmation. Keeping the choice
|
||||
in Page settings makes external-server navigation immediate while still
|
||||
letting users opt out of sending their current settings cookie.
|
||||
*/
|
||||
savePageSettings((current) => ({ ...(current ?? {}), interInstanceTransferSettings: checked }));
|
||||
trackAnalyticsEvent('settings_change', { setting: 'interInstanceTransferSettings', value: checked });
|
||||
};
|
||||
|
||||
const handleVideoFilterChange = (event) => {
|
||||
const nextFilter = normalizeVideoFilter(event.target.value);
|
||||
|
||||
@@ -329,6 +351,20 @@ export default function SettingsPanel() {
|
||||
</SettingRow>
|
||||
<SettingHelp>Switching reconnects your session.</SettingHelp>
|
||||
</CardFrame>
|
||||
<CardFrame title="Inter-instance" bodyClassName="space-y-1 p-1 text-sm">
|
||||
<SettingRow className="grid-cols-[auto_minmax(0,1fr)] max-[420px]:grid-cols-[auto_minmax(0,1fr)]">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-3.5 w-3.5 accent-emerald-500"
|
||||
checked={interInstanceTransferSettings}
|
||||
onChange={handleInterInstanceTransferSettings}
|
||||
/>
|
||||
<span className="font-semibold text-white">Transfer settings when opening external servers</span>
|
||||
</SettingRow>
|
||||
<SettingHelp>
|
||||
Sends this browser's saved identity and page settings to the destination server automatically.
|
||||
</SettingHelp>
|
||||
</CardFrame>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel id="admin">
|
||||
|
||||
Reference in New Issue
Block a user