@adecore/database 0.21.1 → 0.22.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/dist/assign-ref.d.ts +2 -0
- package/dist/assign-ref.js +9 -0
- package/dist/client/client.js +14 -15
- package/dist/client/types.d.ts +1 -0
- package/dist/client/types.js +2 -0
- package/dist/connections/ConnectionForm.js +7 -2
- package/dist/connections/ConnectionManager.js +5 -5
- package/dist/connections/EngineIcon.js +0 -1
- package/dist/console/QueryConsole.js +12 -4
- package/dist/console/editing.js +2 -4
- package/dist/console/useConsoleHistory.js +3 -2
- package/dist/ddl/alter-mysql.js +2 -8
- package/dist/ddl/alter-sqlite.js +1 -1
- package/dist/ddl/column.js +5 -8
- package/dist/ddl/create.d.ts +1 -0
- package/dist/ddl/create.js +1 -1
- package/dist/ddl/diff.js +25 -23
- package/dist/designer/ColumnsEditor.js +8 -6
- package/dist/designer/ForeignKeysEditor.js +5 -1
- package/dist/designer/OptionsEditor.js +12 -3
- package/dist/designer/SqlPreview.d.ts +1 -5
- package/dist/designer/SqlPreview.js +1 -1
- package/dist/designer/TableDesigner.js +7 -6
- package/dist/explorer/DatabaseExplorer.js +12 -13
- package/dist/explorer/TableDialog.js +1 -1
- package/dist/explorer/tree.js +6 -16
- package/dist/grid/DataGrid.js +11 -14
- package/dist/grid/EnumPicker.js +4 -3
- package/dist/grid/GridHeaderCell.d.ts +1 -1
- package/dist/grid/GridHeaderCell.js +2 -2
- package/dist/grid/RecordView.js +2 -1
- package/dist/grid/ValueDock.js +2 -6
- package/dist/grid/aggregates.js +2 -3
- package/dist/grid/column-layout.js +3 -2
- package/dist/grid/copy-formats.js +2 -3
- package/dist/grid/display.d.ts +0 -1
- package/dist/grid/display.js +1 -3
- package/dist/grid/layout.js +6 -11
- package/dist/host/helper-path.js +8 -0
- package/dist/host/host.js +11 -16
- package/dist/host/validate.js +44 -43
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +5 -2
- package/dist/structure/StructureView.js +5 -6
- package/dist/table/TableView.js +19 -12
- package/dist/table/command-field.js +8 -12
- package/dist/table/grid-rows.js +2 -1
- package/dist/table/layout-store.js +5 -2
- package/dist/table/pending.js +11 -3
- package/dist/table/useTableTransfer.js +6 -5
- package/dist/testing/fake.js +7 -9
- package/dist/value/ValueEditor.js +1 -1
- package/dist/value/draft.js +1 -6
- package/dist/value/json.js +4 -4
- package/dist/workbench/DatabaseWorkbench.js +12 -5
- package/dist/workbench/tabs.js +3 -4
- package/package.json +6 -7
- package/src/assign-ref.ts +10 -0
- package/src/client/client.ts +17 -18
- package/src/client/types.ts +3 -0
- package/src/connections/ConnectionForm.tsx +62 -47
- package/src/connections/ConnectionManager.tsx +6 -5
- package/src/connections/EngineIcon.tsx +0 -2
- package/src/console/QueryConsole.tsx +52 -31
- package/src/console/editing.ts +2 -4
- package/src/console/useConsoleHistory.ts +5 -2
- package/src/ddl/alter-mysql.ts +3 -11
- package/src/ddl/alter-sqlite.ts +1 -1
- package/src/ddl/column.ts +5 -7
- package/src/ddl/create.ts +1 -1
- package/src/ddl/diff.ts +30 -25
- package/src/designer/ColumnsEditor.tsx +145 -125
- package/src/designer/ForeignKeysEditor.tsx +44 -27
- package/src/designer/OptionsEditor.tsx +95 -75
- package/src/designer/SqlPreview.tsx +2 -3
- package/src/designer/TableDesigner.tsx +7 -6
- package/src/explorer/DatabaseExplorer.tsx +26 -31
- package/src/explorer/TableDialog.tsx +1 -1
- package/src/explorer/tree.ts +8 -19
- package/src/grid/DataGrid.tsx +11 -14
- package/src/grid/EnumPicker.tsx +4 -3
- package/src/grid/GridHeaderCell.tsx +2 -3
- package/src/grid/RecordView.tsx +2 -1
- package/src/grid/ValueDock.tsx +2 -5
- package/src/grid/aggregates.ts +2 -4
- package/src/grid/column-layout.ts +3 -2
- package/src/grid/copy-formats.ts +2 -3
- package/src/grid/display.ts +1 -5
- package/src/grid/layout.ts +10 -11
- package/src/host/helper-path.ts +8 -8
- package/src/host/host.ts +11 -16
- package/src/host/validate.ts +45 -43
- package/src/protocol/messages.ts +0 -1
- package/src/sql.ts +7 -2
- package/src/structure/StructureView.tsx +7 -8
- package/src/table/CommandField.tsx +0 -1
- package/src/table/TableView.tsx +48 -34
- package/src/table/command-field.ts +9 -12
- package/src/table/grid-rows.ts +2 -7
- package/src/table/layout-store.ts +6 -3
- package/src/table/pending.ts +12 -7
- package/src/table/useTableTransfer.ts +7 -5
- package/src/testing/fake.ts +8 -12
- package/src/value/ValueEditor.tsx +1 -1
- package/src/value/draft.ts +1 -6
- package/src/value/json.ts +4 -4
- package/src/workbench/DatabaseWorkbench.tsx +14 -6
- package/src/workbench/DesignerTab.tsx +1 -1
- package/src/workbench/tabs.ts +5 -8
package/src/table/grid-rows.ts
CHANGED
|
@@ -15,13 +15,8 @@ export const parseRowKey = (key: string): RowRef =>
|
|
|
15
15
|
|
|
16
16
|
export const buildGridColumns = (loaded: Pick<RowsResult, 'columns'>, structure: TableStructure | null): GridColumn[] => {
|
|
17
17
|
const keys = { primaryKey: structure?.primaryKey ?? [], foreignKey: structure?.foreignKeys.flatMap((key) => key.columns) ?? [] };
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
column,
|
|
21
|
-
structure?.columns.find((info) => info.name === column.name),
|
|
22
|
-
keys
|
|
23
|
-
)
|
|
24
|
-
);
|
|
18
|
+
const infoOf = (name: string) => structure?.columns.find((info) => info.name === name);
|
|
19
|
+
return loaded.columns.map((column) => gridColumnOf(column, infoOf(column.name), keys));
|
|
25
20
|
};
|
|
26
21
|
|
|
27
22
|
export interface GridRowsInput {
|
|
@@ -2,7 +2,10 @@ import type { GridLayout } from '../grid/column-layout.ts';
|
|
|
2
2
|
import type { StoredFilter } from './command-field.ts';
|
|
3
3
|
import { PAGE_SIZES } from './paging.ts';
|
|
4
4
|
|
|
5
|
-
/*
|
|
5
|
+
/*
|
|
6
|
+
* Bump when the shape changes; a stored value of another version is ignored rather than migrated. `filters`
|
|
7
|
+
* came later without a bump, since `where` still says the same thing for a value without it.
|
|
8
|
+
*/
|
|
6
9
|
export const LAYOUT_VERSION = 1;
|
|
7
10
|
|
|
8
11
|
/* What is remembered of one table's view between visits. */
|
|
@@ -20,11 +23,11 @@ export const layoutStorageKey = (connectionId: string, schema: string, table: st
|
|
|
20
23
|
|
|
21
24
|
const isStringList = (value: unknown): value is string[] => Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
22
25
|
|
|
26
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
+
|
|
23
28
|
const isStoredFilter = (value: unknown): value is StoredFilter =>
|
|
24
29
|
isRecord(value) && typeof value.text === 'string' && typeof value.sql === 'string' && value.sql.trim() !== '';
|
|
25
30
|
|
|
26
|
-
const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
-
|
|
28
31
|
export const serializeLayout = (layout: StoredLayout): string => JSON.stringify({ version: LAYOUT_VERSION, ...layout });
|
|
29
32
|
|
|
30
33
|
/* The layout a stored text holds, or `null` when it is missing, broken or from another version. A field that is off is dropped alone. */
|
package/src/table/pending.ts
CHANGED
|
@@ -112,8 +112,6 @@ export const rowKeyOf = (structure: TableStructure, loaded: LoadedRows, row: num
|
|
|
112
112
|
return key;
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
const numeric = (text: string): number => Number(text);
|
|
116
|
-
|
|
117
115
|
/* Updates, then deletes, then inserts, all computed against the page as loaded. Throws for a change on a row without a usable key, which the grid never lets a person make. */
|
|
118
116
|
export const toRowChanges = (structure: TableStructure, loaded: LoadedRows, pending: PendingChanges): RowChange[] => {
|
|
119
117
|
const keyOf = (row: number): RowKey => {
|
|
@@ -124,7 +122,7 @@ export const toRowChanges = (structure: TableStructure, loaded: LoadedRows, pend
|
|
|
124
122
|
return key;
|
|
125
123
|
};
|
|
126
124
|
const updates = Object.keys(pending.edits)
|
|
127
|
-
.map(
|
|
125
|
+
.map(Number)
|
|
128
126
|
.filter((row) => !pending.deletes.has(row))
|
|
129
127
|
.sort((left, right) => left - right)
|
|
130
128
|
.map((row): RowChange => ({ kind: 'update', key: keyOf(row), values: pending.edits[row]! }));
|
|
@@ -133,11 +131,18 @@ export const toRowChanges = (structure: TableStructure, loaded: LoadedRows, pend
|
|
|
133
131
|
return [...updates, ...deletes, ...inserts];
|
|
134
132
|
};
|
|
135
133
|
|
|
134
|
+
const keyValueText = (value: Value): string => {
|
|
135
|
+
if (value === null) {
|
|
136
|
+
return 'NULL';
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === 'object') {
|
|
139
|
+
return `0x${value.hex}`;
|
|
140
|
+
}
|
|
141
|
+
return typeof value === 'string' ? `'${value}'` : String(value);
|
|
142
|
+
};
|
|
143
|
+
|
|
136
144
|
/* A key as a person would write it in a WHERE: `id = 5, tenant = 'a'`. */
|
|
137
145
|
export const describeKey = (key: RowKey): string =>
|
|
138
146
|
Object.entries(key)
|
|
139
|
-
.map(
|
|
140
|
-
([column, value]) =>
|
|
141
|
-
`${column} = ${value === null ? 'NULL' : typeof value === 'object' ? `0x${value.hex}` : typeof value === 'string' ? `'${value}'` : String(value)}`
|
|
142
|
-
)
|
|
147
|
+
.map(([column, value]) => `${column} = ${keyValueText(value)}`)
|
|
143
148
|
.join(', ');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { messageOf } from '@adecore/ui';
|
|
3
3
|
import type { DatabaseFiles } from '../actions.ts';
|
|
4
|
-
import {
|
|
4
|
+
import { isCancelled, type DatabaseClient, type DatabaseSession } from '../client/types.ts';
|
|
5
5
|
import type { FileFormat, TableStructure } from '../protocol/index.ts';
|
|
6
6
|
import type { ImportSample } from './ImportDialog.tsx';
|
|
7
7
|
import { formatOfPath, importableColumns, mapColumn, matchColumns, type ImportFormat } from './import-mapping.ts';
|
|
@@ -13,6 +13,9 @@ export type TransferNotice =
|
|
|
13
13
|
| { readonly kind: 'imported'; readonly rows: number }
|
|
14
14
|
| { readonly kind: 'failed'; readonly during: 'export' | 'import'; readonly message: string };
|
|
15
15
|
|
|
16
|
+
/* How long a finished export or import stays on the strip. */
|
|
17
|
+
const NOTICE_MS = 8000;
|
|
18
|
+
|
|
16
19
|
/* The import a person is setting up. */
|
|
17
20
|
export interface ImportDraft {
|
|
18
21
|
readonly path: string;
|
|
@@ -68,7 +71,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
68
71
|
if (notice?.kind !== 'exported' && notice?.kind !== 'imported') {
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
71
|
-
const timer = setTimeout(() => setNotice(null),
|
|
74
|
+
const timer = setTimeout(() => setNotice(null), NOTICE_MS);
|
|
72
75
|
return () => clearTimeout(timer);
|
|
73
76
|
}, [notice]);
|
|
74
77
|
|
|
@@ -102,8 +105,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
102
105
|
);
|
|
103
106
|
show({ kind: 'exported', rows: result.rows });
|
|
104
107
|
} catch (error) {
|
|
105
|
-
|
|
106
|
-
if (cancelled) {
|
|
108
|
+
if (controller.signal.aborted || isCancelled(error)) {
|
|
107
109
|
setNotice(null);
|
|
108
110
|
} else {
|
|
109
111
|
show({ kind: 'failed', during: 'export', message: messageOf(error) });
|
|
@@ -153,7 +155,7 @@ export function useTableTransfer({ client, session, files, schema, table, struct
|
|
|
153
155
|
now === null || now.path !== path ? now : { ...now, header, sample: read, mapping: matchColumns(read.columns, tableColumns, header) }
|
|
154
156
|
);
|
|
155
157
|
} catch (error) {
|
|
156
|
-
if (!(error
|
|
158
|
+
if (!isCancelled(error)) {
|
|
157
159
|
setImportDraft((now) => (now === null || now.path !== path ? now : { ...now, error: messageOf(error) }));
|
|
158
160
|
}
|
|
159
161
|
}
|
package/src/testing/fake.ts
CHANGED
|
@@ -119,14 +119,18 @@ const toTables = (database: FakeDatabase): Map<string, Map<string, Table>> =>
|
|
|
119
119
|
])
|
|
120
120
|
);
|
|
121
121
|
|
|
122
|
-
const
|
|
122
|
+
const findSchema = (session: Session, schema: string): Map<string, Table> => {
|
|
123
123
|
const tables = session.schemas.get(schema);
|
|
124
124
|
|
|
125
125
|
if (tables === undefined) {
|
|
126
126
|
throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
return tables;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const findTable = (session: Session, schema: string, name: string): Table => {
|
|
133
|
+
const table = findSchema(session, schema).get(name);
|
|
130
134
|
|
|
131
135
|
if (table === undefined) {
|
|
132
136
|
throw new FakeError('query-failed', `Table '${schema}.${name}' doesn't exist`, '42S02');
|
|
@@ -334,10 +338,7 @@ const selectSchema = (session: Session, schema: string | undefined): void => {
|
|
|
334
338
|
return;
|
|
335
339
|
}
|
|
336
340
|
|
|
337
|
-
|
|
338
|
-
throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
+
findSchema(session, schema);
|
|
341
342
|
session.schema = schema;
|
|
342
343
|
};
|
|
343
344
|
|
|
@@ -402,12 +403,7 @@ export const fakeDatabaseTransport = (options: FakeDatabaseTransportOptions): Da
|
|
|
402
403
|
schemas: [...sessionOf(session).schemas.keys()].map((name) => ({ name, system: SYSTEM_SCHEMAS.includes(name) }))
|
|
403
404
|
}),
|
|
404
405
|
tables: ({ session, schema }) => {
|
|
405
|
-
const tables = sessionOf(session)
|
|
406
|
-
|
|
407
|
-
if (tables === undefined) {
|
|
408
|
-
throw new FakeError('query-failed', `Unknown database '${schema}'`, '42000');
|
|
409
|
-
}
|
|
410
|
-
|
|
406
|
+
const tables = findSchema(sessionOf(session), schema);
|
|
411
407
|
return { tables: [...tables].map(([name, table]) => ({ name, kind: table.kind, rowEstimate: table.rows.length, comment: null })) };
|
|
412
408
|
},
|
|
413
409
|
structure: ({ session, schema, table: name }) => {
|
|
@@ -96,7 +96,7 @@ export function ValueEditor({ column, value, editable, onCommit }: ValueEditorPr
|
|
|
96
96
|
} else if (rendered !== null && rendered.kind === 'text') {
|
|
97
97
|
copyText(rendered.text);
|
|
98
98
|
} else {
|
|
99
|
-
copyText(
|
|
99
|
+
copyText(placeholder ?? draft.text);
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
|
package/src/value/draft.ts
CHANGED
|
@@ -25,12 +25,7 @@ export type DraftProblem = { readonly kind: 'json'; readonly reason: string } |
|
|
|
25
25
|
export type Conversion = { readonly ok: true; readonly value: EditValue } | { readonly ok: false; readonly problem: DraftProblem };
|
|
26
26
|
|
|
27
27
|
/* The text a value is edited as: a binary value as its hex, everything else as it is written. */
|
|
28
|
-
const textOf = (value: Exclude<Value, null>): string =>
|
|
29
|
-
if (typeof value === 'object') {
|
|
30
|
-
return value.hex;
|
|
31
|
-
}
|
|
32
|
-
return String(value);
|
|
33
|
-
};
|
|
28
|
+
const textOf = (value: Exclude<Value, null>): string => (typeof value === 'object' ? value.hex : String(value));
|
|
34
29
|
|
|
35
30
|
export const sourceOf = (column: Pick<GridColumn, 'name' | 'type' | 'kind'>, value: Value): Source => {
|
|
36
31
|
const binary = column.kind === 'binary' || (value !== null && typeof value === 'object');
|
package/src/value/json.ts
CHANGED
|
@@ -49,11 +49,11 @@ export const prettyJson = (text: string): string | null => {
|
|
|
49
49
|
continue;
|
|
50
50
|
}
|
|
51
51
|
if (char === '{' || char === '[') {
|
|
52
|
-
const
|
|
52
|
+
const close = char === '{' ? '}' : ']';
|
|
53
53
|
output += char;
|
|
54
|
-
if (
|
|
55
|
-
output +=
|
|
56
|
-
i = text.indexOf(
|
|
54
|
+
if (nextToken(i + 1) === close) {
|
|
55
|
+
output += close;
|
|
56
|
+
i = text.indexOf(close, i + 1) + 1;
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
59
|
depth++;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type Ref } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { PencilRuler, Settings2, SquareTerminal, Table, TableProperties } from 'lucide-react';
|
|
3
|
+
import { PencilRuler, Settings2, SquareTerminal, Table, TableProperties, type LucideIcon } from 'lucide-react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import {
|
|
6
6
|
Button,
|
|
@@ -26,6 +26,7 @@ import type { Connection } from '../client/types.ts';
|
|
|
26
26
|
import { ConnectionManager } from '../connections/ConnectionManager.tsx';
|
|
27
27
|
import { QueryConsole } from '../console/QueryConsole.tsx';
|
|
28
28
|
import { DatabaseExplorer } from '../explorer/DatabaseExplorer.tsx';
|
|
29
|
+
import { useStableCallback } from '../use-stable-callback.ts';
|
|
29
30
|
import { DesignerTab } from './DesignerTab.tsx';
|
|
30
31
|
import { TableTab } from './TableTab.tsx';
|
|
31
32
|
import {
|
|
@@ -44,7 +45,6 @@ import {
|
|
|
44
45
|
STORAGE_KEY,
|
|
45
46
|
type WorkbenchTab
|
|
46
47
|
} from './tabs.ts';
|
|
47
|
-
import { useStableCallback } from '../use-stable-callback.ts';
|
|
48
48
|
|
|
49
49
|
export interface DatabaseWorkbenchProps {
|
|
50
50
|
connections: readonly Connection[];
|
|
@@ -56,6 +56,13 @@ export interface DatabaseWorkbenchProps {
|
|
|
56
56
|
|
|
57
57
|
const DEFAULT_WIDTH = 280;
|
|
58
58
|
const MIN_WIDTH = 200;
|
|
59
|
+
const CLOSE_TAB = shortcut('Mod+W');
|
|
60
|
+
|
|
61
|
+
const TAB_ICON: Record<WorkbenchTab['kind'], LucideIcon> = {
|
|
62
|
+
table: Table,
|
|
63
|
+
console: SquareTerminal,
|
|
64
|
+
designer: PencilRuler
|
|
65
|
+
};
|
|
59
66
|
|
|
60
67
|
/*
|
|
61
68
|
* The whole database view in one piece: the explorer beside closable tabs for the tables, consoles and
|
|
@@ -83,8 +90,9 @@ export function DatabaseWorkbench({ connections, onConnectionsChange, className,
|
|
|
83
90
|
if (pruned !== state) {
|
|
84
91
|
setState(pruned);
|
|
85
92
|
}
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
const isOpen = (id: string): boolean => state.tabs.some((tab) => tab.id === id);
|
|
94
|
+
if (![...dirty].every(isOpen)) {
|
|
95
|
+
setDirty(new Set([...dirty].filter(isOpen)));
|
|
88
96
|
}
|
|
89
97
|
const tabs = state.tabs.filter((tab) => known.has(connectionIdOf(tab)));
|
|
90
98
|
const active = tabs.find((tab) => tab.id === state.activeId) ?? tabs.at(-1) ?? null;
|
|
@@ -146,7 +154,7 @@ export function DatabaseWorkbench({ connections, onConnectionsChange, className,
|
|
|
146
154
|
active !== null &&
|
|
147
155
|
e.currentTarget.contains(e.target as Node) &&
|
|
148
156
|
!e.nativeEvent.isComposing &&
|
|
149
|
-
matchesShortcut(
|
|
157
|
+
matchesShortcut(CLOSE_TAB, e.nativeEvent, isApplePlatform())
|
|
150
158
|
) {
|
|
151
159
|
e.preventDefault();
|
|
152
160
|
requestClose(active.id);
|
|
@@ -277,7 +285,7 @@ export function DatabaseWorkbench({ connections, onConnectionsChange, className,
|
|
|
277
285
|
>
|
|
278
286
|
{tabs.map((tab) => (
|
|
279
287
|
<Tabs.Tab key={tab.id} value={tab.id} onClose={() => requestClose(tab.id)}>
|
|
280
|
-
<Icon icon={tab.kind
|
|
288
|
+
<Icon icon={TAB_ICON[tab.kind]} size={14} />
|
|
281
289
|
{titleOf(tab)}
|
|
282
290
|
</Tabs.Tab>
|
|
283
291
|
))}
|
|
@@ -3,8 +3,8 @@ import type { DatabaseAction } from '../actions.ts';
|
|
|
3
3
|
import { useDatabaseAction, useDatabaseClient, useDatabaseFiles, useDatabaseStorage, useNumberNotation } from '../client-context.ts';
|
|
4
4
|
import type { Connection } from '../client/types.ts';
|
|
5
5
|
import { TableDesigner } from '../designer/TableDesigner.tsx';
|
|
6
|
-
import type { WorkbenchTab } from './tabs.ts';
|
|
7
6
|
import { useStableCallback } from '../use-stable-callback.ts';
|
|
7
|
+
import type { WorkbenchTab } from './tabs.ts';
|
|
8
8
|
|
|
9
9
|
export interface DesignerTabProps {
|
|
10
10
|
connection: Connection;
|
package/src/workbench/tabs.ts
CHANGED
|
@@ -25,6 +25,9 @@ export const connectionIdOf = (tab: WorkbenchTab): string => (tab.kind === 'tabl
|
|
|
25
25
|
|
|
26
26
|
const tableTabId = (ref: TableRef): string => `table:${ref.connectionId}\u0000${ref.schema}\u0000${ref.table}`;
|
|
27
27
|
|
|
28
|
+
const isDesignerOf = (tab: WorkbenchTab, connectionId: string, schema: string, table: string): boolean =>
|
|
29
|
+
tab.kind === 'designer' && tab.connectionId === connectionId && tab.schema === schema && tab.table === table;
|
|
30
|
+
|
|
28
31
|
const add = (state: WorkbenchState, tab: WorkbenchTab): WorkbenchState => ({ ...state, tabs: [...state.tabs, tab], activeId: tab.id });
|
|
29
32
|
|
|
30
33
|
const update = (state: WorkbenchState, id: string, change: (tab: WorkbenchTab) => WorkbenchTab): WorkbenchState => {
|
|
@@ -64,10 +67,7 @@ export const openConsole = (state: WorkbenchState, connectionId: string, schema?
|
|
|
64
67
|
|
|
65
68
|
/* A table is designed in one tab; a new table gets a tab each time. */
|
|
66
69
|
export const openDesigner = (state: WorkbenchState, connectionId: string, schema: string, table?: string): WorkbenchState => {
|
|
67
|
-
const existing =
|
|
68
|
-
table === undefined
|
|
69
|
-
? undefined
|
|
70
|
-
: state.tabs.find((tab) => tab.kind === 'designer' && tab.connectionId === connectionId && tab.schema === schema && tab.table === table);
|
|
70
|
+
const existing = table === undefined ? undefined : state.tabs.find((tab) => isDesignerOf(tab, connectionId, schema, table));
|
|
71
71
|
if (existing !== undefined) {
|
|
72
72
|
return focusTab(state, existing.id);
|
|
73
73
|
}
|
|
@@ -87,10 +87,7 @@ export const setDesignerTable = (state: WorkbenchState, id: string, table: strin
|
|
|
87
87
|
if (subject?.kind !== 'designer' || subject.table === table) {
|
|
88
88
|
return state;
|
|
89
89
|
}
|
|
90
|
-
const tabs = state.tabs.filter(
|
|
91
|
-
(tab) =>
|
|
92
|
-
tab.id === id || !(tab.kind === 'designer' && tab.connectionId === subject.connectionId && tab.schema === subject.schema && tab.table === table)
|
|
93
|
-
);
|
|
90
|
+
const tabs = state.tabs.filter((tab) => tab.id === id || !isDesignerOf(tab, subject.connectionId, subject.schema, table));
|
|
94
91
|
return update({ ...state, tabs }, id, (tab) => (tab.kind === 'designer' ? { ...tab, table } : tab));
|
|
95
92
|
};
|
|
96
93
|
|