@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/README.md
CHANGED
|
@@ -91,7 +91,7 @@ A `Connection` is `{ id, name, config }`. The app keeps the list, with the passw
|
|
|
91
91
|
>
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
`onAction` receives a `DatabaseAction` when a view wants a table, a console or the designer opened, so the app decides where it goes. `storage` keeps layouts, console history and open tabs across a remount. `files` holds the app's save and open dialogs for export and import.
|
|
94
|
+
`onAction` receives a `DatabaseAction` when a view wants a table, a console or the designer opened, so the app decides where it goes. `storage` keeps layouts, console history and open tabs across a remount. `files` holds the app's save and open dialogs for export and import. A provider inside another needs none of them: it takes what it leaves out from the one above, so `<DatabaseProvider onAction={...}>` around one tab tells which tab an action came from.
|
|
95
95
|
|
|
96
96
|
Tell Tailwind to scan the package for its classes, next to the `@source` line of `@adecore/ui`. The path is relative to the CSS file:
|
|
97
97
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import type { DatabaseAction, DatabaseFiles, DatabaseStorage } from './actions.ts';
|
|
2
|
+
import type { DatabaseAction, DatabaseFiles, DatabaseNotice, DatabaseStorage } from './actions.ts';
|
|
3
3
|
import type { DatabaseClient } from './client/types.ts';
|
|
4
4
|
import type { NumberNotation } from './grid/display.ts';
|
|
5
5
|
export interface DatabaseProviderProps {
|
|
6
|
-
client
|
|
6
|
+
client?: DatabaseClient;
|
|
7
7
|
onAction?(action: DatabaseAction): void;
|
|
8
|
+
onNotice?(notice: DatabaseNotice): void;
|
|
8
9
|
storage?: DatabaseStorage;
|
|
9
10
|
files?: DatabaseFiles;
|
|
10
11
|
numberNotation?: NumberNotation;
|
|
11
12
|
children: ReactNode;
|
|
12
13
|
}
|
|
13
|
-
export declare function DatabaseProvider({ client, onAction, storage, files, numberNotation, children }: DatabaseProviderProps): import("react").JSX.Element;
|
|
14
|
+
export declare function DatabaseProvider({ client, onAction, onNotice, storage, files, numberNotation, children }: DatabaseProviderProps): import("react").JSX.Element;
|
package/dist/DatabaseProvider.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from 'react';
|
|
2
|
+
import { use, useMemo, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { DatabaseContext } from './client-context.js';
|
|
5
5
|
import { addDatabaseResources } from './locales.js';
|
|
6
6
|
/* Hands every view below it the client and the app's hooks, and adds the package's words to the i18next the app's `UIProvider` was given. Mount it inside that provider. */
|
|
7
|
-
export function DatabaseProvider({ client, onAction, storage, files, numberNotation
|
|
7
|
+
export function DatabaseProvider({ client, onAction, onNotice, storage, files, numberNotation, children }) {
|
|
8
8
|
const { i18n } = useTranslation();
|
|
9
|
+
const outer = use(DatabaseContext);
|
|
9
10
|
const [added, setAdded] = useState(null);
|
|
10
11
|
// In render, as `UIProvider` does, so the first view already reads its words.
|
|
11
12
|
if (added !== i18n) {
|
|
12
13
|
addDatabaseResources(i18n);
|
|
13
14
|
setAdded(i18n);
|
|
14
15
|
}
|
|
15
|
-
const
|
|
16
|
+
const ownClient = client ?? outer?.client;
|
|
17
|
+
const ownAction = onAction ?? outer?.onAction;
|
|
18
|
+
const ownNotice = onNotice ?? outer?.onNotice;
|
|
19
|
+
const ownStorage = storage ?? outer?.storage;
|
|
20
|
+
const ownFiles = files ?? outer?.files;
|
|
21
|
+
const ownNotation = numberNotation ?? outer?.numberNotation ?? 'database';
|
|
22
|
+
const value = useMemo(() => ownClient === undefined
|
|
23
|
+
? null
|
|
24
|
+
: { client: ownClient, onAction: ownAction, onNotice: ownNotice, storage: ownStorage, files: ownFiles, numberNotation: ownNotation }, [ownClient, ownAction, ownNotice, ownStorage, ownFiles, ownNotation]);
|
|
25
|
+
if (value === null) {
|
|
26
|
+
throw new Error('A DatabaseProvider needs a client, or a DatabaseProvider above it to take one from.');
|
|
27
|
+
}
|
|
16
28
|
return _jsx(DatabaseContext, { value: value, children: children });
|
|
17
29
|
}
|
package/dist/KeyIcon.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { KeyRound } from 'lucide-react';
|
|
4
|
+
import { Icon } from '@adecore/ui';
|
|
5
|
+
/*
|
|
6
|
+
* The key of a column, yellow on a primary key and blue on a foreign key, the same in the tree and the
|
|
7
|
+
* grid. A column that is both shows the primary key, since that is what names its row; the tooltip says both.
|
|
8
|
+
*/
|
|
9
|
+
export function KeyIcon({ primaryKey, foreignKey, size }) {
|
|
10
|
+
if (!primaryKey && !foreignKey) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return _jsx(Icon, { icon: KeyRound, size: size, className: clsx('shrink-0', primaryKey ? 'text-(--file-icon-yellow)' : 'text-(--file-icon-blue)') });
|
|
14
|
+
}
|
package/dist/actions.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { TableRef } from './client/types.ts';
|
|
2
|
+
import type { TableKind } from './protocol/index.ts';
|
|
2
3
|
export type DatabaseAction = {
|
|
3
4
|
readonly kind: 'open-table';
|
|
4
5
|
readonly ref: TableRef;
|
|
5
6
|
readonly view: 'data' | 'structure';
|
|
6
7
|
readonly where?: string;
|
|
8
|
+
readonly tableKind?: TableKind;
|
|
9
|
+
readonly preview?: boolean;
|
|
7
10
|
} | {
|
|
8
11
|
readonly kind: 'open-console';
|
|
9
12
|
readonly connectionId: string;
|
|
@@ -20,6 +23,15 @@ export type DatabaseAction = {
|
|
|
20
23
|
readonly kind: 'manage-connection';
|
|
21
24
|
readonly connectionId: string;
|
|
22
25
|
};
|
|
26
|
+
export interface DatabaseNotice {
|
|
27
|
+
readonly tone: 'success' | 'error';
|
|
28
|
+
readonly title: string;
|
|
29
|
+
readonly description?: string;
|
|
30
|
+
readonly action?: {
|
|
31
|
+
readonly label: string;
|
|
32
|
+
run(): void;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
23
35
|
export interface ExplorerSelection {
|
|
24
36
|
readonly connectionId: string;
|
|
25
37
|
readonly schema?: string;
|
package/dist/client-context.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { DatabaseAction, DatabaseFiles, DatabaseStorage } from './actions.ts';
|
|
1
|
+
import type { DatabaseAction, DatabaseFiles, DatabaseNotice, DatabaseStorage } from './actions.ts';
|
|
2
2
|
import type { DatabaseClient } from './client/types.ts';
|
|
3
3
|
import type { NumberNotation } from './grid/display.ts';
|
|
4
4
|
export interface DatabaseContextValue {
|
|
5
5
|
readonly client: DatabaseClient;
|
|
6
6
|
readonly onAction: ((action: DatabaseAction) => void) | undefined;
|
|
7
|
+
readonly onNotice: ((notice: DatabaseNotice) => void) | undefined;
|
|
7
8
|
readonly storage: DatabaseStorage | undefined;
|
|
8
9
|
readonly files: DatabaseFiles | undefined;
|
|
9
10
|
readonly numberNotation: NumberNotation;
|
|
@@ -11,6 +12,7 @@ export interface DatabaseContextValue {
|
|
|
11
12
|
export declare const DatabaseContext: import("react").Context<DatabaseContextValue | null>;
|
|
12
13
|
export declare const useDatabaseClient: () => DatabaseClient;
|
|
13
14
|
export declare const useDatabaseAction: () => ((action: DatabaseAction) => void) | undefined;
|
|
15
|
+
export declare const useDatabaseNotice: () => ((notice: DatabaseNotice) => void) | undefined;
|
|
14
16
|
export declare const useDatabaseStorage: () => DatabaseStorage | undefined;
|
|
15
17
|
export declare const useDatabaseFiles: () => DatabaseFiles | undefined;
|
|
16
18
|
export declare const useNumberNotation: () => NumberNotation;
|
package/dist/client-context.js
CHANGED
|
@@ -10,6 +10,8 @@ const useDatabaseContext = () => {
|
|
|
10
10
|
export const useDatabaseClient = () => useDatabaseContext().client;
|
|
11
11
|
/* Hands an action to the app, or `undefined` when the app takes none, so a view can leave out what would go nowhere. */
|
|
12
12
|
export const useDatabaseAction = () => useDatabaseContext().onAction;
|
|
13
|
+
/* Hands a notice to the app, or `undefined` when the app takes none and the view shows it itself. */
|
|
14
|
+
export const useDatabaseNotice = () => useDatabaseContext().onNotice;
|
|
13
15
|
export const useDatabaseStorage = () => useDatabaseContext().storage;
|
|
14
16
|
/* The app's file dialogs; export and import stay hidden without them. */
|
|
15
17
|
export const useDatabaseFiles = () => useDatabaseContext().files;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CODE_TEXT = "font-mono text-(length:--code-font-size) leading-(--code-line-height)";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ref } from 'react';
|
|
1
|
+
import { type ReactNode, type Ref } from 'react';
|
|
2
2
|
import type { Connection } from '../client/types.ts';
|
|
3
3
|
export interface ConnectionManagerProps {
|
|
4
4
|
value: readonly Connection[];
|
|
@@ -6,7 +6,8 @@ export interface ConnectionManagerProps {
|
|
|
6
6
|
selected?: string | null;
|
|
7
7
|
onSelectedChange?(id: string | null): void;
|
|
8
8
|
onBrowse?(purpose: 'database' | 'identity'): Promise<string | null>;
|
|
9
|
+
renderFields?(connection: Connection): ReactNode;
|
|
9
10
|
className?: string;
|
|
10
11
|
ref?: Ref<HTMLDivElement>;
|
|
11
12
|
}
|
|
12
|
-
export declare function ConnectionManager({ value, onValueChange, selected, onSelectedChange, onBrowse, className, ref }: ConnectionManagerProps): import("react").JSX.Element;
|
|
13
|
+
export declare function ConnectionManager({ value, onValueChange, selected, onSelectedChange, onBrowse, renderFields, className, ref }: ConnectionManagerProps): import("react").JSX.Element;
|
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
3
4
|
import { CircleAlert, CircleCheck, Container, Database, Plus, Trash2, Unplug } from 'lucide-react';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Banner, Button, EmptyState, Icon, Menu, messageOf, PromptDialog, Spinner } from '@adecore/ui';
|
|
6
|
-
import { DetailHeader,
|
|
6
|
+
import { Banner, Button, CloseButton, EmptyState, Icon, Menu, messageOf, PromptDialog, Spinner } from '@adecore/ui';
|
|
7
|
+
import { DetailHeader, MasterItem } from '@adecore/ui/settings';
|
|
7
8
|
import { useDatabaseClient } from '../client-context.js';
|
|
8
9
|
import { defaultConfig, isValidConfig, targetOf } from './connection-config.js';
|
|
9
10
|
import { ConnectionForm } from './ConnectionForm.js';
|
|
10
|
-
import {
|
|
11
|
+
import { connectionFromContainer, containerTitle, isMysqlContainer } from './docker.js';
|
|
11
12
|
import { useDockerContainers } from './use-docker-containers.js';
|
|
12
13
|
import { ENGINE_ICONS } from './engine-icons.js';
|
|
13
14
|
import { EngineIcon } from './EngineIcon.js';
|
|
14
15
|
const ENGINES = ['sqlite', 'mysql'];
|
|
15
|
-
/*
|
|
16
|
-
|
|
16
|
+
/*
|
|
17
|
+
* The saved connections beside the form of the one picked, for a dialog, a settings pane or a view of its own. The list
|
|
18
|
+
* scrolls on its own with New connection under it, and the detail scrolls beside it; in a narrow window they stack.
|
|
19
|
+
*/
|
|
20
|
+
export function ConnectionManager({ value, onValueChange, selected, onSelectedChange, onBrowse, renderFields, className, ref }) {
|
|
17
21
|
const { t } = useTranslation('database');
|
|
18
22
|
const [own, setOwn] = useState(null);
|
|
19
23
|
const [deleting, setDeleting] = useState(false);
|
|
24
|
+
const [outcome, setOutcome] = useState(null);
|
|
20
25
|
const client = useDatabaseClient();
|
|
21
26
|
const pickedId = selected === undefined ? own : selected;
|
|
22
27
|
const current = value.find((connection) => connection.id === pickedId) ?? value[0] ?? null;
|
|
28
|
+
// The answer of one connection is gone once another is picked, so coming back does not bring it back.
|
|
29
|
+
if (outcome !== null && outcome.connectionId !== current?.id) {
|
|
30
|
+
setOutcome(null);
|
|
31
|
+
}
|
|
32
|
+
// An answer to an older config is never shown for a newer one.
|
|
33
|
+
const shown = outcome !== null && current !== null && outcome.config === current.config ? outcome : null;
|
|
23
34
|
const select = (id) => {
|
|
24
35
|
if (selected === undefined) {
|
|
25
36
|
setOwn(id);
|
|
@@ -46,12 +57,14 @@ export function ConnectionManager({ value, onValueChange, selected, onSelectedCh
|
|
|
46
57
|
onValueChange(rest);
|
|
47
58
|
select(rest[Math.min(index, rest.length - 1)]?.id ?? null);
|
|
48
59
|
};
|
|
49
|
-
const list = (_jsxs(
|
|
50
|
-
const detail = current === null ? (_jsx(EmptyState, { icon: Database, children: t('connections.empty') })) : (_jsxs(_Fragment, { children: [
|
|
60
|
+
const list = (_jsxs("div", { className: "flex w-56 shrink-0 flex-col border-r border-border max-[640px]:max-h-60 max-[640px]:w-auto max-[640px]:border-r-0 max-[640px]:border-b", children: [_jsxs("nav", { "aria-label": t('connections.list'), className: "flex min-h-0 grow flex-col gap-0.5 overflow-y-auto p-2", children: [value.length === 0 && _jsx("p", { className: "px-2 py-1.5 text-xs text-text-faint", children: t('connections.empty') }), value.map((connection) => (_jsxs(MasterItem, { selected: connection.id === current?.id, onSelect: () => select(connection.id), children: [_jsx(EngineIcon, { engine: connection.config.engine, size: 16, className: "shrink-0 text-text-muted" }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: connection.name || t('connections.untitled') }), _jsx("span", { className: "truncate text-xs text-text-faint", children: targetOf(connection) })] })] }, connection.id)))] }), _jsx("div", { className: "flex shrink-0 flex-col gap-1 border-t border-border p-2", children: _jsxs(Menu.Root, { children: [_jsxs(Menu.Trigger, { render: _jsx(Button, { variant: "ghost", size: "sm", className: "justify-start" }), children: [_jsx(Icon, { icon: Plus, size: 14 }), t('connections.new')] }), _jsxs(Menu.Popup, { children: [ENGINES.map((engine) => (_jsxs(Menu.Item, { onClick: () => add(engine), children: [_jsx(Icon, { icon: ENGINE_ICONS[engine], size: 14 }), t(`engine.${engine}`)] }, engine))), _jsx(Menu.Separator, {}), _jsxs(Menu.SubmenuRoot, { children: [_jsxs(Menu.SubmenuTrigger, { children: [_jsx(Icon, { icon: Container, size: 14 }), t('connections.fromDocker.label')] }), _jsx(Menu.Popup, { children: _jsx(DockerMenuItems, { onPick: addFromContainer }) })] })] })] }) })] }));
|
|
61
|
+
const detail = current === null ? (_jsx(EmptyState, { icon: Database, children: t('connections.empty') })) : (_jsxs(_Fragment, { children: [shown !== null && (_jsx(Banner, { icon: 'info' in shown ? CircleCheck : CircleAlert, tone: 'info' in shown ? 'neutral' : 'error', className: "sticky top-0 z-10", message: 'info' in shown
|
|
62
|
+
? t('connections.test.success', { server: `${t(`engine.flavor.${shown.info.flavor}`)} ${shown.info.version}` })
|
|
63
|
+
: shown.error, children: _jsx(CloseButton, { size: "sm", label: t('connections.test.dismiss'), onClick: () => setOutcome(null) }) })), _jsx(DetailHeader, { mark: _jsx(EngineIcon, { engine: current.config.engine, size: 20, className: "mt-0.5 shrink-0 text-text-muted" }), title: current.name || t('connections.untitled'), subtitle: targetOf(current), actions: _jsxs(Button, { variant: "danger-outline", onClick: () => setDeleting(true), children: [_jsx(Icon, { icon: Trash2, size: 14 }), t('connections.delete.action')] }) }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(ConnectionForm, { value: current, onValueChange: change, onBrowse: onBrowse }), renderFields?.(current)] }), _jsx(ConnectionTest, { connection: current, onOutcome: setOutcome }, current.id), _jsx(PromptDialog, { open: deleting, danger: true, onOpenChange: setDeleting, title: t('connections.delete.title'), description: t('connections.delete.description', { name: current.name || t('connections.untitled') }), confirmLabel: t('connections.delete.confirm'), onConfirm: async () => {
|
|
51
64
|
await remove(current);
|
|
52
65
|
setDeleting(false);
|
|
53
66
|
} })] }));
|
|
54
|
-
return
|
|
67
|
+
return (_jsxs("div", { ref: ref, className: clsx('flex min-h-0 min-w-0 grow max-[640px]:flex-col', className), children: [list, _jsx("div", { className: "min-h-0 min-w-0 grow overflow-y-auto", children: _jsx("div", { className: "flex min-w-0 flex-col gap-6 px-5 py-4", children: detail }) })] }));
|
|
55
68
|
}
|
|
56
69
|
/* The containers Docker is running as menu items. The submenu mounts this when it opens, so Docker is asked only then. */
|
|
57
70
|
function DockerMenuItems({ onPick }) {
|
|
@@ -67,27 +80,25 @@ function DockerMenuItems({ onPick }) {
|
|
|
67
80
|
if (containers.length === 0) {
|
|
68
81
|
return _jsx(Menu.Label, { children: t('connections.fromDocker.empty') });
|
|
69
82
|
}
|
|
70
|
-
return containers.map((container) => (_jsx(Menu.Item, { onClick: () => onPick(container), children: _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children:
|
|
83
|
+
return containers.map((container) => (_jsx(Menu.Item, { onClick: () => onPick(container), children: _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: containerTitle(container) }), _jsx("span", { className: "truncate text-xs text-text-faint", children: container.image })] }) }, container.id)));
|
|
71
84
|
}
|
|
72
|
-
/* The button that asks the server who it is
|
|
73
|
-
function ConnectionTest({ connection }) {
|
|
85
|
+
/* The button that asks the server who it is. The manager shows the answer at the top of the detail. */
|
|
86
|
+
function ConnectionTest({ connection, onOutcome }) {
|
|
74
87
|
const { t } = useTranslation('database');
|
|
75
88
|
const client = useDatabaseClient();
|
|
76
89
|
const [busy, setBusy] = useState(false);
|
|
77
|
-
const [outcome, setOutcome] = useState(null);
|
|
78
|
-
const shown = outcome !== null && outcome.config === connection.config ? outcome : null;
|
|
79
90
|
const test = async () => {
|
|
80
91
|
const config = connection.config;
|
|
81
92
|
setBusy(true);
|
|
82
93
|
try {
|
|
83
|
-
|
|
94
|
+
onOutcome({ connectionId: connection.id, config, info: await client.test(config) });
|
|
84
95
|
}
|
|
85
96
|
catch (e) {
|
|
86
|
-
|
|
97
|
+
onOutcome({ connectionId: connection.id, config, error: messageOf(e, t('connections.test.failed')) });
|
|
87
98
|
}
|
|
88
99
|
finally {
|
|
89
100
|
setBusy(false);
|
|
90
101
|
}
|
|
91
102
|
};
|
|
92
|
-
return (
|
|
103
|
+
return (_jsx("div", { children: _jsxs(Button, { variant: "secondary", disabled: busy || !isValidConfig(connection.config), onClick: () => void test(), children: [busy ? _jsx(Spinner, { size: 14 }) : _jsx(Icon, { icon: Unplug, size: 14 }), t('connections.test.action')] }) }));
|
|
93
104
|
}
|
|
@@ -53,7 +53,9 @@ const mysqlProblems = (config) => {
|
|
|
53
53
|
}
|
|
54
54
|
return problems;
|
|
55
55
|
};
|
|
56
|
+
/* What is wrong with a config, field by field: the codes the form shows. Empty for a config with nothing to fix. */
|
|
56
57
|
export const configProblems = (config) => (config.engine === 'sqlite' ? sqliteProblems(config) : mysqlProblems(config));
|
|
58
|
+
/* Whether a connection can be saved, rather than a draft a person is still filling in. */
|
|
57
59
|
export const isValidConfig = (config) => Object.keys(configProblems(config)).length === 0;
|
|
58
60
|
const lastSegment = (path) => {
|
|
59
61
|
const segments = path.split(/[\\/]/).filter((segment) => segment !== '');
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { type Ref } from 'react';
|
|
1
|
+
import { type ReactNode, type Ref } from 'react';
|
|
2
2
|
import { type Connection } from '../client/types.ts';
|
|
3
|
+
import type { QueryConsoleEditorProps } from './editor-slot.ts';
|
|
3
4
|
export interface QueryConsoleProps {
|
|
4
|
-
connection
|
|
5
|
+
connection?: Connection;
|
|
5
6
|
schema?: string;
|
|
7
|
+
onSchemaChange?(schema: string): void;
|
|
6
8
|
value?: string;
|
|
7
9
|
defaultValue?: string;
|
|
8
10
|
onValueChange?(sql: string): void;
|
|
9
11
|
defaultHistoryOpen?: boolean;
|
|
10
12
|
autoFocus?: boolean;
|
|
13
|
+
renderEditor?(editor: QueryConsoleEditorProps): ReactNode;
|
|
14
|
+
toolbarEnd?: ReactNode;
|
|
11
15
|
className?: string;
|
|
12
16
|
ref?: Ref<HTMLDivElement>;
|
|
13
17
|
}
|
|
14
|
-
export declare
|
|
18
|
+
export declare const RESULTS_HEIGHT_KEY = "database:console-results";
|
|
19
|
+
export declare function QueryConsole({ connection, schema, onSchemaChange, value, defaultValue, onValueChange, defaultHistoryOpen, autoFocus, renderEditor, toolbarEnd, className, ref }: QueryConsoleProps): import("react").JSX.Element;
|