@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,318 @@
1
+ import { useId, useImperativeHandle, useMemo, useRef, useState, type KeyboardEvent, type Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { ArrowDown, ArrowDownWideNarrow, ArrowRightToLine, ArrowUp, Code, Filter, Search, X, type LucideIcon } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Icon, IconButton, Kbd, KEY_SHORTCUTS, Menu, shortcut } from '@adecore/ui';
6
+ import type { Engine } from '../protocol/index.ts';
7
+ import {
8
+ addChip,
9
+ filterStartText,
10
+ removeChipAt,
11
+ replaceChipAt,
12
+ suggest,
13
+ toggleSort,
14
+ type Chip,
15
+ type CommandColumn,
16
+ type FilterChip,
17
+ type Suggestion
18
+ } from './command-field.ts';
19
+
20
+ const FOCUS_SHORTCUT = shortcut('Mod+F');
21
+
22
+ export interface CommandFieldProps {
23
+ chips: readonly Chip[];
24
+ columns: readonly CommandColumn[];
25
+ engine: Engine;
26
+ /* Every change of the chips applies at once, so this is told the whole list each time. */
27
+ onChipsChange(next: Chip[]): void;
28
+ /* "Jump to column" was picked. */
29
+ onJumpToColumn(name: string): void;
30
+ /* The text input, for the owner to focus on Mod+F. */
31
+ inputRef?: Ref<HTMLInputElement>;
32
+ className?: string;
33
+ ref?: Ref<HTMLDivElement>;
34
+ }
35
+
36
+ const SUGGESTION_ICON: Record<Suggestion['kind'], LucideIcon> = {
37
+ condition: Filter,
38
+ filter: Filter,
39
+ sort: ArrowDownWideNarrow,
40
+ jump: ArrowRightToLine,
41
+ sql: Code
42
+ };
43
+
44
+ const columnOf = (suggestion: Suggestion): CommandColumn | null => (suggestion.kind === 'sql' || suggestion.kind === 'condition' ? null : suggestion.column);
45
+
46
+ /*
47
+ * One field for the conditions and the sorts of a table. They are chips in front of the text; typing
48
+ * opens suggestions to filter on a column, sort by it or jump to it, and a text that reads as a
49
+ * condition (`quantity > 1`) becomes a chip on Enter.
50
+ */
51
+ export function CommandField({ chips, columns, engine, onChipsChange, onJumpToColumn, inputRef, className, ref }: CommandFieldProps) {
52
+ const { t } = useTranslation('database');
53
+ const listId = useId();
54
+ const input = useRef<HTMLInputElement>(null);
55
+ const [text, setText] = useState('');
56
+ /* The chip whose text is in the input to be changed; it stays in the list, hidden, until the change is made. */
57
+ const [editing, setEditing] = useState<number | null>(null);
58
+ const [focused, setFocused] = useState(false);
59
+ const [active, setActive] = useState(0);
60
+ const [dismissed, setDismissed] = useState(false);
61
+ const suggestions = useMemo(() => suggest(engine, text, columns), [engine, text, columns]);
62
+ const open = focused && !dismissed && suggestions.length > 0;
63
+ const optionId = (index: number): string => `${listId}-${index}`;
64
+
65
+ useImperativeHandle(inputRef, () => input.current!, []);
66
+
67
+ const change = (value: string): void => {
68
+ setText(value);
69
+ setActive(0);
70
+ setDismissed(false);
71
+ };
72
+
73
+ const clear = (): void => {
74
+ setText('');
75
+ setEditing(null);
76
+ setActive(0);
77
+ setDismissed(false);
78
+ };
79
+
80
+ const commit = (chip: FilterChip): void => {
81
+ onChipsChange(editing === null ? addChip(chips, chip) : replaceChipAt(chips, editing, chip));
82
+ clear();
83
+ };
84
+
85
+ const pick = (suggestion: Suggestion): void => {
86
+ switch (suggestion.kind) {
87
+ case 'condition':
88
+ case 'sql':
89
+ commit(suggestion.chip);
90
+ break;
91
+ case 'filter':
92
+ change(filterStartText(suggestion.column));
93
+ input.current?.focus();
94
+ break;
95
+ case 'sort':
96
+ onChipsChange(toggleSort(chips, suggestion.column.name));
97
+ clear();
98
+ break;
99
+ case 'jump':
100
+ clear();
101
+ onJumpToColumn(suggestion.column.name);
102
+ break;
103
+ }
104
+ };
105
+
106
+ const edit = (index: number, chip: FilterChip): void => {
107
+ setEditing(index);
108
+ setText(chip.text);
109
+ setActive(0);
110
+ setDismissed(true);
111
+ input.current?.focus();
112
+ };
113
+
114
+ const activate = (index: number, chip: Chip): void => {
115
+ if (chip.kind === 'filter') {
116
+ edit(index, chip);
117
+ } else if (chip.kind === 'sort') {
118
+ onChipsChange(toggleSort(chips, chip.column));
119
+ }
120
+ };
121
+
122
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {
123
+ if (event.nativeEvent.isComposing) {
124
+ return;
125
+ }
126
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
127
+ if (suggestions.length > 0) {
128
+ event.preventDefault();
129
+ setDismissed(false);
130
+ setActive((now) => (now + (event.key === 'ArrowDown' ? 1 : -1) + suggestions.length) % suggestions.length);
131
+ }
132
+ } else if (event.key === 'Enter') {
133
+ event.preventDefault();
134
+ const suggestion = suggestions[Math.min(active, suggestions.length - 1)];
135
+ if (suggestion !== undefined) {
136
+ pick(suggestion);
137
+ } else if (editing !== null) {
138
+ onChipsChange(removeChipAt(chips, editing));
139
+ clear();
140
+ }
141
+ } else if (event.key === 'Escape') {
142
+ if (open) {
143
+ event.preventDefault();
144
+ setDismissed(true);
145
+ } else if (text !== '' || editing !== null) {
146
+ event.preventDefault();
147
+ clear();
148
+ }
149
+ } else if (event.key === 'Backspace' && text === '' && editing === null && chips.length > 0) {
150
+ event.preventDefault();
151
+ onChipsChange(removeChipAt(chips, chips.length - 1));
152
+ }
153
+ };
154
+
155
+ const handleBlur = (): void => {
156
+ setFocused(false);
157
+ if (editing !== null) {
158
+ clear();
159
+ }
160
+ };
161
+
162
+ return (
163
+ <div
164
+ ref={ref}
165
+ className={clsx('field field-sm focus-ring-within relative flex h-auto min-h-7 min-w-0 flex-1 flex-wrap items-center gap-1 py-0.5', className)}
166
+ onClick={(event) => event.target === event.currentTarget && input.current?.focus()}
167
+ >
168
+ <Icon icon={Search} size={14} className="shrink-0 text-text-faint" />
169
+ {chips.map((chip, index) =>
170
+ index === editing ? null : (
171
+ <ChipView
172
+ key={`${chip.kind}:${chip.kind === 'sort' ? chip.column : chip.text}`}
173
+ chip={chip}
174
+ onActivate={() => activate(index, chip)}
175
+ onRemove={() => onChipsChange(removeChipAt(chips, index))}
176
+ />
177
+ )
178
+ )}
179
+ <input
180
+ ref={input}
181
+ role="combobox"
182
+ aria-label={t('table.command.label')}
183
+ aria-expanded={open}
184
+ aria-controls={open ? listId : undefined}
185
+ aria-activedescendant={open ? optionId(active) : undefined}
186
+ aria-autocomplete="list"
187
+ value={text}
188
+ placeholder={chips.length === 0 ? t('table.command.placeholder') : undefined}
189
+ spellCheck={false}
190
+ autoComplete="off"
191
+ className="min-w-24 flex-1 bg-transparent font-mono text-code text-text outline-none placeholder:font-sans placeholder:text-text-faint"
192
+ onChange={(event) => change(event.target.value)}
193
+ onKeyDown={handleKeyDown}
194
+ onFocus={() => setFocused(true)}
195
+ onBlur={handleBlur}
196
+ />
197
+ <Kbd shortcut={FOCUS_SHORTCUT} variant="inline" className="shrink-0" />
198
+ {open && (
199
+ <div
200
+ id={listId}
201
+ role="listbox"
202
+ aria-label={t('table.command.suggestions')}
203
+ className="menu-popup absolute top-full left-0 z-(--z-popup) mt-1.5 max-h-80 w-full max-w-xl"
204
+ // The input keeps the focus, or the popup would close under the pointer.
205
+ onPointerDown={(event) => event.preventDefault()}
206
+ >
207
+ {suggestions.map((suggestion, index) => {
208
+ const column = columnOf(suggestion);
209
+ const previous = suggestions[index - 1];
210
+ const startsGroup = column !== null && (previous === undefined || columnOf(previous)?.name !== column.name);
211
+ return (
212
+ <div key={`${suggestion.kind}:${column?.name ?? ''}`} role="presentation">
213
+ {startsGroup && (
214
+ <Menu.Label>
215
+ <span className="font-mono">{column.name}</span>
216
+ {column.type !== '' && <span> · {column.type}</span>}
217
+ </Menu.Label>
218
+ )}
219
+ {suggestion.kind === 'sql' && index > 0 && <Menu.Separator />}
220
+ <div
221
+ id={optionId(index)}
222
+ role="option"
223
+ aria-selected={index === active}
224
+ data-active={index === active}
225
+ className="menu-item cursor-row"
226
+ onPointerMove={() => setActive(index)}
227
+ onClick={() => pick(suggestion)}
228
+ >
229
+ <Icon icon={SUGGESTION_ICON[suggestion.kind]} size={14} />
230
+ <SuggestionLabel suggestion={suggestion} />
231
+ {index === active && suggestion.kind !== 'sql' && <Kbd shortcut={KEY_SHORTCUTS.enter} />}
232
+ </div>
233
+ </div>
234
+ );
235
+ })}
236
+ </div>
237
+ )}
238
+ </div>
239
+ );
240
+ }
241
+
242
+ function SuggestionLabel({ suggestion }: { suggestion: Suggestion }) {
243
+ const { t } = useTranslation('database');
244
+ switch (suggestion.kind) {
245
+ case 'condition':
246
+ return (
247
+ <>
248
+ {t('table.command.addFilter')} <span className="min-w-0 truncate font-mono">{suggestion.chip.text}</span>
249
+ </>
250
+ );
251
+ case 'filter':
252
+ return (
253
+ <>
254
+ {t('table.command.filterOn')} <span className="font-mono">{suggestion.column.name}</span>
255
+ </>
256
+ );
257
+ case 'sort':
258
+ return (
259
+ <>
260
+ {t('table.command.sortBy')} <span className="font-mono">{suggestion.column.name}</span>
261
+ </>
262
+ );
263
+ case 'jump':
264
+ return t('table.command.jumpTo');
265
+ case 'sql':
266
+ return (
267
+ <>
268
+ {t('table.command.asSql')}
269
+ <Menu.Hint className="font-mono">WHERE …</Menu.Hint>
270
+ </>
271
+ );
272
+ }
273
+ }
274
+
275
+ interface ChipViewProps {
276
+ chip: Chip;
277
+ onActivate(): void;
278
+ onRemove(): void;
279
+ }
280
+
281
+ /* A filter opens its text for editing, a sort flips its direction, and a raw ORDER BY is only shown. */
282
+ function ChipView({ chip, onActivate, onRemove }: ChipViewProps) {
283
+ const { t } = useTranslation('database');
284
+ return (
285
+ <span className="inline-flex h-5 max-w-72 min-w-0 shrink-0 items-center rounded-md bg-text/10 font-mono text-code text-text">
286
+ {chip.kind === 'filter' && (
287
+ <button
288
+ type="button"
289
+ aria-label={t('table.command.editFilter', { filter: chip.text })}
290
+ className="min-w-0 truncate pl-1.5 text-left"
291
+ onClick={onActivate}
292
+ >
293
+ {chip.text}
294
+ </button>
295
+ )}
296
+ {chip.kind === 'sort' && (
297
+ <button
298
+ type="button"
299
+ aria-label={t('table.command.flipSort', { column: chip.column })}
300
+ className="inline-flex min-w-0 items-center gap-1 pl-1.5 text-left"
301
+ onClick={onActivate}
302
+ >
303
+ <Icon icon={chip.direction === 'asc' ? ArrowUp : ArrowDown} size={12} className="shrink-0" />
304
+ <span className="truncate">{chip.column}</span>
305
+ </button>
306
+ )}
307
+ {chip.kind === 'order' && <span className="min-w-0 truncate pl-1.5">{chip.text}</span>}
308
+ <IconButton
309
+ icon={X}
310
+ size="2xs"
311
+ label={t(chip.kind === 'sort' ? 'table.command.removeSort' : 'table.command.removeFilter')}
312
+ tooltip={false}
313
+ className="shrink-0"
314
+ onClick={onRemove}
315
+ />
316
+ </span>
317
+ );
318
+ }
@@ -0,0 +1,168 @@
1
+ import type { Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button, Dialog, FormError, Select, Spinner, Switch } from '@adecore/ui';
5
+ import { formatNumber } from '@adecore/ui/format';
6
+ import type { ColumnInfo } from '../protocol/index.ts';
7
+ import { hasMapping } from './import-mapping.ts';
8
+
9
+ /* What a file's first lines look like, as the client samples them. */
10
+ export interface ImportSample {
11
+ readonly columns: readonly string[];
12
+ readonly rows: readonly (readonly string[])[];
13
+ }
14
+
15
+ /* How many of the sampled rows the preview shows. */
16
+ const PREVIEW_ROWS = 8;
17
+ const SKIP = '-1';
18
+
19
+ export interface ImportFormProps {
20
+ /* The file's path, which the person confirms the import of. */
21
+ path: string;
22
+ /* The columns of the table a file column can go to. */
23
+ tableColumns: readonly ColumnInfo[];
24
+ /* `null` while a new sample is read. */
25
+ sample: ImportSample | null;
26
+ header: boolean;
27
+ /* The table column each file column goes to, by name; `null` skips the file column. */
28
+ mapping: readonly (string | null)[];
29
+ busy: boolean;
30
+ error: string | null;
31
+ onHeaderChange(header: boolean): void;
32
+ onMappingChange(fileColumn: number, tableColumn: string | null): void;
33
+ onImport(): void;
34
+ onCancel(): void;
35
+ className?: string;
36
+ ref?: Ref<HTMLDivElement>;
37
+ }
38
+
39
+ /* The body of the import dialog: how the file's columns map onto the table's, and a look at the rows. */
40
+ export function ImportForm({
41
+ path,
42
+ tableColumns,
43
+ sample,
44
+ header,
45
+ mapping,
46
+ busy,
47
+ error,
48
+ onHeaderChange,
49
+ onMappingChange,
50
+ onImport,
51
+ onCancel,
52
+ className,
53
+ ref
54
+ }: ImportFormProps) {
55
+ const { t } = useTranslation('database');
56
+ const items = [{ value: SKIP, label: t('table.import.skip') }, ...tableColumns.map((column, index) => ({ value: String(index), label: column.name }))];
57
+ const valueOf = (name: string | null): string => {
58
+ const index = name === null ? -1 : tableColumns.findIndex((column) => column.name === name);
59
+ return index < 0 ? SKIP : String(index);
60
+ };
61
+ const canImport = sample !== null && hasMapping(mapping) && !busy;
62
+
63
+ return (
64
+ <div ref={ref} className={clsx('flex min-h-0 flex-col gap-4', className)}>
65
+ <p className="truncate font-mono text-code text-text-muted">{path}</p>
66
+ <div className="flex items-center gap-3 text-sm text-text">
67
+ <Switch checked={header} label={t('table.import.header')} disabled={busy} onCheckedChange={onHeaderChange} />
68
+ <span>{t('table.import.header')}</span>
69
+ {sample === null && <Spinner size={12} label={t('table.import.sampling')} />}
70
+ </div>
71
+ {sample !== null && (
72
+ <>
73
+ <div className="grid min-h-0 grid-cols-2 items-center gap-x-3 gap-y-1.5 overflow-auto">
74
+ <span className="text-xs text-text-faint">{t('table.import.fileColumn')}</span>
75
+ <span className="text-xs text-text-faint">{t('table.import.tableColumn')}</span>
76
+ {sample.columns.map((name, index) => (
77
+ <MappingRow
78
+ key={index}
79
+ name={name}
80
+ value={valueOf(mapping[index] ?? null)}
81
+ items={items}
82
+ disabled={busy}
83
+ label={t('table.import.mapTo', { column: name })}
84
+ onChange={(value) => onMappingChange(index, value === SKIP ? null : (tableColumns[Number(value)]?.name ?? null))}
85
+ />
86
+ ))}
87
+ </div>
88
+ <div className="flex flex-col gap-1">
89
+ <span className="text-xs text-text-faint">
90
+ {t('table.import.preview', { count: formatNumber(Math.min(sample.rows.length, PREVIEW_ROWS)) })}
91
+ </span>
92
+ <div className="max-h-48 overflow-auto rounded-lg border border-border">
93
+ <table className="w-full border-collapse text-left font-mono text-code">
94
+ <thead className="sticky top-0 bg-surface-raised">
95
+ <tr>
96
+ {sample.columns.map((name, index) => (
97
+ <th key={index} className="max-w-48 truncate border-b border-border px-2 py-1 font-medium text-text-muted">
98
+ {name}
99
+ </th>
100
+ ))}
101
+ </tr>
102
+ </thead>
103
+ <tbody>
104
+ {sample.rows.slice(0, PREVIEW_ROWS).map((row, rowIndex) => (
105
+ <tr key={rowIndex} className="border-b border-border-soft last:border-b-0">
106
+ {sample.columns.map((_, index) => (
107
+ <td key={index} className="max-w-48 truncate px-2 py-1 whitespace-nowrap text-text">
108
+ {row[index] ?? ''}
109
+ </td>
110
+ ))}
111
+ </tr>
112
+ ))}
113
+ </tbody>
114
+ </table>
115
+ </div>
116
+ </div>
117
+ </>
118
+ )}
119
+ {error !== null && <FormError>{error}</FormError>}
120
+ <Dialog.Footer className="mt-0">
121
+ <Button disabled={busy} onClick={onCancel}>
122
+ {t('table.import.cancel')}
123
+ </Button>
124
+ <Button variant="primary" disabled={!canImport} onClick={onImport}>
125
+ {busy && <Spinner size={12} />}
126
+ {t('table.import.confirm')}
127
+ </Button>
128
+ </Dialog.Footer>
129
+ </div>
130
+ );
131
+ }
132
+
133
+ interface MappingRowProps {
134
+ name: string;
135
+ value: string;
136
+ items: { value: string; label: string }[];
137
+ disabled: boolean;
138
+ label: string;
139
+ onChange(value: string): void;
140
+ }
141
+
142
+ function MappingRow({ name, value, items, disabled, label, onChange }: MappingRowProps) {
143
+ return (
144
+ <>
145
+ <span className="truncate font-mono text-code text-text">{name}</span>
146
+ <Select size="sm" className="w-full" label={label} value={value} items={items} disabled={disabled} onValueChange={onChange} />
147
+ </>
148
+ );
149
+ }
150
+
151
+ export interface ImportDialogProps extends ImportFormProps {
152
+ open: boolean;
153
+ onOpenChange(open: boolean): void;
154
+ }
155
+
156
+ /* The import of a file into the table: pick how the columns map, look at the rows, confirm. */
157
+ export function ImportDialog({ open, onOpenChange, ...form }: ImportDialogProps) {
158
+ const { t } = useTranslation('database');
159
+ return (
160
+ <Dialog.Root open={open} onOpenChange={(next) => !form.busy && onOpenChange(next)}>
161
+ <Dialog.Popup className="flex max-h-[calc(100vh-96px)] w-[720px] max-w-[calc(100vw-48px)] flex-col p-5">
162
+ <Dialog.Title>{t('table.import.title')}</Dialog.Title>
163
+ <Dialog.Description className="mt-1 mb-4">{t('table.import.description')}</Dialog.Description>
164
+ <ImportForm {...form} />
165
+ </Dialog.Popup>
166
+ </Dialog.Root>
167
+ );
168
+ }
@@ -0,0 +1,91 @@
1
+ import type { Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { ChevronLeft, ChevronRight } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Button, IconButton, Spinner, Tooltip } from '@adecore/ui';
6
+ import { formatDecimal, formatNumber, formatRounded } from '@adecore/ui/format';
7
+ import { useNumberNotation } from '../client-context.ts';
8
+ import type { Aggregates } from '../grid/aggregates.ts';
9
+ import { numeralText } from '../grid/display.ts';
10
+ import type { PageBounds } from './paging.ts';
11
+
12
+ /* What the selected cells add up to, and which columns they are in. */
13
+ export interface SelectionFigures {
14
+ readonly columns: readonly string[];
15
+ readonly aggregates: Aggregates;
16
+ }
17
+
18
+ export interface TableStatusBarProps {
19
+ elapsedMs: number;
20
+ bounds: PageBounds;
21
+ counting: boolean;
22
+ /* Shown on the right; left out when fewer than two cells are selected. */
23
+ selection?: SelectionFigures | null;
24
+ onCount(): void;
25
+ onPrevious(): void;
26
+ onNext(): void;
27
+ className?: string;
28
+ ref?: Ref<HTMLElement>;
29
+ }
30
+
31
+ /* Which rows are on the page and how many there are, the way to the other pages, and the figures of the selected cells. */
32
+ export function TableStatusBar({ elapsedMs, bounds, counting, selection, onCount, onPrevious, onNext, className, ref }: TableStatusBarProps) {
33
+ const { t } = useTranslation('database');
34
+
35
+ return (
36
+ <footer ref={ref} className={clsx('flex h-9 shrink-0 items-center gap-3 border-t border-border px-3 text-xs text-text-muted', className)}>
37
+ <span className="flex items-center gap-1 tabular-nums">
38
+ {bounds.to === 0 ? t('table.noRows') : t('table.range', { from: formatNumber(bounds.from), to: formatNumber(bounds.to) })}
39
+ {bounds.to > 0 &&
40
+ (bounds.exact ? (
41
+ <span>{t('table.total', { total: formatNumber(bounds.total) })}</span>
42
+ ) : (
43
+ <>
44
+ <span>{t('table.of')}</span>
45
+ <Button size="xs" aria-label={t('table.count')} disabled={counting} onClick={onCount}>
46
+ {counting && <Spinner size={12} />}
47
+ {t('table.atLeast', { total: formatNumber(bounds.total) })}
48
+ </Button>
49
+ </>
50
+ ))}
51
+ </span>
52
+ <span className="flex items-center">
53
+ <IconButton icon={ChevronLeft} size="sm" label={t('table.previousPage')} disabled={!bounds.hasPrevious} onClick={onPrevious} />
54
+ <IconButton icon={ChevronRight} size="sm" label={t('table.nextPage')} disabled={!bounds.hasNext} onClick={onNext} />
55
+ </span>
56
+ <Tooltip label={t('table.queryTime')}>
57
+ <span className="text-text-faint tabular-nums">{t('table.elapsed', { value: formatDecimal(elapsedMs) })}</span>
58
+ </Tooltip>
59
+ {selection != null && <SelectionSummary selection={selection} />}
60
+ </footer>
61
+ );
62
+ }
63
+
64
+ function SelectionSummary({ selection }: { selection: SelectionFigures }) {
65
+ const { t } = useTranslation('database');
66
+ const { columns, aggregates } = selection;
67
+ const { count, numeric } = aggregates;
68
+ const notation = useNumberNotation();
69
+ const figure = (label: string, value: string) => (
70
+ <span className="shrink-0">
71
+ <span className="text-text-faint">{label}</span> <span className="text-text">{value}</span>
72
+ </span>
73
+ );
74
+
75
+ return (
76
+ <span className="ml-auto flex min-w-0 items-center gap-4 tabular-nums" data-selection="">
77
+ <span className="min-w-0 truncate font-mono text-text">
78
+ {columns.length === 1 ? columns[0] : t('table.aggregates.columns', { formatted: formatNumber(columns.length) })}
79
+ </span>
80
+ <span className="shrink-0 text-text-faint">{t('table.aggregates.cells', { count, cells: formatNumber(count) })}</span>
81
+ {numeric !== null && (
82
+ <>
83
+ {figure(t('table.aggregates.sum'), formatRounded(numeric.sum, 2))}
84
+ {figure(t('table.aggregates.average'), formatRounded(numeric.average, 2))}
85
+ {figure(t('table.aggregates.minimum'), numeralText(numeric.minimumText, notation))}
86
+ {figure(t('table.aggregates.maximum'), numeralText(numeric.maximumText, notation))}
87
+ </>
88
+ )}
89
+ </span>
90
+ );
91
+ }