dr-widget 0.1.3__py3-none-any.whl

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.
Files changed (127) hide show
  1. dr_widget/__init__.py +5 -0
  2. dr_widget/py.typed +0 -0
  3. dr_widget/widgets/__init__.py +5 -0
  4. dr_widget/widgets/config_file_manager/.gitignore +24 -0
  5. dr_widget/widgets/config_file_manager/.vscode/extensions.json +3 -0
  6. dr_widget/widgets/config_file_manager/README.md +89 -0
  7. dr_widget/widgets/config_file_manager/__init__.py +283 -0
  8. dr_widget/widgets/config_file_manager/components.json +16 -0
  9. dr_widget/widgets/config_file_manager/index.html +12 -0
  10. dr_widget/widgets/config_file_manager/jsrepo.json +18 -0
  11. dr_widget/widgets/config_file_manager/package.json +49 -0
  12. dr_widget/widgets/config_file_manager/postcss.config.js +6 -0
  13. dr_widget/widgets/config_file_manager/public/fonts/Inter-roman.var.woff2 +0 -0
  14. dr_widget/widgets/config_file_manager/public/vite.svg +1 -0
  15. dr_widget/widgets/config_file_manager/src/App.svelte +62 -0
  16. dr_widget/widgets/config_file_manager/src/ConfigFileManager.svelte +605 -0
  17. dr_widget/widgets/config_file_manager/src/app.css +134 -0
  18. dr_widget/widgets/config_file_manager/src/index.js +5 -0
  19. dr_widget/widgets/config_file_manager/src/lib/@test_state.json +20 -0
  20. dr_widget/widgets/config_file_manager/src/lib/Counter.svelte +10 -0
  21. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/BrowseConfigsPanel.svelte +137 -0
  22. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ComplexJsonViewer.svelte +94 -0
  23. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ConfigViewerPanel.svelte +282 -0
  24. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/LoadedConfigPreview.svelte +74 -0
  25. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SaveConfigPanel.svelte +449 -0
  26. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFileRow.svelte +38 -0
  27. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFilesList.svelte +30 -0
  28. dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SimpleJsonViewer.svelte +405 -0
  29. dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/badge.svelte +50 -0
  30. dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/index.ts +2 -0
  31. dr_widget/widgets/config_file_manager/src/lib/components/ui/button/button.svelte +128 -0
  32. dr_widget/widgets/config_file_manager/src/lib/components/ui/button/index.ts +27 -0
  33. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-action.svelte +20 -0
  34. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-content.svelte +15 -0
  35. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-description.svelte +20 -0
  36. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-footer.svelte +20 -0
  37. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-header.svelte +23 -0
  38. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-title.svelte +20 -0
  39. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card.svelte +23 -0
  40. dr_widget/widgets/config_file_manager/src/lib/components/ui/card/index.ts +25 -0
  41. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  42. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-content.svelte +47 -0
  43. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-description.svelte +21 -0
  44. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-footer.svelte +24 -0
  45. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-header.svelte +24 -0
  46. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-overlay.svelte +24 -0
  47. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-title.svelte +21 -0
  48. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  49. dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/index.ts +41 -0
  50. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-close.svelte +11 -0
  51. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-content.svelte +41 -0
  52. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-description.svelte +21 -0
  53. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-footer.svelte +24 -0
  54. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-header.svelte +24 -0
  55. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-nested.svelte +16 -0
  56. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-overlay.svelte +24 -0
  57. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-title.svelte +21 -0
  58. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-trigger.svelte +11 -0
  59. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer.svelte +16 -0
  60. dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/index.ts +45 -0
  61. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-content.svelte +23 -0
  62. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-description.svelte +23 -0
  63. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-header.svelte +20 -0
  64. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-media.svelte +41 -0
  65. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-title.svelte +20 -0
  66. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty.svelte +23 -0
  67. dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/index.ts +22 -0
  68. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-content.svelte +20 -0
  69. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-description.svelte +25 -0
  70. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-error.svelte +58 -0
  71. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-group.svelte +23 -0
  72. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-label.svelte +26 -0
  73. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-legend.svelte +29 -0
  74. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-separator.svelte +38 -0
  75. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-set.svelte +24 -0
  76. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-title.svelte +23 -0
  77. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field.svelte +53 -0
  78. dr_widget/widgets/config_file_manager/src/lib/components/ui/field/index.ts +33 -0
  79. dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/file-drop-zone.svelte +178 -0
  80. dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/index.ts +29 -0
  81. dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/types.ts +51 -0
  82. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/index.ts +34 -0
  83. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-actions.svelte +20 -0
  84. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-content.svelte +20 -0
  85. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-description.svelte +24 -0
  86. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-footer.svelte +20 -0
  87. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-group.svelte +21 -0
  88. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-header.svelte +20 -0
  89. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-media.svelte +42 -0
  90. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-separator.svelte +19 -0
  91. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-title.svelte +20 -0
  92. dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item.svelte +60 -0
  93. dr_widget/widgets/config_file_manager/src/lib/components/ui/label/index.ts +7 -0
  94. dr_widget/widgets/config_file_manager/src/lib/components/ui/label/label.svelte +20 -0
  95. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/index.ts +13 -0
  96. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-content.svelte +29 -0
  97. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-description.svelte +20 -0
  98. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-footer.svelte +29 -0
  99. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-header.svelte +29 -0
  100. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-title.svelte +20 -0
  101. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-trigger.svelte +24 -0
  102. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte +24 -0
  103. dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte.ts +32 -0
  104. dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/index.ts +7 -0
  105. dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/separator.svelte +21 -0
  106. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/index.ts +16 -0
  107. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  108. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-list.svelte +20 -0
  109. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-trigger.svelte +20 -0
  110. dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs.svelte +19 -0
  111. dr_widget/widgets/config_file_manager/src/lib/hooks/use-file-bindings.ts +189 -0
  112. dr_widget/widgets/config_file_manager/src/lib/react/JsonTreeCanvas.tsx +207 -0
  113. dr_widget/widgets/config_file_manager/src/lib/utils/config-format.ts +113 -0
  114. dr_widget/widgets/config_file_manager/src/lib/utils/utils.ts +21 -0
  115. dr_widget/widgets/config_file_manager/src/lib/utils.ts +17 -0
  116. dr_widget/widgets/config_file_manager/src/main.js +7 -0
  117. dr_widget/widgets/config_file_manager/static/fonts/Inter-roman.var.woff2 +0 -0
  118. dr_widget/widgets/config_file_manager/static/index.js +9719 -0
  119. dr_widget/widgets/config_file_manager/static/style.css +1 -0
  120. dr_widget/widgets/config_file_manager/static/vite.svg +1 -0
  121. dr_widget/widgets/config_file_manager/svelte.config.js +8 -0
  122. dr_widget/widgets/config_file_manager/tailwind.config.js +12 -0
  123. dr_widget/widgets/config_file_manager/tsconfig.json +28 -0
  124. dr_widget/widgets/config_file_manager/vite.config.js +36 -0
  125. dr_widget-0.1.3.dist-info/METADATA +62 -0
  126. dr_widget-0.1.3.dist-info/RECORD +127 -0
  127. dr_widget-0.1.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,605 @@
1
+ <script lang="ts">
2
+ import * as Tabs from "$lib/components/ui/tabs/index.js";
3
+ import { Button } from "$lib/components/ui/button/index.js";
4
+ import { Badge } from "$lib/components/ui/badge/index.js";
5
+
6
+ import BrowseConfigsPanel from "$lib/components/file-drop/BrowseConfigsPanel.svelte";
7
+ import LoadedConfigPreview from "$lib/components/file-drop/LoadedConfigPreview.svelte";
8
+ import SaveConfigPanel from "$lib/components/file-drop/SaveConfigPanel.svelte";
9
+ import {
10
+ createFileBindingHandlers,
11
+ type BoundFile,
12
+ type FileBinding,
13
+ } from "$lib/hooks/use-file-bindings";
14
+ import {
15
+ buildWrappedPayload,
16
+ formatSavedAt,
17
+ normalizeConfigPayload,
18
+ } from "$lib/utils/config-format";
19
+
20
+ const { bindings } = $props<{
21
+ bindings: FileBinding;
22
+ }>();
23
+
24
+ const maxFiles = 1;
25
+ const bindingHandlers = createFileBindingHandlers({
26
+ bindings,
27
+ maxFiles,
28
+ });
29
+
30
+ const parseJsonObject = (value?: string | null) => {
31
+ if (!value) return undefined;
32
+ try {
33
+ const parsed = JSON.parse(value);
34
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
35
+ return parsed as Record<string, unknown>;
36
+ }
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ return undefined;
41
+ };
42
+
43
+ const canonicalizeState = (value?: string | null) => {
44
+ const parsed = parseJsonObject(value);
45
+ if (parsed) {
46
+ try {
47
+ return JSON.stringify(parsed);
48
+ } catch {
49
+ return (value ?? "").trim();
50
+ }
51
+ }
52
+ return (value ?? "").trim();
53
+ };
54
+
55
+ const buildMetadataSnapshot = () => {
56
+ const snapshot: Record<string, string> = {};
57
+ const trimmedVersion = bindings.version?.trim();
58
+ if (trimmedVersion) {
59
+ snapshot.version = trimmedVersion;
60
+ }
61
+ return snapshot;
62
+ };
63
+
64
+ const canonicalizeMetadata = (metadata?: Record<string, string>) => {
65
+ if (!metadata) return "";
66
+ const entries = Object.entries(metadata)
67
+ .map(([key, value]) => [key, typeof value === "string" ? value.trim() : value])
68
+ .filter((entry): entry is [string, string] => Boolean(entry[1] && entry[1].length > 0));
69
+ if (entries.length === 0) {
70
+ return "";
71
+ }
72
+ entries.sort(([a], [b]) => a.localeCompare(b));
73
+ return JSON.stringify(Object.fromEntries(entries));
74
+ };
75
+
76
+ const extractFileName = (value?: string | null) => {
77
+ if (!value) return undefined;
78
+ const parts = value.split(/[\\/]+/).filter(Boolean);
79
+ if (parts.length === 0) return value;
80
+ return parts[parts.length - 1];
81
+ };
82
+
83
+ const hasMetadataEntries = (metadata?: Record<string, unknown>) =>
84
+ Boolean(metadata && Object.keys(metadata).length > 0);
85
+
86
+ const parsedFiles = $derived(bindingHandlers.readBoundFiles());
87
+ const baselineParsed = $derived.by(() => parseJsonObject(bindings.baseline_state));
88
+ const metadataSnapshot = $derived.by(() => buildMetadataSnapshot());
89
+ let lastSavedMetadata = $state<Record<string, string>>(buildMetadataSnapshot());
90
+ let lastBaselineSignature = $state(bindings.baseline_state ?? "");
91
+ const metadataDirty = $derived.by(
92
+ () => canonicalizeMetadata(metadataSnapshot) !== canonicalizeMetadata(lastSavedMetadata),
93
+ );
94
+ const isDirty = $derived.by(
95
+ () =>
96
+ canonicalizeState(bindings.current_state) !== canonicalizeState(bindings.baseline_state) || metadataDirty,
97
+ );
98
+ const selectedConfigVersion = $derived.by(() => bindings.version ?? "");
99
+ const canEditSelectedConfigVersion = $derived.by(() => Boolean(bindings.current_state && bindings.current_state.trim().length > 0));
100
+ const configFileDisplayName = $derived.by(
101
+ () => bindings.config_file_display || extractFileName(bindings.config_file) || undefined,
102
+ );
103
+
104
+ let previewFile = $state<BoundFile | undefined>(undefined);
105
+ let previewText = $state<string | undefined>(bindings.current_state ?? undefined);
106
+ let previewJson = $state<unknown | undefined>(() => {
107
+ if (!bindings.current_state) return undefined;
108
+ try {
109
+ return JSON.parse(bindings.current_state);
110
+ } catch {
111
+ return undefined;
112
+ }
113
+ });
114
+ let loadedMetadataExtras = $state<Record<string, unknown>>({});
115
+ const normalizedPreview = $derived.by(() => {
116
+ if (!previewJson || typeof previewJson !== "object") return undefined;
117
+ return normalizeConfigPayload(previewJson);
118
+ });
119
+ const normalizedPreviewData = $derived.by(() => {
120
+ if (!normalizedPreview) return undefined;
121
+ return JSON.stringify(normalizedPreview.data, null, 2);
122
+ });
123
+ const normalizedPreviewParsed = $derived.by(() => normalizedPreview?.data);
124
+ let managerOpen = $state(false);
125
+ let activeTab = $state("find");
126
+ let lastLoadedFileName = $state<string | undefined>(undefined);
127
+ let loadedConfigSummary = $state<
128
+ | {
129
+ name?: string;
130
+ savedAt?: string;
131
+ version?: string;
132
+ rawText?: string;
133
+ parsed?: unknown;
134
+ wrappedRawText?: string;
135
+ wrappedParsed?: unknown;
136
+ }
137
+ | undefined
138
+ >(undefined);
139
+ let showLoadedPreview = $state(false);
140
+ let previewFromLoaded = $state(false);
141
+ let loadedConfigPath = $state<string | undefined>(undefined);
142
+ let lastObservedSavedAt = $state<string | undefined>(bindings.saved_at ?? undefined);
143
+ const defaultSaveTarget = $derived.by(
144
+ () =>
145
+ bindings.config_file_display ||
146
+ bindings.config_file ||
147
+ loadedConfigPath ||
148
+ lastLoadedFileName ||
149
+ "config.json",
150
+ );
151
+ const defaultSaveLabel = $derived.by(
152
+ () =>
153
+ bindings.config_file ||
154
+ loadedConfigPath ||
155
+ defaultSaveTarget,
156
+ );
157
+
158
+ const computeBindingMetadataFallback = () => {
159
+ const displayLabel = bindings.config_file_display?.trim();
160
+ if (displayLabel) {
161
+ return { save_path: displayLabel };
162
+ }
163
+ const resolvedPath = bindings.config_file?.trim();
164
+ if (resolvedPath) {
165
+ return { save_path: resolvedPath };
166
+ }
167
+ const fallbackLabel = loadedConfigPath || lastLoadedFileName;
168
+ if (fallbackLabel) {
169
+ return { save_path: fallbackLabel };
170
+ }
171
+ return undefined;
172
+ };
173
+
174
+ const bindingSaveMetadata = $derived.by(() => {
175
+ if (hasMetadataEntries(loadedMetadataExtras)) {
176
+ return loadedMetadataExtras;
177
+ }
178
+ return computeBindingMetadataFallback();
179
+ });
180
+
181
+ const previewWrappedPayload = $derived.by(() => {
182
+ if (!normalizedPreview) return undefined;
183
+ const previewMetadata = hasMetadataEntries(normalizedPreview.metadata)
184
+ ? normalizedPreview.metadata
185
+ : bindingSaveMetadata;
186
+ return buildWrappedPayload({
187
+ data: normalizedPreview.data,
188
+ version: normalizedPreview.version ?? bindings.version ?? undefined,
189
+ savedAt: normalizedPreview.savedAt ?? bindings.saved_at ?? undefined,
190
+ metadata: previewMetadata,
191
+ });
192
+ });
193
+ const previewWrappedJson = $derived.by(() =>
194
+ previewWrappedPayload ? JSON.stringify(previewWrappedPayload, null, 2) : undefined,
195
+ );
196
+
197
+ $effect(() => {
198
+ const latestPath = bindings.config_file?.trim();
199
+ if (latestPath && latestPath !== loadedConfigPath) {
200
+ loadedConfigPath = latestPath;
201
+ }
202
+ });
203
+
204
+ $effect(() => {
205
+ const latestDisplay = bindings.config_file_display?.trim();
206
+ if (latestDisplay && latestDisplay !== lastLoadedFileName) {
207
+ lastLoadedFileName = latestDisplay;
208
+ }
209
+ });
210
+
211
+ $effect(() => {
212
+ const savedAt = bindings.saved_at?.trim() ?? "";
213
+ if (savedAt && savedAt !== lastObservedSavedAt) {
214
+ previewFromLoaded = false;
215
+ showLoadedPreview = false;
216
+ bindingHandlers.writeError("");
217
+ lastObservedSavedAt = savedAt;
218
+ lastSavedMetadata = buildMetadataSnapshot();
219
+ const latestMetadata = computeBindingMetadataFallback();
220
+ loadedMetadataExtras = latestMetadata ?? {};
221
+ const fallbackPath = bindings.config_file?.trim() || bindings.config_file_display?.trim();
222
+ if (fallbackPath) {
223
+ loadedConfigPath = fallbackPath;
224
+ }
225
+ } else if (!savedAt && lastObservedSavedAt) {
226
+ lastObservedSavedAt = undefined;
227
+ lastSavedMetadata = buildMetadataSnapshot();
228
+ loadedMetadataExtras = {};
229
+ }
230
+ });
231
+
232
+ $effect(() => {
233
+ const baselineSignature = bindings.baseline_state ?? "";
234
+ if (baselineSignature !== lastBaselineSignature) {
235
+ lastBaselineSignature = baselineSignature;
236
+ lastSavedMetadata = buildMetadataSnapshot();
237
+ }
238
+ });
239
+
240
+ const computeByteSize = (input: string): number => {
241
+ if (typeof TextEncoder !== "undefined") {
242
+ return new TextEncoder().encode(input).byteLength;
243
+ }
244
+ return input.length;
245
+ };
246
+
247
+ const resetPreviewState = () => {
248
+ previewFile = undefined;
249
+ previewText = undefined;
250
+ previewJson = undefined;
251
+ };
252
+
253
+ // previewText
254
+ $effect(() => {
255
+ if (!previewText) {
256
+ previewJson = undefined;
257
+ return;
258
+ }
259
+
260
+ try {
261
+ previewJson = JSON.parse(previewText);
262
+ } catch {
263
+ previewJson = undefined;
264
+ }
265
+ });
266
+
267
+ // parsedFiles, previewFile, previewFromLoaded
268
+ $effect(() => {
269
+ if (parsedFiles.length === 0 && previewFile && !previewFromLoaded) {
270
+ resetPreviewState();
271
+ }
272
+ });
273
+
274
+ const previewSavedAt = $derived.by(() => formatSavedAt(normalizedPreview?.savedAt ?? bindings.saved_at));
275
+
276
+ const previewVersion = $derived.by(() => normalizedPreview?.version);
277
+
278
+ // managerOpen, isDirty
279
+ $effect(() => {
280
+ if (!managerOpen) return;
281
+ activeTab = isDirty ? "save" : "find";
282
+ });
283
+
284
+ // current_state and metadata summary
285
+ $effect(() => {
286
+ const raw = bindings.current_state;
287
+ if (!raw || raw.trim().length === 0) {
288
+ loadedConfigSummary = undefined;
289
+ loadedMetadataExtras = {};
290
+ previewFromLoaded = false;
291
+ showLoadedPreview = false;
292
+ lastLoadedFileName = undefined;
293
+ loadedConfigPath = undefined;
294
+ if (!managerOpen) {
295
+ resetPreviewState();
296
+ }
297
+ return;
298
+ }
299
+
300
+ const parsed = parseJsonObject(raw) ?? {};
301
+ const savedAtValue = (() => {
302
+ const value = bindings.saved_at?.trim();
303
+ return value ? value : undefined;
304
+ })();
305
+ const metadataExtras = bindingSaveMetadata;
306
+ const wrappedPayload = buildWrappedPayload({
307
+ data: parsed,
308
+ version: bindings.version ?? undefined,
309
+ savedAt: savedAtValue,
310
+ metadata: metadataExtras,
311
+ });
312
+ const wrappedJson = JSON.stringify(wrappedPayload, null, 2);
313
+
314
+ const savedAtLabel = savedAtValue ? formatSavedAt(savedAtValue) : undefined;
315
+
316
+ loadedConfigSummary = {
317
+ name: configFileDisplayName || lastLoadedFileName || loadedConfigSummary?.name || "Config loaded",
318
+ savedAt: savedAtLabel,
319
+ version: bindings.version ?? undefined,
320
+ rawText: raw,
321
+ parsed,
322
+ wrappedRawText: wrappedJson,
323
+ wrappedParsed: wrappedPayload,
324
+ };
325
+
326
+ if (!previewFromLoaded && !managerOpen) {
327
+ previewText = raw;
328
+ previewJson = parsed;
329
+ }
330
+ });
331
+
332
+ const handleUpload = async (files: File[]) => {
333
+ const [file] = files;
334
+ if (!file) return;
335
+
336
+ const fileText = await file.text();
337
+
338
+ await bindingHandlers.handleUpload([file]);
339
+
340
+ previewFile = {
341
+ name: file.name,
342
+ size: file.size,
343
+ type: file.type,
344
+ };
345
+ previewText = fileText;
346
+ bindingHandlers.writeError("");
347
+ previewFromLoaded = false;
348
+ loadedMetadataExtras = {};
349
+ };
350
+
351
+ const handleRemove = () => {
352
+ if (previewFromLoaded) {
353
+ bindingHandlers.writeCurrentState("");
354
+ bindingHandlers.writeBaselineState("");
355
+ bindingHandlers.writeVersion("");
356
+ bindingHandlers.writeConfigFile("");
357
+ bindingHandlers.writeConfigFileDisplay("");
358
+ bindingHandlers.writeSavedAt("");
359
+ loadedConfigSummary = undefined;
360
+ previewFromLoaded = false;
361
+ showLoadedPreview = false;
362
+ lastLoadedFileName = undefined;
363
+ loadedConfigPath = undefined;
364
+ loadedMetadataExtras = {};
365
+ bindingHandlers.writeError("");
366
+ resetPreviewState();
367
+ return;
368
+ }
369
+
370
+ if (parsedFiles.length > 0) {
371
+ bindingHandlers.removeFile(0);
372
+ }
373
+ bindingHandlers.writeError("");
374
+ resetPreviewState();
375
+ loadedConfigPath = undefined;
376
+ bindingHandlers.writeConfigFileDisplay("");
377
+ bindingHandlers.writeSavedAt("");
378
+ loadedMetadataExtras = {};
379
+ };
380
+
381
+ const handleLoadConfig = () => {
382
+ if (!previewText) {
383
+ bindingHandlers.writeError("Unable to load config: missing file contents.");
384
+ return;
385
+ }
386
+
387
+ lastLoadedFileName = previewFile?.name ?? lastLoadedFileName;
388
+ const summaryName = lastLoadedFileName ?? previewFile?.name ?? "Config loaded";
389
+
390
+ let parsedFile: unknown;
391
+ try {
392
+ parsedFile = JSON.parse(previewText);
393
+ } catch {
394
+ bindingHandlers.writeError("Config is not valid JSON.");
395
+ return;
396
+ }
397
+
398
+ if (!parsedFile || typeof parsedFile !== "object" || Array.isArray(parsedFile)) {
399
+ bindingHandlers.writeError("Config must be a JSON object.");
400
+ return;
401
+ }
402
+
403
+ const normalized = normalizeConfigPayload(parsedFile as Record<string, unknown>);
404
+ const dataJson = JSON.stringify(normalized.data, null, 2);
405
+ const wrappedPayload = buildWrappedPayload({
406
+ data: normalized.data,
407
+ version: normalized.version ?? bindings.version ?? undefined,
408
+ savedAt: normalized.savedAt ?? undefined,
409
+ });
410
+ const wrappedJson = JSON.stringify(wrappedPayload, null, 2);
411
+ loadedMetadataExtras = normalized.metadata ?? {};
412
+
413
+ bindingHandlers.writeCurrentState(dataJson);
414
+ bindingHandlers.writeBaselineState(dataJson);
415
+ if (normalized.version) {
416
+ bindingHandlers.writeVersion(normalized.version);
417
+ }
418
+ if (summaryName) {
419
+ bindingHandlers.writeConfigFile(summaryName);
420
+ bindingHandlers.writeConfigFileDisplay(extractFileName(summaryName) ?? summaryName);
421
+ }
422
+
423
+ bindingHandlers.writeSavedAt(normalized.savedAt ?? "");
424
+
425
+ loadedConfigSummary = {
426
+ name: summaryName,
427
+ savedAt: normalized.savedAt ? formatSavedAt(normalized.savedAt) : undefined,
428
+ version: normalized.version,
429
+ rawText: dataJson,
430
+ parsed: normalized.data,
431
+ wrappedRawText: wrappedJson,
432
+ wrappedParsed: wrappedPayload,
433
+ };
434
+ loadedConfigPath = summaryName;
435
+
436
+ if (parsedFiles.length > 0) {
437
+ bindingHandlers.removeFile(0);
438
+ }
439
+
440
+ bindingHandlers.writeError("");
441
+ resetPreviewState();
442
+ managerOpen = false;
443
+ showLoadedPreview = false;
444
+ previewFromLoaded = false;
445
+ };
446
+
447
+ // managerOpen
448
+ $effect(() => {
449
+ if (managerOpen) {
450
+ showLoadedPreview = false;
451
+
452
+ if (!previewFile && loadedConfigSummary?.rawText) {
453
+ previewFromLoaded = true;
454
+ previewText = loadedConfigSummary.rawText;
455
+ previewFile = {
456
+ name: loadedConfigSummary.name ?? "Loaded config",
457
+ size: computeByteSize(loadedConfigSummary.rawText),
458
+ type: "application/json",
459
+ };
460
+ previewJson = loadedConfigSummary.parsed;
461
+ }
462
+ } else if (previewFromLoaded) {
463
+ resetPreviewState();
464
+ previewFromLoaded = false;
465
+ }
466
+ });
467
+
468
+ const isLoadedConfigCurrent = $derived.by(() => {
469
+ if (!loadedConfigSummary?.rawText) return false;
470
+ const candidate = normalizedPreviewData ?? previewText;
471
+ if (!candidate) return false;
472
+ return candidate.trim() === loadedConfigSummary.rawText.trim();
473
+ });
474
+ </script>
475
+
476
+ <div class="space-y-6">
477
+ {#if managerOpen}
478
+ <div class="space-y-4 rounded-lg border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900">
479
+ <div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
480
+ <div>
481
+ <p class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
482
+ Manage Configs
483
+ </p>
484
+ <p class="text-sm text-zinc-500 dark:text-zinc-400">
485
+ Load a JSON config or prepare a notebook save.
486
+ </p>
487
+ </div>
488
+ <Button variant="outline" onclick={() => (managerOpen = false)}>
489
+ Close
490
+ </Button>
491
+ </div>
492
+
493
+ <Tabs.Root bind:value={activeTab}>
494
+ <Tabs.List>
495
+ <Tabs.Trigger value="find">Browse Configs</Tabs.Trigger>
496
+ <Tabs.Trigger value="save">Save Config</Tabs.Trigger>
497
+ </Tabs.List>
498
+
499
+ <Tabs.Content value="find">
500
+ <BrowseConfigsPanel
501
+ file={previewFile}
502
+ rawContents={normalizedPreviewData ?? previewText}
503
+ parsedContents={normalizedPreviewParsed}
504
+ baselineContents={baselineParsed}
505
+ savedAtLabel={previewSavedAt}
506
+ versionLabel={previewVersion}
507
+ dirty={isDirty}
508
+ error={bindings.error}
509
+ maxFiles={maxFiles}
510
+ onUpload={handleUpload}
511
+ onFileRejected={bindingHandlers.handleFileRejected}
512
+ onRemove={handleRemove}
513
+ onLoad={handleLoadConfig}
514
+ disableLoad={isLoadedConfigCurrent}
515
+ wrappedContents={previewWrappedJson ?? previewText}
516
+ wrappedParsed={previewWrappedPayload ?? previewJson}
517
+ />
518
+ </Tabs.Content>
519
+
520
+ <Tabs.Content value="save">
521
+ <SaveConfigPanel
522
+ bindings={bindings}
523
+ rawConfig={bindings.current_state}
524
+ baselineConfig={baselineParsed}
525
+ defaultFileName={defaultSaveTarget}
526
+ saveTargetLabel={defaultSaveLabel}
527
+ dirty={isDirty}
528
+ currentVersion={selectedConfigVersion}
529
+ canEditVersion={canEditSelectedConfigVersion}
530
+ />
531
+ </Tabs.Content>
532
+ </Tabs.Root>
533
+ </div>
534
+ {:else if showLoadedPreview && loadedConfigSummary?.rawText}
535
+ <LoadedConfigPreview
536
+ fileName={loadedConfigSummary.name}
537
+ savedAtLabel={loadedConfigSummary.savedAt}
538
+ versionLabel={loadedConfigSummary.version}
539
+ rawContents={loadedConfigSummary.rawText}
540
+ parsedContents={loadedConfigSummary.parsed}
541
+ baselineContents={baselineParsed}
542
+ dirty={isDirty}
543
+ onClose={() => (showLoadedPreview = false)}
544
+ wrappedContents={loadedConfigSummary.wrappedRawText}
545
+ wrappedParsed={loadedConfigSummary.wrappedParsed}
546
+ onManage={() => {
547
+ showLoadedPreview = false;
548
+ managerOpen = true;
549
+ }}
550
+ />
551
+ {:else}
552
+ <div
553
+ class="flex flex-col gap-3 rounded-lg border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-800 dark:bg-zinc-900"
554
+ >
555
+ <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
556
+ <div class="space-y-1">
557
+ <p class="text-sm font-medium text-zinc-500 dark:text-zinc-400">
558
+ Configuration
559
+ </p>
560
+ {#if loadedConfigSummary}
561
+ <p class="text-base font-semibold text-zinc-900 dark:text-zinc-100">
562
+ {configFileDisplayName || loadedConfigSummary.name}
563
+ </p>
564
+ {#if loadedConfigSummary.savedAt || bindings.version}
565
+ <div class="flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
566
+ {#if loadedConfigSummary.savedAt}
567
+ <span>Saved {loadedConfigSummary.savedAt}</span>
568
+ {/if}
569
+ {#if bindings.version}
570
+ <Badge variant="secondary" class="px-2 py-0.5 text-[0.65rem]">
571
+ {bindings.version}
572
+ </Badge>
573
+ {/if}
574
+ {#if isDirty}
575
+ <Badge variant="secondary" class="bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-200">
576
+ Unsaved changes
577
+ </Badge>
578
+ {/if}
579
+ </div>
580
+ {/if}
581
+ {:else}
582
+ <p class="text-base text-zinc-600 dark:text-zinc-300">
583
+ No config loaded.
584
+ </p>
585
+ {/if}
586
+ </div>
587
+
588
+ <div class="flex gap-2">
589
+ <Button variant="outline" onclick={() => (managerOpen = true)}>
590
+ Manage Configs
591
+ </Button>
592
+ {#if loadedConfigSummary?.rawText}
593
+ <Button
594
+ variant="outline"
595
+ disabled={!loadedConfigSummary?.rawText}
596
+ onclick={() => (showLoadedPreview = true)}
597
+ >
598
+ View Config
599
+ </Button>
600
+ {/if}
601
+ </div>
602
+ </div>
603
+ </div>
604
+ {/if}
605
+ </div>