@adecore/database 0.0.1 → 0.16.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -1
  3. package/dist/DatabaseProvider.d.ts +13 -0
  4. package/dist/DatabaseProvider.js +17 -0
  5. package/dist/actions.d.ts +40 -0
  6. package/dist/actions.js +1 -0
  7. package/dist/client/client.d.ts +5 -0
  8. package/dist/client/client.js +245 -0
  9. package/dist/client/index.d.ts +2 -0
  10. package/dist/client/index.js +2 -0
  11. package/dist/client/types.d.ts +97 -0
  12. package/dist/client/types.js +13 -0
  13. package/dist/client-context.d.ts +16 -0
  14. package/dist/client-context.js +17 -0
  15. package/dist/connections/ConnectionForm.d.ts +10 -0
  16. package/dist/connections/ConnectionForm.js +145 -0
  17. package/dist/connections/ConnectionManager.d.ts +12 -0
  18. package/dist/connections/ConnectionManager.js +93 -0
  19. package/dist/connections/EngineIcon.d.ts +9 -0
  20. package/dist/connections/EngineIcon.js +7 -0
  21. package/dist/connections/connection-config.d.ts +16 -0
  22. package/dist/connections/connection-config.js +107 -0
  23. package/dist/connections/docker.d.ts +9 -0
  24. package/dist/connections/docker.js +38 -0
  25. package/dist/connections/engine-icons.d.ts +3 -0
  26. package/dist/connections/engine-icons.js +3 -0
  27. package/dist/connections/use-docker-containers.d.ts +17 -0
  28. package/dist/connections/use-docker-containers.js +26 -0
  29. package/dist/console/DestructiveDialog.d.ts +9 -0
  30. package/dist/console/DestructiveDialog.js +9 -0
  31. package/dist/console/HistoryPanel.d.ts +10 -0
  32. package/dist/console/HistoryPanel.js +15 -0
  33. package/dist/console/QueryConsole.d.ts +14 -0
  34. package/dist/console/QueryConsole.js +261 -0
  35. package/dist/console/SchemaPicker.d.ts +11 -0
  36. package/dist/console/SchemaPicker.js +8 -0
  37. package/dist/console/SqlEditor.d.ts +12 -0
  38. package/dist/console/SqlEditor.js +61 -0
  39. package/dist/console/StatementResultView.d.ts +18 -0
  40. package/dist/console/StatementResultView.js +32 -0
  41. package/dist/console/TransactionControls.d.ts +13 -0
  42. package/dist/console/TransactionControls.js +12 -0
  43. package/dist/console/destructive.d.ts +7 -0
  44. package/dist/console/destructive.js +43 -0
  45. package/dist/console/editing.d.ts +12 -0
  46. package/dist/console/editing.js +60 -0
  47. package/dist/console/history.d.ts +13 -0
  48. package/dist/console/history.js +43 -0
  49. package/dist/console/labels.d.ts +1 -0
  50. package/dist/console/labels.js +6 -0
  51. package/dist/console/outcome.d.ts +6 -0
  52. package/dist/console/outcome.js +11 -0
  53. package/dist/console/schemas.d.ts +2 -0
  54. package/dist/console/schemas.js +5 -0
  55. package/dist/console/shortcuts.d.ts +2 -0
  56. package/dist/console/shortcuts.js +3 -0
  57. package/dist/console/useConsoleHistory.d.ts +7 -0
  58. package/dist/console/useConsoleHistory.js +22 -0
  59. package/dist/ddl/alter-mysql.d.ts +4 -0
  60. package/dist/ddl/alter-mysql.js +79 -0
  61. package/dist/ddl/alter-sqlite.d.ts +4 -0
  62. package/dist/ddl/alter-sqlite.js +80 -0
  63. package/dist/ddl/alter.d.ts +4 -0
  64. package/dist/ddl/alter.js +24 -0
  65. package/dist/ddl/column.d.ts +9 -0
  66. package/dist/ddl/column.js +62 -0
  67. package/dist/ddl/create.d.ts +12 -0
  68. package/dist/ddl/create.js +74 -0
  69. package/dist/ddl/dialect.d.ts +12 -0
  70. package/dist/ddl/dialect.js +34 -0
  71. package/dist/ddl/diff.d.ts +34 -0
  72. package/dist/ddl/diff.js +111 -0
  73. package/dist/ddl/draft.d.ts +46 -0
  74. package/dist/ddl/draft.js +70 -0
  75. package/dist/ddl/index.d.ts +10 -0
  76. package/dist/ddl/index.js +10 -0
  77. package/dist/ddl/parse.d.ts +17 -0
  78. package/dist/ddl/parse.js +131 -0
  79. package/dist/ddl/statements.d.ts +6 -0
  80. package/dist/ddl/statements.js +12 -0
  81. package/dist/ddl/types.d.ts +6 -0
  82. package/dist/ddl/types.js +52 -0
  83. package/dist/ddl/validate.d.ts +7 -0
  84. package/dist/ddl/validate.js +38 -0
  85. package/dist/designer/ColumnsEditor.d.ts +2 -0
  86. package/dist/designer/ColumnsEditor.js +24 -0
  87. package/dist/designer/DesignerView.d.ts +25 -0
  88. package/dist/designer/DesignerView.js +33 -0
  89. package/dist/designer/ForeignKeysEditor.d.ts +7 -0
  90. package/dist/designer/ForeignKeysEditor.js +24 -0
  91. package/dist/designer/IndexesEditor.d.ts +2 -0
  92. package/dist/designer/IndexesEditor.js +13 -0
  93. package/dist/designer/NamePicker.d.ts +11 -0
  94. package/dist/designer/NamePicker.js +13 -0
  95. package/dist/designer/OptionsEditor.d.ts +2 -0
  96. package/dist/designer/OptionsEditor.js +17 -0
  97. package/dist/designer/SqlPreview.d.ts +12 -0
  98. package/dist/designer/SqlPreview.js +17 -0
  99. package/dist/designer/TableDesigner.d.ts +10 -0
  100. package/dist/designer/TableDesigner.js +103 -0
  101. package/dist/designer/edit.d.ts +17 -0
  102. package/dist/designer/edit.js +115 -0
  103. package/dist/designer/editor-props.d.ts +10 -0
  104. package/dist/designer/editor-props.js +1 -0
  105. package/dist/designer/plan.d.ts +9 -0
  106. package/dist/designer/plan.js +13 -0
  107. package/dist/designer/recover.d.ts +2 -0
  108. package/dist/designer/recover.js +18 -0
  109. package/dist/designer/useReferenceColumns.d.ts +3 -0
  110. package/dist/designer/useReferenceColumns.js +36 -0
  111. package/dist/explorer/DatabaseExplorer.d.ts +13 -0
  112. package/dist/explorer/DatabaseExplorer.js +264 -0
  113. package/dist/explorer/RowMenu.d.ts +11 -0
  114. package/dist/explorer/RowMenu.js +89 -0
  115. package/dist/explorer/TableDialog.d.ts +15 -0
  116. package/dist/explorer/TableDialog.js +50 -0
  117. package/dist/explorer/expansion.d.ts +7 -0
  118. package/dist/explorer/expansion.js +31 -0
  119. package/dist/explorer/table-statements.d.ts +5 -0
  120. package/dist/explorer/table-statements.js +12 -0
  121. package/dist/explorer/tree.d.ts +125 -0
  122. package/dist/explorer/tree.js +295 -0
  123. package/dist/explorer/use-explorer-loads.d.ts +12 -0
  124. package/dist/explorer/use-explorer-loads.js +109 -0
  125. package/dist/explorer/use-stored-expansion.d.ts +9 -0
  126. package/dist/explorer/use-stored-expansion.js +47 -0
  127. package/dist/grid/CellEditor.d.ts +9 -0
  128. package/dist/grid/CellEditor.js +29 -0
  129. package/dist/grid/CopyAsMenu.d.ts +7 -0
  130. package/dist/grid/CopyAsMenu.js +10 -0
  131. package/dist/grid/DataGrid.d.ts +36 -0
  132. package/dist/grid/DataGrid.js +512 -0
  133. package/dist/grid/EnumPicker.d.ts +17 -0
  134. package/dist/grid/EnumPicker.js +53 -0
  135. package/dist/grid/GridHeaderCell.d.ts +36 -0
  136. package/dist/grid/GridHeaderCell.js +69 -0
  137. package/dist/grid/RecordView.d.ts +20 -0
  138. package/dist/grid/RecordView.js +126 -0
  139. package/dist/grid/ValueDock.d.ts +11 -0
  140. package/dist/grid/ValueDock.js +28 -0
  141. package/dist/grid/aggregates.d.ts +22 -0
  142. package/dist/grid/aggregates.js +62 -0
  143. package/dist/grid/cell-range.d.ts +14 -0
  144. package/dist/grid/cell-range.js +11 -0
  145. package/dist/grid/column-layout.d.ts +18 -0
  146. package/dist/grid/column-layout.js +49 -0
  147. package/dist/grid/column-selection.d.ts +11 -0
  148. package/dist/grid/column-selection.js +28 -0
  149. package/dist/grid/column-view.d.ts +10 -0
  150. package/dist/grid/column-view.js +23 -0
  151. package/dist/grid/copy-formats.d.ts +15 -0
  152. package/dist/grid/copy-formats.js +80 -0
  153. package/dist/grid/display.d.ts +20 -0
  154. package/dist/grid/display.js +81 -0
  155. package/dist/grid/edit-value.d.ts +5 -0
  156. package/dist/grid/edit-value.js +55 -0
  157. package/dist/grid/enum-type.d.ts +7 -0
  158. package/dist/grid/enum-type.js +52 -0
  159. package/dist/grid/focused-value.d.ts +4 -0
  160. package/dist/grid/focused-value.js +19 -0
  161. package/dist/grid/layout.d.ts +22 -0
  162. package/dist/grid/layout.js +74 -0
  163. package/dist/grid/navigation.d.ts +15 -0
  164. package/dist/grid/navigation.js +52 -0
  165. package/dist/grid/row-selection.d.ts +9 -0
  166. package/dist/grid/row-selection.js +24 -0
  167. package/dist/grid/sort.d.ts +7 -0
  168. package/dist/grid/sort.js +7 -0
  169. package/dist/grid/types.d.ts +30 -0
  170. package/dist/grid/types.js +9 -0
  171. package/dist/host/helper-path.d.ts +7 -0
  172. package/dist/host/helper-path.js +15 -0
  173. package/dist/host/host.d.ts +2 -0
  174. package/dist/host/host.js +343 -0
  175. package/dist/host/index.d.ts +5 -0
  176. package/dist/host/index.js +4 -0
  177. package/dist/host/spawn.d.ts +7 -0
  178. package/dist/host/spawn.js +98 -0
  179. package/dist/host/types.d.ts +19 -0
  180. package/dist/host/types.js +1 -0
  181. package/dist/host/validate.d.ts +10 -0
  182. package/dist/host/validate.js +339 -0
  183. package/dist/index.d.ts +14 -0
  184. package/dist/index.js +13 -0
  185. package/dist/locales/en.json +577 -0
  186. package/dist/locales/nl.json +577 -0
  187. package/dist/locales.d.ts +4 -0
  188. package/dist/locales.js +14 -0
  189. package/dist/protocol/connection.d.ts +56 -0
  190. package/dist/protocol/connection.js +1 -0
  191. package/dist/protocol/errors.d.ts +7 -0
  192. package/dist/protocol/errors.js +1 -0
  193. package/dist/protocol/index.d.ts +5 -0
  194. package/dist/protocol/index.js +3 -0
  195. package/dist/protocol/messages.d.ts +250 -0
  196. package/dist/protocol/messages.js +2 -0
  197. package/dist/protocol/schema.d.ts +53 -0
  198. package/dist/protocol/schema.js +1 -0
  199. package/dist/protocol/values.d.ts +21 -0
  200. package/dist/protocol/values.js +15 -0
  201. package/dist/sql-split.d.ts +15 -0
  202. package/dist/sql-split.js +201 -0
  203. package/dist/sql.d.ts +29 -0
  204. package/dist/sql.js +104 -0
  205. package/dist/structure/StructureView.d.ts +14 -0
  206. package/dist/structure/StructureView.js +99 -0
  207. package/dist/structure/structure-text.d.ts +2 -0
  208. package/dist/structure/structure-text.js +2 -0
  209. package/dist/table/CommandField.d.ts +14 -0
  210. package/dist/table/CommandField.js +157 -0
  211. package/dist/table/ImportDialog.d.ts +27 -0
  212. package/dist/table/ImportDialog.js +28 -0
  213. package/dist/table/TableStatusBar.d.ts +19 -0
  214. package/dist/table/TableStatusBar.js +22 -0
  215. package/dist/table/TableToolbar.d.ts +43 -0
  216. package/dist/table/TableToolbar.js +16 -0
  217. package/dist/table/TableView.d.ts +13 -0
  218. package/dist/table/TableView.js +508 -0
  219. package/dist/table/command-field.d.ts +57 -0
  220. package/dist/table/command-field.js +203 -0
  221. package/dist/table/grid-rows.d.ts +23 -0
  222. package/dist/table/grid-rows.js +55 -0
  223. package/dist/table/import-mapping.d.ts +7 -0
  224. package/dist/table/import-mapping.js +28 -0
  225. package/dist/table/layout-store.d.ts +13 -0
  226. package/dist/table/layout-store.js +43 -0
  227. package/dist/table/paging.d.ts +12 -0
  228. package/dist/table/paging.js +20 -0
  229. package/dist/table/pending.d.ts +32 -0
  230. package/dist/table/pending.js +94 -0
  231. package/dist/table/references.d.ts +12 -0
  232. package/dist/table/references.js +21 -0
  233. package/dist/table/useLoaded.d.ts +7 -0
  234. package/dist/table/useLoaded.js +36 -0
  235. package/dist/table/useStoredLayout.d.ts +8 -0
  236. package/dist/table/useStoredLayout.js +39 -0
  237. package/dist/table/useTableTransfer.d.ts +51 -0
  238. package/dist/table/useTableTransfer.js +125 -0
  239. package/dist/testing/fake.d.ts +21 -0
  240. package/dist/testing/fake.js +372 -0
  241. package/dist/testing/index.d.ts +1 -0
  242. package/dist/testing/index.js +1 -0
  243. package/dist/use-popup-press.d.ts +1 -0
  244. package/dist/use-popup-press.js +24 -0
  245. package/dist/use-schema-change.d.ts +1 -0
  246. package/dist/use-schema-change.js +13 -0
  247. package/dist/use-stable-callback.d.ts +1 -0
  248. package/dist/use-stable-callback.js +9 -0
  249. package/dist/value/ValuePanel.d.ts +14 -0
  250. package/dist/value/ValuePanel.js +103 -0
  251. package/dist/value/draft.d.ts +33 -0
  252. package/dist/value/draft.js +44 -0
  253. package/dist/value/hex.d.ts +14 -0
  254. package/dist/value/hex.js +46 -0
  255. package/dist/value/json.d.ts +9 -0
  256. package/dist/value/json.js +71 -0
  257. package/dist/value/views.d.ts +22 -0
  258. package/dist/value/views.js +54 -0
  259. package/dist/workbench/DatabaseWorkbench.d.ts +9 -0
  260. package/dist/workbench/DatabaseWorkbench.js +179 -0
  261. package/dist/workbench/DesignerTab.d.ts +10 -0
  262. package/dist/workbench/DesignerTab.js +22 -0
  263. package/dist/workbench/TableTab.d.ts +10 -0
  264. package/dist/workbench/TableTab.js +8 -0
  265. package/dist/workbench/tabs.d.ts +42 -0
  266. package/dist/workbench/tabs.js +139 -0
  267. package/package.json +79 -3
  268. package/src/DatabaseProvider.tsx +37 -0
  269. package/src/actions.ts +34 -0
  270. package/src/client/client.ts +324 -0
  271. package/src/client/index.ts +18 -0
  272. package/src/client/types.ts +161 -0
  273. package/src/client-context.ts +35 -0
  274. package/src/connections/ConnectionForm.tsx +474 -0
  275. package/src/connections/ConnectionManager.tsx +224 -0
  276. package/src/connections/EngineIcon.tsx +18 -0
  277. package/src/connections/connection-config.ts +143 -0
  278. package/src/connections/docker.ts +50 -0
  279. package/src/connections/engine-icons.ts +5 -0
  280. package/src/connections/use-docker-containers.ts +41 -0
  281. package/src/console/DestructiveDialog.tsx +40 -0
  282. package/src/console/HistoryPanel.tsx +67 -0
  283. package/src/console/QueryConsole.tsx +439 -0
  284. package/src/console/SchemaPicker.tsx +32 -0
  285. package/src/console/SqlEditor.tsx +95 -0
  286. package/src/console/StatementResultView.tsx +176 -0
  287. package/src/console/TransactionControls.tsx +53 -0
  288. package/src/console/destructive.ts +60 -0
  289. package/src/console/editing.ts +77 -0
  290. package/src/console/history.ts +62 -0
  291. package/src/console/labels.ts +7 -0
  292. package/src/console/outcome.ts +14 -0
  293. package/src/console/schemas.ts +7 -0
  294. package/src/console/shortcuts.ts +4 -0
  295. package/src/console/useConsoleHistory.ts +39 -0
  296. package/src/ddl/alter-mysql.ts +90 -0
  297. package/src/ddl/alter-sqlite.ts +93 -0
  298. package/src/ddl/alter.ts +29 -0
  299. package/src/ddl/column.ts +74 -0
  300. package/src/ddl/create.ts +92 -0
  301. package/src/ddl/dialect.ts +53 -0
  302. package/src/ddl/diff.ts +164 -0
  303. package/src/ddl/draft.ts +136 -0
  304. package/src/ddl/index.ts +10 -0
  305. package/src/ddl/parse.ts +161 -0
  306. package/src/ddl/statements.ts +20 -0
  307. package/src/ddl/types.ts +60 -0
  308. package/src/ddl/validate.ts +47 -0
  309. package/src/designer/ColumnsEditor.tsx +204 -0
  310. package/src/designer/DesignerView.tsx +184 -0
  311. package/src/designer/ForeignKeysEditor.tsx +132 -0
  312. package/src/designer/IndexesEditor.tsx +63 -0
  313. package/src/designer/NamePicker.tsx +44 -0
  314. package/src/designer/OptionsEditor.tsx +101 -0
  315. package/src/designer/SqlPreview.tsx +56 -0
  316. package/src/designer/TableDesigner.tsx +186 -0
  317. package/src/designer/edit.ts +137 -0
  318. package/src/designer/editor-props.ts +13 -0
  319. package/src/designer/plan.ts +25 -0
  320. package/src/designer/recover.ts +20 -0
  321. package/src/designer/useReferenceColumns.ts +54 -0
  322. package/src/explorer/DatabaseExplorer.tsx +450 -0
  323. package/src/explorer/RowMenu.tsx +134 -0
  324. package/src/explorer/TableDialog.tsx +80 -0
  325. package/src/explorer/expansion.ts +47 -0
  326. package/src/explorer/table-statements.ts +18 -0
  327. package/src/explorer/tree.ts +453 -0
  328. package/src/explorer/use-explorer-loads.ts +157 -0
  329. package/src/explorer/use-stored-expansion.ts +66 -0
  330. package/src/grid/CellEditor.tsx +53 -0
  331. package/src/grid/CopyAsMenu.tsx +33 -0
  332. package/src/grid/DataGrid.tsx +871 -0
  333. package/src/grid/EnumPicker.tsx +108 -0
  334. package/src/grid/GridHeaderCell.tsx +243 -0
  335. package/src/grid/RecordView.tsx +293 -0
  336. package/src/grid/ValueDock.tsx +52 -0
  337. package/src/grid/aggregates.ts +94 -0
  338. package/src/grid/cell-range.ts +26 -0
  339. package/src/grid/column-layout.ts +71 -0
  340. package/src/grid/column-selection.ts +42 -0
  341. package/src/grid/column-view.ts +35 -0
  342. package/src/grid/copy-formats.ts +108 -0
  343. package/src/grid/display.ts +109 -0
  344. package/src/grid/edit-value.ts +60 -0
  345. package/src/grid/enum-type.ts +62 -0
  346. package/src/grid/focused-value.ts +23 -0
  347. package/src/grid/layout.ts +91 -0
  348. package/src/grid/navigation.ts +74 -0
  349. package/src/grid/row-selection.ts +35 -0
  350. package/src/grid/sort.ts +12 -0
  351. package/src/grid/types.ts +59 -0
  352. package/src/host/helper-path.ts +41 -0
  353. package/src/host/host.ts +456 -0
  354. package/src/host/index.ts +5 -0
  355. package/src/host/spawn.ts +124 -0
  356. package/src/host/types.ts +46 -0
  357. package/src/host/validate.ts +402 -0
  358. package/src/index.ts +37 -0
  359. package/src/locales/en.json +577 -0
  360. package/src/locales/nl.json +577 -0
  361. package/src/locales.ts +18 -0
  362. package/src/protocol/connection.ts +86 -0
  363. package/src/protocol/errors.ts +38 -0
  364. package/src/protocol/index.ts +33 -0
  365. package/src/protocol/messages.ts +168 -0
  366. package/src/protocol/schema.ts +76 -0
  367. package/src/protocol/values.ts +52 -0
  368. package/src/sql-split.ts +220 -0
  369. package/src/sql.ts +152 -0
  370. package/src/structure/StructureView.tsx +269 -0
  371. package/src/structure/structure-text.ts +4 -0
  372. package/src/table/CommandField.tsx +318 -0
  373. package/src/table/ImportDialog.tsx +168 -0
  374. package/src/table/TableStatusBar.tsx +91 -0
  375. package/src/table/TableToolbar.tsx +217 -0
  376. package/src/table/TableView.tsx +880 -0
  377. package/src/table/command-field.ts +285 -0
  378. package/src/table/grid-rows.ts +80 -0
  379. package/src/table/import-mapping.ts +37 -0
  380. package/src/table/layout-store.ts +62 -0
  381. package/src/table/paging.ts +33 -0
  382. package/src/table/pending.ts +143 -0
  383. package/src/table/references.ts +38 -0
  384. package/src/table/useLoaded.ts +58 -0
  385. package/src/table/useStoredLayout.ts +53 -0
  386. package/src/table/useTableTransfer.ts +187 -0
  387. package/src/testing/fake.ts +533 -0
  388. package/src/testing/index.ts +1 -0
  389. package/src/use-popup-press.ts +27 -0
  390. package/src/use-schema-change.ts +19 -0
  391. package/src/use-stable-callback.ts +12 -0
  392. package/src/value/ValuePanel.tsx +254 -0
  393. package/src/value/draft.ts +72 -0
  394. package/src/value/hex.ts +62 -0
  395. package/src/value/json.ts +74 -0
  396. package/src/value/views.ts +68 -0
  397. package/src/workbench/DatabaseWorkbench.tsx +348 -0
  398. package/src/workbench/DesignerTab.tsx +37 -0
  399. package/src/workbench/TableTab.tsx +27 -0
  400. package/src/workbench/tabs.ts +184 -0
@@ -0,0 +1,254 @@
1
+ import { useMemo, useState, type KeyboardEvent, type ReactNode, type Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { Copy, Ellipsis, MousePointerClick, WrapText, X } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import {
6
+ Button,
7
+ copyText,
8
+ EmptyState,
9
+ FormError,
10
+ IconButton,
11
+ isApplePlatform,
12
+ KEY_SHORTCUTS,
13
+ matchesShortcut,
14
+ Menu,
15
+ PanelHeader,
16
+ Segmented,
17
+ TextArea,
18
+ Tooltip
19
+ } from '@adecore/ui';
20
+ import { formatBytes, formatNumber } from '@adecore/ui/format';
21
+ import type { GridColumn } from '../grid/types.ts';
22
+ import type { EditValue, Value } from '../protocol/index.ts';
23
+ import { editValueOf, sameDraft, sameSource, sourceOf, type Draft, type DraftProblem } from './draft.ts';
24
+ import { hexDumpLines, parseHex } from './hex.ts';
25
+ import { renderView, sizeOf, viewsOf, type Rendered, type ViewId } from './views.ts';
26
+
27
+ export interface ValuePanelProps {
28
+ /* The column of the focused cell; `null` when no cell has focus, which draws an empty state. */
29
+ column: GridColumn | null;
30
+ /* The whole value of the cell. `undefined` while it loads, or when it cannot be had. */
31
+ value: Value | undefined;
32
+ loading: boolean;
33
+ /* Whether this cell can be changed; a read only panel still shows, formats and copies. */
34
+ editable: boolean;
35
+ /* The panel's draft, applied as a pending edit like an edit in the grid. */
36
+ onCommit?(value: EditValue): void;
37
+ onClose(): void;
38
+ className?: string;
39
+ ref?: Ref<HTMLDivElement>;
40
+ }
41
+
42
+ const WRAPPING_VIEWS: ReadonlySet<ViewId> = new Set(['text', 'formatted', 'utf8']);
43
+
44
+ const READING = 'min-h-0 flex-1 overflow-auto rounded-lg border border-border bg-surface p-2 font-mono text-code text-text';
45
+
46
+ /* The whole value of the focused cell beside the grid, to read and edit what a cell is too small for. */
47
+ export function ValuePanel({ column, value, loading, editable, onCommit, onClose, className, ref }: ValuePanelProps) {
48
+ const { t } = useTranslation('database');
49
+ const ready = column !== null && !loading && value !== undefined;
50
+
51
+ return (
52
+ <div ref={ref} className={clsx('flex h-full min-h-0 min-w-0 flex-col', className)}>
53
+ <PanelHeader title={column === null ? t('value.title') : undefined}>
54
+ {column !== null && (
55
+ <>
56
+ <span className="min-w-0 truncate text-sm font-medium text-text">{column.name}</span>
57
+ {column.type !== '' && <span className="min-w-0 shrink-0 truncate text-xs text-text-faint">{column.type}</span>}
58
+ </>
59
+ )}
60
+ <IconButton icon={X} size="sm" label={t('value.close')} className="ml-auto" onClick={onClose} />
61
+ </PanelHeader>
62
+ {ready ? (
63
+ <ValueEditor column={column} value={value} editable={editable} onCommit={onCommit} />
64
+ ) : (
65
+ <div className="grid min-h-0 grow place-items-center">
66
+ <EmptyState icon={column === null ? MousePointerClick : undefined} busy={column !== null && loading}>
67
+ {column === null ? t('value.selectCell') : loading ? t('value.loading') : t('value.notLoaded')}
68
+ </EmptyState>
69
+ </div>
70
+ )}
71
+ </div>
72
+ );
73
+ }
74
+
75
+ interface ValueEditorProps {
76
+ column: GridColumn;
77
+ value: Value;
78
+ editable: boolean;
79
+ onCommit?(value: EditValue): void;
80
+ }
81
+
82
+ function ValueEditor({ column, value, editable, onCommit }: ValueEditorProps) {
83
+ const { t } = useTranslation('database');
84
+ const { name, type, kind } = column;
85
+ const source = useMemo(() => sourceOf({ name, type, kind }, value), [name, type, kind, value]);
86
+ const [seen, setSeen] = useState(source);
87
+ const [draft, setDraft] = useState<Draft>(source.original);
88
+ const [choice, setChoice] = useState<ViewId | null>(null);
89
+ const [problem, setProblem] = useState<DraftProblem | null>(null);
90
+ const [wrap, setWrap] = useState(true);
91
+
92
+ // A new cell, or the same cell with a new value, starts over from that value.
93
+ if (!sameSource(seen, source)) {
94
+ setSeen(source);
95
+ setDraft(source.original);
96
+ setChoice(null);
97
+ setProblem(null);
98
+ }
99
+
100
+ const views = viewsOf(source, draft);
101
+ const view = choice !== null && views.includes(choice) ? choice : views[0]!;
102
+ const dirty = !sameDraft(draft, source.original);
103
+ const rendered = useMemo(() => (view === 'text' ? null : renderView(view, draft)), [view, draft]);
104
+ const hex = source.binary && draft.mode === 'value' ? parseHex(draft.text) : null;
105
+ const shownProblem: DraftProblem | null = problem ?? (hex !== null && !hex.ok ? { kind: 'hex', problem: hex.problem } : null);
106
+ const size = sizeOf(source, draft);
107
+ const placeholder = draft.mode === 'null' ? 'NULL' : draft.mode === 'default' ? 'DEFAULT' : undefined;
108
+
109
+ const change = (next: Draft): void => {
110
+ setDraft(next);
111
+ setProblem(null);
112
+ };
113
+
114
+ const apply = (): void => {
115
+ if (!editable || onCommit === undefined || !dirty) {
116
+ return;
117
+ }
118
+ const conversion = editValueOf(draft, source);
119
+ if (conversion.ok) {
120
+ onCommit(conversion.value);
121
+ } else {
122
+ setProblem(conversion.problem);
123
+ }
124
+ };
125
+
126
+ const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {
127
+ if (!event.nativeEvent.isComposing && matchesShortcut(KEY_SHORTCUTS.modEnter, event.nativeEvent, isApplePlatform())) {
128
+ event.preventDefault();
129
+ apply();
130
+ }
131
+ };
132
+
133
+ const copy = (): void => {
134
+ if (view === 'hex' && hex !== null && hex.ok) {
135
+ copyText(hexDumpLines(hex.hex).join('\n'));
136
+ } else if (rendered !== null && rendered.kind === 'text') {
137
+ copyText(rendered.text);
138
+ } else {
139
+ copyText(draft.mode === 'value' ? draft.text : draft.mode === 'null' ? 'NULL' : 'DEFAULT');
140
+ }
141
+ };
142
+
143
+ const editor = (extra?: string): ReactNode => (
144
+ <TextArea
145
+ mono
146
+ aria-label={t(source.binary ? 'value.hexEditor' : 'value.editor', { column: column.name })}
147
+ wrap={wrap || source.binary ? 'soft' : 'off'}
148
+ spellCheck={false}
149
+ readOnly={!editable}
150
+ placeholder={placeholder}
151
+ value={draft.mode === 'value' ? draft.text : ''}
152
+ className={extra}
153
+ onChange={(event) => change({ text: event.target.value, mode: 'value' })}
154
+ onKeyDown={handleKeyDown}
155
+ />
156
+ );
157
+
158
+ const problemText = (found: DraftProblem): string =>
159
+ found.kind === 'json'
160
+ ? t('value.invalidJson', { reason: found.reason })
161
+ : t(found.problem === 'odd' ? 'value.invalidHexOdd' : 'value.invalidHexCharacters');
162
+
163
+ const reading = (content: Rendered, wrapped: boolean): ReactNode => {
164
+ if (content.kind === 'empty') {
165
+ return <Faint>{placeholder}</Faint>;
166
+ }
167
+ if (content.kind === 'problem') {
168
+ return <Faint>{problemText(content.problem)}</Faint>;
169
+ }
170
+ return (
171
+ <>
172
+ <pre className={clsx(READING, wrapped ? 'break-words whitespace-pre-wrap' : 'whitespace-pre')}>{content.text}</pre>
173
+ {content.truncated && (
174
+ <p className="shrink-0 text-xs text-text-faint">
175
+ {t('value.dumpTruncated', { shown: formatNumber(content.truncated.shown), total: formatNumber(content.truncated.total) })}
176
+ </p>
177
+ )}
178
+ </>
179
+ );
180
+ };
181
+
182
+ const body = (): ReactNode => {
183
+ if (view === 'text') {
184
+ return editor('min-h-0 flex-1');
185
+ }
186
+ if (view === 'hex' && editable) {
187
+ return (
188
+ <>
189
+ {draft.mode === 'value' && rendered !== null && reading(rendered, false)}
190
+ {editor(draft.mode === 'value' ? 'flex-none' : 'min-h-0 flex-1')}
191
+ </>
192
+ );
193
+ }
194
+ return rendered === null ? null : reading(rendered, wrap && WRAPPING_VIEWS.has(view));
195
+ };
196
+
197
+ return (
198
+ <>
199
+ <div className="flex h-10 shrink-0 items-center gap-2 px-2">
200
+ {views.length > 1 && (
201
+ <Segmented<ViewId>
202
+ value={view}
203
+ onValueChange={(id) => setChoice(id)}
204
+ label={t('value.views')}
205
+ options={views.map((id) => ({ id, label: t(`value.view.${id}`) }))}
206
+ />
207
+ )}
208
+ <div className="ml-auto flex items-center gap-1">
209
+ {WRAPPING_VIEWS.has(view) && (
210
+ <IconButton icon={WrapText} size="sm" label={t('value.wrap')} aria-pressed={wrap} active={wrap} onClick={() => setWrap(!wrap)} />
211
+ )}
212
+ <IconButton icon={Copy} size="sm" label={t('value.copy')} onClick={copy} />
213
+ </div>
214
+ </div>
215
+ <div className="flex min-h-0 flex-1 flex-col gap-2 px-2 pb-2">{body()}</div>
216
+ {shownProblem !== null && <FormError className="shrink-0 px-3 pb-2 text-xs">{problemText(shownProblem)}</FormError>}
217
+ {editable && (
218
+ <div className="flex shrink-0 items-center gap-1.5 border-t border-border p-2">
219
+ <Menu.Root>
220
+ <IconButton icon={Ellipsis} size="sm" label={t('value.more')} render={<Menu.Trigger />} />
221
+ <Menu.Popup>
222
+ {column.nullable !== false && (
223
+ <Menu.Item disabled={draft.mode === 'null'} onClick={() => change({ text: '', mode: 'null' })}>
224
+ {t('value.setNull')}
225
+ </Menu.Item>
226
+ )}
227
+ <Menu.Item disabled={draft.mode === 'default'} onClick={() => change({ text: '', mode: 'default' })}>
228
+ {t('value.setDefault')}
229
+ </Menu.Item>
230
+ </Menu.Popup>
231
+ </Menu.Root>
232
+ <span className="ml-auto flex items-center gap-1.5">
233
+ <Button size="xs" disabled={!dirty} onClick={() => change(source.original)}>
234
+ {t('value.revert')}
235
+ </Button>
236
+ <Tooltip label={t('value.applyHint')} kbd={KEY_SHORTCUTS.modEnter}>
237
+ <Button size="xs" variant="primary" disabled={!dirty || onCommit === undefined} onClick={apply}>
238
+ {t('value.apply')}
239
+ </Button>
240
+ </Tooltip>
241
+ </span>
242
+ </div>
243
+ )}
244
+ <footer className="flex h-8 shrink-0 items-center border-t border-border px-3 text-xs text-text-muted tabular-nums">
245
+ {size !== null &&
246
+ (size.unit === 'bytes' ? formatBytes(size.count) : t('value.characters', { count: size.count, formatted: formatNumber(size.count) }))}
247
+ </footer>
248
+ </>
249
+ );
250
+ }
251
+
252
+ function Faint({ children }: { children?: ReactNode }) {
253
+ return <div className="grid min-h-0 flex-1 place-items-center font-mono text-code text-text-faint">{children}</div>;
254
+ }
@@ -0,0 +1,72 @@
1
+ import { parseDraft } from '../grid/edit-value.ts';
2
+ import type { GridColumn } from '../grid/types.ts';
3
+ import type { EditValue, Value } from '../protocol/index.ts';
4
+ import { parseHex, type HexProblem } from './hex.ts';
5
+ import { checkJson, isJsonDocument } from './json.ts';
6
+
7
+ /* What the editor holds: the text of a value, or a NULL or a DEFAULT that has no text. */
8
+ export interface Draft {
9
+ readonly text: string;
10
+ readonly mode: 'value' | 'null' | 'default';
11
+ }
12
+
13
+ /* What a draft starts from, and what tells a new cell from the same one. */
14
+ export interface Source {
15
+ readonly name: string;
16
+ readonly type: string;
17
+ readonly kind: GridColumn['kind'];
18
+ readonly binary: boolean;
19
+ readonly json: boolean;
20
+ readonly original: Draft;
21
+ }
22
+
23
+ export type DraftProblem = { readonly kind: 'json'; readonly reason: string } | { readonly kind: 'hex'; readonly problem: HexProblem };
24
+
25
+ export type Conversion = { readonly ok: true; readonly value: EditValue } | { readonly ok: false; readonly problem: DraftProblem };
26
+
27
+ /* The text a value is edited as: a binary value as its hex, everything else as it is written. */
28
+ const textOf = (value: Exclude<Value, null>): string => {
29
+ if (typeof value === 'object') {
30
+ return value.hex;
31
+ }
32
+ return String(value);
33
+ };
34
+
35
+ export const sourceOf = (column: Pick<GridColumn, 'name' | 'type' | 'kind'>, value: Value): Source => {
36
+ const binary = column.kind === 'binary' || (value !== null && typeof value === 'object');
37
+ const original: Draft = value === null ? { text: '', mode: 'null' } : { text: textOf(value), mode: 'value' };
38
+ return {
39
+ name: column.name,
40
+ type: column.type,
41
+ kind: column.kind,
42
+ binary,
43
+ json: !binary && (column.kind === 'json' ? value !== null : typeof value === 'string' && isJsonDocument(value)),
44
+ original
45
+ };
46
+ };
47
+
48
+ export const sameSource = (a: Source, b: Source): boolean =>
49
+ a.name === b.name && a.type === b.type && a.kind === b.kind && a.binary === b.binary && a.json === b.json && sameDraft(a.original, b.original);
50
+
51
+ export const sameDraft = (a: Draft, b: Draft): boolean => a.mode === b.mode && (a.mode !== 'value' || a.text === b.text);
52
+
53
+ /* A draft becomes the value to send. Text goes as text, and a number only where the column holds numbers and the text spells one. */
54
+ export const editValueOf = (draft: Draft, source: Source): Conversion => {
55
+ if (draft.mode === 'null') {
56
+ return { ok: true, value: null };
57
+ }
58
+ if (draft.mode === 'default') {
59
+ return { ok: true, value: { kind: 'default' } };
60
+ }
61
+ if (source.binary) {
62
+ const parsed = parseHex(draft.text);
63
+ return parsed.ok ? { ok: true, value: { kind: 'binary', hex: parsed.hex } } : { ok: false, problem: { kind: 'hex', problem: parsed.problem } };
64
+ }
65
+ if (source.json) {
66
+ const checked = checkJson(draft.text);
67
+ if (!checked.ok) {
68
+ return { ok: false, problem: { kind: 'json', reason: checked.reason } };
69
+ }
70
+ }
71
+ return { ok: true, value: parseDraft(draft.text, source.kind) };
72
+ };
@@ -0,0 +1,62 @@
1
+ /* Binary values cross as lowercase hex; these turn it into what a person reads and back. */
2
+
3
+ export type HexProblem = 'characters' | 'odd';
4
+
5
+ export type ParsedHex = { readonly ok: true; readonly hex: string } | { readonly ok: false; readonly problem: HexProblem };
6
+
7
+ export const BYTES_PER_LINE = 16;
8
+
9
+ const GROUP = 8;
10
+ const GROUP_WIDTH = GROUP * 3 - 1;
11
+ const NOT_HEX = /[^0-9a-f]/;
12
+ const UUID_BYTES = 16;
13
+
14
+ /* Spaces and newlines may separate the digits, and a leading `0x` is the way the grid writes a binary value. */
15
+ export const parseHex = (text: string): ParsedHex => {
16
+ const stripped = text.replace(/\s+/g, '').toLowerCase();
17
+ const hex = stripped.startsWith('0x') ? stripped.slice(2) : stripped;
18
+ if (NOT_HEX.test(hex)) {
19
+ return { ok: false, problem: 'characters' };
20
+ }
21
+ return hex.length % 2 === 0 ? { ok: true, hex } : { ok: false, problem: 'odd' };
22
+ };
23
+
24
+ export const bytesOf = (hex: string): Uint8Array => {
25
+ const bytes = new Uint8Array(hex.length / 2);
26
+ for (let i = 0; i < bytes.length; i++) {
27
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
28
+ }
29
+ return bytes;
30
+ };
31
+
32
+ /* Bytes that are not valid UTF-8 become replacement characters rather than an error. */
33
+ export const decodeUtf8 = (hex: string): string => new TextDecoder('utf-8').decode(bytesOf(hex));
34
+
35
+ /* A UUID is exactly 16 bytes; anything else has no UUID form. */
36
+ export const formatUuid = (hex: string): string | null => {
37
+ if (hex.length !== UUID_BYTES * 2) {
38
+ return null;
39
+ }
40
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
41
+ };
42
+
43
+ const isPrintable = (byte: number): boolean => byte >= 0x20 && byte <= 0x7e;
44
+
45
+ const slotsOf = (bytes: Uint8Array, from: number, to: number): string =>
46
+ Array.from(bytes.subarray(from, to), (byte) => byte.toString(16).padStart(2, '0'))
47
+ .join(' ')
48
+ .padEnd(GROUP_WIDTH);
49
+
50
+ /* One line per 16 bytes: the offset, the bytes in two groups of eight, and the same bytes as ASCII with `.` for what does not print. */
51
+ export const hexDumpLines = (hex: string, maxBytes = Number.POSITIVE_INFINITY): string[] => {
52
+ const bytes = bytesOf(hex.slice(0, Math.min(hex.length, maxBytes * 2)));
53
+ const lines: string[] = [];
54
+ for (let offset = 0; offset < bytes.length; offset += BYTES_PER_LINE) {
55
+ const line = bytes.subarray(offset, offset + BYTES_PER_LINE);
56
+ const ascii = Array.from(line, (byte) => (isPrintable(byte) ? String.fromCharCode(byte) : '.')).join('');
57
+ lines.push(
58
+ `${offset.toString(16).padStart(8, '0')} ${slotsOf(bytes, offset, offset + GROUP)} ${slotsOf(bytes, offset + GROUP, offset + BYTES_PER_LINE)} |${ascii}|`
59
+ );
60
+ }
61
+ return lines;
62
+ };
@@ -0,0 +1,74 @@
1
+ /* JSON detection and layout. Numbers stay as the text the server wrote, so a 64-bit integer is not rounded by a parse. */
2
+
3
+ const INDENT = ' ';
4
+
5
+ export type JsonCheck = { readonly ok: true } | { readonly ok: false; readonly reason: string };
6
+
7
+ export const checkJson = (text: string): JsonCheck => {
8
+ try {
9
+ JSON.parse(text);
10
+ return { ok: true };
11
+ } catch (error) {
12
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
13
+ }
14
+ };
15
+
16
+ /* Whether a string is a JSON object or array. A scalar such as `42` or `"x"` is a plain value, not a document. */
17
+ export const isJsonDocument = (text: string): boolean => {
18
+ const start = text.trimStart()[0];
19
+ return (start === '{' || start === '[') && checkJson(text).ok;
20
+ };
21
+
22
+ /* The text laid out with two spaces per level, or `null` when it is not JSON. */
23
+ export const prettyJson = (text: string): string | null => {
24
+ if (!checkJson(text).ok) {
25
+ return null;
26
+ }
27
+
28
+ let output = '';
29
+ let depth = 0;
30
+ let i = 0;
31
+ const lineBreak = () => `\n${INDENT.repeat(depth)}`;
32
+ const nextToken = (from: number): string => {
33
+ let at = from;
34
+ while (/\s/.test(text[at] ?? '')) {
35
+ at++;
36
+ }
37
+ return text[at] ?? '';
38
+ };
39
+
40
+ while (i < text.length) {
41
+ const char = text[i]!;
42
+ if (char === '"') {
43
+ let end = i + 1;
44
+ while (text[end] !== '"') {
45
+ end += text[end] === '\\' ? 2 : 1;
46
+ }
47
+ output += text.slice(i, end + 1);
48
+ i = end + 1;
49
+ continue;
50
+ }
51
+ if (char === '{' || char === '[') {
52
+ const empty = nextToken(i + 1) === (char === '{' ? '}' : ']');
53
+ output += char;
54
+ if (empty) {
55
+ output += char === '{' ? '}' : ']';
56
+ i = text.indexOf(char === '{' ? '}' : ']', i + 1) + 1;
57
+ continue;
58
+ }
59
+ depth++;
60
+ output += lineBreak();
61
+ } else if (char === '}' || char === ']') {
62
+ depth--;
63
+ output += `${lineBreak()}${char}`;
64
+ } else if (char === ',') {
65
+ output += `,${lineBreak()}`;
66
+ } else if (char === ':') {
67
+ output += ': ';
68
+ } else if (!/\s/.test(char)) {
69
+ output += char;
70
+ }
71
+ i++;
72
+ }
73
+ return output.trim();
74
+ };
@@ -0,0 +1,68 @@
1
+ import type { Draft, DraftProblem, Source } from './draft.ts';
2
+ import { decodeUtf8, formatUuid, hexDumpLines, parseHex } from './hex.ts';
3
+ import { checkJson, prettyJson } from './json.ts';
4
+
5
+ export type ViewId = 'text' | 'formatted' | 'hex' | 'utf8' | 'uuid';
6
+
7
+ /* The views that make sense for this value, the first being the one it opens in. */
8
+ export const viewsOf = (source: Source, draft: Draft): ViewId[] => {
9
+ if (source.binary) {
10
+ const parsed = parseHex(draft.text);
11
+ const hex = parsed.ok ? parsed.hex : source.original.text;
12
+ return formatUuid(hex) === null ? ['hex', 'utf8'] : ['hex', 'utf8', 'uuid'];
13
+ }
14
+ return source.json ? ['formatted', 'text'] : ['text'];
15
+ };
16
+
17
+ /* What a draft is, in bytes for a binary value and in characters for the rest; `null` when there is nothing to measure. */
18
+ export const sizeOf = (source: Source, draft: Draft): { unit: 'bytes' | 'characters'; count: number } | null => {
19
+ if (draft.mode !== 'value') {
20
+ return null;
21
+ }
22
+ if (!source.binary) {
23
+ return { unit: 'characters', count: draft.text.length };
24
+ }
25
+ const parsed = parseHex(draft.text);
26
+ return parsed.ok ? { unit: 'bytes', count: parsed.hex.length / 2 } : null;
27
+ };
28
+
29
+ /* A read only view stops its dump here; the text view and a copy are not limited. */
30
+ export const DUMP_LIMIT_BYTES = 16 * 1024;
31
+
32
+ export type Rendered =
33
+ /* A NULL or a DEFAULT, which has nothing to lay out. */
34
+ | { readonly kind: 'empty' }
35
+ | { readonly kind: 'text'; readonly text: string; readonly truncated?: { readonly shown: number; readonly total: number } }
36
+ | { readonly kind: 'problem'; readonly problem: DraftProblem };
37
+
38
+ /* What a read only view shows for the draft. The text view is not here, since it is the editor itself. */
39
+ export const renderView = (view: Exclude<ViewId, 'text'>, draft: Draft): Rendered => {
40
+ if (draft.mode !== 'value') {
41
+ return { kind: 'empty' };
42
+ }
43
+ if (view === 'formatted') {
44
+ const pretty = prettyJson(draft.text);
45
+ if (pretty !== null) {
46
+ return { kind: 'text', text: pretty };
47
+ }
48
+ const checked = checkJson(draft.text);
49
+ return { kind: 'problem', problem: { kind: 'json', reason: checked.ok ? '' : checked.reason } };
50
+ }
51
+
52
+ const parsed = parseHex(draft.text);
53
+ if (!parsed.ok) {
54
+ return { kind: 'problem', problem: { kind: 'hex', problem: parsed.problem } };
55
+ }
56
+ if (view === 'utf8') {
57
+ return { kind: 'text', text: decodeUtf8(parsed.hex) };
58
+ }
59
+ if (view === 'uuid') {
60
+ return { kind: 'text', text: formatUuid(parsed.hex) ?? '' };
61
+ }
62
+ const total = parsed.hex.length / 2;
63
+ return {
64
+ kind: 'text',
65
+ text: hexDumpLines(parsed.hex, DUMP_LIMIT_BYTES).join('\n'),
66
+ truncated: total > DUMP_LIMIT_BYTES ? { shown: DUMP_LIMIT_BYTES, total } : undefined
67
+ };
68
+ };