@d34dman/flowdrop 0.0.43 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/api/enhanced-client.d.ts +3 -1
- package/dist/api/enhanced-client.js +35 -5
- package/dist/components/App.svelte +68 -34
- package/dist/components/ConfigForm.svelte +169 -142
- package/dist/components/ConfigForm.svelte.d.ts +4 -2
- package/dist/components/ConfigPanel.svelte +42 -15
- package/dist/components/LogsSidebar.svelte +20 -19
- package/dist/components/Navbar.svelte +150 -80
- package/dist/components/Navbar.svelte.d.ts +8 -0
- package/dist/components/NodeSidebar.svelte +334 -217
- package/dist/components/PipelineStatus.svelte +6 -1
- package/dist/components/ReadOnlyDetails.svelte +14 -14
- package/dist/components/SchemaForm.svelte +49 -30
- package/dist/components/SchemaForm.svelte.d.ts +11 -1
- package/dist/components/SettingsModal.svelte +279 -0
- package/dist/components/SettingsModal.svelte.d.ts +23 -0
- package/dist/components/SettingsPanel.svelte +615 -0
- package/dist/components/SettingsPanel.svelte.d.ts +21 -0
- package/dist/components/ThemeToggle.svelte +186 -0
- package/dist/components/ThemeToggle.svelte.d.ts +14 -0
- package/dist/components/WorkflowEditor.svelte +114 -38
- package/dist/components/form/FormArray.svelte +81 -81
- package/dist/components/form/FormAutocomplete.svelte +1014 -0
- package/dist/components/form/FormAutocomplete.svelte.d.ts +25 -0
- package/dist/components/form/FormCheckboxGroup.svelte +16 -16
- package/dist/components/form/FormCodeEditor.svelte +26 -26
- package/dist/components/form/FormField.svelte +52 -21
- package/dist/components/form/FormFieldLight.svelte +19 -19
- package/dist/components/form/FormFieldWrapper.svelte +4 -4
- package/dist/components/form/FormMarkdownEditor.svelte +124 -57
- package/dist/components/form/FormNumberField.svelte +13 -13
- package/dist/components/form/FormRangeField.svelte +16 -16
- package/dist/components/form/FormSelect.svelte +15 -15
- package/dist/components/form/FormTemplateEditor.svelte +34 -34
- package/dist/components/form/FormTextField.svelte +13 -13
- package/dist/components/form/FormTextarea.svelte +13 -13
- package/dist/components/form/FormToggle.svelte +8 -8
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/types.d.ts +133 -8
- package/dist/components/form/types.js +50 -1
- package/dist/components/interrupt/ChoicePrompt.svelte +45 -38
- package/dist/components/interrupt/ConfirmationPrompt.svelte +35 -35
- package/dist/components/interrupt/FormPrompt.svelte +27 -20
- package/dist/components/interrupt/InterruptBubble.svelte +50 -50
- package/dist/components/interrupt/TextInputPrompt.svelte +39 -32
- package/dist/components/layouts/MainLayout.svelte +233 -34
- package/dist/components/layouts/MainLayout.svelte.d.ts +12 -0
- package/dist/components/nodes/GatewayNode.svelte +175 -125
- package/dist/components/nodes/IdeaNode.svelte +70 -84
- package/dist/components/nodes/NotesNode.svelte +124 -88
- package/dist/components/nodes/SimpleNode.svelte +91 -69
- package/dist/components/nodes/SquareNode.svelte +102 -75
- package/dist/components/nodes/TerminalNode.svelte +127 -113
- package/dist/components/nodes/ToolNode.svelte +125 -76
- package/dist/components/nodes/WorkflowNode.svelte +164 -108
- package/dist/components/playground/ChatPanel.svelte +76 -76
- package/dist/components/playground/ExecutionLogs.svelte +71 -69
- package/dist/components/playground/InputCollector.svelte +59 -59
- package/dist/components/playground/MessageBubble.svelte +111 -112
- package/dist/components/playground/Playground.svelte +184 -138
- package/dist/components/playground/PlaygroundModal.svelte +18 -19
- package/dist/components/playground/SessionManager.svelte +68 -67
- package/dist/config/defaultPortConfig.js +22 -22
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/form/fieldRegistry.d.ts +17 -1
- package/dist/form/fieldRegistry.js +18 -2
- package/dist/form/index.d.ts +20 -2
- package/dist/form/index.js +19 -1
- package/dist/helpers/nodeLayoutHelper.d.ts +14 -0
- package/dist/helpers/nodeLayoutHelper.js +19 -0
- package/dist/helpers/workflowEditorHelper.js +23 -11
- package/dist/index.d.ts +5 -0
- package/dist/index.js +13 -0
- package/dist/services/autoSaveService.d.ts +112 -0
- package/dist/services/autoSaveService.js +223 -0
- package/dist/services/settingsService.d.ts +92 -0
- package/dist/services/settingsService.js +202 -0
- package/dist/services/toastService.d.ts +9 -0
- package/dist/services/toastService.js +30 -1
- package/dist/stores/settingsStore.d.ts +128 -0
- package/dist/stores/settingsStore.js +488 -0
- package/dist/stores/themeStore.d.ts +68 -0
- package/dist/stores/themeStore.js +215 -0
- package/dist/styles/base.css +338 -621
- package/dist/styles/toast.css +33 -0
- package/dist/styles/tokens.css +402 -0
- package/dist/types/index.d.ts +78 -0
- package/dist/types/index.js +2 -0
- package/dist/types/playground.d.ts +12 -0
- package/dist/types/settings.d.ts +185 -0
- package/dist/types/settings.js +101 -0
- package/dist/utils/colors.d.ts +100 -7
- package/dist/utils/colors.js +228 -67
- package/package.json +2 -2
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Store for FlowDrop
|
|
3
|
+
*
|
|
4
|
+
* Provides unified state management for all user-configurable settings with:
|
|
5
|
+
* - Hybrid persistence (localStorage primary, optional API sync)
|
|
6
|
+
* - Category-specific derived stores for performance
|
|
7
|
+
* - Deep merge support for partial updates
|
|
8
|
+
* - Theme system compatibility (backward compatible with themeStore)
|
|
9
|
+
*
|
|
10
|
+
* @module stores/settingsStore
|
|
11
|
+
*/
|
|
12
|
+
import { writable, derived, get } from "svelte/store";
|
|
13
|
+
import { DEFAULT_SETTINGS, SETTINGS_STORAGE_KEY } from "../types/settings.js";
|
|
14
|
+
// =========================================================================
|
|
15
|
+
// Internal State
|
|
16
|
+
// =========================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Settings service reference (set via setSettingsService)
|
|
19
|
+
* Allows API sync without circular dependencies
|
|
20
|
+
*/
|
|
21
|
+
let settingsService = null;
|
|
22
|
+
/**
|
|
23
|
+
* Change listeners for external subscribers
|
|
24
|
+
*/
|
|
25
|
+
const changeListeners = new Set();
|
|
26
|
+
// =========================================================================
|
|
27
|
+
// localStorage Persistence
|
|
28
|
+
// =========================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Load settings from localStorage
|
|
31
|
+
*
|
|
32
|
+
* @returns Saved settings or null if not found/invalid
|
|
33
|
+
*/
|
|
34
|
+
function loadFromStorage() {
|
|
35
|
+
if (typeof window === "undefined") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const saved = localStorage.getItem(SETTINGS_STORAGE_KEY);
|
|
40
|
+
if (saved) {
|
|
41
|
+
const parsed = JSON.parse(saved);
|
|
42
|
+
// Deep merge with defaults to handle missing properties
|
|
43
|
+
return deepMergeSettings(DEFAULT_SETTINGS, parsed);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.warn("Failed to load settings from localStorage:", error);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Save settings to localStorage
|
|
53
|
+
*
|
|
54
|
+
* @param settings - Settings to persist
|
|
55
|
+
*/
|
|
56
|
+
function saveToStorage(settings) {
|
|
57
|
+
if (typeof window === "undefined") {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.warn("Failed to save settings to localStorage:", error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// =========================================================================
|
|
68
|
+
// Deep Merge Utility
|
|
69
|
+
// =========================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Deep merge settings objects
|
|
72
|
+
*
|
|
73
|
+
* @param target - Target object (defaults)
|
|
74
|
+
* @param source - Source object (partial updates)
|
|
75
|
+
* @returns Merged settings
|
|
76
|
+
*/
|
|
77
|
+
function deepMergeSettings(target, source) {
|
|
78
|
+
const result = {
|
|
79
|
+
theme: { ...target.theme },
|
|
80
|
+
editor: { ...target.editor },
|
|
81
|
+
ui: { ...target.ui },
|
|
82
|
+
behavior: { ...target.behavior },
|
|
83
|
+
api: { ...target.api }
|
|
84
|
+
};
|
|
85
|
+
// Merge theme settings
|
|
86
|
+
if (source.theme) {
|
|
87
|
+
result.theme = { ...result.theme, ...source.theme };
|
|
88
|
+
}
|
|
89
|
+
// Merge editor settings
|
|
90
|
+
if (source.editor) {
|
|
91
|
+
result.editor = { ...result.editor, ...source.editor };
|
|
92
|
+
}
|
|
93
|
+
// Merge UI settings
|
|
94
|
+
if (source.ui) {
|
|
95
|
+
result.ui = { ...result.ui, ...source.ui };
|
|
96
|
+
}
|
|
97
|
+
// Merge behavior settings
|
|
98
|
+
if (source.behavior) {
|
|
99
|
+
result.behavior = { ...result.behavior, ...source.behavior };
|
|
100
|
+
}
|
|
101
|
+
// Merge API settings
|
|
102
|
+
if (source.api) {
|
|
103
|
+
result.api = { ...result.api, ...source.api };
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
// =========================================================================
|
|
108
|
+
// Main Settings Store
|
|
109
|
+
// =========================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Initial state loaded from localStorage or defaults
|
|
112
|
+
*/
|
|
113
|
+
const initialSettings = loadFromStorage() ?? DEFAULT_SETTINGS;
|
|
114
|
+
/**
|
|
115
|
+
* Main settings store state
|
|
116
|
+
*/
|
|
117
|
+
const storeState = writable({
|
|
118
|
+
settings: initialSettings,
|
|
119
|
+
initialized: true,
|
|
120
|
+
syncStatus: "idle",
|
|
121
|
+
lastSyncedAt: null,
|
|
122
|
+
syncError: null
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* Main settings store (read-only access to current settings)
|
|
126
|
+
*/
|
|
127
|
+
export const settingsStore = derived(storeState, ($state) => $state.settings);
|
|
128
|
+
/**
|
|
129
|
+
* Sync status store for UI indicators
|
|
130
|
+
*/
|
|
131
|
+
export const syncStatusStore = derived(storeState, ($state) => ({
|
|
132
|
+
status: $state.syncStatus,
|
|
133
|
+
lastSyncedAt: $state.lastSyncedAt,
|
|
134
|
+
error: $state.syncError
|
|
135
|
+
}));
|
|
136
|
+
// =========================================================================
|
|
137
|
+
// Category-Specific Derived Stores
|
|
138
|
+
// =========================================================================
|
|
139
|
+
/**
|
|
140
|
+
* Theme settings store
|
|
141
|
+
*/
|
|
142
|
+
export const themeSettings = derived(settingsStore, ($settings) => $settings.theme);
|
|
143
|
+
/**
|
|
144
|
+
* Editor settings store
|
|
145
|
+
*/
|
|
146
|
+
export const editorSettings = derived(settingsStore, ($settings) => $settings.editor);
|
|
147
|
+
/**
|
|
148
|
+
* UI settings store
|
|
149
|
+
*/
|
|
150
|
+
export const uiSettings = derived(settingsStore, ($settings) => $settings.ui);
|
|
151
|
+
/**
|
|
152
|
+
* Behavior settings store
|
|
153
|
+
*/
|
|
154
|
+
export const behaviorSettings = derived(settingsStore, ($settings) => $settings.behavior);
|
|
155
|
+
/**
|
|
156
|
+
* API settings store
|
|
157
|
+
*/
|
|
158
|
+
export const apiSettings = derived(settingsStore, ($settings) => $settings.api);
|
|
159
|
+
// =========================================================================
|
|
160
|
+
// Theme Compatibility (for themeStore migration)
|
|
161
|
+
// =========================================================================
|
|
162
|
+
/**
|
|
163
|
+
* System theme preference store
|
|
164
|
+
* Updates when system preference changes
|
|
165
|
+
*/
|
|
166
|
+
const systemTheme = writable(typeof window !== "undefined" ? getSystemTheme() : "light");
|
|
167
|
+
/**
|
|
168
|
+
* Get the system's color scheme preference
|
|
169
|
+
*/
|
|
170
|
+
function getSystemTheme() {
|
|
171
|
+
if (typeof window === "undefined") {
|
|
172
|
+
return "light";
|
|
173
|
+
}
|
|
174
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
175
|
+
? "dark"
|
|
176
|
+
: "light";
|
|
177
|
+
}
|
|
178
|
+
// Listen for system theme changes
|
|
179
|
+
if (typeof window !== "undefined") {
|
|
180
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
181
|
+
const handleSystemThemeChange = (event) => {
|
|
182
|
+
systemTheme.set(event.matches ? "dark" : "light");
|
|
183
|
+
};
|
|
184
|
+
if (mediaQuery.addEventListener) {
|
|
185
|
+
mediaQuery.addEventListener("change", handleSystemThemeChange);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Fallback for older browsers
|
|
189
|
+
mediaQuery.addListener(handleSystemThemeChange);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Theme preference store (backward compatible with themeStore)
|
|
194
|
+
*/
|
|
195
|
+
export const theme = derived(themeSettings, ($theme) => $theme.preference);
|
|
196
|
+
/**
|
|
197
|
+
* Resolved theme store
|
|
198
|
+
* Always returns the actual theme being applied ('light' or 'dark')
|
|
199
|
+
*/
|
|
200
|
+
export const resolvedTheme = derived([themeSettings, systemTheme], ([$themeSettings, $systemTheme]) => {
|
|
201
|
+
if ($themeSettings.preference === "auto") {
|
|
202
|
+
return $systemTheme;
|
|
203
|
+
}
|
|
204
|
+
return $themeSettings.preference;
|
|
205
|
+
});
|
|
206
|
+
// =========================================================================
|
|
207
|
+
// Settings Update Functions
|
|
208
|
+
// =========================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Notify change listeners about a settings change
|
|
211
|
+
*/
|
|
212
|
+
function notifyChange(category, key, previousValue, newValue) {
|
|
213
|
+
const event = {
|
|
214
|
+
category,
|
|
215
|
+
key,
|
|
216
|
+
previousValue,
|
|
217
|
+
newValue
|
|
218
|
+
};
|
|
219
|
+
changeListeners.forEach((listener) => {
|
|
220
|
+
try {
|
|
221
|
+
listener(event);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error("Settings change listener error:", error);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get category settings as a plain object for comparison
|
|
230
|
+
*/
|
|
231
|
+
function getCategoryAsRecord(settings, category) {
|
|
232
|
+
return Object.fromEntries(Object.entries(settings[category]));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Update settings with partial values
|
|
236
|
+
*
|
|
237
|
+
* @param partial - Partial settings to merge
|
|
238
|
+
*/
|
|
239
|
+
export function updateSettings(partial) {
|
|
240
|
+
storeState.update((state) => {
|
|
241
|
+
const previousSettings = state.settings;
|
|
242
|
+
const newSettings = deepMergeSettings(state.settings, partial);
|
|
243
|
+
// Persist to localStorage immediately
|
|
244
|
+
saveToStorage(newSettings);
|
|
245
|
+
// Notify listeners for each changed category
|
|
246
|
+
for (const category of Object.keys(partial)) {
|
|
247
|
+
const partialCategory = partial[category];
|
|
248
|
+
if (partialCategory && typeof partialCategory === "object") {
|
|
249
|
+
for (const key of Object.keys(partialCategory)) {
|
|
250
|
+
const prevCat = getCategoryAsRecord(previousSettings, category);
|
|
251
|
+
const newCat = getCategoryAsRecord(newSettings, category);
|
|
252
|
+
if (prevCat[key] !== newCat[key]) {
|
|
253
|
+
notifyChange(category, key, prevCat[key], newCat[key]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
...state,
|
|
260
|
+
settings: newSettings
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Reset settings to defaults
|
|
266
|
+
*
|
|
267
|
+
* @param categories - Optional categories to reset (all if not specified)
|
|
268
|
+
*/
|
|
269
|
+
export function resetSettings(categories) {
|
|
270
|
+
if (categories && categories.length > 0) {
|
|
271
|
+
const partial = {};
|
|
272
|
+
for (const category of categories) {
|
|
273
|
+
partial[category] = DEFAULT_SETTINGS[category];
|
|
274
|
+
}
|
|
275
|
+
updateSettings(partial);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
storeState.update((state) => {
|
|
279
|
+
saveToStorage(DEFAULT_SETTINGS);
|
|
280
|
+
return {
|
|
281
|
+
...state,
|
|
282
|
+
settings: DEFAULT_SETTINGS
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get current settings synchronously
|
|
289
|
+
*/
|
|
290
|
+
export function getSettings() {
|
|
291
|
+
return get(settingsStore);
|
|
292
|
+
}
|
|
293
|
+
// =========================================================================
|
|
294
|
+
// Theme-Specific Functions (backward compatible with themeStore)
|
|
295
|
+
// =========================================================================
|
|
296
|
+
/**
|
|
297
|
+
* Set the theme preference
|
|
298
|
+
*
|
|
299
|
+
* @param newTheme - The new theme preference ('light', 'dark', or 'auto')
|
|
300
|
+
*/
|
|
301
|
+
export function setTheme(newTheme) {
|
|
302
|
+
updateSettings({ theme: { preference: newTheme } });
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Toggle between light and dark themes
|
|
306
|
+
* If currently 'auto', switches to the opposite of system preference
|
|
307
|
+
*/
|
|
308
|
+
export function toggleTheme() {
|
|
309
|
+
const currentTheme = get(theme);
|
|
310
|
+
const currentResolved = get(resolvedTheme);
|
|
311
|
+
if (currentTheme === "auto") {
|
|
312
|
+
setTheme(currentResolved === "dark" ? "light" : "dark");
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
setTheme(currentTheme === "dark" ? "light" : "dark");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Cycle through theme options: light -> dark -> auto -> light
|
|
320
|
+
*/
|
|
321
|
+
export function cycleTheme() {
|
|
322
|
+
const currentTheme = get(theme);
|
|
323
|
+
switch (currentTheme) {
|
|
324
|
+
case "light":
|
|
325
|
+
setTheme("dark");
|
|
326
|
+
break;
|
|
327
|
+
case "dark":
|
|
328
|
+
setTheme("auto");
|
|
329
|
+
break;
|
|
330
|
+
case "auto":
|
|
331
|
+
setTheme("light");
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Apply theme to the document
|
|
337
|
+
*
|
|
338
|
+
* @param resolved - The resolved theme to apply
|
|
339
|
+
*/
|
|
340
|
+
function applyTheme(resolved) {
|
|
341
|
+
if (typeof document === "undefined") {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
document.documentElement.setAttribute("data-theme", resolved);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Initialize the theme system
|
|
348
|
+
* Should be called once on app startup
|
|
349
|
+
*/
|
|
350
|
+
export function initializeTheme() {
|
|
351
|
+
const resolved = get(resolvedTheme);
|
|
352
|
+
applyTheme(resolved);
|
|
353
|
+
// Subscribe to resolved theme changes and apply them
|
|
354
|
+
resolvedTheme.subscribe((theme) => {
|
|
355
|
+
applyTheme(theme);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
// =========================================================================
|
|
359
|
+
// API Sync Functions
|
|
360
|
+
// =========================================================================
|
|
361
|
+
/**
|
|
362
|
+
* Set the settings service for API operations
|
|
363
|
+
*
|
|
364
|
+
* @param service - Settings service instance
|
|
365
|
+
*/
|
|
366
|
+
export function setSettingsService(service) {
|
|
367
|
+
settingsService = service;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Sync current settings to the backend API
|
|
371
|
+
*
|
|
372
|
+
* @returns Promise that resolves when sync is complete
|
|
373
|
+
*/
|
|
374
|
+
export async function syncSettingsToApi() {
|
|
375
|
+
if (!settingsService) {
|
|
376
|
+
console.warn("Settings service not configured for API sync");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
storeState.update((state) => ({
|
|
380
|
+
...state,
|
|
381
|
+
syncStatus: "syncing",
|
|
382
|
+
syncError: null
|
|
383
|
+
}));
|
|
384
|
+
try {
|
|
385
|
+
const currentSettings = get(settingsStore);
|
|
386
|
+
await settingsService.savePreferences(currentSettings);
|
|
387
|
+
storeState.update((state) => ({
|
|
388
|
+
...state,
|
|
389
|
+
syncStatus: "synced",
|
|
390
|
+
lastSyncedAt: Date.now(),
|
|
391
|
+
syncError: null
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to sync settings";
|
|
396
|
+
storeState.update((state) => ({
|
|
397
|
+
...state,
|
|
398
|
+
syncStatus: "error",
|
|
399
|
+
syncError: errorMessage
|
|
400
|
+
}));
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Load settings from the backend API
|
|
406
|
+
*
|
|
407
|
+
* @returns Promise that resolves when load is complete
|
|
408
|
+
*/
|
|
409
|
+
export async function loadSettingsFromApi() {
|
|
410
|
+
if (!settingsService) {
|
|
411
|
+
console.warn("Settings service not configured for API sync");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
storeState.update((state) => ({
|
|
415
|
+
...state,
|
|
416
|
+
syncStatus: "syncing",
|
|
417
|
+
syncError: null
|
|
418
|
+
}));
|
|
419
|
+
try {
|
|
420
|
+
const apiSettings = await settingsService.getPreferences();
|
|
421
|
+
const mergedSettings = deepMergeSettings(DEFAULT_SETTINGS, apiSettings);
|
|
422
|
+
storeState.update((state) => ({
|
|
423
|
+
...state,
|
|
424
|
+
settings: mergedSettings,
|
|
425
|
+
syncStatus: "synced",
|
|
426
|
+
lastSyncedAt: Date.now(),
|
|
427
|
+
syncError: null
|
|
428
|
+
}));
|
|
429
|
+
// Also persist to localStorage
|
|
430
|
+
saveToStorage(mergedSettings);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to load settings from API";
|
|
434
|
+
storeState.update((state) => ({
|
|
435
|
+
...state,
|
|
436
|
+
syncStatus: "error",
|
|
437
|
+
syncError: errorMessage
|
|
438
|
+
}));
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// =========================================================================
|
|
443
|
+
// Change Listeners
|
|
444
|
+
// =========================================================================
|
|
445
|
+
/**
|
|
446
|
+
* Subscribe to settings changes
|
|
447
|
+
*
|
|
448
|
+
* @param callback - Function to call when settings change
|
|
449
|
+
* @returns Unsubscribe function
|
|
450
|
+
*/
|
|
451
|
+
export function onSettingsChange(callback) {
|
|
452
|
+
changeListeners.add(callback);
|
|
453
|
+
return () => {
|
|
454
|
+
changeListeners.delete(callback);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
// =========================================================================
|
|
458
|
+
// Initialization
|
|
459
|
+
// =========================================================================
|
|
460
|
+
/**
|
|
461
|
+
* Initialize the settings system
|
|
462
|
+
*
|
|
463
|
+
* @param options - Initialization options
|
|
464
|
+
*/
|
|
465
|
+
export async function initializeSettings(options) {
|
|
466
|
+
// Apply custom defaults if provided
|
|
467
|
+
if (options?.defaults) {
|
|
468
|
+
const currentSettings = get(settingsStore);
|
|
469
|
+
const withDefaults = deepMergeSettings(currentSettings, options.defaults);
|
|
470
|
+
storeState.update((state) => ({
|
|
471
|
+
...state,
|
|
472
|
+
settings: withDefaults
|
|
473
|
+
}));
|
|
474
|
+
saveToStorage(withDefaults);
|
|
475
|
+
}
|
|
476
|
+
// Initialize theme system
|
|
477
|
+
initializeTheme();
|
|
478
|
+
// Optionally sync with API
|
|
479
|
+
if (options?.apiSync && settingsService) {
|
|
480
|
+
try {
|
|
481
|
+
await loadSettingsFromApi();
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Silently fail - local settings are still available
|
|
485
|
+
console.warn("Failed to sync settings from API on initialization");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Store for FlowDrop
|
|
3
|
+
*
|
|
4
|
+
* @deprecated This module is deprecated. Use `settingsStore` instead.
|
|
5
|
+
*
|
|
6
|
+
* The theme functionality has been moved to the unified settings system.
|
|
7
|
+
* Import from settingsStore for new code:
|
|
8
|
+
*
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { theme, resolvedTheme, setTheme, toggleTheme, cycleTheme } from "./settingsStore.js";
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* This module re-exports from settingsStore for backward compatibility.
|
|
14
|
+
*
|
|
15
|
+
* Provides reactive theme state management with:
|
|
16
|
+
* - Three theme modes: 'light', 'dark', 'auto' (follows system preference)
|
|
17
|
+
* - localStorage persistence
|
|
18
|
+
* - System preference detection via matchMedia
|
|
19
|
+
* - Automatic data-theme attribute application
|
|
20
|
+
*
|
|
21
|
+
* @module stores/themeStore
|
|
22
|
+
*/
|
|
23
|
+
/** Theme preference options */
|
|
24
|
+
export type ThemePreference = "light" | "dark" | "auto";
|
|
25
|
+
/** Resolved theme (actual applied theme, never 'auto') */
|
|
26
|
+
export type ResolvedTheme = "light" | "dark";
|
|
27
|
+
/**
|
|
28
|
+
* User's theme preference store
|
|
29
|
+
* Can be 'light', 'dark', or 'auto'
|
|
30
|
+
*/
|
|
31
|
+
export declare const theme: import("svelte/store").Writable<ThemePreference>;
|
|
32
|
+
/**
|
|
33
|
+
* Resolved theme store
|
|
34
|
+
* Always returns the actual theme being applied ('light' or 'dark')
|
|
35
|
+
* When theme is 'auto', this reflects the system preference
|
|
36
|
+
*/
|
|
37
|
+
export declare const resolvedTheme: import("svelte/store").Readable<ResolvedTheme>;
|
|
38
|
+
/**
|
|
39
|
+
* Set the theme preference
|
|
40
|
+
*
|
|
41
|
+
* @param newTheme - The new theme preference ('light', 'dark', or 'auto')
|
|
42
|
+
*/
|
|
43
|
+
export declare function setTheme(newTheme: ThemePreference): void;
|
|
44
|
+
/**
|
|
45
|
+
* Toggle between light and dark themes
|
|
46
|
+
* If currently 'auto', switches to the opposite of system preference
|
|
47
|
+
*/
|
|
48
|
+
export declare function toggleTheme(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Cycle through theme options: light -> dark -> auto -> light
|
|
51
|
+
*/
|
|
52
|
+
export declare function cycleTheme(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the theme system
|
|
55
|
+
* Should be called once on app startup
|
|
56
|
+
*
|
|
57
|
+
* This function:
|
|
58
|
+
* 1. Applies the current resolved theme to the document
|
|
59
|
+
* 2. Sets up reactivity to apply theme changes
|
|
60
|
+
*/
|
|
61
|
+
export declare function initializeTheme(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Check if theme system is initialized
|
|
64
|
+
* Useful for SSR scenarios
|
|
65
|
+
*
|
|
66
|
+
* @returns true if running in browser and theme is applied
|
|
67
|
+
*/
|
|
68
|
+
export declare function isThemeInitialized(): boolean;
|