sqlite-hub 0.17.2 → 1.1.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 +143 -47
- package/bin/sqlite-hub.js +206 -444
- package/examples/api/queries.js +31 -0
- package/examples/api/rows.js +27 -0
- package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
- package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
- package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
- package/frontend/assets/mockups/connections_1200.webp +0 -0
- package/frontend/assets/mockups/data_1_1200.webp +0 -0
- package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
- package/frontend/assets/mockups/documents_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
- package/frontend/assets/mockups/overview_1200.webp +0 -0
- package/frontend/assets/mockups/settings_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
- package/frontend/assets/mockups/structure_1_1200.webp +0 -0
- package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
- package/frontend/js/api.js +25 -0
- package/frontend/js/app.js +263 -34
- package/frontend/js/components/formControls.js +38 -0
- package/frontend/js/components/modal.js +77 -21
- package/frontend/js/components/queryChartRenderer.js +28 -3
- package/frontend/js/components/queryEditor.js +20 -24
- 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 +699 -89
- package/frontend/js/components/tableDesignerEditor.js +3 -5
- package/frontend/js/store.js +188 -7
- package/frontend/js/utils/exportFilenames.js +3 -1
- package/frontend/js/views/charts.js +320 -169
- package/frontend/js/views/connections.js +59 -64
- package/frontend/js/views/data.js +12 -20
- package/frontend/js/views/editor.js +0 -2
- package/frontend/js/views/settings.js +219 -5
- package/frontend/js/views/structure.js +27 -13
- package/frontend/styles/components.css +155 -0
- package/frontend/styles/structure-graph.css +140 -35
- package/frontend/styles/tailwind.generated.css +2 -2
- package/frontend/styles/views.css +12 -6
- package/package.json +7 -6
- package/server/middleware/apiTokenAuth.js +30 -0
- package/server/routes/connections.js +17 -0
- package/server/routes/export.js +89 -22
- package/server/routes/externalApi.js +304 -0
- package/server/routes/settings.js +90 -21
- package/server/server.js +22 -1
- package/server/services/apiTokenService.js +101 -0
- package/server/services/appInfoService.js +215 -0
- package/server/services/databaseCommandService.js +443 -0
- package/server/services/nativeFileDialogService.js +93 -1
- package/server/services/sqlite/exportService.js +307 -22
- package/server/services/storage/appStateStore.js +113 -0
- package/server/utils/errors.js +7 -0
- package/tests/api-token-auth.test.js +236 -0
- package/tests/cli-args.test.js +16 -3
- package/tests/cli-service-delegation.test.js +43 -0
- package/tests/connections-file-dialog-route.test.js +43 -0
- package/tests/copy-column-modal.test.js +34 -0
- package/tests/database-command-service.test.js +139 -0
- package/tests/export-blob.test.js +54 -1
- package/tests/export-filenames.test.js +4 -0
- package/tests/form-controls.test.js +34 -0
- package/tests/native-file-dialog.test.js +27 -0
- package/tests/settings-api-tokens-route.test.js +97 -0
- package/tests/settings-metadata.test.js +99 -1
- package/tests/settings-view.test.js +75 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const SQLITE_HUB_URL = 'http://127.0.0.1:4180';
|
|
2
|
+
// Create a new token for your database in Settings > API Tokens.
|
|
3
|
+
const apiToken = 'shub_Lv9A2xocv01PqT6a0jEmuT-PxPyqfzI_UBaY_VEzjqE';
|
|
4
|
+
const DATABASE_ID = 'conn_ae9b5e54ae8eca1d';
|
|
5
|
+
const path = `${SQLITE_HUB_URL}/api/v1/databases/${DATABASE_ID}`;
|
|
6
|
+
|
|
7
|
+
async function databaseInfo(path, { method = 'GET' } = {}) {
|
|
8
|
+
console.log(path);
|
|
9
|
+
const response = await fetch(path, {
|
|
10
|
+
method,
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: 'application/json',
|
|
13
|
+
Authorization: `Bearer ${apiToken}`,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const queries = await databaseInfo(`${path}/queries`);
|
|
20
|
+
|
|
21
|
+
console.log(queries.data.items.map(query => query.displayTitle));
|
|
22
|
+
|
|
23
|
+
const queryName = encodeURIComponent(queries.data.items[0].displayTitle);
|
|
24
|
+
const query = await databaseInfo(`${path}/queries/${queryName}`);
|
|
25
|
+
|
|
26
|
+
console.log(query.data);
|
|
27
|
+
|
|
28
|
+
// Executing a saved query changes server state and therefore requires POST.
|
|
29
|
+
const exec = await databaseInfo(`${path}/queries/${queryName}/execute`, { method: 'POST' });
|
|
30
|
+
|
|
31
|
+
console.log(exec.data);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const SQLITE_HUB_URL = 'http://127.0.0.1:4180';
|
|
2
|
+
// Create a new token for your database in Settings > API Tokens.
|
|
3
|
+
const apiToken = 'shub_Lv9A2xocv01PqT6a0jEmuT-PxPyqfzI_UBaY_VEzjqE';
|
|
4
|
+
const DATABASE_ID = 'conn_ae9b5e54ae8eca1d';
|
|
5
|
+
const path = `${SQLITE_HUB_URL}/api/v1/databases/${DATABASE_ID}`;
|
|
6
|
+
|
|
7
|
+
async function databaseInfo(path) {
|
|
8
|
+
console.log(path);
|
|
9
|
+
const response = await fetch(path, {
|
|
10
|
+
headers: {
|
|
11
|
+
Accept: 'application/json',
|
|
12
|
+
Authorization: `Bearer ${apiToken}`,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const info = await databaseInfo(path);
|
|
20
|
+
const tables = await databaseInfo(`${path}/tables`);
|
|
21
|
+
|
|
22
|
+
console.log(info.data);
|
|
23
|
+
console.log(tables.data.items);
|
|
24
|
+
|
|
25
|
+
const row = await databaseInfo(`${path}/tables/${tables.data.items[0].name}`);
|
|
26
|
+
|
|
27
|
+
console.log(row.data);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/frontend/js/api.js
CHANGED
|
@@ -86,6 +86,12 @@ export function createConnection(payload) {
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
export function chooseOpenDatabasePath() {
|
|
90
|
+
return request("/api/connections/choose-open-path", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
89
95
|
export function chooseCreateDatabasePath() {
|
|
90
96
|
return request("/api/connections/choose-create-path", {
|
|
91
97
|
method: "POST",
|
|
@@ -425,10 +431,29 @@ export function patchSettings(settings) {
|
|
|
425
431
|
});
|
|
426
432
|
}
|
|
427
433
|
|
|
434
|
+
export function checkAppVersion() {
|
|
435
|
+
return request("/api/settings/version-check");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function createApiToken(name) {
|
|
439
|
+
return request("/api/settings/api-tokens", {
|
|
440
|
+
method: "POST",
|
|
441
|
+
body: { name },
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function deleteApiToken(tokenId) {
|
|
446
|
+
return request(`/api/settings/api-tokens/${encodeURIComponent(tokenId)}`, {
|
|
447
|
+
method: "DELETE",
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
428
451
|
const TEXT_EXPORT_EXTENSIONS = {
|
|
429
452
|
csv: "csv",
|
|
430
453
|
tsv: "tsv",
|
|
431
454
|
md: "md",
|
|
455
|
+
json: "json",
|
|
456
|
+
parquet: "parquet",
|
|
432
457
|
};
|
|
433
458
|
|
|
434
459
|
function normalizeTextExportFormat(format) {
|
package/frontend/js/app.js
CHANGED
|
@@ -24,6 +24,8 @@ import { renderTopNav } from './components/topNav.js';
|
|
|
24
24
|
import { createRouter } from './router.js';
|
|
25
25
|
import {
|
|
26
26
|
createActiveConnectionBackup,
|
|
27
|
+
createSettingsApiToken,
|
|
28
|
+
chooseOpenDatabasePath,
|
|
27
29
|
chooseCreateDatabasePath,
|
|
28
30
|
createDocument,
|
|
29
31
|
createDocumentFromMarkdownExport,
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
clearEditorResults,
|
|
34
36
|
clearQueryHistorySelection,
|
|
35
37
|
closeModal,
|
|
38
|
+
checkSettingsAppVersion,
|
|
36
39
|
dismissMediaTaggingIssue,
|
|
37
40
|
dismissToast,
|
|
38
41
|
executeCurrentQuery,
|
|
@@ -49,6 +52,7 @@ import {
|
|
|
49
52
|
openDeleteDataRowModal,
|
|
50
53
|
openDeleteEditorRowModal,
|
|
51
54
|
openDeleteQueryHistoryModal,
|
|
55
|
+
openDeleteSettingsApiTokenModal,
|
|
52
56
|
openDeleteQueryChartModal,
|
|
53
57
|
openDataExportModal,
|
|
54
58
|
openDeleteDocumentModal,
|
|
@@ -92,7 +96,9 @@ import {
|
|
|
92
96
|
toggleDataTablesPanel,
|
|
93
97
|
setCurrentQuery,
|
|
94
98
|
setChartsHeightPreset,
|
|
99
|
+
setChartsDetailPanelVisibility,
|
|
95
100
|
setChartsHistoryTab,
|
|
101
|
+
setChartsHistorySearchInput,
|
|
96
102
|
setChartsHistoryPanelVisibility,
|
|
97
103
|
setEditorPanelVisibility,
|
|
98
104
|
setEditorTab,
|
|
@@ -103,6 +109,7 @@ import {
|
|
|
103
109
|
submitCreateMediaTaggingTagTable,
|
|
104
110
|
submitCreateMediaTaggingMappingTable,
|
|
105
111
|
submitDeleteQueryHistoryConfirmation,
|
|
112
|
+
submitDeleteSettingsApiTokenConfirmation,
|
|
106
113
|
submitRowUpdatePreviewConfirmation,
|
|
107
114
|
setQueryHistoryPanelVisibility,
|
|
108
115
|
sortDataTableByColumn,
|
|
@@ -113,6 +120,7 @@ import {
|
|
|
113
120
|
setCopyColumnModalEditedText,
|
|
114
121
|
setCopyColumnModalSubmitting,
|
|
115
122
|
setRoute,
|
|
123
|
+
setSettingsSection,
|
|
116
124
|
saveQueryHistoryNotes,
|
|
117
125
|
saveQueryHistoryTitle,
|
|
118
126
|
saveCurrentTableDesignerDraft,
|
|
@@ -217,6 +225,7 @@ let lastRenderedPanelMarkup = '';
|
|
|
217
225
|
let lastRenderedModalMarkup = '';
|
|
218
226
|
let lastRenderedToastMarkup = '';
|
|
219
227
|
let lastRenderedChartsHistorySignature = '';
|
|
228
|
+
let lastRenderedChartsCardSignature = '';
|
|
220
229
|
let lastRenderedPanelOpen = false;
|
|
221
230
|
let lastRenderedLockedRoute = false;
|
|
222
231
|
let pendingNewTableDesignerAutofocus = false;
|
|
@@ -709,7 +718,11 @@ function buildChartsHistorySignature(state) {
|
|
|
709
718
|
}
|
|
710
719
|
|
|
711
720
|
return JSON.stringify({
|
|
721
|
+
detailPanelVisible: Boolean(state.charts.detailPanelVisible),
|
|
722
|
+
detailPanelHistoryId: state.charts.detailPanelVisible ? state.charts.selectedHistoryId : null,
|
|
712
723
|
tab: state.charts.historyTab ?? 'recent',
|
|
724
|
+
searchInput: state.charts.historySearchInput ?? '',
|
|
725
|
+
search: state.charts.historySearch ?? '',
|
|
713
726
|
queries: queries.map(item => [
|
|
714
727
|
item.id,
|
|
715
728
|
item.displayTitle,
|
|
@@ -720,6 +733,46 @@ function buildChartsHistorySignature(state) {
|
|
|
720
733
|
});
|
|
721
734
|
}
|
|
722
735
|
|
|
736
|
+
const chartSignatureObjectIds = new WeakMap();
|
|
737
|
+
let nextChartSignatureObjectId = 1;
|
|
738
|
+
|
|
739
|
+
function getChartSignatureObjectId(value) {
|
|
740
|
+
if (!value || typeof value !== 'object') {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (!chartSignatureObjectIds.has(value)) {
|
|
745
|
+
chartSignatureObjectIds.set(value, nextChartSignatureObjectId);
|
|
746
|
+
nextChartSignatureObjectId += 1;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return chartSignatureObjectIds.get(value);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function buildChartsCardSignature(state) {
|
|
753
|
+
if (state.route.name !== 'charts' || !state.charts.selectedHistoryId || !state.charts.detail?.item) {
|
|
754
|
+
return '';
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const charts = state.charts.detail?.charts ?? [];
|
|
758
|
+
|
|
759
|
+
return JSON.stringify({
|
|
760
|
+
selectedHistoryId: state.charts.selectedHistoryId,
|
|
761
|
+
chartHeightPreset: state.charts.chartHeightPreset ?? 'medium',
|
|
762
|
+
resultObjectId: getChartSignatureObjectId(state.charts.result),
|
|
763
|
+
resultLoading: Boolean(state.charts.resultLoading),
|
|
764
|
+
resultError: state.charts.resultError
|
|
765
|
+
? [state.charts.resultError.code ?? '', state.charts.resultError.message ?? '']
|
|
766
|
+
: null,
|
|
767
|
+
charts: charts.map(chart => [
|
|
768
|
+
chart.id,
|
|
769
|
+
chart.name,
|
|
770
|
+
chart.chartType,
|
|
771
|
+
JSON.stringify(chart.config ?? {}),
|
|
772
|
+
]),
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
723
776
|
function syncChartsHistorySelectionUi(state) {
|
|
724
777
|
const selectedHistoryId = String(state.charts.selectedHistoryId ?? '');
|
|
725
778
|
const historyButtons = shellRefs.view.querySelectorAll(
|
|
@@ -733,56 +786,123 @@ function syncChartsHistorySelectionUi(state) {
|
|
|
733
786
|
|
|
734
787
|
const itemNode = button.closest('[data-charts-history-item]');
|
|
735
788
|
const isSelected = button.dataset.historyId === selectedHistoryId;
|
|
736
|
-
const titleNode = button.querySelector('[data-charts-history-title]');
|
|
737
789
|
const selectionNode = itemNode instanceof HTMLElement ? itemNode : button;
|
|
738
790
|
|
|
739
|
-
selectionNode.classList.toggle('
|
|
740
|
-
|
|
741
|
-
selectionNode.classList.toggle('border-outline-variant/10', !isSelected);
|
|
742
|
-
selectionNode.classList.toggle('bg-surface-container-lowest', !isSelected);
|
|
743
|
-
selectionNode.classList.toggle('hover:bg-surface-container-high', !isSelected);
|
|
744
|
-
|
|
745
|
-
if (titleNode instanceof HTMLElement) {
|
|
746
|
-
titleNode.classList.toggle('text-primary-container', isSelected);
|
|
747
|
-
titleNode.classList.toggle('text-on-surface', !isSelected);
|
|
748
|
-
}
|
|
791
|
+
selectionNode.classList.toggle('is-active', isSelected);
|
|
792
|
+
button.classList.toggle('is-active', isSelected);
|
|
749
793
|
}
|
|
750
794
|
|
|
751
795
|
return true;
|
|
752
796
|
}
|
|
753
797
|
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
actionNode.dataset.nextValue = nextValue ? 'false' : 'true';
|
|
759
|
-
actionNode.title = nextValue ? 'Remove from saved' : 'Save query';
|
|
798
|
+
function syncChartsSavedToggleButtonUi(button, nextValue) {
|
|
799
|
+
button.classList.toggle('is-active', nextValue);
|
|
800
|
+
button.dataset.nextValue = nextValue ? 'false' : 'true';
|
|
801
|
+
button.title = nextValue ? 'Remove from saved' : 'Save query';
|
|
760
802
|
|
|
761
|
-
const iconNode =
|
|
803
|
+
const iconNode = button.querySelector('.material-symbols-outlined');
|
|
762
804
|
if (iconNode instanceof HTMLElement) {
|
|
763
805
|
iconNode.textContent = nextValue ? 'bookmark' : 'bookmark_add';
|
|
764
806
|
}
|
|
765
807
|
|
|
766
|
-
|
|
767
|
-
|
|
808
|
+
const labelNode = button.querySelector('[data-charts-saved-label]');
|
|
809
|
+
if (labelNode instanceof HTMLElement) {
|
|
810
|
+
labelNode.textContent = nextValue ? 'Unsave' : 'Save';
|
|
811
|
+
}
|
|
812
|
+
}
|
|
768
813
|
|
|
769
|
-
|
|
814
|
+
function syncChartsSavedToggleUi(actionNode, nextValue) {
|
|
815
|
+
const historyId = actionNode.dataset.historyId;
|
|
816
|
+
const relatedButtons = [
|
|
817
|
+
actionNode,
|
|
818
|
+
...shellRefs.view.querySelectorAll('[data-action="toggle-charts-query-history-saved"][data-history-id]'),
|
|
819
|
+
...shellRefs.panel.querySelectorAll('[data-action="toggle-charts-query-history-saved"][data-history-id]'),
|
|
820
|
+
].filter((button, index, buttons) => {
|
|
821
|
+
return (
|
|
822
|
+
button instanceof HTMLElement &&
|
|
823
|
+
button.dataset.historyId === historyId &&
|
|
824
|
+
buttons.indexOf(button) === index
|
|
825
|
+
);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
for (const button of relatedButtons) {
|
|
829
|
+
syncChartsSavedToggleButtonUi(button, nextValue);
|
|
830
|
+
|
|
831
|
+
const itemNode = button.closest('[data-charts-history-item]');
|
|
832
|
+
const historyTab = getState().charts.historyTab;
|
|
833
|
+
if (
|
|
834
|
+
itemNode instanceof HTMLElement &&
|
|
835
|
+
((historyTab === 'saved' && !nextValue) || (historyTab === 'unsaved' && nextValue))
|
|
836
|
+
) {
|
|
770
837
|
itemNode.remove();
|
|
771
838
|
}
|
|
772
839
|
}
|
|
773
840
|
|
|
841
|
+
for (const badgeNode of [
|
|
842
|
+
...shellRefs.view.querySelectorAll('[data-charts-saved-badge][data-history-id]'),
|
|
843
|
+
...shellRefs.panel.querySelectorAll('[data-charts-saved-badge][data-history-id]'),
|
|
844
|
+
]) {
|
|
845
|
+
if (badgeNode instanceof HTMLElement && badgeNode.dataset.historyId === historyId) {
|
|
846
|
+
badgeNode.toggleAttribute('hidden', !nextValue);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
774
850
|
const countNode = shellRefs.view.querySelector('[data-charts-history-count]');
|
|
775
851
|
if (countNode instanceof HTMLElement) {
|
|
776
|
-
|
|
777
|
-
const count =
|
|
778
|
-
state.charts.historyTab === 'saved'
|
|
779
|
-
? (state.charts.queries ?? []).filter(item => item.isSaved).length
|
|
780
|
-
: (state.charts.queries ?? []).length;
|
|
781
|
-
countNode.textContent = String(count);
|
|
852
|
+
countNode.textContent = String(shellRefs.view.querySelectorAll('[data-charts-history-item]').length);
|
|
782
853
|
}
|
|
783
854
|
}
|
|
784
855
|
|
|
785
|
-
function
|
|
856
|
+
function renderChartsMainIntoScratch(state) {
|
|
857
|
+
const scratch = document.createElement('div');
|
|
858
|
+
|
|
859
|
+
replaceChildrenFromRenderedMarkup(scratch, renderChartsView(state).main);
|
|
860
|
+
return scratch;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function patchChartsHistoryUi(state) {
|
|
864
|
+
const chartsView = shellRefs.view.querySelector('.charts-view');
|
|
865
|
+
|
|
866
|
+
if (!(chartsView instanceof HTMLElement)) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const currentSidebar = chartsView.querySelector('.charts-view__sidebar');
|
|
871
|
+
const scratch = renderChartsMainIntoScratch(state);
|
|
872
|
+
const nextSidebar = scratch.querySelector('.charts-view__sidebar');
|
|
873
|
+
|
|
874
|
+
if (!currentSidebar && !nextSidebar) {
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!(currentSidebar instanceof HTMLElement) || !(nextSidebar instanceof HTMLElement)) {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
currentSidebar.replaceWith(nextSidebar);
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function renderChartsDetailIntoScratch(state) {
|
|
887
|
+
const scratch = document.createElement('div');
|
|
888
|
+
|
|
889
|
+
replaceChildrenFromRenderedMarkup(scratch, renderChartsDetail(state));
|
|
890
|
+
return scratch;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function replaceChartsDetailSection(detailNode, scratchNode, selector) {
|
|
894
|
+
const currentNode = detailNode.querySelector(selector);
|
|
895
|
+
const nextNode = scratchNode.querySelector(selector);
|
|
896
|
+
|
|
897
|
+
if (!(currentNode instanceof HTMLElement) || !(nextNode instanceof HTMLElement)) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
currentNode.replaceWith(nextNode);
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function patchChartsDetailUi(state, { preserveCharts = false } = {}) {
|
|
786
906
|
const chartsView = shellRefs.view.querySelector('.charts-view');
|
|
787
907
|
const detailNode = chartsView?.querySelector('.charts-view__detail');
|
|
788
908
|
|
|
@@ -797,7 +917,17 @@ function patchChartsDetailUi(state) {
|
|
|
797
917
|
return false;
|
|
798
918
|
}
|
|
799
919
|
|
|
800
|
-
|
|
920
|
+
if (preserveCharts) {
|
|
921
|
+
const scratch = renderChartsDetailIntoScratch(state);
|
|
922
|
+
const patched = replaceChartsDetailSection(detailNode, scratch, '[data-charts-detail-header]');
|
|
923
|
+
|
|
924
|
+
if (!patched) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
replaceChildrenFromRenderedMarkup(detailNode, renderChartsDetail(state));
|
|
929
|
+
}
|
|
930
|
+
|
|
801
931
|
syncChartsHistorySelectionUi(state);
|
|
802
932
|
return true;
|
|
803
933
|
}
|
|
@@ -1223,6 +1353,7 @@ function renderApp(state) {
|
|
|
1223
1353
|
const modalMarkup = renderModal(state);
|
|
1224
1354
|
const toastMarkup = renderToasts(state.toasts);
|
|
1225
1355
|
const chartsHistorySignature = buildChartsHistorySignature(state);
|
|
1356
|
+
const chartsCardSignature = buildChartsCardSignature(state);
|
|
1226
1357
|
const isLockedRoute = [
|
|
1227
1358
|
'editor',
|
|
1228
1359
|
'editorResults',
|
|
@@ -1242,6 +1373,7 @@ function renderApp(state) {
|
|
|
1242
1373
|
const panelChanged = panel !== lastRenderedPanelMarkup;
|
|
1243
1374
|
const modalChanged = modalMarkup !== lastRenderedModalMarkup;
|
|
1244
1375
|
const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
|
|
1376
|
+
const chartsCardsChanged = chartsCardSignature !== lastRenderedChartsCardSignature;
|
|
1245
1377
|
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
1246
1378
|
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
1247
1379
|
const shellMarkupUnchanged =
|
|
@@ -1281,12 +1413,25 @@ function renderApp(state) {
|
|
|
1281
1413
|
}
|
|
1282
1414
|
|
|
1283
1415
|
const canPatchChartsMain =
|
|
1284
|
-
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts'
|
|
1416
|
+
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts';
|
|
1285
1417
|
let mainPatched = false;
|
|
1418
|
+
let preservedChartsDom = false;
|
|
1286
1419
|
|
|
1287
1420
|
if (canPatchChartsMain) {
|
|
1288
|
-
|
|
1289
|
-
|
|
1421
|
+
const historyPatched = !chartsHistoryChanged || patchChartsHistoryUi(state);
|
|
1422
|
+
|
|
1423
|
+
if (historyPatched) {
|
|
1424
|
+
if (chartsCardsChanged) {
|
|
1425
|
+
teardownQueryChartRenderer();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
preservedChartsDom = !chartsCardsChanged;
|
|
1429
|
+
mainPatched = patchChartsDetailUi(state, { preserveCharts: preservedChartsDom });
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (!mainPatched) {
|
|
1433
|
+
preservedChartsDom = false;
|
|
1434
|
+
}
|
|
1290
1435
|
}
|
|
1291
1436
|
|
|
1292
1437
|
if (mainChanged) {
|
|
@@ -1382,6 +1527,7 @@ function renderApp(state) {
|
|
|
1382
1527
|
lastRenderedModalMarkup = modalMarkup;
|
|
1383
1528
|
lastRenderedToastMarkup = toastMarkup;
|
|
1384
1529
|
lastRenderedChartsHistorySignature = chartsHistorySignature;
|
|
1530
|
+
lastRenderedChartsCardSignature = chartsCardSignature;
|
|
1385
1531
|
lastRenderedPanelOpen = panelOpen;
|
|
1386
1532
|
lastRenderedLockedRoute = isLockedRoute;
|
|
1387
1533
|
|
|
@@ -1391,7 +1537,7 @@ function renderApp(state) {
|
|
|
1391
1537
|
});
|
|
1392
1538
|
}
|
|
1393
1539
|
|
|
1394
|
-
if (state.route.name === 'charts') {
|
|
1540
|
+
if (state.route.name === 'charts' && !preservedChartsDom) {
|
|
1395
1541
|
mountQueryChartRenderer(state);
|
|
1396
1542
|
}
|
|
1397
1543
|
}
|
|
@@ -2091,6 +2237,35 @@ async function handleAction(actionNode) {
|
|
|
2091
2237
|
case 'refresh-view':
|
|
2092
2238
|
await refreshCurrentRoute();
|
|
2093
2239
|
return;
|
|
2240
|
+
case 'set-settings-section':
|
|
2241
|
+
setSettingsSection(actionNode.dataset.section);
|
|
2242
|
+
return;
|
|
2243
|
+
case 'check-app-version':
|
|
2244
|
+
await checkSettingsAppVersion();
|
|
2245
|
+
return;
|
|
2246
|
+
case 'open-delete-api-token-modal':
|
|
2247
|
+
openDeleteSettingsApiTokenModal(actionNode.dataset.tokenId);
|
|
2248
|
+
return;
|
|
2249
|
+
case 'copy-created-api-token': {
|
|
2250
|
+
const tokenInput = document.querySelector('[data-created-api-token]');
|
|
2251
|
+
const token = tokenInput instanceof HTMLInputElement ? tokenInput.value : '';
|
|
2252
|
+
|
|
2253
|
+
if (token && navigator.clipboard?.writeText) {
|
|
2254
|
+
await navigator.clipboard.writeText(token);
|
|
2255
|
+
showToast('API token copied.', 'success');
|
|
2256
|
+
}
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
case 'copy-database-id': {
|
|
2260
|
+
const databaseIdNode = document.querySelector('[data-database-id]');
|
|
2261
|
+
const databaseId = databaseIdNode?.textContent?.trim() ?? '';
|
|
2262
|
+
|
|
2263
|
+
if (databaseId && navigator.clipboard?.writeText) {
|
|
2264
|
+
await navigator.clipboard.writeText(databaseId);
|
|
2265
|
+
showToast('Database ID copied.', 'success');
|
|
2266
|
+
}
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2094
2269
|
case 'open-row-editor-url':
|
|
2095
2270
|
openRowEditorUrl(actionNode);
|
|
2096
2271
|
return;
|
|
@@ -2181,6 +2356,33 @@ async function handleAction(actionNode) {
|
|
|
2181
2356
|
case 'edit-connection':
|
|
2182
2357
|
openEditConnectionModal(actionNode.dataset.connectionId);
|
|
2183
2358
|
return;
|
|
2359
|
+
case 'choose-open-database-path': {
|
|
2360
|
+
const labelNode = actionNode.querySelector('[data-open-database-path-button-label]');
|
|
2361
|
+
|
|
2362
|
+
actionNode.setAttribute('disabled', '');
|
|
2363
|
+
if (labelNode) {
|
|
2364
|
+
labelNode.textContent = 'Choosing...';
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
const selectedPath = await chooseOpenDatabasePath();
|
|
2368
|
+
const pathInput = document.querySelector(
|
|
2369
|
+
'[data-form="open-connection"] [data-open-database-path]',
|
|
2370
|
+
);
|
|
2371
|
+
|
|
2372
|
+
if (selectedPath && pathInput instanceof HTMLInputElement) {
|
|
2373
|
+
pathInput.value = selectedPath;
|
|
2374
|
+
pathInput.focus({ preventScroll: true });
|
|
2375
|
+
pathInput.setSelectionRange(pathInput.value.length, pathInput.value.length);
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
if (actionNode.isConnected) {
|
|
2379
|
+
actionNode.removeAttribute('disabled');
|
|
2380
|
+
if (labelNode) {
|
|
2381
|
+
labelNode.textContent = 'Browse...';
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2184
2386
|
case 'choose-create-database-path': {
|
|
2185
2387
|
const labelNode = actionNode.querySelector('[data-create-database-path-button-label]');
|
|
2186
2388
|
|
|
@@ -2329,7 +2531,12 @@ async function handleAction(actionNode) {
|
|
|
2329
2531
|
await loadMoreQueryHistory();
|
|
2330
2532
|
return;
|
|
2331
2533
|
case 'open-query-history':
|
|
2332
|
-
if (
|
|
2534
|
+
if (
|
|
2535
|
+
actionNode.dataset.historyId &&
|
|
2536
|
+
openQueryHistoryInEditor(actionNode.dataset.historyId, {
|
|
2537
|
+
notify: getState().route.name !== 'charts',
|
|
2538
|
+
})
|
|
2539
|
+
) {
|
|
2333
2540
|
router.navigate('/editor');
|
|
2334
2541
|
}
|
|
2335
2542
|
return;
|
|
@@ -2362,6 +2569,17 @@ async function handleAction(actionNode) {
|
|
|
2362
2569
|
}
|
|
2363
2570
|
}
|
|
2364
2571
|
return;
|
|
2572
|
+
case 'open-charts-query-detail':
|
|
2573
|
+
if (actionNode.dataset.historyId) {
|
|
2574
|
+
setChartsDetailPanelVisibility(true);
|
|
2575
|
+
if (getState().route.params?.historyId !== actionNode.dataset.historyId) {
|
|
2576
|
+
router.navigate(`/charts/${encodeURIComponent(actionNode.dataset.historyId)}`);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return;
|
|
2580
|
+
case 'close-charts-query-detail':
|
|
2581
|
+
setChartsDetailPanelVisibility(false);
|
|
2582
|
+
return;
|
|
2365
2583
|
case 'toggle-charts-sql-panel':
|
|
2366
2584
|
toggleChartsSqlPanel();
|
|
2367
2585
|
return;
|
|
@@ -2924,6 +3142,11 @@ document.addEventListener('input', event => {
|
|
|
2924
3142
|
return;
|
|
2925
3143
|
}
|
|
2926
3144
|
|
|
3145
|
+
if (bindNode.dataset.bind === 'charts-history-search') {
|
|
3146
|
+
setChartsHistorySearchInput(bindNode.value);
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
|
|
2927
3150
|
if (bindNode.dataset.bind === 'query-chart-draft:name') {
|
|
2928
3151
|
updateCurrentQueryChartDraftField('name', bindNode.value);
|
|
2929
3152
|
}
|
|
@@ -3116,6 +3339,9 @@ document.addEventListener('submit', async event => {
|
|
|
3116
3339
|
const formData = submitter ? new FormData(form, submitter) : new FormData(form);
|
|
3117
3340
|
|
|
3118
3341
|
switch (form.dataset.form) {
|
|
3342
|
+
case 'create-api-token':
|
|
3343
|
+
await createSettingsApiToken(String(formData.get('name') ?? ''));
|
|
3344
|
+
return;
|
|
3119
3345
|
case 'open-connection': {
|
|
3120
3346
|
resetStructureGraphForDatabaseChange();
|
|
3121
3347
|
const connection = await submitOpenConnection({
|
|
@@ -3206,6 +3432,9 @@ document.addEventListener('submit', async event => {
|
|
|
3206
3432
|
}
|
|
3207
3433
|
return;
|
|
3208
3434
|
}
|
|
3435
|
+
case 'delete-api-token-confirm':
|
|
3436
|
+
await submitDeleteSettingsApiTokenConfirmation();
|
|
3437
|
+
return;
|
|
3209
3438
|
case 'document-insert-table': {
|
|
3210
3439
|
const inserted = await submitDocumentInsertTable();
|
|
3211
3440
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { escapeHtml } from '../utils/format.js';
|
|
2
|
+
|
|
3
|
+
export const STANDARD_TEXT_INPUT_CLASS =
|
|
4
|
+
'control-input w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors placeholder:text-on-surface-variant/35 focus:border-primary-container';
|
|
5
|
+
|
|
6
|
+
export function renderTextInput({
|
|
7
|
+
className = '',
|
|
8
|
+
dataAttributes = {},
|
|
9
|
+
disabled = false,
|
|
10
|
+
maxlength = null,
|
|
11
|
+
name = '',
|
|
12
|
+
placeholder = '',
|
|
13
|
+
readonly = false,
|
|
14
|
+
spellcheck = null,
|
|
15
|
+
type = 'text',
|
|
16
|
+
value = '',
|
|
17
|
+
} = {}) {
|
|
18
|
+
const attributes = [
|
|
19
|
+
`class="${escapeHtml([STANDARD_TEXT_INPUT_CLASS, className].filter(Boolean).join(' '))}"`,
|
|
20
|
+
name ? `name="${escapeHtml(name)}"` : '',
|
|
21
|
+
placeholder ? `placeholder="${escapeHtml(placeholder)}"` : '',
|
|
22
|
+
`type="${escapeHtml(type)}"`,
|
|
23
|
+
`value="${escapeHtml(value)}"`,
|
|
24
|
+
Number.isInteger(maxlength) ? `maxlength="${maxlength}"` : '',
|
|
25
|
+
disabled ? 'disabled' : '',
|
|
26
|
+
readonly ? 'readonly' : '',
|
|
27
|
+
typeof spellcheck === 'boolean' ? `spellcheck="${spellcheck}"` : '',
|
|
28
|
+
...Object.entries(dataAttributes).map(([name, attributeValue]) => {
|
|
29
|
+
const attributeName = `data-${String(name).replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`;
|
|
30
|
+
|
|
31
|
+
return attributeValue === true
|
|
32
|
+
? attributeName
|
|
33
|
+
: `${attributeName}="${escapeHtml(attributeValue)}"`;
|
|
34
|
+
}),
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
|
|
37
|
+
return `<input ${attributes.join(' ')} />`;
|
|
38
|
+
}
|