Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/components/launch/LaunchWindow.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@
color: #4eeeb0;
}



.menuCard {
width: 300px;
max-height: 400px;
Expand Down
266 changes: 133 additions & 133 deletions src/components/launch/LaunchWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
import {
ArrowClockwiseIcon,
CaretUpIcon,
DotsThreeVerticalIcon,
MicrophoneIcon,
MicrophoneSlashIcon,
MinusIcon,
MonitorIcon,
DotsThreeVerticalIcon,
TimerIcon,
VideoCameraIcon,
VideoCameraSlashIcon,
XIcon,
ArrowClockwiseIcon,
} from "@phosphor-icons/react";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useRef } from "react";
import { RxDragHandleDots2 } from "react-icons/rx";
import { Separator } from "@/components/ui/separator";
import { useScopedT } from "../../contexts/I18nContext";
import { useHudBarDrag } from "./hooks/useHudBarDrag";
import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices";
import { useLaunchWindowSystemState } from "./hooks/useLaunchWindowSystemState";
import { useScreenRecorder } from "../../hooks/useScreenRecorder";
import { useVideoDevices } from "../../hooks/useVideoDevices";
import { Button } from "../ui/button";
import { HudInteractionContext } from "./contexts/HudInteractionContext";
import { canToggleFloatingWebcamPreview } from "./floatingWebcamPreview";
import { useHudBarDrag } from "./hooks/useHudBarDrag";
import { useLaunchHudInteractionState } from "./hooks/useLaunchHudInteractionState";
import { useLaunchWindowActions } from "./hooks/useLaunchWindowActions";
import { useLaunchWindowSystemState } from "./hooks/useLaunchWindowSystemState";
import { useRecordingTimer } from "./hooks/useRecordingTimer";
import { useScreenRecorder } from "../../hooks/useScreenRecorder";
import { useVideoDevices } from "../../hooks/useVideoDevices";
import { useWebcamPreviewOverlay } from "./hooks/useWebcamPreviewOverlay";
import {
canToggleFloatingWebcamPreview,
} from "./floatingWebcamPreview";
import { LaunchPopoverCoordinatorProvider, useLaunchPopoverCoordinator } from "./popovers/LaunchPopoverCoordinator";
import styles from "./LaunchWindow.module.css";
import { CountdownPopover } from "./popovers/CountdownPopover";
import {
LaunchPopoverCoordinatorProvider,
useLaunchPopoverCoordinator,
} from "./popovers/LaunchPopoverCoordinator";
import { MicPopover } from "./popovers/MicPopover";
import { MorePopover } from "./popovers/MorePopover";
import { ProjectPopover } from "./popovers/ProjectPopover";
import { SourcePopover } from "./popovers/SourcePopover";
import { WebcamPopover } from "./popovers/WebcamPopover";
import { HudInteractionContext } from "./contexts/HudInteractionContext";
import { MarqueeText } from "./SourceSelector";
import styles from "./LaunchWindow.module.css";

import { Separator } from "@/components/ui/separator";
import { Button } from "../ui/button";
import { RecordingControls } from "./RecordingControls";
import { useEffect, useRef } from "react";
import { MarqueeText } from "./SourceSelector";

const SHOW_DEV_UPDATE_PREVIEW = import.meta.env.DEV;

Expand Down Expand Up @@ -84,7 +84,6 @@ function LaunchWindowContent() {
const hudContentRef = useRef<HTMLDivElement>(null);
const hudBarRef = useRef<HTMLDivElement>(null);


const {
selectedSource,
hasSelectedSource,
Expand Down Expand Up @@ -167,12 +166,13 @@ function LaunchWindowContent() {
recordingWebcamPreviewContainerRef,
});

const { handleHudMouseEnter, handleHudMouseLeave, beginInteractiveHudAction } = useLaunchHudInteractionState({
openId,
isHudDraggingRef,
isWebcamPreviewDraggingRef,
webcamPreviewDragStartRef,
});
const { handleHudMouseEnter, handleHudMouseLeave, beginInteractiveHudAction } =
useLaunchHudInteractionState({
openId,
isHudDraggingRef,
isWebcamPreviewDraggingRef,
webcamPreviewDragStartRef,
});

useEffect(() => {
let mounted = true;
Expand All @@ -196,7 +196,6 @@ function LaunchWindowContent() {
ease: [0.22, 1, 0.36, 1] as const,
};


const recordingControls = (
<RecordingControls
paused={paused}
Expand Down Expand Up @@ -270,7 +269,11 @@ function LaunchWindowContent() {
}
className={microphoneEnabled ? styles.ibActive : ""}
>
{microphoneEnabled ? <MicrophoneIcon size={18} /> : <MicrophoneSlashIcon size={18} />}
{microphoneEnabled ? (
<MicrophoneIcon size={18} />
) : (
<MicrophoneSlashIcon size={18} />
)}
</Button>
}
/>
Expand All @@ -283,9 +286,7 @@ function LaunchWindowContent() {
hudOverlayMousePassthroughSupported,
)}
showFloatingWebcamPreview={showFloatingWebcamPreview}
onToggleFloatingPreview={() =>
setShowFloatingWebcamPreview((current) => !current)
}
onToggleFloatingPreview={() => setShowFloatingWebcamPreview((current) => !current)}
showWebcamControls={showWebcamControls}
setWebcamPreviewNode={setWebcamPreviewNode}
videoDevices={videoDevices}
Expand All @@ -308,7 +309,11 @@ function LaunchWindowContent() {
}
className={webcamEnabled ? styles.ibActive : ""}
>
{webcamEnabled ? <VideoCameraIcon size={18} /> : <VideoCameraSlashIcon size={18} />}
{webcamEnabled ? (
<VideoCameraIcon size={18} />
) : (
<VideoCameraSlashIcon size={18} />
)}
</Button>
}
/>
Expand All @@ -329,7 +334,6 @@ function LaunchWindowContent() {
}
/>


<button
type="button"
className={`${styles.recBtn} ${styles.electronNoDrag}`}
Expand All @@ -339,7 +343,7 @@ function LaunchWindowContent() {
: () => {
beginInteractiveHudAction();
requestOpen("sources");
}
}
}
disabled={countdownActive}
title={t("recording.record")}
Expand Down Expand Up @@ -383,12 +387,7 @@ function LaunchWindowContent() {
}}
appVersion={appVersion}
trigger={
<Button
variant="ghost"
size="icon"
iconSize="lg"
title={t("recording.more")}
>
<Button variant="ghost" size="icon" iconSize="lg" title={t("recording.more")}>
<DotsThreeVerticalIcon size={18} />
</Button>
}
Expand Down Expand Up @@ -429,116 +428,117 @@ function LaunchWindowContent() {
const hudMode = finalizing ? "finalizing" : recording ? "recording" : "idle";

return (
<HudInteractionContext.Provider value={{ onMouseEnter: handleHudMouseEnter, onMouseLeave: handleHudMouseLeave }}>
<HudInteractionContext.Provider
value={{ onMouseEnter: handleHudMouseEnter, onMouseLeave: handleHudMouseLeave }}
>
<div
className="w-full flex justify-center bg-transparent overflow-visible items-end pb-5 pointer-events-none"
style={{ height: "100vh" }}
>
<div
ref={hudContentRef}
className="flex items-center overflow-visible flex-col-reverse pointer-events-none"
>
<div
className="flex flex-col items-center pointer-events-auto p-2"
onMouseEnter={handleHudMouseEnter}
onMouseLeave={handleHudMouseLeave}
ref={hudContentRef}
className="flex items-center overflow-visible flex-col-reverse pointer-events-none"
>
<div
ref={hudBarTransformRef}
style={{
transform: `translate3d(${recordingHudOffset.x}px, ${recordingHudOffset.y}px, 0)`,
}}
className="flex flex-col items-center pointer-events-auto p-2"
onMouseEnter={handleHudMouseEnter}
onMouseLeave={handleHudMouseLeave}
>
<motion.div
ref={hudBarRef}
layout={!showRecordingWebcamPreview && !isHudDragging}
transition={hudStateTransition}
className={`${styles.bar} launch-theme mb-2`}
>
<div
// On Linux (especially Wayland) the compositor owns window
// placement, so BrowserWindow.setBounds() is silently ignored.
// Fall back to a native OS drag via -webkit-app-region on the
// handle. We still need JS pointer handlers in webcam-preview
// mode (which translates via CSS inside the window), so only
// mark the handle as a native drag region for the IPC path.
className={`flex items-center px-0.5 cursor-grab active:cursor-grabbing ${
platform === "linux" && !showRecordingWebcamPreview
? styles.electronDrag
: ""
}`}
onPointerDown={handleHudBarPointerDown}
onPointerMove={handleHudBarPointerMove}
onPointerUp={handleHudBarPointerUp}
onPointerCancel={handleHudBarPointerUp}
>
<RxDragHandleDots2 size={14} className="text-[#6b6b78]" />
</div>

<div className={styles.barStateViewport}>
<AnimatePresence initial={false} mode="wait">
<motion.div
key={hudMode}
layout={!showRecordingWebcamPreview && !isHudDragging}
className={styles.barState}
initial={{
opacity: 0,
y: 10,
scale: 0.985,
filter: "blur(8px)",
}}
animate={{
opacity: 1,
y: 0,
scale: 1,
filter: "blur(0px)",
}}
exit={{
opacity: 0,
y: -10,
scale: 0.985,
filter: "blur(6px)",
}}
transition={hudStateTransition}
>
{finalizing
? finalizingControls
: recording
? recordingControls
: idleControls}
</motion.div>
</AnimatePresence>
</div>
</motion.div>
</div>
{showRecordingWebcamPreview && (
<div
ref={recordingWebcamPreviewContainerRef}
className={`${styles.recordingWebcamPreview} ${styles.electronNoDrag} pointer-events-auto`}
title={t("recording.webcam")}
ref={hudBarTransformRef}
style={{
transform: `translate(${webcamPreviewOffset.x}px, ${webcamPreviewOffset.y}px)`,
transform: `translate3d(${recordingHudOffset.x}px, ${recordingHudOffset.y}px, 0)`,
}}
onMouseEnter={handleHudMouseEnter}
onMouseLeave={handleHudMouseLeave}
onPointerDown={handleWebcamPreviewPointerDown}
onPointerMove={handleWebcamPreviewPointerMove}
onPointerUp={handleWebcamPreviewPointerUp}
onPointerCancel={handleWebcamPreviewPointerUp}
>
<video
ref={setRecordingWebcamPreviewNode}
className={styles.recordingWebcamPreviewVideo}
muted
playsInline
style={{ transform: "scaleX(-1)" }}
/>
<motion.div
ref={hudBarRef}
layout={!showRecordingWebcamPreview && !isHudDragging}
transition={hudStateTransition}
className={`${styles.bar} launch-theme mb-2`}
>
<div
// On Linux (especially Wayland) the compositor owns window
// placement, so BrowserWindow.setBounds() is silently ignored.
// Fall back to a native OS drag via -webkit-app-region on the
// handle. We still need JS pointer handlers in webcam-preview
// mode (which translates via CSS inside the window), so only
// mark the handle as a native drag region for the IPC path.
className={`flex items-center px-0.5 cursor-grab active:cursor-grabbing ${
platform === "linux" && !showRecordingWebcamPreview
? styles.electronDrag
: ""
}`}
onPointerDown={handleHudBarPointerDown}
onPointerMove={handleHudBarPointerMove}
onPointerUp={handleHudBarPointerUp}
onPointerCancel={handleHudBarPointerUp}
>
<RxDragHandleDots2 size={14} className="text-[#6b6b78]" />
</div>

<div className={styles.barStateViewport}>
<AnimatePresence initial={false} mode="wait">
<motion.div
key={hudMode}
layout={!showRecordingWebcamPreview && !isHudDragging}
className={styles.barState}
initial={{
opacity: 0,
y: 10,
scale: 0.985,
filter: "blur(8px)",
}}
animate={{
opacity: 1,
y: 0,
scale: 1,
filter: "blur(0px)",
}}
exit={{
opacity: 0,
y: -10,
scale: 0.985,
filter: "blur(6px)",
}}
transition={hudStateTransition}
>
{finalizing
? finalizingControls
: recording
? recordingControls
: idleControls}
</motion.div>
</AnimatePresence>
</div>
</motion.div>
</div>
)}
{showRecordingWebcamPreview && (
<div
ref={recordingWebcamPreviewContainerRef}
className={`${styles.recordingWebcamPreview} ${styles.electronNoDrag} pointer-events-auto`}
title={t("recording.webcam")}
style={{
transform: `translate(${webcamPreviewOffset.x}px, ${webcamPreviewOffset.y}px)`,
}}
onMouseEnter={handleHudMouseEnter}
onMouseLeave={handleHudMouseLeave}
onPointerDown={handleWebcamPreviewPointerDown}
onPointerMove={handleWebcamPreviewPointerMove}
onPointerUp={handleWebcamPreviewPointerUp}
onPointerCancel={handleWebcamPreviewPointerUp}
>
<video
ref={setRecordingWebcamPreviewNode}
className={styles.recordingWebcamPreviewVideo}
muted
playsInline
style={{ transform: "scaleX(-1)" }}
/>
</div>
)}
</div>
</div>

</div>
</div>
</HudInteractionContext.Provider>
);
}
Loading