From 5dc076cac42af3dbf81a2ce610f410817a2c199b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:33:11 +0100 Subject: [PATCH] feat(desktop): stop recording overlay and stable in-progress bounds --- .../routes/(window-chrome)/new-main/index.tsx | 36 ++++++++++++++ .../src/routes/in-progress-recording.tsx | 48 ++++++++++++++----- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx b/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx index 03d7cc274d..3543b1960e 100644 --- a/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx +++ b/apps/desktop/src/routes/(window-chrome)/new-main/index.tsx @@ -979,6 +979,8 @@ function Page() { const { rawOptions, setOptions } = useRecordingOptions(); const currentRecording = createCurrentRecordingQuery(); const isRecording = () => !!currentRecording.data; + const isActivelyRecording = () => + currentRecording.data?.status === "recording"; const auth = authStore.createQuery(); const [hasHiddenMainWindowForPicker, setHasHiddenMainWindowForPicker] = @@ -1508,6 +1510,20 @@ function Page() { const license = createLicenseQuery(); const signIn = createSignInMutation(); + const stopRecording = createMutation(() => ({ + mutationFn: async () => { + try { + await commands.stopRecording(); + } catch (error) { + await dialog.message( + `Failed to stop recording: ${ + error instanceof Error ? error.message : String(error) + }`, + { title: "Stop Recording", kind: "error" }, + ); + } + }, + })); const BaseControls = () => (
@@ -1994,6 +2010,26 @@ function Page() {
+ +
+
+ +
+
+
); diff --git a/apps/desktop/src/routes/in-progress-recording.tsx b/apps/desktop/src/routes/in-progress-recording.tsx index f9b9df3e2f..d3718916c2 100644 --- a/apps/desktop/src/routes/in-progress-recording.tsx +++ b/apps/desktop/src/routes/in-progress-recording.tsx @@ -1,4 +1,3 @@ -import { createElementBounds } from "@solid-primitives/bounds"; import { createTimer } from "@solid-primitives/timer"; import { createMutation } from "@tanstack/solid-query"; import { LogicalPosition } from "@tauri-apps/api/dpi"; @@ -111,8 +110,8 @@ function InProgressRecordingInner() { const [startingDismissed, setStartingDismissed] = createSignal(false); const [interactiveAreaRef, setInteractiveAreaRef] = createSignal(null); - const interactiveBounds = createElementBounds(interactiveAreaRef); let settingsButtonRef: HTMLButtonElement | undefined; + let lastInteractiveBoundsKey = ""; const recordingMode = createMemo( () => currentRecording.data?.mode ?? optionsQuery.rawOptions.mode, ); @@ -292,30 +291,55 @@ function InProgressRecordingInner() { void refreshCameraWindowState(); }); - createEffect(() => { + const syncInteractiveAreaBounds = () => { const element = interactiveAreaRef(); if (!element) { - void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME); + if (lastInteractiveBoundsKey !== "") { + lastInteractiveBoundsKey = ""; + void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME); + } return; } - const left = interactiveBounds.left ?? 0; - const top = interactiveBounds.top ?? 0; - const width = interactiveBounds.width ?? 0; - const height = interactiveBounds.height ?? 0; + const rect = element.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; - if (width === 0 || height === 0) return; + const key = [rect.left, rect.top, rect.width, rect.height] + .map((value) => value.toFixed(2)) + .join(":"); + if (key === lastInteractiveBoundsKey) return; + lastInteractiveBoundsKey = key; void commands.setFakeWindowBounds(FAKE_WINDOW_BOUNDS_NAME, { - position: { x: left, y: top }, - size: { width, height }, + position: { x: rect.left, y: rect.top }, + size: { width: rect.width, height: rect.height }, }); + }; + + createEffect(() => { + interactiveAreaRef(); + queueMicrotask(syncInteractiveAreaBounds); + }); + + createEffect(() => { + state(); + issuePanelVisible(); + queueMicrotask(syncInteractiveAreaBounds); }); onCleanup(() => { + lastInteractiveBoundsKey = ""; void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME); }); + onMount(() => { + const onResize = () => syncInteractiveAreaBounds(); + window.addEventListener("resize", onResize); + onCleanup(() => window.removeEventListener("resize", onResize)); + requestAnimationFrame(() => syncInteractiveAreaBounds()); + setTimeout(() => syncInteractiveAreaBounds(), 150); + }); + createTimer( () => { void refreshCameraWindowState(); @@ -324,6 +348,8 @@ function InProgressRecordingInner() { setInterval, ); + createTimer(syncInteractiveAreaBounds, 250, setInterval); + createEffect(() => { if ( state().variant === "stopped" &&