Skip to content

Commit a1aa344

Browse files
authored
Customisable Delete Recording behaviour (#865)
* getting there * it actually works now * better copy * rename to DoNothing * actually do nothing * rename * types * icons
1 parent d04f66a commit a1aa344

File tree

6 files changed

+92
-38
lines changed

6 files changed

+92
-38
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use serde_json::json;
33
use specta::Type;
44
use tauri::{AppHandle, Wry};
55
use tauri_plugin_store::StoreExt;
6+
use tracing::error;
67
use uuid::Uuid;
78

89
#[derive(Default, Serialize, Deserialize, Type, Debug, Clone, Copy)]
@@ -21,6 +22,14 @@ pub enum MainWindowRecordingStartBehaviour {
2122
Minimise,
2223
}
2324

25+
#[derive(Default, Serialize, Deserialize, Type, Debug, Clone, Copy)]
26+
#[serde(rename_all = "camelCase")]
27+
pub enum PostDeletionBehaviour {
28+
#[default]
29+
DoNothing,
30+
ReopenRecordingWindow,
31+
}
32+
2433
impl MainWindowRecordingStartBehaviour {
2534
pub fn perform(&self, window: &tauri::WebviewWindow) -> tauri::Result<()> {
2635
match self {
@@ -79,6 +88,8 @@ pub struct GeneralSettingsStore {
7988
pub enable_native_camera_preview: bool,
8089
#[serde(default)]
8190
pub auto_zoom_on_clicks: bool,
91+
#[serde(default)]
92+
pub post_deletion_behaviour: PostDeletionBehaviour,
8293
}
8394

8495
fn default_server_url() -> String {
@@ -119,6 +130,7 @@ impl Default for GeneralSettingsStore {
119130
_open_editor_after_recording: false,
120131
enable_native_camera_preview: false,
121132
auto_zoom_on_clicks: false,
133+
post_deletion_behaviour: PostDeletionBehaviour::DoNothing,
122134
}
123135
}
124136
}
@@ -178,9 +190,9 @@ pub fn init(app: &AppHandle) {
178190
let store = match GeneralSettingsStore::get(app) {
179191
Ok(Some(store)) => store,
180192
Ok(None) => GeneralSettingsStore::default(),
181-
e => {
182-
e.unwrap();
183-
return;
193+
Err(e) => {
194+
error!("Failed to deserialize general settings store: {}", e);
195+
GeneralSettingsStore::default()
184196
}
185197
};
186198

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
audio::AppSounds,
77
auth::AuthStore,
88
create_screenshot,
9-
general_settings::{GeneralSettingsStore, PostStudioRecordingBehaviour},
9+
general_settings::{GeneralSettingsStore, PostDeletionBehaviour, PostStudioRecordingBehaviour},
1010
open_external_link,
1111
presets::PresetsStore,
1212
upload::{
@@ -485,6 +485,7 @@ pub async fn start_recording(
485485
// this clears the current recording for us
486486
handle_recording_end(app, None, &mut state).await.ok();
487487
}
488+
// Actor hasn't errored, it's just finished
488489
_ => {}
489490
}
490491
}
@@ -589,6 +590,23 @@ pub async fn delete_recording(app: AppHandle, state: MutableState<'_, App>) -> R
589590
)
590591
.await;
591592
}
593+
594+
// Check user's post-deletion behavior setting
595+
let settings = GeneralSettingsStore::get(&app)
596+
.ok()
597+
.flatten()
598+
.unwrap_or_default();
599+
600+
if let Some(window) = CapWindowId::InProgressRecording.get(&app) {
601+
let _ = window.close();
602+
}
603+
604+
match settings.post_deletion_behaviour {
605+
PostDeletionBehaviour::DoNothing => {}
606+
PostDeletionBehaviour::ReopenRecordingWindow => {
607+
let _ = ShowCapWindow::Main.show(&app).await;
608+
}
609+
}
592610
}
593611

594612
Ok(())

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
type GeneralSettingsStore,
1717
type MainWindowRecordingStartBehaviour,
1818
type PostStudioRecordingBehaviour,
19+
type PostDeletionBehaviour,
1920
} from "~/utils/tauri";
2021
import { CheckMenuItem, Menu } from "@tauri-apps/api/menu";
2122
import { confirm } from "@tauri-apps/plugin-dialog";
@@ -148,9 +149,14 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
148149
value:
149150
| MainWindowRecordingStartBehaviour
150151
| PostStudioRecordingBehaviour
152+
| PostDeletionBehaviour
151153
| number;
152154
onChange: (
153-
value: MainWindowRecordingStartBehaviour | PostStudioRecordingBehaviour
155+
value:
156+
| MainWindowRecordingStartBehaviour
157+
| PostStudioRecordingBehaviour
158+
| PostDeletionBehaviour
159+
| number
154160
) => void | Promise<void>;
155161
};
156162

@@ -253,6 +259,7 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
253259
value:
254260
| MainWindowRecordingStartBehaviour
255261
| PostStudioRecordingBehaviour
262+
| PostDeletionBehaviour
256263
| number
257264
) => handleChange("recordingCountdown", value as number),
258265
},
@@ -267,6 +274,8 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
267274
value:
268275
| MainWindowRecordingStartBehaviour
269276
| PostStudioRecordingBehaviour
277+
| PostDeletionBehaviour
278+
| number
270279
) =>
271280
handleChange(
272281
"mainWindowRecordingStartBehaviour",
@@ -284,12 +293,33 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
284293
value:
285294
| MainWindowRecordingStartBehaviour
286295
| PostStudioRecordingBehaviour
296+
| PostDeletionBehaviour
297+
| number
287298
) =>
288299
handleChange(
289300
"postStudioRecordingBehaviour",
290301
value as PostStudioRecordingBehaviour
291302
),
292303
},
304+
{
305+
label: "After deleting recording",
306+
description: "What happens to the window after deleting a recording",
307+
type: "select",
308+
get value() {
309+
return settings.postDeletionBehaviour ?? "doNothing";
310+
},
311+
onChange: (
312+
value:
313+
| MainWindowRecordingStartBehaviour
314+
| PostStudioRecordingBehaviour
315+
| PostDeletionBehaviour
316+
| number
317+
) =>
318+
handleChange(
319+
"postDeletionBehaviour",
320+
value as PostDeletionBehaviour
321+
),
322+
},
293323
],
294324
},
295325
];
@@ -301,6 +331,7 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
301331
getValue: () =>
302332
| MainWindowRecordingStartBehaviour
303333
| PostStudioRecordingBehaviour
334+
| PostDeletionBehaviour
304335
| number,
305336
onChange: (value: any) => void,
306337
options: { text: string; value: any }[]
@@ -421,6 +452,20 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
421452
{ text: "10 seconds", value: 10 },
422453
]
423454
);
455+
} else if (item.label === "After deleting recording") {
456+
return renderRecordingSelect(
457+
item.label,
458+
item.description,
459+
() => item.value,
460+
item.onChange,
461+
[
462+
{ text: "Do Nothing", value: "exit" },
463+
{
464+
text: "Reopen Recording Window",
465+
value: "reopenRecordingWindow",
466+
},
467+
]
468+
);
424469
}
425470
}
426471
return null;

apps/desktop/src/utils/tauri.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ openEditorAfterRecording?: boolean;
360360
/**
361361
* @deprecated can be removed when native camera preview is ready
362362
*/
363-
enableNativeCameraPreview?: boolean; autoZoomOnClicks?: boolean }
363+
enableNativeCameraPreview?: boolean; autoZoomOnClicks?: boolean; postDeletionBehaviour?: PostDeletionBehaviour }
364364
export type GifExportSettings = { fps: number; resolution_base: XY<number> }
365365
export type HapticPattern = "Alignment" | "LevelChange" | "Generic"
366366
export type HapticPerformanceTime = "Default" | "Now" | "DrawCompleted"
@@ -383,6 +383,7 @@ export type OSPermissionStatus = "notNeeded" | "empty" | "granted" | "denied"
383383
export type OSPermissionsCheck = { screenRecording: OSPermissionStatus; microphone: OSPermissionStatus; camera: OSPermissionStatus; accessibility: OSPermissionStatus }
384384
export type Plan = { upgraded: boolean; manual: boolean; last_checked: number }
385385
export type Platform = "MacOS" | "Windows"
386+
export type PostDeletionBehaviour = "doNothing" | "reopenRecordingWindow"
386387
export type PostStudioRecordingBehaviour = "openEditor" | "showOverlay"
387388
export type Preset = { name: string; config: ProjectConfiguration }
388389
export type PresetsStore = { presets: Preset[]; default: number | null }

crates/recording/src/studio_recording.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,15 @@ async fn run_actor_iteration(
450450
// Cancel from any state
451451
(Msg::Cancel(tx), state) => {
452452
let result = match state {
453-
State::Recording { mut pipeline, .. } => pipeline.inner.shutdown().await,
453+
State::Recording { mut pipeline, .. } => {
454+
if let Some(cursor) = &mut pipeline.cursor
455+
&& let Some(actor) = cursor.actor.take()
456+
{
457+
actor.stop().await;
458+
}
459+
460+
pipeline.inner.shutdown().await
461+
}
454462
State::Paused { .. } => Ok(()),
455463
};
456464

packages/ui-solid/src/auto-imports.d.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,26 @@ export {}
88
declare global {
99
const IconCapArrows: typeof import('~icons/cap/arrows.jsx')['default']
1010
const IconCapAudioOn: typeof import('~icons/cap/audio-on.jsx')['default']
11-
const IconCapAuto: typeof import("~icons/cap/auto.jsx")["default"]
1211
const IconCapBgBlur: typeof import('~icons/cap/bg-blur.jsx')['default']
13-
const IconCapBlur: typeof import("~icons/cap/blur.jsx")["default"]
1412
const IconCapCamera: typeof import('~icons/cap/camera.jsx')['default']
1513
const IconCapCaptions: typeof import('~icons/cap/captions.jsx')['default']
16-
const IconCapCheck: typeof import("~icons/cap/check.jsx")["default"]
1714
const IconCapChevronDown: typeof import('~icons/cap/chevron-down.jsx')['default']
1815
const IconCapCircle: typeof import('~icons/cap/circle.jsx')['default']
1916
const IconCapCircleCheck: typeof import('~icons/cap/circle-check.jsx')['default']
2017
const IconCapCirclePlus: typeof import('~icons/cap/circle-plus.jsx')['default']
2118
const IconCapCircleX: typeof import('~icons/cap/circle-x.jsx')['default']
22-
const IconCapClock: typeof import("~icons/cap/clock.jsx")["default"]
2319
const IconCapCopy: typeof import('~icons/cap/copy.jsx')['default']
2420
const IconCapCorners: typeof import('~icons/cap/corners.jsx')['default']
2521
const IconCapCrop: typeof import('~icons/cap/crop.jsx')['default']
2622
const IconCapCursor: typeof import('~icons/cap/cursor.jsx')['default']
27-
const IconCapDownload: typeof import("~icons/cap/download.jsx")["default"]
2823
const IconCapEditor: typeof import('~icons/cap/editor.jsx')['default']
2924
const IconCapEnlarge: typeof import('~icons/cap/enlarge.jsx')['default']
3025
const IconCapFile: typeof import('~icons/cap/file.jsx')['default']
3126
const IconCapFilmCut: typeof import('~icons/cap/film-cut.jsx')['default']
32-
const IconCapFrameFirst: typeof import("~icons/cap/frame-first.jsx")["default"]
33-
const IconCapFrameLast: typeof import("~icons/cap/frame-last.jsx")["default"]
3427
const IconCapGauge: typeof import('~icons/cap/gauge.jsx')['default']
3528
const IconCapHotkeys: typeof import('~icons/cap/hotkeys.jsx')['default']
3629
const IconCapImage: typeof import('~icons/cap/image.jsx')['default']
3730
const IconCapInfo: typeof import('~icons/cap/info.jsx')['default']
38-
const IconCapInset: typeof import("~icons/cap/inset.jsx")["default"]
3931
const IconCapInstant: typeof import('~icons/cap/instant.jsx')['default']
4032
const IconCapLayout: typeof import('~icons/cap/layout.jsx')['default']
4133
const IconCapLink: typeof import('~icons/cap/link.jsx')['default']
@@ -45,8 +37,6 @@ declare global {
4537
const IconCapMessageBubble: typeof import('~icons/cap/message-bubble.jsx')['default']
4638
const IconCapMicrophone: typeof import('~icons/cap/microphone.jsx')['default']
4739
const IconCapMoreVertical: typeof import('~icons/cap/more-vertical.jsx')['default']
48-
const IconCapMoveLeft: typeof import("~icons/cap/move-left.jsx")["default"]
49-
const IconCapMoveRight: typeof import("~icons/cap/move-right.jsx")["default"]
5040
const IconCapNext: typeof import('~icons/cap/next.jsx')['default']
5141
const IconCapPadding: typeof import('~icons/cap/padding.jsx')['default']
5242
const IconCapPause: typeof import('~icons/cap/pause.jsx')['default']
@@ -56,58 +46,38 @@ declare global {
5646
const IconCapPresets: typeof import('~icons/cap/presets.jsx')['default']
5747
const IconCapPrev: typeof import('~icons/cap/prev.jsx')['default']
5848
const IconCapRedo: typeof import('~icons/cap/redo.jsx')['default']
59-
const IconCapRefresh: typeof import("~icons/cap/refresh.jsx")["default"]
6049
const IconCapRestart: typeof import('~icons/cap/restart.jsx')['default']
6150
const IconCapScissors: typeof import('~icons/cap/scissors.jsx')['default']
6251
const IconCapSettings: typeof import('~icons/cap/settings.jsx')['default']
6352
const IconCapShadow: typeof import('~icons/cap/shadow.jsx')['default']
64-
const IconCapSize: typeof import("~icons/cap/size.jsx")["default"]
6553
const IconCapSquare: typeof import('~icons/cap/square.jsx')['default']
66-
const IconCapStop: typeof import("~icons/cap/stop.jsx")["default"]
6754
const IconCapStopCircle: typeof import('~icons/cap/stop-circle.jsx')['default']
6855
const IconCapTrash: typeof import('~icons/cap/trash.jsx')['default']
6956
const IconCapUndo: typeof import('~icons/cap/undo.jsx')['default']
70-
const IconCapUpload: typeof import("~icons/cap/upload.jsx")["default"]
71-
const IconCapWindow: typeof import("~icons/cap/window.jsx")["default"]
72-
const IconCapX: typeof import("~icons/cap/x.jsx")["default"]
57+
const IconCapUpload: typeof import('~icons/cap/upload.jsx')['default']
7358
const IconCapZoomIn: typeof import('~icons/cap/zoom-in.jsx')['default']
7459
const IconCapZoomOut: typeof import('~icons/cap/zoom-out.jsx')['default']
7560
const IconHugeiconsEaseCurveControlPoints: typeof import('~icons/hugeicons/ease-curve-control-points.jsx')['default']
76-
const IconIcBaselineMonitor: typeof import("~icons/ic/baseline-monitor.jsx")["default"]
77-
const IconIcRoundSearch: typeof import("~icons/ic/round-search.jsx")["default"]
78-
const IconLucideAppWindowMac: typeof import('~icons/lucide/app-window-mac.jsx')['default']
7961
const IconLucideBell: typeof import('~icons/lucide/bell.jsx')['default']
8062
const IconLucideBug: typeof import('~icons/lucide/bug.jsx')['default']
81-
const IconLucideCamera: typeof import("~icons/lucide/camera.jsx")["default"]
8263
const IconLucideCheck: typeof import('~icons/lucide/check.jsx')['default']
83-
const IconLucideChevronDown: typeof import("~icons/lucide/chevron-down.jsx")["default"]
8464
const IconLucideClock: typeof import('~icons/lucide/clock.jsx')['default']
8565
const IconLucideDatabase: typeof import('~icons/lucide/database.jsx')['default']
8666
const IconLucideEdit: typeof import('~icons/lucide/edit.jsx')['default']
8767
const IconLucideEye: typeof import('~icons/lucide/eye.jsx')['default']
8868
const IconLucideFolder: typeof import('~icons/lucide/folder.jsx')['default']
8969
const IconLucideGift: typeof import('~icons/lucide/gift.jsx')['default']
9070
const IconLucideHardDrive: typeof import('~icons/lucide/hard-drive.jsx')['default']
91-
const IconLucideLayoutGrid: typeof import("~icons/lucide/layout-grid.jsx")["default"]
9271
const IconLucideLoaderCircle: typeof import('~icons/lucide/loader-circle.jsx')['default']
93-
const IconLucideMessageCircle: typeof import("~icons/lucide/message-circle.jsx")["default"]
94-
const IconLucideMessageSquare: typeof import("~icons/lucide/message-square.jsx")["default"]
9572
const IconLucideMessageSquarePlus: typeof import('~icons/lucide/message-square-plus.jsx')['default']
9673
const IconLucideMicOff: typeof import('~icons/lucide/mic-off.jsx')['default']
9774
const IconLucideMonitor: typeof import('~icons/lucide/monitor.jsx')['default']
98-
const IconLucideRabbit: typeof import("~icons/lucide/rabbit.jsx")["default"]
99-
const IconLucideRectangle: typeof import("~icons/lucide/rectangle.jsx")["default"]
10075
const IconLucideRectangleHorizontal: typeof import('~icons/lucide/rectangle-horizontal.jsx')['default']
101-
const IconLucideRectangleSquare: typeof import("~icons/lucide/rectangle-square.jsx")["default"]
10276
const IconLucideRotateCcw: typeof import('~icons/lucide/rotate-ccw.jsx')['default']
10377
const IconLucideSearch: typeof import('~icons/lucide/search.jsx')['default']
10478
const IconLucideSquarePlay: typeof import('~icons/lucide/square-play.jsx')['default']
10579
const IconLucideUnplug: typeof import('~icons/lucide/unplug.jsx')['default']
106-
const IconLucideVideo: typeof import("~icons/lucide/video.jsx")["default"]
10780
const IconLucideVolume2: typeof import('~icons/lucide/volume2.jsx')['default']
10881
const IconLucideVolumeX: typeof import('~icons/lucide/volume-x.jsx')['default']
109-
const IconLucideX: typeof import("~icons/lucide/x.jsx")["default"]
110-
const IconMaterialSymbolsScreenshotFrame2Rounded: typeof import('~icons/material-symbols/screenshot-frame2-rounded.jsx')['default']
111-
const IconMdiMonitor: typeof import('~icons/mdi/monitor.jsx')['default']
11282
const IconPhMonitorBold: typeof import('~icons/ph/monitor-bold.jsx')['default']
11383
}

0 commit comments

Comments
 (0)