mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
popupaboveinsteadofcovering heheeee
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
|||||||
<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="Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DZt_FmKq.js"></script>
|
<script type="module" crossorigin src="/assets/index-BJ6v-SvH.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BJwNeHyz.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Cjf8GsR5.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export default function ReplayReadyPopup({ replay, onClose, variant = 'modal' })
|
|||||||
if (!videoUrl) return null;
|
if (!videoUrl) return null;
|
||||||
|
|
||||||
const isPanel = variant === 'panel';
|
const isPanel = variant === 'panel';
|
||||||
|
const isFloatingPanel = variant === 'floating-panel';
|
||||||
const title = String(replay?.title || 'Replay').trim() || 'Replay';
|
const title = String(replay?.title || 'Replay').trim() || 'Replay';
|
||||||
const messageUrl = normalizeUrl(replay?.messageUrl);
|
const messageUrl = normalizeUrl(replay?.messageUrl);
|
||||||
const meta = formatBytes(replay?.size) || null;
|
const meta = formatBytes(replay?.size) || null;
|
||||||
@@ -39,6 +40,20 @@ export default function ReplayReadyPopup({ replay, onClose, variant = 'modal' })
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isFloatingPanel) {
|
||||||
|
return (
|
||||||
|
<CardFrame
|
||||||
|
title={title}
|
||||||
|
meta={meta}
|
||||||
|
actions={actions}
|
||||||
|
clipOverflow={false}
|
||||||
|
bodyClassName="hidden"
|
||||||
|
>
|
||||||
|
{null}
|
||||||
|
</CardFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const card = (
|
const card = (
|
||||||
<CardFrame
|
<CardFrame
|
||||||
title={title}
|
title={title}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ function normalizeSources(list = []) {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PANEL_REPLAY_AUTO_CLOSE_MS = 35000;
|
||||||
|
|
||||||
export default function ReplaySourcesPanel({ panelId = 'replay-sources', fillHeight = false }) {
|
export default function ReplaySourcesPanel({ panelId = 'replay-sources', fillHeight = false }) {
|
||||||
const replaySources = useSessionSelector((state) => state.session?.replaySources ?? []);
|
const replaySources = useSessionSelector((state) => state.session?.replaySources ?? []);
|
||||||
const mode = useSessionSelector((state) => state.session?.mode || null);
|
const mode = useSessionSelector((state) => state.session?.mode || null);
|
||||||
@@ -191,69 +193,80 @@ export default function ReplaySourcesPanel({ panelId = 'replay-sources', fillHei
|
|||||||
|
|
||||||
const listWrapClass = fillHeight ? 'flex-1 min-h-0 overflow-y-auto' : '';
|
const listWrapClass = fillHeight ? 'flex-1 min-h-0 overflow-y-auto' : '';
|
||||||
|
|
||||||
if (showPanelReplay) {
|
useEffect(() => {
|
||||||
return (
|
if (!showPanelReplay || !latestReplayJobId) return undefined;
|
||||||
<ReplayReadyPopup
|
// Non-requester replay previews are intentionally lightweight. They appear automatically
|
||||||
replay={latestReplay}
|
// so web-only users can catch the replay, then clear themselves before blocking the source controls.
|
||||||
variant="panel"
|
const timeoutId = setTimeout(() => {
|
||||||
onClose={() => setDismissedPanelReplayId(latestReplayJobId)}
|
setDismissedPanelReplayId(latestReplayJobId);
|
||||||
/>
|
}, PANEL_REPLAY_AUTO_CLOSE_MS);
|
||||||
);
|
return () => clearTimeout(timeoutId);
|
||||||
}
|
}, [showPanelReplay, latestReplayJobId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardFrame title="Replay Sources" fillHeight={fillHeight} bodyClassName="space-y-0.5 text-sm">
|
<div className={`relative ${fillHeight ? 'flex h-full min-h-0 flex-col' : ''}`}>
|
||||||
<div className={`grid gap-0.5 md:grid-cols-2 ${listWrapClass}`}>
|
{showPanelReplay ? (
|
||||||
<GroupList title="Rovers" items={grouped.rovers} selected={selected} onToggle={toggleKey} />
|
<div className="absolute bottom-[calc(100%+0.125rem)] left-0 z-[70] w-full max-w-full">
|
||||||
<GroupList title="Room Cams" items={grouped.rooms} selected={selected} onToggle={toggleKey} />
|
<ReplayReadyPopup
|
||||||
</div>
|
replay={latestReplay}
|
||||||
<div className="space-y-0.5">
|
variant="floating-panel"
|
||||||
<label className="panel-muted block text-xs" htmlFor={`${panelId}-title`}>
|
onClose={() => setDismissedPanelReplayId(latestReplayJobId)}
|
||||||
Replay title
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id={`${panelId}-title`}
|
|
||||||
type="text"
|
|
||||||
className="field-input w-full text-xs"
|
|
||||||
value={title}
|
|
||||||
onChange={(event) => {
|
|
||||||
const next = event.target.value;
|
|
||||||
setTitle(next);
|
|
||||||
setTitleDirty(true);
|
|
||||||
saveSettings((current) => ({ ...(current || {}), [`${panelId}:title`]: next }));
|
|
||||||
}}
|
|
||||||
placeholder={defaultTitle}
|
|
||||||
maxLength={120}
|
|
||||||
/>
|
|
||||||
<label className="surface flex items-center gap-0.5 text-xs">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={includeSidebar}
|
|
||||||
onChange={(event) => {
|
|
||||||
const next = Boolean(event.target.checked);
|
|
||||||
setIncludeSidebar(next);
|
|
||||||
saveSettings((current) => ({ ...(current || {}), [`${panelId}:includeSidebar`]: next }));
|
|
||||||
}}
|
|
||||||
className="accent-emerald-400"
|
|
||||||
/>
|
/>
|
||||||
<span>Include replay sidebar</span>
|
</div>
|
||||||
</label>
|
) : null}
|
||||||
<button
|
<CardFrame title="Replay Sources" fillHeight={fillHeight} bodyClassName="space-y-0.5 text-sm">
|
||||||
type="button"
|
<div className={`grid gap-0.5 md:grid-cols-2 ${listWrapClass}`}>
|
||||||
className="button-dark w-full text-xs disabled:opacity-40"
|
<GroupList title="Rovers" items={grouped.rovers} selected={selected} onToggle={toggleKey} />
|
||||||
onClick={handleReplay}
|
<GroupList title="Room Cams" items={grouped.rooms} selected={selected} onToggle={toggleKey} />
|
||||||
disabled={replayDisabled}
|
</div>
|
||||||
>
|
<div className="space-y-0.5">
|
||||||
{remainingMs > 0 ? `Replay (${Math.ceil(remainingMs / 1000)}s)` : busy ? 'Replay…' : 'Replay'}
|
<label className="panel-muted block text-xs" htmlFor={`${panelId}-title`}>
|
||||||
</button>
|
Replay title
|
||||||
{error ? <div className="text-xs text-amber-400">{error}</div> : null}
|
</label>
|
||||||
{activeJobStatusText ? (
|
<input
|
||||||
<div className={`text-xs ${activeReplayJob?.status === 'failed' ? 'text-amber-400' : 'text-emerald-300'}`}>
|
id={`${panelId}-title`}
|
||||||
{activeJobStatusText}
|
type="text"
|
||||||
</div>
|
className="field-input w-full text-xs"
|
||||||
) : success ? <div className="text-xs text-emerald-300">{success}</div> : null}
|
value={title}
|
||||||
</div>
|
onChange={(event) => {
|
||||||
</CardFrame>
|
const next = event.target.value;
|
||||||
|
setTitle(next);
|
||||||
|
setTitleDirty(true);
|
||||||
|
saveSettings((current) => ({ ...(current || {}), [`${panelId}:title`]: next }));
|
||||||
|
}}
|
||||||
|
placeholder={defaultTitle}
|
||||||
|
maxLength={120}
|
||||||
|
/>
|
||||||
|
<label className="surface flex items-center gap-0.5 text-xs">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={includeSidebar}
|
||||||
|
onChange={(event) => {
|
||||||
|
const next = Boolean(event.target.checked);
|
||||||
|
setIncludeSidebar(next);
|
||||||
|
saveSettings((current) => ({ ...(current || {}), [`${panelId}:includeSidebar`]: next }));
|
||||||
|
}}
|
||||||
|
className="accent-emerald-400"
|
||||||
|
/>
|
||||||
|
<span>Include replay sidebar</span>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button-dark w-full text-xs disabled:opacity-40"
|
||||||
|
onClick={handleReplay}
|
||||||
|
disabled={replayDisabled}
|
||||||
|
>
|
||||||
|
{remainingMs > 0 ? `Replay (${Math.ceil(remainingMs / 1000)}s)` : busy ? 'Replay…' : 'Replay'}
|
||||||
|
</button>
|
||||||
|
{error ? <div className="text-xs text-amber-400">{error}</div> : null}
|
||||||
|
{activeJobStatusText ? (
|
||||||
|
<div className={`text-xs ${activeReplayJob?.status === 'failed' ? 'text-amber-400' : 'text-emerald-300'}`}>
|
||||||
|
{activeJobStatusText}
|
||||||
|
</div>
|
||||||
|
) : success ? <div className="text-xs text-emerald-300">{success}</div> : null}
|
||||||
|
</div>
|
||||||
|
</CardFrame>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user