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,134 @@
1
+ @import "tailwindcss";
2
+
3
+ @import "tw-animate-css";
4
+
5
+ @font-face {
6
+ font-family: "Inter";
7
+ src: url("/fonts/Inter-roman.var.woff2") format("woff2");
8
+ font-weight: 100 900;
9
+ font-style: normal;
10
+ font-display: swap;
11
+ }
12
+
13
+ @custom-variant dark (&:is(.dark *));
14
+
15
+ :root {
16
+ --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
17
+ --radius: 0.625rem;
18
+ --background: oklch(1 0 0);
19
+ --foreground: oklch(0.129 0.042 264.695);
20
+ --card: oklch(1 0 0);
21
+ --card-foreground: oklch(0.129 0.042 264.695);
22
+ --popover: oklch(1 0 0);
23
+ --popover-foreground: oklch(0.129 0.042 264.695);
24
+ --primary: oklch(0.208 0.042 265.755);
25
+ --primary-foreground: oklch(0.984 0.003 247.858);
26
+ --secondary: oklch(0.968 0.007 247.896);
27
+ --secondary-foreground: oklch(0.208 0.042 265.755);
28
+ --muted: oklch(0.968 0.007 247.896);
29
+ --muted-foreground: oklch(0.554 0.046 257.417);
30
+ --accent: oklch(0.968 0.007 247.896);
31
+ --accent-foreground: oklch(0.208 0.042 265.755);
32
+ --destructive: oklch(0.577 0.245 27.325);
33
+ --border: oklch(0.929 0.013 255.508);
34
+ --input: oklch(0.929 0.013 255.508);
35
+ --ring: oklch(0.704 0.04 256.788);
36
+ --chart-1: oklch(0.646 0.222 41.116);
37
+ --chart-2: oklch(0.6 0.118 184.704);
38
+ --chart-3: oklch(0.398 0.07 227.392);
39
+ --chart-4: oklch(0.828 0.189 84.429);
40
+ --chart-5: oklch(0.769 0.188 70.08);
41
+ --sidebar: oklch(0.984 0.003 247.858);
42
+ --sidebar-foreground: oklch(0.129 0.042 264.695);
43
+ --sidebar-primary: oklch(0.208 0.042 265.755);
44
+ --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
45
+ --sidebar-accent: oklch(0.968 0.007 247.896);
46
+ --sidebar-accent-foreground: oklch(0.208 0.042 265.755);
47
+ --sidebar-border: oklch(0.929 0.013 255.508);
48
+ --sidebar-ring: oklch(0.704 0.04 256.788);
49
+ }
50
+
51
+ .dark {
52
+ --background: oklch(0.129 0.042 264.695);
53
+ --foreground: oklch(0.984 0.003 247.858);
54
+ --card: oklch(0.208 0.042 265.755);
55
+ --card-foreground: oklch(0.984 0.003 247.858);
56
+ --popover: oklch(0.208 0.042 265.755);
57
+ --popover-foreground: oklch(0.984 0.003 247.858);
58
+ --primary: oklch(0.929 0.013 255.508);
59
+ --primary-foreground: oklch(0.208 0.042 265.755);
60
+ --secondary: oklch(0.279 0.041 260.031);
61
+ --secondary-foreground: oklch(0.984 0.003 247.858);
62
+ --muted: oklch(0.279 0.041 260.031);
63
+ --muted-foreground: oklch(0.704 0.04 256.788);
64
+ --accent: oklch(0.279 0.041 260.031);
65
+ --accent-foreground: oklch(0.984 0.003 247.858);
66
+ --destructive: oklch(0.704 0.191 22.216);
67
+ --border: oklch(1 0 0 / 10%);
68
+ --input: oklch(1 0 0 / 15%);
69
+ --ring: oklch(0.551 0.027 264.364);
70
+ --chart-1: oklch(0.488 0.243 264.376);
71
+ --chart-2: oklch(0.696 0.17 162.48);
72
+ --chart-3: oklch(0.769 0.188 70.08);
73
+ --chart-4: oklch(0.627 0.265 303.9);
74
+ --chart-5: oklch(0.645 0.246 16.439);
75
+ --sidebar: oklch(0.208 0.042 265.755);
76
+ --sidebar-foreground: oklch(0.984 0.003 247.858);
77
+ --sidebar-primary: oklch(0.488 0.243 264.376);
78
+ --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
79
+ --sidebar-accent: oklch(0.279 0.041 260.031);
80
+ --sidebar-accent-foreground: oklch(0.984 0.003 247.858);
81
+ --sidebar-border: oklch(1 0 0 / 10%);
82
+ --sidebar-ring: oklch(0.551 0.027 264.364);
83
+ }
84
+
85
+ @theme inline {
86
+ --radius-sm: calc(var(--radius) - 4px);
87
+ --radius-md: calc(var(--radius) - 2px);
88
+ --radius-lg: var(--radius);
89
+ --radius-xl: calc(var(--radius) + 4px);
90
+ --color-background: var(--background);
91
+ --color-foreground: var(--foreground);
92
+ --color-card: var(--card);
93
+ --color-card-foreground: var(--card-foreground);
94
+ --color-popover: var(--popover);
95
+ --color-popover-foreground: var(--popover-foreground);
96
+ --color-primary: var(--primary);
97
+ --color-primary-foreground: var(--primary-foreground);
98
+ --color-secondary: var(--secondary);
99
+ --color-secondary-foreground: var(--secondary-foreground);
100
+ --color-muted: var(--muted);
101
+ --color-muted-foreground: var(--muted-foreground);
102
+ --color-accent: var(--accent);
103
+ --color-accent-foreground: var(--accent-foreground);
104
+ --color-destructive: var(--destructive);
105
+ --color-border: var(--border);
106
+ --color-input: var(--input);
107
+ --color-ring: var(--ring);
108
+ --color-chart-1: var(--chart-1);
109
+ --color-chart-2: var(--chart-2);
110
+ --color-chart-3: var(--chart-3);
111
+ --color-chart-4: var(--chart-4);
112
+ --color-chart-5: var(--chart-5);
113
+ --color-sidebar: var(--sidebar);
114
+ --color-sidebar-foreground: var(--sidebar-foreground);
115
+ --color-sidebar-primary: var(--sidebar-primary);
116
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
117
+ --color-sidebar-accent: var(--sidebar-accent);
118
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
119
+ --color-sidebar-border: var(--sidebar-border);
120
+ --color-sidebar-ring: var(--sidebar-ring);
121
+ }
122
+
123
+ @layer base {
124
+ * {
125
+ @apply border-border outline-ring/50;
126
+ }
127
+ body {
128
+ @apply bg-background text-foreground;
129
+ font-family: var(--font-sans);
130
+ font-feature-settings:
131
+ "rlig" 1,
132
+ "calt" 1;
133
+ }
134
+ }
@@ -0,0 +1,5 @@
1
+ import { defineWidget } from "@anywidget/svelte";
2
+ import ConfigFileManager from "./ConfigFileManager.svelte";
3
+ import "./app.css"; // Import styles
4
+
5
+ export default defineWidget(ConfigFileManager);
@@ -0,0 +1,20 @@
1
+ {
2
+ "metadata": {
3
+ "version": "stores_nb_state-init-v0",
4
+ "saved_at": "2025-11-04T00:25:58.747765+00:00"
5
+ },
6
+ "data": {
7
+ "selections": {
8
+ "active_panel": ["summary"],
9
+ "harvest_window_min": 1,
10
+ "highlight_experimental": false,
11
+ "notes": "Monitor harvest cadence",
12
+ "orchard": ["Cloudberry Basin"],
13
+ "orchard_query": "Cloudberry",
14
+ "region": ["Emerald Belt"],
15
+ "show_outliers": true,
16
+ "view_mode": ["Overview"],
17
+ "yield_target": 40
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,10 @@
1
+ <script>
2
+ let count = $state(0)
3
+ const increment = () => {
4
+ count += 1
5
+ }
6
+ </script>
7
+
8
+ <button onclick={increment}>
9
+ count is {count}
10
+ </button>
@@ -0,0 +1,137 @@
1
+ <script lang="ts">
2
+ import * as Card from "$lib/components/ui/card/index.js";
3
+ import { Button } from "$lib/components/ui/button/index.js";
4
+ import { FileDropZone, displaySize } from "$lib/components/ui/file-drop-zone";
5
+ import { Badge } from "$lib/components/ui/badge/index.js";
6
+ import ConfigViewerPanel from "$lib/components/file-drop/ConfigViewerPanel.svelte";
7
+ import type { FileDropZoneProps } from "$lib/components/ui/file-drop-zone";
8
+ import type { BoundFile } from "$lib/hooks/use-file-bindings";
9
+ import { X } from "@lucide/svelte";
10
+
11
+ const {
12
+ file,
13
+ rawContents,
14
+ parsedContents,
15
+ baselineContents,
16
+ savedAtLabel,
17
+ versionLabel,
18
+ dirty,
19
+ error,
20
+ maxFiles,
21
+ onUpload,
22
+ onFileRejected,
23
+ onRemove,
24
+ onLoad,
25
+ disableLoad,
26
+ wrappedContents,
27
+ wrappedParsed,
28
+ } = $props<{
29
+ file?: BoundFile;
30
+ rawContents?: string;
31
+ parsedContents?: unknown;
32
+ baselineContents?: unknown;
33
+ savedAtLabel?: string;
34
+ versionLabel?: string;
35
+ dirty?: boolean;
36
+ error?: string;
37
+ maxFiles: number;
38
+ onUpload: FileDropZoneProps["onUpload"];
39
+ onFileRejected?: FileDropZoneProps["onFileRejected"];
40
+ onRemove: () => void;
41
+ onLoad: () => void;
42
+ disableLoad?: boolean;
43
+ wrappedContents?: string;
44
+ wrappedParsed?: unknown;
45
+ }>();
46
+ </script>
47
+
48
+ <Card.Root>
49
+ <Card.Header>
50
+ <Card.Title>Browse Configs</Card.Title>
51
+ <Card.Description>
52
+ Select a config file to view before loading.
53
+ </Card.Description>
54
+ </Card.Header>
55
+ <Card.Content>
56
+ <div class="space-y-4">
57
+ {#if !file}
58
+ <FileDropZone
59
+ {maxFiles}
60
+ fileCount={file ? 1 : 0}
61
+ onUpload={onUpload}
62
+ onFileRejected={onFileRejected}
63
+ />
64
+ {:else}
65
+ <div class="space-y-3">
66
+ <div
67
+ class="flex items-center justify-between rounded-lg border border-zinc-200 bg-white p-3 text-sm shadow-sm dark:border-zinc-700 dark:bg-zinc-900"
68
+ >
69
+ <div>
70
+ <p class="font-medium text-zinc-800 dark:text-zinc-100">{file.name}</p>
71
+ {#if savedAtLabel || versionLabel}
72
+ <div class="mt-1 flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
73
+ {#if savedAtLabel}
74
+ <span>Saved {savedAtLabel}</span>
75
+ {/if}
76
+ {#if versionLabel}
77
+ <Badge variant="secondary" class="px-2 py-0.5 text-[0.65rem]">
78
+ {versionLabel}
79
+ </Badge>
80
+ {/if}
81
+ {#if dirty}
82
+ <Badge variant="secondary" class="bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-200">
83
+ Unsaved changes
84
+ </Badge>
85
+ {/if}
86
+ <span>{displaySize(file.size)}</span>
87
+ </div>
88
+ {:else}
89
+ <p class="text-xs text-zinc-500 dark:text-zinc-400">
90
+ {displaySize(file.size)} · {file.type || "unknown type"}
91
+ </p>
92
+ {/if}
93
+ </div>
94
+
95
+ <div class="flex gap-2">
96
+ <Button
97
+ variant="ghost"
98
+ class="w-30 bg-slate-50 shadow-sm"
99
+ onclick={onLoad}
100
+ disabled={!rawContents || disableLoad}
101
+ >
102
+ {disableLoad ? "Loaded" : "Load"}
103
+ </Button>
104
+ <Button
105
+ variant="ghost"
106
+ size="icon"
107
+ class="bg-red-100 shadow-sm"
108
+ onclick={onRemove}
109
+ >
110
+ <span class="sr-only">Remove</span>
111
+ <X class="size-4" />
112
+ </Button>
113
+ </div>
114
+ </div>
115
+
116
+ <ConfigViewerPanel
117
+ data={parsedContents}
118
+ rawJson={rawContents}
119
+ baselineData={baselineContents}
120
+ {dirty}
121
+ wrappedJson={wrappedContents}
122
+ wrappedData={wrappedParsed}
123
+ />
124
+ </div>
125
+ {/if}
126
+
127
+ {#if error}
128
+ <div
129
+ class="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-600 dark:border-red-500/40 dark:bg-red-950/40 dark:text-red-200"
130
+ >
131
+ <strong class="font-semibold">Upload error:</strong>
132
+ <span class="ml-2">{error}</span>
133
+ </div>
134
+ {/if}
135
+ </div>
136
+ </Card.Content>
137
+ </Card.Root>
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ import { createElement } from "react";
3
+ import { onDestroy, onMount } from "svelte";
4
+
5
+ const { data } = $props<{ data?: unknown }>();
6
+
7
+ let container: HTMLDivElement | null = null;
8
+ let root: import("react-dom/client").Root | null = null;
9
+ let JsonTreeCanvas:
10
+ | (typeof import("$lib/react/JsonTreeCanvas"))["JsonTreeCanvas"]
11
+ | null = null;
12
+
13
+ const mountReact = async () => {
14
+ const [{ createRoot }, module] = await Promise.all([
15
+ import("react-dom/client"),
16
+ import("$lib/react/JsonTreeCanvas"),
17
+ ]);
18
+
19
+ JsonTreeCanvas = module.JsonTreeCanvas;
20
+ if (container) {
21
+ root = createRoot(container);
22
+ root.render(createElement(JsonTreeCanvas, { data }));
23
+ }
24
+ };
25
+
26
+ onMount(() => {
27
+ mountReact();
28
+ });
29
+
30
+ $effect(() => {
31
+ if (root && JsonTreeCanvas) {
32
+ root.render(createElement(JsonTreeCanvas, { data }));
33
+ }
34
+ });
35
+
36
+ onDestroy(() => {
37
+ root?.unmount();
38
+ root = null;
39
+ });
40
+ </script>
41
+
42
+ <div
43
+ class="flex h-96 w-full items-center justify-center overflow-hidden rounded-md border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950"
44
+ >
45
+ <div
46
+ bind:this={container}
47
+ class="h-full w-full"
48
+ aria-label="Complex JSON graph viewer"
49
+ ></div>
50
+ </div>
51
+
52
+ <style>
53
+ :global(.json-tree-placeholder),
54
+ :global(.json-tree-error) {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ height: 100%;
59
+ width: 100%;
60
+ padding: 1.5rem;
61
+ text-align: center;
62
+ color: var(--viewer-muted, rgba(63, 63, 70, 0.7));
63
+ }
64
+
65
+ :global(.json-tree-error) {
66
+ color: #ef4444;
67
+ }
68
+
69
+ :global(.json-tree-node) {
70
+ fill: #18181b;
71
+ stroke: rgba(63, 63, 70, 0.25);
72
+ stroke-width: 1;
73
+ rx: 12;
74
+ ry: 12;
75
+ }
76
+
77
+ :global(.json-tree-node text) {
78
+ font-size: 12px;
79
+ fill: #f4f4f5;
80
+ }
81
+
82
+ :global(.json-tree-node--root) {
83
+ fill: #2563eb;
84
+ }
85
+
86
+ :global(.json-tree-node--root text) {
87
+ font-weight: 600;
88
+ }
89
+
90
+ :global(.json-tree-edge path) {
91
+ stroke: rgba(37, 99, 235, 0.45);
92
+ stroke-width: 1.5;
93
+ }
94
+ </style>
@@ -0,0 +1,282 @@
1
+ <script lang="ts">
2
+ import * as Card from "$lib/components/ui/card";
3
+ import { Button } from "$lib/components/ui/button";
4
+ import SimpleJsonViewer from "./SimpleJsonViewer.svelte";
5
+ import ComplexJsonViewer from "./ComplexJsonViewer.svelte";
6
+ type ViewerMode = "simple" | "complex";
7
+ type ViewerSource = "data" | "wrapped";
8
+
9
+ const {
10
+ data,
11
+ rawJson,
12
+ baselineData,
13
+ dirty = false,
14
+ initialMode = "simple",
15
+ wrappedJson,
16
+ wrappedData,
17
+ } =
18
+ $props<{
19
+ data?: unknown;
20
+ rawJson?: string;
21
+ baselineData?: unknown;
22
+ dirty?: boolean;
23
+ initialMode?: ViewerMode;
24
+ wrappedJson?: string;
25
+ wrappedData?: unknown;
26
+ }>();
27
+
28
+ const complexModeEnabled = false;
29
+
30
+ let mode = $state<ViewerMode>(complexModeEnabled ? initialMode : "simple");
31
+ let source = $state<ViewerSource>("data");
32
+ let copyState = $state<"idle" | "copied" | "error">("idle");
33
+ const hasWrappedView = $derived.by(() => Boolean(wrappedJson || wrappedData));
34
+
35
+ const switchMode = (nextMode: ViewerMode) => {
36
+ if (!complexModeEnabled && nextMode === "complex") {
37
+ mode = "simple";
38
+ return;
39
+ }
40
+ mode = nextMode;
41
+ };
42
+
43
+ const switchSource = (nextSource: ViewerSource) => {
44
+ if (!hasWrappedView) {
45
+ source = "data";
46
+ return;
47
+ }
48
+ source = nextSource;
49
+ };
50
+
51
+ $effect(() => {
52
+ if (!hasWrappedView) {
53
+ source = "data";
54
+ }
55
+ });
56
+
57
+ const effectiveRawJson = $derived.by(() =>
58
+ source === "data" ? rawJson : wrappedJson ?? rawJson,
59
+ );
60
+ const effectiveData = $derived.by(() =>
61
+ source === "data" ? data : wrappedData ?? data,
62
+ );
63
+ const effectiveDirty = $derived.by(() => (source === "data" ? dirty : false));
64
+ const effectiveBaseline = $derived.by(() =>
65
+ source === "data" ? baselineData : undefined,
66
+ );
67
+
68
+ const copyToClipboard = async () => {
69
+ const payload =
70
+ effectiveRawJson ??
71
+ (effectiveData !== undefined
72
+ ? JSON.stringify(effectiveData, null, 2)
73
+ : undefined);
74
+
75
+ if (!payload) return;
76
+
77
+ try {
78
+ await navigator.clipboard.writeText(payload);
79
+ copyState = "copied";
80
+ setTimeout(() => {
81
+ copyState = "idle";
82
+ }, 1800);
83
+ } catch {
84
+ copyState = "error";
85
+ }
86
+ };
87
+ </script>
88
+
89
+ <Card.Root>
90
+ <Card.Header>
91
+ <div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
92
+ <div>
93
+ <Card.Title>Config Preview</Card.Title>
94
+ <Card.Description>
95
+ Inspect the selected file in a formatted tree.
96
+ </Card.Description>
97
+ {#if hasWrappedView}
98
+ <div class="mt-2 inline-flex rounded-md border border-zinc-200 bg-white p-0.5 text-xs dark:border-zinc-800 dark:bg-zinc-900">
99
+ <button
100
+ type="button"
101
+ class="viewer-toggle"
102
+ class:viewer-toggle-active={source === "data"}
103
+ onclick={() => switchSource("data")}
104
+ >
105
+ Editable Data
106
+ </button>
107
+ <button
108
+ type="button"
109
+ class="viewer-toggle"
110
+ class:viewer-toggle-active={source === "wrapped"}
111
+ onclick={() => switchSource("wrapped")}
112
+ >
113
+ Saved Payload
114
+ </button>
115
+ </div>
116
+ {/if}
117
+ </div>
118
+
119
+ <div class="flex flex-col items-stretch gap-2 sm:flex-row sm:items-center">
120
+ {#if complexModeEnabled}
121
+ <div class="flex rounded-md border border-zinc-200 bg-white p-0.5 dark:border-zinc-800 dark:bg-zinc-900">
122
+ <button
123
+ type="button"
124
+ class="viewer-toggle"
125
+ class:viewer-toggle-active={mode === "simple"}
126
+ onclick={() => switchMode("simple")}
127
+ >
128
+ Simple
129
+ </button>
130
+ <button
131
+ type="button"
132
+ class="viewer-toggle"
133
+ class:viewer-toggle-active={mode === "complex"}
134
+ onclick={() => switchMode("complex")}
135
+ >
136
+ Complex
137
+ </button>
138
+ </div>
139
+ {/if}
140
+
141
+ <Button
142
+ variant="outline"
143
+ size="sm"
144
+ onclick={copyToClipboard}
145
+ disabled={effectiveData === undefined && !effectiveRawJson}
146
+ >
147
+ {copyState === "copied" ? "Copied!" : "Copy JSON"}
148
+ </Button>
149
+ </div>
150
+ </div>
151
+
152
+ {#if copyState === "error"}
153
+ <p class="text-xs font-medium text-red-500">
154
+ Clipboard copy failed. Try copying manually.
155
+ </p>
156
+ {/if}
157
+ </Card.Header>
158
+
159
+ <Card.Content class="space-y-4">
160
+ {#if mode === "simple" || !complexModeEnabled}
161
+ <div
162
+ class="viewer-shell rounded-xl border border-zinc-100 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900"
163
+ >
164
+ <SimpleJsonViewer
165
+ data={effectiveData}
166
+ baseline={effectiveDirty ? effectiveBaseline : undefined}
167
+ dirty={effectiveDirty}
168
+ depth={3}
169
+ preserveKeyOrder={source === "wrapped"}
170
+ />
171
+ </div>
172
+ {:else}
173
+ <div
174
+ class="rounded-xl border border-zinc-100 bg-white shadow-sm dark:border-zinc-800 dark:bg-zinc-900"
175
+ >
176
+ <ComplexJsonViewer data={effectiveData} />
177
+ </div>
178
+ {/if}
179
+
180
+ {#if effectiveDirty && effectiveBaseline}
181
+ <div class="diff-legend" role="note">
182
+ <span class="legend-item">
183
+ <span class="legend-swatch legend-swatch-added"></span>
184
+ New value
185
+ </span>
186
+ <span class="legend-item">
187
+ <span class="legend-swatch legend-swatch-removed"></span>
188
+ Removed value
189
+ </span>
190
+ </div>
191
+ {/if}
192
+
193
+ </Card.Content>
194
+ </Card.Root>
195
+
196
+ <style>
197
+ .viewer-toggle {
198
+ display: inline-flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ padding: 0.35rem 0.85rem;
202
+ font-size: 0.75rem;
203
+ font-weight: 500;
204
+ border-radius: 0.45rem;
205
+ color: #3f3f46;
206
+ transition: all 0.15s ease;
207
+ }
208
+
209
+ .viewer-toggle:hover {
210
+ background-color: rgba(37, 99, 235, 0.08);
211
+ }
212
+
213
+ .viewer-toggle-active {
214
+ background-color: #2563eb;
215
+ color: white;
216
+ box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.15);
217
+ }
218
+
219
+ :global(.dark) .viewer-toggle {
220
+ color: #d4d4d8;
221
+ }
222
+
223
+ :global(.dark) .viewer-toggle:hover {
224
+ background-color: rgba(37, 99, 235, 0.2);
225
+ }
226
+
227
+ .viewer-shell {
228
+ max-height: 22rem;
229
+ overflow: auto;
230
+ padding: 1.25rem;
231
+ font-family: ui-monospace, SFMono-Regular, SFMono, Menlo, Monaco, Consolas,
232
+ "Liberation Mono", "Courier New", monospace;
233
+ font-size: 0.75rem;
234
+ line-height: 1.4;
235
+ color: #3f3f46;
236
+ }
237
+
238
+ .diff-legend {
239
+ display: flex;
240
+ gap: 1rem;
241
+ align-items: center;
242
+ font-size: 0.7rem;
243
+ color: #71717a;
244
+ }
245
+
246
+ .legend-item {
247
+ display: inline-flex;
248
+ align-items: center;
249
+ gap: 0.35rem;
250
+ }
251
+
252
+ .legend-swatch {
253
+ width: 0.75rem;
254
+ height: 0.75rem;
255
+ border-radius: 0.25rem;
256
+ }
257
+
258
+ .legend-swatch-added {
259
+ background: rgba(34, 197, 94, 0.2);
260
+ }
261
+
262
+ .legend-swatch-removed {
263
+ background: rgba(239, 68, 68, 0.25);
264
+ }
265
+
266
+ :global(.dark) .viewer-shell {
267
+ color: #e4e4e7;
268
+ background: rgba(24, 24, 27, 0.8);
269
+ }
270
+
271
+ :global(.dark) .diff-legend {
272
+ color: #a1a1aa;
273
+ }
274
+
275
+ :global(.dark) .legend-swatch-added {
276
+ background: rgba(34, 197, 94, 0.3);
277
+ }
278
+
279
+ :global(.dark) .legend-swatch-removed {
280
+ background: rgba(239, 68, 68, 0.35);
281
+ }
282
+ </style>