@adecore/database 0.39.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/console/QueryConsole.js +2 -10
- package/dist/console/SchemaPicker.js +0 -1
- package/dist/console/StatementResultView.js +1 -1
- package/dist/ddl/diff.js +1 -1
- package/dist/explorer/tree.js +0 -1
- package/dist/export-formats.d.ts +2 -0
- package/dist/export-formats.js +1 -0
- package/dist/grid/CopyAsMenu.js +0 -1
- package/dist/grid/DataGrid.js +10 -10
- package/dist/grid/column-layout.d.ts +0 -1
- package/dist/grid/column-layout.js +1 -2
- package/dist/grid/column-view.js +3 -2
- package/dist/grid/display.js +1 -2
- package/dist/host/host.js +1 -2
- package/dist/host/validate.d.ts +3 -0
- package/dist/host/validate.js +18 -15
- package/dist/same-list.d.ts +1 -0
- package/dist/same-list.js +1 -0
- package/dist/stored-size.d.ts +1 -0
- package/dist/stored-size.js +10 -0
- package/dist/table/TableToolbar.js +1 -1
- package/dist/table/TableView.js +2 -10
- package/package.json +15 -15
- package/src/DatabaseProvider.tsx +2 -0
- package/src/KeyIcon.tsx +1 -0
- package/src/client/client.ts +13 -0
- package/src/client-context.ts +2 -0
- package/src/connections/ConnectionForm.tsx +11 -0
- package/src/connections/ConnectionManager.tsx +9 -0
- package/src/connections/connection-config.ts +21 -0
- package/src/connections/docker.ts +2 -0
- package/src/connections/use-docker-containers.ts +3 -0
- package/src/console/DestructiveDialog.tsx +1 -0
- package/src/console/QueryConsole.tsx +38 -10
- package/src/console/SchemaPicker.tsx +1 -1
- package/src/console/SqlEditor.tsx +10 -0
- package/src/console/StatementResultView.tsx +7 -2
- package/src/console/TransactionControls.tsx +1 -0
- package/src/console/destructive.ts +11 -0
- package/src/console/editing.ts +10 -0
- package/src/console/history.ts +8 -0
- package/src/console/labels.ts +1 -0
- package/src/console/outcome.ts +1 -0
- package/src/console/result-keys.ts +16 -0
- package/src/console/shortcuts.ts +2 -0
- package/src/ddl/alter-mysql.ts +16 -0
- package/src/ddl/alter-sqlite.ts +21 -0
- package/src/ddl/alter.ts +2 -0
- package/src/ddl/column.ts +11 -0
- package/src/ddl/create.ts +15 -0
- package/src/ddl/dialect.ts +4 -0
- package/src/ddl/diff.ts +22 -2
- package/src/ddl/draft.ts +1 -0
- package/src/ddl/parse.ts +31 -0
- package/src/ddl/types.ts +3 -0
- package/src/ddl/validate.ts +12 -0
- package/src/designer/ColumnsEditor.tsx +3 -0
- package/src/designer/DesignerView.tsx +2 -0
- package/src/designer/ForeignKeysEditor.tsx +4 -0
- package/src/designer/IndexesEditor.tsx +1 -0
- package/src/designer/NamePicker.tsx +2 -0
- package/src/designer/OptionsEditor.tsx +4 -0
- package/src/designer/SqlPreview.tsx +2 -0
- package/src/designer/TableDesigner.tsx +14 -0
- package/src/designer/edit.ts +13 -0
- package/src/designer/plan.ts +3 -0
- package/src/designer/recover.ts +1 -0
- package/src/designer/useReferenceColumns.ts +5 -0
- package/src/explorer/DatabaseExplorer.tsx +40 -0
- package/src/explorer/RowMenu.tsx +2 -0
- package/src/explorer/TableDialog.tsx +6 -0
- package/src/explorer/expansion.ts +8 -0
- package/src/explorer/tree.ts +73 -2
- package/src/explorer/use-explorer-loads.ts +15 -0
- package/src/explorer/use-stored-expansion.ts +4 -0
- package/src/export-formats.ts +3 -0
- package/src/grid/CellEditor.tsx +1 -0
- package/src/grid/CopyAsMenu.tsx +1 -1
- package/src/grid/DataGrid.tsx +93 -23
- package/src/grid/EnumPicker.tsx +6 -0
- package/src/grid/GridHeaderCell.tsx +11 -0
- package/src/grid/GridRowView.tsx +3 -0
- package/src/grid/RecordView.tsx +19 -0
- package/src/grid/ValueDock.tsx +2 -0
- package/src/grid/aggregates.ts +12 -0
- package/src/grid/column-layout.ts +12 -4
- package/src/grid/column-selection.ts +9 -0
- package/src/grid/column-view.ts +6 -2
- package/src/grid/copy-formats.ts +13 -0
- package/src/grid/display.ts +17 -3
- package/src/grid/edit-value.ts +11 -0
- package/src/grid/enum-type.ts +15 -0
- package/src/grid/focused-value.ts +4 -0
- package/src/grid/layout.ts +12 -0
- package/src/grid/navigation.ts +5 -0
- package/src/grid/row-selection.ts +7 -0
- package/src/grid/sort.ts +1 -0
- package/src/grid/whole-cells.ts +8 -0
- package/src/host/host.ts +16 -3
- package/src/host/spawn.ts +2 -0
- package/src/host/validate.ts +37 -15
- package/src/same-list.ts +2 -0
- package/src/sql-split.ts +28 -0
- package/src/sql.ts +20 -0
- package/src/stored-size.ts +10 -0
- package/src/structure/StructureView.tsx +18 -0
- package/src/table/CommandField.tsx +11 -0
- package/src/table/ImportDialog.tsx +4 -0
- package/src/table/TableStatusBar.tsx +1 -0
- package/src/table/TableToolbar.tsx +2 -2
- package/src/table/TableView.tsx +50 -10
- package/src/table/command-field.ts +40 -0
- package/src/table/grid-rows.ts +13 -0
- package/src/table/import-mapping.ts +5 -0
- package/src/table/layout-store.ts +7 -0
- package/src/table/paging.ts +1 -0
- package/src/table/pending.ts +18 -0
- package/src/table/references.ts +4 -0
- package/src/table/useLoaded.ts +3 -0
- package/src/table/useStoredLayout.ts +2 -0
- package/src/table/useTableTransfer.ts +17 -1
- package/src/testing/fake.ts +24 -0
- package/src/use-database-resources.ts +1 -0
- package/src/use-popup-press.ts +3 -0
- package/src/value/ValueEditor.tsx +6 -0
- package/src/value/draft.ts +7 -0
- package/src/value/hex.ts +8 -0
- package/src/value/json.ts +16 -0
- package/src/value/views.ts +14 -0
- package/src/workbench/DatabaseWorkbench.tsx +22 -0
- package/src/workbench/tabs.ts +34 -0
|
@@ -60,6 +60,7 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
|
|
|
60
60
|
started.current.delete(key);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
|
|
63
64
|
setLoads((current) => ({
|
|
64
65
|
entries: new Map([...current.entries].filter(([, entry]) => !within(target, entry.target))),
|
|
65
66
|
versions: target.schema === undefined ? new Map([...current.versions].filter(([id]) => id !== target.connectionId)) : current.versions
|
|
@@ -71,16 +72,20 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
|
|
|
71
72
|
if (!silent) {
|
|
72
73
|
store(target, { status: 'loading' });
|
|
73
74
|
}
|
|
75
|
+
|
|
74
76
|
const session = client.session(connection);
|
|
77
|
+
|
|
75
78
|
const request: Promise<Loaded> =
|
|
76
79
|
target.schema === undefined
|
|
77
80
|
? session.schemas()
|
|
78
81
|
: target.table === undefined
|
|
79
82
|
? session.tables(target.schema)
|
|
80
83
|
: session.structure(target.schema, target.table);
|
|
84
|
+
|
|
81
85
|
request.then(
|
|
82
86
|
(value) => {
|
|
83
87
|
store(target, { status: 'ready', value });
|
|
88
|
+
|
|
84
89
|
if (target.schema === undefined) {
|
|
85
90
|
// Chained after the schemas so the version never opens a session by itself.
|
|
86
91
|
session.server().then(
|
|
@@ -95,13 +100,17 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
|
|
|
95
100
|
|
|
96
101
|
const start = (target: LoadTarget): void => {
|
|
97
102
|
const key = loadKey(target);
|
|
103
|
+
|
|
98
104
|
if (started.current.has(key)) {
|
|
99
105
|
return;
|
|
100
106
|
}
|
|
107
|
+
|
|
101
108
|
const connection = connections.find((entry) => entry.id === target.connectionId);
|
|
109
|
+
|
|
102
110
|
if (connection === undefined) {
|
|
103
111
|
return;
|
|
104
112
|
}
|
|
113
|
+
|
|
105
114
|
started.current.set(key, target);
|
|
106
115
|
run(target, connection, false);
|
|
107
116
|
};
|
|
@@ -110,10 +119,12 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
|
|
|
110
119
|
const reload = (change: SchemaChange): void => {
|
|
111
120
|
const connection = connections.find((entry) => entry.id === change.connectionId);
|
|
112
121
|
const dropped: LoadTarget[] = [];
|
|
122
|
+
|
|
113
123
|
for (const [key, began] of [...started.current]) {
|
|
114
124
|
if (!touches(change, began)) {
|
|
115
125
|
continue;
|
|
116
126
|
}
|
|
127
|
+
|
|
117
128
|
if (began.table !== undefined) {
|
|
118
129
|
started.current.delete(key);
|
|
119
130
|
dropped.push(began);
|
|
@@ -121,6 +132,7 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
|
|
|
121
132
|
run(began, connection, true);
|
|
122
133
|
}
|
|
123
134
|
}
|
|
135
|
+
|
|
124
136
|
if (dropped.length > 0) {
|
|
125
137
|
const gone = new Set(dropped.map(loadKey));
|
|
126
138
|
setLoads((current) => ({ ...current, entries: new Map([...current.entries].filter(([key]) => !gone.has(key))) }));
|
|
@@ -132,13 +144,16 @@ export function useExplorerLoads(connections: readonly Connection[]): ExplorerLo
|
|
|
132
144
|
// A connection that was edited or removed is a different session, so what it listed is stale.
|
|
133
145
|
useEffect(() => {
|
|
134
146
|
const known = configs.current;
|
|
147
|
+
|
|
135
148
|
for (const [id, config] of known) {
|
|
136
149
|
const now = connections.find((entry) => entry.id === id);
|
|
150
|
+
|
|
137
151
|
if (now === undefined || now.config !== config) {
|
|
138
152
|
known.delete(id);
|
|
139
153
|
reset({ connectionId: id });
|
|
140
154
|
}
|
|
141
155
|
}
|
|
156
|
+
|
|
142
157
|
for (const connection of connections) {
|
|
143
158
|
known.set(connection.id, connection.config);
|
|
144
159
|
}
|
|
@@ -15,15 +15,18 @@ const restore = (storage: DatabaseStorage | undefined, connections: readonly Con
|
|
|
15
15
|
const expanded = new Set(from.expanded);
|
|
16
16
|
const collapsed = new Set(from.collapsed);
|
|
17
17
|
const loaded = new Set(from.loaded);
|
|
18
|
+
|
|
18
19
|
for (const { id } of connections) {
|
|
19
20
|
if (loaded.has(id)) {
|
|
20
21
|
continue;
|
|
21
22
|
}
|
|
23
|
+
|
|
22
24
|
loaded.add(id);
|
|
23
25
|
const stored = parseExpansion(storage?.get(expansionKey(id)) ?? null, id);
|
|
24
26
|
stored.expanded.forEach((key) => expanded.add(key));
|
|
25
27
|
stored.collapsed.forEach((key) => collapsed.add(key));
|
|
26
28
|
}
|
|
29
|
+
|
|
27
30
|
return { expanded, collapsed, loaded };
|
|
28
31
|
};
|
|
29
32
|
|
|
@@ -51,6 +54,7 @@ export function useStoredExpansion(connections: readonly Connection[]): StoredEx
|
|
|
51
54
|
const next = serializeExpansion(id, state);
|
|
52
55
|
const last = written.current.get(id);
|
|
53
56
|
written.current.set(id, next);
|
|
57
|
+
|
|
54
58
|
if (last !== undefined && last !== next) {
|
|
55
59
|
storage?.set(expansionKey(id), next);
|
|
56
60
|
}
|
package/src/grid/CellEditor.tsx
CHANGED
package/src/grid/CopyAsMenu.tsx
CHANGED
|
@@ -11,9 +11,9 @@ export interface CopyAsMenuProps {
|
|
|
11
11
|
onCopy(format: CopyFormat): void;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/* The "Copy as" submenu of the grid's context menu. */
|
|
15
14
|
export function CopyAsMenu({ disabled, formats, onCopy }: CopyAsMenuProps) {
|
|
16
15
|
const { t } = useTranslation('database');
|
|
16
|
+
|
|
17
17
|
return (
|
|
18
18
|
<ContextMenu.SubmenuRoot>
|
|
19
19
|
<ContextMenu.SubmenuTrigger disabled={disabled}>
|
package/src/grid/DataGrid.tsx
CHANGED
|
@@ -133,14 +133,11 @@ const signatureOf = (columns: readonly GridColumn[]): string => columns.map((col
|
|
|
133
133
|
|
|
134
134
|
const textOf = (view: CellView): string => (view.suffix === undefined ? view.text : `${view.text} ${view.suffix}`);
|
|
135
135
|
|
|
136
|
+
const columnTexts = (column: GridColumn, index: number, rows: readonly GridRow[], notation: NumberNotation): string[] =>
|
|
137
|
+
rows.map((row) => textOf(cellView(row.cells[index] ?? null, column.kind, columnNotation(column, notation))));
|
|
138
|
+
|
|
136
139
|
const estimateWidths = (columns: readonly GridColumn[], rows: readonly GridRow[], notation: NumberNotation): number[] =>
|
|
137
|
-
columns.map((column, index) =>
|
|
138
|
-
estimateColumnWidth(
|
|
139
|
-
column.name,
|
|
140
|
-
rows.slice(0, 50).map((row) => textOf(cellView(row.cells[index] ?? null, column.kind, columnNotation(column, notation)))),
|
|
141
|
-
column.primaryKey === true
|
|
142
|
-
)
|
|
143
|
-
);
|
|
140
|
+
columns.map((column, index) => estimateColumnWidth(column.name, columnTexts(column, index, rows.slice(0, 50), notation), column.primaryKey === true));
|
|
144
141
|
|
|
145
142
|
/*
|
|
146
143
|
* The table of the table view and of the console, and of any rows an app has on its own: it needs no
|
|
@@ -201,6 +198,7 @@ export function DataGrid({
|
|
|
201
198
|
const [menuTarget, setMenuTarget] = useState<MenuTarget | null>(null);
|
|
202
199
|
const [editing, setEditing] = useState<CellEdit | null>(null);
|
|
203
200
|
const [ownSelection, setOwnSelection] = useState<ReadonlySet<string>>(NO_KEYS);
|
|
201
|
+
|
|
204
202
|
const [layout, setLayout] = useState<Layout>(() => ({
|
|
205
203
|
signature: signatureOf(columns),
|
|
206
204
|
rows,
|
|
@@ -208,13 +206,16 @@ export function DataGrid({
|
|
|
208
206
|
estimated: estimateWidths(columns, rows, notation),
|
|
209
207
|
...restoreLayout(columns, initialLayout)
|
|
210
208
|
}));
|
|
209
|
+
|
|
211
210
|
const reportedLayout = useRef<GridLayout>(captureLayout(columns, layout));
|
|
212
211
|
|
|
213
212
|
const signature = signatureOf(columns);
|
|
214
213
|
let current = layout;
|
|
214
|
+
|
|
215
215
|
if (layout.signature !== signature || layout.rows !== rows || layout.notation !== notation) {
|
|
216
216
|
const same = layout.signature === signature;
|
|
217
217
|
const estimated = estimateWidths(columns, rows, notation);
|
|
218
|
+
|
|
218
219
|
current = {
|
|
219
220
|
signature,
|
|
220
221
|
rows,
|
|
@@ -222,6 +223,7 @@ export function DataGrid({
|
|
|
222
223
|
estimated: same ? growWidths(layout.estimated, estimated) : estimated,
|
|
223
224
|
...(same ? { resized: layout.resized, view: layout.view } : restoreLayout(columns, initialLayout))
|
|
224
225
|
};
|
|
226
|
+
|
|
225
227
|
setLayout(current);
|
|
226
228
|
}
|
|
227
229
|
|
|
@@ -229,10 +231,12 @@ export function DataGrid({
|
|
|
229
231
|
const order = useMemo(() => displayOrder(columns.length, current.view), [columns.length, current.view]);
|
|
230
232
|
const pinnedShown = pinnedCount(columns.length, current.view);
|
|
231
233
|
const displayOf = useMemo(() => new Map(order.map((index, position) => [index, position])), [order]);
|
|
234
|
+
|
|
232
235
|
const widths = useMemo(
|
|
233
236
|
() => columns.map((_, index) => current.resized[index] ?? current.estimated[index] ?? clampColumnWidth(0)),
|
|
234
237
|
[columns, current.resized, current.estimated]
|
|
235
238
|
);
|
|
239
|
+
|
|
236
240
|
const shownWidths = useMemo(() => order.map((index) => widths[index]!), [order, widths]);
|
|
237
241
|
const offsets = columnOffsets(shownWidths);
|
|
238
242
|
const gutter = gutterWidth(rows.length);
|
|
@@ -241,12 +245,17 @@ export function DataGrid({
|
|
|
241
245
|
const viewportHeight = size.height > 0 ? size.height : FALLBACK_VIEWPORT_HEIGHT;
|
|
242
246
|
const range = drawnRange(scrolledBlock, viewportHeight, rows.length);
|
|
243
247
|
const selection = selectedKeys ?? ownSelection;
|
|
248
|
+
|
|
244
249
|
const isShown = (position: CellPosition | null): position is CellPosition =>
|
|
245
250
|
position !== null && position.row < rows.length && displayOf.has(position.column);
|
|
251
|
+
|
|
246
252
|
const focusCell = isShown(focused) ? focused : null;
|
|
247
253
|
const anchorCell = isShown(rangeAnchor) ? rangeAnchor : null;
|
|
254
|
+
|
|
248
255
|
const toDisplay = (position: CellPosition): CellPosition => ({ row: position.row, column: displayOf.get(position.column)! });
|
|
256
|
+
|
|
249
257
|
const fromDisplay = (position: CellPosition): CellPosition => ({ row: position.row, column: order[position.column]! });
|
|
258
|
+
|
|
250
259
|
const rect = focusCell === null ? null : rectBetween(toDisplay(anchorCell ?? focusCell), toDisplay(focusCell));
|
|
251
260
|
const page = Math.max(1, Math.floor((viewportHeight - HEADER_HEIGHT) / ROW_HEIGHT));
|
|
252
261
|
const sortable = onSortsChange !== undefined;
|
|
@@ -259,7 +268,9 @@ export function DataGrid({
|
|
|
259
268
|
const cellBlock: RangeBlock | null = rect === null ? null : { top: rect.top, bottom: rect.bottom, columns: order.slice(rect.left, rect.right + 1) };
|
|
260
269
|
const block = columnBlock ?? cellBlock;
|
|
261
270
|
const blockKey = block === null ? '' : `${block.top}:${block.bottom}:${block.columns.join(',')}`;
|
|
271
|
+
|
|
262
272
|
const headerId = (column: number): string => `${gridId}-h-${column}`;
|
|
273
|
+
|
|
263
274
|
const enumTypes = useMemo(() => columns.map((column) => parseEnumType(column.type)), [columns]);
|
|
264
275
|
|
|
265
276
|
useEffect(() => {
|
|
@@ -270,6 +281,7 @@ export function DataGrid({
|
|
|
270
281
|
|
|
271
282
|
useEffect(() => {
|
|
272
283
|
const captured = captureLayout(columns, current);
|
|
284
|
+
|
|
273
285
|
if (!sameLayout(captured, reportedLayout.current)) {
|
|
274
286
|
reportedLayout.current = captured;
|
|
275
287
|
reportLayout.current?.(captured);
|
|
@@ -283,8 +295,10 @@ export function DataGrid({
|
|
|
283
295
|
useEffect(() => {
|
|
284
296
|
if (blockKey === '') {
|
|
285
297
|
reportRange.current?.(null);
|
|
298
|
+
|
|
286
299
|
return;
|
|
287
300
|
}
|
|
301
|
+
|
|
288
302
|
const [top, bottom, list] = blockKey.split(':');
|
|
289
303
|
reportRange.current?.({ top: Number(top), bottom: Number(bottom), columns: list === '' ? [] : list!.split(',').map(Number) });
|
|
290
304
|
}, [blockKey]);
|
|
@@ -293,9 +307,12 @@ export function DataGrid({
|
|
|
293
307
|
if (focusRequest === null || focusRequest === undefined) {
|
|
294
308
|
return;
|
|
295
309
|
}
|
|
310
|
+
|
|
296
311
|
const index = rows.findIndex((row) => row.key === focusRequest.rowKey);
|
|
312
|
+
|
|
297
313
|
if (index >= 0) {
|
|
298
314
|
focusAt({ row: index, column: focusRequest.column });
|
|
315
|
+
|
|
299
316
|
if (focusRequest.quiet !== true) {
|
|
300
317
|
scroller.current?.focus();
|
|
301
318
|
}
|
|
@@ -318,8 +335,10 @@ export function DataGrid({
|
|
|
318
335
|
const stop = (): void => {
|
|
319
336
|
dragging.current = false;
|
|
320
337
|
};
|
|
338
|
+
|
|
321
339
|
window.addEventListener('pointerup', stop);
|
|
322
340
|
window.addEventListener('pointercancel', stop);
|
|
341
|
+
|
|
323
342
|
return () => {
|
|
324
343
|
window.removeEventListener('pointerup', stop);
|
|
325
344
|
window.removeEventListener('pointercancel', stop);
|
|
@@ -340,19 +359,24 @@ export function DataGrid({
|
|
|
340
359
|
const canEdit = (position: CellPosition): boolean => {
|
|
341
360
|
const row = rows[position.row];
|
|
342
361
|
const column = columns[position.column];
|
|
362
|
+
|
|
343
363
|
if (!editable || row === undefined || column === undefined || row.locked === true || row.state === 'deleted' || column.readOnly === true) {
|
|
344
364
|
return false;
|
|
345
365
|
}
|
|
366
|
+
|
|
346
367
|
return !isPreview(row.cells[position.column] ?? null) || loadValue !== undefined;
|
|
347
368
|
};
|
|
348
369
|
|
|
349
370
|
const revealColumn = (column: number): void => {
|
|
350
371
|
const element = scroller.current;
|
|
351
372
|
const at = displayOf.get(column);
|
|
373
|
+
|
|
352
374
|
if (element === null || at === undefined || at < pinnedShown) {
|
|
353
375
|
return;
|
|
354
376
|
}
|
|
377
|
+
|
|
355
378
|
const left = scrollToReveal(gutter + (offsets[at] ?? 0), shownWidths[at] ?? 0, element.scrollLeft, element.clientWidth, gutter + pinnedWidth);
|
|
379
|
+
|
|
356
380
|
if (left !== element.scrollLeft) {
|
|
357
381
|
element.scrollLeft = left;
|
|
358
382
|
}
|
|
@@ -360,13 +384,17 @@ export function DataGrid({
|
|
|
360
384
|
|
|
361
385
|
const reveal = (position: CellPosition): void => {
|
|
362
386
|
const element = scroller.current;
|
|
387
|
+
|
|
363
388
|
if (element === null) {
|
|
364
389
|
return;
|
|
365
390
|
}
|
|
391
|
+
|
|
366
392
|
const top = scrollToReveal(HEADER_HEIGHT + position.row * ROW_HEIGHT, ROW_HEIGHT, element.scrollTop, element.clientHeight, HEADER_HEIGHT);
|
|
393
|
+
|
|
367
394
|
if (top !== element.scrollTop) {
|
|
368
395
|
element.scrollTop = top;
|
|
369
396
|
}
|
|
397
|
+
|
|
370
398
|
revealColumn(position.column);
|
|
371
399
|
};
|
|
372
400
|
|
|
@@ -385,6 +413,7 @@ export function DataGrid({
|
|
|
385
413
|
|
|
386
414
|
const step = (from: CellPosition | null, input: NavigationKey): CellPosition | null => {
|
|
387
415
|
const next = moveFocus(isShown(from) ? toDisplay(from) : null, input, { rows: rows.length, columns: order.length, page });
|
|
416
|
+
|
|
388
417
|
return next === null ? null : fromDisplay(next);
|
|
389
418
|
};
|
|
390
419
|
|
|
@@ -392,31 +421,41 @@ export function DataGrid({
|
|
|
392
421
|
if (selectedKeys === undefined) {
|
|
393
422
|
setOwnSelection(next);
|
|
394
423
|
}
|
|
424
|
+
|
|
395
425
|
onSelectedKeysChange?.(next);
|
|
396
426
|
};
|
|
397
427
|
|
|
398
428
|
const beginEdit = async (position: CellPosition): Promise<void> => {
|
|
399
429
|
const row = rows[position.row];
|
|
430
|
+
|
|
400
431
|
if (row === undefined || !canEdit(position)) {
|
|
401
432
|
return;
|
|
402
433
|
}
|
|
434
|
+
|
|
403
435
|
const cell = row.cells[position.column] ?? null;
|
|
404
436
|
settled.current = false;
|
|
437
|
+
|
|
405
438
|
if (!isPreview(cell)) {
|
|
406
439
|
const text = draftOf(cell);
|
|
407
440
|
setEditing({ ...position, draft: text, initial: text, loading: false });
|
|
441
|
+
|
|
408
442
|
return;
|
|
409
443
|
}
|
|
444
|
+
|
|
410
445
|
setEditing({ ...position, draft: '', initial: '', loading: true });
|
|
411
446
|
const value = await loadValue?.(row.key, position.column);
|
|
447
|
+
|
|
412
448
|
setEditing((now) => {
|
|
413
449
|
if (now === null || !now.loading || now.row !== position.row || now.column !== position.column) {
|
|
414
450
|
return now;
|
|
415
451
|
}
|
|
452
|
+
|
|
416
453
|
if (value === undefined) {
|
|
417
454
|
return null;
|
|
418
455
|
}
|
|
456
|
+
|
|
419
457
|
const text = draftOf(value);
|
|
458
|
+
|
|
420
459
|
return { ...now, draft: text, initial: text, loading: false };
|
|
421
460
|
});
|
|
422
461
|
};
|
|
@@ -425,16 +464,21 @@ export function DataGrid({
|
|
|
425
464
|
if (editing === null || settled.current) {
|
|
426
465
|
return;
|
|
427
466
|
}
|
|
467
|
+
|
|
428
468
|
settled.current = true;
|
|
429
469
|
const column = columns[editing.column];
|
|
430
470
|
const row = rows[editing.row];
|
|
471
|
+
|
|
431
472
|
if (commit && !editing.loading && editing.draft !== editing.initial && column !== undefined && row !== undefined) {
|
|
432
473
|
onCommit?.(row.key, editing.column, parseDraft(editing.draft, column.kind));
|
|
433
474
|
}
|
|
475
|
+
|
|
434
476
|
setEditing(null);
|
|
435
477
|
scroller.current?.focus();
|
|
478
|
+
|
|
436
479
|
if (move !== undefined) {
|
|
437
480
|
const next = step(editing, { key: 'Tab', shiftKey: move === 'previous', mod: false });
|
|
481
|
+
|
|
438
482
|
if (next !== null) {
|
|
439
483
|
focusAt(next);
|
|
440
484
|
}
|
|
@@ -445,33 +489,40 @@ export function DataGrid({
|
|
|
445
489
|
if (editing === null || settled.current) {
|
|
446
490
|
return;
|
|
447
491
|
}
|
|
492
|
+
|
|
448
493
|
settled.current = true;
|
|
449
494
|
const row = rows[editing.row];
|
|
450
495
|
const before = row?.cells[editing.column] ?? null;
|
|
496
|
+
|
|
451
497
|
if (result !== undefined && row !== undefined && result.value !== (typeof before === 'string' ? before : null)) {
|
|
452
498
|
onCommit?.(row.key, editing.column, result.value);
|
|
453
499
|
}
|
|
500
|
+
|
|
454
501
|
setEditing(null);
|
|
455
502
|
scroller.current?.focus();
|
|
456
503
|
};
|
|
457
504
|
|
|
458
505
|
const followCell = (cell: FocusedCell): boolean => onFollow !== undefined && (canFollow?.(cell) ?? true);
|
|
459
506
|
|
|
507
|
+
const menuCell: CellPosition | null = menuTarget === null || menuTarget.column === null ? null : { row: menuTarget.row, column: menuTarget.column };
|
|
508
|
+
|
|
460
509
|
const commitValue = (value: EditValue): void => {
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
if (
|
|
464
|
-
onCommit?.(row.key,
|
|
510
|
+
const row = menuCell === null ? undefined : rows[menuCell.row];
|
|
511
|
+
|
|
512
|
+
if (menuCell !== null && row !== undefined && canEdit(menuCell)) {
|
|
513
|
+
onCommit?.(row.key, menuCell.column, value);
|
|
465
514
|
}
|
|
466
515
|
};
|
|
467
516
|
|
|
468
517
|
/* The cells from row `top` to `bottom` in the given columns, with the whole value of every preview `loadValue` can fetch. */
|
|
469
518
|
const wholeCellsOf = async (top: number, bottom: number, indexes: readonly number[]): Promise<readonly (readonly Shown[])[]> => {
|
|
470
519
|
const shown = rows.slice(top, bottom + 1);
|
|
520
|
+
|
|
471
521
|
const whole = await loadWholeCells(
|
|
472
522
|
shown.map((row) => indexes.map((index) => row.cells[index] ?? null)),
|
|
473
523
|
loadValue === undefined ? undefined : (row, column) => loadValue(shown[row]!.key, indexes[column]!)
|
|
474
524
|
);
|
|
525
|
+
|
|
475
526
|
if (whole.previews > 0) {
|
|
476
527
|
onNotice?.({
|
|
477
528
|
tone: 'error',
|
|
@@ -479,6 +530,7 @@ export function DataGrid({
|
|
|
479
530
|
description: t(whole.tooLarge ? 'grid.copyPreviews.tooLarge' : 'grid.copyPreviews.unavailable')
|
|
480
531
|
});
|
|
481
532
|
}
|
|
533
|
+
|
|
482
534
|
return whole.rows;
|
|
483
535
|
};
|
|
484
536
|
|
|
@@ -486,8 +538,10 @@ export function DataGrid({
|
|
|
486
538
|
if (block === null) {
|
|
487
539
|
return;
|
|
488
540
|
}
|
|
541
|
+
|
|
489
542
|
const picked = block.columns;
|
|
490
543
|
const cells = await wholeCellsOf(block.top, block.bottom, picked);
|
|
544
|
+
|
|
491
545
|
await copyText(
|
|
492
546
|
formatCopy(format, {
|
|
493
547
|
columns: picked.map((index) => ({ name: columns[index]!.name, kind: columns[index]!.kind })),
|
|
@@ -512,13 +566,14 @@ export function DataGrid({
|
|
|
512
566
|
};
|
|
513
567
|
|
|
514
568
|
const copyCell = (): void => {
|
|
515
|
-
if (
|
|
516
|
-
void copyOne(
|
|
569
|
+
if (menuCell !== null && rows[menuCell.row] !== undefined) {
|
|
570
|
+
void copyOne(menuCell.row, menuCell.column);
|
|
517
571
|
}
|
|
518
572
|
};
|
|
519
573
|
|
|
520
574
|
const selectAll = (): void => {
|
|
521
575
|
const all = fullRect(rows.length, order.length);
|
|
576
|
+
|
|
522
577
|
if (all !== null) {
|
|
523
578
|
setColumnSelection(NO_COLUMN_SELECTION);
|
|
524
579
|
setRangeAnchor(fromDisplay({ row: all.top, column: all.left }));
|
|
@@ -534,8 +589,10 @@ export function DataGrid({
|
|
|
534
589
|
index,
|
|
535
590
|
click
|
|
536
591
|
);
|
|
592
|
+
|
|
537
593
|
anchor.current = result.anchor;
|
|
538
594
|
setSelection(result.selected);
|
|
595
|
+
|
|
539
596
|
if (!click.mod && order.length > 0) {
|
|
540
597
|
setColumnSelection(NO_COLUMN_SELECTION);
|
|
541
598
|
setRangeAnchor({ row: result.anchor, column: order[order.length - 1]! });
|
|
@@ -547,14 +604,18 @@ export function DataGrid({
|
|
|
547
604
|
if (event.target !== event.currentTarget || event.altKey) {
|
|
548
605
|
return;
|
|
549
606
|
}
|
|
607
|
+
|
|
550
608
|
if (editing !== null) {
|
|
551
609
|
if (event.key === 'Escape') {
|
|
552
610
|
finishEdit(false);
|
|
553
611
|
}
|
|
612
|
+
|
|
554
613
|
return;
|
|
555
614
|
}
|
|
615
|
+
|
|
556
616
|
const mod = isModHeld(event, isApplePlatform());
|
|
557
617
|
const key = event.key.toLowerCase();
|
|
618
|
+
|
|
558
619
|
if (mod && key === 'c') {
|
|
559
620
|
event.preventDefault();
|
|
560
621
|
copyRange();
|
|
@@ -563,8 +624,10 @@ export function DataGrid({
|
|
|
563
624
|
selectAll();
|
|
564
625
|
} else if (mod && key === 'd' && onDuplicateRows !== undefined) {
|
|
565
626
|
event.preventDefault();
|
|
627
|
+
|
|
566
628
|
const keys =
|
|
567
629
|
selection.size > 0 ? rows.filter((row) => selection.has(row.key)).map((row) => row.key) : focusedRowKey === null ? [] : [focusedRowKey];
|
|
630
|
+
|
|
568
631
|
if (keys.length > 0) {
|
|
569
632
|
onDuplicateRows(keys);
|
|
570
633
|
}
|
|
@@ -582,6 +645,7 @@ export function DataGrid({
|
|
|
582
645
|
setColumnSelection(NO_COLUMN_SELECTION);
|
|
583
646
|
} else {
|
|
584
647
|
const next = step(focusCell, { key: event.key, shiftKey: event.shiftKey, mod });
|
|
648
|
+
|
|
585
649
|
if (next !== null) {
|
|
586
650
|
event.preventDefault();
|
|
587
651
|
focusAt(next, event.shiftKey && event.key !== 'Tab');
|
|
@@ -593,12 +657,16 @@ export function DataGrid({
|
|
|
593
657
|
if (event.button !== 0 || isEditing) {
|
|
594
658
|
return;
|
|
595
659
|
}
|
|
660
|
+
|
|
596
661
|
const row = rows[position.row];
|
|
662
|
+
|
|
597
663
|
if (row !== undefined && isModHeld(event, isApplePlatform()) && followCell({ rowKey: row.key, column: position.column })) {
|
|
598
664
|
focusAt(position);
|
|
599
665
|
onFollow?.({ rowKey: row.key, column: position.column });
|
|
666
|
+
|
|
600
667
|
return;
|
|
601
668
|
}
|
|
669
|
+
|
|
602
670
|
const extend = event.shiftKey && focusCell !== null;
|
|
603
671
|
dragOrigin.current = extend ? (anchorCell ?? focusCell) : position;
|
|
604
672
|
dragging.current = true;
|
|
@@ -616,14 +684,17 @@ export function DataGrid({
|
|
|
616
684
|
if (!rectContains(rect, position.row, displayOf.get(position.column)!)) {
|
|
617
685
|
focusAt(position);
|
|
618
686
|
}
|
|
687
|
+
|
|
619
688
|
setMenuTarget(position);
|
|
620
689
|
};
|
|
621
690
|
|
|
622
691
|
const openRowMenu = (index: number): void => {
|
|
623
692
|
const row = rows[index];
|
|
693
|
+
|
|
624
694
|
if (row !== undefined && !selection.has(row.key)) {
|
|
625
695
|
selectRowAt(index, { shiftKey: false, mod: false });
|
|
626
696
|
}
|
|
697
|
+
|
|
627
698
|
setMenuTarget({ row: index, column: null });
|
|
628
699
|
};
|
|
629
700
|
|
|
@@ -633,15 +704,9 @@ export function DataGrid({
|
|
|
633
704
|
|
|
634
705
|
const fitColumn = (index: number): void => {
|
|
635
706
|
const column = columns[index];
|
|
707
|
+
|
|
636
708
|
if (column !== undefined) {
|
|
637
|
-
resizeColumn(
|
|
638
|
-
index,
|
|
639
|
-
fitColumnWidth(
|
|
640
|
-
column.name,
|
|
641
|
-
rows.map((row) => textOf(cellView(row.cells[index] ?? null, column.kind, columnNotation(column, notation)))),
|
|
642
|
-
column.primaryKey === true
|
|
643
|
-
)
|
|
644
|
-
);
|
|
709
|
+
resizeColumn(index, fitColumnWidth(column.name, columnTexts(column, index, rows, notation), column.primaryKey === true));
|
|
645
710
|
}
|
|
646
711
|
};
|
|
647
712
|
|
|
@@ -684,14 +749,17 @@ export function DataGrid({
|
|
|
684
749
|
const stableDragTo = useStableCallback(dragTo);
|
|
685
750
|
const stableBeginEdit = useStableCallback((position: CellPosition) => void beginEdit(position));
|
|
686
751
|
const stableOpenCellMenu = useStableCallback(openCellMenu);
|
|
752
|
+
|
|
687
753
|
const stablePressRowNumber = useStableCallback((index: number, event: MouseEvent) => {
|
|
688
754
|
if (!pressedInPopup()) {
|
|
689
755
|
selectRowAt(index, { shiftKey: event.shiftKey, mod: isModHeld(event, isApplePlatform()) });
|
|
690
756
|
}
|
|
691
757
|
});
|
|
758
|
+
|
|
692
759
|
const stableOpenRowMenu = useStableCallback(openRowMenu);
|
|
693
760
|
const stableFinishPick = useStableCallback(finishPick);
|
|
694
761
|
const stableFinishEdit = useStableCallback(finishEdit);
|
|
762
|
+
|
|
695
763
|
const rowActions = useMemo(
|
|
696
764
|
(): RowActions => ({
|
|
697
765
|
pressCell: stablePressCell,
|
|
@@ -710,6 +778,7 @@ export function DataGrid({
|
|
|
710
778
|
const renderRow = (index: number): ReactNode => {
|
|
711
779
|
const row = rows[index]!;
|
|
712
780
|
const inRect = columnBlock === null && rect !== null && index >= rect.top && index <= rect.bottom;
|
|
781
|
+
|
|
713
782
|
return (
|
|
714
783
|
<GridRowView
|
|
715
784
|
key={row.key}
|
|
@@ -728,6 +797,7 @@ export function DataGrid({
|
|
|
728
797
|
|
|
729
798
|
const headerCells = order.map((columnIndex, position) => {
|
|
730
799
|
const column = columns[columnIndex]!;
|
|
800
|
+
|
|
731
801
|
return (
|
|
732
802
|
<GridHeaderCell
|
|
733
803
|
key={columnIndex}
|
|
@@ -752,11 +822,11 @@ export function DataGrid({
|
|
|
752
822
|
});
|
|
753
823
|
|
|
754
824
|
const menuRow = menuTarget === null ? undefined : rows[menuTarget.row];
|
|
755
|
-
const menuCell: CellPosition | null = menuTarget === null || menuTarget.column === null ? null : { row: menuTarget.row, column: menuTarget.column };
|
|
756
825
|
const settable = menuCell !== null && canEdit(menuCell);
|
|
757
|
-
const nullable = menuCell !== null && columns[menuCell.column]?.nullable !== false;
|
|
758
826
|
const menuColumn = menuCell === null ? undefined : columns[menuCell.column];
|
|
827
|
+
const nullable = menuColumn?.nullable !== false;
|
|
759
828
|
const hasDefault = menuColumn !== undefined && ((menuColumn.defaultValue ?? null) !== null || menuColumn.autoIncrement === true);
|
|
829
|
+
|
|
760
830
|
const menuContext: GridMenuContext | null =
|
|
761
831
|
menuRow === undefined || menuTarget === null
|
|
762
832
|
? null
|
package/src/grid/EnumPicker.tsx
CHANGED
|
@@ -38,7 +38,9 @@ const LOOK = {
|
|
|
38
38
|
export function EnumPicker({ type, value, nullable, label, autoOpen = false, disabled = false, onDone, look = 'cell', className, ref }: EnumPickerProps) {
|
|
39
39
|
const { t } = useTranslation('database');
|
|
40
40
|
const set = type.kind === 'set';
|
|
41
|
+
|
|
41
42
|
const membersOfValue = (): ReadonlySet<string> => new Set(set && value !== null ? membersOf(value) : []);
|
|
43
|
+
|
|
42
44
|
const settled = useRef(false);
|
|
43
45
|
const [members, setMembers] = useState(membersOfValue);
|
|
44
46
|
|
|
@@ -53,8 +55,10 @@ export function EnumPicker({ type, value, nullable, label, autoOpen = false, dis
|
|
|
53
55
|
if (open) {
|
|
54
56
|
settled.current = false;
|
|
55
57
|
setMembers(membersOfValue());
|
|
58
|
+
|
|
56
59
|
return;
|
|
57
60
|
}
|
|
61
|
+
|
|
58
62
|
const joined = joinMembers(type, members);
|
|
59
63
|
settle(set && joined !== value ? { value: joined } : undefined);
|
|
60
64
|
};
|
|
@@ -62,11 +66,13 @@ export function EnumPicker({ type, value, nullable, label, autoOpen = false, dis
|
|
|
62
66
|
const toggle = (member: string, checked: boolean): void => {
|
|
63
67
|
setMembers((now) => {
|
|
64
68
|
const next = new Set(now);
|
|
69
|
+
|
|
65
70
|
if (checked) {
|
|
66
71
|
next.add(member);
|
|
67
72
|
} else {
|
|
68
73
|
next.delete(member);
|
|
69
74
|
}
|
|
75
|
+
|
|
70
76
|
return next;
|
|
71
77
|
});
|
|
72
78
|
};
|