import { useState, useRef, useCallback, useEffect } from 'react';
import {
ChevronLeft,
ChevronRight,
Play,
Pause,
PencilLine,
LayoutList,
MessageSquare,
Volume1,
Volume2,
VolumeX,
Repeat,
Maximize2,
Minimize2,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { useStageStore } from '@/lib/store';
import { useI18n } from '@/lib/hooks/use-i18n';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
export interface CanvasToolbarProps {
readonly currentSceneIndex: number;
readonly scenesCount: number;
readonly engineState: 'idle' | 'playing' | 'paused';
readonly isLiveSession?: boolean;
readonly whiteboardOpen: boolean;
readonly sidebarCollapsed?: boolean;
readonly chatCollapsed?: boolean;
readonly onToggleSidebar?: () => void;
readonly onToggleChat?: () => void;
readonly onPrevSlide: () => void;
readonly onNextSlide: () => void;
readonly onPlayPause: () => void;
readonly onWhiteboardClose: () => void;
readonly showStopDiscussion?: boolean;
readonly onStopDiscussion?: () => void;
readonly isPresenting?: boolean;
readonly onTogglePresentation?: () => void;
readonly className?: string;
// Audio/playback controls
readonly ttsEnabled?: boolean;
readonly ttsMuted?: boolean;
readonly ttsVolume?: number;
readonly onToggleMute?: () => void;
readonly onVolumeChange?: (volume: number) => void;
readonly autoPlayLecture?: boolean;
readonly onToggleAutoPlay?: () => void;
readonly playbackSpeed?: number;
readonly onCycleSpeed?: () => void;
}
/* Compact control button */
const ctrlBtn = cn(
'relative w-7 h-7 rounded-md flex items-center justify-center',
'transition-all duration-150 outline-none cursor-pointer',
'hover:bg-gray-500/[0.08] dark:hover:bg-gray-400/[0.08] active:scale-90',
);
/* Subtle separator */
function CtrlDivider() {
return
;
}
/* Volume icon based on level */
function VolumeIcon({
muted,
volume,
disabled,
}: {
muted: boolean;
volume: number;
disabled: boolean;
}) {
const cls = 'w-3.5 h-3.5';
if (disabled || muted || volume === 0) return ;
if (volume < 0.5) return ;
return ;
}
export function CanvasToolbar({
currentSceneIndex,
scenesCount,
engineState,
isLiveSession,
whiteboardOpen,
sidebarCollapsed,
chatCollapsed,
onToggleSidebar,
onToggleChat,
onPrevSlide,
onNextSlide,
onPlayPause,
onWhiteboardClose,
showStopDiscussion,
onStopDiscussion,
isPresenting,
onTogglePresentation,
className,
ttsEnabled,
ttsMuted,
ttsVolume = 1,
onToggleMute,
onVolumeChange,
autoPlayLecture,
onToggleAutoPlay,
playbackSpeed = 1,
onCycleSpeed,
}: CanvasToolbarProps) {
const { t } = useI18n();
const canGoPrev = currentSceneIndex > 0;
const canGoNext = currentSceneIndex < scenesCount - 1;
const showPlayPause = !isLiveSession;
const whiteboardElementCount = useStageStore(
(s) => s.stage?.whiteboard?.[0]?.elements?.length || 0,
);
// Volume slider hover state
const [volumeHover, setVolumeHover] = useState(false);
const volumeTimerRef = useRef>(undefined);
const volumeContainerRef = useRef(null);
const handleVolumeEnter = useCallback(() => {
clearTimeout(volumeTimerRef.current);
setVolumeHover(true);
}, []);
const handleVolumeLeave = useCallback(() => {
volumeTimerRef.current = setTimeout(() => setVolumeHover(false), 300);
}, []);
// Cleanup volume hover timer on unmount
useEffect(() => () => clearTimeout(volumeTimerRef.current), []);
// Effective volume for display
const effectiveVolume = ttsMuted ? 0 : ttsVolume;
const presentationLabel = isPresenting ? t('stage.exitFullscreen') : t('stage.fullscreen');
return (
{/* ── Left: sidebar toggle + page indicator ── */}
{onToggleSidebar && (
)}
{currentSceneIndex + 1}
/
{scenesCount}
{/* ── Center: unified playback controls ── */}
{/* Volume with vertical popover slider */}
{onToggleMute && (
{/* Vertical volume slider (pops up above) */}
{Math.round(effectiveVolume * 100)}
{
const v = parseFloat(e.target.value);
onVolumeChange?.(v);
if (v > 0 && ttsMuted) onToggleMute?.();
}}
className={cn(
'appearance-none cursor-pointer',
'h-16 w-1 rounded-full',
'bg-gray-200 dark:bg-gray-600',
'[writing-mode:vertical-lr] [direction:rtl]',
'[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3',
'[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-violet-500 [&::-webkit-slider-thumb]:dark:bg-violet-400',
'[&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:cursor-pointer',
'[&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3',
'[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-violet-500 [&::-moz-range-thumb]:border-0',
)}
/>
{/* Arrow pointing down */}
)}
{/* Speed */}
{onCycleSpeed && (
{t('roundtable.speed')}
)}
{/* Prev scene */}
{scenesCount > 1 && (
)}
{/* Play / Pause / Stop Discussion */}
{showStopDiscussion && onStopDiscussion ? (
) : showPlayPause ? (
) : null}
{/* Next scene */}
{scenesCount > 1 && (
)}
{/* Auto-play */}
{onToggleAutoPlay && (
{autoPlayLecture ? t('roundtable.autoPlayOff') : t('roundtable.autoPlay')}
)}
{/* Whiteboard */}
{/* ── Right: fullscreen + chat toggle ── */}
{onTogglePresentation && (
)}
{onToggleChat && (
)}
);
}