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
dr_widget/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Top-level package for dr_widget."""
2
+
3
+ from .widgets.config_file_manager import ConfigFileManager
4
+
5
+ __all__ = ["ConfigFileManager"]
dr_widget/py.typed ADDED
File without changes
@@ -0,0 +1,5 @@
1
+ """Widget exports for dr_widget."""
2
+
3
+ from .config_file_manager import ConfigFileManager
4
+
5
+ __all__ = ["ConfigFileManager"]
@@ -0,0 +1,24 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["svelte.svelte-vscode"]
3
+ }
@@ -0,0 +1,89 @@
1
+ # Config File Manager Widget
2
+
3
+ The `dr_widget` package ships an AnyWidget-powered config file manager so notebooks can load, inspect, edit, and save JSON configuration blobs without leaving the browser. The frontend lives under `src/dr_widget/widgets/config_file_manager`, is built with Svelte + Vite via Bun, and syncs its state to Python traitlets.
4
+
5
+ ## Repository Layout
6
+
7
+ - `src/dr_widget/widgets/config_file_manager/__init__.py` – AnyWidget class with initialization helpers and traitlet contracts.
8
+ - `src/dr_widget/widgets/config_file_manager/src/` – Svelte workspace (components live under `lib/`).
9
+ - `src/dr_widget/widgets/config_file_manager/static/` – Built bundle consumed by AnyWidget.
10
+ - `notebooks/config_file_manager_widget.py` – Marimo demo that exercises the widget end-to-end.
11
+
12
+ ## Traitlets
13
+
14
+ | Traitlet | Direction | Description |
15
+ | --- | --- | --- |
16
+ | `current_state` | ↔ | JSON string representing **user data only** (no metadata). |
17
+ | `baseline_state` | ↔ | Last saved value of `current_state`, used for dirty detection/diffs. |
18
+ | `version` | ↔ | String metadata displayed in the UI and written to disk alongside `current_state` (now nested under `metadata.version`). |
19
+ | `config_file` | ↔ | Path to the backing file (may be relative today). |
20
+ | `config_file_display` | ↔ | UI-friendly label derived from `config_file`. |
21
+ | `files` | ↔ | JSON array of uploaded files (`{ name, size, type }`). |
22
+ | `file_count` | ← | Derived from `files.length`; read-only in the UI. |
23
+ | `error` | ↔ | User-facing error message cleared automatically on recovery. |
24
+
25
+ Python helper properties (`current_data`, `baseline_data`, `is_dirty`) expose parsed state for notebooks.
26
+
27
+ ## Initialization Patterns
28
+
29
+ ```python
30
+ ConfigFileManager() # empty widget, user loads file via UI
31
+
32
+ ConfigFileManager(config_dict={"orchard": ["Basin"]}, version="exp_v1")
33
+ # - current_state populated with data dict
34
+ # - baseline_state empty → dirty until saved
35
+ # - config_file defaults to "exp_v1.json"
36
+
37
+ ConfigFileManager(config_file="/path/to/existing.json")
38
+ # - loads and migrates the file into new format
39
+ # - baseline_state matches current_state (clean)
40
+ # - version pulled from file metadata
41
+
42
+ ConfigFileManager(
43
+ config_file="/tmp/new.json",
44
+ config_dict={"selections": {"foo": True}},
45
+ version="v2",
46
+ )
47
+ # - writes wrapped payload {metadata:{version,saved_at},data}
48
+ # - baseline_state matches current_state
49
+ ```
50
+
51
+ Files saved through the UI (or via `config_file` + `config_dict`) are always written as:
52
+
53
+ ```json
54
+ {
55
+ "metadata": {
56
+ "version": "v1",
57
+ "saved_at": "2025-11-12T10:30:00Z"
58
+ },
59
+ "data": { ... user data ... }
60
+ }
61
+ ```
62
+
63
+ Older files that only contain `selections` or embed metadata at the top level are migrated into this structure when loaded, with legacy `version`/`saved_at` values relocated under `metadata`.
64
+
65
+ ## Frontend Behavior
66
+
67
+ - `use-file-bindings.ts` manages read/write loops for every synced traitlet so Marimo reactivity stays intact.
68
+ - `ConfigFileManager.svelte` derives dirty state by comparing `current_state` vs `baseline_state`, shows the currently loaded file name/version, and exposes Browse + Save panels.
69
+ - `SaveConfigPanel.svelte` wraps the current data with metadata before writing to disk (File System Access API when available, otherwise download).
70
+
71
+ ## Build & Test
72
+
73
+ ```bash
74
+ bun install
75
+ npx svelte-check --tsconfig src/dr_widget/widgets/config_file_manager/tsconfig.json
76
+ bun run build:config-file-manager
77
+ bun run build # aggregates widgets (currently same as line above)
78
+ uv build # packages the Python wheel with fresh static assets
79
+ ```
80
+
81
+ Manual validation: run `marimo run notebooks/config_file_manager_widget.py`, load/upload JSON configs (including legacy "selections" files), edit values, and save to disk. Confirm dirty badge toggles correctly and version changes propagate between the sidebar and save panel.
82
+
83
+ ## Contributing Tips
84
+
85
+ - Shared UI lives in `src/dr_widget/widgets/config_file_manager/src/lib/{components,hooks}`; prefer reusing hooks like `use-file-bindings`.
86
+ - Keep Tailwind utility classes grouped logically (layout → spacing → color → effects).
87
+ - Treat `node_modules/` as generated; never edit or commit them.
88
+ - Run `bunx prettier --write src/dr_widget/widgets/config_file_manager/src` before opening a PR.
89
+ - Document new traitlets or metadata fields in `docs/architecture.md` and notebook demos to keep Python + Svelte in sync.
@@ -0,0 +1,283 @@
1
+ """AnyWidget bindings for the config file manager widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional
9
+
10
+ import anywidget
11
+ import traitlets
12
+
13
+ __all__ = ["ConfigFileManager"]
14
+
15
+ _STATIC_DIR = Path(__file__).parent / "static"
16
+
17
+
18
+ def _normalize_version(value: Optional[str]) -> str:
19
+ if value is None:
20
+ return "default_v0"
21
+ value = str(value).strip()
22
+ return value or "default_v0"
23
+
24
+
25
+ def _default_config_name(version: str) -> str:
26
+ safe = ''.join(ch if ch.isalnum() or ch in {"-", "_"} else "_" for ch in version)
27
+ safe = safe or "default_v0"
28
+ return f"{safe}.json"
29
+
30
+
31
+ def _resolve_config_path(value: str | Path | None) -> str:
32
+ if value is None:
33
+ return ""
34
+
35
+ raw = str(value).strip()
36
+ if not raw:
37
+ return ""
38
+
39
+ candidate = Path(raw).expanduser()
40
+ if not candidate.is_absolute():
41
+ candidate = Path.cwd() / candidate
42
+
43
+ return str(candidate.resolve())
44
+
45
+
46
+ def _serialize_user_state(data: Dict[str, Any]) -> str:
47
+ """Return a JSON string for the user-facing state or an empty string."""
48
+
49
+ if not data:
50
+ return ""
51
+
52
+ try:
53
+ return json.dumps(data, sort_keys=True, separators=(",", ":"))
54
+ except (TypeError, ValueError) as exc: # pragma: no cover - defensive
55
+ raise ValueError("Config state must be JSON serializable") from exc
56
+
57
+
58
+ def _ensure_mapping(value: Optional[Dict[str, Any]]) -> Dict[str, Any]:
59
+ if value is None:
60
+ return {}
61
+ if not isinstance(value, dict):
62
+ raise TypeError("config_dict must be a mapping of keys to values")
63
+ return value
64
+
65
+
66
+ def _utc_timestamp() -> str:
67
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
68
+
69
+
70
+ def _normalize_payload(payload: Dict[str, Any]) -> Dict[str, Any]:
71
+ """Ensure the payload follows the new metadata/data contract."""
72
+
73
+ normalized: Dict[str, Any] = dict(payload)
74
+ data = normalized.get("data")
75
+
76
+ metadata_candidate = normalized.get("metadata")
77
+ if isinstance(metadata_candidate, dict):
78
+ metadata: Dict[str, Any] = dict(metadata_candidate)
79
+ else:
80
+ metadata = {}
81
+
82
+ for legacy_key in ("version", "saved_at"):
83
+ if legacy_key in normalized and legacy_key not in metadata:
84
+ metadata[legacy_key] = normalized.pop(legacy_key)
85
+
86
+ if not isinstance(data, dict):
87
+ user_data: Dict[str, Any] = {}
88
+
89
+ selections = normalized.pop("selections", None)
90
+ if isinstance(selections, dict):
91
+ user_data.setdefault("selections", selections)
92
+
93
+ for key in list(normalized.keys()):
94
+ if key in {"version", "saved_at", "data", "metadata"}:
95
+ continue
96
+ user_data[key] = normalized.pop(key)
97
+
98
+ data = user_data
99
+
100
+ normalized["metadata"] = metadata
101
+ normalized["data"] = data if isinstance(data, dict) else {}
102
+ return normalized
103
+
104
+
105
+ def _load_config_from_file(path: Path) -> Dict[str, Any]:
106
+ try:
107
+ raw = path.read_text(encoding="utf-8")
108
+ except FileNotFoundError:
109
+ raise
110
+ except OSError as exc: # pragma: no cover - filesystem specific
111
+ raise IOError(f"Unable to read config file: {path}") from exc
112
+
113
+ try:
114
+ parsed = json.loads(raw)
115
+ except json.JSONDecodeError as exc:
116
+ raise ValueError(f"Config file must contain valid JSON: {path}") from exc
117
+
118
+ if not isinstance(parsed, dict):
119
+ raise ValueError("Config file root must be a JSON object")
120
+
121
+ return _normalize_payload(parsed)
122
+
123
+
124
+ def _write_config_to_file(path: Path, *, data: Dict[str, Any], version: str) -> str:
125
+ saved_at = _utc_timestamp()
126
+ payload = {
127
+ "metadata": {
128
+ "version": version,
129
+ "saved_at": saved_at,
130
+ "save_path": str(path),
131
+ },
132
+ "data": data,
133
+ }
134
+
135
+ serialized = json.dumps(payload, indent=2, sort_keys=True)
136
+ path.parent.mkdir(parents=True, exist_ok=True)
137
+ path.write_text(serialized + "\n", encoding="utf-8")
138
+ return saved_at
139
+
140
+
141
+ def _file_binding_entry(path: Path) -> Dict[str, Any]:
142
+ try:
143
+ size = path.stat().st_size
144
+ except OSError:
145
+ size = 0
146
+
147
+ return {
148
+ "name": path.name,
149
+ "size": size,
150
+ "type": "application/json",
151
+ }
152
+
153
+
154
+ class ConfigFileManager(anywidget.AnyWidget):
155
+ """Config file manager widget for notebooks."""
156
+
157
+ # AnyWidget expects module references pointing at the built assets on disk.
158
+ _esm = _STATIC_DIR / "index.js"
159
+ _css = _STATIC_DIR / "style.css"
160
+
161
+ current_state = traitlets.Unicode("").tag(sync=True)
162
+ baseline_state = traitlets.Unicode("").tag(sync=True)
163
+ config_file = traitlets.Unicode("").tag(sync=True)
164
+ config_file_display = traitlets.Unicode("").tag(sync=True)
165
+ version = traitlets.Unicode("default_v0").tag(sync=True)
166
+ saved_at = traitlets.Unicode("").tag(sync=True)
167
+ files = traitlets.Unicode("[]").tag(sync=True)
168
+ file_count = traitlets.Int(0).tag(sync=True)
169
+ error = traitlets.Unicode("").tag(sync=True)
170
+
171
+ def __init__(
172
+ self,
173
+ config_file: str | Path | None = None,
174
+ config_dict: Optional[Dict[str, Any]] = None,
175
+ version: str = "default_v0",
176
+ **kwargs: Any,
177
+ ) -> None:
178
+ super().__init__(**kwargs)
179
+
180
+ normalized_version = _normalize_version(version)
181
+ self.version = normalized_version
182
+ self.current_state = ""
183
+ self.baseline_state = ""
184
+ self.config_file = ""
185
+ self.saved_at = ""
186
+
187
+ if config_file is None and config_dict is None:
188
+ return
189
+
190
+ if config_file is None:
191
+ user_data = _ensure_mapping(config_dict)
192
+ self.current_state = _serialize_user_state(user_data)
193
+ # No baseline until the data is persisted via the UI.
194
+ self.baseline_state = ""
195
+ default_name = _default_config_name(self.version)
196
+ default_path = _resolve_config_path(default_name)
197
+ self.config_file = default_path
198
+ return
199
+
200
+ resolved_path = _resolve_config_path(config_file)
201
+ path = Path(resolved_path)
202
+
203
+ if config_dict is not None:
204
+ if path.exists():
205
+ raise FileExistsError(
206
+ f"Config file already exists: {path}. Refusing to overwrite."
207
+ )
208
+
209
+ user_data = _ensure_mapping(config_dict)
210
+ _write_config_to_file(path, data=user_data, version=self.version)
211
+ elif not path.exists():
212
+ raise FileNotFoundError(f"Config file does not exist: {path}")
213
+
214
+ payload = _load_config_from_file(path)
215
+ file_data = payload.get("data")
216
+ user_state = file_data if isinstance(file_data, dict) else {}
217
+ serialized_state = _serialize_user_state(user_state)
218
+
219
+ metadata = payload.get("metadata")
220
+ if not isinstance(metadata, dict):
221
+ metadata = {}
222
+
223
+ payload_version = metadata.get("version")
224
+ if payload_version is not None:
225
+ version_str = _normalize_version(str(payload_version))
226
+ self.version = version_str
227
+
228
+ self.config_file = str(path)
229
+ self.current_state = serialized_state
230
+ self.baseline_state = serialized_state
231
+ saved_at_value = metadata.get("saved_at")
232
+ if saved_at_value:
233
+ self.saved_at = str(saved_at_value)
234
+ else:
235
+ self.saved_at = ""
236
+
237
+ file_entry = _file_binding_entry(path)
238
+ self.files = json.dumps([file_entry])
239
+ self.file_count = 1
240
+
241
+ def _parse_state(self, value: str) -> Dict[str, Any]:
242
+ if not value:
243
+ return {}
244
+
245
+ try:
246
+ parsed = json.loads(value)
247
+ except json.JSONDecodeError:
248
+ return {}
249
+
250
+ if isinstance(parsed, dict):
251
+ return parsed
252
+
253
+ return {}
254
+
255
+ @property
256
+ def current_data(self) -> Dict[str, Any]:
257
+ """Return the parsed current_state JSON payload as a dict."""
258
+
259
+ return self._parse_state(self.current_state)
260
+
261
+ @property
262
+ def baseline_data(self) -> Dict[str, Any]:
263
+ """Return the parsed baseline_state JSON payload as a dict."""
264
+
265
+ return self._parse_state(self.baseline_state)
266
+
267
+ @property
268
+ def is_dirty(self) -> bool:
269
+ """True if the current state differs from the last saved baseline."""
270
+
271
+ return self.current_data != self.baseline_data
272
+
273
+ @traitlets.validate("config_file")
274
+ def _validate_config_file(self, proposal: traitlets.Bunch) -> str:
275
+ return _resolve_config_path(proposal["value"])
276
+
277
+ @traitlets.observe("config_file")
278
+ def _observe_config_file(self, change: traitlets.Bunch) -> None:
279
+ value = change["new"]
280
+ if value:
281
+ self.config_file_display = Path(value).name
282
+ else:
283
+ self.config_file_display = ""
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://shadcn-svelte.com/schema.json",
3
+ "tailwind": {
4
+ "css": "src/app.css",
5
+ "baseColor": "slate"
6
+ },
7
+ "aliases": {
8
+ "components": "$lib/components",
9
+ "utils": "$lib/utils",
10
+ "ui": "$lib/components/ui",
11
+ "hooks": "$lib/hooks",
12
+ "lib": "$lib"
13
+ },
14
+ "typescript": true,
15
+ "registry": "https://shadcn-svelte.com/registry"
16
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>File Drop Widget - Dev Preview</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://unpkg.com/jsrepo@2.5.1/schemas/project-config.json",
3
+ "repos": ["@ieedan/shadcn-svelte-extras"],
4
+ "includeTests": false,
5
+ "includeDocs": false,
6
+ "watermark": true,
7
+ "formatter": "prettier",
8
+ "configFiles": {
9
+ "Shadcn Svelte Extras Cursor Rules": "./../../../../.cursor/rules/shadcn-svelte-extras.mdc"
10
+ },
11
+ "paths": {
12
+ "*": "./src/lib",
13
+ "ui": "./src/lib/components/ui",
14
+ "actions": "./src/lib/actions",
15
+ "hooks": "./src/lib/hooks",
16
+ "utils": "./src/lib/utils"
17
+ }
18
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@dr-widget/config-file-manager",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "vite build",
8
+ "dev": "vite dev",
9
+ "preview": "vite preview",
10
+ "check": "svelte-check --watch --tsconfig ./tsconfig.json"
11
+ },
12
+ "dependencies": {
13
+ "@anywidget/svelte": "^0.1.0",
14
+ "@zerodevx/svelte-json-view": "^1.0.11",
15
+ "elkjs": "^0.11.0",
16
+ "lucide-svelte": "^0.552.0",
17
+ "react": "18.2.0",
18
+ "react-dom": "18.2.0",
19
+ "react-zoom-pan-pinch": "^3.7.0",
20
+ "reaflow": "^5.4.1",
21
+ "svelte": "^5.43.2"
22
+ },
23
+ "devDependencies": {
24
+ "@internationalized/date": "^3.8.1",
25
+ "@lucide/svelte": "^0.544.0",
26
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
27
+ "@tailwindcss/postcss": "^4.0.9",
28
+ "@types/bun": "latest",
29
+ "@types/react": "18.2.74",
30
+ "@types/react-dom": "18.2.25",
31
+ "@vitejs/plugin-react-swc": "3.7.0",
32
+ "autoprefixer": "^10.4.20",
33
+ "bits-ui": "^2.11.0",
34
+ "clsx": "^2.1.1",
35
+ "postcss": "^8.4.49",
36
+ "prettier": "^3.6.2",
37
+ "runed": "^0.31.1",
38
+ "svelte-check": "^4.3.3",
39
+ "tailwind-merge": "^3.3.1",
40
+ "tailwind-variants": "^3.1.1",
41
+ "tailwindcss": "^4.1.16",
42
+ "tw-animate-css": "^1.4.0",
43
+ "vaul-svelte": "1.0.0-next.7",
44
+ "vite": "^5.4.11"
45
+ },
46
+ "peerDependencies": {
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }
@@ -0,0 +1,6 @@
1
+ import tailwindcss from '@tailwindcss/postcss';
2
+ import autoprefixer from 'autoprefixer';
3
+
4
+ export default {
5
+ plugins: [tailwindcss(), autoprefixer()]
6
+ };
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1,62 @@
1
+ <script>
2
+ import { Separator } from "./lib/components/ui/separator/";
3
+ import ConfigFileManager from "./ConfigFileManager.svelte";
4
+
5
+ // Simulate anywidget bindings for testing
6
+ let bindings = $state({
7
+ files: "",
8
+ file_count: 0,
9
+ error: "",
10
+ });
11
+ </script>
12
+
13
+ <main class="mx-auto max-w-5xl p-8 font-sans">
14
+ <div class="flex flex-col gap-8">
15
+ <div class="space-y-2">
16
+ <h1 class="text-3xl font-semibold text-zinc-900 dark:text-zinc-100">
17
+ Widget
18
+ </h1>
19
+ </div>
20
+
21
+ <ConfigFileManager {bindings} />
22
+
23
+ <div
24
+ class="mt-4 rounded-lg border border-zinc-200 bg-zinc-50 p-6 dark:border-zinc-700 dark:bg-zinc-900"
25
+ >
26
+ <h2 class="text-lg font-medium text-zinc-800 dark:text-zinc-100">
27
+ Debug Output
28
+ </h2>
29
+
30
+ <div
31
+ class="mt-4 rounded-md bg-white p-3 text-sm shadow-sm dark:bg-zinc-800"
32
+ >
33
+ <strong class="font-semibold text-zinc-700 dark:text-zinc-200"
34
+ >File Count:</strong
35
+ >
36
+ <span class="ml-2 text-zinc-600 dark:text-zinc-300"
37
+ >{bindings.file_count}</span
38
+ >
39
+ </div>
40
+
41
+ <div
42
+ class="mt-4 rounded-md bg-white p-3 text-sm shadow-sm dark:bg-zinc-800"
43
+ >
44
+ <strong class="font-semibold text-zinc-700 dark:text-zinc-200"
45
+ >Files Data:</strong
46
+ >
47
+ <pre
48
+ class="mt-2 overflow-x-auto rounded-md bg-zinc-900/10 p-2 font-mono text-xs dark:bg-zinc-50/10">
49
+ {bindings.files || "(empty)"}</pre>
50
+ </div>
51
+
52
+ {#if bindings.error}
53
+ <div
54
+ class="mt-4 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-300"
55
+ >
56
+ <strong class="font-semibold">Error:</strong>
57
+ <span class="ml-2">{bindings.error}</span>
58
+ </div>
59
+ {/if}
60
+ </div>
61
+ </div>
62
+ </main>