|
24 | 24 | getBirdaConfig, |
25 | 25 | openExecutableDialog, |
26 | 26 | openFolderDialog, |
| 27 | + openInExplorer, |
27 | 28 | getAvailableLanguages, |
28 | 29 | getCatalogStats, |
29 | 30 | clearDatabase, |
|
96 | 97 | let birdaStatus = $state<{ available: boolean; path?: string; error?: string } | null>(null); |
97 | 98 | let birdaConfig = $state<Record<string, unknown> | null>(null); |
98 | 99 | let availableLanguages = $state<{ code: string; name: string }[]>([]); |
| 100 | + let savedSettings = $state<AppSettings | null>(null); |
99 | 101 | let saving = $state(false); |
100 | 102 | let saved = $state(false); |
101 | 103 | let error = $state<string | null>(null); |
|
143 | 145 | appState.theme = settings.theme; |
144 | 146 | }); |
145 | 147 |
|
| 148 | + $effect(() => { |
| 149 | + if (!savedSettings) { |
| 150 | + appState.settingsHasUnsavedChanges = false; |
| 151 | + return; |
| 152 | + } |
| 153 | + appState.settingsHasUnsavedChanges = JSON.stringify($state.snapshot(settings)) !== JSON.stringify(savedSettings); |
| 154 | + }); |
| 155 | +
|
146 | 156 | async function load() { |
147 | 157 | try { |
148 | 158 | const loaded = await getSettings(); |
149 | 159 | settings = { ...settings, ...loaded }; |
| 160 | + savedSettings = structuredClone($state.snapshot(settings)); |
150 | 161 | appState.theme = loaded.theme; |
151 | 162 |
|
152 | 163 | birdaStatus = await checkBirda(); |
|
200 | 211 | error = null; |
201 | 212 | try { |
202 | 213 | settings = await setSettings($state.snapshot(settings)); |
| 214 | + savedSettings = structuredClone($state.snapshot(settings)); |
203 | 215 | } catch (e) { |
204 | 216 | error = (e as Error).message; |
205 | 217 | } finally { |
|
214 | 226 | try { |
215 | 227 | const previousLang = settings.ui_language; |
216 | 228 | settings = await setSettings($state.snapshot(settings)); |
| 229 | + savedSettings = structuredClone($state.snapshot(settings)); |
217 | 230 | birdaStatus = await checkBirda(); |
218 | 231 |
|
219 | 232 | // If UI language changed, reload to apply new locale |
|
238 | 251 | } |
239 | 252 |
|
240 | 253 | async function browseClipDir() { |
241 | | - const path = await openFolderDialog(); |
| 254 | + const path = await openFolderDialog(settings.clip_output_dir || undefined); |
242 | 255 | if (path) settings.clip_output_dir = path; |
243 | 256 | } |
244 | 257 |
|
|
263 | 276 | onDestroy(() => { |
264 | 277 | if (savedTimer) clearTimeout(savedTimer); |
265 | 278 | if (clearResultTimer) clearTimeout(clearResultTimer); |
| 279 | + appState.settingsHasUnsavedChanges = false; |
266 | 280 | }); |
267 | 281 | </script> |
268 | 282 |
|
|
437 | 451 | <span class="text-base-content/70 text-sm font-medium">{m.settings_storage_clipDir()}</span> |
438 | 452 | <div class="mt-1 flex gap-2"> |
439 | 453 | <input type="text" bind:value={settings.clip_output_dir} class="input input-bordered flex-1" /> |
| 454 | + <button |
| 455 | + onclick={() => settings.clip_output_dir && openInExplorer(settings.clip_output_dir)} |
| 456 | + disabled={!settings.clip_output_dir} |
| 457 | + class="btn btn-outline gap-1.5" |
| 458 | + title={m.settings_storage_openClipDir()} |
| 459 | + > |
| 460 | + <ExternalLink size={16} /> |
| 461 | + </button> |
440 | 462 | <button onclick={browseClipDir} class="btn btn-outline gap-1.5"> |
441 | 463 | <FolderOpen size={16} /> |
442 | 464 | {m.common_button_browse()} |
|
0 commit comments