sqlite-hub 0.6.0 → 0.7.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.
- package/README.md +107 -0
- package/bin/sqlite-hub.js +285 -4
- package/changelog.md +7 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +34 -0
- package/frontend/js/app.js +99 -2
- package/frontend/js/components/modal.js +314 -1
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +35 -6
- package/frontend/js/components/queryHistoryPanel.js +16 -5
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/components/tableDesignerSidebar.js +41 -38
- package/frontend/js/lib/queryChartOptions.js +283 -0
- package/frontend/js/lib/queryCharts.js +560 -0
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +618 -0
- package/frontend/js/views/charts.js +471 -0
- package/frontend/js/views/data.js +29 -15
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/structure.js +81 -39
- package/frontend/styles/components.css +5 -13
- package/frontend/styles/views.css +296 -66
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/server.js +6 -0
- package/server/services/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { renderQueryResultsPane } from '../components/queryResults.js';
|
|
2
|
+
import { analyzeQueryChartResult, getQueryChartTypeLabel, validateQueryChartConfig } from '../lib/queryCharts.js';
|
|
3
|
+
import { escapeHtml, highlightSql } from '../utils/format.js';
|
|
4
|
+
import { renderStatusBadge } from '../components/badges.js';
|
|
5
|
+
|
|
6
|
+
function renderMissingDatabase() {
|
|
7
|
+
return `
|
|
8
|
+
<section class="flex flex-1 items-center justify-center px-8 text-center">
|
|
9
|
+
<div class="max-w-xl">
|
|
10
|
+
<span class="material-symbols-outlined mb-4 text-5xl text-on-surface-variant/20">database_off</span>
|
|
11
|
+
<h1 class="font-headline text-3xl font-black uppercase tracking-tight text-primary-container">
|
|
12
|
+
No Active SQLite Database
|
|
13
|
+
</h1>
|
|
14
|
+
<p class="mt-3 text-sm leading-7 text-on-surface-variant/65">
|
|
15
|
+
Open a local SQLite database first. The Charts area only works against query-history entries from the active database.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderChartsList(state) {
|
|
23
|
+
const queries = state.charts.queries ?? [];
|
|
24
|
+
const selectedHistoryId = Number(state.charts.selectedHistoryId);
|
|
25
|
+
|
|
26
|
+
if (state.charts.loading && !queries.length) {
|
|
27
|
+
return `
|
|
28
|
+
<div class="flex h-full items-center justify-center px-6 text-center text-on-surface-variant/45">
|
|
29
|
+
<div>
|
|
30
|
+
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
31
|
+
<p class="font-mono text-[10px] uppercase tracking-[0.18em]">Loading Queries</p>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (state.charts.error && !queries.length) {
|
|
38
|
+
return `
|
|
39
|
+
<div class="p-5">
|
|
40
|
+
<div class="border border-error/30 bg-error-container/20 px-4 py-4 text-sm text-error">
|
|
41
|
+
${escapeHtml(state.charts.error.message)}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!queries.length) {
|
|
48
|
+
return `
|
|
49
|
+
<div class="flex h-full items-center justify-center px-6 text-center">
|
|
50
|
+
<div>
|
|
51
|
+
<span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">query_stats</span>
|
|
52
|
+
<p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
|
|
53
|
+
No Query History
|
|
54
|
+
</p>
|
|
55
|
+
<p class="mt-2 max-w-xs text-sm leading-6 text-on-surface-variant/60">
|
|
56
|
+
Run saved queries in the SQL Editor first. They will appear here automatically.
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return `
|
|
64
|
+
<div class="custom-scrollbar flex-1 overflow-auto px-4 py-4">
|
|
65
|
+
<div class="space-y-2">
|
|
66
|
+
${queries
|
|
67
|
+
.map(
|
|
68
|
+
item => `
|
|
69
|
+
<button
|
|
70
|
+
class="w-full border px-4 py-3 text-left transition-colors ${
|
|
71
|
+
selectedHistoryId === item.id
|
|
72
|
+
? 'border-primary-container/30 bg-surface-container-high'
|
|
73
|
+
: 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
|
|
74
|
+
}"
|
|
75
|
+
data-action="navigate"
|
|
76
|
+
data-to="/charts/${encodeURIComponent(item.id)}"
|
|
77
|
+
type="button"
|
|
78
|
+
>
|
|
79
|
+
<div class="flex items-start justify-between gap-3">
|
|
80
|
+
<div class="min-w-0 flex-1 truncate font-mono text-xs ${
|
|
81
|
+
selectedHistoryId === item.id ? 'text-primary-container' : 'text-on-surface'
|
|
82
|
+
}">
|
|
83
|
+
${escapeHtml(item.displayTitle)}
|
|
84
|
+
</div>
|
|
85
|
+
<div class="flex shrink-0 flex-wrap justify-end gap-1">
|
|
86
|
+
${
|
|
87
|
+
item.chartTypes?.length
|
|
88
|
+
? item.chartTypes
|
|
89
|
+
.map(chartType => renderStatusBadge(getQueryChartTypeLabel(chartType), 'primary'))
|
|
90
|
+
.join('')
|
|
91
|
+
: renderStatusBadge('None', 'muted')
|
|
92
|
+
}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
96
|
+
${escapeHtml(item.previewSql)}
|
|
97
|
+
</div>
|
|
98
|
+
</button>
|
|
99
|
+
`,
|
|
100
|
+
)
|
|
101
|
+
.join('')}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderEmptyChartDetail() {
|
|
108
|
+
return `
|
|
109
|
+
<div class="flex flex-1 items-center justify-center px-8 py-10 text-center">
|
|
110
|
+
<div class="max-w-lg">
|
|
111
|
+
<span class="material-symbols-outlined mb-4 text-5xl text-on-surface-variant/20">bar_chart</span>
|
|
112
|
+
<h2 class="font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
|
|
113
|
+
Select A Query
|
|
114
|
+
</h2>
|
|
115
|
+
<p class="mt-3 text-sm leading-7 text-on-surface-variant/65">
|
|
116
|
+
Choose a query-history entry on the left to load its charts, render the live result set, and manage chart definitions.
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderQueryResultState(state, result) {
|
|
124
|
+
if (state.charts.resultLoading && !result) {
|
|
125
|
+
return `
|
|
126
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-4 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
127
|
+
Loading live query result...
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (state.charts.resultError) {
|
|
133
|
+
return `
|
|
134
|
+
<div class="border border-error/30 bg-error-container/15 px-4 py-4 text-sm text-error">
|
|
135
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em]">
|
|
136
|
+
${escapeHtml(state.charts.resultError.code ?? 'RESULT_ERROR')}
|
|
137
|
+
</div>
|
|
138
|
+
<div class="mt-2">${escapeHtml(state.charts.resultError.message)}</div>
|
|
139
|
+
</div>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!result) {
|
|
144
|
+
return `
|
|
145
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-4 text-sm text-on-surface-variant/60">
|
|
146
|
+
No live result set is available for this query.
|
|
147
|
+
</div>
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderQuerySqlSection(state, rawSql) {
|
|
155
|
+
const isExpanded = Boolean(state.charts.sqlExpanded);
|
|
156
|
+
|
|
157
|
+
return `
|
|
158
|
+
<section class="mt-5 border border-outline-variant/10 bg-surface-container-lowest">
|
|
159
|
+
<button
|
|
160
|
+
class="flex w-full items-center justify-between gap-3 border-b border-outline-variant/10 px-4 py-3 text-left"
|
|
161
|
+
data-action="toggle-charts-sql-panel"
|
|
162
|
+
type="button"
|
|
163
|
+
>
|
|
164
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
165
|
+
Query SQL
|
|
166
|
+
</span>
|
|
167
|
+
<span class="material-symbols-outlined text-on-surface-variant/55">
|
|
168
|
+
${isExpanded ? 'expand_less' : 'expand_more'}
|
|
169
|
+
</span>
|
|
170
|
+
</button>
|
|
171
|
+
${
|
|
172
|
+
isExpanded
|
|
173
|
+
? `
|
|
174
|
+
<pre class="custom-scrollbar overflow-auto p-4 font-mono text-sm leading-6 text-on-surface"><code>${highlightSql(
|
|
175
|
+
rawSql,
|
|
176
|
+
)}</code></pre>
|
|
177
|
+
`
|
|
178
|
+
: ''
|
|
179
|
+
}
|
|
180
|
+
</section>
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function renderQueryResultsSection(state) {
|
|
185
|
+
const isVisible = Boolean(state.charts.resultsVisible);
|
|
186
|
+
const statusMarkup = renderQueryResultState(state, state.charts.result);
|
|
187
|
+
|
|
188
|
+
return `
|
|
189
|
+
<section class="mt-6 border border-outline-variant/10 bg-surface-container-lowest">
|
|
190
|
+
<div class="flex items-center justify-between gap-3 border-b border-outline-variant/10 px-4 py-3">
|
|
191
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
192
|
+
Results
|
|
193
|
+
</span>
|
|
194
|
+
<button
|
|
195
|
+
class="toolbar-button query-chart-card__action"
|
|
196
|
+
data-action="toggle-charts-results-panel"
|
|
197
|
+
type="button"
|
|
198
|
+
>
|
|
199
|
+
${isVisible ? 'Hide Results' : 'Show Results'}
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
${statusMarkup}
|
|
203
|
+
${
|
|
204
|
+
isVisible && state.charts.result && !state.charts.resultLoading && !state.charts.resultError
|
|
205
|
+
? `
|
|
206
|
+
<div class="h-[18rem] overflow-hidden">
|
|
207
|
+
${renderQueryResultsPane(state.charts.result, {
|
|
208
|
+
selectedRowIndex: null,
|
|
209
|
+
editable: false,
|
|
210
|
+
sortColumn: null,
|
|
211
|
+
sortDirection: null,
|
|
212
|
+
})}
|
|
213
|
+
</div>
|
|
214
|
+
`
|
|
215
|
+
: ''
|
|
216
|
+
}
|
|
217
|
+
</section>
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resolveChartCardSizeClass(state) {
|
|
222
|
+
return `query-chart-card--${String(state.charts.chartHeightPreset ?? 'medium')
|
|
223
|
+
.trim()
|
|
224
|
+
.toLowerCase()}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function renderChartHeightPresetToggle(state) {
|
|
228
|
+
const activePreset = String(state.charts.chartHeightPreset ?? 'medium')
|
|
229
|
+
.trim()
|
|
230
|
+
.toLowerCase();
|
|
231
|
+
const presets = [
|
|
232
|
+
{ value: 'small', label: 'Small', height: '300px' },
|
|
233
|
+
{ value: 'medium', label: 'Medium', height: '450px' },
|
|
234
|
+
{ value: 'large', label: 'Large', height: '600px' },
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
return `
|
|
238
|
+
<div class="flex flex-col gap-2">
|
|
239
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
240
|
+
Chart Height
|
|
241
|
+
</div>
|
|
242
|
+
<div class="charts-height-toggle" role="group" aria-label="Chart height preset">
|
|
243
|
+
${presets
|
|
244
|
+
.map(
|
|
245
|
+
preset => `
|
|
246
|
+
<button
|
|
247
|
+
class="toolbar-button charts-height-toggle__button ${activePreset === preset.value ? 'is-active' : ''}"
|
|
248
|
+
data-action="set-charts-height-preset"
|
|
249
|
+
data-preset="${preset.value}"
|
|
250
|
+
type="button"
|
|
251
|
+
title="${preset.height}"
|
|
252
|
+
>
|
|
253
|
+
${preset.label}
|
|
254
|
+
</button>
|
|
255
|
+
`,
|
|
256
|
+
)
|
|
257
|
+
.join('')}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function renderChartSurface(chart, state, analysis) {
|
|
264
|
+
if (state.charts.resultLoading) {
|
|
265
|
+
return `
|
|
266
|
+
<div class="query-chart-surface-state flex h-full items-center justify-center text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
267
|
+
Loading chart result...
|
|
268
|
+
</div>
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (state.charts.resultError) {
|
|
273
|
+
return `
|
|
274
|
+
<div class="query-chart-surface-state flex items-center justify-center border border-error/20 bg-error-container/10 px-6 text-center text-sm text-error">
|
|
275
|
+
${escapeHtml(state.charts.resultError.message)}
|
|
276
|
+
</div>
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const validation = validateQueryChartConfig(chart.chartType, chart.config, analysis);
|
|
281
|
+
|
|
282
|
+
if (!validation.valid) {
|
|
283
|
+
return `
|
|
284
|
+
<div class="query-chart-surface-state flex items-center justify-center border border-error/20 bg-error-container/10 px-6 text-center text-sm text-error">
|
|
285
|
+
${escapeHtml(validation.errors.join(' '))}
|
|
286
|
+
</div>
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return `
|
|
291
|
+
<div
|
|
292
|
+
class="query-chart-canvas w-full"
|
|
293
|
+
data-query-chart-id="${escapeHtml(chart.id)}"
|
|
294
|
+
data-chart-export-name="${escapeHtml(chart.name.replace(/[^\w.-]+/g, '_'))}"
|
|
295
|
+
></div>
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function renderChartCard(chart, state, analysis) {
|
|
300
|
+
const sizeClass = resolveChartCardSizeClass(state);
|
|
301
|
+
|
|
302
|
+
return `
|
|
303
|
+
<article class="query-chart-card ${sizeClass}">
|
|
304
|
+
<header class="query-chart-card__header">
|
|
305
|
+
<div class="min-w-0">
|
|
306
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
307
|
+
<h3 class="truncate font-headline text-xl font-black uppercase tracking-tight text-on-surface">
|
|
308
|
+
${escapeHtml(chart.name)}
|
|
309
|
+
</h3>
|
|
310
|
+
${renderStatusBadge(getQueryChartTypeLabel(chart.chartType), 'primary')}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
314
|
+
<button
|
|
315
|
+
class="toolbar-button query-chart-card__action"
|
|
316
|
+
data-action="open-edit-query-chart-modal"
|
|
317
|
+
data-chart-id="${escapeHtml(chart.id)}"
|
|
318
|
+
type="button"
|
|
319
|
+
>
|
|
320
|
+
Edit
|
|
321
|
+
</button>
|
|
322
|
+
<button
|
|
323
|
+
class="toolbar-button query-chart-card__action"
|
|
324
|
+
data-action="open-delete-query-chart-modal"
|
|
325
|
+
data-chart-id="${escapeHtml(chart.id)}"
|
|
326
|
+
type="button"
|
|
327
|
+
>
|
|
328
|
+
Delete
|
|
329
|
+
</button>
|
|
330
|
+
<button
|
|
331
|
+
class="toolbar-button query-chart-card__action"
|
|
332
|
+
data-action="export-query-chart-png"
|
|
333
|
+
data-chart-id="${escapeHtml(chart.id)}"
|
|
334
|
+
type="button"
|
|
335
|
+
>
|
|
336
|
+
Export PNG
|
|
337
|
+
</button>
|
|
338
|
+
</div>
|
|
339
|
+
</header>
|
|
340
|
+
<div class="query-chart-card__body">
|
|
341
|
+
${renderChartSurface(chart, state, analysis)}
|
|
342
|
+
</div>
|
|
343
|
+
</article>
|
|
344
|
+
`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function renderChartsDetail(state) {
|
|
348
|
+
const detail = state.charts.detail;
|
|
349
|
+
const selectedHistoryId = state.charts.selectedHistoryId;
|
|
350
|
+
|
|
351
|
+
if (!selectedHistoryId) {
|
|
352
|
+
return renderEmptyChartDetail();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (state.charts.detailLoading && !detail) {
|
|
356
|
+
return `
|
|
357
|
+
<div class="flex flex-1 items-center justify-center px-8 text-center text-on-surface-variant/45">
|
|
358
|
+
<div>
|
|
359
|
+
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
360
|
+
<p class="font-mono text-[10px] uppercase tracking-[0.18em]">Loading Query Detail</p>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (state.charts.detailError && !detail) {
|
|
367
|
+
return `
|
|
368
|
+
<div class="p-8">
|
|
369
|
+
<div class="border border-error/30 bg-error-container/20 px-4 py-4 text-sm text-error">
|
|
370
|
+
${escapeHtml(state.charts.detailError.message)}
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!detail?.item) {
|
|
377
|
+
return renderEmptyChartDetail();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const analysis = state.charts.result ? analyzeQueryChartResult(state.charts.result) : null;
|
|
381
|
+
const charts = detail.charts ?? [];
|
|
382
|
+
|
|
383
|
+
return `
|
|
384
|
+
<div class="custom-scrollbar flex-1 overflow-auto">
|
|
385
|
+
<div class="charts-detail-shell">
|
|
386
|
+
<header class="charts-detail-shell__header">
|
|
387
|
+
<div class="min-w-0">
|
|
388
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">
|
|
389
|
+
Charts
|
|
390
|
+
</div>
|
|
391
|
+
<h1 class="mt-2 truncate font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
|
|
392
|
+
${escapeHtml(detail.item.displayTitle)}
|
|
393
|
+
</h1>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="charts-detail-shell__controls">
|
|
396
|
+
${renderChartHeightPresetToggle(state)}
|
|
397
|
+
<button
|
|
398
|
+
class="toolbar-button charts-detail-shell__button"
|
|
399
|
+
data-action="open-query-history"
|
|
400
|
+
data-history-id="${escapeHtml(detail.item.id)}"
|
|
401
|
+
type="button"
|
|
402
|
+
>
|
|
403
|
+
Open In Editor
|
|
404
|
+
</button>
|
|
405
|
+
<button
|
|
406
|
+
class="toolbar-button charts-detail-shell__button charts-detail-shell__button--primary"
|
|
407
|
+
data-action="open-create-query-chart-modal"
|
|
408
|
+
type="button"
|
|
409
|
+
${state.charts.resultError || !state.charts.result ? 'disabled' : ''}
|
|
410
|
+
>
|
|
411
|
+
New Chart
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
</header>
|
|
415
|
+
|
|
416
|
+
${renderQuerySqlSection(state, detail.item.rawSql)}
|
|
417
|
+
|
|
418
|
+
<section class="mt-6 space-y-5">
|
|
419
|
+
${
|
|
420
|
+
charts.length
|
|
421
|
+
? charts.map(chart => renderChartCard(chart, state, analysis)).join('')
|
|
422
|
+
: `
|
|
423
|
+
<div class="flex min-h-[240px] items-center justify-center border border-dashed border-outline-variant/20 bg-surface-container-low px-8 text-center">
|
|
424
|
+
<div>
|
|
425
|
+
<span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">add_chart</span>
|
|
426
|
+
<p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
|
|
427
|
+
No Charts Yet
|
|
428
|
+
</p>
|
|
429
|
+
<p class="mt-2 max-w-md text-sm leading-6 text-on-surface-variant/60">
|
|
430
|
+
Create the first chart for this query. The query stays read-only here; only chart definitions are editable.
|
|
431
|
+
</p>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
`
|
|
435
|
+
}
|
|
436
|
+
</section>
|
|
437
|
+
|
|
438
|
+
${renderQueryResultsSection(state)}
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function renderChartsView(state) {
|
|
445
|
+
if (!state.connections.active && state.charts.error?.code === 'ACTIVE_DATABASE_REQUIRED') {
|
|
446
|
+
return {
|
|
447
|
+
main: `<section class="view-surface flex min-h-full">${renderMissingDatabase()}</section>`,
|
|
448
|
+
panel: '',
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
main: `
|
|
454
|
+
<section class="charts-view">
|
|
455
|
+
<aside class="charts-view__sidebar">
|
|
456
|
+
<div class="charts-view__sidebar-header">
|
|
457
|
+
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
458
|
+
Query History
|
|
459
|
+
</div>
|
|
460
|
+
<h2 class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
461
|
+
Charts
|
|
462
|
+
</h2>
|
|
463
|
+
</div>
|
|
464
|
+
${renderChartsList(state)}
|
|
465
|
+
</aside>
|
|
466
|
+
<div class="charts-view__detail">${renderChartsDetail(state)}</div>
|
|
467
|
+
</section>
|
|
468
|
+
`,
|
|
469
|
+
panel: '',
|
|
470
|
+
};
|
|
471
|
+
}
|
|
@@ -67,6 +67,7 @@ function renderTableList(state) {
|
|
|
67
67
|
|
|
68
68
|
function renderWorkspaceHeader(state) {
|
|
69
69
|
const table = state.dataBrowser.table;
|
|
70
|
+
const tablesVisible = state.dataBrowser.tablesVisible !== false;
|
|
70
71
|
|
|
71
72
|
return `
|
|
72
73
|
<header class="border-b border-outline-variant/10 bg-surface-container px-6 py-5">
|
|
@@ -91,15 +92,20 @@ function renderWorkspaceHeader(state) {
|
|
|
91
92
|
<div class="flex items-center gap-3">
|
|
92
93
|
${
|
|
93
94
|
table
|
|
94
|
-
?
|
|
95
|
+
? `<button
|
|
96
|
+
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"
|
|
97
|
+
data-action="toggle-data-tables"
|
|
98
|
+
type="button"
|
|
99
|
+
>
|
|
100
|
+
${tablesVisible ? '<span class="material-symbols-outlined text-sm">visibility_off</span> Hide Tables' : 'Show Tables'}
|
|
101
|
+
</button>
|
|
95
102
|
<button
|
|
96
103
|
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"
|
|
97
104
|
data-action="export-data-csv"
|
|
98
105
|
type="button"
|
|
99
106
|
>
|
|
100
107
|
${state.dataBrowser.exportLoading ? 'Exporting...' : 'Export CSV'}
|
|
101
|
-
</button
|
|
102
|
-
`
|
|
108
|
+
</button>`
|
|
103
109
|
: ''
|
|
104
110
|
}
|
|
105
111
|
<button
|
|
@@ -505,21 +511,29 @@ function renderDataRowEditorPanel(state) {
|
|
|
505
511
|
}
|
|
506
512
|
|
|
507
513
|
export function renderDataView(state) {
|
|
514
|
+
const tablesVisible = state.dataBrowser.tablesVisible !== false;
|
|
515
|
+
|
|
508
516
|
return {
|
|
509
517
|
main: `
|
|
510
518
|
<section class="view-surface min-h-full bg-surface-container flex h-full min-h-0 flex-col overflow-hidden">
|
|
511
|
-
<div class="grid h-full min-h-0 grid-cols-1 md:grid-cols-[18rem_minmax(0,1fr)]">
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
519
|
+
<div class="grid h-full min-h-0 grid-cols-1 ${tablesVisible ? 'md:grid-cols-[18rem_minmax(0,1fr)]' : ''}">
|
|
520
|
+
${
|
|
521
|
+
tablesVisible
|
|
522
|
+
? `
|
|
523
|
+
<aside class="flex min-h-0 flex-col border-r border-outline-variant/10 bg-surface-low">
|
|
524
|
+
<div class="border-b border-outline-variant/10 px-6 py-5">
|
|
525
|
+
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
526
|
+
Tables
|
|
527
|
+
</div>
|
|
528
|
+
<div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
529
|
+
total ${escapeHtml(formatNumber(state.dataBrowser.tables.length))}
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
${renderTableList(state)}
|
|
533
|
+
</aside>
|
|
534
|
+
`
|
|
535
|
+
: ''
|
|
536
|
+
}
|
|
523
537
|
<section class="flex min-h-0 flex-col overflow-hidden">
|
|
524
538
|
${renderWorkspaceHeader(state)}
|
|
525
539
|
${renderWorkspaceError(state)}
|
|
@@ -236,7 +236,7 @@ function renderResultsSurface(state, isResultsRoute) {
|
|
|
236
236
|
|
|
237
237
|
export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
238
238
|
const connection = getCurrentConnection(state);
|
|
239
|
-
const editorSectionClass = "min-h-[27.5%]";
|
|
239
|
+
const editorSectionClass = state.editor.editorPanelVisible ? "min-h-[27.5%]" : "flex-none";
|
|
240
240
|
const resultsSectionClass = "flex-1";
|
|
241
241
|
|
|
242
242
|
return {
|
|
@@ -252,6 +252,8 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
|
252
252
|
exporting: state.editor.exportLoading,
|
|
253
253
|
historyLoading: state.editor.historyLoading,
|
|
254
254
|
historyTotal: state.editor.historyTotal,
|
|
255
|
+
editorVisible: state.editor.editorPanelVisible,
|
|
256
|
+
historyVisible: state.editor.historyPanelVisible,
|
|
255
257
|
title: connection?.label ?? "SQLite Query Workspace",
|
|
256
258
|
})}
|
|
257
259
|
</section>
|
|
@@ -259,18 +261,22 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
|
259
261
|
${renderResultsSurface(state, isResultsRoute)}
|
|
260
262
|
</section>
|
|
261
263
|
</div>
|
|
262
|
-
${
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
264
|
+
${
|
|
265
|
+
state.editor.historyPanelVisible
|
|
266
|
+
? renderQueryHistoryPanel({
|
|
267
|
+
items: state.editor.history,
|
|
268
|
+
loading: state.editor.historyLoading,
|
|
269
|
+
loadingMore: state.editor.historyLoadingMore,
|
|
270
|
+
error: state.editor.historyError,
|
|
271
|
+
activeTab: state.editor.historyTab,
|
|
272
|
+
search: state.editor.historySearchInput,
|
|
273
|
+
total: state.editor.historyTotal,
|
|
274
|
+
hasMore: state.editor.historyHasMore,
|
|
275
|
+
activeHistoryId: state.editor.historyActiveId,
|
|
276
|
+
selectedHistoryId: state.editor.historySelectedId,
|
|
277
|
+
})
|
|
278
|
+
: ""
|
|
279
|
+
}
|
|
274
280
|
</section>
|
|
275
281
|
`,
|
|
276
282
|
panel:
|