Skip to content

Commit 47d12f4

Browse files
committed
store configs
1 parent c2f2b90 commit 47d12f4

File tree

13 files changed

+284
-116
lines changed

13 files changed

+284
-116
lines changed

src/main/ipc/dialogs.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
*/
44

55
import { ipcMain, dialog, BrowserWindow } from 'electron';
6-
import { setConfiguredOutputPath, getConfiguredOutputPath } from '../state';
6+
import { setConfiguredOutputDir, getConfiguredOutputDir } from '../state';
7+
import { loadConfig, saveConfig } from '../state';
8+
import type { UserConfig } from '../state';
79

810
/**
911
* Register dialog-related IPC handlers
@@ -14,31 +16,29 @@ export function registerDialogHandlers(
1416
): void {
1517
ipcMain.handle('select-output-path', async () => {
1618
const mainWindow = getMainWindow();
17-
const result = await dialog.showSaveDialog(mainWindow!, {
18-
title: 'Save Recording',
19-
defaultPath: `recording_${Date.now()}.mp4`,
20-
filters: [
21-
{ name: 'Video Files', extensions: ['mp4', 'mov'] },
22-
{ name: 'All Files', extensions: ['*'] },
23-
],
19+
const currentDir = getConfiguredOutputDir();
20+
const result = await dialog.showOpenDialog(mainWindow!, {
21+
title: 'Select Save Location',
22+
defaultPath: currentDir || undefined,
23+
properties: ['openDirectory', 'createDirectory'],
2424
});
2525

26-
if (result.canceled) {
26+
if (result.canceled || !result.filePaths || result.filePaths.length === 0) {
2727
return null;
2828
}
2929

30-
// Store the configured output path
31-
setConfiguredOutputPath(result.filePath || null);
32-
return result.filePath;
30+
const dir = result.filePaths[0];
31+
setConfiguredOutputDir(dir);
32+
return dir;
3333
});
3434

35-
ipcMain.handle('set-output-path', async (_, path: string | null) => {
36-
setConfiguredOutputPath(path);
35+
ipcMain.handle('set-output-path', async (_, dir: string | null) => {
36+
setConfiguredOutputDir(dir);
3737
return { success: true };
3838
});
3939

4040
ipcMain.handle('get-output-path', async () => {
41-
return getConfiguredOutputPath();
41+
return getConfiguredOutputDir();
4242
});
4343

4444
ipcMain.handle('select-video-file', async () => {
@@ -76,4 +76,15 @@ export function registerDialogHandlers(
7676

7777
return result.filePaths[0];
7878
});
79+
80+
ipcMain.handle('get-user-config', async () => {
81+
return loadConfig();
82+
});
83+
84+
ipcMain.handle('set-user-config', async (_, partial: Partial<UserConfig>) => {
85+
const config = loadConfig();
86+
Object.assign(config, partial);
87+
saveConfig(config);
88+
return { success: true };
89+
});
7990
}

src/main/ipc/recording-bar.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type { Platform } from '../../platform';
99
import type { CursorConfig, ZoomConfig } from '../../types';
1010
import { MetadataExporter } from '../../processing/metadata-exporter';
1111
import { createLogger } from '../../utils/logger';
12-
import { DEFAULT_FRAME_RATE, DEFAULT_CURSOR_SIZE } from '../../utils/constants';
1312
import {
1413
getRecordingState,
1514
getCurrentRecordingConfig,
@@ -21,6 +20,7 @@ import {
2120
createScreenCapture,
2221
createMouseTracker,
2322
cleanupRecording,
23+
loadConfig,
2424
} from '../state';
2525
import {
2626
showRecordingBar,
@@ -55,15 +55,17 @@ export function registerRecordingBarHandlers(
5555
// Stop the timer immediately so UI shows recording has ended
5656
stopRecordingBarTimer();
5757

58-
// Use default configs for stop
58+
// Load configs from persistent store
59+
const userConfig = loadConfig();
60+
5961
const cursorConfig: CursorConfig = {
60-
size: DEFAULT_CURSOR_SIZE,
61-
shape: 'arrow',
62+
size: userConfig.cursorSize,
63+
shape: userConfig.cursorShape as CursorConfig['shape'],
6264
};
6365

6466
const zoomConfig: ZoomConfig = {
65-
enabled: true,
66-
level: 2.0,
67+
enabled: userConfig.zoomEnabled,
68+
level: userConfig.zoomLevel,
6769
transitionSpeed: 300,
6870
padding: 0,
6971
followSpeed: 1.0,
@@ -150,7 +152,7 @@ export function registerRecordingBarHandlers(
150152
mouseEvents: adjustedMouseEvents,
151153
cursorConfig,
152154
zoomConfig,
153-
frameRate: DEFAULT_FRAME_RATE,
155+
frameRate: parseInt(userConfig.frameRate, 10) || 60,
154156
videoDuration: recordingDuration,
155157
screenDimensions,
156158
recordingRegion: currentRecordingConfig?.region,
@@ -352,10 +354,11 @@ export function registerRecordingBarHandlers(
352354
outputPath: configuredOutputPath,
353355
});
354356

355-
// Create a default recording config
357+
// Create recording config from persisted settings
358+
const userConfig = loadConfig();
356359
setCurrentRecordingConfig({
357360
outputPath: configuredOutputPath,
358-
frameRate: DEFAULT_FRAME_RATE,
361+
frameRate: parseInt(userConfig.frameRate, 10) || 60,
359362
});
360363

361364
const mainWindow = getMainWindow();

src/main/ipc/recording.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type { Platform } from '../../platform';
99
import type { RecordingConfig, CursorConfig, ZoomConfig, MouseEffectsConfig } from '../../types';
1010
import { MetadataExporter } from '../../processing/metadata-exporter';
1111
import { createLogger } from '../../utils/logger';
12-
import { DEFAULT_FRAME_RATE, DEFAULT_CURSOR_SIZE } from '../../utils/constants';
1312
import {
1413
getRecordingState,
1514
getCurrentRecordingConfig,
@@ -19,6 +18,7 @@ import {
1918
getMouseTracker,
2019
createScreenCapture,
2120
createMouseTracker,
21+
loadConfig,
2222
} from '../state';
2323
import { showRecordingBar, hideRecordingBar, stopRecordingBarTimer } from '../recording-bar-window';
2424

@@ -149,9 +149,10 @@ export function registerRecordingHandlers(
149149
mouseEffectsConfig?: MouseEffectsConfig;
150150
} | CursorConfig) => {
151151
// Handle both old format (just CursorConfig) and new format (object with cursorConfig)
152+
const userConfig = loadConfig();
152153
let cursorConfig: CursorConfig = {
153-
size: DEFAULT_CURSOR_SIZE,
154-
shape: 'arrow',
154+
size: userConfig.cursorSize,
155+
shape: userConfig.cursorShape as CursorConfig['shape'],
155156
};
156157
let zoomConfig: ZoomConfig | undefined;
157158
let mouseEffectsConfig: MouseEffectsConfig | undefined;
@@ -278,7 +279,7 @@ export function registerRecordingHandlers(
278279
cursorConfig,
279280
zoomConfig,
280281
mouseEffectsConfig,
281-
frameRate: DEFAULT_FRAME_RATE,
282+
frameRate: parseInt(userConfig.frameRate, 10) || 60,
282283
videoDuration: recordingDuration,
283284
screenDimensions,
284285
recordingRegion: currentRecordingConfig?.region,

src/main/ipc/studio.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { VideoProcessor } from '../../processing/video-processor';
99
import { MetadataExporter } from '../../processing/metadata-exporter';
1010
import type { RecordingMetadata } from '../../types/metadata';
1111
import { createLogger } from '../../utils/logger';
12-
import { DEFAULT_FRAME_RATE } from '../../utils/constants';
12+
import { loadConfig } from '../state';
1313

1414
const logger = createLogger('IPC:Studio');
1515

@@ -60,7 +60,7 @@ export function registerStudioHandlers(
6060
return {
6161
width: dimensions.width,
6262
height: dimensions.height,
63-
frameRate: DEFAULT_FRAME_RATE, // Default, could be extracted from video
63+
frameRate: parseInt(loadConfig().frameRate, 10) || 60,
6464
duration: 0, // Would need to extract from video metadata
6565
};
6666
});

src/main/screen-capture.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { RecordingConfig } from '../types';
55
import { getFfmpegPath } from '../utils/ffmpeg-path';
66
import { waitForFileStable, validateVideoFile } from '../utils/file-utils';
77
import { createLogger } from '../utils/logger';
8-
import { DEFAULT_FRAME_RATE } from '../utils/constants';
8+
import { loadConfig } from './state';
99

1010
// Create logger for screen capture
1111
const logger = createLogger('ScreenCapture');
@@ -110,7 +110,7 @@ export class ScreenCapture {
110110
* Uses platform-specific input methods
111111
*/
112112
private async buildRecordingArgs(config: RecordingConfig): Promise<string[]> {
113-
const frameRate = String(config.frameRate || DEFAULT_FRAME_RATE);
113+
const frameRate = String(config.frameRate || parseInt(loadConfig().frameRate, 10) || 60);
114114

115115
if (isWindows) {
116116
// Windows: use gdigrab for desktop capture

src/main/state/config-store.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Persistent user config storage
3+
* Reads/writes config.json in the platform-appropriate userData directory
4+
*/
5+
6+
import { app } from 'electron';
7+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
8+
import { join } from 'path';
9+
import { createLogger } from '../../utils/logger';
10+
11+
const logger = createLogger('ConfigStore');
12+
13+
export interface UserConfig {
14+
// Recording
15+
outputDir: string | null;
16+
frameRate: string;
17+
18+
// Cursor
19+
cursorSize: number;
20+
cursorShape: string;
21+
22+
// Zoom
23+
zoomEnabled: boolean;
24+
zoomLevel: number;
25+
zoomAnimation: string;
26+
27+
// Click effects
28+
clickCirclesEnabled: boolean;
29+
clickCircleColor: string;
30+
}
31+
32+
const defaults: UserConfig = {
33+
outputDir: null,
34+
frameRate: '60',
35+
36+
cursorSize: 32,
37+
cursorShape: 'arrow',
38+
39+
zoomEnabled: true,
40+
zoomLevel: 2.0,
41+
zoomAnimation: 'mellow',
42+
43+
clickCirclesEnabled: false,
44+
clickCircleColor: '#ffffff',
45+
};
46+
47+
function configPath(): string {
48+
return join(app.getPath('userData'), 'config.json');
49+
}
50+
51+
export function loadConfig(): UserConfig {
52+
try {
53+
const raw = readFileSync(configPath(), 'utf-8');
54+
const parsed = JSON.parse(raw);
55+
return { ...defaults, ...parsed };
56+
} catch {
57+
return { ...defaults };
58+
}
59+
}
60+
61+
export function saveConfig(config: UserConfig): void {
62+
try {
63+
const dir = app.getPath('userData');
64+
mkdirSync(dir, { recursive: true });
65+
writeFileSync(configPath(), JSON.stringify(config, null, 2), 'utf-8');
66+
} catch (error) {
67+
logger.error('Failed to save config:', error);
68+
}
69+
}
70+
71+
export function getConfigValue<K extends keyof UserConfig>(key: K): UserConfig[K] {
72+
return loadConfig()[key];
73+
}
74+
75+
export function setConfigValue<K extends keyof UserConfig>(key: K, value: UserConfig[K]): void {
76+
const config = loadConfig();
77+
config[key] = value;
78+
saveConfig(config);
79+
}

src/main/state/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@
44
export {
55
getRecordingState,
66
getCurrentRecordingConfig,
7+
getConfiguredOutputDir,
78
getConfiguredOutputPath,
89
getScreenCapture,
910
getMouseTracker,
1011
setRecordingState,
1112
setCurrentRecordingConfig,
12-
setConfiguredOutputPath,
13+
setConfiguredOutputDir,
1314
createScreenCapture,
1415
createMouseTracker,
1516
cleanupRecording,
1617
} from './recording-state';
18+
19+
export {
20+
loadConfig,
21+
saveConfig,
22+
getConfigValue,
23+
setConfigValue,
24+
} from './config-store';
25+
export type { UserConfig } from './config-store';

src/main/state/recording-state.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
*/
55

66
import { existsSync, unlinkSync } from 'fs';
7+
import { join } from 'path';
78
import { ScreenCapture } from '../screen-capture';
89
import { MouseTracker } from '../mouse-tracker';
910
import type { RecordingConfig, RecordingState } from '../../types';
1011
import type { Platform } from '../../platform';
1112
import { createLogger } from '../../utils/logger';
1213
import { showRecordingBarIdle } from '../recording-bar-window';
14+
import { getConfigValue, setConfigValue } from './config-store';
1315

1416
const logger = createLogger('RecordingState');
1517

@@ -18,7 +20,7 @@ let recordingState: RecordingState = {
1820
isRecording: false,
1921
};
2022
let currentRecordingConfig: RecordingConfig | null = null;
21-
let configuredOutputPath: string | null = null;
23+
let configuredOutputDir: string | null | undefined = undefined;
2224

2325
// Components
2426
let screenCapture: ScreenCapture | null = null;
@@ -33,8 +35,16 @@ export function getCurrentRecordingConfig(): RecordingConfig | null {
3335
return currentRecordingConfig;
3436
}
3537

38+
export function getConfiguredOutputDir(): string | null {
39+
if (configuredOutputDir === undefined) {
40+
configuredOutputDir = getConfigValue('outputDir');
41+
}
42+
return configuredOutputDir;
43+
}
44+
3645
export function getConfiguredOutputPath(): string | null {
37-
return configuredOutputPath;
46+
const dir = getConfiguredOutputDir();
47+
return dir ? join(dir, `recording_${Date.now()}.mp4`) : null;
3848
}
3949

4050
export function getScreenCapture(): ScreenCapture | null {
@@ -54,8 +64,9 @@ export function setCurrentRecordingConfig(config: RecordingConfig | null): void
5464
currentRecordingConfig = config;
5565
}
5666

57-
export function setConfiguredOutputPath(path: string | null): void {
58-
configuredOutputPath = path;
67+
export function setConfiguredOutputDir(dir: string | null): void {
68+
configuredOutputDir = dir;
69+
setConfigValue('outputDir', dir);
5970
}
6071

6172
// Create new instances

0 commit comments

Comments
 (0)