@adecore/database 0.21.1 → 0.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assign-ref.d.ts +2 -0
- package/dist/assign-ref.js +9 -0
- package/dist/client/client.js +14 -15
- package/dist/client/types.d.ts +1 -0
- package/dist/client/types.js +2 -0
- package/dist/connections/ConnectionForm.js +7 -2
- package/dist/connections/ConnectionManager.js +5 -5
- package/dist/connections/EngineIcon.js +0 -1
- package/dist/console/QueryConsole.js +12 -4
- package/dist/console/editing.js +2 -4
- package/dist/console/useConsoleHistory.js +3 -2
- package/dist/ddl/alter-mysql.js +2 -8
- package/dist/ddl/alter-sqlite.js +1 -1
- package/dist/ddl/column.js +5 -8
- package/dist/ddl/create.d.ts +1 -0
- package/dist/ddl/create.js +1 -1
- package/dist/ddl/diff.js +25 -23
- package/dist/designer/ColumnsEditor.js +8 -6
- package/dist/designer/ForeignKeysEditor.js +5 -1
- package/dist/designer/OptionsEditor.js +12 -3
- package/dist/designer/SqlPreview.d.ts +1 -5
- package/dist/designer/SqlPreview.js +1 -1
- package/dist/designer/TableDesigner.js +7 -6
- package/dist/explorer/DatabaseExplorer.js +12 -13
- package/dist/explorer/TableDialog.js +1 -1
- package/dist/explorer/tree.js +6 -16
- package/dist/grid/DataGrid.js +11 -14
- package/dist/grid/EnumPicker.js +4 -3
- package/dist/grid/GridHeaderCell.d.ts +1 -1
- package/dist/grid/GridHeaderCell.js +2 -2
- package/dist/grid/RecordView.js +2 -1
- package/dist/grid/ValueDock.js +2 -6
- package/dist/grid/aggregates.js +2 -3
- package/dist/grid/column-layout.js +3 -2
- package/dist/grid/copy-formats.js +2 -3
- package/dist/grid/display.d.ts +0 -1
- package/dist/grid/display.js +1 -3
- package/dist/grid/layout.js +6 -11
- package/dist/host/helper-path.js +8 -0
- package/dist/host/host.js +11 -16
- package/dist/host/validate.js +44 -43
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +5 -2
- package/dist/structure/StructureView.js +5 -6
- package/dist/table/TableView.js +19 -12
- package/dist/table/command-field.js +8 -12
- package/dist/table/grid-rows.js +2 -1
- package/dist/table/layout-store.js +5 -2
- package/dist/table/pending.js +11 -3
- package/dist/table/useTableTransfer.js +6 -5
- package/dist/testing/fake.js +7 -9
- package/dist/value/ValueEditor.js +1 -1
- package/dist/value/draft.js +1 -6
- package/dist/value/json.js +4 -4
- package/dist/workbench/DatabaseWorkbench.js +12 -5
- package/dist/workbench/tabs.js +3 -4
- package/package.json +6 -6
- package/src/assign-ref.ts +10 -0
- package/src/client/client.ts +17 -18
- package/src/client/types.ts +3 -0
- package/src/connections/ConnectionForm.tsx +62 -47
- package/src/connections/ConnectionManager.tsx +6 -5
- package/src/connections/EngineIcon.tsx +0 -2
- package/src/console/QueryConsole.tsx +52 -31
- package/src/console/editing.ts +2 -4
- package/src/console/useConsoleHistory.ts +5 -2
- package/src/ddl/alter-mysql.ts +3 -11
- package/src/ddl/alter-sqlite.ts +1 -1
- package/src/ddl/column.ts +5 -7
- package/src/ddl/create.ts +1 -1
- package/src/ddl/diff.ts +30 -25
- package/src/designer/ColumnsEditor.tsx +145 -125
- package/src/designer/ForeignKeysEditor.tsx +44 -27
- package/src/designer/OptionsEditor.tsx +95 -75
- package/src/designer/SqlPreview.tsx +2 -3
- package/src/designer/TableDesigner.tsx +7 -6
- package/src/explorer/DatabaseExplorer.tsx +26 -31
- package/src/explorer/TableDialog.tsx +1 -1
- package/src/explorer/tree.ts +8 -19
- package/src/grid/DataGrid.tsx +11 -14
- package/src/grid/EnumPicker.tsx +4 -3
- package/src/grid/GridHeaderCell.tsx +2 -3
- package/src/grid/RecordView.tsx +2 -1
- package/src/grid/ValueDock.tsx +2 -5
- package/src/grid/aggregates.ts +2 -4
- package/src/grid/column-layout.ts +3 -2
- package/src/grid/copy-formats.ts +2 -3
- package/src/grid/display.ts +1 -5
- package/src/grid/layout.ts +10 -11
- package/src/host/helper-path.ts +8 -8
- package/src/host/host.ts +11 -16
- package/src/host/validate.ts +45 -43
- package/src/protocol/messages.ts +0 -1
- package/src/sql.ts +7 -2
- package/src/structure/StructureView.tsx +7 -8
- package/src/table/CommandField.tsx +0 -1
- package/src/table/TableView.tsx +48 -34
- package/src/table/command-field.ts +9 -12
- package/src/table/grid-rows.ts +2 -7
- package/src/table/layout-store.ts +6 -3
- package/src/table/pending.ts +12 -7
- package/src/table/useTableTransfer.ts +7 -5
- package/src/testing/fake.ts +8 -12
- package/src/value/ValueEditor.tsx +1 -1
- package/src/value/draft.ts +1 -6
- package/src/value/json.ts +4 -4
- package/src/workbench/DatabaseWorkbench.tsx +14 -6
- package/src/workbench/DesignerTab.tsx +1 -1
- package/src/workbench/tabs.ts +5 -8
|
@@ -65,14 +65,17 @@ export function TableDesigner({ connection, schema, table, toolbarStart, classNa
|
|
|
65
65
|
const session = client.session(connection);
|
|
66
66
|
const statements = plan.statements;
|
|
67
67
|
const wasNew = snapshot.structure === null;
|
|
68
|
+
const settle = (message) => {
|
|
69
|
+
setFailure(message);
|
|
70
|
+
setConfirming(false);
|
|
71
|
+
};
|
|
68
72
|
let outcome;
|
|
69
73
|
applying.current = true;
|
|
70
74
|
try {
|
|
71
75
|
outcome = await session.execute(statements.join(';\n'), { schema });
|
|
72
76
|
}
|
|
73
77
|
catch (e) {
|
|
74
|
-
|
|
75
|
-
setConfirming(false);
|
|
78
|
+
settle(messageOf(e, t('designer.failed')));
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
81
|
finally {
|
|
@@ -81,12 +84,10 @@ export function TableDesigner({ connection, schema, table, toolbarStart, classNa
|
|
|
81
84
|
const failed = outcome.results.find((result) => result.kind === 'error');
|
|
82
85
|
if (failed?.kind === 'error') {
|
|
83
86
|
await recoverFrom(session, statements, failed.sql, outcome.inTransaction);
|
|
84
|
-
|
|
85
|
-
setConfirming(false);
|
|
87
|
+
settle(failed.error.message);
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
|
-
|
|
89
|
-
setConfirming(false);
|
|
90
|
+
settle(null);
|
|
90
91
|
// The client has told its listeners already: `execute` does for a statement that creates, alters or drops.
|
|
91
92
|
if (wasNew || draft.name !== draft.originalName) {
|
|
92
93
|
setSubject({ requested: table, current: draft.name });
|
|
@@ -9,11 +9,11 @@ import { useDatabaseAction, useDatabaseClient } from '../client-context.js';
|
|
|
9
9
|
import { EngineIcon } from '../connections/EngineIcon.js';
|
|
10
10
|
import { keyLabelOf } from '../column-keys.js';
|
|
11
11
|
import { KeyIcon } from '../KeyIcon.js';
|
|
12
|
+
import { usePopupPress } from '../use-popup-press.js';
|
|
12
13
|
import { RowMenu } from './RowMenu.js';
|
|
13
14
|
import { TableDialog } from './TableDialog.js';
|
|
14
15
|
import { connectionKey, flattenTree, hasMenu, isExpandable, startsOpen, isSelectable, navigate, neededLoads, selectionKey, selectionOf, tabStop, openedTableOf, tableKindIn, tableOf } from './tree.js';
|
|
15
16
|
import { useExplorerLoads } from './use-explorer-loads.js';
|
|
16
|
-
import { usePopupPress } from '../use-popup-press.js';
|
|
17
17
|
import { useStoredExpansion } from './use-stored-expansion.js';
|
|
18
18
|
/*
|
|
19
19
|
* The connections, their schemas and their tables as a tree, loaded as each node opens. Every row
|
|
@@ -166,9 +166,6 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
166
166
|
loads.reset(row.retry);
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
|
-
const refresh = (target) => {
|
|
170
|
-
loads.reset(target);
|
|
171
|
-
};
|
|
172
169
|
const disconnect = (connection) => {
|
|
173
170
|
void client.disconnect(connection.id).catch(() => undefined);
|
|
174
171
|
loads.reset({ connectionId: connection.id });
|
|
@@ -188,8 +185,9 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
188
185
|
const isSelected = (row) => row !== undefined && isSelectable(row) && row.key === selectedKey;
|
|
189
186
|
const renderRow = (row, index) => {
|
|
190
187
|
const connection = connectionOf(row);
|
|
191
|
-
const menu = row.kind === 'appItem' ? ((row.item.menu ?? null)) : connection !== undefined && hasMenu(row) ? (_jsx(RowMenu, { row: row, connection: connection, onRefresh:
|
|
188
|
+
const menu = row.kind === 'appItem' ? ((row.item.menu ?? null)) : connection !== undefined && hasMenu(row) ? (_jsx(RowMenu, { row: row, connection: connection, onRefresh: loads.reset, onDisconnect: disconnect, onRequest: setRequest })) : null;
|
|
192
189
|
const selectedRow = isSelected(row);
|
|
190
|
+
const draggable = row.kind === 'table' && onTableDragStart !== undefined;
|
|
193
191
|
const props = {
|
|
194
192
|
level: row.level,
|
|
195
193
|
selected: selectedRow,
|
|
@@ -210,11 +208,9 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
210
208
|
rowElements.current.set(row.key, element);
|
|
211
209
|
}
|
|
212
210
|
},
|
|
213
|
-
draggable
|
|
214
|
-
onDragStart: row.
|
|
215
|
-
|
|
216
|
-
: undefined,
|
|
217
|
-
onDragEnd: row.kind === 'table' && onTableDragStart !== undefined ? onTableDragEnd : undefined,
|
|
211
|
+
draggable,
|
|
212
|
+
onDragStart: draggable ? (event) => onTableDragStart(row.ref, event, row.table.kind) : undefined,
|
|
213
|
+
onDragEnd: draggable ? onTableDragEnd : undefined,
|
|
218
214
|
onClick: (event) => click(row, event.detail),
|
|
219
215
|
onDoubleClick: tableOf(row) === null && row.kind !== 'appItem' ? undefined : () => open(row),
|
|
220
216
|
onFocus: () => {
|
|
@@ -243,13 +239,13 @@ function RowContent({ row, onToggle }) {
|
|
|
243
239
|
case 'schema':
|
|
244
240
|
return (_jsxs(_Fragment, { children: [_jsx(Tree.Chevron, { expanded: row.expanded, onExpandedChange: onToggle }), _jsx(Icon, { icon: Database, size: 16, className: "shrink-0 text-text-muted" }), _jsx(Tree.Label, { children: row.schema })] }));
|
|
245
241
|
case 'folder':
|
|
246
|
-
return (
|
|
242
|
+
return (_jsx(FolderContent, { expanded: row.expanded, count: row.count, onToggle: onToggle, children: t(row.group === 'view' ? 'explorer.views' : 'explorer.tables') }));
|
|
247
243
|
case 'table':
|
|
248
244
|
return (_jsxs(_Fragment, { children: [_jsx(Tree.Chevron, { expanded: row.expanded, onExpandedChange: onToggle }), _jsx(Icon, { icon: row.table.kind === 'view' ? Eye : Table, size: 16, className: "shrink-0 text-text-muted" }), _jsx(Tree.Label, { children: row.table.name })] }));
|
|
249
245
|
case 'part':
|
|
250
|
-
return (
|
|
246
|
+
return (_jsx(FolderContent, { expanded: row.expanded, count: row.count, onToggle: onToggle, children: t(`explorer.parts.${row.part}`) }));
|
|
251
247
|
case 'appFolder':
|
|
252
|
-
return (
|
|
248
|
+
return (_jsx(FolderContent, { expanded: row.expanded, count: row.count, onToggle: onToggle, children: row.folder.label }));
|
|
253
249
|
case 'appItem':
|
|
254
250
|
return (_jsxs(_Fragment, { children: [_jsx(Tree.ChevronSlot, {}), _jsx(Icon, { icon: row.item.icon, size: 16, className: "shrink-0 text-text-muted" }), _jsx(Tree.Label, { children: row.item.label })] }));
|
|
255
251
|
case 'entry':
|
|
@@ -264,6 +260,9 @@ function RowContent({ row, onToggle }) {
|
|
|
264
260
|
return (_jsxs(_Fragment, { children: [_jsx(Tree.ChevronSlot, {}), _jsx("span", { className: "text-text-faint", children: t(EMPTY_WORDS[row.of]) })] }));
|
|
265
261
|
}
|
|
266
262
|
}
|
|
263
|
+
function FolderContent({ expanded, count, onToggle, children }) {
|
|
264
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tree.Chevron, { expanded: expanded, onExpandedChange: onToggle }), _jsx(Icon, { icon: Folder, size: 16, className: "shrink-0 text-text-muted" }), _jsx(Tree.Label, { children: children }), _jsx("span", { className: "shrink-0 text-text-faint tabular-nums", children: formatNumber(count) })] }));
|
|
265
|
+
}
|
|
267
266
|
const EMPTY_WORDS = { schemas: 'explorer.noSchemas', tables: 'explorer.noTables', columns: 'explorer.noColumns' };
|
|
268
267
|
function SchemaCount({ shown, total }) {
|
|
269
268
|
const { t } = useTranslation('database');
|
|
@@ -15,7 +15,7 @@ export function TableDialog({ request, onClose, onDone }) {
|
|
|
15
15
|
const dialect = dialectOf(await session.server());
|
|
16
16
|
const { results } = await session.execute(statementOf(dialect, change, ref, kind, to));
|
|
17
17
|
const failed = results.find((result) => result.kind === 'error');
|
|
18
|
-
if (failed
|
|
18
|
+
if (failed?.kind === 'error') {
|
|
19
19
|
throw new Error(failed.error.message);
|
|
20
20
|
}
|
|
21
21
|
// The client tells its listeners after a `RENAME`, a `DROP` and a `TRUNCATE`, but not after a SQLite emptying, which is a `DELETE FROM`.
|
package/dist/explorer/tree.js
CHANGED
|
@@ -58,14 +58,7 @@ export const selectionOf = (row) => {
|
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
/* The key of the row a selection points at: its table, else its schema, else its connection. */
|
|
61
|
-
export const selectionKey = (selection) =>
|
|
62
|
-
if (selection.schema === undefined) {
|
|
63
|
-
return connectionKey(selection.connectionId);
|
|
64
|
-
}
|
|
65
|
-
return selection.table === undefined
|
|
66
|
-
? schemaKey(selection.connectionId, selection.schema)
|
|
67
|
-
: tableKey({ connectionId: selection.connectionId, schema: selection.schema, table: selection.table });
|
|
68
|
-
};
|
|
61
|
+
export const selectionKey = (selection) => loadKey(selection);
|
|
69
62
|
/* Whether a row has a menu: the rows of a connection, a schema, a table or a column, an item the app gave one, and none of the rows that only report. */
|
|
70
63
|
export const hasMenu = (row) => row.kind === 'connection' ||
|
|
71
64
|
row.kind === 'schema' ||
|
|
@@ -87,6 +80,8 @@ const statusDraft = (row, level, parent, focusable) => ({
|
|
|
87
80
|
make: (place) => ({ ...row, key: `${row.kind}:${parent}`, level, parent, focusable, ...place }),
|
|
88
81
|
below: []
|
|
89
82
|
});
|
|
83
|
+
/* The row of a load that is not ready: its error, which can be retried and so takes the focus, or that it is loading. */
|
|
84
|
+
const pendingDraft = (load, retry, level, parent) => statusDraft(load.status === 'error' ? { kind: 'error', message: load.message, retry } : { kind: 'loading' }, level, parent, load.status === 'error');
|
|
90
85
|
const matches = (name, query) => name.toLowerCase().includes(query);
|
|
91
86
|
/* What each folder of a table besides its columns holds: the primary key and the unique keys, the foreign keys, the other indexes, the checks and the triggers. */
|
|
92
87
|
export const tableEntries = (structure) => {
|
|
@@ -133,10 +128,7 @@ export const flattenTree = (input) => {
|
|
|
133
128
|
const tableChildren = (ref, kind, level, parent) => {
|
|
134
129
|
const load = input.structures(ref.connectionId, ref.schema, ref.table);
|
|
135
130
|
if (load.status !== 'ready') {
|
|
136
|
-
|
|
137
|
-
? { kind: 'error', message: load.message, retry: { connectionId: ref.connectionId, schema: ref.schema, table: ref.table } }
|
|
138
|
-
: { kind: 'loading' };
|
|
139
|
-
return [statusDraft(row, level, parent, load.status === 'error')];
|
|
131
|
+
return [pendingDraft(load, { connectionId: ref.connectionId, schema: ref.schema, table: ref.table }, level, parent)];
|
|
140
132
|
}
|
|
141
133
|
const structure = load.value;
|
|
142
134
|
if (structure.columns.length === 0) {
|
|
@@ -224,8 +216,7 @@ export const flattenTree = (input) => {
|
|
|
224
216
|
if (filtering) {
|
|
225
217
|
return { drafts: [], tables: 0 };
|
|
226
218
|
}
|
|
227
|
-
|
|
228
|
-
return { drafts: [statusDraft(row, level, parent, load.status === 'error')], tables: 0 };
|
|
219
|
+
return { drafts: [pendingDraft(load, { connectionId: connection.id, schema }, level, parent)], tables: 0 };
|
|
229
220
|
};
|
|
230
221
|
const schemaDraft = (connection, schema, parent) => {
|
|
231
222
|
const key = schemaKey(connection.id, schema);
|
|
@@ -302,8 +293,7 @@ export const flattenTree = (input) => {
|
|
|
302
293
|
}
|
|
303
294
|
}
|
|
304
295
|
else if (open && !filtering) {
|
|
305
|
-
|
|
306
|
-
children = [statusDraft(row, 2, key, load.status === 'error')];
|
|
296
|
+
children = [pendingDraft(load, { connectionId: connection.id }, 2, key)];
|
|
307
297
|
}
|
|
308
298
|
// Behind a status row too: what the app keeps of a connection does not wait on its server.
|
|
309
299
|
children = [...children, ...(open ? appFolders : [])];
|
package/dist/grid/DataGrid.js
CHANGED
|
@@ -4,6 +4,10 @@ import clsx from 'clsx';
|
|
|
4
4
|
import { Ban, Copy, RotateCcw } from 'lucide-react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { ContextMenu, copyText, EDIT_SHORTCUTS, Icon, isApplePlatform, isModHeld, Kbd, useContentSize } from '@adecore/ui';
|
|
7
|
+
import { assignRef } from '../assign-ref.js';
|
|
8
|
+
import { useNumberNotation } from '../client-context.js';
|
|
9
|
+
import { usePopupPress } from '../use-popup-press.js';
|
|
10
|
+
import { useStableCallback } from '../use-stable-callback.js';
|
|
7
11
|
import { fullRect, rectBetween, rectContains } from './cell-range.js';
|
|
8
12
|
import { captureLayout, restoreLayout, sameLayout } from './column-layout.js';
|
|
9
13
|
import { NO_COLUMN_SELECTION, selectColumn, shownSelection } from './column-selection.js';
|
|
@@ -20,9 +24,6 @@ import { clampColumnWidth, columnOffsets, estimateColumnWidth, fitColumnWidth, F
|
|
|
20
24
|
import { moveFocus } from './navigation.js';
|
|
21
25
|
import { selectRow } from './row-selection.js';
|
|
22
26
|
import { sortOnly, sortStateOf } from './sort.js';
|
|
23
|
-
import { useNumberNotation } from '../client-context.js';
|
|
24
|
-
import { usePopupPress } from '../use-popup-press.js';
|
|
25
|
-
import { useStableCallback } from '../use-stable-callback.js';
|
|
26
27
|
const NO_KEYS = new Set();
|
|
27
28
|
const NO_SORTS = [];
|
|
28
29
|
const signatureOf = (columns) => columns.map((column) => column.name).join('\u0000');
|
|
@@ -41,7 +42,7 @@ export function DataGrid({ columns, rows, label, editable = false, selectedKeys,
|
|
|
41
42
|
const anchor = useRef(-1);
|
|
42
43
|
const pressedInPopup = usePopupPress();
|
|
43
44
|
const notation = useNumberNotation();
|
|
44
|
-
|
|
45
|
+
// Set once an edit commits or cancels, so the blur that follows the input's removal does not commit it again.
|
|
45
46
|
const settled = useRef(true);
|
|
46
47
|
const dragging = useRef(false);
|
|
47
48
|
const dragOrigin = useRef(null);
|
|
@@ -112,8 +113,6 @@ export function DataGrid({ columns, rows, label, editable = false, selectedKeys,
|
|
|
112
113
|
const enumTypes = useMemo(() => columns.map((column) => parseEnumType(column.type)), [columns]);
|
|
113
114
|
useEffect(() => {
|
|
114
115
|
reportFocus.current = onFocusedCellChange;
|
|
115
|
-
});
|
|
116
|
-
useEffect(() => {
|
|
117
116
|
reportRange.current = onRangeChange;
|
|
118
117
|
reportLayout.current = onLayoutChange;
|
|
119
118
|
});
|
|
@@ -136,8 +135,11 @@ export function DataGrid({ columns, rows, label, editable = false, selectedKeys,
|
|
|
136
135
|
reportRange.current?.({ top: Number(top), bottom: Number(bottom), columns: list === '' ? [] : list.split(',').map(Number) });
|
|
137
136
|
}, [blockKey]);
|
|
138
137
|
useEffect(() => {
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
if (focusRequest === null || focusRequest === undefined) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const index = rows.findIndex((row) => row.key === focusRequest.rowKey);
|
|
142
|
+
if (index >= 0) {
|
|
141
143
|
focusAt({ row: index, column: focusRequest.column });
|
|
142
144
|
if (focusRequest.quiet !== true) {
|
|
143
145
|
scroller.current?.focus();
|
|
@@ -169,12 +171,7 @@ export function DataGrid({ columns, rows, label, editable = false, selectedKeys,
|
|
|
169
171
|
const attach = useCallback((node) => {
|
|
170
172
|
scroller.current = node;
|
|
171
173
|
measure(node);
|
|
172
|
-
|
|
173
|
-
ref(node);
|
|
174
|
-
}
|
|
175
|
-
else if (ref) {
|
|
176
|
-
ref.current = node;
|
|
177
|
-
}
|
|
174
|
+
assignRef(ref, node);
|
|
178
175
|
}, [measure, ref]);
|
|
179
176
|
const cellId = (row, column) => cellIdOf(gridId, row, column);
|
|
180
177
|
const canEdit = (position) => {
|
package/dist/grid/EnumPicker.js
CHANGED
|
@@ -18,9 +18,10 @@ const LOOK = {
|
|
|
18
18
|
*/
|
|
19
19
|
export function EnumPicker({ type, value, nullable, label, autoOpen = false, disabled = false, onDone, look = 'cell', className, ref }) {
|
|
20
20
|
const { t } = useTranslation('database');
|
|
21
|
-
const settled = useRef(false);
|
|
22
|
-
const [members, setMembers] = useState(() => new Set(type.kind === 'set' && value !== null ? membersOf(value) : []));
|
|
23
21
|
const set = type.kind === 'set';
|
|
22
|
+
const membersOfValue = () => new Set(set && value !== null ? membersOf(value) : []);
|
|
23
|
+
const settled = useRef(false);
|
|
24
|
+
const [members, setMembers] = useState(membersOfValue);
|
|
24
25
|
const settle = (result) => {
|
|
25
26
|
if (!settled.current) {
|
|
26
27
|
settled.current = true;
|
|
@@ -30,7 +31,7 @@ export function EnumPicker({ type, value, nullable, label, autoOpen = false, dis
|
|
|
30
31
|
const handleOpenChange = (open) => {
|
|
31
32
|
if (open) {
|
|
32
33
|
settled.current = false;
|
|
33
|
-
setMembers(
|
|
34
|
+
setMembers(membersOfValue());
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
const joined = joinMembers(type, members);
|
|
@@ -8,7 +8,7 @@ import { formatNumber } from '@adecore/ui/format';
|
|
|
8
8
|
import { CODE_TEXT } from '../code-text.js';
|
|
9
9
|
import { keyLabelOf } from '../column-keys.js';
|
|
10
10
|
import { KeyIcon } from '../KeyIcon.js';
|
|
11
|
-
import { isNumericKind } from '
|
|
11
|
+
import { isNumericKind } from '../sql.js';
|
|
12
12
|
import { isDoubleClick, MAX_RESIZED_WIDTH, MIN_COLUMN_WIDTH } from './layout.js';
|
|
13
13
|
/* The items of the menu a header opens, from its button and from a right click. */
|
|
14
14
|
function HeaderMenuItems({ column, index, sortable, hasSorts, pinned, hasHidden, canHide, actions }) {
|
|
@@ -33,7 +33,7 @@ export function GridHeaderCell({ column, index, position, width, sortable, selec
|
|
|
33
33
|
const { t } = useTranslation('database');
|
|
34
34
|
const cell = useRef(null);
|
|
35
35
|
const lastPress = useRef(null);
|
|
36
|
-
|
|
36
|
+
// Set by a press on the resize strip, since the click that ends the drag would otherwise pick the column.
|
|
37
37
|
const resized = useRef(false);
|
|
38
38
|
const { startResize } = useColumnResize(cell, {
|
|
39
39
|
size: width,
|
package/dist/grid/RecordView.js
CHANGED
|
@@ -8,7 +8,8 @@ import { formatNumber } from '@adecore/ui/format';
|
|
|
8
8
|
import { useNumberNotation } from '../client-context.js';
|
|
9
9
|
import { CODE_TEXT } from '../code-text.js';
|
|
10
10
|
import { ValueEditor } from '../value/ValueEditor.js';
|
|
11
|
-
import {
|
|
11
|
+
import { isNumericKind } from '../sql.js';
|
|
12
|
+
import { cellView, copyTextOf, isPreview, numeralText } from './display.js';
|
|
12
13
|
import { draftOf, parseDraft } from './edit-value.js';
|
|
13
14
|
import { parseEnumType } from './enum-type.js';
|
|
14
15
|
import { EnumPicker } from './EnumPicker.js';
|
package/dist/grid/ValueDock.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useRef, useState } from 'react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { ColumnResizeHandle, useColumnResize } from '@adecore/ui';
|
|
5
|
+
import { assignRef } from '../assign-ref.js';
|
|
5
6
|
export const VALUE_PANEL_WIDTH = 320;
|
|
6
7
|
export const VALUE_PANEL_MIN_WIDTH = 240;
|
|
7
8
|
/* A grid with a panel along its right edge that a person can widen by its left edge. */
|
|
@@ -24,11 +25,6 @@ export function ValueDock({ open, panel, children, width: given, onWidthChange,
|
|
|
24
25
|
});
|
|
25
26
|
return (_jsxs("div", { ref: (node) => {
|
|
26
27
|
root.current = node;
|
|
27
|
-
|
|
28
|
-
ref(node);
|
|
29
|
-
}
|
|
30
|
-
else if (ref) {
|
|
31
|
-
ref.current = node;
|
|
32
|
-
}
|
|
28
|
+
assignRef(ref, node);
|
|
33
29
|
}, className: clsx('flex min-h-0 min-w-0 flex-1', className), children: [_jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: children }), open && (_jsxs("div", { ref: dock, className: "relative flex min-h-0 shrink-0 border-l border-border", style: { width }, children: [_jsx(ColumnResizeHandle, { from: "right", onPointerDown: startResize, className: "hover:bg-border-strong" }), panel] }))] }));
|
|
34
30
|
}
|
package/dist/grid/aggregates.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const NUMERIC_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
1
|
+
import { isNumericKind, NUMERIC_TEXT } from '../sql.js';
|
|
3
2
|
const numberOf = (cell) => {
|
|
4
3
|
if (typeof cell === 'number') {
|
|
5
4
|
return Number.isFinite(cell) ? { value: cell, text: String(cell) } : null;
|
|
@@ -30,7 +29,7 @@ export const aggregateBlock = (rows, kinds, block) => {
|
|
|
30
29
|
for (const column of block.columns) {
|
|
31
30
|
count++;
|
|
32
31
|
const kind = kinds[column];
|
|
33
|
-
const found = kind !== undefined &&
|
|
32
|
+
const found = kind !== undefined && isNumericKind(kind) ? numberOf(cells[column] ?? null) : null;
|
|
34
33
|
if (found === null) {
|
|
35
34
|
continue;
|
|
36
35
|
}
|
|
@@ -33,10 +33,11 @@ export const captureLayout = (columns, state) => {
|
|
|
33
33
|
widths[column.name] = width;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
const namesOf = (indexes) => indexes.flatMap((index) => columns[index]?.name ?? []);
|
|
36
37
|
return {
|
|
37
38
|
widths,
|
|
38
|
-
hidden: [...state.view.hidden].sort((a, b) => a - b)
|
|
39
|
-
pinned: state.view.pinned
|
|
39
|
+
hidden: namesOf([...state.view.hidden].sort((a, b) => a - b)),
|
|
40
|
+
pinned: namesOf(state.view.pinned)
|
|
40
41
|
};
|
|
41
42
|
};
|
|
42
43
|
const sameList = (a, b) => a.length === b.length && a.every((item, index) => item === b[index]);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { qualifiedName, quoteIdentifier, sqlLiteral } from '../sql.js';
|
|
1
|
+
import { isNumericKind, qualifiedName, quoteIdentifier, sqlLiteral } from '../sql.js';
|
|
2
2
|
import { copyTextOf } from './display.js';
|
|
3
3
|
export const COPY_FORMATS = ['tsv', 'csv', 'json', 'sql'];
|
|
4
|
-
const NUMERIC_KINDS = new Set(['integer', 'decimal', 'float']);
|
|
5
4
|
const JSON_NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
6
5
|
const quoteField = (text) => `"${text.replaceAll('"', '""')}"`;
|
|
7
6
|
/* Tabs and line breaks would split a cell on paste, so a cell that holds one is quoted the way a spreadsheet does. */
|
|
@@ -22,7 +21,7 @@ const jsonOf = (cell, kind) => {
|
|
|
22
21
|
return Number.isFinite(cell) ? String(cell) : 'null';
|
|
23
22
|
}
|
|
24
23
|
if (typeof cell === 'string') {
|
|
25
|
-
if (
|
|
24
|
+
if (isNumericKind(kind) && JSON_NUMBER.test(cell)) {
|
|
26
25
|
return cell;
|
|
27
26
|
}
|
|
28
27
|
if (kind === 'json') {
|
package/dist/grid/display.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ export interface CellView {
|
|
|
12
12
|
export type NumberNotation = 'database' | 'region';
|
|
13
13
|
export declare const DISPLAY_LIMIT = 200;
|
|
14
14
|
export declare const BINARY_PREVIEW_BYTES = 8;
|
|
15
|
-
export declare const isNumericKind: (kind: ValueKind) => boolean;
|
|
16
15
|
export declare const isPreview: (cell: Shown) => boolean;
|
|
17
16
|
export declare const numeralText: (numeral: string | number, notation: NumberNotation) => string;
|
|
18
17
|
export declare const cellView: (cell: Shown, kind: ValueKind, notation?: NumberNotation) => CellView;
|
package/dist/grid/display.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { formatBytes, formatNumeral } from '@adecore/ui/format';
|
|
2
|
+
import { isNumericKind } from '../sql.js';
|
|
2
3
|
/* A cell never draws more than this, however long the text: the DOM stays small and the cell is one line anyway. */
|
|
3
4
|
export const DISPLAY_LIMIT = 200;
|
|
4
5
|
/* How many bytes of a binary value the cell spells out. */
|
|
5
6
|
export const BINARY_PREVIEW_BYTES = 8;
|
|
6
|
-
const NUMERIC_KINDS = new Set(['integer', 'decimal', 'float']);
|
|
7
|
-
/* Whether the cells of a column of this kind sit against the end of their cell, as numbers do. */
|
|
8
|
-
export const isNumericKind = (kind) => NUMERIC_KINDS.has(kind);
|
|
9
7
|
const ELLIPSIS = '…';
|
|
10
8
|
const isObject = (cell) => cell !== null && typeof cell === 'object';
|
|
11
9
|
/* Whether a cell is only the start of its value, so editing it needs the whole value first. */
|
package/dist/grid/layout.js
CHANGED
|
@@ -63,18 +63,13 @@ export const columnOffsets = (widths) => {
|
|
|
63
63
|
/* Wide enough for the largest row number, never narrower than `GUTTER_MIN_WIDTH`. */
|
|
64
64
|
export const gutterWidth = (rowCount) => Math.max(GUTTER_MIN_WIDTH, String(Math.max(rowCount, 1)).length * CHARACTER_WIDTH + CELL_PADDING);
|
|
65
65
|
export const clampColumnWidth = (width) => Math.max(MIN_COLUMN_WIDTH, Math.min(Math.ceil(width), MAX_COLUMN_WIDTH));
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
66
|
+
const bodyWidth = (texts) => texts.reduce((longest, text) => Math.max(longest, text.length), 0) * CHARACTER_WIDTH;
|
|
67
|
+
/* `keyed` makes room for the key icon in front of the name. */
|
|
68
|
+
const headerWidth = (header, keyed) => header.length * CHARACTER_WIDTH + (keyed ? KEY_ICON_ROOM : 0) + SORT_ROOM;
|
|
69
|
+
/* From the longest text among the header and the first rows. */
|
|
70
|
+
export const estimateColumnWidth = (header, samples, keyed) => clampColumnWidth(Math.max(bodyWidth(samples.slice(0, SAMPLE_ROWS)), headerWidth(header, keyed)) + CELL_PADDING);
|
|
72
71
|
/* Wide enough for the header and every text of the column, up to `MAX_FIT_WIDTH`. */
|
|
73
|
-
export const fitColumnWidth = (header, texts, keyed) =>
|
|
74
|
-
const body = texts.reduce((longest, text) => Math.max(longest, text.length), 0) * CHARACTER_WIDTH;
|
|
75
|
-
const named = header.length * CHARACTER_WIDTH + (keyed ? KEY_ICON_ROOM : 0) + SORT_ROOM;
|
|
76
|
-
return Math.max(MIN_COLUMN_WIDTH, Math.min(Math.ceil(Math.max(body, named) + CELL_PADDING), MAX_FIT_WIDTH));
|
|
77
|
-
};
|
|
72
|
+
export const fitColumnWidth = (header, texts, keyed) => Math.max(MIN_COLUMN_WIDTH, Math.min(Math.ceil(Math.max(bodyWidth(texts), headerWidth(header, keyed)) + CELL_PADDING), MAX_FIT_WIDTH));
|
|
78
73
|
export const isDoubleClick = (previous, now) => previous !== null && now - previous <= DOUBLE_CLICK_MS;
|
|
79
74
|
/*
|
|
80
75
|
* The estimated widths of a new page, never narrower than they were. A page with longer content widens
|
package/dist/host/helper-path.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { dirname, join, sep } from 'node:path';
|
|
4
|
+
/*
|
|
5
|
+
* Finds the prebuilt helper that npm installed for this machine, in the `@adecore/database-<platform>-<arch>`
|
|
6
|
+
* package, or null when there is none: an unsupported platform, or an install with optional dependencies off.
|
|
7
|
+
*
|
|
8
|
+
* A binary cannot run from inside an Electron archive. The path is mapped from `app.asar` to
|
|
9
|
+
* `app.asar.unpacked`, so the app must unpack the packages (electron-builder: `asarUnpack` with
|
|
10
|
+
* `node_modules/@adecore/database-*` followed by `/**`).
|
|
11
|
+
*/
|
|
4
12
|
export const helperPath = ({ platform = process.platform, arch = process.arch, resolve = (specifier) => createRequire(import.meta.url).resolve(specifier), exists = existsSync } = {}) => {
|
|
5
13
|
let manifest;
|
|
6
14
|
try {
|
package/dist/host/host.js
CHANGED
|
@@ -3,7 +3,6 @@ import { parseRequest } from './validate.js';
|
|
|
3
3
|
const DEFAULT_READY_TIMEOUT_MS = 10000;
|
|
4
4
|
/* How long a release or a dispose waits for a helper to answer its `close` requests. */
|
|
5
5
|
const CLOSE_GRACE_MS = 2000;
|
|
6
|
-
/* A failure with the code the caller gets. */
|
|
7
6
|
class Failure extends Error {
|
|
8
7
|
code;
|
|
9
8
|
constructor(code, message) {
|
|
@@ -13,6 +12,15 @@ class Failure extends Error {
|
|
|
13
12
|
}
|
|
14
13
|
const failure = (id, code, message) => ({ id, ok: false, error: { code, message } });
|
|
15
14
|
const messageOf = (error) => (error instanceof Error ? error.message : String(error));
|
|
15
|
+
/* `undefined` for a line that is not JSON. */
|
|
16
|
+
const parseLine = (line) => {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(line);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
16
24
|
const isDict = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
25
|
/* Settles when every promise has, or after the grace period; a helper that hangs must not hold up an app that quits. */
|
|
18
26
|
const settleWithin = async (promises) => {
|
|
@@ -72,14 +80,7 @@ export const createDatabaseHost = (options) => {
|
|
|
72
80
|
retire(run, message);
|
|
73
81
|
};
|
|
74
82
|
const onHandshake = (run, line) => {
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
message = JSON.parse(line);
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
abort(run, 'The helper did not start with a ready line.');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
+
const message = parseLine(line);
|
|
83
84
|
if (!isDict(message) || message.event !== 'ready') {
|
|
84
85
|
abort(run, 'The helper did not start with a ready line.');
|
|
85
86
|
return;
|
|
@@ -93,13 +94,7 @@ export const createDatabaseHost = (options) => {
|
|
|
93
94
|
run.resolveReady();
|
|
94
95
|
};
|
|
95
96
|
const onResponse = (run, line) => {
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
message = JSON.parse(line);
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
97
|
+
const message = parseLine(line);
|
|
103
98
|
if (!isDict(message) || typeof message.id !== 'string') {
|
|
104
99
|
return;
|
|
105
100
|
}
|