mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-11-14 10:42:45 -05:00
allowed to leave is now used in the web UI to show and hide request / open buttons and trigger popup when leaving the page
This commit is contained in:
@@ -24,8 +24,34 @@ export default function PageExitGuard() {
|
||||
event.preventDefault();
|
||||
event.returnValue = true;
|
||||
}
|
||||
function blockNavigation(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setWarning({ blocked: true, visible: true });
|
||||
}
|
||||
function handleLinkClick(event) {
|
||||
if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
|
||||
const link = event.target instanceof Element ? event.target.closest('a[href]') : null;
|
||||
if (!link || link.hasAttribute('download')) return;
|
||||
const target = (link.getAttribute('target') || document.querySelector('base[target]')?.getAttribute('target') || '_self').toLowerCase();
|
||||
if (target !== '_self') return;
|
||||
const destination = new URL(link.href, window.location.href);
|
||||
if (destination.protocol !== 'http:' && destination.protocol !== 'https:') return;
|
||||
// Hash links stay in this document and do not abandon the rover.
|
||||
if (destination.origin === window.location.origin
|
||||
&& destination.pathname === window.location.pathname
|
||||
&& destination.search === window.location.search
|
||||
&& link.getAttribute('href').includes('#')) return;
|
||||
blockNavigation(event);
|
||||
}
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
document.addEventListener('click', handleLinkClick, true);
|
||||
window.addEventListener('page:navigate', blockNavigation);
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
document.removeEventListener('click', handleLinkClick, true);
|
||||
window.removeEventListener('page:navigate', blockNavigation);
|
||||
};
|
||||
}, [shouldBlock]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,18 +72,21 @@ export default function PageExitGuard() {
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="page-exit-warning"
|
||||
className="fixed inset-0 z-[2147483647] flex flex-col items-center justify-center gap-10 bg-neutral-950 p-6 text-center text-white"
|
||||
className="fixed inset-0 z-[2147483647] flex flex-col items-center justify-between gap-4 bg-black p-6 text-center text-white"
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation();
|
||||
if (event.key === 'Escape') dismiss();
|
||||
}}
|
||||
>
|
||||
<p id="page-exit-warning" className="text-[clamp(3rem,10vw,10rem)] font-bold leading-tight">
|
||||
Please dock your rover
|
||||
Please dock your rover!
|
||||
</p>
|
||||
<button type="button" autoFocus className="button-dark px-8 py-4 text-3xl" onClick={dismiss}>
|
||||
Okay
|
||||
</button>
|
||||
<p aria-hidden="true" className="text-[clamp(3rem,10vw,10rem)] font-bold leading-tight">
|
||||
Please dock your rover!
|
||||
</p>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function PtzQueueCard() {
|
||||
const featureEnabled = useSessionSelector((state) => isFeatureEnabled(state, 'ptzCamera'));
|
||||
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
|
||||
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
||||
const canLeaveRover = useSessionSelector((state) => state.session?.canLeaveRover !== false);
|
||||
const { setOperatingMode } = useSessionActions();
|
||||
const navigate = useNavigate();
|
||||
const now = useSharedClock(1000, Boolean(ptz?.turn?.deadline));
|
||||
@@ -32,7 +33,7 @@ export default function PtzQueueCard() {
|
||||
if (!featureEnabled) return null;
|
||||
|
||||
const handleRequest = async () => {
|
||||
if (!canUse || pending) return;
|
||||
if (!canUse || !canLeaveRover || pending) return;
|
||||
if (isParticipant) {
|
||||
navigate('/ptz');
|
||||
return;
|
||||
@@ -76,14 +77,14 @@ export default function PtzQueueCard() {
|
||||
}}
|
||||
turn={ptz?.turn}
|
||||
selfId={selfId}
|
||||
canClick={canUse && !pending}
|
||||
canClick={canUse && canLeaveRover && !pending}
|
||||
pending={pending}
|
||||
buttonLabel={actionLabel}
|
||||
batteryLabel={ptz?.turn?.ownsControl ? 'LIVE' : ptz?.turn?.turnsAhead ? `#${ptz.turn.turnsAhead}` : '--'}
|
||||
batteryClassName={ptz?.turn?.ownsControl ? 'text-emerald-300' : ptz?.turn?.turnsAhead ? 'text-sky-300' : 'text-slate-400'}
|
||||
timerLabel={timerLabel}
|
||||
onRequest={handleRequest}
|
||||
showAction={canUse}
|
||||
showAction={canUse && canLeaveRover}
|
||||
/>
|
||||
</ul>
|
||||
{isParticipant ? (
|
||||
|
||||
@@ -101,6 +101,7 @@ export default function RoverQueuesPanel({
|
||||
fillHeight = false,
|
||||
}) {
|
||||
const role = useSessionSelector((state) => state.session?.role || null);
|
||||
const canLeaveRover = useSessionSelector((state) => state.session?.canLeaveRover !== false);
|
||||
const localRoster = useSessionSelector((state) => state.session?.roster ?? []);
|
||||
const interInstanceEnabled = useSessionSelector((state) => isFeatureEnabled(state, 'interInstance'));
|
||||
const { value: pageSettings } = useSettingsNamespace('page', { interInstanceTransferSettings: true });
|
||||
@@ -122,8 +123,8 @@ export default function RoverQueuesPanel({
|
||||
const roster = Array.isArray(rosterOverride) ? rosterOverride : localRoster;
|
||||
|
||||
const canRequest = useMemo(
|
||||
() => (externalMode ? !externalBlocked : role && role !== 'spectator'),
|
||||
[externalBlocked, externalMode, role],
|
||||
() => canLeaveRover && (externalMode ? !externalBlocked : role && role !== 'spectator'),
|
||||
[canLeaveRover, externalBlocked, externalMode, role],
|
||||
);
|
||||
const adminCapable = useMemo(
|
||||
() => role === 'admin' || role === 'lockdown',
|
||||
@@ -142,7 +143,7 @@ export default function RoverQueuesPanel({
|
||||
|
||||
async function handleRequest(targetRoverId) {
|
||||
if (!targetRoverId) return;
|
||||
if (externalBlocked) return;
|
||||
if (!canRequest) return;
|
||||
if (externalMode) {
|
||||
/*
|
||||
External queue cards deliberately reuse the local row layout, but their
|
||||
|
||||
@@ -40,5 +40,8 @@ export function buildExternalRoverUrl(instance, roverId, { includeSettings = fal
|
||||
export function openExternalRover(instance, roverId, { includeSettings = true } = {}) {
|
||||
const targetUrl = buildExternalRoverUrl(instance, roverId, { includeSettings });
|
||||
if (!targetUrl) return;
|
||||
// These queue actions are buttons, so let the page guard veto navigation
|
||||
// before beforeunload would pause painting behind the native browser dialog.
|
||||
if (!window.dispatchEvent(new Event('page:navigate', { cancelable: true }))) return;
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user