sqlite-hub 1.2.0 → 1.4.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 (152) hide show
  1. package/README.md +106 -73
  2. package/bin/sqlite-hub.js +176 -2
  3. package/docs/API.md +122 -0
  4. package/docs/CLI.md +227 -0
  5. package/docs/DESIGN_GUIDELINES.md +45 -0
  6. package/docs/changelog.md +141 -0
  7. package/docs/shortkeys.md +27 -0
  8. package/docs/todo.md +16 -0
  9. package/examples/api/generate-types.js +52 -0
  10. package/frontend/assets/mockups/backups_1_1920.webp +0 -0
  11. package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
  12. package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
  13. package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
  14. package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
  15. package/frontend/assets/mockups/charts_1_1920.webp +0 -0
  16. package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
  17. package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
  18. package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
  19. package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
  20. package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
  21. package/frontend/assets/mockups/connections_1_1920.webp +0 -0
  22. package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
  23. package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
  24. package/frontend/assets/mockups/data_1_1920.webp +0 -0
  25. package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
  26. package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
  27. package/frontend/assets/mockups/documents_1_1920.webp +0 -0
  28. package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
  29. package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
  30. package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
  31. package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
  32. package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
  33. package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
  34. package/frontend/assets/mockups/overview_1_1920.webp +0 -0
  35. package/frontend/assets/mockups/settings_1_1920.webp +0 -0
  36. package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
  37. package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
  38. package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
  39. package/frontend/assets/mockups/structure_1_1920.webp +0 -0
  40. package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
  41. package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
  42. package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
  43. package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
  44. package/frontend/js/api.js +18 -0
  45. package/frontend/js/app.js +118 -8
  46. package/frontend/js/components/connectionCard.js +1 -1
  47. package/frontend/js/components/emptyState.js +4 -4
  48. package/frontend/js/components/generateTypesDropdown.js +33 -0
  49. package/frontend/js/components/metricCard.js +1 -1
  50. package/frontend/js/components/modal.js +211 -18
  51. package/frontend/js/components/pageHeader.js +1 -1
  52. package/frontend/js/components/queryHistoryDetail.js +3 -3
  53. package/frontend/js/components/queryHistoryHeader.js +1 -1
  54. package/frontend/js/components/queryHistoryPanel.js +1 -1
  55. package/frontend/js/components/rowEditorPanel.js +13 -3
  56. package/frontend/js/router.js +28 -6
  57. package/frontend/js/store.js +422 -8
  58. package/frontend/js/views/backups.js +529 -37
  59. package/frontend/js/views/charts.js +9 -9
  60. package/frontend/js/views/connections.js +2 -2
  61. package/frontend/js/views/data.js +1 -1
  62. package/frontend/js/views/documents.js +3 -3
  63. package/frontend/js/views/editor.js +6 -6
  64. package/frontend/js/views/mediaTagging.js +7 -5
  65. package/frontend/js/views/overview.js +3 -3
  66. package/frontend/js/views/settings.js +3 -3
  67. package/frontend/js/views/structure.js +6 -1
  68. package/frontend/styles/base.css +1 -40
  69. package/frontend/styles/components.css +109 -232
  70. package/frontend/styles/structure-graph.css +32 -68
  71. package/frontend/styles/tailwind.css +8 -4
  72. package/frontend/styles/tailwind.generated.css +1 -1
  73. package/frontend/styles/tokens.css +28 -21
  74. package/frontend/styles/views.css +94 -261
  75. package/package.json +18 -2
  76. package/server/routes/backups.js +18 -2
  77. package/server/routes/externalApi.js +36 -0
  78. package/server/routes/structure.js +22 -0
  79. package/server/services/databaseCommandService.js +14 -0
  80. package/server/services/sqlite/backupDiff.js +914 -0
  81. package/server/services/sqlite/backupService.js +77 -1
  82. package/server/services/sqlite/structureService.js +7 -0
  83. package/server/services/typeGenerationService.js +663 -0
  84. package/tailwind.config.cjs +0 -1
  85. package/.github/funding.yml +0 -2
  86. package/.github/workflows/ci.yml +0 -36
  87. package/database.sqlite +0 -0
  88. package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
  89. package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
  90. package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
  91. package/frontend/assets/mockups/connections_1200.webp +0 -0
  92. package/frontend/assets/mockups/data_1_1200.webp +0 -0
  93. package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
  94. package/frontend/assets/mockups/documents_1200.webp +0 -0
  95. package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
  96. package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
  97. package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
  98. package/frontend/assets/mockups/overview_1200.webp +0 -0
  99. package/frontend/assets/mockups/settings_1200.webp +0 -0
  100. package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
  101. package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
  102. package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
  103. package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
  104. package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
  105. package/frontend/assets/mockups/structure_1_1200.webp +0 -0
  106. package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
  107. package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
  108. package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
  109. package/tests/api-token-auth.test.js +0 -279
  110. package/tests/backup-manager.test.js +0 -140
  111. package/tests/backups-view.test.js +0 -70
  112. package/tests/charts-height-preset-storage.test.js +0 -60
  113. package/tests/charts-route-state.test.js +0 -144
  114. package/tests/check-constraint-options.test.js +0 -90
  115. package/tests/cli-args.test.js +0 -113
  116. package/tests/cli-service-delegation.test.js +0 -140
  117. package/tests/connection-removal.test.js +0 -52
  118. package/tests/connections-file-dialog-route.test.js +0 -89
  119. package/tests/copy-column-modal.test.js +0 -131
  120. package/tests/database-command-service.test.js +0 -174
  121. package/tests/database-documents.test.js +0 -85
  122. package/tests/documents-view.test.js +0 -132
  123. package/tests/dropdown-button.test.js +0 -101
  124. package/tests/email-preview.test.js +0 -89
  125. package/tests/export-blob.test.js +0 -99
  126. package/tests/export-filenames.test.js +0 -38
  127. package/tests/file-path-preview.test.js +0 -165
  128. package/tests/form-controls.test.js +0 -34
  129. package/tests/json-preview.test.js +0 -49
  130. package/tests/local-request-security.test.js +0 -85
  131. package/tests/markdown-documents.test.js +0 -103
  132. package/tests/native-file-dialog.test.js +0 -105
  133. package/tests/query-editor.test.js +0 -28
  134. package/tests/query-history-detail.test.js +0 -37
  135. package/tests/query-history-header.test.js +0 -30
  136. package/tests/query-results-truncation.test.js +0 -20
  137. package/tests/risky-sql.test.js +0 -30
  138. package/tests/row-editor-json.test.js +0 -82
  139. package/tests/row-editor-null-values.test.js +0 -184
  140. package/tests/row-editor-timestamp-preview.test.js +0 -192
  141. package/tests/security-paths.test.js +0 -84
  142. package/tests/settings-api-tokens-route.test.js +0 -97
  143. package/tests/settings-metadata.test.js +0 -114
  144. package/tests/settings-view.test.js +0 -108
  145. package/tests/sql-formatter.test.js +0 -173
  146. package/tests/sql-highlight.test.js +0 -38
  147. package/tests/sql-identifier-safety.test.js +0 -171
  148. package/tests/sql-result-limit.test.js +0 -66
  149. package/tests/structure-view.test.js +0 -60
  150. package/tests/table-designer-v2-unique-constraints.test.js +0 -78
  151. package/tests/table-scroll-state.test.js +0 -70
  152. package/tests/text-cell-stats.test.js +0 -38
@@ -153,6 +153,17 @@ export function getBackup(backupId) {
153
153
  return request(`/api/backups/${encodeURIComponent(backupId)}`);
154
154
  }
155
155
 
156
+ export function getBackupDiff(backupId, options = {}) {
157
+ const params = new URLSearchParams();
158
+
159
+ if (options.sampleLimit !== undefined) {
160
+ params.set("sampleLimit", String(options.sampleLimit));
161
+ }
162
+
163
+ const query = params.toString();
164
+ return request(`/api/backups/${encodeURIComponent(backupId)}/diff${query ? `?${query}` : ""}`);
165
+ }
166
+
156
167
  export function createBackup(payload = {}) {
157
168
  return request("/api/backups", {
158
169
  method: "POST",
@@ -340,6 +351,13 @@ export function getStructureDetail(tableName) {
340
351
  return request(`/api/structure/${encodeURIComponent(tableName)}`);
341
352
  }
342
353
 
354
+ export function generateStructureTypes(tableName, payload = {}) {
355
+ return request(`/api/structure/${encodeURIComponent(tableName)}/types`, {
356
+ method: "POST",
357
+ body: payload,
358
+ });
359
+ }
360
+
343
361
  export function getTableDesignerOverview() {
344
362
  return request("/api/table-designer");
345
363
  }
@@ -56,7 +56,10 @@ import {
56
56
  openDeleteQueryHistoryModal,
57
57
  openDeleteSettingsApiTokenModal,
58
58
  openCreateBackupModal,
59
- openEditBackupNotesModal,
59
+ openEditBackupModal,
60
+ openBackupDiffDrawer,
61
+ closeBackupDiffDrawer,
62
+ openGenerateTypesModal,
60
63
  openDeleteBackupModal,
61
64
  openRestoreBackupModal,
62
65
  openDeleteQueryChartModal,
@@ -122,7 +125,7 @@ import {
122
125
  submitDeleteSettingsApiTokenConfirmation,
123
126
  submitBackupSafetyChoice,
124
127
  submitCreateBackupConfirmation,
125
- submitEditBackupNotesConfirmation,
128
+ submitEditBackupConfirmation,
126
129
  submitRowUpdatePreviewConfirmation,
127
130
  setQueryHistoryPanelVisibility,
128
131
  sortDataTableByColumn,
@@ -132,6 +135,7 @@ import {
132
135
  setCopyColumnModalError,
133
136
  setCopyColumnModalEditedText,
134
137
  setCopyColumnModalSubmitting,
138
+ setBackupDiffTab,
135
139
  setRoute,
136
140
  setSettingsSection,
137
141
  saveQueryHistoryNotes,
@@ -166,6 +170,7 @@ import {
166
170
  updateCurrentTableDesignerColumnField,
167
171
  updateCurrentTableDesignerConstraintField,
168
172
  updateCurrentTableDesignerField,
173
+ updateGenerateTypesModal,
169
174
  addCurrentTableDesignerColumn,
170
175
  applyCurrentMediaTaggingSelection,
171
176
  removeCurrentTableDesignerColumn,
@@ -1065,11 +1070,11 @@ function renderNotFoundView() {
1065
1070
  <p class="font-mono text-[10px] uppercase tracking-[0.3em] text-primary-container/40">
1066
1071
  ROUTE_LOST // HASH_NOT_RECOGNIZED
1067
1072
  </p>
1068
- <h1 class="mt-4 font-headline text-6xl font-black uppercase tracking-tight text-primary-container">
1073
+ <h1 class="mt-4 font-body text-6xl font-black uppercase tracking-tight text-primary-container">
1069
1074
  404_SIGNAL
1070
1075
  </h1>
1071
1076
  <button
1072
- class="standard-button mt-8 px-6 font-headline text-sm"
1077
+ class="standard-button mt-8 px-6 font-body text-sm"
1073
1078
  data-action="navigate"
1074
1079
  data-to="/"
1075
1080
  type="button"
@@ -2003,6 +2008,68 @@ function downloadTextFile({ text, filename, mimeType }) {
2003
2008
  URL.revokeObjectURL(url);
2004
2009
  }
2005
2010
 
2011
+ async function copyGeneratedTypes() {
2012
+ const modal = getState().modal;
2013
+ const code = modal?.kind === 'generate-types' ? String(modal.result?.code ?? '') : '';
2014
+
2015
+ if (!code) {
2016
+ showToast('No generated code to copy.', 'alert');
2017
+ return;
2018
+ }
2019
+
2020
+ if (!navigator.clipboard?.writeText) {
2021
+ showToast('Clipboard API is not available.', 'alert');
2022
+ return;
2023
+ }
2024
+
2025
+ try {
2026
+ await navigator.clipboard.writeText(code);
2027
+ showToast(
2028
+ modal.result?.files?.length ? 'Type definitions copied to clipboard.' : 'Type definition copied to clipboard.',
2029
+ 'success',
2030
+ );
2031
+ } catch (error) {
2032
+ showToast(
2033
+ modal.result?.files?.length
2034
+ ? 'Type definitions could not be copied.'
2035
+ : 'Type definition could not be copied.',
2036
+ 'alert',
2037
+ );
2038
+ }
2039
+ }
2040
+
2041
+ function downloadGeneratedTypes() {
2042
+ const modal = getState().modal;
2043
+ const result = modal?.kind === 'generate-types' ? modal.result : null;
2044
+ const files = result?.files ?? [];
2045
+
2046
+ if (files.length) {
2047
+ files.forEach((file, index) => {
2048
+ window.setTimeout(() => {
2049
+ downloadTextFile({
2050
+ text: file.code,
2051
+ filename: file.fileName || `${file.tableName || 'types'}.txt`,
2052
+ mimeType: 'text/plain;charset=utf-8',
2053
+ });
2054
+ }, index * 120);
2055
+ });
2056
+ showToast(`${files.length} type definition downloads started.`, 'success');
2057
+ return;
2058
+ }
2059
+
2060
+ if (!result?.code) {
2061
+ showToast('No generated code to download.', 'alert');
2062
+ return;
2063
+ }
2064
+
2065
+ downloadTextFile({
2066
+ text: result.code,
2067
+ filename: result.fileName || 'types.txt',
2068
+ mimeType: 'text/plain;charset=utf-8',
2069
+ });
2070
+ showToast('Type definition download started.', 'success');
2071
+ }
2072
+
2006
2073
  function getSelectedDataBrowserRowForJson(state) {
2007
2074
  if (state.dataBrowser.selectedRow) {
2008
2075
  return state.dataBrowser.selectedRow;
@@ -2582,11 +2649,29 @@ async function handleAction(actionNode) {
2582
2649
  case 'open-table-in-sql-editor':
2583
2650
  await openTableInSqlEditor(actionNode.dataset.tableName);
2584
2651
  return;
2652
+ case 'open-generate-types-modal':
2653
+ await openGenerateTypesModal(actionNode.dataset.tableName, actionNode.dataset.typeTarget, actionNode.dataset.typeScope);
2654
+ return;
2655
+ case 'copy-generated-types':
2656
+ await copyGeneratedTypes();
2657
+ return;
2658
+ case 'download-generated-types':
2659
+ downloadGeneratedTypes();
2660
+ return;
2585
2661
  case 'open-restore-backup-modal':
2586
2662
  openRestoreBackupModal(actionNode.dataset.backupId);
2587
2663
  return;
2588
- case 'open-edit-backup-notes-modal':
2589
- openEditBackupNotesModal(actionNode.dataset.backupId);
2664
+ case 'open-compare-backup-drawer':
2665
+ await openBackupDiffDrawer(actionNode.dataset.backupId);
2666
+ return;
2667
+ case 'set-backup-diff-tab':
2668
+ setBackupDiffTab(actionNode.dataset.tab);
2669
+ return;
2670
+ case 'close-backup-diff-drawer':
2671
+ closeBackupDiffDrawer();
2672
+ return;
2673
+ case 'open-edit-backup-modal':
2674
+ openEditBackupModal(actionNode.dataset.backupId);
2590
2675
  return;
2591
2676
  case 'open-delete-backup-modal':
2592
2677
  openDeleteBackupModal(actionNode.dataset.backupId);
@@ -3181,6 +3266,12 @@ document.addEventListener('keydown', event => {
3181
3266
  return;
3182
3267
  }
3183
3268
 
3269
+ if (state.route.name === 'backups' && state.backups.diff?.visible) {
3270
+ event.preventDefault();
3271
+ closeBackupDiffDrawer();
3272
+ return;
3273
+ }
3274
+
3184
3275
  if (state.editor.historySelectedId !== null || state.editor.historyDetail) {
3185
3276
  event.preventDefault();
3186
3277
  clearQueryHistorySelection();
@@ -3227,6 +3318,15 @@ document.addEventListener('input', event => {
3227
3318
  return;
3228
3319
  }
3229
3320
 
3321
+ if (bindNode.dataset.bind === 'type-generation-field') {
3322
+ if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
3323
+ return;
3324
+ }
3325
+
3326
+ void updateGenerateTypesModal(bindNode.dataset.typeGenerationField, bindNode.value);
3327
+ return;
3328
+ }
3329
+
3230
3330
  if (bindNode.dataset.bind === 'copy-column-format-field') {
3231
3331
  updateCopyColumnModalFormatField(
3232
3332
  bindNode.dataset.field,
@@ -3398,6 +3498,13 @@ document.addEventListener('change', event => {
3398
3498
  return;
3399
3499
  }
3400
3500
 
3501
+ if (bindNode.dataset.bind === 'type-generation-field') {
3502
+ const nextValue =
3503
+ bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox' ? bindNode.checked : bindNode.value;
3504
+ void updateGenerateTypesModal(bindNode.dataset.typeGenerationField, nextValue);
3505
+ return;
3506
+ }
3507
+
3401
3508
  if (bindNode.dataset.bind === 'data-search-column') {
3402
3509
  void setDataSearchColumn(bindNode.value);
3403
3510
  return;
@@ -3623,8 +3730,11 @@ document.addEventListener('submit', async event => {
3623
3730
  type: String(formData.get('type') ?? 'manual'),
3624
3731
  });
3625
3732
  return;
3626
- case 'edit-backup-notes':
3627
- await submitEditBackupNotesConfirmation(String(formData.get('notes') ?? ''));
3733
+ case 'edit-backup':
3734
+ await submitEditBackupConfirmation({
3735
+ name: String(formData.get('name') ?? ''),
3736
+ notes: String(formData.get('notes') ?? ''),
3737
+ });
3628
3738
  return;
3629
3739
  case 'delete-backup-confirm':
3630
3740
  await submitDeleteBackupConfirmation();
@@ -36,7 +36,7 @@ export function renderConnectionCard(connection, activeConnectionId) {
36
36
  renderStatusBadge(isActive ? "ACTIVE" : "RECENT", isActive ? "primary" : "muted"),
37
37
  connection.readOnly ? renderStatusBadge("READ_ONLY", "alert") : "",
38
38
  "</div></div>",
39
- '<h3 class="mb-1 font-headline text-xl font-bold uppercase ',
39
+ '<h3 class="mb-1 font-body text-xl font-bold uppercase ',
40
40
  isActive ? "text-[#FCE300]" : "text-on-surface",
41
41
  '">',
42
42
  escapeHtml(connection.label),
@@ -61,7 +61,7 @@ function renderActiveConnection(activeConnection) {
61
61
  <p class="text-[10px] font-mono uppercase tracking-[0.24em] text-primary-container/70">
62
62
  ACTIVE_DATABASE
63
63
  </p>
64
- <h2 class="mt-2 font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
64
+ <h2 class="mt-2 font-body text-2xl font-black uppercase tracking-tight text-primary-container">
65
65
  ${escapeHtml(activeConnection.label)}
66
66
  </h2>
67
67
  <p class="mt-2 font-mono text-[10px] text-on-surface-variant/55">${escapeHtml(
@@ -93,7 +93,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
93
93
  SYSTEM_READY // ${hasActive ? 'ACTIVE_CONTEXT' : 'IDLE_STATE'}
94
94
  </span>
95
95
  </div>
96
- <h1 class="mb-4 font-headline text-7xl font-black tracking-tighter text-primary-container opacity-90 md:text-9xl">
96
+ <h1 class="mb-4 font-body text-7xl font-black tracking-tighter text-primary-container opacity-90 md:text-9xl">
97
97
  SQLite Hub
98
98
  </h1>
99
99
  <p class="font-mono text-primary-container/40">The only fucking SQLite Manager you'll ever need.</p>
@@ -104,7 +104,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
104
104
  </div>
105
105
  <div class="mx-auto grid w-full max-w-3xl grid-cols-1 gap-4 px-6 md:grid-cols-2">
106
106
  <button
107
- class="landing-primary-action clipped-btn group flex items-center justify-between bg-primary-container px-8 py-6 font-headline text-lg font-bold transition-all duration-300 hover:shadow-[0_0_20px_rgba(252,227,0,0.3)]"
107
+ class="landing-primary-action clipped-btn group flex items-center justify-between bg-primary-container px-8 py-6 font-body text-lg font-bold transition-all duration-300 hover:shadow-[0_0_20px_rgba(252,227,0,0.3)]"
108
108
  data-action="open-modal"
109
109
  data-modal="open-connection"
110
110
  style="--clip-path: polygon(0 0, 90% 0, 100% 25%, 100% 100%, 0 100%);"
@@ -114,7 +114,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
114
114
  <span class="material-symbols-outlined transition-transform group-hover:translate-x-1">add_circle</span>
115
115
  </button>
116
116
  <button
117
- class="flex items-center justify-between border-l-2 border-primary-container bg-surface-container-highest px-8 py-6 font-headline text-lg font-bold text-primary-container transition-colors duration-150 hover:bg-surface-bright"
117
+ class="flex items-center justify-between border-l-2 border-primary-container bg-surface-container-highest px-8 py-6 font-body text-lg font-bold text-primary-container transition-colors duration-150 hover:bg-surface-bright"
118
118
  data-action="open-modal"
119
119
  data-modal="create-connection"
120
120
  type="button"
@@ -0,0 +1,33 @@
1
+ import { renderDropdownButton } from './dropdownButton.js';
2
+
3
+ export function renderGenerateTypesDropdown({ selectedTableName = '', hasTables = false } = {}) {
4
+ const hasSelectedTable = Boolean(String(selectedTableName ?? '').trim());
5
+
6
+ return renderDropdownButton({
7
+ align: 'left',
8
+ icon: 'code',
9
+ label: 'Generate Types',
10
+ title: 'Generate types',
11
+ items: [
12
+ {
13
+ action: 'open-generate-types-modal',
14
+ disabled: !hasSelectedTable,
15
+ icon: 'select_check_box',
16
+ label: 'Selected table',
17
+ dataAttributes: {
18
+ tableName: selectedTableName,
19
+ typeScope: 'selected',
20
+ },
21
+ },
22
+ {
23
+ action: 'open-generate-types-modal',
24
+ disabled: !hasTables,
25
+ icon: 'library_books',
26
+ label: 'All tables',
27
+ dataAttributes: {
28
+ typeScope: 'all',
29
+ },
30
+ },
31
+ ],
32
+ });
33
+ }
@@ -10,7 +10,7 @@ export function renderMetricCard({
10
10
  return `
11
11
  <div class="metric-card ${accent ? "metric-card--accent" : ""}">
12
12
  <span class="text-[10px] font-mono text-on-surface/40 uppercase">${escapeHtml(label)}</span>
13
- <span class="text-3xl font-headline font-bold text-on-surface">${escapeHtml(value)}</span>
13
+ <span class="text-3xl font-body font-bold text-on-surface">${escapeHtml(value)}</span>
14
14
  ${
15
15
  progress
16
16
  ? `