flexdesk 0.1.0
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/LICENSE +21 -0
- package/README.md +195 -0
- package/THIRD_PARTY_LICENSES.md +53 -0
- package/css/base.css +3439 -0
- package/css/flexdesk.css +5797 -0
- package/css/overrides.css +2349 -0
- package/css/reset.css +46 -0
- package/css/tokens.css +137 -0
- package/dist/charts.js +780 -0
- package/dist/charts.js.map +7 -0
- package/dist/chunk-3PHCPZHT.js +222 -0
- package/dist/chunk-3PHCPZHT.js.map +7 -0
- package/dist/chunk-CT4YXXLP.js +1770 -0
- package/dist/chunk-CT4YXXLP.js.map +7 -0
- package/dist/chunk-DRYCDMEG.js +2217 -0
- package/dist/chunk-DRYCDMEG.js.map +7 -0
- package/dist/chunk-DVU44T77.js +855 -0
- package/dist/chunk-DVU44T77.js.map +7 -0
- package/dist/chunk-FL5KFNQH.js +640 -0
- package/dist/chunk-FL5KFNQH.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +117 -0
- package/dist/chunk-FOOS3T5L.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +32 -0
- package/dist/chunk-JYWURG5T.js.map +7 -0
- package/dist/chunk-TLZUUFOE.js +276 -0
- package/dist/chunk-TLZUUFOE.js.map +7 -0
- package/dist/chunk-UCJ2WD4D.js +625 -0
- package/dist/chunk-UCJ2WD4D.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +205 -0
- package/dist/chunk-WVFGV5FT.js.map +7 -0
- package/dist/core.js +1098 -0
- package/dist/core.js.map +7 -0
- package/dist/editor.js +1661 -0
- package/dist/editor.js.map +7 -0
- package/dist/flexdesk.css +5797 -0
- package/dist/host.js +132 -0
- package/dist/host.js.map +7 -0
- package/dist/reset.css +46 -0
- package/dist/tile_registry-6WZPTUZV.js +28 -0
- package/dist/tile_registry-6WZPTUZV.js.map +7 -0
- package/dist/tiles.js +2068 -0
- package/dist/tiles.js.map +7 -0
- package/dist/tokens.css +137 -0
- package/dist/widgets.js +5438 -0
- package/dist/widgets.js.map +7 -0
- package/dist/wm.js +4830 -0
- package/dist/wm.js.map +7 -0
- package/package.json +80 -0
- package/src/charts/chart_types.js +699 -0
- package/src/charts/data_series_plot_window.js +761 -0
- package/src/charts/downsample.js +119 -0
- package/src/charts/plot_config.js +176 -0
- package/src/charts/plot_popout_window.js +1560 -0
- package/src/charts/plotly_wrapper.js +432 -0
- package/src/core/event_bus.js +230 -0
- package/src/core/logging.js +300 -0
- package/src/core/settings.js +813 -0
- package/src/core/state_guard.js +382 -0
- package/src/core/state_guard_config.js +309 -0
- package/src/core/state_machine.js +258 -0
- package/src/editor/editor_pane.js +264 -0
- package/src/editor/monaco_editor_factory.js +449 -0
- package/src/editor/monaco_loader.js +74 -0
- package/src/editor/notebook_search.js +460 -0
- package/src/editor/notebook_tab_bar.js +456 -0
- package/src/editor/notebook_undo_manager.js +108 -0
- package/src/editor/split_pane_container.js +197 -0
- package/src/help/help_modal.js +409 -0
- package/src/help/help_registry.js +65 -0
- package/src/host/host.js +69 -0
- package/src/host/pywebview_host.js +133 -0
- package/src/tiles/config_schema.js +256 -0
- package/src/tiles/layout_persistence.js +662 -0
- package/src/tiles/tile_base.js +785 -0
- package/src/tiles/tile_grid.js +905 -0
- package/src/tiles/tile_registry.js +136 -0
- package/src/tiling/command_palette.js +328 -0
- package/src/tiling/content_registry.js +110 -0
- package/src/tiling/desktops.js +133 -0
- package/src/tiling/entity_sources.js +179 -0
- package/src/tiling/keymap.js +156 -0
- package/src/tiling/kind_taxonomy.js +172 -0
- package/src/tiling/landing_table.js +717 -0
- package/src/tiling/loading_overlay.js +33 -0
- package/src/tiling/nav_panel.js +142 -0
- package/src/tiling/page_factory.js +215 -0
- package/src/tiling/panel_keys.js +145 -0
- package/src/tiling/shell.js +437 -0
- package/src/tiling/tile_breadcrumb.js +222 -0
- package/src/tiling/tile_renderer.js +459 -0
- package/src/tiling/tile_tab_menu.js +340 -0
- package/src/tiling/tile_tree.js +880 -0
- package/src/tiling/wm.js +1352 -0
- package/src/ui/base/component_base.js +30 -0
- package/src/ui/base/controller_base.js +48 -0
- package/src/ui/base/page_base.js +102 -0
- package/src/ui/components/about_dialog.js +224 -0
- package/src/ui/components/action_dropdown.js +389 -0
- package/src/ui/components/attribute_list_editor.js +652 -0
- package/src/ui/components/autocomplete_field.js +732 -0
- package/src/ui/components/computing_status_window.js +81 -0
- package/src/ui/components/confirm_dialog.js +307 -0
- package/src/ui/components/context_menu.js +87 -0
- package/src/ui/components/data_table.js +2228 -0
- package/src/ui/components/detail_header.js +177 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/gallery_picker.js +147 -0
- package/src/ui/components/inline_renamer.js +129 -0
- package/src/ui/components/managed_window.js +804 -0
- package/src/ui/components/modal.js +512 -0
- package/src/ui/components/notification_history.js +264 -0
- package/src/ui/components/slide_out_panel.js +185 -0
- package/src/ui/components/slider_field.js +122 -0
- package/src/ui/components/sortable_list.js +309 -0
- package/src/ui/components/table_state_store.js +81 -0
- package/src/ui/components/toast.js +59 -0
- package/src/ui/components/tree_view.js +460 -0
- package/src/ui/components/window_taskbar.js +143 -0
- package/src/ui/controllers/panel_state_machine.js +634 -0
- package/src/ui/controllers/window_chrome_controller.js +659 -0
- package/src/ui/notification_center.js +576 -0
- package/src/ui/utils/overlay_scrollbar.js +492 -0
- package/src/ui/utils/raf_resize_observer.js +31 -0
- package/src/ui/utils/tooltip_service.js +406 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
// src/core/settings.js
|
|
2
|
+
var DEFAULTS = Object.freeze({
|
|
3
|
+
workspace: Object.freeze({
|
|
4
|
+
save: Object.freeze({
|
|
5
|
+
showToast: false,
|
|
6
|
+
showErrorToast: true
|
|
7
|
+
}),
|
|
8
|
+
import: Object.freeze({
|
|
9
|
+
showToast: false,
|
|
10
|
+
showErrorToast: true
|
|
11
|
+
}),
|
|
12
|
+
autosave: Object.freeze({
|
|
13
|
+
enabled: true,
|
|
14
|
+
intervalSeconds: 60
|
|
15
|
+
}),
|
|
16
|
+
undoHistoryLimit: 200
|
|
17
|
+
}),
|
|
18
|
+
host: Object.freeze({
|
|
19
|
+
showConnectedToast: false
|
|
20
|
+
}),
|
|
21
|
+
window: Object.freeze({
|
|
22
|
+
animateMinimize: true,
|
|
23
|
+
macShadows: true
|
|
24
|
+
}),
|
|
25
|
+
notifications: Object.freeze({
|
|
26
|
+
durationMs: 3500
|
|
27
|
+
}),
|
|
28
|
+
data: Object.freeze({
|
|
29
|
+
tablePageSize: 100,
|
|
30
|
+
defaultResampleMethod: "mean"
|
|
31
|
+
}),
|
|
32
|
+
etl: Object.freeze({
|
|
33
|
+
parallelWorkers: 1
|
|
34
|
+
}),
|
|
35
|
+
logging: Object.freeze({
|
|
36
|
+
minLevel: "info",
|
|
37
|
+
historyLimit: 500,
|
|
38
|
+
enableConsole: false
|
|
39
|
+
}),
|
|
40
|
+
editor: Object.freeze({
|
|
41
|
+
autosaveDelayMs: 5e3
|
|
42
|
+
}),
|
|
43
|
+
notebook: Object.freeze({
|
|
44
|
+
cellWidthMode: "fixed",
|
|
45
|
+
codeCellMaxHeight: false,
|
|
46
|
+
defaultCellView: "config"
|
|
47
|
+
}),
|
|
48
|
+
debug: Object.freeze({
|
|
49
|
+
logSettingsAccess: false
|
|
50
|
+
}),
|
|
51
|
+
projects: Object.freeze({
|
|
52
|
+
directory: null
|
|
53
|
+
}),
|
|
54
|
+
// Only auto-create-defaults survives; the modules directory / addon /
|
|
55
|
+
// ETL-plugin loaders were the node-graph plugin system.
|
|
56
|
+
ai: Object.freeze({
|
|
57
|
+
provider: "",
|
|
58
|
+
cloud: Object.freeze({
|
|
59
|
+
providerId: "anthropic",
|
|
60
|
+
providers: Object.freeze({})
|
|
61
|
+
}),
|
|
62
|
+
local: Object.freeze({
|
|
63
|
+
serverType: "llamacpp",
|
|
64
|
+
modelPath: "",
|
|
65
|
+
baseUrl: "",
|
|
66
|
+
model: "",
|
|
67
|
+
port: 8080,
|
|
68
|
+
gpuLayers: -1,
|
|
69
|
+
contextLength: 32768,
|
|
70
|
+
flashAttention: true,
|
|
71
|
+
evalBatchSize: 512,
|
|
72
|
+
kvCacheOnGpu: true
|
|
73
|
+
}),
|
|
74
|
+
defaultMode: "ask",
|
|
75
|
+
maxToolCalls: 25
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
var SCHEMA = Object.freeze({
|
|
79
|
+
// ── General ──────────────────────────────────────────────────────────────
|
|
80
|
+
"workspace.save.showToast": {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
category: "general",
|
|
83
|
+
group: "Workspace Notifications",
|
|
84
|
+
label: "Save success notification",
|
|
85
|
+
description: "Show a toast notification when a workspace is saved successfully.",
|
|
86
|
+
defaultValue: false
|
|
87
|
+
},
|
|
88
|
+
"workspace.save.showErrorToast": {
|
|
89
|
+
type: "boolean",
|
|
90
|
+
category: "general",
|
|
91
|
+
group: "Workspace Notifications",
|
|
92
|
+
label: "Save error notification",
|
|
93
|
+
description: "Show a toast notification when a workspace save fails.",
|
|
94
|
+
defaultValue: true
|
|
95
|
+
},
|
|
96
|
+
"workspace.import.showToast": {
|
|
97
|
+
type: "boolean",
|
|
98
|
+
category: "general",
|
|
99
|
+
group: "Workspace Notifications",
|
|
100
|
+
label: "Import success notification",
|
|
101
|
+
description: "Show a toast notification when a workspace is imported successfully.",
|
|
102
|
+
defaultValue: false
|
|
103
|
+
},
|
|
104
|
+
"workspace.import.showErrorToast": {
|
|
105
|
+
type: "boolean",
|
|
106
|
+
category: "general",
|
|
107
|
+
group: "Workspace Notifications",
|
|
108
|
+
label: "Import error notification",
|
|
109
|
+
description: "Show a toast notification when a workspace import fails.",
|
|
110
|
+
defaultValue: true
|
|
111
|
+
},
|
|
112
|
+
"host.showConnectedToast": {
|
|
113
|
+
type: "boolean",
|
|
114
|
+
category: "general",
|
|
115
|
+
group: "Host Bridge",
|
|
116
|
+
label: "Host connected notification",
|
|
117
|
+
description: "Show a toast notification when the desktop host bridge connects.",
|
|
118
|
+
defaultValue: false
|
|
119
|
+
},
|
|
120
|
+
"notifications.durationMs": {
|
|
121
|
+
type: "number",
|
|
122
|
+
category: "general",
|
|
123
|
+
group: "Notifications",
|
|
124
|
+
label: "Toast notification duration",
|
|
125
|
+
description: "How long toast notifications stay visible (milliseconds).",
|
|
126
|
+
defaultValue: 3500,
|
|
127
|
+
min: 1e3,
|
|
128
|
+
max: 15e3,
|
|
129
|
+
step: 500
|
|
130
|
+
},
|
|
131
|
+
"workspace.autosave.enabled": {
|
|
132
|
+
type: "boolean",
|
|
133
|
+
category: "general",
|
|
134
|
+
group: "Auto-Save",
|
|
135
|
+
label: "Enable auto-save",
|
|
136
|
+
description: "Automatically save the workspace at regular intervals.",
|
|
137
|
+
defaultValue: true
|
|
138
|
+
},
|
|
139
|
+
"workspace.autosave.intervalSeconds": {
|
|
140
|
+
type: "number",
|
|
141
|
+
category: "general",
|
|
142
|
+
group: "Auto-Save",
|
|
143
|
+
label: "Auto-save interval (seconds)",
|
|
144
|
+
description: "Time between automatic saves.",
|
|
145
|
+
defaultValue: 60,
|
|
146
|
+
min: 10,
|
|
147
|
+
max: 600,
|
|
148
|
+
step: 10
|
|
149
|
+
},
|
|
150
|
+
"workspace.undoHistoryLimit": {
|
|
151
|
+
type: "number",
|
|
152
|
+
category: "general",
|
|
153
|
+
group: "History",
|
|
154
|
+
label: "Undo/redo history depth",
|
|
155
|
+
description: "Maximum number of undo/redo steps retained in memory.",
|
|
156
|
+
defaultValue: 200,
|
|
157
|
+
min: 10,
|
|
158
|
+
max: 1e3,
|
|
159
|
+
step: 10
|
|
160
|
+
},
|
|
161
|
+
// ── Windows ──────────────────────────────────────────────────────────────
|
|
162
|
+
"window.animateMinimize": {
|
|
163
|
+
type: "boolean",
|
|
164
|
+
category: "window",
|
|
165
|
+
group: "Appearance",
|
|
166
|
+
label: "Animate minimize/restore",
|
|
167
|
+
description: "Animate managed windows toward/from the taskbar when minimizing and restoring.",
|
|
168
|
+
defaultValue: true
|
|
169
|
+
},
|
|
170
|
+
"window.macShadows": {
|
|
171
|
+
type: "boolean",
|
|
172
|
+
category: "window",
|
|
173
|
+
group: "Appearance",
|
|
174
|
+
label: "macOS-style shadows",
|
|
175
|
+
description: "Use multi-layered soft shadows on managed windows.",
|
|
176
|
+
defaultValue: true
|
|
177
|
+
},
|
|
178
|
+
// ── Data ─────────────────────────────────────────────────────────────────
|
|
179
|
+
"data.tablePageSize": {
|
|
180
|
+
type: "number",
|
|
181
|
+
category: "data",
|
|
182
|
+
group: "Tables",
|
|
183
|
+
label: "Table page size",
|
|
184
|
+
description: "Number of rows displayed per page in data tables.",
|
|
185
|
+
defaultValue: 100,
|
|
186
|
+
min: 25,
|
|
187
|
+
max: 1e3,
|
|
188
|
+
step: 25
|
|
189
|
+
},
|
|
190
|
+
"data.defaultResampleMethod": {
|
|
191
|
+
type: "select",
|
|
192
|
+
category: "data",
|
|
193
|
+
group: "Import",
|
|
194
|
+
label: "Default resample method",
|
|
195
|
+
description: "Aggregation method used when resampling imported time series.",
|
|
196
|
+
defaultValue: "mean",
|
|
197
|
+
options: [
|
|
198
|
+
{ value: "mean", label: "Mean" },
|
|
199
|
+
{ value: "sum", label: "Sum" },
|
|
200
|
+
{ value: "last", label: "Last" },
|
|
201
|
+
{ value: "first", label: "First" },
|
|
202
|
+
{ value: "linear", label: "Linear interpolation" }
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
"etl.parallelWorkers": {
|
|
206
|
+
type: "number",
|
|
207
|
+
category: "data",
|
|
208
|
+
group: "ETL Pipelines",
|
|
209
|
+
label: "Parallel pipeline workers",
|
|
210
|
+
description: "Number of pipelines to execute simultaneously in orchestrations. 1 = sequential.",
|
|
211
|
+
defaultValue: 1,
|
|
212
|
+
min: 1,
|
|
213
|
+
max: 8,
|
|
214
|
+
step: 1
|
|
215
|
+
},
|
|
216
|
+
// ── Logging ──────────────────────────────────────────────────────────────
|
|
217
|
+
"logging.minLevel": {
|
|
218
|
+
type: "select",
|
|
219
|
+
category: "logging",
|
|
220
|
+
group: "Output",
|
|
221
|
+
label: "Minimum log level",
|
|
222
|
+
description: "Only messages at this level or above are recorded.",
|
|
223
|
+
defaultValue: "info",
|
|
224
|
+
options: [
|
|
225
|
+
{ value: "trace", label: "Trace" },
|
|
226
|
+
{ value: "debug", label: "Debug" },
|
|
227
|
+
{ value: "info", label: "Info" },
|
|
228
|
+
{ value: "warn", label: "Warning" },
|
|
229
|
+
{ value: "error", label: "Error" },
|
|
230
|
+
{ value: "fatal", label: "Fatal" }
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"logging.enableConsole": {
|
|
234
|
+
type: "boolean",
|
|
235
|
+
category: "logging",
|
|
236
|
+
group: "Output",
|
|
237
|
+
label: "Enable console output",
|
|
238
|
+
description: "Mirror log messages to the browser console.",
|
|
239
|
+
defaultValue: false
|
|
240
|
+
},
|
|
241
|
+
"logging.historyLimit": {
|
|
242
|
+
type: "number",
|
|
243
|
+
category: "logging",
|
|
244
|
+
group: "History",
|
|
245
|
+
label: "Log history limit",
|
|
246
|
+
description: "Maximum number of log entries retained in memory.",
|
|
247
|
+
defaultValue: 500,
|
|
248
|
+
min: 50,
|
|
249
|
+
max: 1e4,
|
|
250
|
+
step: 50
|
|
251
|
+
},
|
|
252
|
+
// ── Projects ─────────────────────────────────────────────────────────────
|
|
253
|
+
"projects.directory": {
|
|
254
|
+
type: "text",
|
|
255
|
+
category: "general",
|
|
256
|
+
group: "Projects",
|
|
257
|
+
label: "Projects directory",
|
|
258
|
+
description: "Default directory for new projects and bundled demos. Leave empty for OS default.",
|
|
259
|
+
defaultValue: null,
|
|
260
|
+
placeholder: "OS default (%APPDATA%/EcoSim/projects)"
|
|
261
|
+
},
|
|
262
|
+
// ── AI Assistant ────────────────────────────────────────────────────────
|
|
263
|
+
// Provider, auth, model, and server settings are managed from the chat UI
|
|
264
|
+
// modals. Only behavior settings appear here.
|
|
265
|
+
"ai.defaultMode": {
|
|
266
|
+
type: "select",
|
|
267
|
+
category: "ai",
|
|
268
|
+
group: "Behavior",
|
|
269
|
+
label: "Default mode",
|
|
270
|
+
description: "Default interaction mode for the AI assistant.",
|
|
271
|
+
defaultValue: "ask",
|
|
272
|
+
options: [
|
|
273
|
+
{ value: "ask", label: "Ask (preview before applying)" },
|
|
274
|
+
{ value: "edit", label: "Edit (apply immediately)" }
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
"ai.maxToolCalls": {
|
|
278
|
+
type: "number",
|
|
279
|
+
category: "ai",
|
|
280
|
+
group: "Behavior",
|
|
281
|
+
label: "Max tool calls",
|
|
282
|
+
description: "Maximum number of tool calls per AI turn.",
|
|
283
|
+
defaultValue: 25,
|
|
284
|
+
min: 1,
|
|
285
|
+
max: 100,
|
|
286
|
+
step: 1
|
|
287
|
+
},
|
|
288
|
+
// ── Notebook ─────────────────────────────────────────────────────────────
|
|
289
|
+
"notebook.cellWidthMode": {
|
|
290
|
+
type: "select",
|
|
291
|
+
category: "notebook",
|
|
292
|
+
group: "Layout",
|
|
293
|
+
label: "Cell width mode",
|
|
294
|
+
description: 'Controls how wide all cells appear. "Fixed" constrains cells to a max-width; "Full" stretches all cells.',
|
|
295
|
+
defaultValue: "fixed",
|
|
296
|
+
options: [
|
|
297
|
+
{ value: "fixed", label: "Fixed width (900px)" },
|
|
298
|
+
{ value: "full", label: "Full width" }
|
|
299
|
+
]
|
|
300
|
+
},
|
|
301
|
+
"notebook.codeCellMaxHeight": {
|
|
302
|
+
type: "boolean",
|
|
303
|
+
category: "notebook",
|
|
304
|
+
group: "Layout",
|
|
305
|
+
label: "Limit code cell height",
|
|
306
|
+
description: "When enabled, code cells have a maximum height and scroll internally instead of expanding to show all content.",
|
|
307
|
+
defaultValue: false
|
|
308
|
+
},
|
|
309
|
+
"notebook.paramCellMaxHeight": {
|
|
310
|
+
type: "boolean",
|
|
311
|
+
category: "notebook",
|
|
312
|
+
group: "Layout",
|
|
313
|
+
label: "Limit parameter cell height",
|
|
314
|
+
description: "When enabled, parameter cells have a maximum height and scroll internally instead of expanding to show all content.",
|
|
315
|
+
defaultValue: false
|
|
316
|
+
},
|
|
317
|
+
"notebook.defaultCellView": {
|
|
318
|
+
type: "select",
|
|
319
|
+
category: "notebook",
|
|
320
|
+
group: "Layout",
|
|
321
|
+
label: "Default cell view",
|
|
322
|
+
description: "Which tab to show by default on all cells: Config (edit) or EcoLang (generated code).",
|
|
323
|
+
defaultValue: "config",
|
|
324
|
+
options: [
|
|
325
|
+
{ value: "config", label: "Config" },
|
|
326
|
+
{ value: "dsl", label: "EcoLang" }
|
|
327
|
+
]
|
|
328
|
+
},
|
|
329
|
+
// ── Advanced ─────────────────────────────────────────────────────────────
|
|
330
|
+
"editor.autosaveDelayMs": {
|
|
331
|
+
type: "number",
|
|
332
|
+
category: "advanced",
|
|
333
|
+
group: "Performance",
|
|
334
|
+
label: "Editor autosave delay (ms)",
|
|
335
|
+
description: "Delay before the function editor auto-saves changes.",
|
|
336
|
+
defaultValue: 5e3,
|
|
337
|
+
min: 1e3,
|
|
338
|
+
max: 3e4,
|
|
339
|
+
step: 1e3
|
|
340
|
+
},
|
|
341
|
+
"debug.logSettingsAccess": {
|
|
342
|
+
type: "boolean",
|
|
343
|
+
category: "advanced",
|
|
344
|
+
group: "Debug",
|
|
345
|
+
label: "Log settings access",
|
|
346
|
+
description: "Log all getSetting/setSetting calls to the console for debugging.",
|
|
347
|
+
defaultValue: false
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
var CATEGORIES = Object.freeze([
|
|
351
|
+
{ id: "general", label: "General", icon: "tune", description: "Workspace behavior and notifications", order: 10 },
|
|
352
|
+
{ id: "ai", label: "AI Assistant", icon: "smart_toy", description: "AI assistant behavior", order: 30 },
|
|
353
|
+
{ id: "window", label: "Windows", icon: "web_asset", description: "Managed window animation and appearance", order: 40 },
|
|
354
|
+
{ id: "data", label: "Data", icon: "database", description: "Table display, import, and pipeline defaults", order: 50 },
|
|
355
|
+
{ id: "logging", label: "Logging", icon: "terminal", description: "Log level, console output, and history", order: 60 },
|
|
356
|
+
{ id: "notebook", label: "Notebook", icon: "menu_book", description: "Notebook editor layout and behavior", order: 70 },
|
|
357
|
+
{ id: "advanced", label: "Advanced", icon: "code", description: "Debug and developer settings", order: 80 }
|
|
358
|
+
]);
|
|
359
|
+
var STORAGE_KEY = "ecosim.settings.v1";
|
|
360
|
+
function _getNestedValue(obj, parts) {
|
|
361
|
+
let current = obj;
|
|
362
|
+
for (const part of parts) {
|
|
363
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
current = current[part];
|
|
367
|
+
}
|
|
368
|
+
return current;
|
|
369
|
+
}
|
|
370
|
+
function _setNestedValue(obj, parts, value) {
|
|
371
|
+
let current = obj;
|
|
372
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
373
|
+
if (current[parts[i]] === void 0 || current[parts[i]] === null || typeof current[parts[i]] !== "object") {
|
|
374
|
+
current[parts[i]] = {};
|
|
375
|
+
}
|
|
376
|
+
current = current[parts[i]];
|
|
377
|
+
}
|
|
378
|
+
current[parts[parts.length - 1]] = value;
|
|
379
|
+
}
|
|
380
|
+
function _deepEqual(a, b) {
|
|
381
|
+
if (a === b) return true;
|
|
382
|
+
if (a == null || b == null) return a === b;
|
|
383
|
+
if (typeof a !== typeof b) return false;
|
|
384
|
+
if (Array.isArray(a)) {
|
|
385
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
386
|
+
return a.every((v, i) => _deepEqual(v, b[i]));
|
|
387
|
+
}
|
|
388
|
+
if (typeof a === "object") {
|
|
389
|
+
const keysA = Object.keys(a);
|
|
390
|
+
const keysB = Object.keys(b);
|
|
391
|
+
if (keysA.length !== keysB.length) return false;
|
|
392
|
+
return keysA.every((k) => _deepEqual(a[k], b[k]));
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
function _clone(value) {
|
|
397
|
+
return value === void 0 ? void 0 : JSON.parse(JSON.stringify(value));
|
|
398
|
+
}
|
|
399
|
+
function _deepMerge(target, src) {
|
|
400
|
+
for (const [key, value] of Object.entries(src || {})) {
|
|
401
|
+
const isPlain = value !== null && typeof value === "object" && !Array.isArray(value);
|
|
402
|
+
if (isPlain) {
|
|
403
|
+
if (target[key] === null || typeof target[key] !== "object" || Array.isArray(target[key])) {
|
|
404
|
+
target[key] = {};
|
|
405
|
+
}
|
|
406
|
+
_deepMerge(target[key], value);
|
|
407
|
+
} else {
|
|
408
|
+
target[key] = _clone(value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return target;
|
|
412
|
+
}
|
|
413
|
+
function createSettingsStore({
|
|
414
|
+
defaults = {},
|
|
415
|
+
schema = {},
|
|
416
|
+
categories = [],
|
|
417
|
+
storageKey = null,
|
|
418
|
+
eventBus = null
|
|
419
|
+
} = {}) {
|
|
420
|
+
const _defaults = _clone(defaults) ?? {};
|
|
421
|
+
const _schema = { ...schema };
|
|
422
|
+
let _categories = categories.map((c) => ({ ...c }));
|
|
423
|
+
const _values = _clone(defaults) ?? {};
|
|
424
|
+
let _bus = eventBus;
|
|
425
|
+
const _sortCategories = () => {
|
|
426
|
+
_categories.sort((a, b) => (a.order ?? 1e3) - (b.order ?? 1e3));
|
|
427
|
+
};
|
|
428
|
+
_sortCategories();
|
|
429
|
+
const _debug = () => _values.debug?.logSettingsAccess;
|
|
430
|
+
const _load = () => {
|
|
431
|
+
try {
|
|
432
|
+
if (!storageKey || typeof localStorage === "undefined") return;
|
|
433
|
+
const raw = localStorage.getItem(storageKey);
|
|
434
|
+
if (!raw) return;
|
|
435
|
+
const saved = JSON.parse(raw);
|
|
436
|
+
for (const path of Object.keys(_schema)) {
|
|
437
|
+
const parts = path.split(".");
|
|
438
|
+
const value = _getNestedValue(saved, parts);
|
|
439
|
+
if (value !== void 0) {
|
|
440
|
+
_setNestedValue(_values, parts, value);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.warn("[Settings] Failed to load persisted settings", err);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const _save = () => {
|
|
448
|
+
try {
|
|
449
|
+
if (!storageKey || typeof localStorage === "undefined") return;
|
|
450
|
+
const sparse = {};
|
|
451
|
+
for (const path of Object.keys(_schema)) {
|
|
452
|
+
const parts = path.split(".");
|
|
453
|
+
const current = _getNestedValue(_values, parts);
|
|
454
|
+
const def = _schema[path].defaultValue;
|
|
455
|
+
if (!_deepEqual(current, def)) {
|
|
456
|
+
_setNestedValue(sparse, parts, current);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (Object.keys(sparse).length === 0) {
|
|
460
|
+
localStorage.removeItem(storageKey);
|
|
461
|
+
} else {
|
|
462
|
+
localStorage.setItem(storageKey, JSON.stringify(sparse));
|
|
463
|
+
}
|
|
464
|
+
} catch (err) {
|
|
465
|
+
console.warn("[Settings] Failed to persist settings", err);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const _emit = (path, value) => {
|
|
469
|
+
if (!_bus) return;
|
|
470
|
+
_bus.emit(`settings:${path}:changed`, { path, value });
|
|
471
|
+
_bus.emit("settings:changed", { path, value });
|
|
472
|
+
};
|
|
473
|
+
_load();
|
|
474
|
+
const extend = ({ defaults: d = {}, schema: s = {}, categories: c = [] } = {}) => {
|
|
475
|
+
_deepMerge(_defaults, d);
|
|
476
|
+
_deepMerge(_values, d);
|
|
477
|
+
Object.assign(_schema, s);
|
|
478
|
+
for (const cat of c) {
|
|
479
|
+
if (!cat?.id) continue;
|
|
480
|
+
if (_categories.some((x) => x.id === cat.id)) continue;
|
|
481
|
+
_categories.push({ ...cat });
|
|
482
|
+
}
|
|
483
|
+
_sortCategories();
|
|
484
|
+
_load();
|
|
485
|
+
};
|
|
486
|
+
const get = (path, defaultValue = void 0) => {
|
|
487
|
+
const parts = path.split(".");
|
|
488
|
+
let current = _values;
|
|
489
|
+
for (const part of parts) {
|
|
490
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
491
|
+
if (_debug()) console.warn(`[Settings] Path not found: ${path}`);
|
|
492
|
+
return defaultValue;
|
|
493
|
+
}
|
|
494
|
+
current = current[part];
|
|
495
|
+
}
|
|
496
|
+
if (current === void 0) return defaultValue;
|
|
497
|
+
if (_debug()) console.log(`[Settings] getSetting('${path}') =>`, current);
|
|
498
|
+
return current;
|
|
499
|
+
};
|
|
500
|
+
const set = (path, value) => {
|
|
501
|
+
const parts = path.split(".");
|
|
502
|
+
const lastPart = parts.pop();
|
|
503
|
+
let current = _values;
|
|
504
|
+
for (const part of parts) {
|
|
505
|
+
if (current[part] === void 0 || current[part] === null) {
|
|
506
|
+
current[part] = {};
|
|
507
|
+
}
|
|
508
|
+
if (typeof current[part] !== "object") {
|
|
509
|
+
console.warn(`[Settings] Cannot set '${path}': intermediate path is not an object`);
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
current = current[part];
|
|
513
|
+
}
|
|
514
|
+
current[lastPart] = value;
|
|
515
|
+
if (_debug()) console.log(`[Settings] setSetting('${path}', ${JSON.stringify(value)})`);
|
|
516
|
+
_save();
|
|
517
|
+
_emit(path, value);
|
|
518
|
+
return true;
|
|
519
|
+
};
|
|
520
|
+
const getDefault = (path) => {
|
|
521
|
+
const def = _schema[path];
|
|
522
|
+
if (!def) return _clone(_getNestedValue(_defaults, path.split(".")));
|
|
523
|
+
return _clone(def.defaultValue);
|
|
524
|
+
};
|
|
525
|
+
const resetOne = (path) => {
|
|
526
|
+
const defaultVal = getDefault(path);
|
|
527
|
+
if (defaultVal === void 0) {
|
|
528
|
+
console.warn(`[Settings] No default found for '${path}'`);
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
return set(path, defaultVal);
|
|
532
|
+
};
|
|
533
|
+
const resetCategoryFn = (categoryId) => {
|
|
534
|
+
for (const [path, def] of Object.entries(_schema)) {
|
|
535
|
+
if (def.category !== categoryId) continue;
|
|
536
|
+
_setNestedValue(_values, path.split("."), _clone(def.defaultValue));
|
|
537
|
+
}
|
|
538
|
+
_save();
|
|
539
|
+
if (_bus) {
|
|
540
|
+
_bus.emit("settings:category:reset", { category: categoryId });
|
|
541
|
+
_bus.emit("settings:changed", { path: null, category: categoryId });
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
const resetAll = () => {
|
|
545
|
+
for (const [path, def] of Object.entries(_schema)) {
|
|
546
|
+
_setNestedValue(_values, path.split("."), _clone(def.defaultValue));
|
|
547
|
+
}
|
|
548
|
+
_save();
|
|
549
|
+
if (_bus) {
|
|
550
|
+
_bus.emit("settings:reset", {});
|
|
551
|
+
_bus.emit("settings:changed", { path: null });
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
const getByCategory = (categoryId) => {
|
|
555
|
+
const result = [];
|
|
556
|
+
for (const [path, def] of Object.entries(_schema)) {
|
|
557
|
+
if (def.category === categoryId) result.push({ path, ...def });
|
|
558
|
+
}
|
|
559
|
+
return result;
|
|
560
|
+
};
|
|
561
|
+
return Object.freeze({
|
|
562
|
+
get,
|
|
563
|
+
set,
|
|
564
|
+
extend,
|
|
565
|
+
getDefault,
|
|
566
|
+
resetOne,
|
|
567
|
+
resetCategory: resetCategoryFn,
|
|
568
|
+
resetAll,
|
|
569
|
+
getAll: () => _clone(_values),
|
|
570
|
+
getSchema: () => _schema,
|
|
571
|
+
// Frozen snapshot: the old module-level CATEGORIES was a frozen const, and
|
|
572
|
+
// a caller that sorted or spliced the live array in place would silently
|
|
573
|
+
// reorder the Settings sidebar. Same objects, same order — just not ours
|
|
574
|
+
// to wreck.
|
|
575
|
+
getCategories: () => Object.freeze(_categories.slice()),
|
|
576
|
+
getByCategory,
|
|
577
|
+
setEventBus: (bus) => {
|
|
578
|
+
_bus = bus;
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
var _store = createSettingsStore({
|
|
583
|
+
defaults: DEFAULTS,
|
|
584
|
+
schema: SCHEMA,
|
|
585
|
+
categories: CATEGORIES,
|
|
586
|
+
storageKey: STORAGE_KEY
|
|
587
|
+
});
|
|
588
|
+
function registerSettings(slice) {
|
|
589
|
+
_store.extend(slice);
|
|
590
|
+
}
|
|
591
|
+
function registerSettingsEventBus(eventBus) {
|
|
592
|
+
_store.setEventBus(eventBus);
|
|
593
|
+
}
|
|
594
|
+
function getSetting(path, defaultValue = void 0) {
|
|
595
|
+
return _store.get(path, defaultValue);
|
|
596
|
+
}
|
|
597
|
+
function setSetting(path, value) {
|
|
598
|
+
return _store.set(path, value);
|
|
599
|
+
}
|
|
600
|
+
function getDefaultValue(path) {
|
|
601
|
+
return _store.getDefault(path);
|
|
602
|
+
}
|
|
603
|
+
function resetSetting(path) {
|
|
604
|
+
return _store.resetOne(path);
|
|
605
|
+
}
|
|
606
|
+
function resetCategory(categoryId) {
|
|
607
|
+
_store.resetCategory(categoryId);
|
|
608
|
+
}
|
|
609
|
+
function resetAllSettings() {
|
|
610
|
+
_store.resetAll();
|
|
611
|
+
}
|
|
612
|
+
function getAllSettings() {
|
|
613
|
+
return _store.getAll();
|
|
614
|
+
}
|
|
615
|
+
function getSchema() {
|
|
616
|
+
return _store.getSchema();
|
|
617
|
+
}
|
|
618
|
+
function getCategories() {
|
|
619
|
+
return _store.getCategories();
|
|
620
|
+
}
|
|
621
|
+
function getSettingsByCategory(categoryId) {
|
|
622
|
+
return _store.getByCategory(categoryId);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export {
|
|
626
|
+
createSettingsStore,
|
|
627
|
+
registerSettings,
|
|
628
|
+
registerSettingsEventBus,
|
|
629
|
+
getSetting,
|
|
630
|
+
setSetting,
|
|
631
|
+
getDefaultValue,
|
|
632
|
+
resetSetting,
|
|
633
|
+
resetCategory,
|
|
634
|
+
resetAllSettings,
|
|
635
|
+
getAllSettings,
|
|
636
|
+
getSchema,
|
|
637
|
+
getCategories,
|
|
638
|
+
getSettingsByCategory
|
|
639
|
+
};
|
|
640
|
+
//# sourceMappingURL=chunk-FL5KFNQH.js.map
|