import { Check, ChevronDown, Languages } from "lucide-react";
import { useEffect, useState } from "react";
import { BsPauseCircle, BsPlayCircle, BsRecordCircle } from "react-icons/bs";
import { FaRegStopCircle } from "react-icons/fa";
import { FaFolderOpen } from "react-icons/fa6";
import { FiMinus, FiX } from "react-icons/fi";
import {
MdCancel,
MdMic,
MdMicOff,
MdMonitor,
MdRestartAlt,
MdVideocam,
MdVideocamOff,
MdVideoFile,
MdVolumeOff,
MdVolumeUp,
} from "react-icons/md";
import { RxDragHandleDots2 } from "react-icons/rx";
import { useI18n, useScopedT } from "@/contexts/I18nContext";
import { SUPPORTED_LOCALES } from "@/i18n/config";
import { getLocaleName } from "@/i18n/loader";
import { useAudioLevelMeter } from "../../hooks/useAudioLevelMeter";
import { useCameraDevices } from "../../hooks/useCameraDevices";
import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices";
import { useScreenRecorder } from "../../hooks/useScreenRecorder";
import { requestCameraAccess } from "../../lib/requestCameraAccess";
import { formatTimePadded } from "../../utils/timeUtils";
import { AudioLevelMeter } from "../ui/audio-level-meter";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { Tooltip } from "../ui/tooltip";
import styles from "./LaunchWindow.module.css";
const ICON_SIZE = 20;
const ICON_CONFIG = {
drag: { icon: RxDragHandleDots2, size: ICON_SIZE },
monitor: { icon: MdMonitor, size: ICON_SIZE },
volumeOn: { icon: MdVolumeUp, size: ICON_SIZE },
volumeOff: { icon: MdVolumeOff, size: ICON_SIZE },
micOn: { icon: MdMic, size: ICON_SIZE },
micOff: { icon: MdMicOff, size: ICON_SIZE },
webcamOn: { icon: MdVideocam, size: ICON_SIZE },
webcamOff: { icon: MdVideocamOff, size: ICON_SIZE },
pause: { icon: BsPauseCircle, size: ICON_SIZE },
resume: { icon: BsPlayCircle, size: ICON_SIZE },
stop: { icon: FaRegStopCircle, size: ICON_SIZE },
restart: { icon: MdRestartAlt, size: ICON_SIZE },
cancel: { icon: MdCancel, size: ICON_SIZE },
record: { icon: BsRecordCircle, size: ICON_SIZE },
videoFile: { icon: MdVideoFile, size: ICON_SIZE },
folder: { icon: FaFolderOpen, size: ICON_SIZE },
minimize: { icon: FiMinus, size: ICON_SIZE },
close: { icon: FiX, size: ICON_SIZE },
} as const;
type IconName = keyof typeof ICON_CONFIG;
function getIcon(name: IconName, className?: string) {
const { icon: Icon, size } = ICON_CONFIG[name];
return ;
}
const hudGroupClasses =
"flex items-center gap-0.5 bg-white/5 rounded-full transition-colors duration-150 hover:bg-white/[0.08]";
const hudIconBtnClasses =
"flex items-center justify-center p-2 rounded-full transition-all duration-150 cursor-pointer text-white hover:bg-white/10 hover:scale-[1.08] active:scale-95";
const hudAuxIconBtnClasses =
"flex items-center justify-center p-1.5 rounded-full transition-colors duration-150 text-white/55 hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed";
const windowBtnClasses =
"flex items-center justify-center p-2 rounded-full transition-all duration-150 cursor-pointer opacity-50 hover:opacity-90 hover:bg-white/[0.08]";
const hudSidebarClasses = "ml-0.5 pl-1.5 border-l border-white/10 flex items-center gap-0.5";
export function LaunchWindow() {
const t = useScopedT("launch");
const {
locale,
setLocale,
systemLocaleSuggestion,
acceptSystemLocaleSuggestion,
dismissSystemLocaleSuggestion,
resolveSystemLocaleSuggestion,
} = useI18n();
const suggestedLanguageName = systemLocaleSuggestion ? getLocaleName(systemLocaleSuggestion) : "";
const {
recording,
paused,
elapsedSeconds,
toggleRecording,
togglePaused,
restartRecording,
cancelRecording,
microphoneEnabled,
setMicrophoneEnabled,
microphoneDeviceId,
setMicrophoneDeviceId,
systemAudioEnabled,
setSystemAudioEnabled,
webcamEnabled,
setWebcamEnabled,
webcamDeviceId,
setWebcamDeviceId,
} = useScreenRecorder();
const showMicControls = microphoneEnabled && !recording;
const showWebcamControls = webcamEnabled && !recording;
const [isMicHovered, setIsMicHovered] = useState(false);
const [isMicFocused, setIsMicFocused] = useState(false);
const micExpanded = isMicHovered || isMicFocused;
const [isWebcamHovered, setIsWebcamHovered] = useState(false);
const [isWebcamFocused, setIsWebcamFocused] = useState(false);
const webcamExpanded = isWebcamHovered || isWebcamFocused;
const {
devices: micDevices,
selectedDeviceId: selectedMicId,
setSelectedDeviceId: setSelectedMicId,
} = useMicrophoneDevices(microphoneEnabled);
const {
devices: cameraDevices,
selectedDeviceId: selectedCameraId,
setSelectedDeviceId: setSelectedCameraId,
isLoading: isCameraDevicesLoading,
error: cameraDevicesError,
} = useCameraDevices(webcamEnabled);
const selectedMicLabel =
micDevices.find((d) => d.deviceId === (microphoneDeviceId || selectedMicId))?.label ||
t("audio.defaultMicrophone");
const selectedCameraLabel = isCameraDevicesLoading
? t("webcam.searching")
: cameraDevicesError
? t("webcam.unavailable")
: cameraDevices.length === 0
? t("webcam.noneFound")
: cameraDevices.find((d) => d.deviceId === (webcamDeviceId || selectedCameraId))?.label ||
t("webcam.defaultCamera");
const { level } = useAudioLevelMeter({
enabled: showMicControls,
deviceId: microphoneDeviceId,
});
useEffect(() => {
if (selectedMicId && selectedMicId !== "default") {
setMicrophoneDeviceId(selectedMicId);
}
}, [selectedMicId, setMicrophoneDeviceId]);
useEffect(() => {
if (selectedCameraId) {
setWebcamDeviceId(selectedCameraId);
}
}, [selectedCameraId, setWebcamDeviceId]);
useEffect(() => {
if (!import.meta.env.DEV) {
return;
}
void requestCameraAccess().catch((error) => {
console.warn("Failed to trigger camera access request during development:", error);
});
}, []);
const [selectedSource, setSelectedSource] = useState("Screen");
const [hasSelectedSource, setHasSelectedSource] = useState(false);
useEffect(() => {
const checkSelectedSource = async () => {
if (window.electronAPI) {
const source = await window.electronAPI.getSelectedSource();
if (source) {
setSelectedSource(source.name);
setHasSelectedSource(true);
} else {
setSelectedSource("Screen");
setHasSelectedSource(false);
}
}
};
checkSelectedSource();
const interval = setInterval(checkSelectedSource, 500);
return () => clearInterval(interval);
}, []);
const openSourceSelector = () => {
if (window.electronAPI) {
window.electronAPI.openSourceSelector();
}
};
const openVideoFile = async () => {
const result = await window.electronAPI.openVideoFilePicker();
if (result.canceled) {
return;
}
if (result.success && result.path) {
await window.electronAPI.setCurrentVideoPath(result.path);
await window.electronAPI.switchToEditor();
}
};
const openProjectFile = async () => {
const result = await window.electronAPI.loadProjectFile();
if (result.canceled || !result.success) return;
await window.electronAPI.switchToEditor();
};
const sendHudOverlayHide = () => {
if (window.electronAPI && window.electronAPI.hudOverlayHide) {
window.electronAPI.hudOverlayHide();
}
};
const sendHudOverlayClose = () => {
if (window.electronAPI && window.electronAPI.hudOverlayClose) {
window.electronAPI.hudOverlayClose();
}
};
const toggleMicrophone = () => {
if (!recording) {
setMicrophoneEnabled(!microphoneEnabled);
}
};
return (
{systemLocaleSuggestion && (
{t("systemLanguagePrompt.title")}
{t("systemLanguagePrompt.description", {
language: suggestedLanguageName,
})}
)}
{/* Device selectors — fixed above HUD bar, viewport-relative, never clipped */}
{(showMicControls || showWebcamControls) && (
{/* Mic selector */}
{showMicControls && (
setIsMicHovered(true)}
onMouseLeave={() => setIsMicHovered(false)}
onFocus={() => setIsMicFocused(true)}
onBlur={() => setIsMicFocused(false)}
style={{ width: micExpanded ? "240px" : "140px", transition: "width 300ms ease" }}
>
{!micExpanded && (
{selectedMicLabel}
)}
{micExpanded && (
)}
)}
{/* Webcam selector */}
{showWebcamControls && (
setIsWebcamHovered(true)}
onMouseLeave={() => setIsWebcamHovered(false)}
onFocus={() => setIsWebcamFocused(true)}
onBlur={() => setIsWebcamFocused(false)}
style={{ width: webcamExpanded ? "240px" : "140px", transition: "width 300ms ease" }}
>
{!webcamExpanded && (
{selectedCameraLabel}
)}
{webcamExpanded &&
(isCameraDevicesLoading ? (
{t("webcam.searching")}
) : cameraDevicesError ? (
{t("webcam.unavailable")}
) : cameraDevices.length === 0 ? (
{t("webcam.noneFound")}
) : (
<>
>
))}
{(!webcamExpanded || cameraDevices.length === 0) && (
)}
)}
)}
{/* HUD bar — fixed at bottom center, viewport-relative, never moves */}
{/* Drag handle */}
{getIcon("drag", "text-white/30")}
{/* Source selector */}
{/* Audio controls group */}
{/* Record/Stop group */}
{recording && (
)}
{!recording && (
<>
{/* Open video file */}
{/* Open project */}
>
)}
{/* Right sidebar controls */}
{SUPPORTED_LOCALES.map((loc) => (
{
setLocale(loc);
resolveSystemLocaleSuggestion();
}}
className={`flex items-center justify-between rounded-sm px-2 py-1.5 text-[11px] transition-colors ${loc === locale ? "text-white" : "text-white/90"} focus:bg-white/10 focus:text-white ${styles.electronNoDrag}`}
>
{getLocaleName(loc)}
{loc === locale ? : null}
))}
{/* Window controls */}
);
}