Skip to content

Commit af1cc60

Browse files
committed
feat: Screenshots V1 - Enhance screenshot editor with mask tool and export cancel
1 parent 61982eb commit af1cc60

27 files changed

Lines changed: 830 additions & 440 deletions

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,25 @@ pub async fn export_video(
5050
settings
5151
.export(exporter_base, move |frame_index| {
5252
// Ensure progress never exceeds total frames
53-
let _ = progress.send(FramesRendered {
54-
rendered_count: (frame_index + 1).min(total_frames),
55-
total_frames,
56-
});
53+
progress
54+
.send(FramesRendered {
55+
rendered_count: (frame_index + 1).min(total_frames),
56+
total_frames,
57+
})
58+
.is_ok()
5759
})
5860
.await
5961
}
6062
ExportSettings::Gif(settings) => {
6163
settings
6264
.export(exporter_base, move |frame_index| {
6365
// Ensure progress never exceeds total frames
64-
let _ = progress.send(FramesRendered {
65-
rendered_count: (frame_index + 1).min(total_frames),
66-
total_frames,
67-
});
66+
progress
67+
.send(FramesRendered {
68+
rendered_count: (frame_index + 1).min(total_frames),
69+
total_frames,
70+
})
71+
.is_ok()
6872
})
6973
.await
7074
}

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,9 @@ impl ScreenshotEditorInstances {
194194
let mut layers = RendererLayers::new(&constants.device, &constants.queue);
195195

196196
// Initial render
197-
println!("Initial screenshot render");
198197
let mut current_config = config_rx.borrow().clone();
199198

200199
loop {
201-
println!(
202-
"Rendering screenshot frame with config: {:?}",
203-
current_config
204-
);
205200
let segment_frames = DecodedSegmentFrames {
206201
screen_frame: DecodedFrame::new(
207202
decoded_frame.data().to_vec(),
@@ -237,10 +232,6 @@ impl ScreenshotEditorInstances {
237232

238233
match rendered_frame {
239234
Ok(frame) => {
240-
println!(
241-
"Frame rendered successfully: {}x{}",
242-
frame.width, frame.height
243-
);
244235
let _ = frame_tx.send(Some(WSFrame {
245236
data: frame.data,
246237
width: frame.width,
@@ -254,13 +245,10 @@ impl ScreenshotEditorInstances {
254245
}
255246

256247
// Wait for config change
257-
println!("Waiting for config change");
258248
if config_rx.changed().await.is_err() {
259-
println!("Config channel closed");
260249
break;
261250
}
262251
current_config = config_rx.borrow().clone();
263-
println!("Config changed");
264252
}
265253
});
266254

apps/desktop/src/entry-server.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ export default createHandler(() => (
99
<meta charset="utf-8" />
1010
<meta name="viewport" content="width=device-width, initial-scale=1" />
1111
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg" />
12+
<script
13+
innerHTML={`
14+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
15+
document.documentElement.classList.add('dark');
16+
}
17+
`}
18+
/>
1219
{assets}
1320
</head>
1421
<body class="w-screen h-screen cursor-default select-none">

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ function Page() {
473473
return result.map(([path, meta]) => ({ ...meta, path }));
474474
},
475475
refetchInterval: 2000,
476+
reconcile: (old, next) => reconcile(next)(old),
476477
}),
477478
);
478479

@@ -950,42 +951,42 @@ function Page() {
950951
<IconCapSettings class="transition-colors text-gray-11 size-4 hover:text-gray-12" />
951952
</button>
952953
</Tooltip>
953-
<Tooltip content={<span>Recordings</span>}>
954+
<Tooltip content={<span>Screenshots</span>}>
954955
<button
955956
type="button"
956957
onClick={() => {
957-
setRecordingsMenuOpen((prev) => {
958+
setScreenshotsMenuOpen((prev) => {
958959
const next = !prev;
959960
if (next) {
960961
setDisplayMenuOpen(false);
961962
setWindowMenuOpen(false);
962-
setScreenshotsMenuOpen(false);
963+
setRecordingsMenuOpen(false);
963964
}
964965
return next;
965966
});
966967
}}
967968
class="flex justify-center items-center size-5"
968969
>
969-
<IconLucideSquarePlay class="transition-colors text-gray-11 size-4 hover:text-gray-12" />
970+
<IconLucideImage class="transition-colors text-gray-11 size-4 hover:text-gray-12" />
970971
</button>
971972
</Tooltip>
972-
<Tooltip content={<span>Screenshots</span>}>
973+
<Tooltip content={<span>Recordings</span>}>
973974
<button
974975
type="button"
975976
onClick={() => {
976-
setScreenshotsMenuOpen((prev) => {
977+
setRecordingsMenuOpen((prev) => {
977978
const next = !prev;
978979
if (next) {
979980
setDisplayMenuOpen(false);
980981
setWindowMenuOpen(false);
981-
setRecordingsMenuOpen(false);
982+
setScreenshotsMenuOpen(false);
982983
}
983984
return next;
984985
});
985986
}}
986987
class="flex justify-center items-center size-5"
987988
>
988-
<IconLucideImage class="transition-colors text-gray-11 size-4 hover:text-gray-12" />
989+
<IconLucideSquarePlay class="transition-colors text-gray-11 size-4 hover:text-gray-12" />
989990
</button>
990991
</Tooltip>
991992
<ChangelogButton />

apps/desktop/src/routes/camera.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,6 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
496496
frameDimensions()?.height,
497497
] as const,
498498
async ([size, shape, frameWidth, frameHeight]) => {
499-
const monitor = await currentMonitor();
500-
501499
const BAR_HEIGHT = 56;
502500
const base = Math.max(CAMERA_MIN_SIZE, Math.min(CAMERA_MAX_SIZE, size));
503501
const aspect = frameWidth && frameHeight ? frameWidth / frameHeight : 1;
@@ -507,14 +505,16 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
507505
shape === "full" ? (aspect >= 1 ? base : base / aspect) : base;
508506
const totalHeight = windowHeight + BAR_HEIGHT;
509507

510-
if (!monitor) return;
508+
const currentWindow = getCurrentWindow();
509+
await currentWindow.setSize(new LogicalSize(windowWidth, totalHeight));
510+
511+
const monitor = await currentMonitor();
512+
if (!monitor) return { size: base, windowWidth, windowHeight };
511513

512514
const scalingFactor = monitor.scaleFactor;
513515
const width = monitor.size.width / scalingFactor - windowWidth - 100;
514516
const height = monitor.size.height / scalingFactor - totalHeight - 100;
515517

516-
const currentWindow = getCurrentWindow();
517-
518518
if (!hasPositioned()) {
519519
currentWindow.setPosition(
520520
new LogicalPosition(
@@ -563,8 +563,6 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
563563
}
564564
}
565565

566-
await currentWindow.setSize(new LogicalSize(windowWidth, totalHeight));
567-
568566
return { width, height, size: base, windowWidth, windowHeight };
569567
},
570568
);

apps/desktop/src/routes/editor/ExportDialog.tsx

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
} from "@tanstack/solid-query";
99
import { Channel } from "@tauri-apps/api/core";
1010
import { CheckMenuItem, Menu } from "@tauri-apps/api/menu";
11-
import { save as saveDialog } from "@tauri-apps/plugin-dialog";
11+
import { ask, save as saveDialog } from "@tauri-apps/plugin-dialog";
12+
import { remove } from "@tauri-apps/plugin-fs";
1213
import { cx } from "cva";
1314
import {
1415
createEffect,
@@ -194,6 +195,27 @@ export function ExportDialog() {
194195
);
195196

196197
const [outputPath, setOutputPath] = createSignal<string | null>(null);
198+
const [isCancelled, setIsCancelled] = createSignal(false);
199+
200+
const handleCancel = async () => {
201+
if (
202+
await ask("Are you sure you want to cancel the export?", {
203+
title: "Cancel Export",
204+
kind: "warning",
205+
})
206+
) {
207+
setIsCancelled(true);
208+
setExportState({ type: "idle" });
209+
const path = outputPath();
210+
if (path) {
211+
try {
212+
await remove(path);
213+
} catch (e) {
214+
console.error("Failed to delete cancelled file", e);
215+
}
216+
}
217+
}
218+
};
197219

198220
const projectPath = editorInstance.path;
199221

@@ -222,13 +244,17 @@ export function ExportDialog() {
222244

223245
const copy = createMutation(() => ({
224246
mutationFn: async () => {
247+
setIsCancelled(false);
225248
if (exportState.type !== "idle") return;
226249
setExportState(reconcile({ action: "copy", type: "starting" }));
227250

228251
const outputPath = await exportWithSettings((progress) => {
252+
if (isCancelled()) return;
229253
setExportState({ type: "rendering", progress });
230254
});
231255

256+
if (isCancelled()) throw new SilentError("Cancelled");
257+
232258
setExportState({ type: "copying" });
233259

234260
await commands.copyVideoToClipboard(outputPath);
@@ -265,6 +291,7 @@ export function ExportDialog() {
265291

266292
const save = createMutation(() => ({
267293
mutationFn: async () => {
294+
setIsCancelled(false);
268295
if (exportState.type !== "idle") return;
269296

270297
const extension = settings.format === "Gif" ? "gif" : "mp4";
@@ -293,9 +320,12 @@ export function ExportDialog() {
293320
});
294321

295322
const videoPath = await exportWithSettings((progress) => {
323+
if (isCancelled()) return;
296324
setExportState({ type: "rendering", progress });
297325
});
298326

327+
if (isCancelled()) throw new SilentError("Cancelled");
328+
299329
setExportState({ type: "copying" });
300330

301331
await commands.copyFileToPath(videoPath, savePath);
@@ -332,6 +362,7 @@ export function ExportDialog() {
332362

333363
const upload = createMutation(() => ({
334364
mutationFn: async () => {
365+
setIsCancelled(false);
335366
if (exportState.type !== "idle") return;
336367
setExportState(reconcile({ action: "upload", type: "starting" }));
337368

@@ -371,9 +402,12 @@ export function ExportDialog() {
371402
);
372403
});
373404

374-
await exportWithSettings((progress) =>
375-
setExportState({ type: "rendering", progress }),
376-
);
405+
await exportWithSettings((progress) => {
406+
if (isCancelled()) return;
407+
setExportState({ type: "rendering", progress });
408+
});
409+
410+
if (isCancelled()) throw new SilentError("Cancelled");
377411

378412
setExportState({ type: "uploading", progress: 0 });
379413

@@ -853,10 +887,20 @@ export function ExportDialog() {
853887
keyed
854888
>
855889
{(copyState) => (
856-
<RenderProgress
857-
state={copyState}
858-
format={settings.format}
859-
/>
890+
<>
891+
<RenderProgress
892+
state={copyState}
893+
format={settings.format}
894+
/>
895+
<Button
896+
variant="ghost"
897+
size="sm"
898+
onClick={handleCancel}
899+
class="mt-4 hover:bg-red-9 hover:text-white"
900+
>
901+
Cancel
902+
</Button>
903+
</>
860904
)}
861905
</Show>
862906
</div>
@@ -895,10 +939,20 @@ export function ExportDialog() {
895939
keyed
896940
>
897941
{(copyState) => (
898-
<RenderProgress
899-
state={copyState}
900-
format={settings.format}
901-
/>
942+
<>
943+
<RenderProgress
944+
state={copyState}
945+
format={settings.format}
946+
/>
947+
<Button
948+
variant="ghost"
949+
size="sm"
950+
onClick={handleCancel}
951+
class="mt-4 hover:bg-red-9 hover:text-white"
952+
>
953+
Cancel
954+
</Button>
955+
</>
902956
)}
903957
</Show>
904958
</>
@@ -967,10 +1021,20 @@ export function ExportDialog() {
9671021
keyed
9681022
>
9691023
{(renderState) => (
970-
<RenderProgress
971-
state={renderState}
972-
format={settings.format}
973-
/>
1024+
<>
1025+
<RenderProgress
1026+
state={renderState}
1027+
format={settings.format}
1028+
/>
1029+
<Button
1030+
variant="ghost"
1031+
size="sm"
1032+
onClick={handleCancel}
1033+
class="mt-4 hover:bg-red-9 hover:text-white"
1034+
>
1035+
Cancel
1036+
</Button>
1037+
</>
9741038
)}
9751039
</Match>
9761040
</Switch>

0 commit comments

Comments
 (0)