Skip to content
Merged
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: 2 additions & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface Window {
electronAPI: {
getSources: (opts: Electron.SourcesOptions) => Promise<ProcessedDesktopSource[]>;
switchToEditor: () => Promise<void>;
switchToHud: () => Promise<void>;
startNewRecording: () => Promise<{ success: boolean; error?: string }>;
openSourceSelector: () => Promise<void>;
selectSource: (source: ProcessedDesktopSource) => Promise<ProcessedDesktopSource | null>;
getSelectedSource: () => Promise<ProcessedDesktopSource | null>;
Expand Down
17 changes: 17 additions & 0 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,24 @@ export function registerIpcHandlers(
getMainWindow: () => BrowserWindow | null,
getSourceSelectorWindow: () => BrowserWindow | null,
onRecordingStateChange?: (recording: boolean, sourceName: string) => void,
switchToHud?: () => void,
) {
ipcMain.handle("switch-to-hud", () => {
if (switchToHud) switchToHud();
});
ipcMain.handle("start-new-recording", async () => {
try {
setCurrentRecordingSessionState(null);
if (switchToHud) {
switchToHud();
}
return { success: true };
} catch (error) {
console.error("Failed to start new recording:", error);
return { success: false, error: String(error) };
}
});

ipcMain.handle("get-sources", async (_, opts) => {
const sources = await desktopCapturer.getSources(opts);
return sources.map((source) => ({
Expand Down
11 changes: 11 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,16 @@ app.whenReady().then(async () => {
// Ensure recordings directory exists
await ensureRecordingsDir();

function switchToHudWrapper() {
if (mainWindow) {
isForceClosing = true;
mainWindow.close();
isForceClosing = false;
mainWindow = null;
}
showMainWindow();
}

registerIpcHandlers(
createEditorWindowWrapper,
createSourceSelectorWindowWrapper,
Expand All @@ -384,6 +394,7 @@ app.whenReady().then(async () => {
showMainWindow();
}
},
switchToHudWrapper,
);
createWindow();
});
6 changes: 6 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
switchToEditor: () => {
return ipcRenderer.invoke("switch-to-editor");
},
switchToHud: () => {
return ipcRenderer.invoke("switch-to-hud");
},
startNewRecording: () => {
return ipcRenderer.invoke("start-new-recording");
},
openSourceSelector: () => {
return ipcRenderer.invoke("open-source-selector");
},
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 56 additions & 1 deletion src/components/video-editor/VideoEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { Span } from "dnd-timeline";
import { FolderOpen, Languages, Save } from "lucide-react";
import { FolderOpen, Languages, Save, Video } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { toast } from "sonner";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useI18n, useScopedT } from "@/contexts/I18nContext";
import { useShortcuts } from "@/contexts/ShortcutsContext";
import { INITIAL_EDITOR_STATE, useEditorHistory } from "@/hooks/useEditorHistory";
Expand Down Expand Up @@ -114,6 +122,7 @@ export default function VideoEditor() {
const [exportProgress, setExportProgress] = useState<ExportProgress | null>(null);
const [exportError, setExportError] = useState<string | null>(null);
const [showExportDialog, setShowExportDialog] = useState(false);
const [showNewRecordingDialog, setShowNewRecordingDialog] = useState(false);
const [exportQuality, setExportQuality] = useState<ExportQuality>("good");
const [exportFormat, setExportFormat] = useState<ExportFormat>("mp4");
const [gifFrameRate, setGifFrameRate] = useState<GifFrameRate>(15);
Expand Down Expand Up @@ -476,6 +485,16 @@ export default function VideoEditor() {
await saveProject(true);
}, [saveProject]);

const handleNewRecordingConfirm = useCallback(async () => {
const result = await window.electronAPI.startNewRecording();
if (result.success) {
setShowNewRecordingDialog(false);
} else {
console.error("Failed to start new recording:", result.error);
setError("Failed to start new recording: " + (result.error || "Unknown error"));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}, []);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const handleLoadProject = useCallback(async () => {
const result = await window.electronAPI.loadProjectFile();

Expand Down Expand Up @@ -1457,6 +1476,34 @@ export default function VideoEditor() {

return (
<div className="flex flex-col h-screen bg-[#09090b] text-slate-200 overflow-hidden selection:bg-[#34B27B]/30">
<Dialog open={showNewRecordingDialog} onOpenChange={setShowNewRecordingDialog}>
<DialogContent
className="sm:max-w-[425px]"
style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
>
<DialogHeader>
<DialogTitle>{t("newRecording.title")}</DialogTitle>
<DialogDescription>{t("newRecording.description")}</DialogDescription>
</DialogHeader>
<DialogFooter>
<button
type="button"
onClick={() => setShowNewRecordingDialog(false)}
className="px-4 py-2 rounded-md bg-white/10 text-white hover:bg-white/20 text-sm font-medium transition-colors"
>
{t("newRecording.cancel")}
</button>
<button
type="button"
onClick={handleNewRecordingConfirm}
className="px-4 py-2 rounded-md bg-[#34B27B] text-white hover:bg-[#34B27B]/90 text-sm font-medium transition-colors"
>
{t("newRecording.confirm")}
</button>
</DialogFooter>
</DialogContent>
</Dialog>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<div
className="h-10 flex-shrink-0 bg-[#09090b]/80 backdrop-blur-md border-b border-white/5 flex items-center justify-between px-6 z-50"
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
Expand All @@ -1482,6 +1529,14 @@ export default function VideoEditor() {
))}
</select>
</div>
<button
type="button"
onClick={() => setShowNewRecordingDialog(true)}
className="flex items-center gap-1 px-2 py-1 rounded-md text-white/50 hover:text-white/90 hover:bg-white/10 transition-all duration-150 text-[11px] font-medium"
>
<Video size={14} />
{t("newRecording.title")}
</button>
<button
type="button"
onClick={handleLoadProject}
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/locales/en/editor.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"newRecording": {
"title": "Return to Recorder",
"description": "Your current session has been saved.",
"cancel": "Cancel",
"confirm": "Confirm"
},
"errors": {
"noVideoLoaded": "No video loaded",
"videoNotReady": "Video not ready",
Expand Down
2 changes: 2 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface Window {
electronAPI: {
getSources: (opts: Electron.SourcesOptions) => Promise<ProcessedDesktopSource[]>;
switchToEditor: () => Promise<void>;
switchToHud: () => Promise<void>;
startNewRecording: () => Promise<{ success: boolean; error?: string }>;
openSourceSelector: () => Promise<void>;
selectSource: (source: ProcessedDesktopSource) => Promise<ProcessedDesktopSource | null>;
getSelectedSource: () => Promise<ProcessedDesktopSource | null>;
Expand Down
Loading