sqlite-hub 0.12.0 → 0.17.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 +118 -23
- package/bin/sqlite-hub.js +1041 -224
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +32 -2
- package/frontend/js/app.js +989 -13
- package/frontend/js/components/modal.js +644 -15
- package/frontend/js/components/pageHeader.js +14 -16
- package/frontend/js/components/queryEditor.js +9 -1
- package/frontend/js/components/queryHistoryPanel.js +126 -131
- package/frontend/js/components/queryResults.js +79 -17
- package/frontend/js/components/rowEditorPanel.js +204 -10
- package/frontend/js/components/sidebar.js +102 -18
- package/frontend/js/components/tableDesignerEditor.js +69 -11
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +868 -9
- package/frontend/js/utils/copyColumnExport.js +117 -0
- package/frontend/js/utils/exportFilenames.js +32 -0
- package/frontend/js/utils/filePathPreview.js +315 -0
- package/frontend/js/utils/format.js +1 -1
- package/frontend/js/utils/markdownDocuments.js +248 -0
- package/frontend/js/utils/rowEditorJson.js +65 -0
- package/frontend/js/utils/sqlFormatter.js +691 -0
- package/frontend/js/utils/tableDesigner.js +178 -6
- package/frontend/js/utils/textCellStats.js +20 -0
- package/frontend/js/utils/timestampPreview.js +264 -0
- package/frontend/js/views/charts.js +3 -1
- package/frontend/js/views/data.js +34 -2
- package/frontend/js/views/documents.js +300 -0
- package/frontend/js/views/editor.js +48 -1
- package/frontend/js/views/settings.js +39 -6
- package/frontend/js/views/structure.js +154 -212
- package/frontend/styles/base.css +6 -0
- package/frontend/styles/components.css +476 -2
- package/frontend/styles/structure-graph.css +0 -3
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +1 -1
- package/frontend/styles/views.css +422 -0
- package/package.json +3 -3
- package/server/routes/documents.js +163 -0
- package/server/routes/settings.js +22 -6
- package/server/server.js +6 -0
- package/server/services/sqlite/introspection.js +10 -0
- package/server/services/sqlite/sqlExecutor.js +29 -0
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +25 -1
- package/server/services/sqlite/tableDesigner/schemaMapping.js +105 -10
- package/server/services/sqlite/tableDesigner/validation.js +60 -2
- package/server/services/sqlite/tableDesignerService.js +1 -1
- package/server/services/storage/appStateStore.js +313 -0
- package/tests/check-constraint-options.test.js +14 -0
- package/tests/cli-args.test.js +100 -0
- package/tests/copy-column-modal.test.js +83 -0
- package/tests/database-documents.test.js +85 -0
- package/tests/export-filenames.test.js +34 -0
- package/tests/file-path-preview.test.js +165 -0
- package/tests/markdown-documents.test.js +79 -0
- package/tests/row-editor-json.test.js +82 -0
- package/tests/row-editor-timestamp-preview.test.js +192 -0
- package/tests/settings-metadata.test.js +16 -0
- package/tests/settings-view.test.js +32 -0
- package/tests/sql-formatter.test.js +173 -0
- package/tests/sql-highlight.test.js +38 -0
- package/tests/table-designer-v2-unique-constraints.test.js +78 -0
- package/tests/text-cell-stats.test.js +38 -0
- package/fill.js +0 -526
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import { escapeHtml } from
|
|
1
|
+
import { escapeHtml } from '../utils/format.js';
|
|
2
2
|
|
|
3
|
-
export function renderPageHeader({ eyebrow =
|
|
4
|
-
|
|
3
|
+
export function renderPageHeader({ eyebrow = '', title, subtitle = '', actions = '' }) {
|
|
4
|
+
return `
|
|
5
5
|
<div class="mb-10 flex flex-wrap items-end justify-between gap-6">
|
|
6
6
|
<div>
|
|
7
7
|
${
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
eyebrow
|
|
9
|
+
? `
|
|
10
10
|
<div class="page-eyebrow">
|
|
11
11
|
<span class="text-[#FCE300] text-xs font-mono font-bold tracking-widest uppercase">${escapeHtml(
|
|
12
|
-
|
|
12
|
+
eyebrow,
|
|
13
13
|
)}</span>
|
|
14
14
|
<div class="page-eyebrow-line"></div>
|
|
15
15
|
</div>
|
|
16
16
|
`
|
|
17
|
-
|
|
17
|
+
: ''
|
|
18
18
|
}
|
|
19
|
-
<h1 class="text-5xl font-headline font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(
|
|
20
|
-
title
|
|
21
|
-
)}</h1>
|
|
19
|
+
<h1 class="text-5xl font-headline font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(title)}</h1>
|
|
22
20
|
${
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
subtitle
|
|
22
|
+
? `<p class="text-xs font-mono text-on-surface/40 mt-1 uppercase tracking-widest">${escapeHtml(
|
|
23
|
+
subtitle,
|
|
24
|
+
)}</p>`
|
|
25
|
+
: ''
|
|
28
26
|
}
|
|
29
27
|
</div>
|
|
30
|
-
${actions ? `<div class="flex gap-3">${actions}</div>` :
|
|
28
|
+
${actions ? `<div class="flex gap-3">${actions}</div>` : ''}
|
|
31
29
|
</div>
|
|
32
30
|
`;
|
|
33
31
|
}
|
|
@@ -69,9 +69,17 @@ export function renderQueryEditor({
|
|
|
69
69
|
data-next-value="${historyVisible ? "false" : "true"}"
|
|
70
70
|
type="button"
|
|
71
71
|
>
|
|
72
|
-
<span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "
|
|
72
|
+
<span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "visibility"}</span>
|
|
73
73
|
${historyVisible ? "Hide History" : "Show History"}
|
|
74
74
|
</button>
|
|
75
|
+
<button
|
|
76
|
+
class="${secondaryButtonClass}"
|
|
77
|
+
data-action="format-current-query"
|
|
78
|
+
type="button"
|
|
79
|
+
>
|
|
80
|
+
<span class="material-symbols-outlined text-sm">format_align_left</span>
|
|
81
|
+
Format
|
|
82
|
+
</button>
|
|
75
83
|
<button
|
|
76
84
|
class="${secondaryButtonClass}"
|
|
77
85
|
data-action="clear-query"
|
|
@@ -1,133 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
formatNumber,
|
|
4
|
-
} from "../utils/format.js";
|
|
5
|
-
import { renderStatusBadge } from "./badges.js";
|
|
1
|
+
import { escapeHtml, formatNumber } from '../utils/format.js';
|
|
2
|
+
import { renderStatusBadge } from './badges.js';
|
|
6
3
|
|
|
7
4
|
function getQueryTypeTone(queryType) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
if (queryType === 'select' || queryType === 'update') {
|
|
6
|
+
return 'success';
|
|
7
|
+
}
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
if (queryType === 'pragma') {
|
|
10
|
+
return 'primary';
|
|
11
|
+
}
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
return 'muted';
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
export function renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
const isActive = Number(activeHistoryId) === Number(item.id);
|
|
18
|
+
const isSelected = Number(selectedHistoryId) === Number(item.id);
|
|
19
|
+
const visibleTables = (item.tablesDetected ?? []).slice(0, 3);
|
|
20
|
+
const itemClasses = [
|
|
21
|
+
'query-history-item',
|
|
22
|
+
isActive ? 'is-active' : '',
|
|
23
|
+
item.lastRun?.status === 'error' ? 'is-error' : '',
|
|
24
|
+
]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ');
|
|
27
|
+
const tableMarkup = visibleTables
|
|
28
|
+
.map(tableName =>
|
|
29
|
+
[
|
|
30
|
+
'<span class="border border-outline-variant/20 bg-surface-highest px-2 py-1">',
|
|
31
|
+
escapeHtml(tableName),
|
|
32
|
+
'</span>',
|
|
33
|
+
].join(''),
|
|
34
|
+
)
|
|
35
|
+
.join('');
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
37
|
+
return [
|
|
38
|
+
'<article class="',
|
|
39
|
+
itemClasses,
|
|
40
|
+
'"><button class="query-history-item-hit ',
|
|
41
|
+
isActive ? 'is-active' : '',
|
|
42
|
+
'" data-action="select-query-history-item" data-history-id="',
|
|
43
|
+
escapeHtml(item.id),
|
|
44
|
+
'" type="button">',
|
|
45
|
+
'<div class="flex flex-wrap items-center gap-2"><span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface w-full">',
|
|
46
|
+
escapeHtml(item.displayTitle),
|
|
47
|
+
'</span>',
|
|
48
|
+
renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
|
|
49
|
+
item.isSaved ? renderStatusBadge('saved', 'primary') : '',
|
|
50
|
+
item.isDestructive ? renderStatusBadge('destructive', 'warning') : '',
|
|
51
|
+
'</div>',
|
|
52
|
+
'<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">',
|
|
53
|
+
escapeHtml(item.previewSql),
|
|
54
|
+
'</p></button>',
|
|
55
|
+
'<div class="flex items-center justify-between gap-3 border-t border-outline-variant/10 px-3 pb-3 pt-2">',
|
|
56
|
+
'<div class="min-w-0 flex flex-wrap gap-2 text-[10px] font-mono uppercase tracking-[0.14em] text-on-surface-variant/55">',
|
|
57
|
+
tableMarkup,
|
|
58
|
+
'</div><div class="flex items-center gap-1">',
|
|
59
|
+
'<button class="query-history-icon-button" data-action="open-query-history" data-history-id="',
|
|
60
|
+
escapeHtml(item.id),
|
|
61
|
+
'" title="Open in editor" type="button"><span class="material-symbols-outlined text-[18px]">edit_note</span></button>',
|
|
62
|
+
'<button class="query-history-icon-button" data-action="run-query-history" data-history-id="',
|
|
63
|
+
escapeHtml(item.id),
|
|
64
|
+
'" title="Run query" type="button"><span class="material-symbols-outlined text-[18px]">play_arrow</span></button>',
|
|
65
|
+
'<button class="query-history-icon-button ',
|
|
66
|
+
item.isSaved ? 'is-active' : '',
|
|
67
|
+
'" data-action="toggle-query-history-saved" data-history-id="',
|
|
68
|
+
escapeHtml(item.id),
|
|
69
|
+
'" data-next-value="',
|
|
70
|
+
item.isSaved ? 'false' : 'true',
|
|
71
|
+
'" title="',
|
|
72
|
+
item.isSaved ? 'Remove from saved' : 'Save query',
|
|
73
|
+
'" type="button"><span class="material-symbols-outlined text-[18px]">',
|
|
74
|
+
item.isSaved ? 'bookmark' : 'bookmark_add',
|
|
75
|
+
'</span></button>',
|
|
76
|
+
'<button class="query-history-icon-button ',
|
|
77
|
+
isSelected ? 'is-active' : '',
|
|
78
|
+
'" data-action="select-query-history-item" data-history-id="',
|
|
79
|
+
escapeHtml(item.id),
|
|
80
|
+
'" title="Open query detail" type="button"><span class="material-symbols-outlined text-[18px]">info</span></button>',
|
|
81
|
+
'</div></div></article>',
|
|
82
|
+
].join('');
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
function renderQueryHistoryTabs(activeTab, historyTotal) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
const tabs = [
|
|
87
|
+
{ id: 'recent', label: 'Recent' },
|
|
88
|
+
{ id: 'saved', label: 'Saved' },
|
|
89
|
+
{ id: 'unsaved', label: 'Unsaved' },
|
|
90
|
+
{ id: 'failed', label: 'Failed' },
|
|
91
|
+
];
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
return [
|
|
94
|
+
'<div class="flex items-center gap-2">',
|
|
95
|
+
tabs
|
|
96
|
+
.map(tab =>
|
|
97
|
+
[
|
|
98
|
+
'<button class="query-history-tab ',
|
|
99
|
+
activeTab === tab.id ? 'is-active' : '',
|
|
100
|
+
'" data-action="set-query-history-tab" data-tab="',
|
|
101
|
+
tab.id,
|
|
102
|
+
'" type="button">',
|
|
103
|
+
escapeHtml(tab.label),
|
|
104
|
+
'</button>',
|
|
105
|
+
].join(''),
|
|
106
|
+
)
|
|
107
|
+
.join(''),
|
|
108
|
+
'<span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">',
|
|
109
|
+
escapeHtml(formatNumber(historyTotal)),
|
|
110
|
+
'</span></div>',
|
|
111
|
+
].join('');
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
export function renderQueryHistoryPanel({
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
115
|
+
items = [],
|
|
116
|
+
loading = false,
|
|
117
|
+
loadingMore = false,
|
|
118
|
+
error = null,
|
|
119
|
+
activeTab = 'recent',
|
|
120
|
+
search = '',
|
|
121
|
+
committedSearch = '',
|
|
122
|
+
total = 0,
|
|
123
|
+
hasMore = false,
|
|
124
|
+
activeHistoryId = null,
|
|
125
|
+
selectedHistoryId = null,
|
|
129
126
|
}) {
|
|
130
|
-
|
|
127
|
+
return `
|
|
131
128
|
<aside class="query-history-panel border-l border-outline-variant/10 bg-surface-container-lowest">
|
|
132
129
|
<div class="border-b border-outline-variant/10 px-4 py-4">
|
|
133
130
|
<div class="flex items-center justify-between gap-3">
|
|
@@ -162,23 +159,23 @@ export function renderQueryHistoryPanel({
|
|
|
162
159
|
<div
|
|
163
160
|
class="custom-scrollbar min-h-0 flex-1 overflow-auto px-3 py-3"
|
|
164
161
|
data-query-history-committed-search="${escapeHtml(committedSearch)}"
|
|
165
|
-
data-query-history-loading-more="${loadingMore ?
|
|
162
|
+
data-query-history-loading-more="${loadingMore ? 'true' : 'false'}"
|
|
166
163
|
data-query-history-search="${escapeHtml(search)}"
|
|
167
164
|
data-query-history-scroll
|
|
168
165
|
data-query-history-tab="${escapeHtml(activeTab)}"
|
|
169
166
|
>
|
|
170
167
|
${
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
error
|
|
169
|
+
? `
|
|
173
170
|
<div class="border border-error/30 bg-error-container/20 px-4 py-3 text-sm text-error">
|
|
174
171
|
${escapeHtml(error.message)}
|
|
175
172
|
</div>
|
|
176
173
|
`
|
|
177
|
-
|
|
174
|
+
: ''
|
|
178
175
|
}
|
|
179
176
|
${
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
!loading && !items.length
|
|
178
|
+
? `
|
|
182
179
|
<div class="flex h-full min-h-[240px] flex-col items-center justify-center px-6 text-center">
|
|
183
180
|
<span class="material-symbols-outlined text-4xl text-on-surface-variant/25">manage_search</span>
|
|
184
181
|
<p class="mt-4 font-headline text-lg font-bold uppercase tracking-tight text-on-surface">
|
|
@@ -189,36 +186,34 @@ export function renderQueryHistoryPanel({
|
|
|
189
186
|
</p>
|
|
190
187
|
</div>
|
|
191
188
|
`
|
|
192
|
-
|
|
189
|
+
: ''
|
|
193
190
|
}
|
|
194
191
|
<div class="space-y-3">
|
|
195
|
-
${items
|
|
196
|
-
.map((item) => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId))
|
|
197
|
-
.join("")}
|
|
192
|
+
${items.map(item => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId)).join('')}
|
|
198
193
|
</div>
|
|
199
194
|
${
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
loading
|
|
196
|
+
? `
|
|
202
197
|
<div class="mt-4 text-center text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/50">
|
|
203
198
|
Loading query history...
|
|
204
199
|
</div>
|
|
205
200
|
`
|
|
206
|
-
|
|
201
|
+
: ''
|
|
207
202
|
}
|
|
208
203
|
${
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
hasMore
|
|
205
|
+
? `
|
|
211
206
|
<div class="mt-4 flex justify-center">
|
|
212
207
|
<button
|
|
213
208
|
class="standard-button"
|
|
214
209
|
data-action="load-more-query-history"
|
|
215
210
|
type="button"
|
|
216
211
|
>
|
|
217
|
-
${loadingMore ?
|
|
212
|
+
${loadingMore ? 'Loading...' : 'Load More'}
|
|
218
213
|
</button>
|
|
219
214
|
</div>
|
|
220
215
|
`
|
|
221
|
-
|
|
216
|
+
: ''
|
|
222
217
|
}
|
|
223
218
|
</div>
|
|
224
219
|
</aside>
|
|
@@ -9,31 +9,93 @@ function getSortIcon(columnName, sortColumn, sortDirection) {
|
|
|
9
9
|
return sortDirection === "desc" ? "south" : "north";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const COPY_COLUMN_ACTIONS = [
|
|
13
|
+
{ mode: "column", label: "Copy column" },
|
|
14
|
+
{ mode: "column-with-header", label: "Copy column with header" },
|
|
15
|
+
{ mode: "first-10", label: "Copy first 10" },
|
|
16
|
+
{ mode: "markdown-todo", label: "Export as Markdown Todo" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function renderColumnActionMenu(columnName, resultScope) {
|
|
20
|
+
return `
|
|
21
|
+
<details class="query-result-column-menu" data-copy-column-menu>
|
|
22
|
+
<summary
|
|
23
|
+
aria-label="Column actions for ${escapeHtml(columnName)}"
|
|
24
|
+
class="query-result-column-menu__toggle"
|
|
25
|
+
title="Column actions"
|
|
26
|
+
>
|
|
27
|
+
<span class="material-symbols-outlined" aria-hidden="true">more_vert</span>
|
|
28
|
+
</summary>
|
|
29
|
+
<div class="query-result-column-menu__panel" role="menu">
|
|
30
|
+
${COPY_COLUMN_ACTIONS.map(
|
|
31
|
+
(item) => `
|
|
32
|
+
<button
|
|
33
|
+
class="query-result-column-menu__item"
|
|
34
|
+
data-action="open-copy-column-modal"
|
|
35
|
+
data-column-name="${escapeHtml(columnName)}"
|
|
36
|
+
data-copy-mode="${escapeHtml(item.mode)}"
|
|
37
|
+
data-result-scope="${escapeHtml(resultScope)}"
|
|
38
|
+
role="menuitem"
|
|
39
|
+
type="button"
|
|
40
|
+
>
|
|
41
|
+
${escapeHtml(item.label)}
|
|
42
|
+
</button>
|
|
43
|
+
`
|
|
44
|
+
).join("")}
|
|
45
|
+
</div>
|
|
46
|
+
</details>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderSortableHeader(columnName, sortColumn, sortDirection, { resultScope, sortAction }) {
|
|
13
51
|
const isActive = columnName === sortColumn;
|
|
52
|
+
const labelMarkup = `<span class="query-result-column-label truncate" title="${escapeHtml(columnName)}">${escapeHtml(
|
|
53
|
+
columnName
|
|
54
|
+
)}</span>`;
|
|
14
55
|
|
|
15
56
|
return `
|
|
16
|
-
<
|
|
17
|
-
class="
|
|
18
|
-
isActive ? "text-primary-container" : "text-on-surface-variant hover:text-primary-container"
|
|
19
|
-
}"
|
|
20
|
-
data-action="sort-editor-results-column"
|
|
57
|
+
<div
|
|
58
|
+
class="query-result-column-header"
|
|
21
59
|
data-column-name="${escapeHtml(columnName)}"
|
|
22
|
-
|
|
60
|
+
data-result-column-header
|
|
61
|
+
data-result-scope="${escapeHtml(resultScope)}"
|
|
23
62
|
>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
63
|
+
${
|
|
64
|
+
sortAction
|
|
65
|
+
? `
|
|
66
|
+
<button
|
|
67
|
+
class="query-result-column-sort ${
|
|
68
|
+
isActive ? "text-primary-container" : "text-on-surface-variant hover:text-primary-container"
|
|
69
|
+
}"
|
|
70
|
+
data-action="${escapeHtml(sortAction)}"
|
|
71
|
+
data-column-name="${escapeHtml(columnName)}"
|
|
72
|
+
type="button"
|
|
73
|
+
>
|
|
74
|
+
${labelMarkup}
|
|
75
|
+
<span class="material-symbols-outlined text-sm leading-none">${getSortIcon(
|
|
76
|
+
columnName,
|
|
77
|
+
sortColumn,
|
|
78
|
+
sortDirection
|
|
79
|
+
)}</span>
|
|
80
|
+
</button>
|
|
81
|
+
`
|
|
82
|
+
: `<span class="query-result-column-static text-on-surface-variant">${labelMarkup}</span>`
|
|
83
|
+
}
|
|
84
|
+
${renderColumnActionMenu(columnName, resultScope)}
|
|
85
|
+
</div>
|
|
31
86
|
`;
|
|
32
87
|
}
|
|
33
88
|
|
|
34
89
|
export function renderQueryResultsPane(
|
|
35
90
|
result,
|
|
36
|
-
{
|
|
91
|
+
{
|
|
92
|
+
selectedRowIndex = null,
|
|
93
|
+
editable = false,
|
|
94
|
+
sortColumn = null,
|
|
95
|
+
sortDirection = null,
|
|
96
|
+
resultScope = "editor",
|
|
97
|
+
sortAction = "sort-editor-results-column",
|
|
98
|
+
} = {}
|
|
37
99
|
) {
|
|
38
100
|
if (!result) {
|
|
39
101
|
return `
|
|
@@ -48,8 +110,8 @@ export function renderQueryResultsPane(
|
|
|
48
110
|
|
|
49
111
|
const columns = (result.columns ?? []).map((columnName) => ({
|
|
50
112
|
headerClassName:
|
|
51
|
-
"border-b-2 border-primary-container px-4 py-3 text-[10px] font-bold uppercase tracking-widest",
|
|
52
|
-
renderHeader: () => renderSortableHeader(columnName, sortColumn, sortDirection),
|
|
113
|
+
"query-result-header-cell border-b-2 border-primary-container px-4 py-3 text-[10px] font-bold uppercase tracking-widest",
|
|
114
|
+
renderHeader: () => renderSortableHeader(columnName, sortColumn, sortDirection, { resultScope, sortAction }),
|
|
53
115
|
cellClassName: "px-4 py-3 align-top text-on-surface",
|
|
54
116
|
render: (row) => {
|
|
55
117
|
const value = formatCellValue(row[columnName]);
|