@adecore/database 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +246 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, type KeyboardEvent, type ReactNode, type Ref } from 'react';
|
|
1
|
+
import { useEffect, useRef, useState, type KeyboardEvent, type MouseEvent, type ReactNode, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { CircleAlert, Database, Eye, Folder, KeyRound,
|
|
3
|
+
import { CircleAlert, Columns2, Database, Eye, Folder, KeyRound, ListOrdered, Lock, Search, ShieldCheck, Table, Zap, type LucideIcon } from 'lucide-react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Button, ContextMenu, EmptyState, Icon, Input, Spinner, Tooltip, Tree } from '@adecore/ui';
|
|
6
6
|
import { formatNumber } from '@adecore/ui/format';
|
|
@@ -8,6 +8,9 @@ import type { ExplorerSelection } from '../actions.ts';
|
|
|
8
8
|
import { useDatabaseAction, useDatabaseClient } from '../client-context.ts';
|
|
9
9
|
import type { Connection } from '../client/types.ts';
|
|
10
10
|
import { EngineIcon } from '../connections/EngineIcon.tsx';
|
|
11
|
+
import { keyLabelOf, type ColumnKeys } from '../column-keys.ts';
|
|
12
|
+
import { KeyIcon } from '../KeyIcon.tsx';
|
|
13
|
+
import type { ExplorerFolder } from './folders.ts';
|
|
11
14
|
import { RowMenu } from './RowMenu.tsx';
|
|
12
15
|
import { TableDialog, type TableRequest } from './TableDialog.tsx';
|
|
13
16
|
import {
|
|
@@ -15,14 +18,17 @@ import {
|
|
|
15
18
|
flattenTree,
|
|
16
19
|
hasMenu,
|
|
17
20
|
isExpandable,
|
|
18
|
-
|
|
21
|
+
startsOpen,
|
|
19
22
|
isSelectable,
|
|
20
23
|
navigate,
|
|
21
24
|
neededLoads,
|
|
22
25
|
selectionKey,
|
|
23
26
|
selectionOf,
|
|
24
27
|
tabStop,
|
|
28
|
+
openedTableOf,
|
|
29
|
+
tableKindIn,
|
|
25
30
|
tableOf,
|
|
31
|
+
type EntryRow,
|
|
26
32
|
type ErrorRow,
|
|
27
33
|
type LoadTarget,
|
|
28
34
|
type ExpandableRow,
|
|
@@ -39,15 +45,30 @@ export interface DatabaseExplorerProps {
|
|
|
39
45
|
defaultValue?: ExplorerSelection | null;
|
|
40
46
|
onValueChange?(selection: ExplorerSelection | null): void;
|
|
41
47
|
showSystemSchemas?: boolean;
|
|
48
|
+
/* Opens a table on a click too, with `preview: true`; a double click or Enter then sends `preview: false`. */
|
|
49
|
+
openOnClick?: boolean;
|
|
50
|
+
/* The app's own folders under each connection, by connection id, after its schemas. */
|
|
51
|
+
folders?: Readonly<Record<string, readonly ExplorerFolder[]>>;
|
|
42
52
|
className?: string;
|
|
43
53
|
ref?: Ref<HTMLDivElement>;
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
/*
|
|
47
57
|
* The connections, their schemas and their tables as a tree, loaded as each node opens. Every row
|
|
48
|
-
* selects; a double click or Enter on a table asks the app to open it through `useDatabaseAction
|
|
58
|
+
* selects; a double click or Enter on a table asks the app to open it through `useDatabaseAction`,
|
|
59
|
+
* and on an item of a folder the app added opens it through the item's own `onOpen`.
|
|
49
60
|
*/
|
|
50
|
-
export function DatabaseExplorer({
|
|
61
|
+
export function DatabaseExplorer({
|
|
62
|
+
connections,
|
|
63
|
+
value,
|
|
64
|
+
defaultValue = null,
|
|
65
|
+
onValueChange,
|
|
66
|
+
showSystemSchemas = false,
|
|
67
|
+
openOnClick = false,
|
|
68
|
+
folders,
|
|
69
|
+
className,
|
|
70
|
+
ref
|
|
71
|
+
}: DatabaseExplorerProps) {
|
|
51
72
|
const { t } = useTranslation('database');
|
|
52
73
|
const client = useDatabaseClient();
|
|
53
74
|
const act = useDatabaseAction();
|
|
@@ -71,7 +92,8 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
71
92
|
schemas: loads.schemas,
|
|
72
93
|
tables: loads.tables,
|
|
73
94
|
structures: loads.structures,
|
|
74
|
-
versions: loads.versions
|
|
95
|
+
versions: loads.versions,
|
|
96
|
+
folders: (connectionId: string) => folders?.[connectionId] ?? []
|
|
75
97
|
};
|
|
76
98
|
const rows = flattenTree({ ...input, filter });
|
|
77
99
|
const stop = tabStop(rows, activeKey, selectedKey);
|
|
@@ -90,8 +112,7 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
90
112
|
}, [activeKey]);
|
|
91
113
|
|
|
92
114
|
const setOpen = (key: string, open: boolean): void => {
|
|
93
|
-
|
|
94
|
-
const folder = isFolderKey(key);
|
|
115
|
+
const folder = startsOpen(key);
|
|
95
116
|
const toggle = (current: ReadonlySet<string>): ReadonlySet<string> => {
|
|
96
117
|
const next = new Set(current);
|
|
97
118
|
if (open !== folder) {
|
|
@@ -122,23 +143,34 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
122
143
|
}
|
|
123
144
|
};
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
/* `preview` says a click opened it, which only an explorer that opens on a click tells the app. */
|
|
147
|
+
const open = (row: TreeRow, preview = false): void => {
|
|
148
|
+
if (row.kind === 'appItem') {
|
|
149
|
+
row.item.onOpen?.({ preview });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const ref = openedTableOf(row);
|
|
153
|
+
if (ref === null) {
|
|
154
|
+
return;
|
|
130
155
|
}
|
|
156
|
+
pick(row);
|
|
157
|
+
const tableKind = row.kind === 'table' && row.ref === ref ? row.table.kind : tableKindIn(loads.tables(ref.connectionId, ref.schema), ref.table);
|
|
158
|
+
act?.({ kind: 'open-table', ref, view: 'data', ...(tableKind === undefined ? {} : { tableKind }), ...(openOnClick ? { preview } : {}) });
|
|
131
159
|
};
|
|
132
160
|
|
|
133
161
|
const activate = (row: TreeRow): void => {
|
|
134
162
|
switch (row.kind) {
|
|
135
163
|
case 'table':
|
|
136
164
|
case 'column':
|
|
165
|
+
case 'entry':
|
|
166
|
+
case 'appItem':
|
|
137
167
|
open(row);
|
|
138
168
|
break;
|
|
139
169
|
case 'connection':
|
|
140
170
|
case 'schema':
|
|
141
171
|
case 'folder':
|
|
172
|
+
case 'part':
|
|
173
|
+
case 'appFolder':
|
|
142
174
|
pick(row);
|
|
143
175
|
toggle(row);
|
|
144
176
|
break;
|
|
@@ -170,11 +202,18 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
170
202
|
}
|
|
171
203
|
};
|
|
172
204
|
|
|
173
|
-
|
|
205
|
+
/* `count` is the click's `detail`: the second click of a double click leaves the opening to the double click. */
|
|
206
|
+
const click = (row: TreeRow, count: number): void => {
|
|
174
207
|
if (pressedInPopup()) {
|
|
175
208
|
return;
|
|
176
209
|
}
|
|
177
210
|
setActiveKey(row.key);
|
|
211
|
+
if (openOnClick && (row.kind === 'table' || row.kind === 'appItem')) {
|
|
212
|
+
if (count <= 1) {
|
|
213
|
+
open(row, true);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
178
217
|
pick(row);
|
|
179
218
|
if (row.kind !== 'table' && isExpandable(row)) {
|
|
180
219
|
toggle(row);
|
|
@@ -211,7 +250,9 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
211
250
|
const renderRow = (row: TreeRow, index: number): ReactNode => {
|
|
212
251
|
const connection = connectionOf(row);
|
|
213
252
|
const menu =
|
|
214
|
-
|
|
253
|
+
row.kind === 'appItem' ? (
|
|
254
|
+
(row.item.menu ?? null)
|
|
255
|
+
) : connection !== undefined && hasMenu(row) ? (
|
|
215
256
|
<RowMenu row={row} connection={connection} onRefresh={refresh} onDisconnect={disconnect} onRequest={setRequest} />
|
|
216
257
|
) : null;
|
|
217
258
|
const selectedRow = isSelected(row);
|
|
@@ -234,8 +275,8 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
234
275
|
rowElements.current.set(row.key, element);
|
|
235
276
|
}
|
|
236
277
|
},
|
|
237
|
-
onClick: () => click(row),
|
|
238
|
-
onDoubleClick: tableOf(row) === null ? undefined : () => open(row),
|
|
278
|
+
onClick: (event: MouseEvent) => click(row, event.detail),
|
|
279
|
+
onDoubleClick: tableOf(row) === null && row.kind !== 'appItem' ? undefined : () => open(row),
|
|
239
280
|
onFocus: () => {
|
|
240
281
|
if (row.focusable) {
|
|
241
282
|
setActiveKey(row.key);
|
|
@@ -282,7 +323,7 @@ export function DatabaseExplorer({ connections, value, defaultValue = null, onVa
|
|
|
282
323
|
) : rows.length === 0 ? (
|
|
283
324
|
<EmptyState icon={Search}>{t('explorer.noMatches', { query: filter.trim() })}</EmptyState>
|
|
284
325
|
) : (
|
|
285
|
-
<Tree.Root aria-label={t('explorer.label')} className="min-h-0 grow overflow-y-auto
|
|
326
|
+
<Tree.Root aria-label={t('explorer.label')} overflow="scroll" className="min-h-0 grow overflow-y-auto pt-2" onKeyDown={onKeyDown}>
|
|
286
327
|
{rows.map(renderRow)}
|
|
287
328
|
</Tree.Root>
|
|
288
329
|
)}
|
|
@@ -307,7 +348,7 @@ function RowContent({ row, onToggle }: { row: TreeRow; onToggle?: () => void })
|
|
|
307
348
|
</span>
|
|
308
349
|
</Tooltip>
|
|
309
350
|
)}
|
|
310
|
-
{row.version !== null && <span className="
|
|
351
|
+
{row.version !== null && <span className="text-text-faint">{row.version}</span>}
|
|
311
352
|
{row.schemaCount !== null && <SchemaCount shown={row.schemaCount.shown} total={row.schemaCount.total} />}
|
|
312
353
|
</>
|
|
313
354
|
);
|
|
@@ -336,13 +377,41 @@ function RowContent({ row, onToggle }: { row: TreeRow; onToggle?: () => void })
|
|
|
336
377
|
<Tree.Label>{row.table.name}</Tree.Label>
|
|
337
378
|
</>
|
|
338
379
|
);
|
|
380
|
+
case 'part':
|
|
381
|
+
return (
|
|
382
|
+
<>
|
|
383
|
+
<Tree.Chevron expanded={row.expanded} onExpandedChange={onToggle} />
|
|
384
|
+
<Icon icon={Folder} size={16} className="shrink-0 text-text-muted" />
|
|
385
|
+
<Tree.Label>{t(`explorer.parts.${row.part}`)}</Tree.Label>
|
|
386
|
+
<span className="shrink-0 text-text-faint tabular-nums">{formatNumber(row.count)}</span>
|
|
387
|
+
</>
|
|
388
|
+
);
|
|
389
|
+
case 'appFolder':
|
|
390
|
+
return (
|
|
391
|
+
<>
|
|
392
|
+
<Tree.Chevron expanded={row.expanded} onExpandedChange={onToggle} />
|
|
393
|
+
<Icon icon={Folder} size={16} className="shrink-0 text-text-muted" />
|
|
394
|
+
<Tree.Label>{row.folder.label}</Tree.Label>
|
|
395
|
+
<span className="shrink-0 text-text-faint tabular-nums">{formatNumber(row.count)}</span>
|
|
396
|
+
</>
|
|
397
|
+
);
|
|
398
|
+
case 'appItem':
|
|
399
|
+
return (
|
|
400
|
+
<>
|
|
401
|
+
<Tree.ChevronSlot />
|
|
402
|
+
<Icon icon={row.item.icon} size={16} className="shrink-0 text-text-muted" />
|
|
403
|
+
<Tree.Label>{row.item.label}</Tree.Label>
|
|
404
|
+
</>
|
|
405
|
+
);
|
|
406
|
+
case 'entry':
|
|
407
|
+
return <EntryContent row={row} />;
|
|
339
408
|
case 'column':
|
|
340
409
|
return (
|
|
341
410
|
<>
|
|
342
411
|
<Tree.ChevronSlot />
|
|
343
412
|
<ColumnIcon primaryKey={row.primaryKey} foreignKey={row.foreignKey} />
|
|
344
413
|
<Tree.Label>{row.column.name}</Tree.Label>
|
|
345
|
-
{row.column.type !== '' && <span className="
|
|
414
|
+
{row.column.type !== '' && <span className="text-text-faint">{row.column.type}</span>}
|
|
346
415
|
</>
|
|
347
416
|
);
|
|
348
417
|
case 'loading':
|
|
@@ -377,20 +446,95 @@ function SchemaCount({ shown, total }: { shown: number; total: number }) {
|
|
|
377
446
|
);
|
|
378
447
|
}
|
|
379
448
|
|
|
380
|
-
|
|
449
|
+
/* A step smaller than the icons of the rows above, in the same 16px slot, so the names stay in line. */
|
|
450
|
+
const ENTRY_ICON_SLOT = 'inline-flex size-4 shrink-0 items-center justify-center';
|
|
451
|
+
|
|
452
|
+
function ColumnIcon(keys: ColumnKeys) {
|
|
381
453
|
const { t } = useTranslation('database');
|
|
382
|
-
if (!primaryKey && !foreignKey) {
|
|
383
|
-
return
|
|
454
|
+
if (!keys.primaryKey && !keys.foreignKey) {
|
|
455
|
+
return (
|
|
456
|
+
<span className={ENTRY_ICON_SLOT}>
|
|
457
|
+
<Icon icon={Columns2} size={14} className="text-text-faint" />
|
|
458
|
+
</span>
|
|
459
|
+
);
|
|
384
460
|
}
|
|
385
461
|
return (
|
|
386
|
-
<Tooltip label={t(
|
|
387
|
-
<span className=
|
|
388
|
-
<
|
|
462
|
+
<Tooltip label={t(keyLabelOf(keys))}>
|
|
463
|
+
<span className={ENTRY_ICON_SLOT}>
|
|
464
|
+
<KeyIcon {...keys} size={14} />
|
|
389
465
|
</span>
|
|
390
466
|
</Tooltip>
|
|
391
467
|
);
|
|
392
468
|
}
|
|
393
469
|
|
|
470
|
+
/* A key, a foreign key, an index, a check or a trigger of a table: its name, and what it covers in the quiet color of a column's type. */
|
|
471
|
+
function EntryContent({ row }: { row: EntryRow }) {
|
|
472
|
+
const { t } = useTranslation('database');
|
|
473
|
+
const { entry } = row;
|
|
474
|
+
const icon = (glyph: LucideIcon, className: string): ReactNode => (
|
|
475
|
+
<span className={ENTRY_ICON_SLOT}>
|
|
476
|
+
<Icon icon={glyph} size={14} className={className} />
|
|
477
|
+
</span>
|
|
478
|
+
);
|
|
479
|
+
switch (entry.type) {
|
|
480
|
+
case 'key':
|
|
481
|
+
return (
|
|
482
|
+
<>
|
|
483
|
+
<Tree.ChevronSlot />
|
|
484
|
+
{icon(KeyRound, entry.primary ? 'text-(--file-icon-yellow)' : 'text-text-muted')}
|
|
485
|
+
<Tree.Label>{entry.name ?? t('explorer.primaryKey')}</Tree.Label>
|
|
486
|
+
<span className="text-text-faint">{entry.columns.join(', ')}</span>
|
|
487
|
+
</>
|
|
488
|
+
);
|
|
489
|
+
case 'foreignKey': {
|
|
490
|
+
const { foreignKey } = entry;
|
|
491
|
+
const columns = foreignKey.columns.join(', ');
|
|
492
|
+
return (
|
|
493
|
+
<>
|
|
494
|
+
<Tree.ChevronSlot />
|
|
495
|
+
{icon(KeyRound, 'text-(--file-icon-blue)')}
|
|
496
|
+
<Tree.Label>{foreignKey.name ?? columns}</Tree.Label>
|
|
497
|
+
<span className="text-text-faint">
|
|
498
|
+
{t('explorer.references', {
|
|
499
|
+
columns,
|
|
500
|
+
table: foreignKey.referencedTable,
|
|
501
|
+
referenced: foreignKey.referencedColumns.join(', ')
|
|
502
|
+
})}
|
|
503
|
+
</span>
|
|
504
|
+
</>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
case 'index':
|
|
508
|
+
return (
|
|
509
|
+
<>
|
|
510
|
+
<Tree.ChevronSlot />
|
|
511
|
+
{icon(ListOrdered, 'text-text-faint')}
|
|
512
|
+
<Tree.Label>{entry.index.name}</Tree.Label>
|
|
513
|
+
<span className="text-text-faint">{entry.index.columns.join(', ')}</span>
|
|
514
|
+
{entry.index.unique && <span className="text-text-faint">{t('explorer.unique')}</span>}
|
|
515
|
+
</>
|
|
516
|
+
);
|
|
517
|
+
case 'check':
|
|
518
|
+
return (
|
|
519
|
+
<>
|
|
520
|
+
<Tree.ChevronSlot />
|
|
521
|
+
{icon(ShieldCheck, 'text-text-faint')}
|
|
522
|
+
<Tree.Label>{entry.check.name ?? t('explorer.check')}</Tree.Label>
|
|
523
|
+
<span className="font-mono text-text-faint">{entry.check.expression}</span>
|
|
524
|
+
</>
|
|
525
|
+
);
|
|
526
|
+
case 'trigger':
|
|
527
|
+
return (
|
|
528
|
+
<>
|
|
529
|
+
<Tree.ChevronSlot />
|
|
530
|
+
{icon(Zap, 'text-text-faint')}
|
|
531
|
+
<Tree.Label>{entry.trigger.name}</Tree.Label>
|
|
532
|
+
<span className="text-text-faint">{`${entry.trigger.timing} ${entry.trigger.event}`}</span>
|
|
533
|
+
</>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
394
538
|
function ErrorContent({ row }: { row: ErrorRow }) {
|
|
395
539
|
const { t } = useTranslation('database');
|
|
396
540
|
return (
|
package/src/explorer/RowMenu.tsx
CHANGED
|
@@ -66,8 +66,8 @@ export function RowMenu({ row, connection, onRefresh, onDisconnect, onRequest }:
|
|
|
66
66
|
const request = (change: TableRequest['change']) => (): void => onRequest({ change, connection, ref, kind });
|
|
67
67
|
return [
|
|
68
68
|
[
|
|
69
|
-
actionItem('open-data', Table, t('explorer.openData'), { kind: 'open-table', ref, view: 'data' }),
|
|
70
|
-
actionItem('open-structure', TableProperties, t('explorer.openStructure'), { kind: 'open-table', ref, view: 'structure' }),
|
|
69
|
+
actionItem('open-data', Table, t('explorer.openData'), { kind: 'open-table', ref, view: 'data', tableKind: kind }),
|
|
70
|
+
actionItem('open-structure', TableProperties, t('explorer.openStructure'), { kind: 'open-table', ref, view: 'structure', tableKind: kind }),
|
|
71
71
|
kind === 'table' && writable ? actionItem('edit-table', PencilRuler, t('explorer.editTable'), { kind: 'edit-table', ref }) : null,
|
|
72
72
|
actionItem('console', SquareTerminal, t('explorer.newConsoleHere'), consoleAction(ref.schema, selectAllSql(connection.config.engine, ref)))
|
|
73
73
|
],
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { connectionOfKey,
|
|
1
|
+
import { connectionOfKey, startsOpen } from './tree.ts';
|
|
2
2
|
|
|
3
3
|
const VERSION = 1;
|
|
4
4
|
|
|
5
5
|
export interface Expansion {
|
|
6
|
-
/* Connections, schemas and
|
|
6
|
+
/* Connections, schemas, tables and the folders of a table the person opened. */
|
|
7
7
|
readonly expanded: ReadonlySet<string>;
|
|
8
|
-
/*
|
|
8
|
+
/* What starts open and the person closed: the folders of a schema and the columns of a table. */
|
|
9
9
|
readonly collapsed: ReadonlySet<string>;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -28,7 +28,7 @@ export const parseExpansion = (raw: string | null, connectionId: string): Expans
|
|
|
28
28
|
Array.isArray(keys)
|
|
29
29
|
? keys.filter(
|
|
30
30
|
(key): key is string =>
|
|
31
|
-
typeof key === 'string' && key.length > 2 &&
|
|
31
|
+
typeof key === 'string' && key.length > 2 && startsOpen(key) === folders && connectionOfKey(key) === connectionId
|
|
32
32
|
)
|
|
33
33
|
: []
|
|
34
34
|
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { LucideIcon } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
/* Something of the app's own under a connection, such as a file that belongs to it. */
|
|
5
|
+
export interface ExplorerItem {
|
|
6
|
+
/* Unique in its folder; it names the row. */
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly icon: LucideIcon;
|
|
10
|
+
/*
|
|
11
|
+
* A double click or Enter, with `preview: false`. With `openOnClick` a click opens it too, with
|
|
12
|
+
* `preview: true`, the way a table opens.
|
|
13
|
+
*/
|
|
14
|
+
onOpen?(options: { readonly preview: boolean }): void;
|
|
15
|
+
/* The items of its context menu, as `ContextMenu.Item`s. Without them the row has no menu. */
|
|
16
|
+
menu?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* A folder of the app's own under a connection, after its schemas. It shows only while it holds items. */
|
|
20
|
+
export interface ExplorerFolder {
|
|
21
|
+
/* Unique among the folders of a connection; it keeps the folder open or closed across a remount. */
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly items: readonly ExplorerItem[];
|
|
25
|
+
}
|