095b9876f9
* first attempt at stream sidebar, tasks, and events * fix folder from root * cleanup folders and events, and condense changes * cleanup and add toggle for sidebar * cleanup * fix nits
142 lines
3.6 KiB
TypeScript
142 lines
3.6 KiB
TypeScript
import { cn } from '@/lib/utils';
|
|
import { PlaybackPageIndicator } from '@/features/particles/playback-page-indicator';
|
|
import { VideoAudioToggle } from '@/components/video-audio-toggle';
|
|
import { KeyHint } from '@/components/key-hint';
|
|
import { useComposeIntentStore } from '@/stores/compose-intent-store';
|
|
import type { HumanPresence } from '@/hooks/use-presence-positions';
|
|
|
|
export function BottomBar({
|
|
visible,
|
|
total,
|
|
current,
|
|
progress,
|
|
onGoTo,
|
|
presenceBySegment,
|
|
onlineHumanIds,
|
|
exitRemainingMs,
|
|
onOpenKeybindings,
|
|
onOpenHuddle,
|
|
onExit,
|
|
}: {
|
|
visible: boolean;
|
|
total: number;
|
|
current: number;
|
|
progress: number;
|
|
onGoTo: (index: number) => void;
|
|
presenceBySegment: Map<number, HumanPresence[]>;
|
|
onlineHumanIds: Set<string>;
|
|
exitRemainingMs: number | null;
|
|
onOpenKeybindings: () => void;
|
|
onOpenHuddle: () => void;
|
|
onExit: () => void;
|
|
}) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'absolute inset-x-0 bottom-0 z-10 transition-all duration-300',
|
|
visible
|
|
? 'opacity-100 translate-y-0'
|
|
: 'opacity-0 translate-y-2 pointer-events-none',
|
|
)}
|
|
>
|
|
{/* Presence avatars — above the blurred background */}
|
|
<PlaybackPageIndicator
|
|
total={total}
|
|
current={current}
|
|
progress={progress}
|
|
onGoTo={onGoTo}
|
|
presenceBySegment={presenceBySegment}
|
|
onlineHumanIds={onlineHumanIds}
|
|
layer="avatars"
|
|
/>
|
|
{/* Blurred background container — tracks + controls */}
|
|
<div className="pb-3">
|
|
<PlaybackPageIndicator
|
|
total={total}
|
|
current={current}
|
|
progress={progress}
|
|
onGoTo={onGoTo}
|
|
layer="tracks"
|
|
/>
|
|
<div className="flex items-center justify-center px-3 pt-2 gap-2">
|
|
{exitRemainingMs !== null && (
|
|
<div className="flex justify-center">
|
|
<span className="rounded-full bg-black/30 px-2 text-xs text-white/70 backdrop-blur-sm">
|
|
Closing in {Math.ceil(exitRemainingMs / 1000)}s
|
|
</span>
|
|
</div>
|
|
)}
|
|
<StreamViewControls
|
|
showEscape
|
|
onOpenKeybindings={onOpenKeybindings}
|
|
onOpenHuddle={onOpenHuddle}
|
|
onExit={onExit}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function StreamViewControls({
|
|
showEscape,
|
|
onOpenKeybindings,
|
|
onOpenHuddle,
|
|
onExit,
|
|
}: {
|
|
showEscape?: boolean;
|
|
onOpenKeybindings: () => void;
|
|
onOpenHuddle: () => void;
|
|
onExit: () => void;
|
|
}) {
|
|
const requestIntent = useComposeIntentStore((s) => s.request);
|
|
return (
|
|
<div className="flex items-center gap-4 text-sm text-white/50">
|
|
{showEscape && (
|
|
<KeyHint
|
|
keys="Esc"
|
|
onClick={onExit}
|
|
title="Back to network (or press Esc)"
|
|
>
|
|
back
|
|
</KeyHint>
|
|
)}
|
|
<VideoAudioToggle />
|
|
<KeyHint
|
|
keys="Hold `"
|
|
onClick={() => requestIntent('record')}
|
|
title="Reply with a recording (or hold `)"
|
|
>
|
|
to reply
|
|
</KeyHint>
|
|
<KeyHint
|
|
keys="T"
|
|
onClick={() => requestIntent('text')}
|
|
title="Reply with text (or press T)"
|
|
>
|
|
text
|
|
</KeyHint>
|
|
<KeyHint
|
|
keys="D"
|
|
onClick={() => requestIntent('task')}
|
|
title="Add a task (or press D)"
|
|
>
|
|
task
|
|
</KeyHint>
|
|
<KeyHint
|
|
keys="H"
|
|
onClick={onOpenHuddle}
|
|
title="Start a huddle (or press H)"
|
|
>
|
|
huddle
|
|
</KeyHint>
|
|
<KeyHint
|
|
keys="?"
|
|
onClick={onOpenKeybindings}
|
|
title="Show all shortcuts"
|
|
aria-label="Show keyboard shortcuts"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|