sqlite-hub 1.0.0 → 1.1.1
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 +47 -33
- package/bin/sqlite-hub.js +127 -47
- package/frontend/js/api.js +6 -0
- package/frontend/js/app.js +229 -44
- package/frontend/js/components/modal.js +17 -1
- package/frontend/js/components/queryChartRenderer.js +28 -3
- package/frontend/js/components/queryEditor.js +24 -26
- package/frontend/js/components/queryHistoryDetail.js +1 -1
- package/frontend/js/components/queryHistoryHeader.js +48 -0
- package/frontend/js/components/queryHistoryList.js +132 -0
- package/frontend/js/components/queryHistoryPanel.js +72 -136
- package/frontend/js/components/structureGraph.js +700 -89
- package/frontend/js/components/tableDesignerEditor.js +26 -47
- package/frontend/js/components/tableDesignerSidebar.js +7 -31
- package/frontend/js/components/tableDesignerSqlPreview.js +2 -1
- package/frontend/js/store.js +97 -8
- package/frontend/js/utils/exportFilenames.js +3 -1
- package/frontend/js/views/charts.js +326 -174
- package/frontend/js/views/connections.js +59 -64
- package/frontend/js/views/data.js +48 -54
- package/frontend/js/views/documents.js +62 -23
- package/frontend/js/views/editor.js +0 -2
- package/frontend/js/views/mediaTagging.js +6 -5
- package/frontend/js/views/settings.js +78 -0
- package/frontend/js/views/structure.js +48 -32
- package/frontend/js/views/tableDesigner.js +45 -1
- package/frontend/styles/components.css +259 -54
- package/frontend/styles/structure-graph.css +150 -37
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +75 -38
- package/package.json +3 -2
- package/server/routes/export.js +89 -22
- package/server/routes/externalApi.js +84 -2
- package/server/routes/settings.js +37 -28
- package/server/services/appInfoService.js +215 -0
- package/server/services/databaseCommandService.js +50 -6
- package/server/services/sqlite/dataBrowserService.js +11 -3
- package/server/services/sqlite/exportService.js +307 -22
- package/tests/api-token-auth.test.js +110 -1
- package/tests/cli-args.test.js +16 -3
- package/tests/database-command-service.test.js +40 -3
- package/tests/export-blob.test.js +54 -1
- package/tests/export-filenames.test.js +4 -0
- package/tests/settings-api-tokens-route.test.js +22 -1
- package/tests/settings-metadata.test.js +99 -1
- package/tests/settings-view.test.js +28 -0
|
@@ -1,39 +1,36 @@
|
|
|
1
|
-
import { renderConnectionCard } from
|
|
2
|
-
import { renderPageHeader } from
|
|
3
|
-
import { escapeHtml } from
|
|
1
|
+
import { renderConnectionCard } from '../components/connectionCard.js';
|
|
2
|
+
import { renderPageHeader } from '../components/pageHeader.js';
|
|
3
|
+
import { escapeHtml } from '../utils/format.js';
|
|
4
4
|
|
|
5
5
|
function renderConnectionsActionButton({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
label,
|
|
7
|
+
icon,
|
|
8
|
+
action = 'open-modal',
|
|
9
|
+
modal,
|
|
10
|
+
tone = 'secondary',
|
|
11
|
+
disabled = false,
|
|
12
12
|
}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
: "standard-button";
|
|
17
|
-
const iconClassName = "";
|
|
18
|
-
const modalAttribute = modal ? `data-modal="${modal}"` : "";
|
|
13
|
+
const toneClassName = tone === 'primary' ? 'signature-button' : 'standard-button';
|
|
14
|
+
const iconClassName = '';
|
|
15
|
+
const modalAttribute = modal ? `data-modal="${modal}"` : '';
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
return `
|
|
21
18
|
<button
|
|
22
19
|
class="${toneClassName}"
|
|
23
20
|
data-action="${escapeHtml(action)}"
|
|
24
21
|
${modalAttribute}
|
|
25
22
|
type="button"
|
|
26
|
-
${disabled ?
|
|
23
|
+
${disabled ? 'disabled' : ''}
|
|
27
24
|
>
|
|
28
25
|
<span>${label}</span>
|
|
29
|
-
<span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` :
|
|
26
|
+
<span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` : ''}">${icon}</span>
|
|
30
27
|
</button>
|
|
31
28
|
`;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
function renderConnectionsBody(state) {
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if (state.connections.loading && !state.connections.recent.length) {
|
|
33
|
+
return `
|
|
37
34
|
<div class="flex min-h-[280px] items-center justify-center border border-outline-variant/10 bg-surface-container-low">
|
|
38
35
|
<div class="text-center text-on-surface-variant/40">
|
|
39
36
|
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
@@ -41,10 +38,10 @@ function renderConnectionsBody(state) {
|
|
|
41
38
|
</div>
|
|
42
39
|
</div>
|
|
43
40
|
`;
|
|
44
|
-
|
|
41
|
+
}
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
if (state.connections.error) {
|
|
44
|
+
return `
|
|
48
45
|
<div class="border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
|
|
49
46
|
<div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
|
|
50
47
|
${escapeHtml(state.connections.error.code)}
|
|
@@ -52,10 +49,10 @@ function renderConnectionsBody(state) {
|
|
|
52
49
|
<div class="mt-2">${escapeHtml(state.connections.error.message)}</div>
|
|
53
50
|
</div>
|
|
54
51
|
`;
|
|
55
|
-
|
|
52
|
+
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
if (!state.connections.recent.length) {
|
|
55
|
+
return `
|
|
59
56
|
<div class="border border-dashed border-outline-variant/20 bg-surface-container-low px-8 py-10 text-center">
|
|
60
57
|
<span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
|
|
61
58
|
<p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
|
|
@@ -66,71 +63,69 @@ function renderConnectionsBody(state) {
|
|
|
66
63
|
</p>
|
|
67
64
|
<div class="mt-6 flex flex-wrap items-center justify-center gap-3">
|
|
68
65
|
${renderConnectionsActionButton({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tone: "primary",
|
|
66
|
+
label: 'Create Database',
|
|
67
|
+
icon: 'note_add',
|
|
68
|
+
modal: 'create-connection',
|
|
73
69
|
})}
|
|
74
70
|
${renderConnectionsActionButton({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
label: 'Open Database',
|
|
72
|
+
icon: 'folder_open',
|
|
73
|
+
modal: 'open-connection',
|
|
74
|
+
tone: 'primary',
|
|
78
75
|
})}
|
|
79
76
|
</div>
|
|
80
77
|
</div>
|
|
81
78
|
`;
|
|
82
|
-
|
|
79
|
+
}
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
return `
|
|
85
82
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
86
83
|
${state.connections.recent
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
.join("")}
|
|
84
|
+
.map(connection => renderConnectionCard(connection, state.connections.active?.id))
|
|
85
|
+
.join('')}
|
|
91
86
|
</div>
|
|
92
87
|
`;
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
export function renderConnectionsView(state) {
|
|
96
|
-
|
|
91
|
+
const actions = `
|
|
92
|
+
${
|
|
93
|
+
state.connections.active
|
|
94
|
+
? renderConnectionsActionButton({
|
|
95
|
+
label: state.connections.backupLoading ? 'Creating Backup...' : 'Create Backup',
|
|
96
|
+
icon: 'inventory_2',
|
|
97
|
+
action: 'create-backup',
|
|
98
|
+
disabled: state.connections.backupLoading,
|
|
99
|
+
})
|
|
100
|
+
: ''
|
|
101
|
+
}
|
|
97
102
|
${renderConnectionsActionButton({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tone: "primary",
|
|
103
|
+
label: 'Create Database',
|
|
104
|
+
icon: 'note_add',
|
|
105
|
+
modal: 'create-connection',
|
|
102
106
|
})}
|
|
103
107
|
${renderConnectionsActionButton({
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
label: 'Open Database',
|
|
109
|
+
icon: 'folder_open',
|
|
110
|
+
modal: 'open-connection',
|
|
111
|
+
tone: 'primary',
|
|
107
112
|
})}
|
|
108
|
-
${
|
|
109
|
-
state.connections.active
|
|
110
|
-
? renderConnectionsActionButton({
|
|
111
|
-
label: state.connections.backupLoading ? "Creating Backup..." : "Create Backup",
|
|
112
|
-
icon: "inventory_2",
|
|
113
|
-
action: "create-backup",
|
|
114
|
-
disabled: state.connections.backupLoading,
|
|
115
|
-
})
|
|
116
|
-
: ""
|
|
117
|
-
}
|
|
118
113
|
`;
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
return {
|
|
116
|
+
main: `
|
|
122
117
|
<section class="view-surface relative min-h-full overflow-hidden">
|
|
123
118
|
<div class="data-grid-texture pointer-events-none absolute inset-0"></div>
|
|
124
119
|
<div class="view-frame relative z-10">
|
|
125
120
|
${renderPageHeader({
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
title: 'Connections',
|
|
122
|
+
subtitle: `Registry // Recent_Targets: ${String(state.connections.recent.length).padStart(2, '0')}`,
|
|
123
|
+
actions,
|
|
129
124
|
})}
|
|
130
125
|
${renderConnectionsBody(state)}
|
|
131
126
|
</div>
|
|
132
127
|
</section>
|
|
133
128
|
`,
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
panel: '',
|
|
130
|
+
};
|
|
136
131
|
}
|
|
@@ -78,31 +78,32 @@ function renderTableList(state) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
return `
|
|
81
|
-
<div class="custom-scrollbar
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
81
|
+
<div class="subnavi-list custom-scrollbar">
|
|
82
|
+
${filteredTables
|
|
83
|
+
.map(
|
|
84
|
+
table => `
|
|
85
|
+
<button
|
|
86
|
+
class="table-designer-sidebar__item subnavi-item border px-4 py-3 text-left transition-colors ${
|
|
87
|
+
table.name === activeName
|
|
88
|
+
? 'is-active border-primary-container/30 bg-surface-container-high'
|
|
89
|
+
: 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
|
|
90
|
+
}"
|
|
91
|
+
data-action="navigate"
|
|
92
|
+
data-to="/data/${encodeURIComponent(table.name)}"
|
|
93
|
+
type="button"
|
|
94
|
+
>
|
|
95
|
+
<div class="table-designer-sidebar__item-name ${table.name === activeName ? 'is-active' : ''}">
|
|
96
|
+
${escapeHtml(table.name)}
|
|
97
|
+
</div>
|
|
98
|
+
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
99
|
+
${escapeHtml(formatNumber(table.columnCount ?? 0))} column${
|
|
100
|
+
Number(table.columnCount ?? 0) === 1 ? '' : 's'
|
|
101
|
+
}
|
|
102
|
+
</div>
|
|
103
|
+
</button>
|
|
104
|
+
`,
|
|
105
|
+
)
|
|
106
|
+
.join('')}
|
|
106
107
|
</div>
|
|
107
108
|
`;
|
|
108
109
|
}
|
|
@@ -112,30 +113,14 @@ function renderWorkspaceHeader(state) {
|
|
|
112
113
|
const tablesVisible = state.dataBrowser.tablesVisible !== false;
|
|
113
114
|
|
|
114
115
|
return `
|
|
115
|
-
<header class="
|
|
116
|
-
<div class="flex flex-wrap items-
|
|
117
|
-
<div class="data-headline-container">
|
|
118
|
-
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
119
|
-
Data Browser
|
|
120
|
-
</div>
|
|
121
|
-
<h1 class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
|
|
122
|
-
${escapeHtml(table?.name ?? 'Table Data')}
|
|
123
|
-
</h1>
|
|
124
|
-
<div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
125
|
-
${
|
|
126
|
-
table
|
|
127
|
-
? `rows ${escapeHtml(formatNumber(table.rowCount ?? 0))} // columns ${escapeHtml(
|
|
128
|
-
formatNumber(table.columns?.length ?? 0),
|
|
129
|
-
)}`
|
|
130
|
-
: `tables ${escapeHtml(formatNumber(state.dataBrowser.tables.length))}`
|
|
131
|
-
}
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
116
|
+
<header class="workspace-header">
|
|
117
|
+
<div class="flex flex-wrap items-center justify-between gap-4">
|
|
134
118
|
<div class="flex items-center gap-3">
|
|
135
119
|
${
|
|
136
120
|
table
|
|
137
121
|
? `<button
|
|
138
|
-
class="standard-button"
|
|
122
|
+
class="standard-button panel-toggle-button ${tablesVisible ? '' : 'is-active'}"
|
|
123
|
+
aria-pressed="${tablesVisible ? 'false' : 'true'}"
|
|
139
124
|
data-action="toggle-data-tables"
|
|
140
125
|
type="button"
|
|
141
126
|
>
|
|
@@ -145,7 +130,14 @@ function renderWorkspaceHeader(state) {
|
|
|
145
130
|
: '<span class="material-symbols-outlined text-sm">visibility</span> Show Tables'
|
|
146
131
|
}
|
|
147
132
|
</button>
|
|
148
|
-
|
|
133
|
+
`
|
|
134
|
+
: ''
|
|
135
|
+
}
|
|
136
|
+
</div>
|
|
137
|
+
<div class="flex flex-wrap items-center justify-end gap-3">
|
|
138
|
+
${
|
|
139
|
+
table
|
|
140
|
+
? `<button
|
|
149
141
|
class="standard-button"
|
|
150
142
|
data-action="open-data-export-modal"
|
|
151
143
|
type="button"
|
|
@@ -476,6 +468,8 @@ function renderTableSurface(state) {
|
|
|
476
468
|
escapeHtml(formatNumber(totalRows)),
|
|
477
469
|
filteredRowsText,
|
|
478
470
|
' rows',
|
|
471
|
+
' // columns ',
|
|
472
|
+
escapeHtml(formatNumber(table.columns?.length ?? 0)),
|
|
479
473
|
'</div>',
|
|
480
474
|
'<div class="flex flex-wrap items-center gap-4"><div class="flex items-center gap-2">',
|
|
481
475
|
'<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">rows</span>',
|
|
@@ -639,17 +633,17 @@ export function renderDataView(state) {
|
|
|
639
633
|
return {
|
|
640
634
|
main: `
|
|
641
635
|
<section class="view-surface min-h-full bg-surface-container flex h-full min-h-0 flex-col overflow-hidden">
|
|
642
|
-
<div class="
|
|
636
|
+
<div class="data-view-grid ${tablesVisible ? 'data-view-grid--with-subnavi' : ''}">
|
|
643
637
|
${
|
|
644
638
|
tablesVisible
|
|
645
639
|
? `
|
|
646
|
-
<aside class="
|
|
647
|
-
<div class="
|
|
648
|
-
<div
|
|
649
|
-
Tables
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
640
|
+
<aside class="data-view__sidebar subnavi-panel">
|
|
641
|
+
<div class="subnavi-header">
|
|
642
|
+
<div>
|
|
643
|
+
<div class="subnavi-header-title">Tables</div>
|
|
644
|
+
<div class="subnavi-header-details">
|
|
645
|
+
total ${escapeHtml(formatNumber(state.dataBrowser.tables.length))}
|
|
646
|
+
</div>
|
|
653
647
|
</div>
|
|
654
648
|
</div>
|
|
655
649
|
${renderDataTableSearch(state)}
|
|
@@ -24,7 +24,7 @@ function renderDocumentListItem(item, selectedId) {
|
|
|
24
24
|
|
|
25
25
|
return `
|
|
26
26
|
<button
|
|
27
|
-
class="documents-list-item ${isSelected ? 'is-selected' : ''}"
|
|
27
|
+
class="documents-list-item subnavi-item ${isSelected ? 'is-selected' : ''}"
|
|
28
28
|
data-action="select-document"
|
|
29
29
|
data-document-id="${escapeHtml(item.id)}"
|
|
30
30
|
type="button"
|
|
@@ -42,33 +42,58 @@ function renderDocumentListItem(item, selectedId) {
|
|
|
42
42
|
`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function getFilteredDocuments(items, searchQuery) {
|
|
46
|
+
const normalizedSearch = String(searchQuery ?? '')
|
|
47
|
+
.trim()
|
|
48
|
+
.toLowerCase();
|
|
49
|
+
|
|
50
|
+
if (!normalizedSearch) {
|
|
51
|
+
return items;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return items.filter(item =>
|
|
55
|
+
[item.filename, item.title]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.some(value => String(value).toLowerCase().includes(normalizedSearch)),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderDocumentsSearch(documents) {
|
|
62
|
+
return `
|
|
63
|
+
<label class="table-designer-sidebar__search">
|
|
64
|
+
<span class="material-symbols-outlined text-sm text-on-surface-variant/55">search</span>
|
|
65
|
+
<input
|
|
66
|
+
class="table-designer-sidebar__search-input"
|
|
67
|
+
data-bind="documents-search"
|
|
68
|
+
placeholder="Search documents..."
|
|
69
|
+
spellcheck="false"
|
|
70
|
+
type="search"
|
|
71
|
+
value="${escapeHtml(documents.searchQuery ?? '')}"
|
|
72
|
+
/>
|
|
73
|
+
</label>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
|
|
45
77
|
function renderDocumentsSidebar(documents) {
|
|
46
78
|
const items = documents.items ?? [];
|
|
79
|
+
const filteredItems = getFilteredDocuments(items, documents.searchQuery);
|
|
47
80
|
|
|
48
81
|
return `
|
|
49
|
-
<aside class="documents-view__sidebar">
|
|
50
|
-
<div class="documents-view__sidebar-header">
|
|
82
|
+
<aside class="documents-view__sidebar subnavi-panel">
|
|
83
|
+
<div class="documents-view__sidebar-header subnavi-header">
|
|
51
84
|
<div>
|
|
52
|
-
<div class="
|
|
53
|
-
|
|
54
|
-
</div>
|
|
55
|
-
<div class="mt-1 font-mono text-xs text-primary-container">${formatNumber(items.length)} files</div>
|
|
85
|
+
<div class="subnavi-header-title">Documents</div>
|
|
86
|
+
<div class="subnavi-header-details">${formatNumber(items.length)} files</div>
|
|
56
87
|
</div>
|
|
57
|
-
<button
|
|
58
|
-
class="icon-button"
|
|
59
|
-
data-action="create-document"
|
|
60
|
-
title="New document"
|
|
61
|
-
type="button"
|
|
62
|
-
${documents.saving ? 'disabled aria-disabled="true"' : ''}
|
|
63
|
-
>
|
|
64
|
-
<span class="material-symbols-outlined">add</span>
|
|
65
|
-
</button>
|
|
66
88
|
</div>
|
|
67
|
-
|
|
89
|
+
${renderDocumentsSearch(documents)}
|
|
90
|
+
<div class="documents-view__sidebar-body subnavi-list custom-scrollbar">
|
|
68
91
|
${
|
|
69
|
-
|
|
70
|
-
?
|
|
71
|
-
: `<div class="documents-list-empty"
|
|
92
|
+
filteredItems.length
|
|
93
|
+
? filteredItems.map(item => renderDocumentListItem(item, documents.selectedId)).join('')
|
|
94
|
+
: `<div class="documents-list-empty">${
|
|
95
|
+
items.length ? 'No documents match the current search.' : 'No documents yet'
|
|
96
|
+
}</div>`
|
|
72
97
|
}
|
|
73
98
|
</div>
|
|
74
99
|
</aside>
|
|
@@ -78,7 +103,9 @@ function renderDocumentsSidebar(documents) {
|
|
|
78
103
|
function renderDocumentToolbar(documents) {
|
|
79
104
|
const disabled = documents.saving || documents.detailLoading || !documents.selectedId;
|
|
80
105
|
|
|
81
|
-
return `
|
|
106
|
+
return `
|
|
107
|
+
<div class="documents-titlebar">
|
|
108
|
+
<label class="documents-filename-field">
|
|
82
109
|
<input
|
|
83
110
|
class="control-input"
|
|
84
111
|
data-bind="document-field"
|
|
@@ -90,13 +117,24 @@ function renderDocumentToolbar(documents) {
|
|
|
90
117
|
${!documents.selectedId ? 'disabled' : ''}
|
|
91
118
|
/>
|
|
92
119
|
</label>
|
|
120
|
+
<button
|
|
121
|
+
class="signature-button documents-create-button"
|
|
122
|
+
data-action="create-document"
|
|
123
|
+
type="button"
|
|
124
|
+
${documents.saving ? 'disabled aria-disabled="true"' : ''}
|
|
125
|
+
>
|
|
126
|
+
<span class="material-symbols-outlined">add</span>
|
|
127
|
+
New Document
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
93
130
|
<div class="documents-toolbar">
|
|
94
131
|
|
|
95
132
|
|
|
96
133
|
|
|
97
134
|
<div class="documents-toolbar__actions">
|
|
98
135
|
<button
|
|
99
|
-
class="standard-button"
|
|
136
|
+
class="standard-button panel-toggle-button ${documents.editorVisible ? '' : 'is-active'}"
|
|
137
|
+
aria-pressed="${documents.editorVisible ? 'false' : 'true'}"
|
|
100
138
|
data-action="toggle-document-pane"
|
|
101
139
|
data-pane="editor"
|
|
102
140
|
type="button"
|
|
@@ -105,7 +143,8 @@ function renderDocumentToolbar(documents) {
|
|
|
105
143
|
${documents.editorVisible ? 'Hide Editor' : 'Show Editor'}
|
|
106
144
|
</button>
|
|
107
145
|
<button
|
|
108
|
-
class="standard-button"
|
|
146
|
+
class="standard-button panel-toggle-button ${documents.previewVisible ? '' : 'is-active'}"
|
|
147
|
+
aria-pressed="${documents.previewVisible ? 'false' : 'true'}"
|
|
109
148
|
data-action="toggle-document-pane"
|
|
110
149
|
data-pane="preview"
|
|
111
150
|
type="button"
|
|
@@ -307,8 +307,6 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
|
307
307
|
query: state.editor.sqlText,
|
|
308
308
|
executing: state.editor.executing,
|
|
309
309
|
exporting: state.editor.exportLoading,
|
|
310
|
-
historyLoading: state.editor.historyLoading,
|
|
311
|
-
historyTotal: state.editor.historyTotal,
|
|
312
310
|
editorVisible: state.editor.editorPanelVisible,
|
|
313
311
|
historyVisible: state.editor.historyPanelVisible,
|
|
314
312
|
})}
|
|
@@ -396,8 +396,8 @@ function renderPreviewMedia(
|
|
|
396
396
|
|
|
397
397
|
const metadata = getWorkflowMetadataEntries(currentItem);
|
|
398
398
|
const toggleLabel = detailsVisible
|
|
399
|
-
? '<span class="material-symbols-outlined">visibility_off</span>
|
|
400
|
-
: 'Show
|
|
399
|
+
? '<span class="material-symbols-outlined">visibility_off</span> Hide Viewer'
|
|
400
|
+
: '<span class="material-symbols-outlined">visibility</span> Show Viewer';
|
|
401
401
|
|
|
402
402
|
return `
|
|
403
403
|
<div class="media-tagging-preview ${detailsVisible ? '' : 'media-tagging-preview--meta-hidden'}">
|
|
@@ -450,11 +450,12 @@ function renderPreviewMedia(
|
|
|
450
450
|
: ''
|
|
451
451
|
}
|
|
452
452
|
<button
|
|
453
|
-
class="standard-button media-tagging-preview__toggle"
|
|
453
|
+
class="standard-button panel-toggle-button media-tagging-preview__toggle ${detailsVisible ? '' : 'is-active'}"
|
|
454
|
+
aria-pressed="${detailsVisible ? 'false' : 'true'}"
|
|
454
455
|
data-action="toggle-media-tagging-current-media"
|
|
455
456
|
data-next-value="${detailsVisible ? 'false' : 'true'}"
|
|
456
|
-
data-expanded-label="
|
|
457
|
-
data-collapsed-label="Show
|
|
457
|
+
data-expanded-label="Hide Viewer"
|
|
458
|
+
data-collapsed-label="Show Viewer"
|
|
458
459
|
aria-expanded="${detailsVisible ? 'true' : 'false'}"
|
|
459
460
|
type="button"
|
|
460
461
|
>
|
|
@@ -30,6 +30,66 @@ function renderSettingsNavigation(activeSection) {
|
|
|
30
30
|
`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function renderVersionCheckStatus(settings) {
|
|
34
|
+
const result = settings.versionCheck;
|
|
35
|
+
const error = settings.versionCheckError;
|
|
36
|
+
|
|
37
|
+
if (error) {
|
|
38
|
+
return `
|
|
39
|
+
<div class="border border-error/20 bg-error-container/10 px-4 py-3 text-sm text-on-surface">
|
|
40
|
+
<div class="font-mono text-[10px] font-bold uppercase tracking-[0.18em] text-error">
|
|
41
|
+
${escapeHtml(error.code ?? 'VERSION_CHECK_FAILED')}
|
|
42
|
+
</div>
|
|
43
|
+
<div class="mt-1 text-on-surface-variant">${escapeHtml(error.message ?? 'Version check failed.')}</div>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!result) {
|
|
49
|
+
return `
|
|
50
|
+
<div class="border border-outline-variant/10 bg-surface-container-high px-4 py-3 text-sm text-on-surface-variant">
|
|
51
|
+
No version check has been run yet.
|
|
52
|
+
</div>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const updateAvailable = Boolean(result.updateAvailable);
|
|
57
|
+
const releaseLink =
|
|
58
|
+
updateAvailable && result.releaseUrl
|
|
59
|
+
? `
|
|
60
|
+
<a
|
|
61
|
+
class="standard-button"
|
|
62
|
+
href="${escapeHtml(result.releaseUrl)}"
|
|
63
|
+
rel="noreferrer"
|
|
64
|
+
target="_blank"
|
|
65
|
+
>
|
|
66
|
+
<span class="material-symbols-outlined text-sm">open_in_new</span>
|
|
67
|
+
Open Release
|
|
68
|
+
</a>
|
|
69
|
+
`
|
|
70
|
+
: '';
|
|
71
|
+
|
|
72
|
+
return `
|
|
73
|
+
<div class="border ${
|
|
74
|
+
updateAvailable ? 'border-primary-container/30 bg-primary-container/5' : 'border-outline-variant/10 bg-surface-container-high'
|
|
75
|
+
} px-4 py-3">
|
|
76
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
77
|
+
<div class="min-w-0">
|
|
78
|
+
<div class="font-mono text-[10px] font-bold uppercase tracking-[0.18em] ${
|
|
79
|
+
updateAvailable ? 'text-primary-container' : 'text-on-surface-variant/70'
|
|
80
|
+
}">
|
|
81
|
+
${updateAvailable ? 'New Version Available' : 'Up To Date'}
|
|
82
|
+
</div>
|
|
83
|
+
<div class="mt-1 text-sm text-on-surface-variant">
|
|
84
|
+
Current v${escapeHtml(result.currentVersion ?? '0.0.0')} · Latest v${escapeHtml(result.latestVersion ?? 'unknown')}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
${releaseLink}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
33
93
|
function renderSettingsContent(state) {
|
|
34
94
|
if (state.settings.loading && !state.settings.appVersion) {
|
|
35
95
|
return `
|
|
@@ -56,6 +116,7 @@ function renderSettingsContent(state) {
|
|
|
56
116
|
const activeSection = state.settings.section === 'api-tokens' ? 'api-tokens' : 'information';
|
|
57
117
|
const appVersion = escapeHtml(state.settings.appVersion ?? '0.0.0');
|
|
58
118
|
const sqliteVersion = escapeHtml(state.settings.sqliteVersion ?? 'unknown');
|
|
119
|
+
const versionCheckLoading = Boolean(state.settings.versionCheckLoading);
|
|
59
120
|
const tokenDatabase = state.settings.tokenDatabase;
|
|
60
121
|
const apiTokens = state.settings.apiTokens ?? [];
|
|
61
122
|
const createdApiToken = state.settings.createdApiToken;
|
|
@@ -171,6 +232,23 @@ function renderSettingsContent(state) {
|
|
|
171
232
|
<div class="border-t border-outline-variant/10 py-4 text-sm leading-6 text-on-surface-variant">
|
|
172
233
|
SQL functions and syntax are evaluated with this SQLite runtime.
|
|
173
234
|
</div>
|
|
235
|
+
<div class="border-t border-outline-variant/10 pt-4">
|
|
236
|
+
<div class="mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
237
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
238
|
+
Version_Check
|
|
239
|
+
</div>
|
|
240
|
+
<button
|
|
241
|
+
class="standard-button"
|
|
242
|
+
data-action="check-app-version"
|
|
243
|
+
type="button"
|
|
244
|
+
${versionCheckLoading ? 'disabled aria-disabled="true"' : ''}
|
|
245
|
+
>
|
|
246
|
+
<span class="material-symbols-outlined text-sm">${versionCheckLoading ? 'progress_activity' : 'sync'}</span>
|
|
247
|
+
${versionCheckLoading ? 'Checking...' : 'Check Updates'}
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
${renderVersionCheckStatus(state.settings)}
|
|
251
|
+
</div>
|
|
174
252
|
</div>
|
|
175
253
|
</section>
|
|
176
254
|
|