sqlite-hub 0.6.0 → 0.8.7

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 (44) hide show
  1. package/README.md +107 -0
  2. package/bin/sqlite-hub.js +285 -4
  3. package/changelog.md +21 -0
  4. package/docs/DESIGN_GUIDELINES.md +36 -0
  5. package/frontend/assets/mockups/sql_editor_croped.png +0 -0
  6. package/frontend/index.html +80 -0
  7. package/frontend/js/api.js +34 -0
  8. package/frontend/js/app.js +188 -10
  9. package/frontend/js/components/connectionCard.js +3 -4
  10. package/frontend/js/components/emptyState.js +4 -4
  11. package/frontend/js/components/modal.js +341 -24
  12. package/frontend/js/components/queryChartRenderer.js +125 -0
  13. package/frontend/js/components/queryEditor.js +33 -6
  14. package/frontend/js/components/queryHistoryDetail.js +16 -7
  15. package/frontend/js/components/queryHistoryPanel.js +18 -7
  16. package/frontend/js/components/rowEditorPanel.js +59 -54
  17. package/frontend/js/components/sidebar.js +32 -32
  18. package/frontend/js/components/structureGraph.js +54 -122
  19. package/frontend/js/components/tableDesignerEditor.js +9 -8
  20. package/frontend/js/components/tableDesignerSidebar.js +52 -48
  21. package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
  22. package/frontend/js/lib/queryChartOptions.js +283 -0
  23. package/frontend/js/lib/queryCharts.js +560 -0
  24. package/frontend/js/router.js +8 -0
  25. package/frontend/js/store.js +641 -0
  26. package/frontend/js/views/charts.js +499 -0
  27. package/frontend/js/views/connections.js +5 -17
  28. package/frontend/js/views/data.js +40 -27
  29. package/frontend/js/views/editor.js +19 -13
  30. package/frontend/js/views/overview.js +149 -10
  31. package/frontend/js/views/settings.js +2 -2
  32. package/frontend/js/views/structure.js +105 -59
  33. package/frontend/js/views/tableDesigner.js +7 -2
  34. package/frontend/styles/base.css +62 -0
  35. package/frontend/styles/components.css +68 -118
  36. package/frontend/styles/structure-graph.css +19 -82
  37. package/frontend/styles/tokens.css +2 -0
  38. package/frontend/styles/views.css +293 -66
  39. package/package.json +2 -1
  40. package/server/routes/charts.js +153 -0
  41. package/server/server.js +6 -0
  42. package/server/services/sqlite/overviewService.js +68 -0
  43. package/server/services/storage/appStateStore.js +400 -1
  44. package/server/services/storage/queryHistoryChartUtils.js +145 -0
@@ -52,7 +52,10 @@ export function renderQueryEditor({
52
52
  exporting = false,
53
53
  historyLoading = false,
54
54
  historyTotal = 0,
55
+ editorVisible = true,
56
+ historyVisible = true,
55
57
  }) {
58
+ const secondaryButtonClass = "standard-button";
56
59
  const left = `
57
60
  <div class="flex items-center gap-2 bg-surface-container-lowest px-3 py-1">
58
61
  <span class="material-symbols-outlined text-xs text-[#FCE300]">database</span>
@@ -68,21 +71,39 @@ export function renderQueryEditor({
68
71
 
69
72
  const right = `
70
73
  <button
71
- class="px-4 py-1.5 text-[10px] font-bold uppercase tracking-widest text-on-surface hover:bg-surface-container-highest transition-colors"
74
+ class="${secondaryButtonClass}"
75
+ data-action="toggle-editor-panel"
76
+ data-next-value="${editorVisible ? "false" : "true"}"
77
+ type="button"
78
+ >
79
+ <span class="material-symbols-outlined text-sm">${editorVisible ? "keyboard_arrow_down" : "terminal"}</span>
80
+ ${editorVisible ? "Hide Editor" : "Show Editor"}
81
+ </button>
82
+ <button
83
+ class="${secondaryButtonClass}"
84
+ data-action="toggle-query-history-panel"
85
+ data-next-value="${historyVisible ? "false" : "true"}"
86
+ type="button"
87
+ >
88
+ <span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "history"}</span>
89
+ ${historyVisible ? "Hide History" : "Show History"}
90
+ </button>
91
+ <button
92
+ class="${secondaryButtonClass}"
72
93
  data-action="clear-query"
73
94
  type="button"
74
95
  >
75
96
  Clear
76
97
  </button>
77
98
  <button
78
- class="px-4 py-1.5 text-[10px] font-bold uppercase tracking-widest text-on-surface hover:bg-surface-container-highest transition-colors"
99
+ class="${secondaryButtonClass}"
79
100
  data-action="export-query-csv"
80
101
  type="button"
81
102
  >
82
103
  ${exporting ? "Exporting..." : "Export CSV"}
83
104
  </button>
84
105
  <button
85
- class="bg-primary-container px-6 py-1.5 text-xs font-black uppercase tracking-tighter text-on-primary shadow-[0_0_15px_-5px_rgba(252,227,0,0.4)] transition-all hover:brightness-110"
106
+ class="signature-button"
86
107
  data-action="execute-query"
87
108
  type="button"
88
109
  >
@@ -99,9 +120,15 @@ export function renderQueryEditor({
99
120
  className: "flex-wrap",
100
121
  })}
101
122
  </div>
102
- <div class="flex min-h-0 flex-1 flex-col">
103
- ${renderEditorSurface({ query })}
104
- </div>
123
+ ${
124
+ editorVisible
125
+ ? `
126
+ <div class="flex min-h-0 flex-1 flex-col">
127
+ ${renderEditorSurface({ query })}
128
+ </div>
129
+ `
130
+ : ""
131
+ }
105
132
  </div>
106
133
  `;
107
134
  }
@@ -155,14 +155,14 @@ export function renderQueryHistoryDetail({
155
155
  </label>
156
156
  <div class="mt-2 flex gap-2">
157
157
  <input
158
- class="flex-1 border border-outline-variant/20 bg-surface-container px-3 py-2 text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
158
+ class="control-input flex-1 border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
159
159
  name="title"
160
160
  placeholder="${escapeHtml(item.displayTitle)}"
161
161
  type="text"
162
162
  value="${escapeHtml(item.title ?? "")}"
163
163
  />
164
164
  <button
165
- class="border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
165
+ class="standard-button"
166
166
  type="submit"
167
167
  >
168
168
  Save
@@ -175,7 +175,7 @@ export function renderQueryHistoryDetail({
175
175
 
176
176
  <div class="mt-5 flex flex-wrap gap-2">
177
177
  <button
178
- class="toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
178
+ class="standard-button"
179
179
  data-action="open-query-history"
180
180
  data-history-id="${escapeHtml(item.id)}"
181
181
  type="button"
@@ -183,7 +183,16 @@ export function renderQueryHistoryDetail({
183
183
  Open In Editor
184
184
  </button>
185
185
  <button
186
- class="toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
186
+ class="standard-button"
187
+ data-action="navigate"
188
+ data-to="/charts/${encodeURIComponent(item.id)}"
189
+ type="button"
190
+ >
191
+ <span class="material-symbols-outlined text-sm">bar_chart</span>
192
+ Open In Charts
193
+ </button>
194
+ <button
195
+ class="standard-button"
187
196
  data-action="run-query-history"
188
197
  data-history-id="${escapeHtml(item.id)}"
189
198
  type="button"
@@ -191,7 +200,7 @@ export function renderQueryHistoryDetail({
191
200
  Run Now
192
201
  </button>
193
202
  <button
194
- class="toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
203
+ class="standard-button"
195
204
  data-action="toggle-query-history-saved"
196
205
  data-history-id="${escapeHtml(item.id)}"
197
206
  data-next-value="${item.isSaved ? "false" : "true"}"
@@ -200,7 +209,7 @@ export function renderQueryHistoryDetail({
200
209
  ${item.isSaved ? "Unsave" : "Save"}
201
210
  </button>
202
211
  <button
203
- class="toolbar-button border border-error/25 bg-error-container/20 px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-error transition-colors hover:border-error hover:bg-error-container/30"
212
+ class="delete-button"
204
213
  data-action="delete-query-history"
205
214
  data-history-id="${escapeHtml(item.id)}"
206
215
  type="button"
@@ -228,7 +237,7 @@ export function renderQueryHistoryDetail({
228
237
  )}</textarea>
229
238
  <div class="mt-2 flex justify-end">
230
239
  <button
231
- class="border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
240
+ class="standard-button"
232
241
  type="submit"
233
242
  >
234
243
  Save Notes
@@ -150,17 +150,28 @@ export function renderQueryHistoryPanel({
150
150
  return `
151
151
  <aside class="query-history-panel border-l border-outline-variant/10 bg-surface-container-lowest">
152
152
  <div class="border-b border-outline-variant/10 px-4 py-4">
153
- <div class="flex items-center gap-2">
154
- <span class="material-symbols-outlined text-[18px] text-primary-container">history</span>
155
- <span class="font-headline text-xs font-black uppercase tracking-[0.18em] text-primary-container">
156
- Query History
157
- </span>
153
+ <div class="flex items-center justify-between gap-3">
154
+ <div class="flex items-center gap-2">
155
+ <span class="material-symbols-outlined text-[18px] text-primary-container">history</span>
156
+ <span class="font-headline text-xs font-black uppercase tracking-[0.18em] text-primary-container">
157
+ Query History
158
+ </span>
159
+ </div>
160
+ <button
161
+ class="query-history-icon-button"
162
+ data-action="toggle-query-history-panel"
163
+ data-next-value="false"
164
+ title="Hide query history"
165
+ type="button"
166
+ >
167
+ <span class="material-symbols-outlined text-[18px]">close</span>
168
+ </button>
158
169
  </div>
159
170
  <div class="mt-4">${renderQueryHistoryTabs(activeTab, total)}</div>
160
171
  <label class="mt-4 block">
161
172
  <span class="sr-only">Search query history</span>
162
173
  <input
163
- class="w-full border border-outline-variant/20 bg-surface-container px-3 py-2 text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
174
+ class="control-input w-full border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
164
175
  data-bind="query-history-search"
165
176
  placeholder="Search SQL, titles, notes..."
166
177
  type="search"
@@ -212,7 +223,7 @@ export function renderQueryHistoryPanel({
212
223
  ? `
213
224
  <div class="mt-4 flex justify-center">
214
225
  <button
215
- class="border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant transition-colors hover:border-primary-container hover:text-primary-container"
226
+ class="standard-button"
216
227
  data-action="load-more-query-history"
217
228
  type="button"
218
229
  >
@@ -106,12 +106,52 @@ export function renderRowEditorPanel({
106
106
  const canSubmit = !disabledMessage && editableFields.length > 0;
107
107
  const canDelete = !disabledMessage && deleteEnabled;
108
108
  const formId = `${formName}-panel-form`;
109
+ const headerActions = [
110
+ reloadAction
111
+ ? `
112
+ <button
113
+ class="standard-button"
114
+ data-action="${escapeHtml(reloadAction)}"
115
+ type="button"
116
+ >
117
+ Reload
118
+ </button>
119
+ `
120
+ : "",
121
+ canSubmit
122
+ ? `
123
+ <button
124
+ class="standard-button"
125
+ form="${escapeHtml(formId)}"
126
+ type="submit"
127
+ ${saving || deleting ? "disabled" : ""}
128
+ >
129
+ ${escapeHtml(saving ? "Saving..." : submitLabel)}
130
+ </button>
131
+ `
132
+ : "",
133
+ canDelete
134
+ ? `
135
+ <button
136
+ class="delete-button"
137
+ data-action="${escapeHtml(deleteAction)}"
138
+ data-row-index="${escapeHtml(String(deleteRowIndex ?? ""))}"
139
+ type="button"
140
+ ${saving || deleting ? "disabled" : ""}
141
+ >
142
+ ${escapeHtml(deleting ? "Deleting..." : deleteLabel)}
143
+ </button>
144
+ `
145
+ : "",
146
+ ]
147
+ .filter(Boolean)
148
+ .join("");
109
149
 
110
150
  return `
111
151
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
112
152
  <header class="border-b border-outline-variant/10 bg-surface-container px-6 py-5">
113
- <div class="space-y-4">
114
- <div>
153
+ <div class="flex items-start justify-between gap-4">
154
+ <div class="min-w-0 flex-1">
115
155
  <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
116
156
  ${escapeHtml(sectionLabel)}
117
157
  </div>
@@ -128,59 +168,24 @@ export function renderRowEditorPanel({
128
168
  : ""
129
169
  }
130
170
  </div>
131
- <div class="flex flex-wrap items-center justify-end gap-2">
132
- ${
133
- reloadAction
134
- ? `
135
- <button
136
- class="border border-outline-variant/20 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-on-surface hover:bg-surface-container-highest"
137
- data-action="${escapeHtml(reloadAction)}"
138
- type="button"
139
- >
140
- Reload
141
- </button>
142
- `
143
- : ""
144
- }
145
- ${
146
- canSubmit
147
- ? `
148
- <button
149
- class="bg-primary-container px-5 py-3 text-[10px] font-black uppercase tracking-[0.16em] text-on-primary disabled:cursor-default disabled:opacity-40"
150
- form="${escapeHtml(formId)}"
151
- type="submit"
152
- ${saving || deleting ? "disabled" : ""}
153
- >
154
- ${escapeHtml(saving ? "Saving..." : submitLabel)}
155
- </button>
156
- `
157
- : ""
158
- }
159
- ${
160
- canDelete
161
- ? `
162
- <button
163
- class="border border-error/25 bg-error-container/10 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-error transition-colors hover:bg-error-container/20 disabled:cursor-default disabled:opacity-40"
164
- data-action="${escapeHtml(deleteAction)}"
165
- data-row-index="${escapeHtml(String(deleteRowIndex ?? ""))}"
166
- type="button"
167
- ${saving || deleting ? "disabled" : ""}
168
- >
169
- ${escapeHtml(deleting ? "Deleting..." : deleteLabel)}
170
- </button>
171
- `
172
- : ""
173
- }
174
- <button
175
- aria-label="Close panel"
176
- class="flex h-11 w-11 items-center justify-center border border-outline-variant/20 text-on-surface hover:bg-surface-container-highest"
177
- data-action="${escapeHtml(closeAction)}"
178
- type="button"
179
- >
180
- <span class="material-symbols-outlined text-base">close</span>
181
- </button>
182
- </div>
171
+ <button
172
+ aria-label="Close panel"
173
+ class="query-history-icon-button shrink-0"
174
+ data-action="${escapeHtml(closeAction)}"
175
+ type="button"
176
+ >
177
+ <span class="material-symbols-outlined text-[18px]">close</span>
178
+ </button>
183
179
  </div>
180
+ ${
181
+ headerActions
182
+ ? `
183
+ <div class="mt-4 flex flex-wrap items-center justify-end gap-2">
184
+ ${headerActions}
185
+ </div>
186
+ `
187
+ : ""
188
+ }
184
189
  </header>
185
190
  <div class="custom-scrollbar flex-1 overflow-auto px-6 py-6">
186
191
  ${
@@ -1,60 +1,60 @@
1
- import { escapeHtml } from "../utils/format.js";
2
- import { renderConnectionLogo } from "./connectionLogo.js";
1
+ import { escapeHtml } from '../utils/format.js';
2
+ import { renderConnectionLogo } from './connectionLogo.js';
3
3
 
4
4
  const sidebarItems = [
5
- { label: "Connections", href: "#/connections", key: "connections", icon: "database" },
6
- { label: "Overview", href: "#/overview", key: "overview", icon: "dashboard" },
7
- { label: "Data", href: "#/data", key: "data", icon: "table_rows" },
8
- { label: "SQL Editor", href: "#/editor", key: "editor", icon: "terminal" },
9
- { label: "Structure", href: "#/structure", key: "structure", icon: "account_tree" },
10
- { label: "Table Designer", href: "#/table-designer", key: "tableDesigner", icon: "table_chart" },
11
- { label: "Settings", href: "#/settings", key: "settings", icon: "settings" },
5
+ { label: 'Connections', href: '#/connections', key: 'connections', icon: 'database' },
6
+ { label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
7
+ { label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
8
+ { label: 'SQL Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
9
+ { label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
10
+ { label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
11
+ { label: 'Table Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
12
+ { label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
12
13
  ];
13
14
 
14
15
  function getActiveSidebarKey(routeName) {
15
- if (routeName === "landing") {
16
- return "connections";
17
- }
16
+ if (routeName === 'landing') {
17
+ return 'connections';
18
+ }
18
19
 
19
- if (routeName === "editorResults") {
20
- return "editor";
21
- }
20
+ if (routeName === 'editorResults') {
21
+ return 'editor';
22
+ }
22
23
 
23
- return routeName;
24
+ return routeName;
24
25
  }
25
26
 
26
27
  export function renderSidebar(state) {
27
- const activeKey = getActiveSidebarKey(state.route.name);
28
- const activeConnection = state.connections.active;
28
+ const activeKey = getActiveSidebarKey(state.route.name);
29
+ const activeConnection = state.connections.active;
29
30
 
30
- return `
31
+ return `
31
32
  <nav class="sidebar-links">
32
33
  ${sidebarItems
33
- .map(
34
- (item) => `
35
- <a class="sidebar-link ${item.key === activeKey ? "is-active" : ""}" href="${item.href}">
34
+ .map(
35
+ item => `
36
+ <a class="sidebar-link ${item.key === activeKey ? 'is-active' : ''}" href="${item.href}">
36
37
  <span class="material-symbols-outlined">${item.icon}</span>
37
38
  <span>${item.label}</span>
38
39
  </a>
39
- `
40
- )
41
- .join("")}
40
+ `,
41
+ )
42
+ .join('')}
42
43
  </nav>
43
44
  <div class="sidebar-footer">
44
45
  <div class="sidebar-footer-card">
45
46
  ${renderConnectionLogo(activeConnection, {
46
- containerClass:
47
- "sidebar-footer-mark overflow-hidden bg-primary-container text-on-primary",
48
- imageClassName: "h-full w-full object-cover",
49
- iconClassName: "text-[15px]",
50
- icon: "memory",
47
+ containerClass: 'sidebar-footer-mark overflow-hidden bg-primary-container text-on-primary',
48
+ imageClassName: 'h-full w-full object-cover',
49
+ iconClassName: 'text-[15px]',
50
+ icon: 'memory',
51
51
  })}
52
52
  <div class="min-w-0">
53
53
  <p class="truncate text-[10px] font-bold text-on-surface">
54
- ${escapeHtml(activeConnection?.label ?? "NO_ACTIVE_DATABASE")}
54
+ ${escapeHtml(activeConnection?.label ?? 'NO_ACTIVE_DATABASE')}
55
55
  </p>
56
56
  <p class="text-[8px] text-on-surface-variant/60">
57
- ${activeConnection?.readOnly ? "READ_ONLY" : "READ_WRITE"}
57
+ ${activeConnection?.readOnly ? 'READ_ONLY' : 'READ_WRITE'}
58
58
  </p>
59
59
  </div>
60
60
  </div>