@adecore/database 0.17.0-beta.2 → 0.18.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 +1 -1
- package/dist/DatabaseProvider.d.ts +4 -3
- package/dist/DatabaseProvider.js +15 -3
- package/dist/KeyIcon.d.ts +4 -0
- package/dist/KeyIcon.js +14 -0
- package/dist/actions.d.ts +12 -0
- package/dist/client-context.d.ts +3 -1
- package/dist/client-context.js +2 -0
- package/dist/code-text.d.ts +1 -0
- package/dist/code-text.js +2 -0
- package/dist/column-keys.d.ts +5 -0
- package/dist/column-keys.js +2 -0
- package/dist/connections/ConnectionManager.d.ts +3 -2
- package/dist/connections/ConnectionManager.js +27 -16
- package/dist/connections/connection-config.js +2 -0
- package/dist/console/QueryConsole.d.ts +8 -3
- package/dist/console/QueryConsole.js +110 -50
- package/dist/console/SqlEditor.d.ts +2 -12
- package/dist/console/SqlEditor.js +29 -18
- package/dist/console/StatementResultView.d.ts +5 -1
- package/dist/console/StatementResultView.js +18 -5
- package/dist/console/TransactionControls.js +3 -6
- package/dist/console/editor-slot.d.ts +18 -0
- package/dist/console/editor-slot.js +1 -0
- package/dist/console/result-keys.d.ts +15 -0
- package/dist/console/result-keys.js +79 -0
- package/dist/designer/ColumnsEditor.js +2 -2
- package/dist/designer/DesignerView.d.ts +3 -2
- package/dist/designer/DesignerView.js +5 -4
- package/dist/designer/SqlPreview.js +2 -1
- package/dist/designer/TableDesigner.d.ts +3 -2
- package/dist/designer/TableDesigner.js +2 -2
- package/dist/explorer/DatabaseExplorer.d.ts +4 -1
- package/dist/explorer/DatabaseExplorer.js +79 -23
- package/dist/explorer/RowMenu.js +2 -2
- package/dist/explorer/expansion.js +2 -2
- package/dist/explorer/folders.d.ts +16 -0
- package/dist/explorer/folders.js +1 -0
- package/dist/explorer/tree.d.ts +55 -4
- package/dist/explorer/tree.js +137 -23
- package/dist/grid/CellEditor.js +3 -1
- package/dist/grid/DataGrid.d.ts +2 -2
- package/dist/grid/DataGrid.js +61 -59
- package/dist/grid/EnumPicker.js +2 -1
- package/dist/grid/GridHeaderCell.d.ts +1 -2
- package/dist/grid/GridHeaderCell.js +12 -5
- package/dist/grid/GridRowView.d.ts +49 -0
- package/dist/grid/GridRowView.js +51 -0
- package/dist/grid/RecordView.d.ts +4 -2
- package/dist/grid/RecordView.js +71 -22
- package/dist/grid/ValueDock.d.ts +3 -1
- package/dist/grid/ValueDock.js +9 -3
- package/dist/grid/cell-id.d.ts +1 -0
- package/dist/grid/cell-id.js +2 -0
- package/dist/grid/layout.d.ts +3 -2
- package/dist/grid/layout.js +15 -6
- package/dist/grid/types.d.ts +8 -1
- package/dist/grid/types.js +4 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/locales/en.json +32 -15
- package/dist/locales/nl.json +32 -15
- package/dist/protocol/index.d.ts +1 -1
- package/dist/protocol/messages.d.ts +1 -1
- package/dist/protocol/messages.js +1 -1
- package/dist/protocol/schema.d.ts +17 -0
- package/dist/structure/StructureView.d.ts +5 -3
- package/dist/structure/StructureView.js +8 -6
- package/dist/table/CommandField.js +2 -2
- package/dist/table/TableStatusBar.js +2 -2
- package/dist/table/TableToolbar.d.ts +3 -4
- package/dist/table/TableToolbar.js +4 -3
- package/dist/table/TableView.d.ts +4 -2
- package/dist/table/TableView.js +63 -91
- package/dist/table/grid-rows.js +4 -1
- package/dist/table/useTableTransfer.d.ts +3 -1
- package/dist/table/useTableTransfer.js +19 -5
- package/dist/testing/fake.d.ts +3 -1
- package/dist/testing/fake.js +7 -1
- package/dist/value/ValueEditor.d.ts +9 -0
- package/dist/value/ValueEditor.js +102 -0
- package/dist/value/ValuePanel.d.ts +1 -1
- package/dist/value/ValuePanel.js +3 -94
- package/dist/workbench/DatabaseWorkbench.js +4 -7
- package/package.json +6 -6
- package/src/DatabaseProvider.tsx +24 -5
- package/src/KeyIcon.tsx +15 -0
- package/src/actions.ts +28 -2
- package/src/client-context.ts +5 -1
- package/src/code-text.ts +2 -0
- package/src/column-keys.ts +8 -0
- package/src/connections/ConnectionManager.tsx +98 -68
- package/src/connections/connection-config.ts +2 -0
- package/src/console/QueryConsole.tsx +251 -150
- package/src/console/SqlEditor.tsx +33 -37
- package/src/console/StatementResultView.tsx +61 -27
- package/src/console/TransactionControls.tsx +8 -11
- package/src/console/editor-slot.ts +27 -0
- package/src/console/result-keys.ts +109 -0
- package/src/designer/ColumnsEditor.tsx +3 -3
- package/src/designer/DesignerView.tsx +14 -3
- package/src/designer/SqlPreview.tsx +2 -1
- package/src/designer/TableDesigner.tsx +5 -2
- package/src/explorer/DatabaseExplorer.tsx +170 -26
- package/src/explorer/RowMenu.tsx +2 -2
- package/src/explorer/expansion.ts +4 -4
- package/src/explorer/folders.ts +25 -0
- package/src/explorer/tree.ts +219 -28
- package/src/grid/CellEditor.tsx +3 -2
- package/src/grid/DataGrid.tsx +114 -176
- package/src/grid/EnumPicker.tsx +2 -1
- package/src/grid/GridHeaderCell.tsx +14 -9
- package/src/grid/GridRowView.tsx +184 -0
- package/src/grid/RecordView.tsx +188 -75
- package/src/grid/ValueDock.tsx +12 -3
- package/src/grid/cell-id.ts +2 -0
- package/src/grid/layout.ts +16 -6
- package/src/grid/types.ts +15 -3
- package/src/index.ts +5 -1
- package/src/locales/en.json +32 -15
- package/src/locales/nl.json +32 -15
- package/src/protocol/index.ts +13 -1
- package/src/protocol/messages.ts +1 -1
- package/src/protocol/schema.ts +28 -0
- package/src/structure/StructureView.tsx +16 -6
- package/src/table/CommandField.tsx +2 -2
- package/src/table/TableStatusBar.tsx +3 -3
- package/src/table/TableToolbar.tsx +107 -92
- package/src/table/TableView.tsx +95 -132
- package/src/table/grid-rows.ts +5 -3
- package/src/table/useTableTransfer.ts +21 -6
- package/src/testing/fake.ts +18 -1
- package/src/value/ValueEditor.tsx +213 -0
- package/src/value/ValuePanel.tsx +4 -206
- package/src/workbench/DatabaseWorkbench.tsx +9 -10
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import { Check, CircleAlert, History, Play, Square } from 'lucide-react';
|
|
4
|
+
import { Check, CircleAlert, History, Play, Square, X } from 'lucide-react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { Banner, Button, Icon, messageOf, PromptDialog, Spinner, Tabs, Tooltip } from '@adecore/ui';
|
|
6
|
+
import { Banner, Button, ColumnResizeHandle, Icon, IconButton, messageOf, PromptDialog, Spinner, Tabs, Tooltip, useColumnResize } from '@adecore/ui';
|
|
7
7
|
import { formatNumber } from '@adecore/ui/format';
|
|
8
8
|
import { DatabaseRequestError } from '../client/types.js';
|
|
9
|
-
import { useDatabaseClient, useDatabaseFiles } from '../client-context.js';
|
|
9
|
+
import { useDatabaseAction, useDatabaseClient, useDatabaseFiles, useDatabaseNotice, useDatabaseStorage } from '../client-context.js';
|
|
10
10
|
import { splitStatements, statementAt } from '../sql-split.js';
|
|
11
11
|
import { DestructiveDialog } from './DestructiveDialog.js';
|
|
12
12
|
import { findDestructive } from './destructive.js';
|
|
@@ -15,6 +15,7 @@ import { statementLabel } from './labels.js';
|
|
|
15
15
|
import { isPageable, outcomeOf } from './outcome.js';
|
|
16
16
|
import { SchemaPicker } from './SchemaPicker.js';
|
|
17
17
|
import { orderSchemas } from './schemas.js';
|
|
18
|
+
import { useResultKeys } from './result-keys.js';
|
|
18
19
|
import { RUN_ALL_SHORTCUT, RUN_SHORTCUT } from './shortcuts.js';
|
|
19
20
|
import { SqlEditor } from './SqlEditor.js';
|
|
20
21
|
import { StatementResultView } from './StatementResultView.js';
|
|
@@ -22,35 +23,47 @@ import { TransactionControls } from './TransactionControls.js';
|
|
|
22
23
|
import { useConsoleHistory } from './useConsoleHistory.js';
|
|
23
24
|
/* Rows in the first page of a result, and in every page after it. */
|
|
24
25
|
const PAGE_SIZE = 500;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
/* Where the height of the results is kept, in pixels, for every console. */
|
|
27
|
+
export const RESULTS_HEIGHT_KEY = 'database:console-results';
|
|
28
|
+
/* The results and the editor never shrink below these; the results start at half the console. */
|
|
29
|
+
const MIN_RESULTS_HEIGHT = 96;
|
|
30
|
+
const MIN_EDITOR_HEIGHT = 120;
|
|
31
|
+
const parseHeight = (raw) => {
|
|
32
|
+
try {
|
|
33
|
+
const value = JSON.parse(raw ?? 'null');
|
|
34
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= MIN_RESULTS_HEIGHT ? Math.round(value) : null;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
33
38
|
}
|
|
34
39
|
};
|
|
40
|
+
/* The app's editor as an element of its own, so it gets its props the way `SqlEditor` does and not during the console's render. */
|
|
41
|
+
function AppEditor({ render, ...editor }) {
|
|
42
|
+
return render(editor);
|
|
43
|
+
}
|
|
35
44
|
/* Type SQL, run it, and read what each statement did. Several statements give a tab each. */
|
|
36
|
-
export function QueryConsole({ connection, schema, value, defaultValue = '', onValueChange, defaultHistoryOpen = false, autoFocus = false, className, ref }) {
|
|
45
|
+
export function QueryConsole({ connection, schema, onSchemaChange, value, defaultValue = '', onValueChange, defaultHistoryOpen = false, autoFocus = false, renderEditor, toolbarEnd, className, ref }) {
|
|
37
46
|
const { t } = useTranslation('database');
|
|
38
47
|
const client = useDatabaseClient();
|
|
48
|
+
const act = useDatabaseAction();
|
|
49
|
+
const onNotice = useDatabaseNotice();
|
|
39
50
|
const files = useDatabaseFiles();
|
|
51
|
+
const storage = useDatabaseStorage();
|
|
40
52
|
// A channel of its own, so a transaction a person starts here stays out of the table views and the designer.
|
|
41
53
|
const channel = `console:${useId()}`;
|
|
42
|
-
const session = useMemo(() => client.session(connection, channel), [client, connection, channel]);
|
|
43
|
-
const engine = connection
|
|
54
|
+
const session = useMemo(() => (connection === undefined ? null : client.session(connection, channel)), [client, connection, channel]);
|
|
55
|
+
const engine = connection?.config.engine;
|
|
44
56
|
useEffect(() => () => {
|
|
45
|
-
void session
|
|
57
|
+
void session?.close();
|
|
46
58
|
}, [session]);
|
|
47
|
-
|
|
59
|
+
// Nothing is recorded without a connection, so the key of none is never written.
|
|
60
|
+
const history = useConsoleHistory(connection?.id ?? '');
|
|
48
61
|
const running = useRef(null);
|
|
49
62
|
const generation = useRef(0);
|
|
50
|
-
const
|
|
63
|
+
const editorHandle = useRef(null);
|
|
64
|
+
const resultsPane = useRef(null);
|
|
51
65
|
const [own, setOwn] = useState(defaultValue);
|
|
52
66
|
const [run, setRun] = useState({ status: 'idle' });
|
|
53
|
-
const [ran, setRan] = useState(null);
|
|
54
67
|
const [tab, setTab] = useState(0);
|
|
55
68
|
const [pages, setPages] = useState({});
|
|
56
69
|
const [paging, setPaging] = useState(false);
|
|
@@ -65,25 +78,34 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
65
78
|
const [seededSchema, setSeededSchema] = useState(schema);
|
|
66
79
|
const [pickedSchema, setPickedSchema] = useState(undefined);
|
|
67
80
|
const [exporting, setExporting] = useState(false);
|
|
81
|
+
// What the console shows itself when the app takes no notices.
|
|
68
82
|
const [notice, setNotice] = useState(null);
|
|
83
|
+
/* Closed until the first run, so the editor has the whole height before anything ran. */
|
|
84
|
+
const [resultsOpen, setResultsOpen] = useState(false);
|
|
85
|
+
const [resultsHeight, setResultsHeight] = useState(() => parseHeight(storage?.get(RESULTS_HEIGHT_KEY)));
|
|
86
|
+
const { startResize } = useColumnResize(resultsPane, {
|
|
87
|
+
size: resultsHeight ?? MIN_RESULTS_HEIGHT,
|
|
88
|
+
min: MIN_RESULTS_HEIGHT,
|
|
89
|
+
from: 'bottom',
|
|
90
|
+
max: () => (resultsPane.current?.parentElement?.clientHeight ?? 0) - MIN_EDITOR_HEIGHT,
|
|
91
|
+
onSize: setResultsHeight
|
|
92
|
+
});
|
|
69
93
|
const sql = value ?? own;
|
|
70
94
|
const blank = sql.trim() === '';
|
|
71
95
|
const busy = run.status === 'running' || settling;
|
|
72
|
-
const activeSchema = pickedSchema ?? schema;
|
|
96
|
+
const activeSchema = onSchemaChange === undefined ? (pickedSchema ?? schema) : schema;
|
|
73
97
|
if (seededSchema !== schema) {
|
|
74
98
|
setSeededSchema(schema);
|
|
75
99
|
setPickedSchema(undefined);
|
|
76
100
|
}
|
|
77
101
|
useEffect(() => () => running.current?.abort(), []);
|
|
78
102
|
useEffect(() => {
|
|
79
|
-
if (
|
|
80
|
-
|
|
103
|
+
if (resultsHeight !== null) {
|
|
104
|
+
storage?.set(RESULTS_HEIGHT_KEY, JSON.stringify(resultsHeight));
|
|
81
105
|
}
|
|
82
|
-
|
|
83
|
-
// eslint-disable-next-line react/exhaustive-deps
|
|
84
|
-
}, []);
|
|
106
|
+
}, [storage, resultsHeight]);
|
|
85
107
|
useEffect(() => {
|
|
86
|
-
if (engine !== 'mysql') {
|
|
108
|
+
if (session === null || engine !== 'mysql') {
|
|
87
109
|
return undefined;
|
|
88
110
|
}
|
|
89
111
|
const controller = new AbortController();
|
|
@@ -93,35 +115,41 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
93
115
|
.catch(() => undefined);
|
|
94
116
|
return () => controller.abort();
|
|
95
117
|
}, [engine, session]);
|
|
118
|
+
const notify = (next) => {
|
|
119
|
+
if (onNotice === undefined) {
|
|
120
|
+
setNotice(next);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
onNotice(next);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
96
126
|
const changeSql = (next) => {
|
|
97
127
|
if (value === undefined) {
|
|
98
128
|
setOwn(next);
|
|
99
129
|
}
|
|
100
130
|
onValueChange?.(next);
|
|
101
131
|
};
|
|
132
|
+
/* The SQL a run of the scope sends, or null when there is nothing to run. */
|
|
102
133
|
const targetOf = (scope) => {
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return { sql, ran: statements.length > 1 ? { kind: 'all', count: statements.length } : { kind: 'statement', label: statementLabel(only.text) } };
|
|
134
|
+
if (engine === undefined) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const selection = editorHandle.current?.selection();
|
|
138
|
+
if (scope === 'all' || selection === undefined) {
|
|
139
|
+
return splitStatements(sql, engine).length === 0 ? null : sql;
|
|
110
140
|
}
|
|
111
|
-
const start
|
|
112
|
-
const end = editor.current?.selectionEnd ?? 0;
|
|
141
|
+
const { start, end } = selection;
|
|
113
142
|
const selected = sql.slice(start, end);
|
|
114
143
|
if (splitStatements(selected, engine).length > 0) {
|
|
115
|
-
return
|
|
144
|
+
return selected;
|
|
116
145
|
}
|
|
117
|
-
|
|
118
|
-
return statement === null ? null : { sql: statement.text, ran: { kind: 'statement', label: statementLabel(statement.text) } };
|
|
146
|
+
return statementAt(sql, start, engine)?.text ?? null;
|
|
119
147
|
};
|
|
120
148
|
const requestRun = (target) => {
|
|
121
|
-
if (target === null || busy) {
|
|
149
|
+
if (target === null || busy || engine === undefined) {
|
|
122
150
|
return;
|
|
123
151
|
}
|
|
124
|
-
const statements = findDestructive(target
|
|
152
|
+
const statements = findDestructive(target, engine);
|
|
125
153
|
if (statements.length > 0) {
|
|
126
154
|
setConfirming({ target, statements });
|
|
127
155
|
return;
|
|
@@ -129,18 +157,21 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
129
157
|
void execute(target);
|
|
130
158
|
};
|
|
131
159
|
const execute = async (target) => {
|
|
160
|
+
if (session === null || connection === undefined) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
132
163
|
const controller = new AbortController();
|
|
133
164
|
const mine = ++generation.current;
|
|
134
165
|
running.current = controller;
|
|
166
|
+
setResultsOpen(true);
|
|
135
167
|
setRun({ status: 'running' });
|
|
136
|
-
setRan(target.ran);
|
|
137
168
|
setNotice(null);
|
|
138
|
-
const record = (outcome) => history.record({ sql: target
|
|
169
|
+
const record = (outcome) => history.record({ sql: target, at: Date.now(), connection: connection.name, ...outcome });
|
|
139
170
|
try {
|
|
140
171
|
if (mode === 'manual' && !inTransaction) {
|
|
141
172
|
setInTransaction(await session.transaction('begin', { signal: controller.signal }));
|
|
142
173
|
}
|
|
143
|
-
const executed = await session.execute(target
|
|
174
|
+
const executed = await session.execute(target, { schema: activeSchema, limit: PAGE_SIZE, signal: controller.signal });
|
|
144
175
|
if (controller.signal.aborted) {
|
|
145
176
|
setRun({ status: 'cancelled' });
|
|
146
177
|
return;
|
|
@@ -169,17 +200,24 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
169
200
|
const pickHistory = (entry, runIt) => {
|
|
170
201
|
changeSql(entry.sql);
|
|
171
202
|
if (runIt) {
|
|
172
|
-
requestRun(
|
|
203
|
+
requestRun(entry.sql);
|
|
173
204
|
}
|
|
174
205
|
};
|
|
175
206
|
const settle = async (action) => {
|
|
207
|
+
if (session === null) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
176
210
|
setSettling(true);
|
|
177
211
|
try {
|
|
178
212
|
setInTransaction(await session.transaction(action));
|
|
179
213
|
return true;
|
|
180
214
|
}
|
|
181
215
|
catch (error) {
|
|
182
|
-
|
|
216
|
+
notify({
|
|
217
|
+
tone: 'error',
|
|
218
|
+
title: t(action === 'commit' ? 'console.transaction.commitFailed' : 'console.transaction.rollbackFailed'),
|
|
219
|
+
description: messageOf(error)
|
|
220
|
+
});
|
|
183
221
|
return false;
|
|
184
222
|
}
|
|
185
223
|
finally {
|
|
@@ -201,6 +239,9 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
201
239
|
}
|
|
202
240
|
};
|
|
203
241
|
const loadPage = async (index, result, offset) => {
|
|
242
|
+
if (session === null) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
204
245
|
const mine = generation.current;
|
|
205
246
|
setPaging(true);
|
|
206
247
|
setNotice(null);
|
|
@@ -211,14 +252,14 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
211
252
|
}
|
|
212
253
|
}
|
|
213
254
|
catch (error) {
|
|
214
|
-
|
|
255
|
+
notify({ tone: 'error', title: t('console.pageFailed'), description: messageOf(error) });
|
|
215
256
|
}
|
|
216
257
|
finally {
|
|
217
258
|
setPaging(false);
|
|
218
259
|
}
|
|
219
260
|
};
|
|
220
261
|
const exportResult = async (result, format) => {
|
|
221
|
-
if (files === undefined) {
|
|
262
|
+
if (files === undefined || session === null) {
|
|
222
263
|
return;
|
|
223
264
|
}
|
|
224
265
|
setExporting(true);
|
|
@@ -226,11 +267,11 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
226
267
|
const path = await files.save({ suggestedName: `result.${format}`, format });
|
|
227
268
|
if (path !== null) {
|
|
228
269
|
const exported = await session.export({ source: { kind: 'query', sql: result.sql, schema: activeSchema }, format, path });
|
|
229
|
-
|
|
270
|
+
notify({ tone: 'success', title: t('console.exported', { count: exported.rows, formatted: formatNumber(exported.rows) }) });
|
|
230
271
|
}
|
|
231
272
|
}
|
|
232
273
|
catch (error) {
|
|
233
|
-
|
|
274
|
+
notify({ tone: 'error', title: t('console.exportFailed'), description: messageOf(error) });
|
|
234
275
|
}
|
|
235
276
|
finally {
|
|
236
277
|
setExporting(false);
|
|
@@ -242,7 +283,7 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
242
283
|
const page = pages[index];
|
|
243
284
|
const shown = base?.kind === 'rows' && page !== undefined ? { kind: 'rows', sql: base.sql, ...page.rows } : base;
|
|
244
285
|
const offset = page?.offset ?? 0;
|
|
245
|
-
const pager = base?.kind === 'rows' && shown?.kind === 'rows' && isPageable(base.sql, engine) && (shown.hasMore || offset > 0)
|
|
286
|
+
const pager = base?.kind === 'rows' && shown?.kind === 'rows' && engine !== undefined && isPageable(base.sql, engine) && (shown.hasMore || offset > 0)
|
|
246
287
|
? {
|
|
247
288
|
page: offset / PAGE_SIZE + 1,
|
|
248
289
|
loading: paging,
|
|
@@ -251,7 +292,26 @@ export function QueryConsole({ connection, schema, value, defaultValue = '', onV
|
|
|
251
292
|
}
|
|
252
293
|
: undefined;
|
|
253
294
|
const exportBase = files !== undefined && base?.kind === 'rows' ? base : undefined;
|
|
254
|
-
|
|
295
|
+
// Read for the statement, not for each page of it, so paging through a result asks for nothing again.
|
|
296
|
+
const keys = useResultKeys(session, engine, base);
|
|
297
|
+
const editorProps = {
|
|
298
|
+
ref: editorHandle,
|
|
299
|
+
value: sql,
|
|
300
|
+
onValueChange: changeSql,
|
|
301
|
+
run: (scope) => requestRun(targetOf(scope)),
|
|
302
|
+
busy,
|
|
303
|
+
label: t('console.editor'),
|
|
304
|
+
placeholder: t('console.placeholder'),
|
|
305
|
+
autoFocus
|
|
306
|
+
};
|
|
307
|
+
return (_jsxs("div", { ref: ref, className: clsx('flex min-h-0 flex-col bg-surface text-text', className), children: [_jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("div", { className: clsx('flex min-h-0 flex-1 flex-col', renderEditor === undefined && 'px-3 pt-3'), children: renderEditor === undefined ? _jsx(SqlEditor, { ...editorProps }) : _jsx(AppEditor, { render: renderEditor, ...editorProps }) }), _jsxs("div", { className: clsx('flex shrink-0 flex-wrap items-center gap-2 px-3 pb-3', renderEditor === undefined ? 'pt-2' : 'border-t border-border pt-3'), children: [session !== null && (_jsxs(_Fragment, { children: [run.status === 'running' ? (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", onClick: () => running.current?.abort(), children: [_jsx(Icon, { icon: Square, size: 12 }), t('console.cancel')] }), _jsx(Spinner, { size: 14, label: t('console.running') })] })) : (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: t('console.run'), kbd: RUN_SHORTCUT, children: _jsxs(Button, { variant: "primary", size: "sm", disabled: blank || busy, onClick: () => requestRun(targetOf('selection-or-statement')), children: [_jsx(Icon, { icon: Play, size: 12 }), t('console.run')] }) }), _jsx(Tooltip, { label: t('console.runAll'), kbd: RUN_ALL_SHORTCUT, children: _jsx(Button, { variant: "secondary", size: "sm", disabled: blank || busy, onClick: () => requestRun(targetOf('all')), children: t('console.runAll') }) })] })), _jsx(TransactionControls, { mode: mode, onModeChange: changeMode, open: inTransaction, busy: busy, onCommit: () => void settle('commit'), onRollback: () => void settle('rollback') })] })), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [toolbarEnd, session !== null && schemas.length > 0 && (_jsx(SchemaPicker, { schemas: schemas, value: activeSchema ?? null, disabled: busy, onValueChange: onSchemaChange ?? setPickedSchema })), session !== null && (_jsxs(Button, { variant: "secondary", size: "sm", "aria-pressed": historyOpen, className: "aria-pressed:bg-surface-active aria-pressed:text-text", onClick: () => setHistoryOpen(!historyOpen), children: [_jsx(Icon, { icon: History, size: 12 }), t('console.history.title')] }))] })] })] }), session !== null && historyOpen && _jsx(HistoryPanel, { entries: history.entries, onPick: pickHistory, onClear: history.clear })] }), session !== null && notice !== null && (_jsx(Banner, { icon: notice.tone === 'error' ? CircleAlert : Check, tone: notice.tone === 'error' ? 'error' : 'neutral', message: notice.description ?? notice.title, className: "shrink-0 pb-2", children: _jsx(Button, { size: "xs", onClick: () => setNotice(null), children: t('console.dismiss') }) })), session !== null && resultsOpen && (_jsxs("section", { ref: resultsPane, "aria-label": t('console.results'), className: "relative flex min-h-24 shrink-0 flex-col border-t border-border", style: { height: resultsHeight ?? '50%', maxHeight: `calc(100% - ${MIN_EDITOR_HEIGHT}px)` }, children: [_jsx(ColumnResizeHandle, { from: "bottom", onPointerDown: startResize }), _jsx(Tabs.Root, { value: index, onValueChange: (next) => setTab(Number(next)), children: _jsx(Tabs.List, { "aria-label": t('console.results'), className: "shrink-0 pr-1.5 pl-3", end: _jsx(IconButton, { icon: X, size: "sm", label: t('console.closeResults'), onClick: () => setResultsOpen(false) }), children: results.map((result, position) => (_jsx(Tabs.Tab, { value: position, children: statementLabel(result.sql) }, position))) }) }), run.status === 'failed' && _jsx(Banner, { icon: CircleAlert, tone: "error", message: run.message, className: "shrink-0 pt-2" }), run.status === 'cancelled' && _jsx(Banner, { icon: CircleAlert, tone: "neutral", message: t('console.cancelled'), className: "shrink-0 pt-2" }), shown !== undefined && (_jsx(StatementResultView, { result: shown, offset: offset, pager: pager, onExport: exportBase === undefined ? undefined : (format) => void exportResult(exportBase, format), exporting: exporting, engine: engine, valuePanelOpen: panelOpen, onValuePanelOpenChange: setPanelOpen, keys: keys, onFollowReference: act === undefined || connection === undefined
|
|
308
|
+
? undefined
|
|
309
|
+
: (reference) => act({
|
|
310
|
+
kind: 'open-table',
|
|
311
|
+
ref: { connectionId: connection.id, schema: reference.schema, table: reference.table },
|
|
312
|
+
view: 'data',
|
|
313
|
+
where: reference.where
|
|
314
|
+
}) }, `${index}:${offset}`))] })), _jsx(DestructiveDialog, { statements: confirming?.statements ?? [], onCancel: () => setConfirming(null), onConfirm: () => {
|
|
255
315
|
const target = confirming?.target;
|
|
256
316
|
setConfirming(null);
|
|
257
317
|
if (target !== undefined) {
|
|
@@ -1,12 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
3
|
-
export interface SqlEditorProps {
|
|
4
|
-
value: string;
|
|
5
|
-
onValueChange(value: string): void;
|
|
6
|
-
onRun(scope: RunScope): void;
|
|
7
|
-
label: string;
|
|
8
|
-
placeholder?: string;
|
|
9
|
-
className?: string;
|
|
10
|
-
ref?: Ref<HTMLTextAreaElement>;
|
|
11
|
-
}
|
|
12
|
-
export declare function SqlEditor({ value, onValueChange, onRun, label, placeholder, className, ref }: SqlEditorProps): import("react").JSX.Element;
|
|
1
|
+
import type { QueryConsoleEditorProps } from './editor-slot.ts';
|
|
2
|
+
export declare function SqlEditor({ ref, value, onValueChange, run, label, placeholder, autoFocus }: QueryConsoleEditorProps): import("react").JSX.Element;
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useEffect, useImperativeHandle, useRef } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
3
4
|
import { isApplePlatform, matchesShortcut, TextArea } from '@adecore/ui';
|
|
5
|
+
import { CODE_TEXT } from '../code-text.js';
|
|
4
6
|
import { applyEdit, indentEdit, newlineEdit, outdentEdit } from './editing.js';
|
|
5
7
|
import { RUN_ALL_SHORTCUT, RUN_SHORTCUT } from './shortcuts.js';
|
|
6
8
|
/*
|
|
7
|
-
* Where the SQL is typed
|
|
8
|
-
*
|
|
9
|
+
* Where the SQL is typed when the app brings no editor of its own: a plain text area that fills the
|
|
10
|
+
* height it gets, with the same props an editor of the app receives.
|
|
9
11
|
*/
|
|
10
|
-
export function SqlEditor({ value, onValueChange,
|
|
12
|
+
export function SqlEditor({ ref, value, onValueChange, run, label, placeholder, autoFocus }) {
|
|
13
|
+
const area = useRef(null);
|
|
11
14
|
const pendingSelection = useRef(null);
|
|
15
|
+
useImperativeHandle(ref, () => ({ selection: () => ({ start: area.current?.selectionStart ?? 0, end: area.current?.selectionEnd ?? 0 }) }), []);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (autoFocus) {
|
|
18
|
+
area.current?.focus();
|
|
19
|
+
}
|
|
20
|
+
// Only on mount: a console that later becomes active keeps the focus where the person put it.
|
|
21
|
+
// eslint-disable-next-line react/exhaustive-deps
|
|
22
|
+
}, []);
|
|
12
23
|
useEffect(() => {
|
|
13
24
|
const pending = pendingSelection.current;
|
|
14
25
|
if (pending !== null) {
|
|
@@ -17,45 +28,45 @@ export function SqlEditor({ value, onValueChange, onRun, label, placeholder, cla
|
|
|
17
28
|
}
|
|
18
29
|
}, [value]);
|
|
19
30
|
// Through the browser's own insert, so Cmd+Z undoes the edit; the fallback sets the value itself.
|
|
20
|
-
const apply = (
|
|
21
|
-
const before =
|
|
22
|
-
|
|
31
|
+
const apply = (target, edit) => {
|
|
32
|
+
const before = target.value;
|
|
33
|
+
target.setSelectionRange(edit.from, edit.to);
|
|
23
34
|
const inserted = document.execCommand('insertText', false, edit.insert);
|
|
24
|
-
if (inserted &&
|
|
25
|
-
|
|
35
|
+
if (inserted && target.value === applyEdit(before, edit)) {
|
|
36
|
+
target.setSelectionRange(edit.selectionStart, edit.selectionEnd);
|
|
26
37
|
return;
|
|
27
38
|
}
|
|
28
|
-
pendingSelection.current = { area, start: edit.selectionStart, end: edit.selectionEnd };
|
|
39
|
+
pendingSelection.current = { area: target, start: edit.selectionStart, end: edit.selectionEnd };
|
|
29
40
|
onValueChange(applyEdit(before, edit));
|
|
30
41
|
};
|
|
31
42
|
const handleKeyDown = (event) => {
|
|
32
43
|
const apple = isApplePlatform();
|
|
33
44
|
if (matchesShortcut(RUN_ALL_SHORTCUT, event, apple)) {
|
|
34
45
|
event.preventDefault();
|
|
35
|
-
|
|
46
|
+
run('all');
|
|
36
47
|
return;
|
|
37
48
|
}
|
|
38
49
|
if (matchesShortcut(RUN_SHORTCUT, event, apple)) {
|
|
39
50
|
event.preventDefault();
|
|
40
|
-
|
|
51
|
+
run('selection-or-statement');
|
|
41
52
|
return;
|
|
42
53
|
}
|
|
43
54
|
if (event.nativeEvent.isComposing || event.metaKey || event.ctrlKey || event.altKey) {
|
|
44
55
|
return;
|
|
45
56
|
}
|
|
46
|
-
const
|
|
47
|
-
const { selectionStart, selectionEnd } =
|
|
57
|
+
const target = event.currentTarget;
|
|
58
|
+
const { selectionStart, selectionEnd } = target;
|
|
48
59
|
if (event.key === 'Tab') {
|
|
49
60
|
event.preventDefault();
|
|
50
|
-
const edit = event.shiftKey ? outdentEdit(
|
|
61
|
+
const edit = event.shiftKey ? outdentEdit(target.value, selectionStart, selectionEnd) : indentEdit(target.value, selectionStart, selectionEnd);
|
|
51
62
|
if (edit !== null) {
|
|
52
|
-
apply(
|
|
63
|
+
apply(target, edit);
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
else if (event.key === 'Enter' && !event.shiftKey) {
|
|
56
67
|
event.preventDefault();
|
|
57
|
-
apply(
|
|
68
|
+
apply(target, newlineEdit(target.value, selectionStart, selectionEnd));
|
|
58
69
|
}
|
|
59
70
|
};
|
|
60
|
-
return (_jsx(TextArea, { ref:
|
|
71
|
+
return (_jsx(TextArea, { ref: area, "aria-label": label, placeholder: placeholder, spellCheck: false, autoCapitalize: "off", autoCorrect: "off", value: value, className: clsx(CODE_TEXT, 'flex-1'), onChange: (event) => onValueChange(event.target.value), onKeyDown: handleKeyDown }));
|
|
61
72
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Engine, FileFormat, StatementResult } from '../protocol/index.ts';
|
|
2
|
+
import type { Reference } from '../table/references.ts';
|
|
3
|
+
import type { ResultKeys } from './result-keys.ts';
|
|
2
4
|
export interface ResultPager {
|
|
3
5
|
readonly page: number;
|
|
4
6
|
readonly loading: boolean;
|
|
@@ -14,5 +16,7 @@ export interface StatementResultViewProps {
|
|
|
14
16
|
engine?: Engine;
|
|
15
17
|
valuePanelOpen?: boolean;
|
|
16
18
|
onValuePanelOpenChange?(open: boolean): void;
|
|
19
|
+
keys?: ResultKeys | null;
|
|
20
|
+
onFollowReference?(reference: Reference): void;
|
|
17
21
|
}
|
|
18
|
-
export declare function StatementResultView({ result, offset, pager, onExport, exporting, engine, valuePanelOpen, onValuePanelOpenChange }: StatementResultViewProps): import("react").JSX.Element;
|
|
22
|
+
export declare function StatementResultView({ result, offset, pager, onExport, exporting, engine, valuePanelOpen, onValuePanelOpenChange, keys, onFollowReference }: StatementResultViewProps): import("react").JSX.Element;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
|
-
import { ChevronLeft, ChevronRight, CircleAlert, Download, PanelRight } from 'lucide-react';
|
|
3
|
+
import { ArrowUpRight, ChevronLeft, ChevronRight, CircleAlert, Download, PanelRight } from 'lucide-react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Banner, IconButton, Menu, Spinner } from '@adecore/ui';
|
|
5
|
+
import { Banner, ButtonGroup, ContextMenu, Icon, IconButton, Menu, Spinner } from '@adecore/ui';
|
|
6
6
|
import { formatDecimal, formatNumber } from '@adecore/ui/format';
|
|
7
7
|
import { DataGrid } from '../grid/DataGrid.js';
|
|
8
8
|
import { previewValueOf } from '../grid/focused-value.js';
|
|
@@ -12,7 +12,7 @@ import { ValuePanel } from '../value/ValuePanel.js';
|
|
|
12
12
|
const RESULT_TABLE = 'result';
|
|
13
13
|
const EXPORT_FORMATS = ['csv', 'tsv', 'json', 'sql'];
|
|
14
14
|
/* One statement's outcome: a result set in a read-only grid, a count of affected rows, or the error the server gave. */
|
|
15
|
-
export function StatementResultView({ result, offset = 0, pager, onExport, exporting = false, engine, valuePanelOpen = false, onValuePanelOpenChange }) {
|
|
15
|
+
export function StatementResultView({ result, offset = 0, pager, onExport, exporting = false, engine, valuePanelOpen = false, onValuePanelOpenChange, keys = null, onFollowReference }) {
|
|
16
16
|
const { t } = useTranslation('database');
|
|
17
17
|
const [focus, setFocus] = useState(null);
|
|
18
18
|
const rows = useMemo(() => (result.kind === 'rows' ? result.rows.map((cells, index) => ({ key: `row:${index}`, number: offset + index + 1, cells })) : []), [result, offset]);
|
|
@@ -24,9 +24,22 @@ export function StatementResultView({ result, offset = 0, pager, onExport, expor
|
|
|
24
24
|
if (result.kind === 'done') {
|
|
25
25
|
return (_jsxs("div", { className: "flex flex-col gap-1 p-4 text-sm text-text", children: [_jsx("p", { children: t('console.affected', { count: result.affected, formatted: formatNumber(result.affected) }) }), result.lastInsertId !== null && _jsx("p", { className: "text-xs text-text-muted", children: t('console.lastInsertId', { id: String(result.lastInsertId) }) }), _jsx("p", { className: "text-xs text-text-muted tabular-nums", children: elapsed })] }));
|
|
26
26
|
}
|
|
27
|
+
const referenceAt = (cell) => {
|
|
28
|
+
const row = rows.find((candidate) => candidate.key === cell.rowKey);
|
|
29
|
+
return keys === null || row === undefined ? null : keys.referenceAt(cell.column, row.cells);
|
|
30
|
+
};
|
|
31
|
+
const follow = keys === null || onFollowReference === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: (cell) => {
|
|
34
|
+
const reference = referenceAt(cell);
|
|
35
|
+
if (reference !== null) {
|
|
36
|
+
onFollowReference(reference);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const referenceMenu = (context) => follow === undefined || context.cell === null || keys?.isReference(context.cell.column) !== true ? null : (_jsxs(ContextMenu.Item, { disabled: referenceAt(context.cell) === null, onClick: () => context.cell !== null && follow(context.cell), children: [_jsx(Icon, { icon: ArrowUpRight, size: 14 }), t('table.goToReferenced')] }));
|
|
27
40
|
const focusedRow = valuePanelOpen && focus !== null ? rows.find((row) => row.key === focus.rowKey) : undefined;
|
|
28
41
|
const focusedCell = focus === null ? undefined : focusedRow?.cells[focus.column];
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(ValueDock, { open: valuePanelOpen, panel: _jsx(ValuePanel, { column: focus === null ? null : (result.columns[focus.column] ?? null), value: focusedCell === undefined ? undefined : previewValueOf(focusedCell), loading: false, editable: false, onClose: () => onValuePanelOpenChange?.(false), className: "min-w-0 flex-1" }), children: _jsx(DataGrid, { label: t('console.result'), columns: result.columns, rows: rows, empty: t('console.noRows'), sqlTarget: sqlTarget, onFocusedCellChange: setFocus }) }), _jsxs("footer", { className: "flex h-10 shrink-0 items-center gap-4 border-t border-border px-3 text-xs text-text-muted", children: [_jsx("span", { className: "text-text tabular-nums", children: offset > 0 || pager !== undefined
|
|
42
|
+
return (_jsxs(_Fragment, { children: [_jsx(ValueDock, { open: valuePanelOpen, panel: _jsx(ValuePanel, { column: focus === null ? null : (result.columns[focus.column] ?? null), value: focusedCell === undefined ? undefined : previewValueOf(focusedCell), loading: false, editable: false, onClose: () => onValuePanelOpenChange?.(false), className: "min-w-0 flex-1" }), children: _jsx(DataGrid, { label: t('console.result'), columns: keys?.columns ?? result.columns, rows: rows, empty: t('console.noRows'), sqlTarget: sqlTarget, onFocusedCellChange: setFocus, onFollow: follow, canFollow: (cell) => referenceAt(cell) !== null, menu: referenceMenu }) }), _jsxs("footer", { className: "flex h-10 shrink-0 items-center gap-4 border-t border-border px-3 text-xs text-text-muted", children: [_jsx("span", { className: "text-text tabular-nums", children: offset > 0 || pager !== undefined
|
|
30
43
|
? t('console.rowRange', { from: formatNumber(offset + Math.min(1, rows.length)), to: formatNumber(offset + rows.length) })
|
|
31
|
-
: t('console.rows', { rows: formatNumber(result.rows.length) }) }), pager === undefined && result.hasMore && _jsx("span", { children: t('console.hasMore') }), _jsx("span", { className: "tabular-nums", children: elapsed }), _jsxs("span", { className: "ml-auto flex items-center gap-1", children: [pager !== undefined && (_jsxs(_Fragment, { children: [pager.loading && _jsx(Spinner, { size: 14, label: t('console.loading') }), _jsx(IconButton, { icon: ChevronLeft, size: "sm", label: t('console.previousPage'), disabled: pager.page <= 1 || pager.loading, onClick: pager.onPrevious }), _jsx("span", { className: "min-w-12 text-center tabular-nums", children: t('console.page', { page: formatNumber(pager.page) }) }), _jsx(IconButton, { icon: ChevronRight, size: "sm", label: t('console.nextPage'), disabled: !result.hasMore || pager.loading, onClick: pager.onNext })] })), onExport !== undefined && (_jsxs(Menu.Root, { children: [_jsx(IconButton, { icon: Download, size: "sm", label: t('console.export'), disabled: exporting, render: _jsx(Menu.Trigger, {}) }), _jsx(Menu.Popup, { children: EXPORT_FORMATS.map((format) => (_jsx(Menu.Item, { onClick: () => onExport(format), children: t('console.exportAs', { format: format.toUpperCase() }) }, format))) })] })), onValuePanelOpenChange !== undefined && (_jsx(IconButton, { icon: PanelRight, size: "sm", label: t('console.valuePanel'), "aria-pressed": valuePanelOpen, onClick: () => onValuePanelOpenChange(!valuePanelOpen) }))] })] })] }));
|
|
44
|
+
: t('console.rows', { rows: formatNumber(result.rows.length) }) }), pager === undefined && result.hasMore && _jsx("span", { children: t('console.hasMore') }), _jsx("span", { className: "tabular-nums", children: elapsed }), _jsxs("span", { className: "ml-auto flex items-center gap-1", children: [pager !== undefined && (_jsxs(_Fragment, { children: [pager.loading && _jsx(Spinner, { size: 14, label: t('console.loading') }), _jsx(IconButton, { icon: ChevronLeft, size: "sm", label: t('console.previousPage'), disabled: pager.page <= 1 || pager.loading, onClick: pager.onPrevious }), _jsx("span", { className: "min-w-12 text-center tabular-nums", children: t('console.page', { page: formatNumber(pager.page) }) }), _jsx(IconButton, { icon: ChevronRight, size: "sm", label: t('console.nextPage'), disabled: !result.hasMore || pager.loading, onClick: pager.onNext })] })), _jsxs(ButtonGroup, { children: [onExport !== undefined && (_jsxs(Menu.Root, { children: [_jsx(IconButton, { icon: Download, size: "sm", label: t('console.export'), disabled: exporting, render: _jsx(Menu.Trigger, {}) }), _jsx(Menu.Popup, { children: EXPORT_FORMATS.map((format) => (_jsx(Menu.Item, { onClick: () => onExport(format), children: t('console.exportAs', { format: format.toUpperCase() }) }, format))) })] })), onValuePanelOpenChange !== undefined && (_jsx(IconButton, { icon: PanelRight, size: "sm", label: t('console.valuePanel'), "aria-pressed": valuePanelOpen, onClick: () => onValuePanelOpenChange(!valuePanelOpen) }))] })] })] })] }));
|
|
32
45
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { Button, Pill,
|
|
5
|
-
/* Auto or
|
|
4
|
+
import { Button, Pill, Switch, Tooltip } from '@adecore/ui';
|
|
5
|
+
/* Auto-commit on or off, and while a transaction is open, the way to end it. */
|
|
6
6
|
export function TransactionControls({ mode, onModeChange, open, busy, onCommit, onRollback, className, ref }) {
|
|
7
7
|
const { t } = useTranslation('database');
|
|
8
|
-
return (_jsxs("div", { ref: ref, className: clsx('flex items-center gap-2', className), children: [_jsx(Tooltip, { label: t('console.transaction.hint'), children:
|
|
9
|
-
{ id: 'auto', label: t('console.transaction.auto') },
|
|
10
|
-
{ id: 'manual', label: t('console.transaction.manual') }
|
|
11
|
-
] }) }) }), open && (_jsxs(_Fragment, { children: [_jsx(Pill, { tone: "needsYou", children: t('console.transaction.open') }), _jsx(Button, { size: "xs", variant: "secondary", disabled: busy, onClick: onCommit, children: t('console.transaction.commit') }), _jsx(Button, { size: "xs", variant: "danger-outline", disabled: busy, onClick: onRollback, children: t('console.transaction.rollback') })] }))] }));
|
|
8
|
+
return (_jsxs("div", { ref: ref, className: clsx('flex items-center gap-2', className), children: [_jsx(Tooltip, { label: t('console.transaction.hint'), children: _jsxs("div", { className: "flex items-center gap-2 text-xs text-text", children: [_jsx(Switch, { checked: mode === 'auto', label: t('console.transaction.autoCommit'), disabled: busy, onCheckedChange: (auto) => onModeChange(auto ? 'auto' : 'manual') }), _jsx("span", { children: t('console.transaction.autoCommit') })] }) }), open && (_jsxs(_Fragment, { children: [_jsx(Pill, { tone: "needsYou", children: t('console.transaction.open') }), _jsx(Button, { size: "xs", variant: "secondary", disabled: busy, onClick: onCommit, children: t('console.transaction.commit') }), _jsx(Button, { size: "xs", variant: "danger-outline", disabled: busy, onClick: onRollback, children: t('console.transaction.rollback') })] }))] }));
|
|
12
9
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Ref } from 'react';
|
|
2
|
+
export type QueryConsoleRunScope = 'selection-or-statement' | 'all';
|
|
3
|
+
export interface QueryConsoleEditorHandle {
|
|
4
|
+
selection(): {
|
|
5
|
+
readonly start: number;
|
|
6
|
+
readonly end: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface QueryConsoleEditorProps {
|
|
10
|
+
ref: Ref<QueryConsoleEditorHandle>;
|
|
11
|
+
value: string;
|
|
12
|
+
onValueChange(sql: string): void;
|
|
13
|
+
run(scope: QueryConsoleRunScope): void;
|
|
14
|
+
busy: boolean;
|
|
15
|
+
label: string;
|
|
16
|
+
placeholder: string;
|
|
17
|
+
autoFocus: boolean;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DatabaseSession } from '../client/types.ts';
|
|
2
|
+
import type { Shown } from '../grid/display.ts';
|
|
3
|
+
import type { GridColumn } from '../grid/types.ts';
|
|
4
|
+
import type { ColumnSource, Engine, ResultColumn, StatementResult, TableStructure } from '../protocol/index.ts';
|
|
5
|
+
import { type Reference } from '../table/references.ts';
|
|
6
|
+
export interface ResultKeys {
|
|
7
|
+
readonly columns: readonly GridColumn[];
|
|
8
|
+
isReference(column: number): boolean;
|
|
9
|
+
referenceAt(column: number, cells: readonly Shown[]): Reference | null;
|
|
10
|
+
}
|
|
11
|
+
type TableName = Pick<ColumnSource, 'schema' | 'table'>;
|
|
12
|
+
export declare const sourceTablesOf: (columns: readonly ResultColumn[]) => TableName[];
|
|
13
|
+
export declare const resultKeysOf: (engine: Engine, columns: readonly ResultColumn[], structures: ReadonlyMap<string, TableStructure>) => ResultKeys;
|
|
14
|
+
export declare function useResultKeys(session: DatabaseSession | null, engine: Engine | undefined, result: StatementResult | undefined): ResultKeys | null;
|
|
15
|
+
export {};
|