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.
- package/README.md +107 -0
- package/bin/sqlite-hub.js +285 -4
- package/changelog.md +21 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/index.html +80 -0
- package/frontend/js/api.js +34 -0
- package/frontend/js/app.js +188 -10
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +341 -24
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +33 -6
- package/frontend/js/components/queryHistoryDetail.js +16 -7
- package/frontend/js/components/queryHistoryPanel.js +18 -7
- package/frontend/js/components/rowEditorPanel.js +59 -54
- package/frontend/js/components/sidebar.js +32 -32
- package/frontend/js/components/structureGraph.js +54 -122
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +52 -48
- package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
- 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 +641 -0
- package/frontend/js/views/charts.js +499 -0
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +40 -27
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +105 -59
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +62 -0
- package/frontend/styles/components.css +68 -118
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +293 -66
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/server.js +6 -0
- package/server/services/sqlite/overviewService.js +68 -0
- package/server/services/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -0
|
@@ -0,0 +1,499 @@
|
|
|
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="standard-button flex w-full justify-between border-b border-outline-variant/10 px-4 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="standard-button"
|
|
196
|
+
data-action="toggle-charts-results-panel"
|
|
197
|
+
type="button"
|
|
198
|
+
>
|
|
199
|
+
<span class="material-symbols-outlined text-sm">
|
|
200
|
+
${isVisible ? 'table_rows_narrow' : 'table_rows'}
|
|
201
|
+
</span>
|
|
202
|
+
${isVisible ? 'Hide Results' : 'Show Results'}
|
|
203
|
+
</button>
|
|
204
|
+
</div>
|
|
205
|
+
${statusMarkup}
|
|
206
|
+
${
|
|
207
|
+
isVisible && state.charts.result && !state.charts.resultLoading && !state.charts.resultError
|
|
208
|
+
? `
|
|
209
|
+
<div class="h-[18rem] overflow-hidden">
|
|
210
|
+
${renderQueryResultsPane(state.charts.result, {
|
|
211
|
+
selectedRowIndex: null,
|
|
212
|
+
editable: false,
|
|
213
|
+
sortColumn: null,
|
|
214
|
+
sortDirection: null,
|
|
215
|
+
})}
|
|
216
|
+
</div>
|
|
217
|
+
`
|
|
218
|
+
: ''
|
|
219
|
+
}
|
|
220
|
+
</section>
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resolveChartCardSizeClass(state) {
|
|
225
|
+
return `query-chart-card--${String(state.charts.chartHeightPreset ?? 'medium')
|
|
226
|
+
.trim()
|
|
227
|
+
.toLowerCase()}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderChartHeightPresetToggle(state) {
|
|
231
|
+
const activePreset = String(state.charts.chartHeightPreset ?? 'medium')
|
|
232
|
+
.trim()
|
|
233
|
+
.toLowerCase();
|
|
234
|
+
const presets = [
|
|
235
|
+
{ value: 'small', label: 'Small', height: '300px' },
|
|
236
|
+
{ value: 'medium', label: 'Medium', height: '450px' },
|
|
237
|
+
{ value: 'large', label: 'Large', height: '600px' },
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
return `
|
|
241
|
+
<div class="flex flex-col gap-2">
|
|
242
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
243
|
+
Chart Height
|
|
244
|
+
</div>
|
|
245
|
+
<div class="charts-height-toggle" role="group" aria-label="Chart height preset">
|
|
246
|
+
${presets
|
|
247
|
+
.map(
|
|
248
|
+
preset => `
|
|
249
|
+
<button
|
|
250
|
+
class="standard-button charts-height-toggle__button ${activePreset === preset.value ? 'is-active' : ''}"
|
|
251
|
+
data-action="set-charts-height-preset"
|
|
252
|
+
data-preset="${preset.value}"
|
|
253
|
+
type="button"
|
|
254
|
+
title="${preset.height}"
|
|
255
|
+
>
|
|
256
|
+
${preset.label}
|
|
257
|
+
</button>
|
|
258
|
+
`,
|
|
259
|
+
)
|
|
260
|
+
.join('')}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderChartSurface(chart, state, analysis) {
|
|
267
|
+
if (state.charts.resultLoading) {
|
|
268
|
+
return `
|
|
269
|
+
<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">
|
|
270
|
+
Loading chart result...
|
|
271
|
+
</div>
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (state.charts.resultError) {
|
|
276
|
+
return `
|
|
277
|
+
<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">
|
|
278
|
+
${escapeHtml(state.charts.resultError.message)}
|
|
279
|
+
</div>
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const validation = validateQueryChartConfig(chart.chartType, chart.config, analysis);
|
|
284
|
+
|
|
285
|
+
if (!validation.valid) {
|
|
286
|
+
return `
|
|
287
|
+
<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">
|
|
288
|
+
${escapeHtml(validation.errors.join(' '))}
|
|
289
|
+
</div>
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return `
|
|
294
|
+
<div
|
|
295
|
+
class="query-chart-canvas w-full"
|
|
296
|
+
data-query-chart-id="${escapeHtml(chart.id)}"
|
|
297
|
+
data-chart-export-name="${escapeHtml(chart.name.replace(/[^\w.-]+/g, '_'))}"
|
|
298
|
+
></div>
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function renderChartCard(chart, state, analysis) {
|
|
303
|
+
const sizeClass = resolveChartCardSizeClass(state);
|
|
304
|
+
|
|
305
|
+
return `
|
|
306
|
+
<article class="query-chart-card ${sizeClass}">
|
|
307
|
+
<header class="query-chart-card__header">
|
|
308
|
+
<div class="min-w-0">
|
|
309
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
310
|
+
<h3 class="truncate font-headline text-xl font-black uppercase tracking-tight text-on-surface">
|
|
311
|
+
${escapeHtml(chart.name)}
|
|
312
|
+
</h3>
|
|
313
|
+
${renderStatusBadge(getQueryChartTypeLabel(chart.chartType), 'primary')}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
317
|
+
<button
|
|
318
|
+
class="standard-button"
|
|
319
|
+
data-action="open-edit-query-chart-modal"
|
|
320
|
+
data-chart-id="${escapeHtml(chart.id)}"
|
|
321
|
+
type="button"
|
|
322
|
+
>
|
|
323
|
+
Edit
|
|
324
|
+
</button>
|
|
325
|
+
<button
|
|
326
|
+
class="delete-button"
|
|
327
|
+
data-action="open-delete-query-chart-modal"
|
|
328
|
+
data-chart-id="${escapeHtml(chart.id)}"
|
|
329
|
+
type="button"
|
|
330
|
+
>
|
|
331
|
+
Delete
|
|
332
|
+
</button>
|
|
333
|
+
<button
|
|
334
|
+
class="standard-button"
|
|
335
|
+
data-action="export-query-chart-png"
|
|
336
|
+
data-chart-id="${escapeHtml(chart.id)}"
|
|
337
|
+
type="button"
|
|
338
|
+
>
|
|
339
|
+
Export PNG
|
|
340
|
+
</button>
|
|
341
|
+
</div>
|
|
342
|
+
</header>
|
|
343
|
+
<div class="query-chart-card__body">
|
|
344
|
+
${renderChartSurface(chart, state, analysis)}
|
|
345
|
+
</div>
|
|
346
|
+
</article>
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function renderChartsDetail(state) {
|
|
351
|
+
const detail = state.charts.detail;
|
|
352
|
+
const selectedHistoryId = state.charts.selectedHistoryId;
|
|
353
|
+
const historyVisible = state.editor.historyPanelVisible !== false;
|
|
354
|
+
|
|
355
|
+
if (!selectedHistoryId) {
|
|
356
|
+
return renderEmptyChartDetail();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (state.charts.detailLoading && !detail) {
|
|
360
|
+
return `
|
|
361
|
+
<div class="flex flex-1 items-center justify-center px-8 text-center text-on-surface-variant/45">
|
|
362
|
+
<div>
|
|
363
|
+
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
364
|
+
<p class="font-mono text-[10px] uppercase tracking-[0.18em]">Loading Query Detail</p>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (state.charts.detailError && !detail) {
|
|
371
|
+
return `
|
|
372
|
+
<div class="p-8">
|
|
373
|
+
<div class="border border-error/30 bg-error-container/20 px-4 py-4 text-sm text-error">
|
|
374
|
+
${escapeHtml(state.charts.detailError.message)}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!detail?.item) {
|
|
381
|
+
return renderEmptyChartDetail();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const analysis = state.charts.result ? analyzeQueryChartResult(state.charts.result) : null;
|
|
385
|
+
const charts = detail.charts ?? [];
|
|
386
|
+
|
|
387
|
+
return `
|
|
388
|
+
<div class="custom-scrollbar flex-1 overflow-auto">
|
|
389
|
+
<div class="charts-detail-shell">
|
|
390
|
+
<header class="charts-detail-shell__header">
|
|
391
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">
|
|
392
|
+
Charts
|
|
393
|
+
</div>
|
|
394
|
+
<div class="charts-detail-shell__title">
|
|
395
|
+
<h1 class="mt-2 truncate font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
|
|
396
|
+
${escapeHtml(detail.item.displayTitle)}
|
|
397
|
+
</h1>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="charts-detail-shell__controls">
|
|
400
|
+
<div class="charts-detail-shell__controls-group">
|
|
401
|
+
${renderChartHeightPresetToggle(state)}
|
|
402
|
+
<button
|
|
403
|
+
class="standard-button"
|
|
404
|
+
data-action="toggle-query-history-panel"
|
|
405
|
+
data-next-value="${historyVisible ? 'false' : 'true'}"
|
|
406
|
+
type="button"
|
|
407
|
+
>
|
|
408
|
+
<span class="material-symbols-outlined text-sm">${
|
|
409
|
+
historyVisible ? 'visibility_off' : 'history'
|
|
410
|
+
}</span>
|
|
411
|
+
${historyVisible ? 'Hide Query History' : 'Show Query History'}
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="charts-detail-shell__controls-group charts-detail-shell__controls-group--end">
|
|
415
|
+
<button
|
|
416
|
+
class="standard-button"
|
|
417
|
+
data-action="open-query-history"
|
|
418
|
+
data-history-id="${escapeHtml(detail.item.id)}"
|
|
419
|
+
type="button"
|
|
420
|
+
>
|
|
421
|
+
<span class="material-symbols-outlined text-sm">terminal</span>
|
|
422
|
+
Open In Editor
|
|
423
|
+
</button>
|
|
424
|
+
<button
|
|
425
|
+
class="signature-button"
|
|
426
|
+
data-action="open-create-query-chart-modal"
|
|
427
|
+
type="button"
|
|
428
|
+
${state.charts.resultError || !state.charts.result ? 'disabled' : ''}
|
|
429
|
+
>
|
|
430
|
+
New Chart
|
|
431
|
+
</button>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</header>
|
|
435
|
+
|
|
436
|
+
${renderQuerySqlSection(state, detail.item.rawSql)}
|
|
437
|
+
|
|
438
|
+
<section class="mt-6 space-y-5">
|
|
439
|
+
${
|
|
440
|
+
charts.length
|
|
441
|
+
? charts.map(chart => renderChartCard(chart, state, analysis)).join('')
|
|
442
|
+
: `
|
|
443
|
+
<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">
|
|
444
|
+
<div>
|
|
445
|
+
<span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">add_chart</span>
|
|
446
|
+
<p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
|
|
447
|
+
No Charts Yet
|
|
448
|
+
</p>
|
|
449
|
+
<p class="mt-2 max-w-md text-sm leading-6 text-on-surface-variant/60">
|
|
450
|
+
Create the first chart for this query. The query stays read-only here; only chart definitions are editable.
|
|
451
|
+
</p>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
`
|
|
455
|
+
}
|
|
456
|
+
</section>
|
|
457
|
+
|
|
458
|
+
${renderQueryResultsSection(state)}
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function renderChartsView(state) {
|
|
465
|
+
const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
|
|
466
|
+
|
|
467
|
+
if (!state.connections.active && state.charts.error?.code === 'ACTIVE_DATABASE_REQUIRED') {
|
|
468
|
+
return {
|
|
469
|
+
main: `<section class="view-surface flex min-h-full">${renderMissingDatabase()}</section>`,
|
|
470
|
+
panel: '',
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
main: `
|
|
476
|
+
<section class="charts-view">
|
|
477
|
+
${
|
|
478
|
+
historyVisible
|
|
479
|
+
? `
|
|
480
|
+
<aside class="charts-view__sidebar">
|
|
481
|
+
<div class="charts-view__sidebar-header">
|
|
482
|
+
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
483
|
+
Query History
|
|
484
|
+
</div>
|
|
485
|
+
<h2 class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
486
|
+
Charts
|
|
487
|
+
</h2>
|
|
488
|
+
</div>
|
|
489
|
+
${renderChartsList(state)}
|
|
490
|
+
</aside>
|
|
491
|
+
`
|
|
492
|
+
: ''
|
|
493
|
+
}
|
|
494
|
+
<div class="charts-view__detail">${renderChartsDetail(state)}</div>
|
|
495
|
+
</section>
|
|
496
|
+
`,
|
|
497
|
+
panel: '',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
@@ -8,32 +8,25 @@ function renderConnectionsActionButton({
|
|
|
8
8
|
action = "open-modal",
|
|
9
9
|
modal,
|
|
10
10
|
tone = "secondary",
|
|
11
|
-
className = "",
|
|
12
11
|
disabled = false,
|
|
13
12
|
}) {
|
|
14
|
-
const clipPath = "polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%)";
|
|
15
13
|
const toneClassName =
|
|
16
14
|
tone === "primary"
|
|
17
|
-
? "
|
|
18
|
-
: "
|
|
19
|
-
const iconClassName =
|
|
20
|
-
tone === "primary"
|
|
21
|
-
? "text-base text-on-primary"
|
|
22
|
-
: "text-base text-primary-container/90";
|
|
23
|
-
const clipStyle = `style="--clip-path: ${clipPath};"`;
|
|
15
|
+
? "signature-button"
|
|
16
|
+
: "standard-button";
|
|
17
|
+
const iconClassName = "";
|
|
24
18
|
const modalAttribute = modal ? `data-modal="${modal}"` : "";
|
|
25
19
|
|
|
26
20
|
return `
|
|
27
21
|
<button
|
|
28
|
-
class="
|
|
22
|
+
class="${toneClassName}"
|
|
29
23
|
data-action="${escapeHtml(action)}"
|
|
30
24
|
${modalAttribute}
|
|
31
|
-
${clipStyle}
|
|
32
25
|
type="button"
|
|
33
26
|
${disabled ? "disabled" : ""}
|
|
34
27
|
>
|
|
35
28
|
<span>${label}</span>
|
|
36
|
-
<span class="material-symbols-outlined ${iconClassName}">${icon}</span>
|
|
29
|
+
<span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` : ""}">${icon}</span>
|
|
37
30
|
</button>
|
|
38
31
|
`;
|
|
39
32
|
}
|
|
@@ -77,13 +70,11 @@ function renderConnectionsBody(state) {
|
|
|
77
70
|
icon: "folder_open",
|
|
78
71
|
modal: "open-connection",
|
|
79
72
|
tone: "primary",
|
|
80
|
-
className: "min-w-[17rem] px-8 py-6 text-sm",
|
|
81
73
|
})}
|
|
82
74
|
${renderConnectionsActionButton({
|
|
83
75
|
label: "Create Database",
|
|
84
76
|
icon: "note_add",
|
|
85
77
|
modal: "create-connection",
|
|
86
|
-
className: "min-w-[17rem] px-8 py-6 text-sm",
|
|
87
78
|
})}
|
|
88
79
|
</div>
|
|
89
80
|
</div>
|
|
@@ -108,13 +99,11 @@ export function renderConnectionsView(state) {
|
|
|
108
99
|
icon: "folder_open",
|
|
109
100
|
modal: "open-connection",
|
|
110
101
|
tone: "primary",
|
|
111
|
-
className: "min-w-[13rem]",
|
|
112
102
|
})}
|
|
113
103
|
${renderConnectionsActionButton({
|
|
114
104
|
label: "Create Database",
|
|
115
105
|
icon: "note_add",
|
|
116
106
|
modal: "create-connection",
|
|
117
|
-
className: "min-w-[13rem]",
|
|
118
107
|
})}
|
|
119
108
|
${
|
|
120
109
|
state.connections.active
|
|
@@ -122,7 +111,6 @@ export function renderConnectionsView(state) {
|
|
|
122
111
|
label: state.connections.backupLoading ? "Creating Backup..." : "Create Backup",
|
|
123
112
|
icon: "inventory_2",
|
|
124
113
|
action: "create-backup",
|
|
125
|
-
className: "min-w-[13rem]",
|
|
126
114
|
disabled: state.connections.backupLoading,
|
|
127
115
|
})
|
|
128
116
|
: ""
|