Skip to content

Commit 5dc076c

Browse files
committed
feat(desktop): stop recording overlay and stable in-progress bounds
1 parent bd987b3 commit 5dc076c

File tree

2 files changed

+73
-11
lines changed

2 files changed

+73
-11
lines changed

apps/desktop/src/routes/(window-chrome)/new-main/index.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,8 @@ function Page() {
979979
const { rawOptions, setOptions } = useRecordingOptions();
980980
const currentRecording = createCurrentRecordingQuery();
981981
const isRecording = () => !!currentRecording.data;
982+
const isActivelyRecording = () =>
983+
currentRecording.data?.status === "recording";
982984
const auth = authStore.createQuery();
983985

984986
const [hasHiddenMainWindowForPicker, setHasHiddenMainWindowForPicker] =
@@ -1508,6 +1510,20 @@ function Page() {
15081510
const license = createLicenseQuery();
15091511

15101512
const signIn = createSignInMutation();
1513+
const stopRecording = createMutation(() => ({
1514+
mutationFn: async () => {
1515+
try {
1516+
await commands.stopRecording();
1517+
} catch (error) {
1518+
await dialog.message(
1519+
`Failed to stop recording: ${
1520+
error instanceof Error ? error.message : String(error)
1521+
}`,
1522+
{ title: "Stop Recording", kind: "error" },
1523+
);
1524+
}
1525+
},
1526+
}));
15111527

15121528
const BaseControls = () => (
15131529
<div class="space-y-2">
@@ -1994,6 +2010,26 @@ function Page() {
19942010
</Show>
19952011
</Show>
19962012
</div>
2013+
<Show when={isActivelyRecording()}>
2014+
<div class="absolute inset-0 z-10 flex flex-col justify-end bg-gray-1/80 px-6 pb-8 backdrop-blur-sm">
2015+
<div class="pointer-events-auto">
2016+
<button
2017+
type="button"
2018+
disabled={stopRecording.isPending}
2019+
onClick={() => stopRecording.mutate()}
2020+
class="flex h-11 w-full items-center justify-center gap-2 rounded-xl bg-red-9 px-4 text-sm font-medium text-white transition hover:bg-red-10 disabled:cursor-not-allowed disabled:opacity-60"
2021+
>
2022+
<Show
2023+
when={!stopRecording.isPending}
2024+
fallback={<IconLucideLoader2 class="size-4 animate-spin" />}
2025+
>
2026+
<IconCapStopCircle class="size-4" />
2027+
</Show>
2028+
<span>Stop Recording</span>
2029+
</button>
2030+
</div>
2031+
</div>
2032+
</Show>
19972033
<RecoveryToast />
19982034
</div>
19992035
);

apps/desktop/src/routes/in-progress-recording.tsx

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { createElementBounds } from "@solid-primitives/bounds";
21
import { createTimer } from "@solid-primitives/timer";
32
import { createMutation } from "@tanstack/solid-query";
43
import { LogicalPosition } from "@tauri-apps/api/dpi";
@@ -111,8 +110,8 @@ function InProgressRecordingInner() {
111110
const [startingDismissed, setStartingDismissed] = createSignal(false);
112111
const [interactiveAreaRef, setInteractiveAreaRef] =
113112
createSignal<HTMLDivElement | null>(null);
114-
const interactiveBounds = createElementBounds(interactiveAreaRef);
115113
let settingsButtonRef: HTMLButtonElement | undefined;
114+
let lastInteractiveBoundsKey = "";
116115
const recordingMode = createMemo(
117116
() => currentRecording.data?.mode ?? optionsQuery.rawOptions.mode,
118117
);
@@ -292,30 +291,55 @@ function InProgressRecordingInner() {
292291
void refreshCameraWindowState();
293292
});
294293

295-
createEffect(() => {
294+
const syncInteractiveAreaBounds = () => {
296295
const element = interactiveAreaRef();
297296
if (!element) {
298-
void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME);
297+
if (lastInteractiveBoundsKey !== "") {
298+
lastInteractiveBoundsKey = "";
299+
void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME);
300+
}
299301
return;
300302
}
301303

302-
const left = interactiveBounds.left ?? 0;
303-
const top = interactiveBounds.top ?? 0;
304-
const width = interactiveBounds.width ?? 0;
305-
const height = interactiveBounds.height ?? 0;
304+
const rect = element.getBoundingClientRect();
305+
if (rect.width === 0 || rect.height === 0) return;
306306

307-
if (width === 0 || height === 0) return;
307+
const key = [rect.left, rect.top, rect.width, rect.height]
308+
.map((value) => value.toFixed(2))
309+
.join(":");
310+
if (key === lastInteractiveBoundsKey) return;
308311

312+
lastInteractiveBoundsKey = key;
309313
void commands.setFakeWindowBounds(FAKE_WINDOW_BOUNDS_NAME, {
310-
position: { x: left, y: top },
311-
size: { width, height },
314+
position: { x: rect.left, y: rect.top },
315+
size: { width: rect.width, height: rect.height },
312316
});
317+
};
318+
319+
createEffect(() => {
320+
interactiveAreaRef();
321+
queueMicrotask(syncInteractiveAreaBounds);
322+
});
323+
324+
createEffect(() => {
325+
state();
326+
issuePanelVisible();
327+
queueMicrotask(syncInteractiveAreaBounds);
313328
});
314329

315330
onCleanup(() => {
331+
lastInteractiveBoundsKey = "";
316332
void commands.removeFakeWindow(FAKE_WINDOW_BOUNDS_NAME);
317333
});
318334

335+
onMount(() => {
336+
const onResize = () => syncInteractiveAreaBounds();
337+
window.addEventListener("resize", onResize);
338+
onCleanup(() => window.removeEventListener("resize", onResize));
339+
requestAnimationFrame(() => syncInteractiveAreaBounds());
340+
setTimeout(() => syncInteractiveAreaBounds(), 150);
341+
});
342+
319343
createTimer(
320344
() => {
321345
void refreshCameraWindowState();
@@ -324,6 +348,8 @@ function InProgressRecordingInner() {
324348
setInterval,
325349
);
326350

351+
createTimer(syncInteractiveAreaBounds, 250, setInterval);
352+
327353
createEffect(() => {
328354
if (
329355
state().variant === "stopped" &&

0 commit comments

Comments
 (0)