Skip to content

Commit fb0f565

Browse files
committed
rendering improvements + mac instant mode pausing
1 parent 5f387b6 commit fb0f565

File tree

13 files changed

+178
-162
lines changed

13 files changed

+178
-162
lines changed

apps/desktop/src-tauri/src/lib.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,16 +533,23 @@ enum CurrentRecordingTarget {
533533
Area { screen: u32, bounds: Bounds },
534534
}
535535

536+
#[derive(Serialize, Type)]
537+
#[serde(rename_all = "camelCase")]
538+
struct CurrentRecording {
539+
target: CurrentRecordingTarget,
540+
r#type: RecordingType,
541+
}
542+
536543
#[tauri::command]
537544
#[specta::specta]
538545
async fn get_current_recording(
539546
state: MutableState<'_, App>,
540-
) -> Result<JsonValue<Option<CurrentRecordingTarget>>, ()> {
547+
) -> Result<JsonValue<Option<CurrentRecording>>, ()> {
541548
let state = state.read().await;
542549
Ok(JsonValue::new(&state.current_recording.as_ref().map(|r| {
543550
let bounds = r.bounds();
544551

545-
match r.capture_target() {
552+
let target = match r.capture_target() {
546553
ScreenCaptureTarget::Screen { id } => CurrentRecordingTarget::Screen { id: *id },
547554
ScreenCaptureTarget::Window { id } => CurrentRecordingTarget::Window {
548555
id: *id,
@@ -552,6 +559,14 @@ async fn get_current_recording(
552559
screen: *screen,
553560
bounds: bounds.clone(),
554561
},
562+
};
563+
564+
CurrentRecording {
565+
target,
566+
r#type: match r {
567+
InProgressRecording::Instant { .. } => RecordingType::Instant,
568+
InProgressRecording::Studio { .. } => RecordingType::Studio,
569+
},
555570
}
556571
})))
557572
}

apps/desktop/src-tauri/src/tray.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
9191
true,
9292
None::<&str>,
9393
)?,
94-
&MenuItem::with_id(
95-
app,
96-
TrayItem::PreviousScreenshots,
97-
"Previous Screenshots",
98-
true,
99-
None::<&str>,
100-
)?,
94+
// &MenuItem::with_id(
95+
// app,
96+
// TrayItem::PreviousScreenshots,
97+
// "Previous Screenshots",
98+
// true,
99+
// None::<&str>,
100+
// )?,
101101
&PredefinedMenuItem::separator(app)?,
102102
&MenuItem::with_id(app, TrayItem::OpenSettings, "Settings", true, None::<&str>)?,
103103
&MenuItem::with_id(app, TrayItem::Quit, "Quit Cap", true, None::<&str>)?,

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ export default function () {
214214
),
215215
});
216216

217+
const auth = authStore.createQuery();
218+
217219
return (
218220
<div class="flex justify-center flex-col p-[1rem] gap-[0.75rem] text-[0.875rem] font-[400] bg-[--gray-50] h-full text-[--text-primary]">
219221
{initialize()}
@@ -222,7 +224,11 @@ export default function () {
222224
<a
223225
class="*:w-[92px] *:h-auto text-[--text-primary]"
224226
target="_blank"
225-
href={import.meta.env.VITE_SERVER_URL}
227+
href={
228+
auth.data
229+
? `${import.meta.env.VITE_SERVER_URL}/dashboard`
230+
: import.meta.env.VITE_SERVER_URL
231+
}
226232
>
227233
<IconCapLogoFullDark class="hidden dark:block" />
228234
<IconCapLogoFull class="block dark:hidden" />
@@ -279,14 +285,14 @@ export default function () {
279285
</>
280286
)}
281287
</Button>
282-
<Button
288+
{/* <Button
283289
disabled={isRecording()}
284290
variant="secondary"
285291
size="md"
286292
onClick={() => commands.takeScreenshot()}
287293
>
288294
<IconLucideCamera class="w-[1rem] h-[1rem]" />
289-
</Button>
295+
</Button> */}
290296
</div>
291297
</div>
292298
);
@@ -327,6 +333,7 @@ import { Transition } from "solid-transition-group";
327333

328334
import { apiClient } from "~/utils/web-api";
329335
import { useWindowChrome } from "./Context";
336+
import { authStore } from "~/store";
330337

331338
let hasChecked = false;
332339
function createUpdateCheck() {

apps/desktop/src/routes/(window-chrome)/settings.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ export default function Settings(props: RouteSectionProps) {
7878
name: "Previous Recordings",
7979
icon: IconLucideSquarePlay,
8080
},
81-
{
82-
type: "link",
83-
href: "screenshots",
84-
name: "Previous Screenshots",
85-
icon: IconLucideCamera,
86-
},
81+
// {
82+
// type: "link",
83+
// href: "screenshots",
84+
// name: "Previous Screenshots",
85+
// icon: IconLucideCamera,
86+
// },
8787
{
8888
type: "link",
8989
href: "integrations",

apps/desktop/src/routes/(window-chrome)/update.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,11 @@ export default function () {
7373
}
7474
>
7575
<Match when={updateStatus()?.type === "done"}>
76-
<div class="flex flex-col gap-4">
76+
<div class="flex flex-col gap-4 items-center">
7777
<p class="text-[--text-tertiary]">
7878
Update has been installed. Restart Cap to finish updating.
7979
</p>
80-
<div class="flex flex-row">
81-
<Button onClick={() => relaunch()}>Restart Now</Button>
82-
</div>
80+
<Button onClick={() => relaunch()}>Restart Now</Button>
8381
</div>
8482
</Match>
8583
<Match
Lines changed: 51 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,28 @@
11
import { createEffect, createSignal, type ComponentProps } from "solid-js";
22
import { cx } from "cva";
3-
4-
import { commands, events } from "~/utils/tauri";
3+
import { type as ostype } from "@tauri-apps/plugin-os";
54
import { createTimer } from "@solid-primitives/timer";
65
import { createMutation } from "@tanstack/solid-query";
6+
import { createStore, produce } from "solid-js/store";
7+
import { getCurrentWindow } from "@tauri-apps/api/window";
8+
9+
import { commands, events } from "~/utils/tauri";
710
import {
811
createOptionsQuery,
912
createCurrentRecordingQuery,
1013
} from "~/utils/queries";
11-
import { createStore, produce } from "solid-js/store";
1214

13-
const audioLevelStore = {
14-
level: 0,
15-
initialized: false,
16-
init() {
17-
if (this.initialized) return;
18-
19-
events.audioInputLevelChange.listen((dbs) => {
20-
const DB_MIN = -60;
21-
const DB_MAX = 0;
22-
23-
const dbValue = dbs.payload ?? DB_MIN;
24-
const normalizedLevel = Math.max(
25-
0,
26-
Math.min(1, (dbValue - DB_MIN) / (DB_MAX - DB_MIN))
27-
);
28-
this.level = normalizedLevel;
29-
30-
window.dispatchEvent(
31-
new CustomEvent("audioLevelChange", { detail: normalizedLevel })
32-
);
33-
});
34-
35-
this.initialized = true;
36-
},
37-
cleanup() {
38-
this.initialized = false;
39-
this.level = 0;
40-
},
41-
};
15+
type State = "recording" | "paused" | "stopped";
4216

4317
export default function () {
4418
const start = Date.now();
4519
const [time, setTime] = createSignal(Date.now());
46-
const [isPaused, setIsPaused] = createSignal(false);
47-
const [stopped, setStopped] = createSignal(false);
48-
const [audioLevel, setAudioLevel] = createSignal<number>(0);
20+
const [state, setState] = createSignal<State>("recording");
4921
const currentRecording = createCurrentRecordingQuery();
5022
const { options } = createOptionsQuery();
5123

24+
const audioLevel = createAudioInputLevel();
25+
5226
const [pauseResumes, setPauseResumes] = createStore<
5327
| []
5428
| [
@@ -57,66 +31,42 @@ export default function () {
5731
]
5832
>([]);
5933

60-
const isAudioEnabled = () => {
61-
return options.data?.micName != null;
62-
};
63-
6434
createTimer(
6535
() => {
66-
if (stopped() || isPaused()) return;
36+
if (state() !== "recording") return;
6737
setTime(Date.now());
6838
},
6939
100,
7040
setInterval
7141
);
7242

7343
createEffect(() => {
74-
setTime(Date.now());
75-
});
76-
77-
// Single effect to handle audio initialization and cleanup
78-
createEffect(() => {
79-
if (!isAudioEnabled()) {
80-
audioLevelStore.cleanup();
81-
setAudioLevel(0);
82-
return;
83-
}
84-
85-
audioLevelStore.init();
86-
setAudioLevel(audioLevelStore.level);
87-
88-
const handler = (e: CustomEvent) => {
89-
setAudioLevel(e.detail);
90-
};
91-
92-
window.addEventListener("audioLevelChange", handler as EventListener);
93-
return () => {
94-
window.removeEventListener("audioLevelChange", handler as EventListener);
95-
};
44+
if (!currentRecording.isPending && currentRecording.data === undefined)
45+
getCurrentWindow().close();
9646
});
9747

9848
const stopRecording = createMutation(() => ({
9949
mutationFn: async () => {
100-
setStopped(true);
50+
setState("stopped");
10151
await commands.stopRecording();
10252
},
10353
}));
10454

10555
const togglePause = createMutation(() => ({
10656
mutationFn: async () => {
107-
if (isPaused()) {
57+
if (state() === "paused") {
10858
await commands.resumeRecording();
10959
setPauseResumes(
11060
produce((a) => {
11161
if (a.length === 0) return a;
11262
a[a.length - 1].resume = Date.now();
11363
})
11464
);
115-
setIsPaused(false);
65+
setState("recording");
11666
} else {
11767
await commands.pauseRecording();
11868
setPauseResumes((a) => [...a, { pause: Date.now() }]);
119-
setIsPaused(true);
69+
setState("paused");
12070
}
12171
setTime(Date.now());
12272
},
@@ -125,8 +75,7 @@ export default function () {
12575
const restartRecording = createMutation(() => ({
12676
mutationFn: async () => {
12777
await events.requestRestartRecording.emit();
128-
setStopped(false);
129-
setIsPaused(false);
78+
setState("recording");
13079
setTime(Date.now());
13180
},
13281
}));
@@ -149,14 +98,14 @@ export default function () {
14998
onClick={() => stopRecording.mutate()}
15099
>
151100
<IconCapStopCircle />
152-
<span class="font-[500] text-[0.875rem]">
101+
<span class="font-[500] text-[0.875rem] tabular-nums">
153102
{formatTime(adjustedTime() / 1000)}
154103
</span>
155104
</button>
156105

157106
<div class="flex items-center gap-1">
158107
<div class="relative h-8 w-8 flex items-center justify-center">
159-
{isAudioEnabled() ? (
108+
{options.data?.micName != null ? (
160109
<>
161110
<IconCapMicrophone class="size-5 text-gray-400" />
162111
<div class="absolute bottom-1 left-1 right-1 h-0.5 bg-gray-400 overflow-hidden rounded-full">
@@ -176,12 +125,19 @@ export default function () {
176125
)}
177126
</div>
178127

179-
<ActionButton
180-
disabled={togglePause.isPending}
181-
onClick={() => togglePause.mutate()}
182-
>
183-
{isPaused() ? <IconCapPlayCircle /> : <IconCapPauseCircle />}
184-
</ActionButton>
128+
{(currentRecording.data?.type === "studio" ||
129+
ostype() === "macos") && (
130+
<ActionButton
131+
disabled={togglePause.isPending}
132+
onClick={() => togglePause.mutate()}
133+
>
134+
{state() === "paused" ? (
135+
<IconCapPlayCircle />
136+
) : (
137+
<IconCapPauseCircle />
138+
)}
139+
</ActionButton>
140+
)}
185141

186142
<ActionButton
187143
disabled={restartRecording.isPending}
@@ -218,7 +174,25 @@ function ActionButton(props: ComponentProps<"button">) {
218174

219175
function formatTime(secs: number) {
220176
const minutes = Math.floor(secs / 60);
221-
const seconds = Math.round(secs % 60);
177+
const seconds = Math.floor(secs % 60);
222178

223179
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
224180
}
181+
182+
function createAudioInputLevel() {
183+
const [level, setLevel] = createSignal(0);
184+
185+
events.audioInputLevelChange.listen((dbs) => {
186+
const DB_MIN = -60;
187+
const DB_MAX = 0;
188+
189+
const dbValue = dbs.payload ?? DB_MIN;
190+
const normalizedLevel = Math.max(
191+
0,
192+
Math.min(1, (dbValue - DB_MIN) / (DB_MAX - DB_MIN))
193+
);
194+
setLevel(normalizedLevel);
195+
});
196+
197+
return level;
198+
}

apps/desktop/src/routes/window-capture-occluder.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ export default function () {
1616

1717
const bounds = () => {
1818
if (!currentRecording.data) return;
19-
if ("window" in currentRecording.data) {
20-
return currentRecording.data.window.bounds;
19+
if ("window" in currentRecording.data.target) {
20+
return currentRecording.data.target.window.bounds;
2121
}
22-
if ("area" in currentRecording.data) {
23-
return currentRecording.data.area.bounds;
22+
if ("area" in currentRecording.data.target) {
23+
return currentRecording.data.target.area.bounds;
2424
}
2525
};
2626

apps/desktop/src/utils/tauri.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async removeFakeWindow(name: string) : Promise<null> {
5050
async focusCapturesPanel() : Promise<void> {
5151
await TAURI_INVOKE("focus_captures_panel");
5252
},
53-
async getCurrentRecording() : Promise<JsonValue<CurrentRecordingTarget | null>> {
53+
async getCurrentRecording() : Promise<JsonValue<CurrentRecording | null>> {
5454
return await TAURI_INVOKE("get_current_recording");
5555
},
5656
async exportVideo(videoId: string, progress: TAURI_CHANNEL<RenderProgress>, force: boolean, fps: number, resolutionBase: XY<number>) : Promise<string> {
@@ -250,6 +250,7 @@ export type CaptureScreen = { id: number; name: string; refresh_rate: number }
250250
export type CaptureWindow = { id: number; owner_name: string; name: string; bounds: Bounds; refresh_rate: number }
251251
export type CommercialLicense = { licenseKey: string; expiryDate: number | null; refresh: number; activatedOn: number }
252252
export type Crop = { position: XY<number>; size: XY<number> }
253+
export type CurrentRecording = { target: CurrentRecordingTarget; type: RecordingType }
253254
export type CurrentRecordingChanged = null
254255
export type CurrentRecordingTarget = { window: { id: number; bounds: Bounds } } | { screen: { id: number } } | { area: { screen: number; bounds: Bounds } }
255256
export type CursorAnimationStyle = "regular" | "slow" | "fast"

0 commit comments

Comments
 (0)