sqlite-hub 0.2.0 → 0.3.2
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/.github/funding.yml +2 -0
- package/LICENSE +21 -0
- package/README.md +57 -25
- package/assets/mockups/graph_visualize.png +0 -0
- package/changelog.md +12 -1
- package/index.html +3 -0
- package/js/api.js +21 -0
- package/js/app.js +54 -1
- package/js/components/modal.js +74 -0
- package/js/components/queryEditor.js +8 -0
- package/js/components/rowEditorPanel.js +63 -33
- package/js/components/structureGraph.js +925 -0
- package/js/lib/cytoscapeRuntime.js +16 -0
- package/js/store.js +259 -1
- package/js/views/connections.js +18 -3
- package/js/views/data.js +17 -0
- package/js/views/editor.js +5 -0
- package/js/views/structure.js +239 -117
- package/package.json +5 -2
- package/publish_brew.sh +22 -0
- package/server/routes/connections.js +15 -1
- package/server/routes/data.js +14 -0
- package/server/routes/export.js +26 -6
- package/server/server.js +38 -1
- package/server/services/sqlite/backupService.js +112 -0
- package/server/services/sqlite/connectionManager.js +15 -0
- package/server/services/sqlite/dataBrowserService.js +30 -0
- package/server/services/sqlite/exportService.js +46 -1
- package/server/services/sqlite/structureService.js +26 -0
- package/server/services/storage/appStateStore.js +7 -2
- package/styles/structure-graph.css +414 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import cytoscape from "/vendor/cytoscape/dist/cytoscape.esm.min.mjs";
|
|
2
|
+
|
|
3
|
+
let elkRegistered = false;
|
|
4
|
+
|
|
5
|
+
export function getCytoscape() {
|
|
6
|
+
if (!elkRegistered) {
|
|
7
|
+
if (typeof window.cytoscapeElk !== "function") {
|
|
8
|
+
throw new Error("cytoscape-elk is not available on window.");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
cytoscape.use(window.cytoscapeElk);
|
|
12
|
+
elkRegistered = true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return cytoscape;
|
|
16
|
+
}
|
package/js/store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as api from "./api.js";
|
|
2
|
-
import { inferStatusTone } from "./utils/format.js";
|
|
2
|
+
import { formatCellValue, inferStatusTone, truncateMiddle } from "./utils/format.js";
|
|
3
3
|
|
|
4
4
|
const listeners = new Set();
|
|
5
5
|
const DEFAULT_SETTINGS = {
|
|
@@ -24,6 +24,7 @@ const state = {
|
|
|
24
24
|
recent: [],
|
|
25
25
|
active: null,
|
|
26
26
|
loading: false,
|
|
27
|
+
backupLoading: false,
|
|
27
28
|
error: null,
|
|
28
29
|
},
|
|
29
30
|
settings: {
|
|
@@ -44,9 +45,11 @@ const state = {
|
|
|
44
45
|
loading: false,
|
|
45
46
|
tableLoading: false,
|
|
46
47
|
saving: false,
|
|
48
|
+
deleting: false,
|
|
47
49
|
page: 1,
|
|
48
50
|
pageSize: 50,
|
|
49
51
|
selectedRowIndex: null,
|
|
52
|
+
exportLoading: false,
|
|
50
53
|
error: null,
|
|
51
54
|
saveError: null,
|
|
52
55
|
},
|
|
@@ -62,6 +65,7 @@ const state = {
|
|
|
62
65
|
exportLoading: false,
|
|
63
66
|
selectedRowIndex: null,
|
|
64
67
|
saving: false,
|
|
68
|
+
deleting: false,
|
|
65
69
|
saveError: null,
|
|
66
70
|
},
|
|
67
71
|
structure: {
|
|
@@ -138,6 +142,32 @@ function getCurrentStructureEntry(snapshot = state) {
|
|
|
138
142
|
return entries.find((entry) => entry.name === snapshot.structure.selectedName) ?? null;
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
function buildDeleteRowPreview(fields = []) {
|
|
146
|
+
return fields
|
|
147
|
+
.filter((field) => field && field.label)
|
|
148
|
+
.slice(0, 8)
|
|
149
|
+
.map((field) => {
|
|
150
|
+
const fullValue = formatCellValue(field.value);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
label: String(field.label),
|
|
154
|
+
value: truncateMiddle(fullValue, 96),
|
|
155
|
+
fullValue,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildFallbackDeleteRowPreview(row) {
|
|
161
|
+
return buildDeleteRowPreview(
|
|
162
|
+
Object.entries(row ?? {})
|
|
163
|
+
.filter(([key]) => key !== "__identity")
|
|
164
|
+
.map(([key, value]) => ({
|
|
165
|
+
label: key,
|
|
166
|
+
value,
|
|
167
|
+
}))
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
141
171
|
function clearRouteSlices() {
|
|
142
172
|
state.overview.error = null;
|
|
143
173
|
state.dataBrowser.error = null;
|
|
@@ -159,6 +189,7 @@ function setMissingDatabaseState() {
|
|
|
159
189
|
state.dataBrowser.table = null;
|
|
160
190
|
state.dataBrowser.page = 1;
|
|
161
191
|
state.dataBrowser.selectedRowIndex = null;
|
|
192
|
+
state.dataBrowser.exportLoading = false;
|
|
162
193
|
state.dataBrowser.error = error;
|
|
163
194
|
state.dataBrowser.saveError = null;
|
|
164
195
|
|
|
@@ -480,6 +511,7 @@ function invalidateDatabaseCaches() {
|
|
|
480
511
|
state.dataBrowser.table = null;
|
|
481
512
|
state.dataBrowser.page = 1;
|
|
482
513
|
state.dataBrowser.selectedRowIndex = null;
|
|
514
|
+
state.dataBrowser.exportLoading = false;
|
|
483
515
|
state.dataBrowser.error = null;
|
|
484
516
|
state.dataBrowser.saveError = null;
|
|
485
517
|
state.structure.data = null;
|
|
@@ -617,6 +649,69 @@ export function openEditConnectionModal(id) {
|
|
|
617
649
|
emitChange();
|
|
618
650
|
}
|
|
619
651
|
|
|
652
|
+
export function openDeleteDataRowModal(rowIndex) {
|
|
653
|
+
const numericIndex = Number(rowIndex);
|
|
654
|
+
const tableName = state.dataBrowser.selectedTable;
|
|
655
|
+
const row = state.dataBrowser.table?.rows?.[numericIndex];
|
|
656
|
+
const rowPreview = buildDeleteRowPreview(
|
|
657
|
+
(state.dataBrowser.table?.columnMeta ?? [])
|
|
658
|
+
.filter((column) => column.visible)
|
|
659
|
+
.map((column) => ({
|
|
660
|
+
label: column.name,
|
|
661
|
+
value: row[column.name],
|
|
662
|
+
}))
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
if (!tableName || !row?.__identity) {
|
|
666
|
+
pushToast("The selected row could not be loaded.", "alert");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
state.modal = {
|
|
671
|
+
kind: "delete-row",
|
|
672
|
+
target: "data",
|
|
673
|
+
rowIndex: numericIndex,
|
|
674
|
+
tableName,
|
|
675
|
+
rowLabel: `row ${numericIndex + 1}`,
|
|
676
|
+
rowPreview: rowPreview.length ? rowPreview : buildFallbackDeleteRowPreview(row),
|
|
677
|
+
error: null,
|
|
678
|
+
submitting: false,
|
|
679
|
+
};
|
|
680
|
+
emitChange();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
export function openDeleteEditorRowModal(rowIndex) {
|
|
684
|
+
const numericIndex = Number(rowIndex);
|
|
685
|
+
const tableName = state.editor.result?.editing?.tableName ?? null;
|
|
686
|
+
const row = state.editor.result?.rows?.[numericIndex];
|
|
687
|
+
const columns = state.editor.result?.editing?.columns ?? [];
|
|
688
|
+
const rowPreview = buildDeleteRowPreview(
|
|
689
|
+
columns
|
|
690
|
+
.filter((column) => column.visible !== false)
|
|
691
|
+
.map((column) => ({
|
|
692
|
+
label: column.sourceColumn || column.resultName,
|
|
693
|
+
value: row[column.resultName],
|
|
694
|
+
}))
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
if (!tableName || !row?.__identity || !canEditQueryResult()) {
|
|
698
|
+
pushToast("The selected query result row could not be loaded.", "alert");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
state.modal = {
|
|
703
|
+
kind: "delete-row",
|
|
704
|
+
target: "editor",
|
|
705
|
+
rowIndex: numericIndex,
|
|
706
|
+
tableName,
|
|
707
|
+
rowLabel: `query row ${numericIndex + 1}`,
|
|
708
|
+
rowPreview: rowPreview.length ? rowPreview : buildFallbackDeleteRowPreview(row),
|
|
709
|
+
error: null,
|
|
710
|
+
submitting: false,
|
|
711
|
+
};
|
|
712
|
+
emitChange();
|
|
713
|
+
}
|
|
714
|
+
|
|
620
715
|
export function closeModal() {
|
|
621
716
|
closeModalInternal();
|
|
622
717
|
}
|
|
@@ -743,6 +838,32 @@ export async function removeConnection(id) {
|
|
|
743
838
|
}
|
|
744
839
|
}
|
|
745
840
|
|
|
841
|
+
export async function createActiveConnectionBackup() {
|
|
842
|
+
if (!state.connections.active) {
|
|
843
|
+
pushToast("No active SQLite database selected for backup.", "alert");
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
state.connections.backupLoading = true;
|
|
848
|
+
emitChange();
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
const response = await api.createActiveConnectionBackup();
|
|
852
|
+
await refreshConnectionsState();
|
|
853
|
+
pushToast(response.message || "Backup created.", "success");
|
|
854
|
+
return response.data;
|
|
855
|
+
} catch (error) {
|
|
856
|
+
pushToast(
|
|
857
|
+
normalizeError(error)?.message || "Backup could not be created.",
|
|
858
|
+
"alert"
|
|
859
|
+
);
|
|
860
|
+
return null;
|
|
861
|
+
} finally {
|
|
862
|
+
state.connections.backupLoading = false;
|
|
863
|
+
emitChange();
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
746
867
|
export function setCurrentQuery(query) {
|
|
747
868
|
const nextQuery = String(query ?? "");
|
|
748
869
|
const previousLineCount = Math.max(1, String(state.editor.sqlText || "").split("\n").length);
|
|
@@ -962,6 +1083,51 @@ export async function submitDataRowUpdate(rowIndex, values) {
|
|
|
962
1083
|
}
|
|
963
1084
|
}
|
|
964
1085
|
|
|
1086
|
+
export async function submitDataRowDelete(rowIndex, options = {}) {
|
|
1087
|
+
const numericIndex = Number(rowIndex);
|
|
1088
|
+
const tableName = state.dataBrowser.selectedTable;
|
|
1089
|
+
const row = state.dataBrowser.table?.rows?.[numericIndex];
|
|
1090
|
+
const reportErrorToModal = Boolean(options.reportErrorToModal);
|
|
1091
|
+
|
|
1092
|
+
if (!tableName || !row?.__identity) {
|
|
1093
|
+
pushToast("The selected row could not be loaded.", "alert");
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const shouldStepBackPage =
|
|
1098
|
+
(state.dataBrowser.table?.rows?.length ?? 0) <= 1 && state.dataBrowser.page > 1;
|
|
1099
|
+
|
|
1100
|
+
state.dataBrowser.deleting = true;
|
|
1101
|
+
state.dataBrowser.saveError = null;
|
|
1102
|
+
emitChange();
|
|
1103
|
+
|
|
1104
|
+
try {
|
|
1105
|
+
const response = await api.deleteDataTableRow(tableName, {
|
|
1106
|
+
identity: row.__identity,
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
if (shouldStepBackPage) {
|
|
1110
|
+
state.dataBrowser.page -= 1;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
pushToast(response.message || "Table row deleted.", "success");
|
|
1114
|
+
await loadDataTable(++routeLoadVersion);
|
|
1115
|
+
state.dataBrowser.selectedRowIndex = null;
|
|
1116
|
+
return response.data;
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
if (reportErrorToModal) {
|
|
1119
|
+
withModalError(error);
|
|
1120
|
+
} else {
|
|
1121
|
+
state.dataBrowser.saveError = normalizeError(error);
|
|
1122
|
+
emitChange();
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
} finally {
|
|
1126
|
+
state.dataBrowser.deleting = false;
|
|
1127
|
+
emitChange();
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
965
1131
|
export async function submitEditorRowUpdate(rowIndex, values) {
|
|
966
1132
|
const numericIndex = Number(rowIndex);
|
|
967
1133
|
const result = state.editor.result;
|
|
@@ -1009,6 +1175,73 @@ export async function submitEditorRowUpdate(rowIndex, values) {
|
|
|
1009
1175
|
}
|
|
1010
1176
|
}
|
|
1011
1177
|
|
|
1178
|
+
export async function submitEditorRowDelete(rowIndex, options = {}) {
|
|
1179
|
+
const numericIndex = Number(rowIndex);
|
|
1180
|
+
const result = state.editor.result;
|
|
1181
|
+
const row = result?.rows?.[numericIndex];
|
|
1182
|
+
const tableName = result?.editing?.tableName ?? null;
|
|
1183
|
+
const reportErrorToModal = Boolean(options.reportErrorToModal);
|
|
1184
|
+
|
|
1185
|
+
if (!tableName || !row?.__identity || !canEditQueryResult()) {
|
|
1186
|
+
pushToast("The selected query result row could not be loaded.", "alert");
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
state.editor.deleting = true;
|
|
1191
|
+
state.editor.saveError = null;
|
|
1192
|
+
emitChange();
|
|
1193
|
+
|
|
1194
|
+
try {
|
|
1195
|
+
const response = await api.deleteDataTableRow(tableName, {
|
|
1196
|
+
identity: row.__identity,
|
|
1197
|
+
});
|
|
1198
|
+
const nextRows = [...(result.rows ?? [])];
|
|
1199
|
+
|
|
1200
|
+
nextRows.splice(numericIndex, 1);
|
|
1201
|
+
state.editor.result = {
|
|
1202
|
+
...result,
|
|
1203
|
+
rows: nextRows,
|
|
1204
|
+
};
|
|
1205
|
+
state.editor.selectedRowIndex = null;
|
|
1206
|
+
invalidateDatabaseCaches();
|
|
1207
|
+
pushToast(response.message || "Query result row deleted.", "success");
|
|
1208
|
+
emitChange();
|
|
1209
|
+
return response.data;
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
if (reportErrorToModal) {
|
|
1212
|
+
withModalError(error);
|
|
1213
|
+
} else {
|
|
1214
|
+
state.editor.saveError = normalizeError(error);
|
|
1215
|
+
emitChange();
|
|
1216
|
+
}
|
|
1217
|
+
return null;
|
|
1218
|
+
} finally {
|
|
1219
|
+
state.editor.deleting = false;
|
|
1220
|
+
emitChange();
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
export async function submitDeleteRowConfirmation() {
|
|
1225
|
+
const modal = state.modal;
|
|
1226
|
+
|
|
1227
|
+
if (modal?.kind !== "delete-row") {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
startModalSubmission();
|
|
1232
|
+
|
|
1233
|
+
const result =
|
|
1234
|
+
modal.target === "editor"
|
|
1235
|
+
? await submitEditorRowDelete(modal.rowIndex, { reportErrorToModal: true })
|
|
1236
|
+
: await submitDataRowDelete(modal.rowIndex, { reportErrorToModal: true });
|
|
1237
|
+
|
|
1238
|
+
if (result) {
|
|
1239
|
+
closeModalInternal();
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
return result;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1012
1245
|
export async function exportCurrentQueryCsv() {
|
|
1013
1246
|
state.editor.exportLoading = true;
|
|
1014
1247
|
emitChange();
|
|
@@ -1027,6 +1260,31 @@ export async function exportCurrentQueryCsv() {
|
|
|
1027
1260
|
}
|
|
1028
1261
|
}
|
|
1029
1262
|
|
|
1263
|
+
export async function exportCurrentDataTableCsv() {
|
|
1264
|
+
const tableName = state.dataBrowser.selectedTable;
|
|
1265
|
+
|
|
1266
|
+
if (!tableName) {
|
|
1267
|
+
pushToast("No table selected for export.", "alert");
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
state.dataBrowser.exportLoading = true;
|
|
1272
|
+
emitChange();
|
|
1273
|
+
|
|
1274
|
+
try {
|
|
1275
|
+
await api.downloadTableCsv(tableName);
|
|
1276
|
+
pushToast(`CSV export started for ${tableName}.`, "success");
|
|
1277
|
+
return true;
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
state.dataBrowser.error = normalizeError(error);
|
|
1280
|
+
emitChange();
|
|
1281
|
+
return false;
|
|
1282
|
+
} finally {
|
|
1283
|
+
state.dataBrowser.exportLoading = false;
|
|
1284
|
+
emitChange();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1030
1288
|
export async function refreshCurrentRoute() {
|
|
1031
1289
|
await loadRouteData(state.route);
|
|
1032
1290
|
}
|
package/js/views/connections.js
CHANGED
|
@@ -5,9 +5,11 @@ import { escapeHtml } from "../utils/format.js";
|
|
|
5
5
|
function renderConnectionsActionButton({
|
|
6
6
|
label,
|
|
7
7
|
icon,
|
|
8
|
+
action = "open-modal",
|
|
8
9
|
modal,
|
|
9
10
|
tone = "secondary",
|
|
10
11
|
className = "",
|
|
12
|
+
disabled = false,
|
|
11
13
|
}) {
|
|
12
14
|
const clipPath = "polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%)";
|
|
13
15
|
const toneClassName =
|
|
@@ -19,14 +21,16 @@ function renderConnectionsActionButton({
|
|
|
19
21
|
? "text-base text-on-primary"
|
|
20
22
|
: "text-base text-primary-container/90";
|
|
21
23
|
const clipStyle = `style="--clip-path: ${clipPath};"`;
|
|
24
|
+
const modalAttribute = modal ? `data-modal="${modal}"` : "";
|
|
22
25
|
|
|
23
26
|
return `
|
|
24
27
|
<button
|
|
25
|
-
class="flex h-11 items-center justify-between gap-6 px-5 font-headline text-xs font-bold uppercase tracking-[0.18em] transition-colors ${toneClassName} ${className}"
|
|
26
|
-
data-action="
|
|
27
|
-
|
|
28
|
+
class="flex h-11 items-center justify-between gap-6 px-5 font-headline text-xs font-bold uppercase tracking-[0.18em] transition-colors disabled:cursor-default disabled:opacity-40 ${toneClassName} ${className}"
|
|
29
|
+
data-action="${escapeHtml(action)}"
|
|
30
|
+
${modalAttribute}
|
|
28
31
|
${clipStyle}
|
|
29
32
|
type="button"
|
|
33
|
+
${disabled ? "disabled" : ""}
|
|
30
34
|
>
|
|
31
35
|
<span>${label}</span>
|
|
32
36
|
<span class="material-symbols-outlined ${iconClassName}">${icon}</span>
|
|
@@ -112,6 +116,17 @@ export function renderConnectionsView(state) {
|
|
|
112
116
|
modal: "create-connection",
|
|
113
117
|
className: "min-w-[13rem]",
|
|
114
118
|
})}
|
|
119
|
+
${
|
|
120
|
+
state.connections.active
|
|
121
|
+
? renderConnectionsActionButton({
|
|
122
|
+
label: state.connections.backupLoading ? "Creating Backup..." : "Create Backup",
|
|
123
|
+
icon: "inventory_2",
|
|
124
|
+
action: "create-backup",
|
|
125
|
+
className: "min-w-[13rem]",
|
|
126
|
+
disabled: state.connections.backupLoading,
|
|
127
|
+
})
|
|
128
|
+
: ""
|
|
129
|
+
}
|
|
115
130
|
`;
|
|
116
131
|
|
|
117
132
|
return {
|
package/js/views/data.js
CHANGED
|
@@ -95,6 +95,19 @@ function renderWorkspaceHeader(state) {
|
|
|
95
95
|
</div>
|
|
96
96
|
</div>
|
|
97
97
|
<div class="flex items-center gap-3">
|
|
98
|
+
${
|
|
99
|
+
table
|
|
100
|
+
? `
|
|
101
|
+
<button
|
|
102
|
+
class="border border-outline-variant/20 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-on-surface hover:bg-surface-container-highest"
|
|
103
|
+
data-action="export-data-csv"
|
|
104
|
+
type="button"
|
|
105
|
+
>
|
|
106
|
+
${state.dataBrowser.exportLoading ? "Exporting..." : "Export CSV"}
|
|
107
|
+
</button>
|
|
108
|
+
`
|
|
109
|
+
: ""
|
|
110
|
+
}
|
|
98
111
|
<button
|
|
99
112
|
class="border border-outline-variant/20 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-on-surface hover:bg-surface-container-highest"
|
|
100
113
|
data-action="refresh-view"
|
|
@@ -367,6 +380,10 @@ function renderDataRowEditorPanel(state) {
|
|
|
367
380
|
})),
|
|
368
381
|
saveError: state.dataBrowser.saveError,
|
|
369
382
|
saving: state.dataBrowser.saving,
|
|
383
|
+
deleting: state.dataBrowser.deleting,
|
|
384
|
+
deleteAction: "delete-data-row",
|
|
385
|
+
deleteRowIndex: rowIndex,
|
|
386
|
+
deleteEnabled: Boolean(row.__identity),
|
|
370
387
|
reloadAction: "reload-data-route",
|
|
371
388
|
});
|
|
372
389
|
}
|
package/js/views/editor.js
CHANGED
|
@@ -192,6 +192,10 @@ function renderEditorRowPanel(state) {
|
|
|
192
192
|
})),
|
|
193
193
|
saveError: state.editor.saveError,
|
|
194
194
|
saving: state.editor.saving,
|
|
195
|
+
deleting: state.editor.deleting,
|
|
196
|
+
deleteAction: "delete-editor-row",
|
|
197
|
+
deleteRowIndex: rowIndex,
|
|
198
|
+
deleteEnabled: editingState.enabled && Boolean(row.__identity),
|
|
195
199
|
});
|
|
196
200
|
}
|
|
197
201
|
|
|
@@ -243,6 +247,7 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
|
243
247
|
${renderQueryEditor({
|
|
244
248
|
query: state.editor.sqlText,
|
|
245
249
|
executing: state.editor.executing,
|
|
250
|
+
exporting: state.editor.exportLoading,
|
|
246
251
|
history: state.editor.history,
|
|
247
252
|
historyLoading: state.editor.historyLoading,
|
|
248
253
|
title: connection?.label ?? "SQLite Query Workspace",
|