@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,450 @@
1
+ import { useEffect, useRef, useState, type KeyboardEvent, type ReactNode, type Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { ChevronRight, CircleAlert, Database, Eye, Folder, KeyRound, Link2, Lock, RectangleVertical, Search, Table } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Button, ContextMenu, EmptyState, Icon, Input, Spinner, Tooltip } from '@adecore/ui';
6
+ import { formatNumber } from '@adecore/ui/format';
7
+ import type { ExplorerSelection } from '../actions.ts';
8
+ import { useDatabaseAction, useDatabaseClient } from '../client-context.ts';
9
+ import type { Connection } from '../client/types.ts';
10
+ import { EngineIcon } from '../connections/EngineIcon.tsx';
11
+ import { RowMenu } from './RowMenu.tsx';
12
+ import { TableDialog, type TableRequest } from './TableDialog.tsx';
13
+ import {
14
+ connectionKey,
15
+ flattenTree,
16
+ hasMenu,
17
+ isExpandable,
18
+ isFolderKey,
19
+ isSelectable,
20
+ navigate,
21
+ neededLoads,
22
+ selectionKey,
23
+ selectionOf,
24
+ tabStop,
25
+ tableOf,
26
+ type ErrorRow,
27
+ type LoadTarget,
28
+ type ExpandableRow,
29
+ type TreeRow
30
+ } from './tree.ts';
31
+ import { useExplorerLoads } from './use-explorer-loads.ts';
32
+ import { usePopupPress } from '../use-popup-press.ts';
33
+ import { useStoredExpansion } from './use-stored-expansion.ts';
34
+
35
+ const INDENT = 16;
36
+ /* Where the chevron slot of the first level starts inside a row. */
37
+ const BASE_INDENT = 4;
38
+ const CHEVRON_SIZE = 16;
39
+
40
+ export interface DatabaseExplorerProps {
41
+ connections: readonly Connection[];
42
+ /* The selected connection, schema or table. Without it the explorer keeps the selection itself, starting at `defaultValue`. */
43
+ value?: ExplorerSelection | null;
44
+ defaultValue?: ExplorerSelection | null;
45
+ onValueChange?(selection: ExplorerSelection | null): void;
46
+ showSystemSchemas?: boolean;
47
+ className?: string;
48
+ ref?: Ref<HTMLDivElement>;
49
+ }
50
+
51
+ /*
52
+ * The connections, their schemas and their tables as a tree, loaded as each node opens. Every row
53
+ * selects; a double click or Enter on a table asks the app to open it through `useDatabaseAction`.
54
+ */
55
+ export function DatabaseExplorer({ connections, value, defaultValue = null, onValueChange, showSystemSchemas = false, className, ref }: DatabaseExplorerProps) {
56
+ const { t } = useTranslation('database');
57
+ const client = useDatabaseClient();
58
+ const act = useDatabaseAction();
59
+ const loads = useExplorerLoads(connections);
60
+ const [own, setOwn] = useState<ExplorerSelection | null>(defaultValue);
61
+ const [request, setRequest] = useState<TableRequest | null>(null);
62
+ const { expanded, collapsed, setExpanded, setCollapsed } = useStoredExpansion(connections);
63
+ const pressedInPopup = usePopupPress();
64
+ const [filter, setFilter] = useState('');
65
+ const [activeKey, setActiveKey] = useState<string | null>(null);
66
+ const rowElements = useRef(new Map<string, HTMLElement>());
67
+ const focusAfterRender = useRef(false);
68
+ const selected = value === undefined ? own : value;
69
+ const selectedKey = selected === null ? null : selectionKey(selected);
70
+
71
+ const input = {
72
+ connections,
73
+ expanded,
74
+ collapsed,
75
+ showSystemSchemas,
76
+ schemas: loads.schemas,
77
+ tables: loads.tables,
78
+ structures: loads.structures,
79
+ versions: loads.versions
80
+ };
81
+ const rows = flattenTree({ ...input, filter });
82
+ const stop = tabStop(rows, activeKey, selectedKey);
83
+
84
+ useEffect(() => {
85
+ for (const target of neededLoads(input)) {
86
+ loads.start(target);
87
+ }
88
+ });
89
+
90
+ useEffect(() => {
91
+ if (focusAfterRender.current && activeKey !== null) {
92
+ focusAfterRender.current = false;
93
+ rowElements.current.get(activeKey)?.focus();
94
+ }
95
+ }, [activeKey]);
96
+
97
+ const setOpen = (key: string, open: boolean): void => {
98
+ // A folder is open until the person closes it, everything else is closed until they open it.
99
+ const folder = isFolderKey(key);
100
+ const toggle = (current: ReadonlySet<string>): ReadonlySet<string> => {
101
+ const next = new Set(current);
102
+ if (open !== folder) {
103
+ next.add(key);
104
+ } else {
105
+ next.delete(key);
106
+ }
107
+ return next;
108
+ };
109
+ (folder ? setCollapsed : setExpanded)(toggle);
110
+ };
111
+
112
+ const toggle = (row: ExpandableRow): void => {
113
+ setOpen(row.key, !row.expanded);
114
+ };
115
+
116
+ const select = (selection: ExplorerSelection): void => {
117
+ if (value === undefined) {
118
+ setOwn(selection);
119
+ }
120
+ onValueChange?.(selection);
121
+ };
122
+
123
+ const pick = (row: TreeRow): void => {
124
+ const selection = selectionOf(row);
125
+ if (selection !== null) {
126
+ select(selection);
127
+ }
128
+ };
129
+
130
+ const open = (row: TreeRow): void => {
131
+ const ref = tableOf(row);
132
+ if (ref !== null) {
133
+ pick(row);
134
+ act?.({ kind: 'open-table', ref, view: 'data' });
135
+ }
136
+ };
137
+
138
+ const activate = (row: TreeRow): void => {
139
+ switch (row.kind) {
140
+ case 'table':
141
+ case 'column':
142
+ open(row);
143
+ break;
144
+ case 'connection':
145
+ case 'schema':
146
+ case 'folder':
147
+ pick(row);
148
+ toggle(row);
149
+ break;
150
+ case 'error':
151
+ loads.reset(row.retry);
152
+ break;
153
+ }
154
+ };
155
+
156
+ const onKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {
157
+ if ((e.target as HTMLElement).tagName === 'BUTTON') {
158
+ return;
159
+ }
160
+ const action = navigate(rows, activeKey ?? stop, e.key);
161
+ if (action === null) {
162
+ return;
163
+ }
164
+ e.preventDefault();
165
+ if (action.type === 'focus') {
166
+ focusAfterRender.current = true;
167
+ setActiveKey(action.key);
168
+ } else if (action.type === 'activate') {
169
+ const row = rows.find((candidate) => candidate.key === action.key);
170
+ if (row !== undefined) {
171
+ activate(row);
172
+ }
173
+ } else {
174
+ setOpen(action.key, action.type === 'expand');
175
+ }
176
+ };
177
+
178
+ const click = (row: TreeRow): void => {
179
+ if (pressedInPopup()) {
180
+ return;
181
+ }
182
+ setActiveKey(row.key);
183
+ pick(row);
184
+ if (row.kind !== 'table' && isExpandable(row)) {
185
+ toggle(row);
186
+ } else if (row.kind === 'error') {
187
+ loads.reset(row.retry);
188
+ }
189
+ };
190
+
191
+ const refresh = (target: LoadTarget): void => {
192
+ loads.reset(target);
193
+ };
194
+
195
+ const disconnect = (connection: Connection): void => {
196
+ void client.disconnect(connection.id).catch(() => undefined);
197
+ loads.reset({ connectionId: connection.id });
198
+ setOpen(connectionKey(connection.id), false);
199
+ };
200
+
201
+ const finish = (done: TableRequest, to: string | undefined): void => {
202
+ setRequest(null);
203
+ const { connectionId, schema, table } = done.ref;
204
+ if (selected?.connectionId === connectionId && selected.schema === schema && selected.table === table) {
205
+ select(to === undefined ? { connectionId, schema } : { connectionId, schema, table: to });
206
+ }
207
+ };
208
+
209
+ const connectionOf = (row: TreeRow): Connection | undefined => {
210
+ const selection = selectionOf(row);
211
+ return selection === null ? undefined : connections.find((entry) => entry.id === selection.connectionId);
212
+ };
213
+
214
+ const isSelected = (row: TreeRow | undefined): boolean => row !== undefined && isSelectable(row) && row.key === selectedKey;
215
+
216
+ const renderRow = (row: TreeRow, index: number): ReactNode => {
217
+ const connection = connectionOf(row);
218
+ const menu =
219
+ connection !== undefined && hasMenu(row) ? (
220
+ <RowMenu row={row} connection={connection} onRefresh={refresh} onDisconnect={disconnect} onRequest={setRequest} />
221
+ ) : null;
222
+ const selectedRow = isSelected(row);
223
+ const props = {
224
+ role: 'treeitem',
225
+ 'aria-level': row.level,
226
+ 'aria-setsize': row.setSize,
227
+ 'aria-posinset': row.posInSet,
228
+ 'aria-expanded': isExpandable(row) ? row.expanded : undefined,
229
+ 'aria-selected': isSelectable(row) ? selectedRow : undefined,
230
+ 'aria-busy': row.kind === 'loading' ? true : undefined,
231
+ tabIndex: row.focusable ? (row.key === stop ? 0 : -1) : undefined,
232
+ className: clsx(
233
+ // The line box is the row's height, so the text lands on whole pixels in a row of an odd height.
234
+ 'focus-ring relative mx-2 flex h-[25px] min-w-0 items-center gap-1.5 rounded-md pr-2 text-xs leading-[25px] text-text focus-visible:-outline-offset-2',
235
+ selectedRow ? 'bg-surface-active' : row.focusable && 'hover:bg-surface-hover',
236
+ selectedRow && isSelected(rows[index - 1]) && 'rounded-t-none',
237
+ selectedRow && isSelected(rows[index + 1]) && 'rounded-b-none'
238
+ ),
239
+ style: { paddingInlineStart: BASE_INDENT + (row.level - 1) * INDENT },
240
+ ref: (element: HTMLElement | null) => {
241
+ if (element === null) {
242
+ rowElements.current.delete(row.key);
243
+ } else {
244
+ rowElements.current.set(row.key, element);
245
+ }
246
+ },
247
+ onClick: () => click(row),
248
+ onDoubleClick: tableOf(row) === null ? undefined : () => open(row),
249
+ onFocus: () => {
250
+ if (row.focusable) {
251
+ setActiveKey(row.key);
252
+ }
253
+ }
254
+ };
255
+ const content = (
256
+ <>
257
+ {Array.from({ length: row.level - 1 }, (_, i) => (
258
+ <span
259
+ key={i}
260
+ aria-hidden
261
+ className="pointer-events-none absolute inset-y-0 w-px bg-border"
262
+ style={{ insetInlineStart: BASE_INDENT + i * INDENT + CHEVRON_SIZE / 2 - 1 }}
263
+ />
264
+ ))}
265
+ <RowContent row={row} onToggle={isExpandable(row) ? () => toggle(row) : undefined} />
266
+ </>
267
+ );
268
+ if (menu === null) {
269
+ return (
270
+ <div key={row.key} {...props}>
271
+ {content}
272
+ </div>
273
+ );
274
+ }
275
+ return (
276
+ <ContextMenu.Root key={row.key}>
277
+ <ContextMenu.Trigger render={<div {...props} />}>{content}</ContextMenu.Trigger>
278
+ <ContextMenu.Popup>{menu}</ContextMenu.Popup>
279
+ </ContextMenu.Root>
280
+ );
281
+ };
282
+
283
+ return (
284
+ <div ref={ref} className={clsx('flex min-h-0 min-w-0 flex-col', className)}>
285
+ <div className="shrink-0 border-b border-border p-2">
286
+ <Input
287
+ size="sm"
288
+ icon={Search}
289
+ type="search"
290
+ value={filter}
291
+ placeholder={t('explorer.filter')}
292
+ aria-label={t('explorer.filter')}
293
+ spellCheck={false}
294
+ onChange={(e) => setFilter(e.target.value)}
295
+ onKeyDown={(e) => {
296
+ if (e.key === 'Escape') {
297
+ setFilter('');
298
+ }
299
+ }}
300
+ />
301
+ </div>
302
+ {connections.length === 0 ? (
303
+ <EmptyState icon={Database}>{t('explorer.noConnections')}</EmptyState>
304
+ ) : rows.length === 0 ? (
305
+ <EmptyState icon={Search}>{t('explorer.noMatches', { query: filter.trim() })}</EmptyState>
306
+ ) : (
307
+ <div role="tree" aria-label={t('explorer.label')} className="min-h-0 grow overflow-y-auto py-2" onKeyDown={onKeyDown}>
308
+ {rows.map(renderRow)}
309
+ </div>
310
+ )}
311
+ {request !== null && <TableDialog request={request} onClose={() => setRequest(null)} onDone={finish} />}
312
+ </div>
313
+ );
314
+ }
315
+
316
+ function RowContent({ row, onToggle }: { row: TreeRow; onToggle?: () => void }) {
317
+ const { t } = useTranslation('database');
318
+ switch (row.kind) {
319
+ case 'connection':
320
+ return (
321
+ <>
322
+ <Chevron open={row.expanded} onToggle={onToggle} />
323
+ <EngineIcon engine={row.connection.config.engine} size={16} className="shrink-0 text-text-muted" />
324
+ <span className="min-w-0 truncate">{row.connection.name || t('connections.untitled')}</span>
325
+ {row.connection.config.readOnly === true && (
326
+ <Tooltip label={t('explorer.readOnly')}>
327
+ <span className="inline-flex shrink-0 text-text-faint">
328
+ <Icon icon={Lock} size={12} />
329
+ </span>
330
+ </Tooltip>
331
+ )}
332
+ {row.version !== null && <span className="min-w-0 shrink-[2] truncate text-text-faint">{row.version}</span>}
333
+ {row.schemaCount !== null && <SchemaCount shown={row.schemaCount.shown} total={row.schemaCount.total} />}
334
+ </>
335
+ );
336
+ case 'schema':
337
+ return (
338
+ <>
339
+ <Chevron open={row.expanded} onToggle={onToggle} />
340
+ <Icon icon={Database} size={16} className="shrink-0 text-text-muted" />
341
+ <span className="min-w-0 truncate">{row.schema}</span>
342
+ </>
343
+ );
344
+ case 'folder':
345
+ return (
346
+ <>
347
+ <Chevron open={row.expanded} onToggle={onToggle} />
348
+ <Icon icon={Folder} size={16} className="shrink-0 text-text-muted" />
349
+ <span className="min-w-0 truncate">{t(row.group === 'view' ? 'explorer.views' : 'explorer.tables')}</span>
350
+ <span className="shrink-0 text-text-faint tabular-nums">{formatNumber(row.count)}</span>
351
+ </>
352
+ );
353
+ case 'table':
354
+ return (
355
+ <>
356
+ <Chevron open={row.expanded} onToggle={onToggle} />
357
+ <Icon icon={row.table.kind === 'view' ? Eye : Table} size={16} className="shrink-0 text-text-muted" />
358
+ <span className="min-w-0 truncate">{row.table.name}</span>
359
+ </>
360
+ );
361
+ case 'column':
362
+ return (
363
+ <>
364
+ <ChevronSlot />
365
+ <ColumnIcon primaryKey={row.primaryKey} foreignKey={row.foreignKey} />
366
+ <span className="min-w-0 truncate">{row.column.name}</span>
367
+ {row.column.type !== '' && <span className="min-w-0 shrink-[2] truncate text-text-faint">{row.column.type}</span>}
368
+ </>
369
+ );
370
+ case 'loading':
371
+ return (
372
+ <>
373
+ <ChevronSlot />
374
+ <Spinner size={12} />
375
+ <span className="text-text-faint">{t('explorer.loading')}</span>
376
+ </>
377
+ );
378
+ case 'error':
379
+ return <ErrorContent row={row} />;
380
+ case 'empty':
381
+ return (
382
+ <>
383
+ <ChevronSlot />
384
+ <span className="text-text-faint">{t(EMPTY_WORDS[row.of])}</span>
385
+ </>
386
+ );
387
+ }
388
+ }
389
+
390
+ const EMPTY_WORDS = { schemas: 'explorer.noSchemas', tables: 'explorer.noTables', columns: 'explorer.noColumns' } as const;
391
+
392
+ /* Where a chevron would be, so a row without one lines up with its siblings that have one. */
393
+ function ChevronSlot() {
394
+ return <span className="size-4 shrink-0" />;
395
+ }
396
+
397
+ function Chevron({ open, onToggle }: { open: boolean; onToggle?: () => void }) {
398
+ return (
399
+ <span
400
+ className="grid size-4 shrink-0 place-items-center text-text-muted"
401
+ onClick={(e) => {
402
+ e.stopPropagation();
403
+ onToggle?.();
404
+ }}
405
+ onDoubleClick={(e) => e.stopPropagation()}
406
+ >
407
+ <Icon icon={ChevronRight} size={12} className={clsx('transition-transform', open && 'rotate-90')} />
408
+ </span>
409
+ );
410
+ }
411
+
412
+ function SchemaCount({ shown, total }: { shown: number; total: number }) {
413
+ const { t } = useTranslation('database');
414
+ const counts = { shown: formatNumber(shown), total: formatNumber(total) };
415
+ return (
416
+ <Tooltip label={t('explorer.schemaCountHint', { count: total, ...counts })}>
417
+ <span className="shrink-0 text-text-faint tabular-nums">{t('explorer.schemaCount', counts)}</span>
418
+ </Tooltip>
419
+ );
420
+ }
421
+
422
+ function ColumnIcon({ primaryKey, foreignKey }: { primaryKey: boolean; foreignKey: boolean }) {
423
+ const { t } = useTranslation('database');
424
+ if (!primaryKey && !foreignKey) {
425
+ return <Icon icon={RectangleVertical} size={16} className="shrink-0 text-text-faint" />;
426
+ }
427
+ return (
428
+ <Tooltip label={t(primaryKey ? 'explorer.primaryKey' : 'explorer.foreignKey')}>
429
+ <span className="inline-flex shrink-0 text-text-muted">
430
+ <Icon icon={primaryKey ? KeyRound : Link2} size={16} />
431
+ </span>
432
+ </Tooltip>
433
+ );
434
+ }
435
+
436
+ function ErrorContent({ row }: { row: ErrorRow }) {
437
+ const { t } = useTranslation('database');
438
+ return (
439
+ <>
440
+ <ChevronSlot />
441
+ <Icon icon={CircleAlert} size={16} className="shrink-0 text-status-error" />
442
+ <Tooltip label={row.message}>
443
+ <span className="min-w-0 truncate text-status-error">{row.message}</span>
444
+ </Tooltip>
445
+ <Button size="xs" variant="ghost" className="ml-auto" tabIndex={-1}>
446
+ {t('explorer.retry')}
447
+ </Button>
448
+ </>
449
+ );
450
+ }
@@ -0,0 +1,134 @@
1
+ import { Fragment, type ReactNode } from 'react';
2
+ import {
3
+ Copy,
4
+ Eraser,
5
+ PencilRuler,
6
+ Pencil,
7
+ Plus,
8
+ RefreshCw,
9
+ Settings2,
10
+ SquareTerminal,
11
+ Table,
12
+ TableProperties,
13
+ Trash2,
14
+ Unplug,
15
+ type LucideIcon
16
+ } from 'lucide-react';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { ContextMenu, copyText, Icon } from '@adecore/ui';
19
+ import type { DatabaseAction } from '../actions.ts';
20
+ import { useDatabaseAction, useDatabaseClient } from '../client-context.ts';
21
+ import type { Connection, TableRef } from '../client/types.ts';
22
+ import type { TableKind } from '../protocol/index.ts';
23
+ import type { TableRequest } from './TableDialog.tsx';
24
+ import { selectAllSql, type LoadTarget, type TreeRow } from './tree.ts';
25
+
26
+ export interface RowMenuProps {
27
+ row: TreeRow;
28
+ connection: Connection;
29
+ onRefresh(target: LoadTarget): void;
30
+ onDisconnect(connection: Connection): void;
31
+ onRequest(request: TableRequest): void;
32
+ }
33
+
34
+ /* The items of a row's context menu. An item whose action has nowhere to go, or that writes to a read only connection, is left out. */
35
+ export function RowMenu({ row, connection, onRefresh, onDisconnect, onRequest }: RowMenuProps) {
36
+ const { t } = useTranslation('database');
37
+ const client = useDatabaseClient();
38
+ const act = useDatabaseAction();
39
+ const writable = connection.config.readOnly !== true;
40
+
41
+ const consoleAction = (schema?: string, sql?: string): DatabaseAction => ({
42
+ kind: 'open-console',
43
+ connectionId: connection.id,
44
+ ...(schema === undefined ? {} : { schema }),
45
+ ...(sql === undefined ? {} : { sql })
46
+ });
47
+
48
+ const copyDdl = async (ref: TableRef): Promise<void> => {
49
+ const structure = await client.session(connection).structure(ref.schema, ref.table);
50
+ if (structure.ddl !== null) {
51
+ copyText(structure.ddl);
52
+ }
53
+ };
54
+
55
+ const item = (key: string, icon: LucideIcon, label: string, onClick: () => void, destructive = false): ReactNode => (
56
+ <ContextMenu.Item key={key} className={destructive ? 'text-status-error' : undefined} onClick={onClick}>
57
+ <Icon icon={icon} size={14} />
58
+ {label}
59
+ </ContextMenu.Item>
60
+ );
61
+
62
+ const actionItem = (key: string, icon: LucideIcon, label: string, action: DatabaseAction): ReactNode =>
63
+ act === undefined ? null : item(key, icon, label, () => act(action));
64
+
65
+ const tableGroups = (ref: TableRef, kind: TableKind): ReactNode[][] => {
66
+ const request = (change: TableRequest['change']) => (): void => onRequest({ change, connection, ref, kind });
67
+ return [
68
+ [
69
+ actionItem('open-data', Table, t('explorer.openData'), { kind: 'open-table', ref, view: 'data' }),
70
+ actionItem('open-structure', TableProperties, t('explorer.openStructure'), { kind: 'open-table', ref, view: 'structure' }),
71
+ kind === 'table' && writable ? actionItem('edit-table', PencilRuler, t('explorer.editTable'), { kind: 'edit-table', ref }) : null,
72
+ actionItem('console', SquareTerminal, t('explorer.newConsoleHere'), consoleAction(ref.schema, selectAllSql(connection.config.engine, ref)))
73
+ ],
74
+ [
75
+ item('copy-name', Copy, t('explorer.copyName'), () => copyText(ref.table)),
76
+ item('copy-ddl', Copy, t('explorer.copyDdl'), () => void copyDdl(ref).catch(() => undefined))
77
+ ],
78
+ writable
79
+ ? [
80
+ kind === 'table' ? item('rename', Pencil, t('explorer.rename.action'), request('rename')) : null,
81
+ kind === 'table' ? item('truncate', Eraser, t('explorer.truncate.action'), request('truncate'), true) : null,
82
+ item('drop', Trash2, t(kind === 'view' ? 'explorer.drop.view.action' : 'explorer.drop.table.action'), request('drop'), true)
83
+ ]
84
+ : []
85
+ ];
86
+ };
87
+
88
+ const groupsOf = (): ReactNode[][] => {
89
+ switch (row.kind) {
90
+ case 'connection':
91
+ return [
92
+ [
93
+ actionItem('console', SquareTerminal, t('explorer.newConsole'), consoleAction()),
94
+ item('refresh', RefreshCw, t('explorer.refresh'), () => onRefresh({ connectionId: connection.id })),
95
+ actionItem('manage', Settings2, t('explorer.editConnection'), { kind: 'manage-connection', connectionId: connection.id })
96
+ ],
97
+ [item('disconnect', Unplug, t('explorer.disconnect'), () => onDisconnect(connection))]
98
+ ];
99
+ case 'schema':
100
+ case 'folder':
101
+ return [
102
+ [
103
+ writable
104
+ ? actionItem('new-table', Plus, t('explorer.newTable'), { kind: 'new-table', connectionId: connection.id, schema: row.schema })
105
+ : null,
106
+ actionItem('console', SquareTerminal, t('explorer.newConsoleHere'), consoleAction(row.schema)),
107
+ item('refresh', RefreshCw, t('explorer.refresh'), () => onRefresh({ connectionId: connection.id, schema: row.schema }))
108
+ ]
109
+ ];
110
+ case 'table':
111
+ return tableGroups(row.ref, row.table.kind);
112
+ case 'column':
113
+ return [
114
+ [
115
+ actionItem('open-data', Table, t('explorer.open'), { kind: 'open-table', ref: row.ref, view: 'data' }),
116
+ item('copy-name', Copy, t('explorer.copyName'), () => copyText(row.column.name))
117
+ ]
118
+ ];
119
+ default:
120
+ return [];
121
+ }
122
+ };
123
+
124
+ const groups = groupsOf()
125
+ .map((items) => items.filter((entry) => entry !== null))
126
+ .filter((items) => items.length > 0);
127
+
128
+ return groups.map((items, i) => (
129
+ <Fragment key={i}>
130
+ {i > 0 && <ContextMenu.Separator />}
131
+ {items}
132
+ </Fragment>
133
+ ));
134
+ }
@@ -0,0 +1,80 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { PromptDialog } from '@adecore/ui';
3
+ import { dialectOf } from '../ddl/index.ts';
4
+ import { useDatabaseClient } from '../client-context.ts';
5
+ import type { Connection, TableRef } from '../client/types.ts';
6
+ import type { TableKind } from '../protocol/index.ts';
7
+ import { statementOf, type TableChange } from './table-statements.ts';
8
+
9
+ export interface TableRequest {
10
+ readonly change: TableChange;
11
+ readonly connection: Connection;
12
+ readonly ref: TableRef;
13
+ readonly kind: TableKind;
14
+ }
15
+
16
+ export interface TableDialogProps {
17
+ request: TableRequest;
18
+ onClose(): void;
19
+ /* After the statement ran; `to` is the new name of a rename. */
20
+ onDone(request: TableRequest, to: string | undefined): void;
21
+ }
22
+
23
+ /* The question behind Rename, Truncate and Drop: it runs the statement, keeps a failure in the dialog and sees to it that the listeners hear the shape changed. */
24
+ export function TableDialog({ request, onClose, onDone }: TableDialogProps) {
25
+ const { t } = useTranslation('database');
26
+ const client = useDatabaseClient();
27
+ const { change, connection, ref, kind } = request;
28
+ const words = change === 'drop' ? `explorer.drop.${kind === 'view' ? 'view' : 'table'}` : `explorer.${change}`;
29
+
30
+ const run = async (to?: string): Promise<void> => {
31
+ const session = client.session(connection);
32
+ const dialect = dialectOf(await session.server());
33
+ const { results } = await session.execute(statementOf(dialect, change, ref, kind, to));
34
+ const failed = results.find((result) => result.kind === 'error');
35
+ if (failed !== undefined && failed.kind === 'error') {
36
+ throw new Error(failed.error.message);
37
+ }
38
+ // The client tells its listeners after a `RENAME`, a `DROP` and a `TRUNCATE`, but not after a SQLite emptying, which is a `DELETE FROM`.
39
+ if (change === 'truncate' && dialect.engine === 'sqlite') {
40
+ client.notifySchemaChange({ connectionId: ref.connectionId, schema: ref.schema });
41
+ }
42
+ onDone(request, to);
43
+ };
44
+
45
+ const confirm = async (typed: string): Promise<void> => {
46
+ if (change === 'rename') {
47
+ if (typed === ref.table) {
48
+ onClose();
49
+ return;
50
+ }
51
+ await run(typed);
52
+ } else if (change === 'drop') {
53
+ if (typed !== ref.table) {
54
+ throw new Error(t('explorer.drop.mismatch'));
55
+ }
56
+ await run();
57
+ } else {
58
+ await run();
59
+ }
60
+ };
61
+
62
+ return (
63
+ <PromptDialog
64
+ open
65
+ danger={change !== 'rename'}
66
+ title={t(`${words}.title`, { name: ref.table })}
67
+ description={change === 'rename' ? undefined : t(`${words}.description`, { name: ref.table })}
68
+ field={
69
+ change === 'truncate'
70
+ ? undefined
71
+ : change === 'rename'
72
+ ? { label: t('explorer.rename.field'), initial: ref.table, mono: true }
73
+ : { label: t('explorer.drop.field', { name: ref.table }), placeholder: ref.table, mono: true }
74
+ }
75
+ confirmLabel={t(`${words}.confirm`)}
76
+ onConfirm={confirm}
77
+ onOpenChange={onClose}
78
+ />
79
+ );
80
+ }