@adecore/database 0.17.0 → 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
package/dist/table/TableView.js
CHANGED
|
@@ -6,15 +6,14 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
import { Banner, Button, ContextMenu, Icon, isApplePlatform, Kbd, KEY_SHORTCUTS, matchesShortcut, messageOf, PanelEmpty, PromptDialog, shortcut, Spinner } from '@adecore/ui';
|
|
7
7
|
import { formatNumber } from '@adecore/ui/format';
|
|
8
8
|
import { DatabaseRequestError } from '../client/types.js';
|
|
9
|
-
import { useDatabaseAction, useDatabaseClient, useDatabaseFiles, useDatabaseStorage } from '../client-context.js';
|
|
9
|
+
import { useDatabaseAction, useDatabaseClient, useDatabaseFiles, useDatabaseNotice, useDatabaseStorage } from '../client-context.js';
|
|
10
10
|
import { aggregateBlock, blockSize } from '../grid/aggregates.js';
|
|
11
11
|
import { DataGrid } from '../grid/DataGrid.js';
|
|
12
|
-
import { wholeValueOf } from '../grid/focused-value.js';
|
|
13
12
|
import { RecordView } from '../grid/RecordView.js';
|
|
14
13
|
import { ValueDock } from '../grid/ValueDock.js';
|
|
15
14
|
import { valueOfCell } from '../protocol/index.js';
|
|
16
15
|
import { useSchemaChange } from '../use-schema-change.js';
|
|
17
|
-
import {
|
|
16
|
+
import { useStableCallback } from '../use-stable-callback.js';
|
|
18
17
|
import { addChip, cellChip, chipsFromQuery, chipsToOrderBy, chipsToWhere, shorten, sortsOf, toggleSort, withSorts } from './command-field.js';
|
|
19
18
|
import { buildGridColumns, buildGridRows, cloneValues, parseRowKey } from './grid-rows.js';
|
|
20
19
|
import { importableColumns } from './import-mapping.js';
|
|
@@ -28,7 +27,32 @@ import { TableToolbar } from './TableToolbar.js';
|
|
|
28
27
|
import { useLoaded } from './useLoaded.js';
|
|
29
28
|
import { useStoredLayout } from './useStoredLayout.js';
|
|
30
29
|
import { useTableTransfer } from './useTableTransfer.js';
|
|
30
|
+
/* Where the width of the record view is kept, in pixels, for every table. */
|
|
31
|
+
export const RECORD_WIDTH_KEY = 'database:record-view';
|
|
32
|
+
const RECORD_WIDTH = 360;
|
|
33
|
+
const parseRecordWidth = (raw) => {
|
|
34
|
+
try {
|
|
35
|
+
const value = JSON.parse(raw ?? 'null');
|
|
36
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.round(value) : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
31
42
|
const NO_KEYS = new Set();
|
|
43
|
+
/* What the strip over the table, or the app's notice, says of an export or an import. */
|
|
44
|
+
const transferText = (found, t) => {
|
|
45
|
+
switch (found.kind) {
|
|
46
|
+
case 'exporting':
|
|
47
|
+
return t('table.export.running', { format: found.format.toUpperCase() });
|
|
48
|
+
case 'exported':
|
|
49
|
+
return t('table.export.done', { count: found.rows, rows: formatNumber(found.rows) });
|
|
50
|
+
case 'imported':
|
|
51
|
+
return t('table.import.done', { count: found.rows, rows: formatNumber(found.rows) });
|
|
52
|
+
case 'failed':
|
|
53
|
+
return found.message;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
32
56
|
/* How much of a value a menu item spells out before it cuts it. */
|
|
33
57
|
const MENU_VALUE_LENGTH = 40;
|
|
34
58
|
const FOCUS_COMMAND = shortcut('Mod+F');
|
|
@@ -37,16 +61,18 @@ const DUPLICATE_ROWS = shortcut('Mod+D');
|
|
|
37
61
|
* The rows of one table: read a page at a time, filtered and sorted with SQL a person types, and
|
|
38
62
|
* editable in place. Changes pile up as pending and go to the server together on Submit.
|
|
39
63
|
*/
|
|
40
|
-
export function TableView({ connection, schema, table, defaultWhere, defaultOrderBy, onDirtyChange, className, ref }) {
|
|
64
|
+
export function TableView({ connection, schema, table, toolbarStart, defaultWhere, defaultOrderBy, onDirtyChange, className, ref }) {
|
|
41
65
|
// A new table, or the same one asked for with other filters, starts from nothing: no page, no pending change.
|
|
42
|
-
return (_jsx(TableBody, { connection: connection, schema: schema, table: table, defaultWhere: defaultWhere, defaultOrderBy: defaultOrderBy, onDirtyChange: onDirtyChange, className: className, ref: ref }, [connection.id, schema, table, defaultWhere ?? '', defaultOrderBy ?? ''].join('\u0000')));
|
|
66
|
+
return (_jsx(TableBody, { connection: connection, schema: schema, table: table, toolbarStart: toolbarStart, defaultWhere: defaultWhere, defaultOrderBy: defaultOrderBy, onDirtyChange: onDirtyChange, className: className, ref: ref }, [connection.id, schema, table, defaultWhere ?? '', defaultOrderBy ?? ''].join('\u0000')));
|
|
43
67
|
}
|
|
44
|
-
function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, onDirtyChange, className, ref }) {
|
|
68
|
+
function TableBody({ connection, schema, table, toolbarStart, defaultWhere, defaultOrderBy, onDirtyChange, className, ref }) {
|
|
45
69
|
const { t } = useTranslation('database');
|
|
46
70
|
const client = useDatabaseClient();
|
|
47
71
|
const onAction = useDatabaseAction();
|
|
72
|
+
const onNotice = useDatabaseNotice();
|
|
48
73
|
const files = useDatabaseFiles();
|
|
49
|
-
const
|
|
74
|
+
const storage = useDatabaseStorage();
|
|
75
|
+
const { stored, remember } = useStoredLayout(storage, layoutStorageKey(connection.id, schema, table));
|
|
50
76
|
const session = useMemo(() => client.session(connection), [client, connection]);
|
|
51
77
|
const grid = useRef(null);
|
|
52
78
|
const command = useRef(null);
|
|
@@ -69,14 +95,12 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
69
95
|
const [failure, setFailure] = useState(null);
|
|
70
96
|
const [shapeChanged, setShapeChanged] = useState(false);
|
|
71
97
|
const [discard, setDiscard] = useState(null);
|
|
72
|
-
const [panelOpen, setPanelOpen] = useState(false);
|
|
73
98
|
const [focus, setFocus] = useState(null);
|
|
74
99
|
const [recordOpen, setRecordOpen] = useState(false);
|
|
75
|
-
const [
|
|
100
|
+
const [recordWidth, setRecordWidth] = useState(() => parseRecordWidth(storage?.get(RECORD_WIDTH_KEY)));
|
|
76
101
|
const [focusRequest, setFocusRequest] = useState(null);
|
|
77
102
|
const [columnRequest, setColumnRequest] = useState(null);
|
|
78
103
|
const [range, setRange] = useState(null);
|
|
79
|
-
const [fetched, setFetched] = useState(null);
|
|
80
104
|
const applied = useMemo(() => ({ where: chipsToWhere(chips), orderBy: chipsToOrderBy(engine, chips) }), [chips, engine]);
|
|
81
105
|
const target = `${connection.id}|${schema}|${table}`;
|
|
82
106
|
const structureLoad = useLoaded((signal) => session.structure(schema, table, { signal }), target);
|
|
@@ -112,7 +136,16 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
112
136
|
onImported: () => {
|
|
113
137
|
setCounted(null);
|
|
114
138
|
rowsLoad.reload();
|
|
115
|
-
}
|
|
139
|
+
},
|
|
140
|
+
report: onNotice === undefined
|
|
141
|
+
? undefined
|
|
142
|
+
: (found) => onNotice(found.kind === 'failed'
|
|
143
|
+
? {
|
|
144
|
+
tone: 'error',
|
|
145
|
+
title: t(found.during === 'export' ? 'table.export.failed' : 'table.import.readFailed'),
|
|
146
|
+
description: found.message
|
|
147
|
+
}
|
|
148
|
+
: { tone: 'success', title: transferText(found, t) })
|
|
116
149
|
});
|
|
117
150
|
const changeCount = pendingCount(pending);
|
|
118
151
|
const dirty = changeCount > 0;
|
|
@@ -121,23 +154,16 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
121
154
|
const sorts = useMemo(() => sortsOf(chips), [chips]);
|
|
122
155
|
const commandColumns = useMemo(() => columns.map((column) => ({ name: column.name, type: column.type })), [columns]);
|
|
123
156
|
const rowByKey = useMemo(() => new Map(rows.map((row) => [row.key, row])), [rows]);
|
|
124
|
-
|
|
125
|
-
const
|
|
157
|
+
// The record view shows the row of the focused cell, or the first row before a cell has the focus.
|
|
158
|
+
const focusedIndex = focus === null ? -1 : rows.findIndex((row) => row.key === focus.rowKey);
|
|
159
|
+
const recordIndex = focusedIndex >= 0 ? focusedIndex : rows.length > 0 ? 0 : -1;
|
|
160
|
+
const selectionFigures = useMemo(() => range === null || blockSize(range) < 2
|
|
126
161
|
? null
|
|
127
162
|
: {
|
|
128
163
|
columns: range.columns.map((index) => columns[index]?.name ?? ''),
|
|
129
164
|
aggregates: aggregateBlock(rows.map((row) => row.cells), columns.map((column) => column.kind), range)
|
|
130
|
-
}, [
|
|
165
|
+
}, [range, rows, columns]);
|
|
131
166
|
const sqlTarget = useMemo(() => ({ engine, schema, table }), [engine, schema, table]);
|
|
132
|
-
const focusedRow = panelOpen && focus !== null ? rows.find((row) => row.key === focus.rowKey) : undefined;
|
|
133
|
-
const focusedCell = focus === null ? undefined : focusedRow?.cells[focus.column];
|
|
134
|
-
const needsFetch = focusedCell !== undefined &&
|
|
135
|
-
focus !== null &&
|
|
136
|
-
wholeValueOf(focusedCell) === undefined &&
|
|
137
|
-
!(focusedCell !== null && typeof focusedCell === 'object' && focusedCell.kind === 'default');
|
|
138
|
-
const focusedRef = focus === null ? null : parseRowKey(focus.rowKey);
|
|
139
|
-
const canFetch = focusedRef?.kind === 'loaded' && structure !== null && loaded !== null && rowKeyOf(structure, loaded, focusedRef.index) !== null;
|
|
140
|
-
const fetchId = needsFetch && canFetch ? `${focus.rowKey}:${focus.column}` : null;
|
|
141
167
|
useSchemaChange(connection.id, schema, () => {
|
|
142
168
|
if (isPendingEmpty(pending)) {
|
|
143
169
|
setCounted(null);
|
|
@@ -172,28 +198,10 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
172
198
|
insertedBefore.current = pending.inserts.length;
|
|
173
199
|
}, [pending.inserts.length]);
|
|
174
200
|
useEffect(() => {
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
const row = parseRowKey(focus.rowKey);
|
|
179
|
-
const key = row.kind === 'loaded' ? rowKeyOf(structure, loaded, row.index) : null;
|
|
180
|
-
const name = loaded.columns[focus.column]?.name;
|
|
181
|
-
if (key === null || name === undefined) {
|
|
182
|
-
return;
|
|
201
|
+
if (recordWidth !== null) {
|
|
202
|
+
storage?.set(RECORD_WIDTH_KEY, JSON.stringify(recordWidth));
|
|
183
203
|
}
|
|
184
|
-
|
|
185
|
-
session.cell(schema, table, key, name, { signal: controller.signal }).then((value) => {
|
|
186
|
-
if (!controller.signal.aborted) {
|
|
187
|
-
setFetched({ id: fetchId, rows: loaded, value });
|
|
188
|
-
}
|
|
189
|
-
}, (error) => {
|
|
190
|
-
if (!controller.signal.aborted) {
|
|
191
|
-
setFetched({ id: fetchId, rows: loaded, value: undefined });
|
|
192
|
-
setFailure(messageOf(error));
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
return () => controller.abort();
|
|
196
|
-
}, [fetchId, focus, structure, loaded, session, schema, table]);
|
|
204
|
+
}, [storage, recordWidth]);
|
|
197
205
|
const discardPending = () => {
|
|
198
206
|
setPending(emptyPending);
|
|
199
207
|
setSelected(NO_KEYS);
|
|
@@ -320,7 +328,7 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
320
328
|
setPending((now) => setEdit(now, row.index, name, value, loaded.rows[row.index][column] ?? null));
|
|
321
329
|
}
|
|
322
330
|
};
|
|
323
|
-
const loadValue = async (rowKey, column) => {
|
|
331
|
+
const loadValue = useStableCallback(async (rowKey, column) => {
|
|
324
332
|
const row = parseRowKey(rowKey);
|
|
325
333
|
const name = loaded?.columns[column]?.name;
|
|
326
334
|
const key = row.kind === 'loaded' && structure !== null && loaded !== null ? rowKeyOf(structure, loaded, row.index) : null;
|
|
@@ -334,7 +342,7 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
334
342
|
setFailure(messageOf(error));
|
|
335
343
|
return undefined;
|
|
336
344
|
}
|
|
337
|
-
};
|
|
345
|
+
});
|
|
338
346
|
const refsOf = (keys) => {
|
|
339
347
|
const loadedRows = [];
|
|
340
348
|
const inserted = [];
|
|
@@ -389,23 +397,17 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
389
397
|
}
|
|
390
398
|
};
|
|
391
399
|
const toggleRecordView = () => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const focused = focus === null ? undefined : rows.find((row) => row.key === focus.rowKey);
|
|
401
|
-
setRecordKey((focused ?? rows[0])?.key ?? null);
|
|
402
|
-
setRecordOpen(true);
|
|
400
|
+
setRecordOpen((now) => !now);
|
|
401
|
+
};
|
|
402
|
+
/* The record view moves the grid's focus along with it, without taking the keyboard from its field. */
|
|
403
|
+
const followInGrid = (cell) => {
|
|
404
|
+
setFocus(cell);
|
|
405
|
+
setFocusRequest({ ...cell, quiet: true });
|
|
403
406
|
};
|
|
404
407
|
const changeRecordIndex = (index) => {
|
|
405
408
|
const row = rows[index];
|
|
406
409
|
if (row !== undefined) {
|
|
407
|
-
|
|
408
|
-
setFocus((now) => (now === null ? now : { rowKey: row.key, column: now.column }));
|
|
410
|
+
followInGrid({ rowKey: row.key, column: focus?.column ?? 0 });
|
|
409
411
|
}
|
|
410
412
|
};
|
|
411
413
|
const queryMenu = (context) => {
|
|
@@ -460,44 +462,14 @@ function TableBody({ connection, schema, table, defaultWhere, defaultOrderBy, on
|
|
|
460
462
|
}
|
|
461
463
|
};
|
|
462
464
|
const bounds = pageBounds(loadedOffset, loaded?.rows.length ?? 0, loaded?.hasMore ?? false, counted);
|
|
463
|
-
const focusedColumn = focus === null ? null : (columns[focus.column] ?? null);
|
|
464
|
-
const panelEditable = editable && focusedRow !== undefined && focusedRow.locked !== true && focusedRow.state !== 'deleted' && focusedColumn?.readOnly !== true;
|
|
465
|
-
const fetchedNow = fetched !== null && fetched.id === fetchId && fetched.rows === loaded;
|
|
466
|
-
const panelValue = (() => {
|
|
467
|
-
if (focusedCell === undefined || focusedRow === undefined || focus === null) {
|
|
468
|
-
return undefined;
|
|
469
|
-
}
|
|
470
|
-
const whole = wholeValueOf(focusedCell);
|
|
471
|
-
if (whole !== undefined) {
|
|
472
|
-
return whole;
|
|
473
|
-
}
|
|
474
|
-
if (focusedCell !== null && typeof focusedCell === 'object' && focusedCell.kind === 'default') {
|
|
475
|
-
// A pending DEFAULT holds no value of its own, so the panel keeps showing what the row had.
|
|
476
|
-
const row = parseRowKey(focusedRow.key);
|
|
477
|
-
return row.kind === 'loaded' && loaded !== null ? wholeValueOf(loaded.rows[row.index]?.[focus.column] ?? null) : null;
|
|
478
|
-
}
|
|
479
|
-
return fetchedNow ? fetched.value : undefined;
|
|
480
|
-
})();
|
|
481
465
|
const notice = failure ?? rowsLoad.error ?? structureLoad.error;
|
|
482
|
-
|
|
483
|
-
switch (found.kind) {
|
|
484
|
-
case 'exporting':
|
|
485
|
-
return t('table.export.running', { format: found.format.toUpperCase() });
|
|
486
|
-
case 'exported':
|
|
487
|
-
return t('table.export.done', { count: found.rows, rows: formatNumber(found.rows) });
|
|
488
|
-
case 'imported':
|
|
489
|
-
return t('table.import.done', { count: found.rows, rows: formatNumber(found.rows) });
|
|
490
|
-
case 'failed':
|
|
491
|
-
return found.message;
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
return (_jsxs("div", { ref: ref, className: clsx('flex min-h-0 flex-col bg-surface text-text', className), onKeyDown: handleKeyDown, children: [_jsx(TableToolbar, { refreshing: rowsLoad.loading, readOnlyReason: readOnlyReason, hasSelection: selected.size > 0, canRevertSelection: selected.size > 0 && canRevert(selected), valuePanelOpen: panelOpen, recordViewOpen: recordOpen, transfer: files === undefined
|
|
466
|
+
return (_jsxs("div", { ref: ref, className: clsx('flex min-h-0 flex-col bg-surface text-text', className), onKeyDown: handleKeyDown, children: [_jsx(TableToolbar, { start: toolbarStart, refreshing: rowsLoad.loading, readOnlyReason: readOnlyReason, hasSelection: selected.size > 0, canRevertSelection: selected.size > 0 && canRevert(selected), recordViewOpen: recordOpen, transfer: files === undefined
|
|
495
467
|
? undefined
|
|
496
468
|
: {
|
|
497
469
|
busy: transfer.notice?.kind === 'exporting',
|
|
498
470
|
onExport: transfer.exportAs,
|
|
499
471
|
onImport: readOnlyReason === null && structure !== null ? () => guard(transfer.chooseImportFile) : undefined
|
|
500
|
-
}, pendingCount: changeCount, submitting: submitting, engine: engine, chips: chips, columns: commandColumns, pageSize: pageSize, bounds: bounds, commandRef: command, onChipsChange: applyChips, onJumpToColumn: jumpToColumn, onRefresh: refresh, onAddRow: () => setPending((now) => addInsertWith(now, {})), onDeleteRows: deleteSelected, onCloneRows: () => cloneRows([...selected]), onRevertRows: () => revertRowKeys(selected),
|
|
472
|
+
}, pendingCount: changeCount, submitting: submitting, engine: engine, chips: chips, columns: commandColumns, pageSize: pageSize, bounds: bounds, commandRef: command, onChipsChange: applyChips, onJumpToColumn: jumpToColumn, onRefresh: refresh, onAddRow: () => setPending((now) => addInsertWith(now, {})), onDeleteRows: deleteSelected, onCloneRows: () => cloneRows([...selected]), onRevertRows: () => revertRowKeys(selected), onToggleRecordView: toggleRecordView, onPageSizeChange: changePageSize, onFirstPage: () => goToPage(0), onLastPage: () => void goToLast(), onSubmit: () => void submit(), onRevert: discardPending }), notice !== null && (_jsx(Banner, { icon: CircleAlert, tone: "error", message: notice, className: "shrink-0 pt-2", children: failure === null ? (_jsx(Button, { size: "xs", onClick: refresh, children: t('table.retry') })) : (_jsx(Button, { size: "xs", onClick: () => setFailure(null), children: t('table.dismiss') })) })), shapeChanged && (_jsx(Banner, { icon: RefreshCw, tone: "neutral", message: t('table.changed'), className: "shrink-0 pt-2", children: _jsx(Button, { size: "xs", onClick: refresh, children: t('table.reload') }) })), transfer.notice !== null && (_jsx(Banner, { icon: transfer.notice.kind === 'failed' ? CircleAlert : transfer.notice.kind === 'exporting' ? Download : Check, tone: transfer.notice.kind === 'failed' ? 'error' : 'neutral', message: transferText(transfer.notice, t), className: "shrink-0 pt-2", children: transfer.notice.kind === 'exporting' ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: 12, label: t('table.export.running', { format: transfer.notice.format.toUpperCase() }) }), _jsx(Button, { size: "xs", onClick: transfer.cancelExport, children: t('table.export.cancel') })] })) : (_jsx(Button, { size: "xs", onClick: transfer.dismiss, children: t('table.dismiss') })) })), loaded === null ? (_jsx(PanelEmpty, { busy: notice === null, children: notice === null ? t('table.loading') : t('table.notLoaded') })) : (_jsxs(_Fragment, { children: [_jsx(ValueDock, { open: recordOpen, width: recordWidth ?? RECORD_WIDTH, onWidthChange: setRecordWidth, panel: _jsx(RecordView, { label: t('table.recordFields', { table }), columns: columns, rows: rows, index: recordIndex, onIndexChange: changeRecordIndex, focusedColumn: focus !== null && focusedIndex === recordIndex ? focus.column : null, editable: editable, onCommit: commit, loadValue: loadValue, onFocusedCellChange: followInGrid, onFollow: onAction === undefined ? undefined : follow, canFollow: (cell) => referenceAt(cell) !== null, onClose: () => setRecordOpen(false), empty: t('table.empty') }), children: _jsx("div", { "aria-busy": rowsLoad.loading, className: clsx('relative flex min-h-0 flex-1 flex-col', rowsLoad.loading && 'pointer-events-none opacity-60'), children: _jsx(DataGrid, { ref: grid, label: t('table.grid', { table }), columns: columns, rows: rows, editable: editable, empty: t('table.empty'), selectedKeys: selected, onSelectedKeysChange: setSelected, onCommit: commit, loadValue: loadValue, sorts: sorts, onSortsChange: changeSorts, onFocusedCellChange: setFocus, onDeleteSelected: editable ? deleteSelected : undefined, sqlTarget: sqlTarget, queryMenu: queryMenu, menu: gridMenu, onDuplicateRows: editable ? cloneRows : undefined, onFollow: onAction === undefined ? undefined : follow, canFollow: (cell) => referenceAt(cell) !== null, onRangeChange: setRange, initialLayout: stored ?? undefined, onLayoutChange: remember, focusRequest: focusRequest, columnRequest: columnRequest }) }) }), _jsx(TableStatusBar, { elapsedMs: loaded.elapsedMs, bounds: bounds, counting: counting, selection: selectionFigures, onCount: () => void countRows(), onPrevious: () => goToPage(offset - pageSize), onNext: () => goToPage(offset + pageSize) })] })), transfer.importDraft !== null && structure !== null && (_jsx(ImportDialog, { open: true, path: transfer.importDraft.path, tableColumns: importableColumns(structure.columns), sample: transfer.importDraft.sample, header: transfer.importDraft.header, mapping: transfer.importDraft.mapping, busy: transfer.importDraft.busy, error: transfer.importDraft.error, onHeaderChange: transfer.changeHeader, onMappingChange: transfer.changeMapping, onImport: transfer.runImport, onCancel: transfer.closeImport, onOpenChange: (open) => !open && transfer.closeImport() })), _jsx(PromptDialog, { open: discard !== null, danger: true, title: t('table.discard.title'), description: t('table.discard.description'), confirmLabel: t('table.discard.confirm'), onConfirm: () => {
|
|
501
473
|
discard?.run();
|
|
502
474
|
setDiscard(null);
|
|
503
475
|
}, onOpenChange: (open) => {
|
package/dist/table/grid-rows.js
CHANGED
|
@@ -5,7 +5,10 @@ import { rowKeyOf } from './pending.js';
|
|
|
5
5
|
export const loadedRowKey = (index) => `row:${index}`;
|
|
6
6
|
export const insertedRowKey = (id) => `new:${id}`;
|
|
7
7
|
export const parseRowKey = (key) => key.startsWith('new:') ? { kind: 'inserted', id: Number(key.slice(4)) } : { kind: 'loaded', index: Number(key.slice(4)) };
|
|
8
|
-
export const buildGridColumns = (loaded, structure) =>
|
|
8
|
+
export const buildGridColumns = (loaded, structure) => {
|
|
9
|
+
const keys = { primaryKey: structure?.primaryKey ?? [], foreignKey: structure?.foreignKeys.flatMap((key) => key.columns) ?? [] };
|
|
10
|
+
return loaded.columns.map((column) => gridColumnOf(column, structure?.columns.find((info) => info.name === column.name), keys));
|
|
11
|
+
};
|
|
9
12
|
/* The loaded rows with their pending edits laid over them, then the rows waiting to be inserted. */
|
|
10
13
|
export const buildGridRows = ({ structure, loaded, pending, offset }) => {
|
|
11
14
|
const rows = loaded.rows.map((cells, index) => {
|
|
@@ -14,6 +14,7 @@ export type TransferNotice = {
|
|
|
14
14
|
readonly rows: number;
|
|
15
15
|
} | {
|
|
16
16
|
readonly kind: 'failed';
|
|
17
|
+
readonly during: 'export' | 'import';
|
|
17
18
|
readonly message: string;
|
|
18
19
|
};
|
|
19
20
|
export interface ImportDraft {
|
|
@@ -35,6 +36,7 @@ export interface TableTransferInput {
|
|
|
35
36
|
readonly where: string;
|
|
36
37
|
readonly orderBy: string;
|
|
37
38
|
readonly onImported: () => void;
|
|
39
|
+
readonly report?: (notice: TransferNotice) => void;
|
|
38
40
|
}
|
|
39
41
|
export interface TableTransfer {
|
|
40
42
|
readonly notice: TransferNotice | null;
|
|
@@ -48,4 +50,4 @@ export interface TableTransfer {
|
|
|
48
50
|
runImport(): void;
|
|
49
51
|
closeImport(): void;
|
|
50
52
|
}
|
|
51
|
-
export declare function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported }: TableTransferInput): TableTransfer;
|
|
53
|
+
export declare function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported, report }: TableTransferInput): TableTransfer;
|
|
@@ -3,7 +3,7 @@ import { messageOf } from '@adecore/ui';
|
|
|
3
3
|
import { DatabaseRequestError } from '../client/types.js';
|
|
4
4
|
import { formatOfPath, importableColumns, mapColumn, matchColumns } from './import-mapping.js';
|
|
5
5
|
/* Export of the table as filtered, and import of a file into it, with the state both need between the dialogs and the strip. */
|
|
6
|
-
export function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported }) {
|
|
6
|
+
export function useTableTransfer({ client, session, files, schema, table, structure, where, orderBy, onImported, report }) {
|
|
7
7
|
const [notice, setNotice] = useState(null);
|
|
8
8
|
const [importDraft, setImportDraft] = useState(null);
|
|
9
9
|
const exporting = useRef(null);
|
|
@@ -17,6 +17,15 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
17
17
|
return () => clearTimeout(timer);
|
|
18
18
|
}, [notice]);
|
|
19
19
|
useEffect(() => () => sampling.current?.abort(), []);
|
|
20
|
+
const show = (next) => {
|
|
21
|
+
if (report === undefined) {
|
|
22
|
+
setNotice(next);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
setNotice(null);
|
|
26
|
+
report(next);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
20
29
|
const exportAs = (format) => {
|
|
21
30
|
if (files === undefined || exporting.current !== null) {
|
|
22
31
|
return;
|
|
@@ -31,11 +40,16 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
31
40
|
setNotice({ kind: 'exporting', format });
|
|
32
41
|
try {
|
|
33
42
|
const result = await session.export({ source: { kind: 'table', schema, table, where: where || undefined, orderBy: orderBy || undefined }, format, path }, { signal: controller.signal });
|
|
34
|
-
|
|
43
|
+
show({ kind: 'exported', rows: result.rows });
|
|
35
44
|
}
|
|
36
45
|
catch (error) {
|
|
37
46
|
const cancelled = controller.signal.aborted || (error instanceof DatabaseRequestError && error.code === 'cancelled');
|
|
38
|
-
|
|
47
|
+
if (cancelled) {
|
|
48
|
+
setNotice(null);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
show({ kind: 'failed', during: 'export', message: messageOf(error) });
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
54
|
finally {
|
|
41
55
|
exporting.current = null;
|
|
@@ -63,7 +77,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
63
77
|
setImportDraft({ path, format, header: true, sample: read, mapping: matchColumns(read.columns, tableColumns, true), busy: false, error: null });
|
|
64
78
|
}
|
|
65
79
|
catch (error) {
|
|
66
|
-
|
|
80
|
+
show({ kind: 'failed', during: 'import', message: messageOf(error) });
|
|
67
81
|
}
|
|
68
82
|
})();
|
|
69
83
|
};
|
|
@@ -98,7 +112,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
98
112
|
try {
|
|
99
113
|
const rows = await session.import(schema, table, { path, format, header, columns: mapping });
|
|
100
114
|
setImportDraft(null);
|
|
101
|
-
|
|
115
|
+
show({ kind: 'imported', rows });
|
|
102
116
|
onImported();
|
|
103
117
|
}
|
|
104
118
|
catch (error) {
|
package/dist/testing/fake.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ColumnInfo, DockerContainer, ForeignKeyInfo, IndexInfo, ServerInfo, TableKind, Value } from '../protocol/index.ts';
|
|
1
|
+
import type { CheckInfo, ColumnInfo, DockerContainer, ForeignKeyInfo, IndexInfo, ServerInfo, TableKind, TriggerInfo, Value } from '../protocol/index.ts';
|
|
2
2
|
import type { DatabaseTransport } from '../client/types.ts';
|
|
3
3
|
export interface FakeTable {
|
|
4
4
|
readonly kind?: TableKind;
|
|
@@ -7,6 +7,8 @@ export interface FakeTable {
|
|
|
7
7
|
readonly rows: readonly (readonly Value[])[];
|
|
8
8
|
readonly indexes?: readonly IndexInfo[];
|
|
9
9
|
readonly foreignKeys?: readonly ForeignKeyInfo[];
|
|
10
|
+
readonly checks?: readonly CheckInfo[];
|
|
11
|
+
readonly triggers?: readonly TriggerInfo[];
|
|
10
12
|
readonly ddl?: string;
|
|
11
13
|
}
|
|
12
14
|
export interface FakeDatabase {
|
package/dist/testing/fake.js
CHANGED
|
@@ -18,12 +18,16 @@ const toTables = (database) => new Map(Object.entries(database.schemas).map(([sc
|
|
|
18
18
|
new Map(Object.entries(tables).map(([name, table]) => [
|
|
19
19
|
name,
|
|
20
20
|
{
|
|
21
|
+
schema,
|
|
22
|
+
name,
|
|
21
23
|
kind: table.kind ?? 'table',
|
|
22
24
|
columns: structuredClone([...table.columns]),
|
|
23
25
|
primaryKey: [...(table.primaryKey ?? [])],
|
|
24
26
|
rows: structuredClone(table.rows.map((row) => [...row])),
|
|
25
27
|
indexes: structuredClone([...(table.indexes ?? [])]),
|
|
26
28
|
foreignKeys: structuredClone([...(table.foreignKeys ?? [])]),
|
|
29
|
+
checks: table.checks === undefined ? undefined : structuredClone([...table.checks]),
|
|
30
|
+
triggers: table.triggers === undefined ? undefined : structuredClone([...table.triggers]),
|
|
27
31
|
ddl: table.ddl ?? null
|
|
28
32
|
}
|
|
29
33
|
]))
|
|
@@ -71,7 +75,7 @@ const toCell = (value, cellLimit) => {
|
|
|
71
75
|
}
|
|
72
76
|
return value;
|
|
73
77
|
};
|
|
74
|
-
const toResultColumns = (table) => table.columns.map(({ name, type, kind }) => ({ name, type, kind }));
|
|
78
|
+
const toResultColumns = (table) => table.columns.map(({ name, type, kind }) => ({ name, type, kind, source: { schema: table.schema, table: table.name, column: name } }));
|
|
75
79
|
const toRowsResult = (table, rows, offset, limit, cellLimit) => ({
|
|
76
80
|
columns: toResultColumns(table),
|
|
77
81
|
rows: rows.slice(offset, offset + limit).map((row) => row.map((value) => toCell(value, cellLimit))),
|
|
@@ -259,6 +263,8 @@ export const fakeDatabaseTransport = (options) => {
|
|
|
259
263
|
rowKey: rowKeyOf(table),
|
|
260
264
|
indexes: table.indexes,
|
|
261
265
|
foreignKeys: table.foreignKeys,
|
|
266
|
+
...(table.checks === undefined ? {} : { checks: table.checks }),
|
|
267
|
+
...(table.triggers === undefined ? {} : { triggers: table.triggers }),
|
|
262
268
|
ddl: table.ddl
|
|
263
269
|
};
|
|
264
270
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GridColumn } from '../grid/types.ts';
|
|
2
|
+
import type { EditValue, Value } from '../protocol/index.ts';
|
|
3
|
+
export interface ValueEditorProps {
|
|
4
|
+
column: GridColumn;
|
|
5
|
+
value: Value;
|
|
6
|
+
editable: boolean;
|
|
7
|
+
onCommit?(value: EditValue): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ValueEditor({ column, value, editable, onCommit }: ValueEditorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { Copy, Ellipsis, WrapText } from 'lucide-react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Button, ButtonGroup, copyText, FormError, IconButton, isApplePlatform, KEY_SHORTCUTS, matchesShortcut, Menu, Segmented, TextArea, Tooltip } from '@adecore/ui';
|
|
7
|
+
import { formatBytes, formatNumber } from '@adecore/ui/format';
|
|
8
|
+
import { CODE_TEXT } from '../code-text.js';
|
|
9
|
+
import { editValueOf, sameDraft, sameSource, sourceOf } from './draft.js';
|
|
10
|
+
import { hexDumpLines, parseHex } from './hex.js';
|
|
11
|
+
import { renderView, sizeOf, viewsOf } from './views.js';
|
|
12
|
+
const WRAPPING_VIEWS = new Set(['text', 'formatted', 'utf8']);
|
|
13
|
+
const READING = `min-h-0 flex-1 overflow-auto rounded-lg border border-border bg-surface p-2 ${CODE_TEXT} text-text`;
|
|
14
|
+
/*
|
|
15
|
+
* The whole of one value, to read and change what a cell is too small for: Text, Formatted for JSON, or
|
|
16
|
+
* Hex, Text and UUID for binary, with the checks of JSON and hex before a change is taken.
|
|
17
|
+
*/
|
|
18
|
+
export function ValueEditor({ column, value, editable, onCommit }) {
|
|
19
|
+
const { t } = useTranslation('database');
|
|
20
|
+
const { name, type, kind } = column;
|
|
21
|
+
const source = useMemo(() => sourceOf({ name, type, kind }, value), [name, type, kind, value]);
|
|
22
|
+
const [seen, setSeen] = useState(source);
|
|
23
|
+
const [draft, setDraft] = useState(source.original);
|
|
24
|
+
const [choice, setChoice] = useState(null);
|
|
25
|
+
const [problem, setProblem] = useState(null);
|
|
26
|
+
const [wrap, setWrap] = useState(true);
|
|
27
|
+
// A new cell, or the same cell with a new value, starts over from that value.
|
|
28
|
+
if (!sameSource(seen, source)) {
|
|
29
|
+
setSeen(source);
|
|
30
|
+
setDraft(source.original);
|
|
31
|
+
setChoice(null);
|
|
32
|
+
setProblem(null);
|
|
33
|
+
}
|
|
34
|
+
const views = viewsOf(source, draft);
|
|
35
|
+
const view = choice !== null && views.includes(choice) ? choice : views[0];
|
|
36
|
+
const dirty = !sameDraft(draft, source.original);
|
|
37
|
+
const rendered = useMemo(() => (view === 'text' ? null : renderView(view, draft)), [view, draft]);
|
|
38
|
+
const hex = source.binary && draft.mode === 'value' ? parseHex(draft.text) : null;
|
|
39
|
+
const shownProblem = problem ?? (hex !== null && !hex.ok ? { kind: 'hex', problem: hex.problem } : null);
|
|
40
|
+
const size = sizeOf(source, draft);
|
|
41
|
+
const placeholder = draft.mode === 'null' ? 'NULL' : draft.mode === 'default' ? 'DEFAULT' : undefined;
|
|
42
|
+
const change = (next) => {
|
|
43
|
+
setDraft(next);
|
|
44
|
+
setProblem(null);
|
|
45
|
+
};
|
|
46
|
+
const apply = () => {
|
|
47
|
+
if (!editable || onCommit === undefined || !dirty) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const conversion = editValueOf(draft, source);
|
|
51
|
+
if (conversion.ok) {
|
|
52
|
+
onCommit(conversion.value);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
setProblem(conversion.problem);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const handleKeyDown = (event) => {
|
|
59
|
+
if (!event.nativeEvent.isComposing && matchesShortcut(KEY_SHORTCUTS.modEnter, event.nativeEvent, isApplePlatform())) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
apply();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const copy = () => {
|
|
65
|
+
if (view === 'hex' && hex !== null && hex.ok) {
|
|
66
|
+
copyText(hexDumpLines(hex.hex).join('\n'));
|
|
67
|
+
}
|
|
68
|
+
else if (rendered !== null && rendered.kind === 'text') {
|
|
69
|
+
copyText(rendered.text);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
copyText(draft.mode === 'value' ? draft.text : draft.mode === 'null' ? 'NULL' : 'DEFAULT');
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const editor = (extra) => (_jsx(TextArea, { "aria-label": t(source.binary ? 'value.hexEditor' : 'value.editor', { column: column.name }), wrap: wrap || source.binary ? 'soft' : 'off', spellCheck: false, readOnly: !editable, placeholder: placeholder, value: draft.mode === 'value' ? draft.text : '', className: clsx(CODE_TEXT, extra), onChange: (event) => change({ text: event.target.value, mode: 'value' }), onKeyDown: handleKeyDown }));
|
|
76
|
+
const problemText = (found) => found.kind === 'json'
|
|
77
|
+
? t('value.invalidJson', { reason: found.reason })
|
|
78
|
+
: t(found.problem === 'odd' ? 'value.invalidHexOdd' : 'value.invalidHexCharacters');
|
|
79
|
+
const reading = (content, wrapped) => {
|
|
80
|
+
if (content.kind === 'empty') {
|
|
81
|
+
return _jsx(Faint, { children: placeholder });
|
|
82
|
+
}
|
|
83
|
+
if (content.kind === 'problem') {
|
|
84
|
+
return _jsx(Faint, { children: problemText(content.problem) });
|
|
85
|
+
}
|
|
86
|
+
return (_jsxs(_Fragment, { children: [_jsx("pre", { className: clsx(READING, wrapped ? 'break-words whitespace-pre-wrap' : 'whitespace-pre'), children: content.text }), content.truncated && (_jsx("p", { className: "shrink-0 text-xs text-text-faint", children: t('value.dumpTruncated', { shown: formatNumber(content.truncated.shown), total: formatNumber(content.truncated.total) }) }))] }));
|
|
87
|
+
};
|
|
88
|
+
const body = () => {
|
|
89
|
+
if (view === 'text') {
|
|
90
|
+
return editor('min-h-0 flex-1');
|
|
91
|
+
}
|
|
92
|
+
if (view === 'hex' && editable) {
|
|
93
|
+
return (_jsxs(_Fragment, { children: [draft.mode === 'value' && rendered !== null && reading(rendered, false), editor(draft.mode === 'value' ? 'flex-none' : 'min-h-0 flex-1')] }));
|
|
94
|
+
}
|
|
95
|
+
return rendered === null ? null : reading(rendered, wrap && WRAPPING_VIEWS.has(view));
|
|
96
|
+
};
|
|
97
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex h-10 shrink-0 items-center gap-2 px-2", children: [views.length > 1 && (_jsx(Segmented, { value: view, onValueChange: (id) => setChoice(id), label: t('value.views'), options: views.map((id) => ({ id, label: t(`value.view.${id}`) })) })), _jsxs(ButtonGroup, { className: "ml-auto", children: [WRAPPING_VIEWS.has(view) && (_jsx(IconButton, { icon: WrapText, size: "sm", label: t('value.wrap'), "aria-pressed": wrap, active: wrap, onClick: () => setWrap(!wrap) })), _jsx(IconButton, { icon: Copy, size: "sm", label: t('value.copy'), onClick: copy })] })] }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-2 px-2 pb-2", children: body() }), shownProblem !== null && _jsx(FormError, { className: "shrink-0 px-3 pb-2 text-xs", children: problemText(shownProblem) }), editable && (_jsxs("div", { className: "flex shrink-0 items-center gap-1.5 border-t border-border p-2", children: [_jsxs(Menu.Root, { children: [_jsx(IconButton, { icon: Ellipsis, size: "sm", label: t('value.more'), render: _jsx(Menu.Trigger, {}) }), _jsxs(Menu.Popup, { children: [column.nullable !== false && (_jsx(Menu.Item, { disabled: draft.mode === 'null', onClick: () => change({ text: '', mode: 'null' }), children: t('value.setNull') })), _jsx(Menu.Item, { disabled: draft.mode === 'default', onClick: () => change({ text: '', mode: 'default' }), children: t('value.setDefault') })] })] }), _jsxs("span", { className: "ml-auto flex items-center gap-1.5", children: [_jsx(Button, { size: "xs", disabled: !dirty, onClick: () => change(source.original), children: t('value.revert') }), _jsx(Tooltip, { label: t('value.applyHint'), kbd: KEY_SHORTCUTS.modEnter, children: _jsx(Button, { size: "xs", variant: "primary", disabled: !dirty || onCommit === undefined, onClick: apply, children: t('value.apply') }) })] })] })), _jsx("footer", { className: "flex h-8 shrink-0 items-center border-t border-border px-3 text-xs text-text-muted tabular-nums", children: size !== null &&
|
|
98
|
+
(size.unit === 'bytes' ? formatBytes(size.count) : t('value.characters', { count: size.count, formatted: formatNumber(size.count) })) })] }));
|
|
99
|
+
}
|
|
100
|
+
function Faint({ children }) {
|
|
101
|
+
return _jsx("div", { className: clsx(CODE_TEXT, 'grid min-h-0 flex-1 place-items-center text-text-faint'), children: children });
|
|
102
|
+
}
|