@adecore/database 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/DatabaseProvider.d.ts +4 -3
- package/dist/DatabaseProvider.js +15 -3
- package/dist/KeyIcon.d.ts +4 -0
- package/dist/KeyIcon.js +14 -0
- package/dist/actions.d.ts +12 -0
- package/dist/client-context.d.ts +3 -1
- package/dist/client-context.js +2 -0
- package/dist/code-text.d.ts +1 -0
- package/dist/code-text.js +2 -0
- package/dist/column-keys.d.ts +5 -0
- package/dist/column-keys.js +2 -0
- package/dist/connections/ConnectionManager.d.ts +3 -2
- package/dist/connections/ConnectionManager.js +27 -16
- package/dist/connections/connection-config.js +2 -0
- package/dist/console/QueryConsole.d.ts +8 -3
- package/dist/console/QueryConsole.js +110 -50
- package/dist/console/SqlEditor.d.ts +2 -12
- package/dist/console/SqlEditor.js +29 -18
- package/dist/console/StatementResultView.d.ts +5 -1
- package/dist/console/StatementResultView.js +18 -5
- package/dist/console/TransactionControls.js +3 -6
- package/dist/console/editor-slot.d.ts +18 -0
- package/dist/console/editor-slot.js +1 -0
- package/dist/console/result-keys.d.ts +15 -0
- package/dist/console/result-keys.js +79 -0
- package/dist/designer/ColumnsEditor.js +2 -2
- package/dist/designer/DesignerView.d.ts +3 -2
- package/dist/designer/DesignerView.js +5 -4
- package/dist/designer/SqlPreview.js +2 -1
- package/dist/designer/TableDesigner.d.ts +3 -2
- package/dist/designer/TableDesigner.js +2 -2
- package/dist/explorer/DatabaseExplorer.d.ts +4 -1
- package/dist/explorer/DatabaseExplorer.js +79 -23
- package/dist/explorer/RowMenu.js +2 -2
- package/dist/explorer/expansion.js +2 -2
- package/dist/explorer/folders.d.ts +16 -0
- package/dist/explorer/folders.js +1 -0
- package/dist/explorer/tree.d.ts +55 -4
- package/dist/explorer/tree.js +137 -23
- package/dist/grid/CellEditor.js +3 -1
- package/dist/grid/DataGrid.d.ts +2 -2
- package/dist/grid/DataGrid.js +61 -59
- package/dist/grid/EnumPicker.js +2 -1
- package/dist/grid/GridHeaderCell.d.ts +1 -2
- package/dist/grid/GridHeaderCell.js +12 -5
- package/dist/grid/GridRowView.d.ts +49 -0
- package/dist/grid/GridRowView.js +51 -0
- package/dist/grid/RecordView.d.ts +4 -2
- package/dist/grid/RecordView.js +71 -22
- package/dist/grid/ValueDock.d.ts +3 -1
- package/dist/grid/ValueDock.js +9 -3
- package/dist/grid/cell-id.d.ts +1 -0
- package/dist/grid/cell-id.js +2 -0
- package/dist/grid/layout.d.ts +3 -2
- package/dist/grid/layout.js +15 -6
- package/dist/grid/types.d.ts +8 -1
- package/dist/grid/types.js +4 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/locales/en.json +32 -15
- package/dist/locales/nl.json +32 -15
- package/dist/protocol/index.d.ts +1 -1
- package/dist/protocol/messages.d.ts +1 -1
- package/dist/protocol/messages.js +1 -1
- package/dist/protocol/schema.d.ts +17 -0
- package/dist/structure/StructureView.d.ts +5 -3
- package/dist/structure/StructureView.js +8 -6
- package/dist/table/CommandField.js +2 -2
- package/dist/table/TableStatusBar.js +2 -2
- package/dist/table/TableToolbar.d.ts +3 -4
- package/dist/table/TableToolbar.js +4 -3
- package/dist/table/TableView.d.ts +4 -2
- package/dist/table/TableView.js +63 -91
- package/dist/table/grid-rows.js +4 -1
- package/dist/table/useTableTransfer.d.ts +3 -1
- package/dist/table/useTableTransfer.js +19 -5
- package/dist/testing/fake.d.ts +3 -1
- package/dist/testing/fake.js +7 -1
- package/dist/value/ValueEditor.d.ts +9 -0
- package/dist/value/ValueEditor.js +102 -0
- package/dist/value/ValuePanel.d.ts +1 -1
- package/dist/value/ValuePanel.js +3 -94
- package/dist/workbench/DatabaseWorkbench.js +4 -7
- package/package.json +6 -6
- package/src/DatabaseProvider.tsx +24 -5
- package/src/KeyIcon.tsx +15 -0
- package/src/actions.ts +28 -2
- package/src/client-context.ts +5 -1
- package/src/code-text.ts +2 -0
- package/src/column-keys.ts +8 -0
- package/src/connections/ConnectionManager.tsx +98 -68
- package/src/connections/connection-config.ts +2 -0
- package/src/console/QueryConsole.tsx +251 -150
- package/src/console/SqlEditor.tsx +33 -37
- package/src/console/StatementResultView.tsx +61 -27
- package/src/console/TransactionControls.tsx +8 -11
- package/src/console/editor-slot.ts +27 -0
- package/src/console/result-keys.ts +109 -0
- package/src/designer/ColumnsEditor.tsx +3 -3
- package/src/designer/DesignerView.tsx +14 -3
- package/src/designer/SqlPreview.tsx +2 -1
- package/src/designer/TableDesigner.tsx +5 -2
- package/src/explorer/DatabaseExplorer.tsx +170 -26
- package/src/explorer/RowMenu.tsx +2 -2
- package/src/explorer/expansion.ts +4 -4
- package/src/explorer/folders.ts +25 -0
- package/src/explorer/tree.ts +219 -28
- package/src/grid/CellEditor.tsx +3 -2
- package/src/grid/DataGrid.tsx +114 -176
- package/src/grid/EnumPicker.tsx +2 -1
- package/src/grid/GridHeaderCell.tsx +14 -9
- package/src/grid/GridRowView.tsx +184 -0
- package/src/grid/RecordView.tsx +188 -75
- package/src/grid/ValueDock.tsx +12 -3
- package/src/grid/cell-id.ts +2 -0
- package/src/grid/layout.ts +16 -6
- package/src/grid/types.ts +15 -3
- package/src/index.ts +5 -1
- package/src/locales/en.json +32 -15
- package/src/locales/nl.json +32 -15
- package/src/protocol/index.ts +13 -1
- package/src/protocol/messages.ts +1 -1
- package/src/protocol/schema.ts +28 -0
- package/src/structure/StructureView.tsx +16 -6
- package/src/table/CommandField.tsx +2 -2
- package/src/table/TableStatusBar.tsx +3 -3
- package/src/table/TableToolbar.tsx +107 -92
- package/src/table/TableView.tsx +95 -132
- package/src/table/grid-rows.ts +5 -3
- package/src/table/useTableTransfer.ts +21 -6
- package/src/testing/fake.ts +18 -1
- package/src/value/ValueEditor.tsx +213 -0
- package/src/value/ValuePanel.tsx +4 -206
- package/src/workbench/DatabaseWorkbench.tsx +9 -10
package/src/explorer/tree.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import type { ExplorerSelection } from '../actions.ts';
|
|
2
2
|
import type { Connection, TableRef } from '../client/types.ts';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
CheckInfo,
|
|
5
|
+
ColumnInfo,
|
|
6
|
+
Engine,
|
|
7
|
+
ForeignKeyInfo,
|
|
8
|
+
IndexInfo,
|
|
9
|
+
SchemaInfo,
|
|
10
|
+
TableInfo,
|
|
11
|
+
TableKind,
|
|
12
|
+
TableStructure,
|
|
13
|
+
TriggerInfo
|
|
14
|
+
} from '../protocol/index.ts';
|
|
4
15
|
import { qualifiedName } from '../sql.ts';
|
|
16
|
+
import type { ExplorerFolder, ExplorerItem } from './folders.ts';
|
|
5
17
|
|
|
6
18
|
/* Where a lazy list stands: not asked for yet, on its way, failed, or here. */
|
|
7
19
|
export type Load<T> =
|
|
@@ -26,7 +38,12 @@ export const connectionKey = (connectionId: string): string => `c:${connectionId
|
|
|
26
38
|
export const schemaKey = (connectionId: string, schema: string): string => `s:${connectionId}${SEPARATOR}${schema}`;
|
|
27
39
|
export const folderKey = (connectionId: string, schema: string, group: TableKind): string => `f:${connectionId}${SEPARATOR}${schema}${SEPARATOR}${group}`;
|
|
28
40
|
export const tableKey = (ref: TableRef): string => `t:${ref.connectionId}${SEPARATOR}${ref.schema}${SEPARATOR}${ref.table}`;
|
|
29
|
-
|
|
41
|
+
/* The folders of a table, in the order they come under it. */
|
|
42
|
+
export const PARTS = ['columns', 'keys', 'foreignKeys', 'indexes', 'checks', 'triggers'] as const;
|
|
43
|
+
export type TablePart = (typeof PARTS)[number];
|
|
44
|
+
export const partKey = (ref: TableRef, part: TablePart): string => `p:${ref.connectionId}${SEPARATOR}${ref.schema}${SEPARATOR}${ref.table}${SEPARATOR}${part}`;
|
|
45
|
+
/* A folder of a schema, and the columns of a table, are open until the person closes them; everything else is closed until they open it. */
|
|
46
|
+
export const startsOpen = (key: string): boolean => key.startsWith('f:') || (key.startsWith('p:') && key.endsWith(`${SEPARATOR}columns`));
|
|
30
47
|
/* The connection a key of a connection, schema, folder or table belongs to. */
|
|
31
48
|
export const connectionOfKey = (key: string): string => {
|
|
32
49
|
const rest = key.slice(2);
|
|
@@ -34,6 +51,10 @@ export const connectionOfKey = (key: string): string => {
|
|
|
34
51
|
return end === -1 ? rest : rest.slice(0, end);
|
|
35
52
|
};
|
|
36
53
|
export const columnKey = (ref: TableRef, column: string): string => `${tableKey(ref)}${SEPARATOR}${column}`;
|
|
54
|
+
/* A folder of the app's own under a connection, and an item in it. */
|
|
55
|
+
export const appFolderKey = (connectionId: string, folder: string): string => `x:${connectionId}${SEPARATOR}${folder}`;
|
|
56
|
+
const appItemKey = (connectionId: string, folder: string, item: string): string => `${appFolderKey(connectionId, folder)}${SEPARATOR}${item}`;
|
|
57
|
+
const entryKey = (ref: TableRef, part: TablePart, index: number): string => `${partKey(ref, part)}${SEPARATOR}${index}`;
|
|
37
58
|
|
|
38
59
|
export const loadKey = (target: LoadTarget): string => {
|
|
39
60
|
if (target.schema === undefined) {
|
|
@@ -102,6 +123,30 @@ export interface ColumnRow extends RowBase {
|
|
|
102
123
|
readonly foreignKey: boolean;
|
|
103
124
|
}
|
|
104
125
|
|
|
126
|
+
/* One of the folders a table opens into, which exists only when it holds something. */
|
|
127
|
+
export interface PartRow extends RowBase {
|
|
128
|
+
readonly kind: 'part';
|
|
129
|
+
readonly ref: TableRef;
|
|
130
|
+
readonly part: TablePart;
|
|
131
|
+
readonly expanded: boolean;
|
|
132
|
+
readonly count: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* What a folder of a table holds besides its columns. */
|
|
136
|
+
export type TableEntry =
|
|
137
|
+
| { readonly type: 'key'; readonly name: string | null; readonly columns: readonly string[]; readonly primary: boolean }
|
|
138
|
+
| { readonly type: 'foreignKey'; readonly foreignKey: ForeignKeyInfo }
|
|
139
|
+
| { readonly type: 'index'; readonly index: IndexInfo }
|
|
140
|
+
| { readonly type: 'check'; readonly check: CheckInfo }
|
|
141
|
+
| { readonly type: 'trigger'; readonly trigger: TriggerInfo };
|
|
142
|
+
|
|
143
|
+
export interface EntryRow extends RowBase {
|
|
144
|
+
readonly kind: 'entry';
|
|
145
|
+
/* The table the entry belongs to. */
|
|
146
|
+
readonly ref: TableRef;
|
|
147
|
+
readonly entry: TableEntry;
|
|
148
|
+
}
|
|
149
|
+
|
|
105
150
|
export interface LoadingRow extends RowBase {
|
|
106
151
|
readonly kind: 'loading';
|
|
107
152
|
}
|
|
@@ -112,21 +157,59 @@ export interface ErrorRow extends RowBase {
|
|
|
112
157
|
readonly retry: LoadTarget;
|
|
113
158
|
}
|
|
114
159
|
|
|
160
|
+
/* A folder of the app's own under a connection. */
|
|
161
|
+
export interface AppFolderRow extends RowBase {
|
|
162
|
+
readonly kind: 'appFolder';
|
|
163
|
+
readonly connection: Connection;
|
|
164
|
+
readonly folder: ExplorerFolder;
|
|
165
|
+
readonly expanded: boolean;
|
|
166
|
+
/* Its items, or those that match the filter. */
|
|
167
|
+
readonly count: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface AppItemRow extends RowBase {
|
|
171
|
+
readonly kind: 'appItem';
|
|
172
|
+
readonly connection: Connection;
|
|
173
|
+
readonly item: ExplorerItem;
|
|
174
|
+
}
|
|
175
|
+
|
|
115
176
|
/* A connection without schemas, a schema without tables or a table without columns. */
|
|
116
177
|
export interface EmptyRow extends RowBase {
|
|
117
178
|
readonly kind: 'empty';
|
|
118
179
|
readonly of: 'schemas' | 'tables' | 'columns';
|
|
119
180
|
}
|
|
120
181
|
|
|
121
|
-
export type TreeRow =
|
|
122
|
-
|
|
123
|
-
|
|
182
|
+
export type TreeRow =
|
|
183
|
+
| ConnectionRow
|
|
184
|
+
| SchemaRow
|
|
185
|
+
| FolderRow
|
|
186
|
+
| TableRow
|
|
187
|
+
| PartRow
|
|
188
|
+
| ColumnRow
|
|
189
|
+
| EntryRow
|
|
190
|
+
| AppFolderRow
|
|
191
|
+
| AppItemRow
|
|
192
|
+
| LoadingRow
|
|
193
|
+
| ErrorRow
|
|
194
|
+
| EmptyRow;
|
|
195
|
+
|
|
196
|
+
export type ExpandableRow = ConnectionRow | SchemaRow | FolderRow | TableRow | PartRow | AppFolderRow;
|
|
124
197
|
|
|
125
198
|
export const isExpandable = (row: TreeRow): row is ExpandableRow =>
|
|
126
|
-
row.kind === 'connection' || row.kind === 'schema' || row.kind === 'folder' || row.kind === 'table';
|
|
199
|
+
row.kind === 'connection' || row.kind === 'schema' || row.kind === 'folder' || row.kind === 'table' || row.kind === 'part' || row.kind === 'appFolder';
|
|
127
200
|
|
|
128
201
|
/* The table a row stands for, when it stands for one. */
|
|
129
|
-
export const tableOf = (row: TreeRow): TableRef | null => (row.kind === 'table' || row.kind === 'column' ? row.ref : null);
|
|
202
|
+
export const tableOf = (row: TreeRow): TableRef | null => (row.kind === 'table' || row.kind === 'column' || row.kind === 'entry' ? row.ref : null);
|
|
203
|
+
|
|
204
|
+
/* The table activating a row opens: its own, or for a foreign key the table it references. */
|
|
205
|
+
export const openedTableOf = (row: TreeRow): TableRef | null =>
|
|
206
|
+
row.kind === 'entry' && row.entry.type === 'foreignKey'
|
|
207
|
+
? { connectionId: row.ref.connectionId, schema: row.entry.foreignKey.referencedSchema, table: row.entry.foreignKey.referencedTable }
|
|
208
|
+
: tableOf(row);
|
|
209
|
+
|
|
210
|
+
/* Whether a list of tables holds `name` as a table or a view; `undefined` while the list has not loaded or lacks it. */
|
|
211
|
+
export const tableKindIn = (tables: Load<readonly TableInfo[]>, name: string): TableKind | undefined =>
|
|
212
|
+
tables.status === 'ready' ? tables.value.find((table) => table.name === name)?.kind : undefined;
|
|
130
213
|
|
|
131
214
|
/* What picking a row selects: its connection, its schema, or its table (a column stands for its table). Status rows select nothing. */
|
|
132
215
|
export const selectionOf = (row: TreeRow): ExplorerSelection | null => {
|
|
@@ -137,7 +220,9 @@ export const selectionOf = (row: TreeRow): ExplorerSelection | null => {
|
|
|
137
220
|
case 'folder':
|
|
138
221
|
return { connectionId: row.connection.id, schema: row.schema };
|
|
139
222
|
case 'table':
|
|
223
|
+
case 'part':
|
|
140
224
|
case 'column':
|
|
225
|
+
case 'entry':
|
|
141
226
|
return { connectionId: row.ref.connectionId, schema: row.ref.schema, table: row.ref.table };
|
|
142
227
|
default:
|
|
143
228
|
return null;
|
|
@@ -154,9 +239,14 @@ export const selectionKey = (selection: ExplorerSelection): string => {
|
|
|
154
239
|
: tableKey({ connectionId: selection.connectionId, schema: selection.schema, table: selection.table });
|
|
155
240
|
};
|
|
156
241
|
|
|
157
|
-
/* 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. */
|
|
242
|
+
/* 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. */
|
|
158
243
|
export const hasMenu = (row: TreeRow): boolean =>
|
|
159
|
-
row.kind === 'connection' ||
|
|
244
|
+
row.kind === 'connection' ||
|
|
245
|
+
row.kind === 'schema' ||
|
|
246
|
+
row.kind === 'folder' ||
|
|
247
|
+
row.kind === 'table' ||
|
|
248
|
+
row.kind === 'column' ||
|
|
249
|
+
(row.kind === 'appItem' && row.item.menu !== undefined);
|
|
160
250
|
|
|
161
251
|
/* Only the rows a selection can point at are drawn selected; a folder or a column leaves that to the row above it. */
|
|
162
252
|
export const isSelectable = (row: TreeRow): boolean => row.kind === 'connection' || row.kind === 'schema' || row.kind === 'table';
|
|
@@ -175,6 +265,8 @@ export interface TreeInput {
|
|
|
175
265
|
tables(connectionId: string, schema: string): Load<readonly TableInfo[]>;
|
|
176
266
|
structures(connectionId: string, schema: string, table: string): Load<TableStructure>;
|
|
177
267
|
versions(connectionId: string): string | null;
|
|
268
|
+
/* The app's own folders under a connection. */
|
|
269
|
+
folders?(connectionId: string): readonly ExplorerFolder[];
|
|
178
270
|
}
|
|
179
271
|
|
|
180
272
|
/* The schemas a list shows. */
|
|
@@ -207,6 +299,23 @@ const statusDraft = (row: StatusRow, level: number, parent: string, focusable: b
|
|
|
207
299
|
|
|
208
300
|
const matches = (name: string, query: string): boolean => name.toLowerCase().includes(query);
|
|
209
301
|
|
|
302
|
+
/* 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. */
|
|
303
|
+
export const tableEntries = (structure: TableStructure): Readonly<Record<Exclude<TablePart, 'columns'>, readonly TableEntry[]>> => {
|
|
304
|
+
const primaryIndex = structure.indexes.find((index) => index.primary);
|
|
305
|
+
const primary: TableEntry[] =
|
|
306
|
+
structure.primaryKey.length === 0 ? [] : [{ type: 'key', name: primaryIndex?.name ?? null, columns: structure.primaryKey, primary: true }];
|
|
307
|
+
const unique = structure.indexes
|
|
308
|
+
.filter((index) => index.unique && !index.primary)
|
|
309
|
+
.map((index): TableEntry => ({ type: 'key', name: index.name, columns: index.columns, primary: false }));
|
|
310
|
+
return {
|
|
311
|
+
keys: [...primary, ...unique],
|
|
312
|
+
foreignKeys: structure.foreignKeys.map((foreignKey) => ({ type: 'foreignKey', foreignKey })),
|
|
313
|
+
indexes: structure.indexes.filter((index) => !index.primary).map((index) => ({ type: 'index', index })),
|
|
314
|
+
checks: (structure.checks ?? []).map((check) => ({ type: 'check', check })),
|
|
315
|
+
triggers: (structure.triggers ?? []).map((trigger) => ({ type: 'trigger', trigger }))
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
|
|
210
319
|
/*
|
|
211
320
|
* The rows a tree shows, top to bottom. With a filter only the loaded tables that match stay, under
|
|
212
321
|
* their folder, schema and connection, which then count as open whatever the person left them at.
|
|
@@ -215,22 +324,10 @@ export const flattenTree = (input: TreeInput): TreeRow[] => {
|
|
|
215
324
|
const query = input.filter.trim().toLowerCase();
|
|
216
325
|
const filtering = query !== '';
|
|
217
326
|
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
load.status === 'error'
|
|
223
|
-
? { kind: 'error', message: load.message, retry: { connectionId: ref.connectionId, schema: ref.schema, table: ref.table } }
|
|
224
|
-
: { kind: 'loading' };
|
|
225
|
-
return [statusDraft(row, level, parent, load.status === 'error')];
|
|
226
|
-
}
|
|
227
|
-
const { columns, primaryKey, foreignKeys } = load.value;
|
|
228
|
-
if (columns.length === 0) {
|
|
229
|
-
return [statusDraft({ kind: 'empty', of: 'columns' }, level, parent, true)];
|
|
230
|
-
}
|
|
231
|
-
const primary = new Set(primaryKey);
|
|
232
|
-
const foreign = new Set(foreignKeys.flatMap((foreignKey) => foreignKey.columns));
|
|
233
|
-
return columns.map((column): Draft => ({
|
|
327
|
+
const columnRows = (ref: TableRef, structure: TableStructure, level: number, parent: string): Draft[] => {
|
|
328
|
+
const primary = new Set(structure.primaryKey);
|
|
329
|
+
const foreign = new Set(structure.foreignKeys.flatMap((foreignKey) => foreignKey.columns));
|
|
330
|
+
return structure.columns.map((column): Draft => ({
|
|
234
331
|
make: (place) => ({
|
|
235
332
|
kind: 'column',
|
|
236
333
|
key: columnKey(ref, column.name),
|
|
@@ -247,12 +344,62 @@ export const flattenTree = (input: TreeInput): TreeRow[] => {
|
|
|
247
344
|
}));
|
|
248
345
|
};
|
|
249
346
|
|
|
347
|
+
/* The folders a table opens into, each only when it holds something. A view has only its columns, straight under it. */
|
|
348
|
+
const tableChildren = (ref: TableRef, kind: TableKind, level: number, parent: string): Draft[] => {
|
|
349
|
+
const load = input.structures(ref.connectionId, ref.schema, ref.table);
|
|
350
|
+
if (load.status !== 'ready') {
|
|
351
|
+
const row: StatusRow =
|
|
352
|
+
load.status === 'error'
|
|
353
|
+
? { kind: 'error', message: load.message, retry: { connectionId: ref.connectionId, schema: ref.schema, table: ref.table } }
|
|
354
|
+
: { kind: 'loading' };
|
|
355
|
+
return [statusDraft(row, level, parent, load.status === 'error')];
|
|
356
|
+
}
|
|
357
|
+
const structure = load.value;
|
|
358
|
+
if (structure.columns.length === 0) {
|
|
359
|
+
return [statusDraft({ kind: 'empty', of: 'columns' }, level, parent, true)];
|
|
360
|
+
}
|
|
361
|
+
if (kind === 'view') {
|
|
362
|
+
return columnRows(ref, structure, level, parent);
|
|
363
|
+
}
|
|
364
|
+
const entries = tableEntries(structure);
|
|
365
|
+
return PARTS.flatMap((part): Draft[] => {
|
|
366
|
+
const count = part === 'columns' ? structure.columns.length : entries[part].length;
|
|
367
|
+
if (count === 0) {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
const key = partKey(ref, part);
|
|
371
|
+
const open = startsOpen(key) ? !input.collapsed.has(key) : input.expanded.has(key);
|
|
372
|
+
const below = (): Draft[] =>
|
|
373
|
+
part === 'columns'
|
|
374
|
+
? columnRows(ref, structure, level + 1, key)
|
|
375
|
+
: entries[part].map((entry, index): Draft => ({
|
|
376
|
+
make: (place) => ({
|
|
377
|
+
kind: 'entry',
|
|
378
|
+
key: entryKey(ref, part, index),
|
|
379
|
+
level: level + 1,
|
|
380
|
+
parent: key,
|
|
381
|
+
focusable: true,
|
|
382
|
+
ref,
|
|
383
|
+
entry,
|
|
384
|
+
...place
|
|
385
|
+
}),
|
|
386
|
+
below: []
|
|
387
|
+
}));
|
|
388
|
+
return [
|
|
389
|
+
{
|
|
390
|
+
make: (place) => ({ kind: 'part', key, level, parent, focusable: true, ref, part, expanded: open, count, ...place }),
|
|
391
|
+
below: open ? settle(below()) : []
|
|
392
|
+
}
|
|
393
|
+
];
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
|
|
250
397
|
const tableDraft = (ref: TableRef, table: TableInfo, level: number, parent: string): Draft => {
|
|
251
398
|
const key = tableKey(ref);
|
|
252
399
|
const open = input.expanded.has(key);
|
|
253
400
|
return {
|
|
254
401
|
make: (place) => ({ kind: 'table', key, level, parent, focusable: true, ref, table, expanded: open, ...place }),
|
|
255
|
-
below: open ? settle(
|
|
402
|
+
below: open ? settle(tableChildren(ref, table.kind, level + 1, key)) : []
|
|
256
403
|
};
|
|
257
404
|
};
|
|
258
405
|
|
|
@@ -314,10 +461,52 @@ export const flattenTree = (input: TreeInput): TreeRow[] => {
|
|
|
314
461
|
};
|
|
315
462
|
};
|
|
316
463
|
|
|
464
|
+
/* The app's folders under a connection, each while it holds items; with a filter only the items that match, in folders that count as open. */
|
|
465
|
+
const appFolderDrafts = (connection: Connection, level: number, parent: string): Draft[] =>
|
|
466
|
+
(input.folders?.(connection.id) ?? []).flatMap((folder): Draft[] => {
|
|
467
|
+
const items = filtering ? folder.items.filter((item) => matches(item.label, query)) : folder.items;
|
|
468
|
+
if (items.length === 0) {
|
|
469
|
+
return [];
|
|
470
|
+
}
|
|
471
|
+
const key = appFolderKey(connection.id, folder.id);
|
|
472
|
+
const open = filtering || input.expanded.has(key);
|
|
473
|
+
const rows = items.map((item): Draft => ({
|
|
474
|
+
make: (place) => ({
|
|
475
|
+
kind: 'appItem',
|
|
476
|
+
key: appItemKey(connection.id, folder.id, item.id),
|
|
477
|
+
level: level + 1,
|
|
478
|
+
parent: key,
|
|
479
|
+
focusable: true,
|
|
480
|
+
connection,
|
|
481
|
+
item,
|
|
482
|
+
...place
|
|
483
|
+
}),
|
|
484
|
+
below: []
|
|
485
|
+
}));
|
|
486
|
+
return [
|
|
487
|
+
{
|
|
488
|
+
make: (place) => ({
|
|
489
|
+
kind: 'appFolder',
|
|
490
|
+
key,
|
|
491
|
+
level,
|
|
492
|
+
parent,
|
|
493
|
+
focusable: true,
|
|
494
|
+
connection,
|
|
495
|
+
folder,
|
|
496
|
+
expanded: open,
|
|
497
|
+
count: items.length,
|
|
498
|
+
...place
|
|
499
|
+
}),
|
|
500
|
+
below: open ? settle(rows) : []
|
|
501
|
+
}
|
|
502
|
+
];
|
|
503
|
+
});
|
|
504
|
+
|
|
317
505
|
const connectionDraft = (connection: Connection): Draft | null => {
|
|
318
506
|
const key = connectionKey(connection.id);
|
|
319
507
|
const load = input.schemas(connection.id);
|
|
320
|
-
const
|
|
508
|
+
const appFolders = appFolderDrafts(connection, 2, key);
|
|
509
|
+
const open = filtering ? load.status === 'ready' || appFolders.length > 0 : input.expanded.has(key);
|
|
321
510
|
let children: Draft[] = [];
|
|
322
511
|
let schemaCount: ConnectionRow['schemaCount'] = null;
|
|
323
512
|
if (load.status === 'ready') {
|
|
@@ -333,11 +522,13 @@ export const flattenTree = (input: TreeInput): TreeRow[] => {
|
|
|
333
522
|
} else {
|
|
334
523
|
children = visible.flatMap((entry) => schemaDraft(connection, entry.name, key) ?? []);
|
|
335
524
|
}
|
|
336
|
-
} else if (open) {
|
|
525
|
+
} else if (open && !filtering) {
|
|
337
526
|
const row: StatusRow =
|
|
338
527
|
load.status === 'error' ? { kind: 'error', message: load.message, retry: { connectionId: connection.id } } : { kind: 'loading' };
|
|
339
528
|
children = [statusDraft(row, 2, key, load.status === 'error')];
|
|
340
529
|
}
|
|
530
|
+
// Behind a status row too: what the app keeps of a connection does not wait on its server.
|
|
531
|
+
children = [...children, ...(open ? appFolders : [])];
|
|
341
532
|
if (filtering && children.length === 0) {
|
|
342
533
|
return null;
|
|
343
534
|
}
|
package/src/grid/CellEditor.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, type KeyboardEvent } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
2
3
|
import { Input } from '@adecore/ui';
|
|
4
|
+
import { CODE_TEXT } from '../code-text.ts';
|
|
3
5
|
|
|
4
6
|
export type EditMove = 'next' | 'previous';
|
|
5
7
|
|
|
@@ -40,11 +42,10 @@ export function CellEditor({ value, label, onValueChange, onCommit, onCancel }:
|
|
|
40
42
|
return (
|
|
41
43
|
<Input
|
|
42
44
|
ref={input}
|
|
43
|
-
mono
|
|
44
45
|
size="sm"
|
|
45
46
|
aria-label={label}
|
|
46
47
|
value={value}
|
|
47
|
-
className=
|
|
48
|
+
className={clsx(CODE_TEXT, 'absolute inset-0 z-10 h-full min-w-0 rounded-none px-3')}
|
|
48
49
|
onChange={(event) => onValueChange(event.target.value)}
|
|
49
50
|
onKeyDown={handleKeyDown}
|
|
50
51
|
onBlur={() => onCommit()}
|