@adecore/database 0.0.1 → 0.16.0-beta.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/LICENSE +21 -0
- package/README.md +152 -1
- package/dist/DatabaseProvider.d.ts +13 -0
- package/dist/DatabaseProvider.js +17 -0
- package/dist/actions.d.ts +40 -0
- package/dist/actions.js +1 -0
- package/dist/client/client.d.ts +5 -0
- package/dist/client/client.js +245 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -0
- package/dist/client/types.d.ts +97 -0
- package/dist/client/types.js +13 -0
- package/dist/client-context.d.ts +16 -0
- package/dist/client-context.js +17 -0
- package/dist/connections/ConnectionForm.d.ts +10 -0
- package/dist/connections/ConnectionForm.js +145 -0
- package/dist/connections/ConnectionManager.d.ts +12 -0
- package/dist/connections/ConnectionManager.js +93 -0
- package/dist/connections/EngineIcon.d.ts +9 -0
- package/dist/connections/EngineIcon.js +7 -0
- package/dist/connections/connection-config.d.ts +16 -0
- package/dist/connections/connection-config.js +107 -0
- package/dist/connections/docker.d.ts +9 -0
- package/dist/connections/docker.js +38 -0
- package/dist/connections/engine-icons.d.ts +3 -0
- package/dist/connections/engine-icons.js +3 -0
- package/dist/connections/use-docker-containers.d.ts +17 -0
- package/dist/connections/use-docker-containers.js +26 -0
- package/dist/console/DestructiveDialog.d.ts +9 -0
- package/dist/console/DestructiveDialog.js +9 -0
- package/dist/console/HistoryPanel.d.ts +10 -0
- package/dist/console/HistoryPanel.js +15 -0
- package/dist/console/QueryConsole.d.ts +14 -0
- package/dist/console/QueryConsole.js +261 -0
- package/dist/console/SchemaPicker.d.ts +11 -0
- package/dist/console/SchemaPicker.js +8 -0
- package/dist/console/SqlEditor.d.ts +12 -0
- package/dist/console/SqlEditor.js +61 -0
- package/dist/console/StatementResultView.d.ts +18 -0
- package/dist/console/StatementResultView.js +32 -0
- package/dist/console/TransactionControls.d.ts +13 -0
- package/dist/console/TransactionControls.js +12 -0
- package/dist/console/destructive.d.ts +7 -0
- package/dist/console/destructive.js +43 -0
- package/dist/console/editing.d.ts +12 -0
- package/dist/console/editing.js +60 -0
- package/dist/console/history.d.ts +13 -0
- package/dist/console/history.js +43 -0
- package/dist/console/labels.d.ts +1 -0
- package/dist/console/labels.js +6 -0
- package/dist/console/outcome.d.ts +6 -0
- package/dist/console/outcome.js +11 -0
- package/dist/console/schemas.d.ts +2 -0
- package/dist/console/schemas.js +5 -0
- package/dist/console/shortcuts.d.ts +2 -0
- package/dist/console/shortcuts.js +3 -0
- package/dist/console/useConsoleHistory.d.ts +7 -0
- package/dist/console/useConsoleHistory.js +22 -0
- package/dist/ddl/alter-mysql.d.ts +4 -0
- package/dist/ddl/alter-mysql.js +79 -0
- package/dist/ddl/alter-sqlite.d.ts +4 -0
- package/dist/ddl/alter-sqlite.js +80 -0
- package/dist/ddl/alter.d.ts +4 -0
- package/dist/ddl/alter.js +24 -0
- package/dist/ddl/column.d.ts +9 -0
- package/dist/ddl/column.js +62 -0
- package/dist/ddl/create.d.ts +12 -0
- package/dist/ddl/create.js +74 -0
- package/dist/ddl/dialect.d.ts +12 -0
- package/dist/ddl/dialect.js +34 -0
- package/dist/ddl/diff.d.ts +34 -0
- package/dist/ddl/diff.js +111 -0
- package/dist/ddl/draft.d.ts +46 -0
- package/dist/ddl/draft.js +70 -0
- package/dist/ddl/index.d.ts +10 -0
- package/dist/ddl/index.js +10 -0
- package/dist/ddl/parse.d.ts +17 -0
- package/dist/ddl/parse.js +131 -0
- package/dist/ddl/statements.d.ts +6 -0
- package/dist/ddl/statements.js +12 -0
- package/dist/ddl/types.d.ts +6 -0
- package/dist/ddl/types.js +52 -0
- package/dist/ddl/validate.d.ts +7 -0
- package/dist/ddl/validate.js +38 -0
- package/dist/designer/ColumnsEditor.d.ts +2 -0
- package/dist/designer/ColumnsEditor.js +24 -0
- package/dist/designer/DesignerView.d.ts +25 -0
- package/dist/designer/DesignerView.js +33 -0
- package/dist/designer/ForeignKeysEditor.d.ts +7 -0
- package/dist/designer/ForeignKeysEditor.js +24 -0
- package/dist/designer/IndexesEditor.d.ts +2 -0
- package/dist/designer/IndexesEditor.js +13 -0
- package/dist/designer/NamePicker.d.ts +11 -0
- package/dist/designer/NamePicker.js +13 -0
- package/dist/designer/OptionsEditor.d.ts +2 -0
- package/dist/designer/OptionsEditor.js +17 -0
- package/dist/designer/SqlPreview.d.ts +12 -0
- package/dist/designer/SqlPreview.js +17 -0
- package/dist/designer/TableDesigner.d.ts +10 -0
- package/dist/designer/TableDesigner.js +103 -0
- package/dist/designer/edit.d.ts +17 -0
- package/dist/designer/edit.js +115 -0
- package/dist/designer/editor-props.d.ts +10 -0
- package/dist/designer/editor-props.js +1 -0
- package/dist/designer/plan.d.ts +9 -0
- package/dist/designer/plan.js +13 -0
- package/dist/designer/recover.d.ts +2 -0
- package/dist/designer/recover.js +18 -0
- package/dist/designer/useReferenceColumns.d.ts +3 -0
- package/dist/designer/useReferenceColumns.js +36 -0
- package/dist/explorer/DatabaseExplorer.d.ts +13 -0
- package/dist/explorer/DatabaseExplorer.js +264 -0
- package/dist/explorer/RowMenu.d.ts +11 -0
- package/dist/explorer/RowMenu.js +89 -0
- package/dist/explorer/TableDialog.d.ts +15 -0
- package/dist/explorer/TableDialog.js +50 -0
- package/dist/explorer/expansion.d.ts +7 -0
- package/dist/explorer/expansion.js +31 -0
- package/dist/explorer/table-statements.d.ts +5 -0
- package/dist/explorer/table-statements.js +12 -0
- package/dist/explorer/tree.d.ts +125 -0
- package/dist/explorer/tree.js +295 -0
- package/dist/explorer/use-explorer-loads.d.ts +12 -0
- package/dist/explorer/use-explorer-loads.js +109 -0
- package/dist/explorer/use-stored-expansion.d.ts +9 -0
- package/dist/explorer/use-stored-expansion.js +47 -0
- package/dist/grid/CellEditor.d.ts +9 -0
- package/dist/grid/CellEditor.js +29 -0
- package/dist/grid/CopyAsMenu.d.ts +7 -0
- package/dist/grid/CopyAsMenu.js +10 -0
- package/dist/grid/DataGrid.d.ts +36 -0
- package/dist/grid/DataGrid.js +512 -0
- package/dist/grid/EnumPicker.d.ts +17 -0
- package/dist/grid/EnumPicker.js +53 -0
- package/dist/grid/GridHeaderCell.d.ts +36 -0
- package/dist/grid/GridHeaderCell.js +69 -0
- package/dist/grid/RecordView.d.ts +20 -0
- package/dist/grid/RecordView.js +126 -0
- package/dist/grid/ValueDock.d.ts +11 -0
- package/dist/grid/ValueDock.js +28 -0
- package/dist/grid/aggregates.d.ts +22 -0
- package/dist/grid/aggregates.js +62 -0
- package/dist/grid/cell-range.d.ts +14 -0
- package/dist/grid/cell-range.js +11 -0
- package/dist/grid/column-layout.d.ts +18 -0
- package/dist/grid/column-layout.js +49 -0
- package/dist/grid/column-selection.d.ts +11 -0
- package/dist/grid/column-selection.js +28 -0
- package/dist/grid/column-view.d.ts +10 -0
- package/dist/grid/column-view.js +23 -0
- package/dist/grid/copy-formats.d.ts +15 -0
- package/dist/grid/copy-formats.js +80 -0
- package/dist/grid/display.d.ts +20 -0
- package/dist/grid/display.js +81 -0
- package/dist/grid/edit-value.d.ts +5 -0
- package/dist/grid/edit-value.js +55 -0
- package/dist/grid/enum-type.d.ts +7 -0
- package/dist/grid/enum-type.js +52 -0
- package/dist/grid/focused-value.d.ts +4 -0
- package/dist/grid/focused-value.js +19 -0
- package/dist/grid/layout.d.ts +22 -0
- package/dist/grid/layout.js +74 -0
- package/dist/grid/navigation.d.ts +15 -0
- package/dist/grid/navigation.js +52 -0
- package/dist/grid/row-selection.d.ts +9 -0
- package/dist/grid/row-selection.js +24 -0
- package/dist/grid/sort.d.ts +7 -0
- package/dist/grid/sort.js +7 -0
- package/dist/grid/types.d.ts +30 -0
- package/dist/grid/types.js +9 -0
- package/dist/host/helper-path.d.ts +7 -0
- package/dist/host/helper-path.js +15 -0
- package/dist/host/host.d.ts +2 -0
- package/dist/host/host.js +343 -0
- package/dist/host/index.d.ts +5 -0
- package/dist/host/index.js +4 -0
- package/dist/host/spawn.d.ts +7 -0
- package/dist/host/spawn.js +98 -0
- package/dist/host/types.d.ts +19 -0
- package/dist/host/types.js +1 -0
- package/dist/host/validate.d.ts +10 -0
- package/dist/host/validate.js +339 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/locales/en.json +577 -0
- package/dist/locales/nl.json +577 -0
- package/dist/locales.d.ts +4 -0
- package/dist/locales.js +14 -0
- package/dist/protocol/connection.d.ts +56 -0
- package/dist/protocol/connection.js +1 -0
- package/dist/protocol/errors.d.ts +7 -0
- package/dist/protocol/errors.js +1 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/messages.d.ts +250 -0
- package/dist/protocol/messages.js +2 -0
- package/dist/protocol/schema.d.ts +53 -0
- package/dist/protocol/schema.js +1 -0
- package/dist/protocol/values.d.ts +21 -0
- package/dist/protocol/values.js +15 -0
- package/dist/sql-split.d.ts +15 -0
- package/dist/sql-split.js +201 -0
- package/dist/sql.d.ts +29 -0
- package/dist/sql.js +104 -0
- package/dist/structure/StructureView.d.ts +14 -0
- package/dist/structure/StructureView.js +99 -0
- package/dist/structure/structure-text.d.ts +2 -0
- package/dist/structure/structure-text.js +2 -0
- package/dist/table/CommandField.d.ts +14 -0
- package/dist/table/CommandField.js +157 -0
- package/dist/table/ImportDialog.d.ts +27 -0
- package/dist/table/ImportDialog.js +28 -0
- package/dist/table/TableStatusBar.d.ts +19 -0
- package/dist/table/TableStatusBar.js +22 -0
- package/dist/table/TableToolbar.d.ts +43 -0
- package/dist/table/TableToolbar.js +16 -0
- package/dist/table/TableView.d.ts +13 -0
- package/dist/table/TableView.js +508 -0
- package/dist/table/command-field.d.ts +57 -0
- package/dist/table/command-field.js +203 -0
- package/dist/table/grid-rows.d.ts +23 -0
- package/dist/table/grid-rows.js +55 -0
- package/dist/table/import-mapping.d.ts +7 -0
- package/dist/table/import-mapping.js +28 -0
- package/dist/table/layout-store.d.ts +13 -0
- package/dist/table/layout-store.js +43 -0
- package/dist/table/paging.d.ts +12 -0
- package/dist/table/paging.js +20 -0
- package/dist/table/pending.d.ts +32 -0
- package/dist/table/pending.js +94 -0
- package/dist/table/references.d.ts +12 -0
- package/dist/table/references.js +21 -0
- package/dist/table/useLoaded.d.ts +7 -0
- package/dist/table/useLoaded.js +36 -0
- package/dist/table/useStoredLayout.d.ts +8 -0
- package/dist/table/useStoredLayout.js +39 -0
- package/dist/table/useTableTransfer.d.ts +51 -0
- package/dist/table/useTableTransfer.js +125 -0
- package/dist/testing/fake.d.ts +21 -0
- package/dist/testing/fake.js +372 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/use-popup-press.d.ts +1 -0
- package/dist/use-popup-press.js +24 -0
- package/dist/use-schema-change.d.ts +1 -0
- package/dist/use-schema-change.js +13 -0
- package/dist/use-stable-callback.d.ts +1 -0
- package/dist/use-stable-callback.js +9 -0
- package/dist/value/ValuePanel.d.ts +14 -0
- package/dist/value/ValuePanel.js +103 -0
- package/dist/value/draft.d.ts +33 -0
- package/dist/value/draft.js +44 -0
- package/dist/value/hex.d.ts +14 -0
- package/dist/value/hex.js +46 -0
- package/dist/value/json.d.ts +9 -0
- package/dist/value/json.js +71 -0
- package/dist/value/views.d.ts +22 -0
- package/dist/value/views.js +54 -0
- package/dist/workbench/DatabaseWorkbench.d.ts +9 -0
- package/dist/workbench/DatabaseWorkbench.js +179 -0
- package/dist/workbench/DesignerTab.d.ts +10 -0
- package/dist/workbench/DesignerTab.js +22 -0
- package/dist/workbench/TableTab.d.ts +10 -0
- package/dist/workbench/TableTab.js +8 -0
- package/dist/workbench/tabs.d.ts +42 -0
- package/dist/workbench/tabs.js +139 -0
- package/package.json +79 -3
- package/src/DatabaseProvider.tsx +37 -0
- package/src/actions.ts +34 -0
- package/src/client/client.ts +324 -0
- package/src/client/index.ts +18 -0
- package/src/client/types.ts +161 -0
- package/src/client-context.ts +35 -0
- package/src/connections/ConnectionForm.tsx +474 -0
- package/src/connections/ConnectionManager.tsx +224 -0
- package/src/connections/EngineIcon.tsx +18 -0
- package/src/connections/connection-config.ts +143 -0
- package/src/connections/docker.ts +50 -0
- package/src/connections/engine-icons.ts +5 -0
- package/src/connections/use-docker-containers.ts +41 -0
- package/src/console/DestructiveDialog.tsx +40 -0
- package/src/console/HistoryPanel.tsx +67 -0
- package/src/console/QueryConsole.tsx +439 -0
- package/src/console/SchemaPicker.tsx +32 -0
- package/src/console/SqlEditor.tsx +95 -0
- package/src/console/StatementResultView.tsx +176 -0
- package/src/console/TransactionControls.tsx +53 -0
- package/src/console/destructive.ts +60 -0
- package/src/console/editing.ts +77 -0
- package/src/console/history.ts +62 -0
- package/src/console/labels.ts +7 -0
- package/src/console/outcome.ts +14 -0
- package/src/console/schemas.ts +7 -0
- package/src/console/shortcuts.ts +4 -0
- package/src/console/useConsoleHistory.ts +39 -0
- package/src/ddl/alter-mysql.ts +90 -0
- package/src/ddl/alter-sqlite.ts +93 -0
- package/src/ddl/alter.ts +29 -0
- package/src/ddl/column.ts +74 -0
- package/src/ddl/create.ts +92 -0
- package/src/ddl/dialect.ts +53 -0
- package/src/ddl/diff.ts +164 -0
- package/src/ddl/draft.ts +136 -0
- package/src/ddl/index.ts +10 -0
- package/src/ddl/parse.ts +161 -0
- package/src/ddl/statements.ts +20 -0
- package/src/ddl/types.ts +60 -0
- package/src/ddl/validate.ts +47 -0
- package/src/designer/ColumnsEditor.tsx +204 -0
- package/src/designer/DesignerView.tsx +184 -0
- package/src/designer/ForeignKeysEditor.tsx +132 -0
- package/src/designer/IndexesEditor.tsx +63 -0
- package/src/designer/NamePicker.tsx +44 -0
- package/src/designer/OptionsEditor.tsx +101 -0
- package/src/designer/SqlPreview.tsx +56 -0
- package/src/designer/TableDesigner.tsx +186 -0
- package/src/designer/edit.ts +137 -0
- package/src/designer/editor-props.ts +13 -0
- package/src/designer/plan.ts +25 -0
- package/src/designer/recover.ts +20 -0
- package/src/designer/useReferenceColumns.ts +54 -0
- package/src/explorer/DatabaseExplorer.tsx +450 -0
- package/src/explorer/RowMenu.tsx +134 -0
- package/src/explorer/TableDialog.tsx +80 -0
- package/src/explorer/expansion.ts +47 -0
- package/src/explorer/table-statements.ts +18 -0
- package/src/explorer/tree.ts +453 -0
- package/src/explorer/use-explorer-loads.ts +157 -0
- package/src/explorer/use-stored-expansion.ts +66 -0
- package/src/grid/CellEditor.tsx +53 -0
- package/src/grid/CopyAsMenu.tsx +33 -0
- package/src/grid/DataGrid.tsx +871 -0
- package/src/grid/EnumPicker.tsx +108 -0
- package/src/grid/GridHeaderCell.tsx +243 -0
- package/src/grid/RecordView.tsx +293 -0
- package/src/grid/ValueDock.tsx +52 -0
- package/src/grid/aggregates.ts +94 -0
- package/src/grid/cell-range.ts +26 -0
- package/src/grid/column-layout.ts +71 -0
- package/src/grid/column-selection.ts +42 -0
- package/src/grid/column-view.ts +35 -0
- package/src/grid/copy-formats.ts +108 -0
- package/src/grid/display.ts +109 -0
- package/src/grid/edit-value.ts +60 -0
- package/src/grid/enum-type.ts +62 -0
- package/src/grid/focused-value.ts +23 -0
- package/src/grid/layout.ts +91 -0
- package/src/grid/navigation.ts +74 -0
- package/src/grid/row-selection.ts +35 -0
- package/src/grid/sort.ts +12 -0
- package/src/grid/types.ts +59 -0
- package/src/host/helper-path.ts +41 -0
- package/src/host/host.ts +456 -0
- package/src/host/index.ts +5 -0
- package/src/host/spawn.ts +124 -0
- package/src/host/types.ts +46 -0
- package/src/host/validate.ts +402 -0
- package/src/index.ts +37 -0
- package/src/locales/en.json +577 -0
- package/src/locales/nl.json +577 -0
- package/src/locales.ts +18 -0
- package/src/protocol/connection.ts +86 -0
- package/src/protocol/errors.ts +38 -0
- package/src/protocol/index.ts +33 -0
- package/src/protocol/messages.ts +168 -0
- package/src/protocol/schema.ts +76 -0
- package/src/protocol/values.ts +52 -0
- package/src/sql-split.ts +220 -0
- package/src/sql.ts +152 -0
- package/src/structure/StructureView.tsx +269 -0
- package/src/structure/structure-text.ts +4 -0
- package/src/table/CommandField.tsx +318 -0
- package/src/table/ImportDialog.tsx +168 -0
- package/src/table/TableStatusBar.tsx +91 -0
- package/src/table/TableToolbar.tsx +217 -0
- package/src/table/TableView.tsx +880 -0
- package/src/table/command-field.ts +285 -0
- package/src/table/grid-rows.ts +80 -0
- package/src/table/import-mapping.ts +37 -0
- package/src/table/layout-store.ts +62 -0
- package/src/table/paging.ts +33 -0
- package/src/table/pending.ts +143 -0
- package/src/table/references.ts +38 -0
- package/src/table/useLoaded.ts +58 -0
- package/src/table/useStoredLayout.ts +53 -0
- package/src/table/useTableTransfer.ts +187 -0
- package/src/testing/fake.ts +533 -0
- package/src/testing/index.ts +1 -0
- package/src/use-popup-press.ts +27 -0
- package/src/use-schema-change.ts +19 -0
- package/src/use-stable-callback.ts +12 -0
- package/src/value/ValuePanel.tsx +254 -0
- package/src/value/draft.ts +72 -0
- package/src/value/hex.ts +62 -0
- package/src/value/json.ts +74 -0
- package/src/value/views.ts +68 -0
- package/src/workbench/DatabaseWorkbench.tsx +348 -0
- package/src/workbench/DesignerTab.tsx +37 -0
- package/src/workbench/TableTab.tsx +27 -0
- package/src/workbench/tabs.ts +184 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { qualifiedName } from '../sql.js';
|
|
2
|
+
export const IDLE = { status: 'idle' };
|
|
3
|
+
const SEPARATOR = '\u0000';
|
|
4
|
+
export const connectionKey = (connectionId) => `c:${connectionId}`;
|
|
5
|
+
export const schemaKey = (connectionId, schema) => `s:${connectionId}${SEPARATOR}${schema}`;
|
|
6
|
+
export const folderKey = (connectionId, schema, group) => `f:${connectionId}${SEPARATOR}${schema}${SEPARATOR}${group}`;
|
|
7
|
+
export const tableKey = (ref) => `t:${ref.connectionId}${SEPARATOR}${ref.schema}${SEPARATOR}${ref.table}`;
|
|
8
|
+
export const isFolderKey = (key) => key.startsWith('f:');
|
|
9
|
+
/* The connection a key of a connection, schema, folder or table belongs to. */
|
|
10
|
+
export const connectionOfKey = (key) => {
|
|
11
|
+
const rest = key.slice(2);
|
|
12
|
+
const end = rest.indexOf(SEPARATOR);
|
|
13
|
+
return end === -1 ? rest : rest.slice(0, end);
|
|
14
|
+
};
|
|
15
|
+
export const columnKey = (ref, column) => `${tableKey(ref)}${SEPARATOR}${column}`;
|
|
16
|
+
export const loadKey = (target) => {
|
|
17
|
+
if (target.schema === undefined) {
|
|
18
|
+
return connectionKey(target.connectionId);
|
|
19
|
+
}
|
|
20
|
+
return target.table === undefined
|
|
21
|
+
? schemaKey(target.connectionId, target.schema)
|
|
22
|
+
: tableKey({ connectionId: target.connectionId, schema: target.schema, table: target.table });
|
|
23
|
+
};
|
|
24
|
+
/* The kinds of table a schema lists, in the order of its folders. */
|
|
25
|
+
export const GROUPS = ['table', 'view'];
|
|
26
|
+
export const isExpandable = (row) => row.kind === 'connection' || row.kind === 'schema' || row.kind === 'folder' || row.kind === 'table';
|
|
27
|
+
/* The table a row stands for, when it stands for one. */
|
|
28
|
+
export const tableOf = (row) => (row.kind === 'table' || row.kind === 'column' ? row.ref : null);
|
|
29
|
+
/* What picking a row selects: its connection, its schema, or its table (a column stands for its table). Status rows select nothing. */
|
|
30
|
+
export const selectionOf = (row) => {
|
|
31
|
+
switch (row.kind) {
|
|
32
|
+
case 'connection':
|
|
33
|
+
return { connectionId: row.connection.id };
|
|
34
|
+
case 'schema':
|
|
35
|
+
case 'folder':
|
|
36
|
+
return { connectionId: row.connection.id, schema: row.schema };
|
|
37
|
+
case 'table':
|
|
38
|
+
case 'column':
|
|
39
|
+
return { connectionId: row.ref.connectionId, schema: row.ref.schema, table: row.ref.table };
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/* The key of the row a selection points at: its table, else its schema, else its connection. */
|
|
45
|
+
export const selectionKey = (selection) => {
|
|
46
|
+
if (selection.schema === undefined) {
|
|
47
|
+
return connectionKey(selection.connectionId);
|
|
48
|
+
}
|
|
49
|
+
return selection.table === undefined
|
|
50
|
+
? schemaKey(selection.connectionId, selection.schema)
|
|
51
|
+
: tableKey({ connectionId: selection.connectionId, schema: selection.schema, table: selection.table });
|
|
52
|
+
};
|
|
53
|
+
/* Whether a row has a menu: the rows of a connection, a schema, a table or a column, and none of the rows that only report. */
|
|
54
|
+
export const hasMenu = (row) => row.kind === 'connection' || row.kind === 'schema' || row.kind === 'folder' || row.kind === 'table' || row.kind === 'column';
|
|
55
|
+
/* Only the rows a selection can point at are drawn selected; a folder or a column leaves that to the row above it. */
|
|
56
|
+
export const isSelectable = (row) => row.kind === 'connection' || row.kind === 'schema' || row.kind === 'table';
|
|
57
|
+
/* The statement behind New console here on a table: every row, as the engine quotes it. */
|
|
58
|
+
export const selectAllSql = (engine, ref) => `SELECT * FROM ${qualifiedName({ engine, schema: ref.schema, table: ref.table })}`;
|
|
59
|
+
/* The schemas a list shows. */
|
|
60
|
+
export const visibleSchemas = (schemas, showSystemSchemas) => showSystemSchemas ? schemas : schemas.filter((schema) => !schema.system);
|
|
61
|
+
/* One schema is no level worth a row: its folders hang straight under the connection. */
|
|
62
|
+
const soleSchema = (visible) => (visible.length === 1 ? visible[0].name : null);
|
|
63
|
+
/* Gives the drafts of one parent their place among each other, and lays them out depth first. */
|
|
64
|
+
const settle = (drafts) => drafts.flatMap((draft, i) => [draft.make({ posInSet: i + 1, setSize: drafts.length }), ...draft.below]);
|
|
65
|
+
const statusDraft = (row, level, parent, focusable) => ({
|
|
66
|
+
make: (place) => ({ ...row, key: `${row.kind}:${parent}`, level, parent, focusable, ...place }),
|
|
67
|
+
below: []
|
|
68
|
+
});
|
|
69
|
+
const matches = (name, query) => name.toLowerCase().includes(query);
|
|
70
|
+
/*
|
|
71
|
+
* The rows a tree shows, top to bottom. With a filter only the loaded tables that match stay, under
|
|
72
|
+
* their folder, schema and connection, which then count as open whatever the person left them at.
|
|
73
|
+
*/
|
|
74
|
+
export const flattenTree = (input) => {
|
|
75
|
+
const query = input.filter.trim().toLowerCase();
|
|
76
|
+
const filtering = query !== '';
|
|
77
|
+
const columnDrafts = (ref, level, parent) => {
|
|
78
|
+
const load = input.structures(ref.connectionId, ref.schema, ref.table);
|
|
79
|
+
if (load.status !== 'ready') {
|
|
80
|
+
const row = load.status === 'error'
|
|
81
|
+
? { kind: 'error', message: load.message, retry: { connectionId: ref.connectionId, schema: ref.schema, table: ref.table } }
|
|
82
|
+
: { kind: 'loading' };
|
|
83
|
+
return [statusDraft(row, level, parent, load.status === 'error')];
|
|
84
|
+
}
|
|
85
|
+
const { columns, primaryKey, foreignKeys } = load.value;
|
|
86
|
+
if (columns.length === 0) {
|
|
87
|
+
return [statusDraft({ kind: 'empty', of: 'columns' }, level, parent, true)];
|
|
88
|
+
}
|
|
89
|
+
const primary = new Set(primaryKey);
|
|
90
|
+
const foreign = new Set(foreignKeys.flatMap((foreignKey) => foreignKey.columns));
|
|
91
|
+
return columns.map((column) => ({
|
|
92
|
+
make: (place) => ({
|
|
93
|
+
kind: 'column',
|
|
94
|
+
key: columnKey(ref, column.name),
|
|
95
|
+
level,
|
|
96
|
+
parent,
|
|
97
|
+
focusable: true,
|
|
98
|
+
ref,
|
|
99
|
+
column,
|
|
100
|
+
primaryKey: primary.has(column.name),
|
|
101
|
+
foreignKey: foreign.has(column.name),
|
|
102
|
+
...place
|
|
103
|
+
}),
|
|
104
|
+
below: []
|
|
105
|
+
}));
|
|
106
|
+
};
|
|
107
|
+
const tableDraft = (ref, table, level, parent) => {
|
|
108
|
+
const key = tableKey(ref);
|
|
109
|
+
const open = input.expanded.has(key);
|
|
110
|
+
return {
|
|
111
|
+
make: (place) => ({ kind: 'table', key, level, parent, focusable: true, ref, table, expanded: open, ...place }),
|
|
112
|
+
below: open ? settle(columnDrafts(ref, level + 1, key)) : []
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
/* The folders under a schema (or under a connection with only that schema), and how many tables they hold. */
|
|
116
|
+
const folderDrafts = (connection, schema, level, parent) => {
|
|
117
|
+
const load = input.tables(connection.id, schema);
|
|
118
|
+
if (load.status === 'ready') {
|
|
119
|
+
const shown = filtering ? load.value.filter((table) => matches(table.name, query)) : load.value;
|
|
120
|
+
if (shown.length === 0 && !filtering) {
|
|
121
|
+
return { drafts: [statusDraft({ kind: 'empty', of: 'tables' }, level, parent, true)], tables: 0 };
|
|
122
|
+
}
|
|
123
|
+
const drafts = GROUPS.flatMap((group) => {
|
|
124
|
+
const members = shown.filter((table) => table.kind === group);
|
|
125
|
+
if (members.length === 0) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const key = folderKey(connection.id, schema, group);
|
|
129
|
+
const open = filtering || !input.collapsed.has(key);
|
|
130
|
+
const tables = members.map((table) => tableDraft({ connectionId: connection.id, schema, table: table.name }, table, level + 1, key));
|
|
131
|
+
return [
|
|
132
|
+
{
|
|
133
|
+
make: (place) => ({
|
|
134
|
+
kind: 'folder',
|
|
135
|
+
key,
|
|
136
|
+
level,
|
|
137
|
+
parent,
|
|
138
|
+
focusable: true,
|
|
139
|
+
connection,
|
|
140
|
+
schema,
|
|
141
|
+
group,
|
|
142
|
+
expanded: open,
|
|
143
|
+
count: members.length,
|
|
144
|
+
...place
|
|
145
|
+
}),
|
|
146
|
+
below: open ? settle(tables) : []
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
});
|
|
150
|
+
return { drafts, tables: shown.length };
|
|
151
|
+
}
|
|
152
|
+
if (filtering) {
|
|
153
|
+
return { drafts: [], tables: 0 };
|
|
154
|
+
}
|
|
155
|
+
const row = load.status === 'error' ? { kind: 'error', message: load.message, retry: { connectionId: connection.id, schema } } : { kind: 'loading' };
|
|
156
|
+
return { drafts: [statusDraft(row, level, parent, load.status === 'error')], tables: 0 };
|
|
157
|
+
};
|
|
158
|
+
const schemaDraft = (connection, schema, parent) => {
|
|
159
|
+
const key = schemaKey(connection.id, schema);
|
|
160
|
+
const { drafts, tables } = folderDrafts(connection, schema, 3, key);
|
|
161
|
+
if (filtering && tables === 0) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const open = filtering || input.expanded.has(key);
|
|
165
|
+
return {
|
|
166
|
+
make: (place) => ({ kind: 'schema', key, level: 2, parent, focusable: true, connection, schema, expanded: open, ...place }),
|
|
167
|
+
below: open ? settle(drafts) : []
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
const connectionDraft = (connection) => {
|
|
171
|
+
const key = connectionKey(connection.id);
|
|
172
|
+
const load = input.schemas(connection.id);
|
|
173
|
+
const open = filtering ? load.status === 'ready' : input.expanded.has(key);
|
|
174
|
+
let children = [];
|
|
175
|
+
let schemaCount = null;
|
|
176
|
+
if (load.status === 'ready') {
|
|
177
|
+
const visible = visibleSchemas(load.value, input.showSystemSchemas);
|
|
178
|
+
const sole = soleSchema(visible);
|
|
179
|
+
if (visible.length < load.value.length) {
|
|
180
|
+
schemaCount = { shown: visible.length, total: load.value.length };
|
|
181
|
+
}
|
|
182
|
+
if (sole !== null) {
|
|
183
|
+
children = open ? folderDrafts(connection, sole, 2, key).drafts : [];
|
|
184
|
+
}
|
|
185
|
+
else if (visible.length === 0) {
|
|
186
|
+
children = open && !filtering ? [statusDraft({ kind: 'empty', of: 'schemas' }, 2, key, true)] : [];
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
children = visible.flatMap((entry) => schemaDraft(connection, entry.name, key) ?? []);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (open) {
|
|
193
|
+
const row = load.status === 'error' ? { kind: 'error', message: load.message, retry: { connectionId: connection.id } } : { kind: 'loading' };
|
|
194
|
+
children = [statusDraft(row, 2, key, load.status === 'error')];
|
|
195
|
+
}
|
|
196
|
+
if (filtering && children.length === 0) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const version = input.versions(connection.id);
|
|
200
|
+
return {
|
|
201
|
+
make: (place) => ({ kind: 'connection', key, level: 1, parent: null, focusable: true, connection, expanded: open, version, schemaCount, ...place }),
|
|
202
|
+
below: open ? settle(children) : []
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
return settle(input.connections.flatMap((connection) => connectionDraft(connection) ?? []));
|
|
206
|
+
};
|
|
207
|
+
/* The loads an open node is waiting for and nobody has asked for yet. */
|
|
208
|
+
export const neededLoads = (input) => {
|
|
209
|
+
const needed = [];
|
|
210
|
+
for (const connection of input.connections) {
|
|
211
|
+
if (!input.expanded.has(connectionKey(connection.id))) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const load = input.schemas(connection.id);
|
|
215
|
+
if (load.status === 'idle') {
|
|
216
|
+
needed.push({ connectionId: connection.id });
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (load.status !== 'ready') {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const visible = visibleSchemas(load.value, input.showSystemSchemas);
|
|
223
|
+
const sole = soleSchema(visible);
|
|
224
|
+
const wanted = sole !== null ? [sole] : visible.map((schema) => schema.name).filter((name) => input.expanded.has(schemaKey(connection.id, name)));
|
|
225
|
+
for (const schema of wanted) {
|
|
226
|
+
const tables = input.tables(connection.id, schema);
|
|
227
|
+
if (tables.status === 'idle') {
|
|
228
|
+
needed.push({ connectionId: connection.id, schema });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (tables.status !== 'ready') {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const table of tables.value) {
|
|
235
|
+
if (input.expanded.has(tableKey({ connectionId: connection.id, schema, table: table.name })) &&
|
|
236
|
+
input.structures(connection.id, schema, table.name).status === 'idle') {
|
|
237
|
+
needed.push({ connectionId: connection.id, schema, table: table.name });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return needed;
|
|
243
|
+
};
|
|
244
|
+
/* What a key does to the row that has the focus, or `null` when it does nothing there. */
|
|
245
|
+
export const navigate = (rows, activeKey, pressed) => {
|
|
246
|
+
const stops = rows.filter((row) => row.focusable);
|
|
247
|
+
if (stops.length === 0) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const at = stops.findIndex((row) => row.key === activeKey);
|
|
251
|
+
const row = at === -1 ? null : stops[at];
|
|
252
|
+
switch (pressed) {
|
|
253
|
+
case 'ArrowDown':
|
|
254
|
+
return { type: 'focus', key: stops[Math.min(at + 1, stops.length - 1)].key };
|
|
255
|
+
case 'ArrowUp':
|
|
256
|
+
return { type: 'focus', key: stops[Math.max(at - 1, 0)].key };
|
|
257
|
+
case 'Home':
|
|
258
|
+
return { type: 'focus', key: stops[0].key };
|
|
259
|
+
case 'End':
|
|
260
|
+
return { type: 'focus', key: stops[stops.length - 1].key };
|
|
261
|
+
case 'ArrowRight': {
|
|
262
|
+
if (row === null || !isExpandable(row)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
if (!row.expanded) {
|
|
266
|
+
return { type: 'expand', key: row.key };
|
|
267
|
+
}
|
|
268
|
+
const child = stops.find((candidate) => candidate.parent === row.key);
|
|
269
|
+
return child === undefined ? null : { type: 'focus', key: child.key };
|
|
270
|
+
}
|
|
271
|
+
case 'ArrowLeft': {
|
|
272
|
+
if (row === null) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
if (isExpandable(row) && row.expanded) {
|
|
276
|
+
return { type: 'collapse', key: row.key };
|
|
277
|
+
}
|
|
278
|
+
return row.parent === null ? null : { type: 'focus', key: row.parent };
|
|
279
|
+
}
|
|
280
|
+
case 'Enter':
|
|
281
|
+
case ' ':
|
|
282
|
+
return row === null ? null : { type: 'activate', key: row.key };
|
|
283
|
+
default:
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
/* The row that takes the tab stop: the one with the focus, else the selected table, else the first. */
|
|
288
|
+
export const tabStop = (rows, activeKey, selectedKey) => {
|
|
289
|
+
for (const key of [activeKey, selectedKey]) {
|
|
290
|
+
if (key !== null && rows.some((row) => row.key === key && row.focusable)) {
|
|
291
|
+
return key;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return rows.find((row) => row.focusable)?.key ?? null;
|
|
295
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Connection } from '../client/types.ts';
|
|
2
|
+
import type { SchemaInfo, TableInfo, TableStructure } from '../protocol/index.ts';
|
|
3
|
+
import { type Load, type LoadTarget } from './tree.ts';
|
|
4
|
+
export interface ExplorerLoads {
|
|
5
|
+
schemas(connectionId: string): Load<readonly SchemaInfo[]>;
|
|
6
|
+
tables(connectionId: string, schema: string): Load<readonly TableInfo[]>;
|
|
7
|
+
structures(connectionId: string, schema: string, table: string): Load<TableStructure>;
|
|
8
|
+
versions(connectionId: string): string | null;
|
|
9
|
+
start(target: LoadTarget): void;
|
|
10
|
+
reset(target: LoadTarget): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function useExplorerLoads(connections: readonly Connection[]): ExplorerLoads;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { messageOf } from '@adecore/ui';
|
|
3
|
+
import { useDatabaseClient } from '../client-context.js';
|
|
4
|
+
import { IDLE, loadKey } from './tree.js';
|
|
5
|
+
const EMPTY = { entries: new Map(), versions: new Map() };
|
|
6
|
+
/* Whether a load belongs to what a reset names: the same connection, and the same schema or table when the reset names one. */
|
|
7
|
+
const within = (reset, target) => reset.connectionId === target.connectionId &&
|
|
8
|
+
(reset.schema === undefined || reset.schema === target.schema) &&
|
|
9
|
+
(reset.table === undefined || reset.table === target.table);
|
|
10
|
+
/* A change to a schema also changes the schema list, and one without a schema changes every list of the connection. */
|
|
11
|
+
const touches = (change, target) => change.connectionId === target.connectionId && (change.schema === undefined || target.schema === undefined || change.schema === target.schema);
|
|
12
|
+
/* The schemas, tables and column lists the explorer has asked a session for, by connection, schema and table. */
|
|
13
|
+
export function useExplorerLoads(connections) {
|
|
14
|
+
const client = useDatabaseClient();
|
|
15
|
+
const [loads, setLoads] = useState(EMPTY);
|
|
16
|
+
// Written before the state is, so a strict-mode second effect run does not fetch twice.
|
|
17
|
+
const started = useRef(new Map());
|
|
18
|
+
const configs = useRef(new Map());
|
|
19
|
+
const store = (target, load) => {
|
|
20
|
+
setLoads((current) => ({ ...current, entries: new Map(current.entries).set(loadKey(target), { target, load }) }));
|
|
21
|
+
};
|
|
22
|
+
const reset = (target) => {
|
|
23
|
+
for (const [key, began] of [...started.current]) {
|
|
24
|
+
if (within(target, began)) {
|
|
25
|
+
started.current.delete(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
setLoads((current) => ({
|
|
29
|
+
entries: new Map([...current.entries].filter(([, entry]) => !within(target, entry.target))),
|
|
30
|
+
versions: target.schema === undefined ? new Map([...current.versions].filter(([id]) => id !== target.connectionId)) : current.versions
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
/* A silent load keeps showing what it replaces, so a reload does not blink the tree. */
|
|
34
|
+
const run = (target, connection, silent) => {
|
|
35
|
+
if (!silent) {
|
|
36
|
+
store(target, { status: 'loading' });
|
|
37
|
+
}
|
|
38
|
+
const session = client.session(connection);
|
|
39
|
+
const request = target.schema === undefined
|
|
40
|
+
? session.schemas()
|
|
41
|
+
: target.table === undefined
|
|
42
|
+
? session.tables(target.schema)
|
|
43
|
+
: session.structure(target.schema, target.table);
|
|
44
|
+
request.then((value) => {
|
|
45
|
+
store(target, { status: 'ready', value });
|
|
46
|
+
if (target.schema === undefined) {
|
|
47
|
+
// Chained after the schemas so the version never opens a session by itself.
|
|
48
|
+
session.server().then((info) => setLoads((current) => ({ ...current, versions: new Map(current.versions).set(connection.id, info.version) })), () => undefined);
|
|
49
|
+
}
|
|
50
|
+
}, (e) => store(target, { status: 'error', message: messageOf(e) }));
|
|
51
|
+
};
|
|
52
|
+
const start = (target) => {
|
|
53
|
+
const key = loadKey(target);
|
|
54
|
+
if (started.current.has(key)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const connection = connections.find((entry) => entry.id === target.connectionId);
|
|
58
|
+
if (connection === undefined) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
started.current.set(key, target);
|
|
62
|
+
run(target, connection, false);
|
|
63
|
+
};
|
|
64
|
+
/* Lists load again in place and column lists are dropped, since the table they describe may be gone; an open table asks again by itself. */
|
|
65
|
+
const reload = (change) => {
|
|
66
|
+
const connection = connections.find((entry) => entry.id === change.connectionId);
|
|
67
|
+
const dropped = [];
|
|
68
|
+
for (const [key, began] of [...started.current]) {
|
|
69
|
+
if (!touches(change, began)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (began.table !== undefined) {
|
|
73
|
+
started.current.delete(key);
|
|
74
|
+
dropped.push(began);
|
|
75
|
+
}
|
|
76
|
+
else if (connection !== undefined) {
|
|
77
|
+
run(began, connection, true);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (dropped.length > 0) {
|
|
81
|
+
const gone = new Set(dropped.map(loadKey));
|
|
82
|
+
setLoads((current) => ({ ...current, entries: new Map([...current.entries].filter(([key]) => !gone.has(key))) }));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
useEffect(() => client.onSchemaChange(reload));
|
|
86
|
+
// A connection that was edited or removed is a different session, so what it listed is stale.
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const known = configs.current;
|
|
89
|
+
for (const [id, config] of known) {
|
|
90
|
+
const now = connections.find((entry) => entry.id === id);
|
|
91
|
+
if (now === undefined || now.config !== config) {
|
|
92
|
+
known.delete(id);
|
|
93
|
+
reset({ connectionId: id });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const connection of connections) {
|
|
97
|
+
known.set(connection.id, connection.config);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const loadOf = (target) => loads.entries.get(loadKey(target))?.load ?? IDLE;
|
|
101
|
+
return {
|
|
102
|
+
schemas: (connectionId) => loadOf({ connectionId }),
|
|
103
|
+
tables: (connectionId, schema) => loadOf({ connectionId, schema }),
|
|
104
|
+
structures: (connectionId, schema, table) => loadOf({ connectionId, schema, table }),
|
|
105
|
+
versions: (connectionId) => loads.versions.get(connectionId) ?? null,
|
|
106
|
+
start,
|
|
107
|
+
reset
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Connection } from '../client/types.ts';
|
|
2
|
+
import { type Expansion } from './expansion.ts';
|
|
3
|
+
type Update = (current: ReadonlySet<string>) => ReadonlySet<string>;
|
|
4
|
+
export interface StoredExpansion extends Expansion {
|
|
5
|
+
setExpanded(update: Update): void;
|
|
6
|
+
setCollapsed(update: Update): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function useStoredExpansion(connections: readonly Connection[]): StoredExpansion;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useDatabaseStorage } from '../client-context.js';
|
|
3
|
+
import { expansionKey, parseExpansion, serializeExpansion } from './expansion.js';
|
|
4
|
+
const restore = (storage, connections, from) => {
|
|
5
|
+
const expanded = new Set(from.expanded);
|
|
6
|
+
const collapsed = new Set(from.collapsed);
|
|
7
|
+
const loaded = new Set(from.loaded);
|
|
8
|
+
for (const { id } of connections) {
|
|
9
|
+
if (loaded.has(id)) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
loaded.add(id);
|
|
13
|
+
const stored = parseExpansion(storage?.get(expansionKey(id)) ?? null, id);
|
|
14
|
+
stored.expanded.forEach((key) => expanded.add(key));
|
|
15
|
+
stored.collapsed.forEach((key) => collapsed.add(key));
|
|
16
|
+
}
|
|
17
|
+
return { expanded, collapsed, loaded };
|
|
18
|
+
};
|
|
19
|
+
/*
|
|
20
|
+
* Which nodes of the tree are open, kept per connection in the app's storage when it has one. A
|
|
21
|
+
* connection that shows up later is restored when it does.
|
|
22
|
+
*/
|
|
23
|
+
export function useStoredExpansion(connections) {
|
|
24
|
+
const storage = useDatabaseStorage();
|
|
25
|
+
const [state, setState] = useState(() => restore(storage, connections, { expanded: new Set(), collapsed: new Set(), loaded: new Set() }));
|
|
26
|
+
// What the storage holds per connection, so only a change is written back.
|
|
27
|
+
const written = useRef(new Map());
|
|
28
|
+
if (connections.some((connection) => !state.loaded.has(connection.id))) {
|
|
29
|
+
setState((current) => restore(storage, connections, current));
|
|
30
|
+
}
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
for (const id of state.loaded) {
|
|
33
|
+
const next = serializeExpansion(id, state);
|
|
34
|
+
const last = written.current.get(id);
|
|
35
|
+
written.current.set(id, next);
|
|
36
|
+
if (last !== undefined && last !== next) {
|
|
37
|
+
storage?.set(expansionKey(id), next);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}, [storage, state]);
|
|
41
|
+
return {
|
|
42
|
+
expanded: state.expanded,
|
|
43
|
+
collapsed: state.collapsed,
|
|
44
|
+
setExpanded: (update) => setState((current) => ({ ...current, expanded: update(current.expanded) })),
|
|
45
|
+
setCollapsed: (update) => setState((current) => ({ ...current, collapsed: update(current.collapsed) }))
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type EditMove = 'next' | 'previous';
|
|
2
|
+
export interface CellEditorProps {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
onValueChange(value: string): void;
|
|
6
|
+
onCommit(move?: EditMove): void;
|
|
7
|
+
onCancel(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function CellEditor({ value, label, onValueChange, onCommit, onCancel }: CellEditorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Input } from '@adecore/ui';
|
|
4
|
+
/* The input a cell turns into while it is edited. It takes the focus and selects its text when it appears. */
|
|
5
|
+
export function CellEditor({ value, label, onValueChange, onCommit, onCancel }) {
|
|
6
|
+
const input = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
input.current?.focus();
|
|
9
|
+
input.current?.select();
|
|
10
|
+
}, []);
|
|
11
|
+
const handleKeyDown = (event) => {
|
|
12
|
+
if (event.nativeEvent.isComposing) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (event.key === 'Enter') {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
onCommit();
|
|
18
|
+
}
|
|
19
|
+
else if (event.key === 'Escape') {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
onCancel();
|
|
22
|
+
}
|
|
23
|
+
else if (event.key === 'Tab') {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
onCommit(event.shiftKey ? 'previous' : 'next');
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
return (_jsx(Input, { ref: input, mono: true, size: "sm", "aria-label": label, value: value, className: "absolute inset-0 z-10 h-full min-w-0 rounded-none px-3", onChange: (event) => onValueChange(event.target.value), onKeyDown: handleKeyDown, onBlur: () => onCommit() }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CopyFormat } from './copy-formats.ts';
|
|
2
|
+
export interface CopyAsMenuProps {
|
|
3
|
+
disabled: boolean;
|
|
4
|
+
formats: readonly CopyFormat[];
|
|
5
|
+
onCopy(format: CopyFormat): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function CopyAsMenu({ disabled, formats, onCopy }: CopyAsMenuProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Braces, Copy, Database, FileSpreadsheet, Table2 } from 'lucide-react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { ContextMenu, Icon } from '@adecore/ui';
|
|
5
|
+
const ICONS = { tsv: Table2, csv: FileSpreadsheet, json: Braces, sql: Database };
|
|
6
|
+
/* The "Copy as" submenu of the grid's context menu. */
|
|
7
|
+
export function CopyAsMenu({ disabled, formats, onCopy }) {
|
|
8
|
+
const { t } = useTranslation('database');
|
|
9
|
+
return (_jsxs(ContextMenu.SubmenuRoot, { children: [_jsxs(ContextMenu.SubmenuTrigger, { disabled: disabled, children: [_jsx(Icon, { icon: Copy, size: 14 }), t('grid.copyAs.label')] }), _jsx(ContextMenu.Popup, { children: formats.map((format) => (_jsxs(ContextMenu.Item, { onClick: () => onCopy(format), children: [_jsx(Icon, { icon: ICONS[format], size: 14 }), t(`grid.copyAs.${format}`)] }, format))) })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ReactNode, type Ref } from 'react';
|
|
2
|
+
import type { EditValue, Value } from '../protocol/index.ts';
|
|
3
|
+
import type { SqlTarget } from '../sql.ts';
|
|
4
|
+
import type { RangeBlock } from './aggregates.ts';
|
|
5
|
+
import { type GridLayout } from './column-layout.ts';
|
|
6
|
+
import { type GridSort } from './sort.ts';
|
|
7
|
+
import type { ColumnRequest, FocusedCell, GridColumn, GridMenuContext, GridRow } from './types.ts';
|
|
8
|
+
export interface DataGridProps {
|
|
9
|
+
columns: readonly GridColumn[];
|
|
10
|
+
rows: readonly GridRow[];
|
|
11
|
+
label: string;
|
|
12
|
+
editable?: boolean;
|
|
13
|
+
selectedKeys?: ReadonlySet<string>;
|
|
14
|
+
onSelectedKeysChange?(keys: ReadonlySet<string>): void;
|
|
15
|
+
onCommit?(rowKey: string, column: number, value: EditValue): void;
|
|
16
|
+
loadValue?(rowKey: string, column: number): Promise<Value | undefined>;
|
|
17
|
+
sorts?: readonly GridSort[];
|
|
18
|
+
onSortsChange?(sorts: readonly GridSort[]): void;
|
|
19
|
+
onFocusedCellChange?(cell: FocusedCell | null): void;
|
|
20
|
+
onDeleteSelected?(): void;
|
|
21
|
+
sqlTarget?: SqlTarget;
|
|
22
|
+
queryMenu?(context: GridMenuContext): ReactNode;
|
|
23
|
+
menu?(context: GridMenuContext): ReactNode;
|
|
24
|
+
onDuplicateRows?(rowKeys: readonly string[]): void;
|
|
25
|
+
onFollow?(cell: FocusedCell): void;
|
|
26
|
+
canFollow?(cell: FocusedCell): boolean;
|
|
27
|
+
onRangeChange?(block: RangeBlock | null): void;
|
|
28
|
+
initialLayout?: GridLayout;
|
|
29
|
+
onLayoutChange?(layout: GridLayout): void;
|
|
30
|
+
focusRequest?: FocusedCell | null;
|
|
31
|
+
columnRequest?: ColumnRequest | null;
|
|
32
|
+
empty?: ReactNode;
|
|
33
|
+
className?: string;
|
|
34
|
+
ref?: Ref<HTMLDivElement>;
|
|
35
|
+
}
|
|
36
|
+
export declare function DataGrid({ columns, rows, label, editable, selectedKeys, onSelectedKeysChange, onCommit, loadValue, sorts, onSortsChange, onFocusedCellChange, onDeleteSelected, sqlTarget, queryMenu, menu, onDuplicateRows, onFollow, canFollow, onRangeChange, initialLayout, onLayoutChange, focusRequest, columnRequest, empty, className, ref }: DataGridProps): import("react").JSX.Element;
|