@alepha/devtools 0.16.1 → 0.19.1

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 (236) hide show
  1. package/README.md +1 -5
  2. package/dist/index.browser.js +224 -0
  3. package/dist/index.browser.js.map +1 -0
  4. package/dist/index.d.ts +349 -321
  5. package/dist/index.js +293 -186
  6. package/dist/index.js.map +1 -1
  7. package/package.json +30 -23
  8. package/src/assets.ts +6 -0
  9. package/src/{api/entities → entities}/logs.ts +2 -2
  10. package/src/index.browser.ts +11 -0
  11. package/src/index.shared.ts +15 -0
  12. package/src/index.ts +11 -37
  13. package/src/{api/providers → providers}/DevToolsMetadataProvider.ts +84 -47
  14. package/src/providers/DevToolsProvider.ts +280 -0
  15. package/src/{api/schemas → schemas}/DevActionMetadata.ts +8 -0
  16. package/src/{api/schemas → schemas}/DevEntityMetadata.ts +3 -0
  17. package/src/{api/schemas → schemas}/DevMetadata.ts +13 -2
  18. package/src/{api/schemas → schemas}/DevPageMetadata.ts +3 -0
  19. package/src/{api/schemas → schemas}/DevTopicMetadata.ts +1 -0
  20. package/src/ui/AppRouter.tsx +55 -59
  21. package/src/ui/components/DevLayout.tsx +104 -84
  22. package/src/ui/components/configuration/ConfigAtoms.page.tsx +5 -0
  23. package/src/ui/components/configuration/ConfigAtoms.tsx +511 -0
  24. package/src/ui/components/configuration/ConfigEnv.page.tsx +5 -0
  25. package/src/ui/components/configuration/ConfigEnv.tsx +230 -0
  26. package/src/ui/components/configuration/DevConfiguration.tsx +36 -0
  27. package/src/ui/components/configuration/index.ts +3 -0
  28. package/src/ui/components/dashboard/DevDashboard.tsx +482 -0
  29. package/src/ui/components/database/DatabaseEditor.page.tsx +23 -0
  30. package/src/ui/components/database/DatabaseEditor.tsx +399 -0
  31. package/src/ui/components/database/DatabaseErd.page.tsx +28 -0
  32. package/src/ui/components/database/DatabaseErd.tsx +107 -0
  33. package/src/ui/components/database/DevDatabase.tsx +36 -0
  34. package/src/ui/components/database/EntityNode.tsx +83 -0
  35. package/src/ui/components/explorer/DevExplorer.tsx +351 -0
  36. package/src/ui/components/explorer/ExplorerTree.tsx +178 -0
  37. package/src/ui/components/explorer/panels/DevPanelAction.tsx +499 -0
  38. package/src/ui/components/explorer/panels/DevPanelCache.tsx +73 -0
  39. package/src/ui/components/explorer/panels/DevPanelPage.tsx +96 -0
  40. package/src/ui/components/explorer/panels/DevPanelQueue.tsx +51 -0
  41. package/src/ui/components/explorer/panels/DevPanelTopic.tsx +56 -0
  42. package/src/ui/components/explorer/panels/index.ts +5 -0
  43. package/src/ui/components/graph/DevDependencyGraph.tsx +35 -60
  44. package/src/ui/components/graph/GraphControls.tsx +10 -11
  45. package/src/ui/components/graph/NodeDetails.tsx +22 -29
  46. package/src/ui/components/graph/ProviderNode.tsx +4 -4
  47. package/src/ui/components/graph/helpers.ts +1 -1
  48. package/src/ui/components/logs/DevLogs.tsx +661 -0
  49. package/src/ui/components/logs/index.ts +1 -0
  50. package/src/ui/components/shared/TreeView.tsx +189 -0
  51. package/src/ui/main.css +17 -0
  52. package/src/ui/main.ts +2 -6
  53. package/LICENSE +0 -21
  54. package/assets/devtools/actions.html +0 -21
  55. package/assets/devtools/actions.html.br +0 -0
  56. package/assets/devtools/actions.html.gz +0 -0
  57. package/assets/devtools/asset.BZV40eAE.css +0 -1
  58. package/assets/devtools/asset.BZV40eAE.css.br +0 -0
  59. package/assets/devtools/asset.BZV40eAE.css.gz +0 -0
  60. package/assets/devtools/asset.CBnMq2vO.css +0 -1
  61. package/assets/devtools/asset.CBnMq2vO.css.br +0 -0
  62. package/assets/devtools/asset.CBnMq2vO.css.gz +0 -0
  63. package/assets/devtools/atoms.html +0 -21
  64. package/assets/devtools/atoms.html.br +0 -0
  65. package/assets/devtools/atoms.html.gz +0 -0
  66. package/assets/devtools/caches.html +0 -21
  67. package/assets/devtools/caches.html.br +0 -0
  68. package/assets/devtools/caches.html.gz +0 -0
  69. package/assets/devtools/chunk.6INqNjF0.js +0 -1
  70. package/assets/devtools/chunk.6INqNjF0.js.br +0 -0
  71. package/assets/devtools/chunk.6INqNjF0.js.gz +0 -0
  72. package/assets/devtools/chunk.9vpWpXSF.js +0 -1
  73. package/assets/devtools/chunk.9vpWpXSF.js.br +0 -0
  74. package/assets/devtools/chunk.9vpWpXSF.js.gz +0 -0
  75. package/assets/devtools/chunk.B4peH6PS.js +0 -1
  76. package/assets/devtools/chunk.B4peH6PS.js.br +0 -0
  77. package/assets/devtools/chunk.B4peH6PS.js.gz +0 -0
  78. package/assets/devtools/chunk.B8CNjZzU.js +0 -1
  79. package/assets/devtools/chunk.B8CNjZzU.js.br +0 -0
  80. package/assets/devtools/chunk.B8CNjZzU.js.gz +0 -0
  81. package/assets/devtools/chunk.Bgd10SVI.js +0 -1
  82. package/assets/devtools/chunk.Bgd10SVI.js.br +0 -0
  83. package/assets/devtools/chunk.Bgd10SVI.js.gz +0 -0
  84. package/assets/devtools/chunk.BjFrJKj1.js +0 -1
  85. package/assets/devtools/chunk.BjFrJKj1.js.br +0 -2
  86. package/assets/devtools/chunk.BjFrJKj1.js.gz +0 -0
  87. package/assets/devtools/chunk.BlqFPyLh.js +0 -1
  88. package/assets/devtools/chunk.BlqFPyLh.js.br +0 -0
  89. package/assets/devtools/chunk.BlqFPyLh.js.gz +0 -0
  90. package/assets/devtools/chunk.BqBNmfN9.js +0 -1
  91. package/assets/devtools/chunk.BqBNmfN9.js.br +0 -0
  92. package/assets/devtools/chunk.BqBNmfN9.js.gz +0 -0
  93. package/assets/devtools/chunk.Bt0_vkJm.js +0 -2
  94. package/assets/devtools/chunk.Bt0_vkJm.js.br +0 -0
  95. package/assets/devtools/chunk.Bt0_vkJm.js.gz +0 -0
  96. package/assets/devtools/chunk.C3GuU4pz.js +0 -2
  97. package/assets/devtools/chunk.C3GuU4pz.js.br +0 -0
  98. package/assets/devtools/chunk.C3GuU4pz.js.gz +0 -0
  99. package/assets/devtools/chunk.CGwoN_Mo.js +0 -1
  100. package/assets/devtools/chunk.CGwoN_Mo.js.br +0 -0
  101. package/assets/devtools/chunk.CGwoN_Mo.js.gz +0 -0
  102. package/assets/devtools/chunk.CJCvhHA7.js +0 -1
  103. package/assets/devtools/chunk.CJCvhHA7.js.br +0 -2
  104. package/assets/devtools/chunk.CJCvhHA7.js.gz +0 -0
  105. package/assets/devtools/chunk.CKr2VE6v.js +0 -1
  106. package/assets/devtools/chunk.CKr2VE6v.js.br +0 -0
  107. package/assets/devtools/chunk.CKr2VE6v.js.gz +0 -0
  108. package/assets/devtools/chunk.CLvTwbkw.js +0 -1
  109. package/assets/devtools/chunk.CLvTwbkw.js.br +0 -0
  110. package/assets/devtools/chunk.CLvTwbkw.js.gz +0 -0
  111. package/assets/devtools/chunk.CR13dZhE.js +0 -7
  112. package/assets/devtools/chunk.CR13dZhE.js.br +0 -0
  113. package/assets/devtools/chunk.CR13dZhE.js.gz +0 -0
  114. package/assets/devtools/chunk.C_C-cVqs.js +0 -1
  115. package/assets/devtools/chunk.C_C-cVqs.js.br +0 -1
  116. package/assets/devtools/chunk.C_C-cVqs.js.gz +0 -0
  117. package/assets/devtools/chunk.CjevPbPy.js +0 -1
  118. package/assets/devtools/chunk.CjevPbPy.js.br +0 -0
  119. package/assets/devtools/chunk.CjevPbPy.js.gz +0 -0
  120. package/assets/devtools/chunk.CkNMZqAe.js +0 -1
  121. package/assets/devtools/chunk.CkNMZqAe.js.br +0 -0
  122. package/assets/devtools/chunk.CkNMZqAe.js.gz +0 -0
  123. package/assets/devtools/chunk.Cl1Mlnqx.js +0 -1
  124. package/assets/devtools/chunk.Cl1Mlnqx.js.br +0 -0
  125. package/assets/devtools/chunk.Cl1Mlnqx.js.gz +0 -0
  126. package/assets/devtools/chunk.CyY8OGdZ.js +0 -1
  127. package/assets/devtools/chunk.CyY8OGdZ.js.br +0 -0
  128. package/assets/devtools/chunk.CyY8OGdZ.js.gz +0 -0
  129. package/assets/devtools/chunk.Cyx9kLqD.js +0 -1
  130. package/assets/devtools/chunk.Cyx9kLqD.js.br +0 -0
  131. package/assets/devtools/chunk.Cyx9kLqD.js.gz +0 -0
  132. package/assets/devtools/chunk.D1MGgxUI.js +0 -1
  133. package/assets/devtools/chunk.D1MGgxUI.js.br +0 -0
  134. package/assets/devtools/chunk.D1MGgxUI.js.gz +0 -0
  135. package/assets/devtools/chunk.D5Ci-dwk.js +0 -1
  136. package/assets/devtools/chunk.D5Ci-dwk.js.br +0 -0
  137. package/assets/devtools/chunk.D5Ci-dwk.js.gz +0 -0
  138. package/assets/devtools/chunk.DFrWQW5x.js +0 -9
  139. package/assets/devtools/chunk.DFrWQW5x.js.br +0 -0
  140. package/assets/devtools/chunk.DFrWQW5x.js.gz +0 -0
  141. package/assets/devtools/chunk.DaVlli3f.js +0 -1
  142. package/assets/devtools/chunk.DaVlli3f.js.br +0 -0
  143. package/assets/devtools/chunk.DaVlli3f.js.gz +0 -0
  144. package/assets/devtools/chunk.DdyBCs50.js +0 -1
  145. package/assets/devtools/chunk.DdyBCs50.js.br +0 -0
  146. package/assets/devtools/chunk.DdyBCs50.js.gz +0 -0
  147. package/assets/devtools/chunk.Dl0THvrP.js +0 -1
  148. package/assets/devtools/chunk.Dl0THvrP.js.br +0 -0
  149. package/assets/devtools/chunk.Dl0THvrP.js.gz +0 -0
  150. package/assets/devtools/chunk.DwUNDm68.js +0 -1
  151. package/assets/devtools/chunk.DwUNDm68.js.br +0 -0
  152. package/assets/devtools/chunk.DwUNDm68.js.gz +0 -0
  153. package/assets/devtools/chunk.DzDkh4C6.js +0 -1
  154. package/assets/devtools/chunk.DzDkh4C6.js.br +0 -0
  155. package/assets/devtools/chunk.DzDkh4C6.js.gz +0 -0
  156. package/assets/devtools/chunk.QTExp4CY.js +0 -1
  157. package/assets/devtools/chunk.QTExp4CY.js.br +0 -0
  158. package/assets/devtools/chunk.QTExp4CY.js.gz +0 -0
  159. package/assets/devtools/chunk.ReCPcJln.js +0 -1
  160. package/assets/devtools/chunk.ReCPcJln.js.br +0 -0
  161. package/assets/devtools/chunk.ReCPcJln.js.gz +0 -0
  162. package/assets/devtools/chunk.UEhIKOMY.js +0 -1
  163. package/assets/devtools/chunk.UEhIKOMY.js.br +0 -0
  164. package/assets/devtools/chunk.UEhIKOMY.js.gz +0 -0
  165. package/assets/devtools/chunk.mWQqK3dU.js +0 -1
  166. package/assets/devtools/chunk.mWQqK3dU.js.br +0 -0
  167. package/assets/devtools/chunk.mWQqK3dU.js.gz +0 -0
  168. package/assets/devtools/chunk.uyVen0u2.js +0 -1
  169. package/assets/devtools/chunk.uyVen0u2.js.br +0 -0
  170. package/assets/devtools/chunk.uyVen0u2.js.gz +0 -0
  171. package/assets/devtools/chunk.yLRX_cUF.js +0 -1
  172. package/assets/devtools/chunk.yLRX_cUF.js.br +0 -0
  173. package/assets/devtools/chunk.yLRX_cUF.js.gz +0 -0
  174. package/assets/devtools/chunk.zuZxBYZg.js +0 -1
  175. package/assets/devtools/chunk.zuZxBYZg.js.br +0 -0
  176. package/assets/devtools/chunk.zuZxBYZg.js.gz +0 -0
  177. package/assets/devtools/db.html +0 -21
  178. package/assets/devtools/db.html.br +0 -0
  179. package/assets/devtools/db.html.gz +0 -0
  180. package/assets/devtools/entry.Cry3rxEI.js +0 -79
  181. package/assets/devtools/entry.Cry3rxEI.js.br +0 -0
  182. package/assets/devtools/entry.Cry3rxEI.js.gz +0 -0
  183. package/assets/devtools/env.html +0 -21
  184. package/assets/devtools/env.html.br +0 -0
  185. package/assets/devtools/env.html.gz +0 -0
  186. package/assets/devtools/graph.html +0 -22
  187. package/assets/devtools/graph.html.br +0 -0
  188. package/assets/devtools/graph.html.gz +0 -0
  189. package/assets/devtools/index.html +0 -21
  190. package/assets/devtools/index.html.br +0 -0
  191. package/assets/devtools/index.html.gz +0 -0
  192. package/assets/devtools/logs.html +0 -21
  193. package/assets/devtools/logs.html.br +0 -0
  194. package/assets/devtools/logs.html.gz +0 -0
  195. package/assets/devtools/queues.html +0 -21
  196. package/assets/devtools/queues.html.br +0 -0
  197. package/assets/devtools/queues.html.gz +0 -0
  198. package/assets/devtools/topics.html +0 -21
  199. package/assets/devtools/topics.html.br +0 -0
  200. package/assets/devtools/topics.html.gz +0 -0
  201. package/src/api/DevToolsProvider.ts +0 -157
  202. package/src/api/providers/DevToolsDatabaseProvider.ts +0 -27
  203. package/src/api/repositories/LogRepository.ts +0 -8
  204. package/src/api/schemas/DevCommandMetadata.ts +0 -9
  205. package/src/ui/components/DevAtomsViewer.tsx +0 -637
  206. package/src/ui/components/DevCacheInspector.tsx +0 -423
  207. package/src/ui/components/DevDashboard.tsx +0 -38
  208. package/src/ui/components/DevEnvExplorer.tsx +0 -462
  209. package/src/ui/components/DevLogViewer.tsx +0 -252
  210. package/src/ui/components/DevQueueMonitor.tsx +0 -51
  211. package/src/ui/components/DevTopicsViewer.tsx +0 -686
  212. package/src/ui/components/actions/ActionGroup.tsx +0 -37
  213. package/src/ui/components/actions/ActionItem.tsx +0 -138
  214. package/src/ui/components/actions/DevActionsExplorer.tsx +0 -132
  215. package/src/ui/components/actions/MethodBadge.tsx +0 -18
  216. package/src/ui/components/actions/SchemaViewer.tsx +0 -21
  217. package/src/ui/components/actions/TryItPanel.tsx +0 -140
  218. package/src/ui/components/actions/constants.ts +0 -7
  219. package/src/ui/components/actions/helpers.ts +0 -18
  220. package/src/ui/components/actions/index.ts +0 -8
  221. package/src/ui/components/db/ColumnBadge.tsx +0 -55
  222. package/src/ui/components/db/DevDbStudio.tsx +0 -485
  223. package/src/ui/components/db/constants.ts +0 -11
  224. package/src/ui/components/db/index.ts +0 -4
  225. package/src/ui/components/db/types.ts +0 -7
  226. package/src/ui/styles.css +0 -1
  227. /package/src/{api/schemas → schemas}/DevAtomMetadata.ts +0 -0
  228. /package/src/{api/schemas → schemas}/DevBucketMetadata.ts +0 -0
  229. /package/src/{api/schemas → schemas}/DevCacheMetadata.ts +0 -0
  230. /package/src/{api/schemas → schemas}/DevEnvMetadata.ts +0 -0
  231. /package/src/{api/schemas → schemas}/DevModuleMetadata.ts +0 -0
  232. /package/src/{api/schemas → schemas}/DevProviderMetadata.ts +0 -0
  233. /package/src/{api/schemas → schemas}/DevQueueMetadata.ts +0 -0
  234. /package/src/{api/schemas → schemas}/DevRealmMetadata.ts +0 -0
  235. /package/src/{api/schemas → schemas}/DevRouteMetadata.ts +0 -0
  236. /package/src/{api/schemas → schemas}/DevSchedulerMetadata.ts +0 -0
@@ -1,8 +0,0 @@
1
- import { Repository } from "alepha/orm";
2
- import { logs } from "../entities/logs.ts";
3
- import { DevToolsDatabaseProvider } from "../providers/DevToolsDatabaseProvider.ts";
4
-
5
- export class LogRepository extends Repository.of(
6
- logs,
7
- DevToolsDatabaseProvider,
8
- ) {}
@@ -1,9 +0,0 @@
1
- import { type Static, t } from "alepha";
2
-
3
- export const devCommandMetadataSchema = t.object({
4
- name: t.text(),
5
- description: t.optional(t.text()),
6
- hidden: t.optional(t.boolean()),
7
- });
8
-
9
- export type DevCommandMetadata = Static<typeof devCommandMetadataSchema>;
@@ -1,637 +0,0 @@
1
- import { ui } from "@alepha/ui";
2
- import { JsonViewer } from "@alepha/ui/json";
3
- import {
4
- ActionIcon,
5
- Badge,
6
- Box,
7
- Button,
8
- Flex,
9
- NumberInput,
10
- ScrollArea,
11
- Stack,
12
- Switch,
13
- Tabs,
14
- Text,
15
- Textarea,
16
- TextInput,
17
- Tooltip,
18
- } from "@mantine/core";
19
- import {
20
- IconAtom,
21
- IconDeviceFloppy,
22
- IconEdit,
23
- IconRefresh,
24
- IconSearch,
25
- IconX,
26
- } from "@tabler/icons-react";
27
- import { useInject } from "alepha/react";
28
- import { HttpClient } from "alepha/server";
29
- import { useCallback, useEffect, useMemo, useState } from "react";
30
- import type { DevAtomMetadata } from "../../api/schemas/DevAtomMetadata.ts";
31
- import { devMetadataSchema } from "../../api/schemas/DevMetadata.ts";
32
-
33
- const AtomSidebar = ({
34
- atoms,
35
- selectedAtom,
36
- onSelectAtom,
37
- search,
38
- onSearchChange,
39
- }: {
40
- atoms: DevAtomMetadata[];
41
- selectedAtom: DevAtomMetadata | null;
42
- onSelectAtom: (atom: DevAtomMetadata) => void;
43
- search: string;
44
- onSearchChange: (s: string) => void;
45
- }) => {
46
- return (
47
- <Stack gap={0} h="100%">
48
- <Box p="sm" style={{ borderBottom: `1px solid ${ui.colors.border}` }}>
49
- <TextInput
50
- placeholder="Search atoms..."
51
- leftSection={<IconSearch size={14} />}
52
- size="xs"
53
- value={search}
54
- onChange={(e) => onSearchChange(e.target.value)}
55
- />
56
- </Box>
57
- <ScrollArea style={{ flex: 1 }}>
58
- <Box>
59
- <Text
60
- size="xs"
61
- fw={600}
62
- c="dimmed"
63
- px="sm"
64
- py="xs"
65
- style={{
66
- backgroundColor: ui.colors.background,
67
- borderBottom: `1px solid ${ui.colors.border}`,
68
- textTransform: "uppercase",
69
- letterSpacing: "0.05em",
70
- }}
71
- >
72
- Atoms ({atoms.length})
73
- </Text>
74
- {atoms.map((atom) => {
75
- const isSelected = selectedAtom?.name === atom.name;
76
- const hasValue = atom.currentValue !== undefined;
77
- return (
78
- <Flex
79
- key={atom.name}
80
- align="center"
81
- gap="xs"
82
- px="sm"
83
- py={6}
84
- onClick={() => onSelectAtom(atom)}
85
- style={{
86
- cursor: "pointer",
87
- backgroundColor: isSelected ? "#228be615" : undefined,
88
- borderLeft: isSelected
89
- ? "2px solid #228be6"
90
- : "2px solid transparent",
91
- borderBottom: `1px solid ${ui.colors.border}`,
92
- }}
93
- >
94
- <IconAtom size={14} opacity={0.5} />
95
- <Text size="sm" style={{ flex: 1 }} truncate>
96
- {atom.name}
97
- </Text>
98
- {hasValue && (
99
- <Badge size="xs" variant="light" color="green">
100
- set
101
- </Badge>
102
- )}
103
- </Flex>
104
- );
105
- })}
106
- </Box>
107
- </ScrollArea>
108
- </Stack>
109
- );
110
- };
111
-
112
- // Simple schema-based editor for atom values
113
- const SchemaEditor = ({
114
- schema,
115
- value,
116
- onChange,
117
- path = "",
118
- }: {
119
- schema: any;
120
- value: any;
121
- onChange: (value: any) => void;
122
- path?: string;
123
- }) => {
124
- if (!schema) return null;
125
-
126
- // Handle union types (optional)
127
- let actualSchema = schema;
128
- if (schema.anyOf) {
129
- const nonNull = schema.anyOf.find((t: any) => t.type !== "null");
130
- if (nonNull) actualSchema = nonNull;
131
- }
132
-
133
- const type = actualSchema.type;
134
- const format = actualSchema.format;
135
-
136
- // Object type
137
- if (type === "object" && actualSchema.properties) {
138
- return (
139
- <Stack gap="sm">
140
- {Object.entries(actualSchema.properties).map(
141
- ([key, propSchema]: [string, any]) => (
142
- <Box key={key}>
143
- <Text size="sm" fw={500} mb={4}>
144
- {propSchema.title || key}
145
- {propSchema.description && (
146
- <Text span size="xs" c="dimmed" ml="xs">
147
- {propSchema.description}
148
- </Text>
149
- )}
150
- </Text>
151
- <SchemaEditor
152
- schema={propSchema}
153
- value={value?.[key]}
154
- onChange={(v) => onChange({ ...value, [key]: v })}
155
- path={path ? `${path}.${key}` : key}
156
- />
157
- </Box>
158
- ),
159
- )}
160
- </Stack>
161
- );
162
- }
163
-
164
- // Array type
165
- if (type === "array") {
166
- const items = value ?? [];
167
- return (
168
- <Stack gap="xs">
169
- {items.map((item: any, idx: number) => (
170
- <Flex key={idx} gap="xs" align="center">
171
- <Box style={{ flex: 1 }}>
172
- <SchemaEditor
173
- schema={actualSchema.items}
174
- value={item}
175
- onChange={(v) => {
176
- const newItems = [...items];
177
- newItems[idx] = v;
178
- onChange(newItems);
179
- }}
180
- path={`${path}[${idx}]`}
181
- />
182
- </Box>
183
- <ActionIcon
184
- size="sm"
185
- variant="light"
186
- color="red"
187
- onClick={() => {
188
- const newItems = items.filter((_: any, i: number) => i !== idx);
189
- onChange(newItems);
190
- }}
191
- >
192
- <IconX size={14} />
193
- </ActionIcon>
194
- </Flex>
195
- ))}
196
- <Button
197
- size="xs"
198
- variant="light"
199
- onClick={() => onChange([...items, undefined])}
200
- >
201
- Add item
202
- </Button>
203
- </Stack>
204
- );
205
- }
206
-
207
- // Boolean
208
- if (type === "boolean") {
209
- return (
210
- <Switch
211
- checked={value ?? false}
212
- onChange={(e) => onChange(e.currentTarget.checked)}
213
- />
214
- );
215
- }
216
-
217
- // Number/Integer
218
- if (type === "number" || type === "integer") {
219
- return (
220
- <NumberInput
221
- size="sm"
222
- value={value ?? ""}
223
- onChange={(v) => onChange(v === "" ? undefined : v)}
224
- allowDecimal={type === "number"}
225
- />
226
- );
227
- }
228
-
229
- // Enum
230
- if (actualSchema.enum) {
231
- return (
232
- <select
233
- value={value ?? ""}
234
- onChange={(e) => onChange(e.target.value || undefined)}
235
- style={{
236
- padding: "6px 10px",
237
- borderRadius: 4,
238
- border: `1px solid ${ui.colors.border}`,
239
- backgroundColor: ui.colors.surface,
240
- width: "100%",
241
- }}
242
- >
243
- <option value="">Select...</option>
244
- {actualSchema.enum.map((opt: string) => (
245
- <option key={opt} value={opt}>
246
- {opt}
247
- </option>
248
- ))}
249
- </select>
250
- );
251
- }
252
-
253
- // Date/DateTime
254
- if (format === "date" || format === "date-time") {
255
- return (
256
- <TextInput
257
- size="sm"
258
- type={format === "date" ? "date" : "datetime-local"}
259
- value={value ?? ""}
260
- onChange={(e) => onChange(e.target.value || undefined)}
261
- />
262
- );
263
- }
264
-
265
- // Long text (if format suggests or multiline)
266
- if (format === "textarea" || actualSchema.multiline) {
267
- return (
268
- <Textarea
269
- size="sm"
270
- value={value ?? ""}
271
- onChange={(e) => onChange(e.target.value || undefined)}
272
- autosize
273
- minRows={2}
274
- maxRows={6}
275
- />
276
- );
277
- }
278
-
279
- // Default: string
280
- return (
281
- <TextInput
282
- size="sm"
283
- value={value ?? ""}
284
- onChange={(e) => onChange(e.target.value || undefined)}
285
- />
286
- );
287
- };
288
-
289
- const InfoTab = ({ atom }: { atom: DevAtomMetadata }) => {
290
- return (
291
- <ScrollArea h="100%" p="md">
292
- <Stack gap="lg">
293
- {/* Description */}
294
- {atom.description && (
295
- <Box>
296
- <Text size="sm" fw={600} mb="xs">
297
- Description
298
- </Text>
299
- <Text size="sm" c="dimmed">
300
- {atom.description}
301
- </Text>
302
- </Box>
303
- )}
304
-
305
- {/* Current Value */}
306
- <Box>
307
- <Text size="sm" fw={600} mb="xs">
308
- Current Value
309
- </Text>
310
- {atom.currentValue !== undefined ? (
311
- <JsonViewer data={atom.currentValue} maxDepth={3} />
312
- ) : (
313
- <Text size="sm" c="dimmed" fs="italic">
314
- (not set - using default)
315
- </Text>
316
- )}
317
- </Box>
318
-
319
- {/* Default Value */}
320
- <Box>
321
- <Text size="sm" fw={600} mb="xs">
322
- Default Value
323
- </Text>
324
- {atom.defaultValue !== undefined ? (
325
- <JsonViewer data={atom.defaultValue} maxDepth={3} />
326
- ) : (
327
- <Text size="sm" c="dimmed" fs="italic">
328
- (no default)
329
- </Text>
330
- )}
331
- </Box>
332
-
333
- {/* Schema */}
334
- <Box>
335
- <Text size="sm" fw={600} mb="xs">
336
- Schema
337
- </Text>
338
- <JsonViewer data={atom.schema} maxDepth={2} />
339
- </Box>
340
- </Stack>
341
- </ScrollArea>
342
- );
343
- };
344
-
345
- const EditTab = ({
346
- atom,
347
- onSave,
348
- }: {
349
- atom: DevAtomMetadata;
350
- onSave: (value: any) => void;
351
- }) => {
352
- const [editValue, setEditValue] = useState<any>(
353
- atom.currentValue ?? atom.defaultValue,
354
- );
355
- const [jsonMode, setJsonMode] = useState(false);
356
- const [jsonText, setJsonText] = useState("");
357
- const [jsonError, setJsonError] = useState<string | null>(null);
358
-
359
- // Sync editValue when atom changes
360
- useEffect(() => {
361
- const val = atom.currentValue ?? atom.defaultValue;
362
- setEditValue(val);
363
- setJsonText(JSON.stringify(val, null, 2));
364
- setJsonError(null);
365
- }, [atom]);
366
-
367
- const handleJsonChange = (text: string) => {
368
- setJsonText(text);
369
- try {
370
- const parsed = JSON.parse(text);
371
- setEditValue(parsed);
372
- setJsonError(null);
373
- } catch {
374
- setJsonError("Invalid JSON");
375
- }
376
- };
377
-
378
- const handleSave = () => {
379
- onSave(editValue);
380
- };
381
-
382
- const handleReset = () => {
383
- const val = atom.defaultValue;
384
- setEditValue(val);
385
- setJsonText(JSON.stringify(val, null, 2));
386
- setJsonError(null);
387
- };
388
-
389
- return (
390
- <Flex direction="column" h="100%">
391
- <Box
392
- p="sm"
393
- style={{
394
- borderBottom: `1px solid ${ui.colors.border}`,
395
- }}
396
- >
397
- <Flex align="center" justify="space-between">
398
- <Flex gap="xs" align="center">
399
- <Switch
400
- size="xs"
401
- label="JSON mode"
402
- checked={jsonMode}
403
- onChange={(e) => setJsonMode(e.currentTarget.checked)}
404
- />
405
- </Flex>
406
- <Flex gap="xs">
407
- <Tooltip label="Reset to default">
408
- <ActionIcon
409
- size="sm"
410
- variant="light"
411
- color="gray"
412
- onClick={handleReset}
413
- >
414
- <IconRefresh size={14} />
415
- </ActionIcon>
416
- </Tooltip>
417
- <Button
418
- size="xs"
419
- leftSection={<IconDeviceFloppy size={14} />}
420
- onClick={handleSave}
421
- disabled={jsonMode && !!jsonError}
422
- >
423
- Save
424
- </Button>
425
- </Flex>
426
- </Flex>
427
- </Box>
428
-
429
- <ScrollArea style={{ flex: 1 }} p="md">
430
- {jsonMode ? (
431
- <Stack gap="xs">
432
- <Textarea
433
- value={jsonText}
434
- onChange={(e) => handleJsonChange(e.target.value)}
435
- autosize
436
- minRows={10}
437
- maxRows={20}
438
- error={jsonError}
439
- styles={{
440
- input: {
441
- fontFamily: "monospace",
442
- },
443
- }}
444
- />
445
- {jsonError && (
446
- <Text size="xs" c="red">
447
- {jsonError}
448
- </Text>
449
- )}
450
- </Stack>
451
- ) : (
452
- <SchemaEditor
453
- schema={atom.schema}
454
- value={editValue}
455
- onChange={setEditValue}
456
- />
457
- )}
458
- </ScrollArea>
459
- </Flex>
460
- );
461
- };
462
-
463
- const AtomPanel = ({
464
- atom,
465
- onSave,
466
- }: {
467
- atom: DevAtomMetadata;
468
- onSave: (atom: DevAtomMetadata, value: any) => void;
469
- }) => {
470
- const hasValue = atom.currentValue !== undefined;
471
-
472
- return (
473
- <Flex direction="column" h="100%">
474
- {/* Header */}
475
- <Box
476
- px="md"
477
- py="sm"
478
- style={{
479
- borderBottom: `1px solid ${ui.colors.border}`,
480
- backgroundColor: "#228be608",
481
- }}
482
- >
483
- <Flex align="center" gap="sm">
484
- <IconAtom size={18} opacity={0.7} />
485
- <Text size="md" fw={600}>
486
- {atom.name}
487
- </Text>
488
- <Badge size="xs" variant="light" color={hasValue ? "green" : "gray"}>
489
- {hasValue ? "has value" : "default"}
490
- </Badge>
491
- </Flex>
492
- </Box>
493
-
494
- {/* Tabs */}
495
- <Tabs
496
- defaultValue="info"
497
- style={{ flex: 1, display: "flex", flexDirection: "column" }}
498
- >
499
- <Tabs.List px="md">
500
- <Tabs.Tab value="info">Info</Tabs.Tab>
501
- <Tabs.Tab value="edit" leftSection={<IconEdit size={14} />}>
502
- Edit
503
- </Tabs.Tab>
504
- </Tabs.List>
505
-
506
- <Tabs.Panel value="info" style={{ flex: 1, overflow: "hidden" }}>
507
- <InfoTab atom={atom} />
508
- </Tabs.Panel>
509
-
510
- <Tabs.Panel value="edit" style={{ flex: 1, overflow: "hidden" }}>
511
- <EditTab atom={atom} onSave={(value) => onSave(atom, value)} />
512
- </Tabs.Panel>
513
- </Tabs>
514
- </Flex>
515
- );
516
- };
517
-
518
- const EmptyState = () => (
519
- <Flex align="center" justify="center" h="100%" c="dimmed">
520
- <Stack align="center" gap="xs">
521
- <IconAtom size={48} opacity={0.3} />
522
- <Text size="sm">Select an atom to view its details</Text>
523
- </Stack>
524
- </Flex>
525
- );
526
-
527
- const NoAtomsState = () => (
528
- <Flex align="center" justify="center" h="100%" c="dimmed">
529
- <Stack align="center" gap="xs">
530
- <IconAtom size={48} opacity={0.3} />
531
- <Text>No atoms found</Text>
532
- <Text size="sm" c="dimmed">
533
- Use $atom primitive to define state atoms
534
- </Text>
535
- </Stack>
536
- </Flex>
537
- );
538
-
539
- export const DevAtomsViewer = () => {
540
- const http = useInject(HttpClient);
541
- const [atoms, setAtoms] = useState<DevAtomMetadata[]>([]);
542
- const [loading, setLoading] = useState(true);
543
- const [selectedAtom, setSelectedAtom] = useState<DevAtomMetadata | null>(
544
- null,
545
- );
546
- const [search, setSearch] = useState("");
547
-
548
- const fetchAtoms = useCallback(() => {
549
- http
550
- .fetch("/devtools/api/metadata", {
551
- schema: { response: devMetadataSchema },
552
- })
553
- .then((res) => {
554
- setAtoms(res.data.atoms);
555
- setLoading(false);
556
-
557
- // Update selected atom if it exists
558
- if (selectedAtom) {
559
- const updated = res.data.atoms.find(
560
- (a) => a.name === selectedAtom.name,
561
- );
562
- if (updated) setSelectedAtom(updated);
563
- }
564
- });
565
- }, [selectedAtom?.name]);
566
-
567
- useEffect(() => {
568
- fetchAtoms();
569
- }, []);
570
-
571
- const filteredAtoms = useMemo(() => {
572
- if (!search) return atoms;
573
- const searchLower = search.toLowerCase();
574
- return atoms.filter((a) => a.name.toLowerCase().includes(searchLower));
575
- }, [atoms, search]);
576
-
577
- const handleSave = useCallback(
578
- async (atom: DevAtomMetadata, value: any) => {
579
- // POST to update atom value
580
- try {
581
- await http.fetch("/devtools/api/atoms", {
582
- method: "POST",
583
- body: JSON.stringify({ name: atom.name, value }),
584
- });
585
- // Refresh data
586
- fetchAtoms();
587
- } catch (error) {
588
- console.error("Failed to save atom:", error);
589
- }
590
- },
591
- [fetchAtoms],
592
- );
593
-
594
- if (loading) {
595
- return (
596
- <Flex align="center" justify="center" h="100%">
597
- <Text c="dimmed">Loading...</Text>
598
- </Flex>
599
- );
600
- }
601
-
602
- if (atoms.length === 0) {
603
- return <NoAtomsState />;
604
- }
605
-
606
- return (
607
- <Flex h="100%" style={{ overflow: "hidden" }}>
608
- {/* Sidebar */}
609
- <Box
610
- w={260}
611
- style={{
612
- borderRight: `1px solid ${ui.colors.border}`,
613
- backgroundColor: ui.colors.surface,
614
- }}
615
- >
616
- <AtomSidebar
617
- atoms={filteredAtoms}
618
- selectedAtom={selectedAtom}
619
- onSelectAtom={setSelectedAtom}
620
- search={search}
621
- onSearchChange={setSearch}
622
- />
623
- </Box>
624
-
625
- {/* Main content */}
626
- <Box style={{ flex: 1, overflow: "hidden" }}>
627
- {selectedAtom ? (
628
- <AtomPanel atom={selectedAtom} onSave={handleSave} />
629
- ) : (
630
- <EmptyState />
631
- )}
632
- </Box>
633
- </Flex>
634
- );
635
- };
636
-
637
- export default DevAtomsViewer;