дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
Initial open-source release
Claude-Session: https://claude.ai/code/session_01B65qLEKnkeYTGdbwhLpjCu
Цей коміт міститься в:
коміт
9a2025b76e
2034 files changed
+243381
No files matched your search
@@ -0,0 +1,608 @@
|
||||
/**
|
||||
* Dialogs of the Database tree's object actions, after IDEA's: Rename, Confirm
|
||||
* Drop, Truncate, Modify Table / New Table (with a Preview of the SQL they
|
||||
* run), Remove Data Source, Data Source from URL, and the Quick Documentation popup.
|
||||
*/
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { call, commands, type DataSource, type DbColumnInfo, type DbDriver } from "@/platform/ipc";
|
||||
import { highlightInto } from "@/editor/lsp/markdown";
|
||||
import { Button, Checkbox, Dialog, IconButton, Input, Popup, Icon } from "@/ui";
|
||||
import { parseSourceUrl, sourceLabel, splitTarget } from "./model";
|
||||
import { nodeTarget, tableOf, type DbNode } from "./nodes";
|
||||
import {
|
||||
alterTableSql,
|
||||
canAlterColumn,
|
||||
createTableSql,
|
||||
draftOf,
|
||||
dropSql,
|
||||
ident,
|
||||
reference,
|
||||
renameSql,
|
||||
truncateSql,
|
||||
type ColumnDraft,
|
||||
type DbObjectRef,
|
||||
} from "./objects";
|
||||
import {
|
||||
closeDialog,
|
||||
deleteSource,
|
||||
detailsKey,
|
||||
disconnect,
|
||||
connectedTargets,
|
||||
loadDetails,
|
||||
openSourceDialog,
|
||||
readableDdl,
|
||||
renameSource,
|
||||
runSql,
|
||||
sourceById,
|
||||
useDatabase,
|
||||
withConnection,
|
||||
type DialogState,
|
||||
} from "./store";
|
||||
import { openTableData } from "./treeActions";
|
||||
import styles from "./Database.module.css";
|
||||
|
||||
/** Read-only SQL with the editor's syntax colours (IDEA's dialog Preview pane). */
|
||||
function SqlPreview({ sql, label = "Preview" }: { sql: string; label?: string }) {
|
||||
const ref = useRef<HTMLPreElement>(null);
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
el.textContent = sql;
|
||||
void highlightInto(el, sql, "SQL");
|
||||
}, [sql]);
|
||||
return (
|
||||
<div className={styles.previewBlock}>
|
||||
<div className={styles.previewTitle}>{label}</div>
|
||||
<pre ref={ref} className={styles.sqlPreview} aria-label={`${label} SQL`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const driverOf = (id: string): DbDriver => sourceById(id)?.driver ?? "sqlite";
|
||||
|
||||
const KIND_LABEL: Record<DbObjectRef["kind"], string> = {
|
||||
table: "table",
|
||||
view: "view",
|
||||
column: "column",
|
||||
index: "index",
|
||||
schema: "schema",
|
||||
};
|
||||
|
||||
function objectName(o: DbObjectRef): string {
|
||||
return o.kind === "column" ? (o.column ?? "") : o.name;
|
||||
}
|
||||
|
||||
function useRunner() {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const running = useRef(false);
|
||||
const runAndClose = async (id: string, statements: string[]) => {
|
||||
if (running.current) return;
|
||||
running.current = true;
|
||||
setBusy(true);
|
||||
const ok = await runSql(id, statements);
|
||||
running.current = false;
|
||||
setBusy(false);
|
||||
if (ok) closeDialog();
|
||||
};
|
||||
return { busy, runAndClose };
|
||||
}
|
||||
|
||||
// ---- Rename ------------------------------------------------------------------
|
||||
|
||||
function RenameDialog({ id, object }: { id: string; object: DbObjectRef | null }) {
|
||||
const source = sourceById(id);
|
||||
const initial = object ? objectName(object) : (source?.name ?? "");
|
||||
const [name, setName] = useState(initial);
|
||||
const { busy, runAndClose } = useRunner();
|
||||
const driver = driverOf(id);
|
||||
const changed = !!name.trim() && name.trim() !== initial;
|
||||
const sql = object && changed ? renameSql(driver, object, name.trim()) : "";
|
||||
const submit = () => {
|
||||
if (!changed) return closeDialog();
|
||||
if (!object) return void renameSource(id, name).then(closeDialog);
|
||||
void runAndClose(id, [sql]);
|
||||
};
|
||||
const what = object ? KIND_LABEL[object.kind] : "data source";
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title="Rename"
|
||||
width={480}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{ label: "Refactor", role: "default", disabled: busy || !name.trim(), onClick: submit },
|
||||
]}
|
||||
>
|
||||
<div className={styles.dialogBody}>
|
||||
<label className={styles.formLabelLeft} htmlFor="db-rename">
|
||||
Rename {what} '{initial}' to:
|
||||
</label>
|
||||
<Input
|
||||
id="db-rename"
|
||||
autoFocus
|
||||
value={name}
|
||||
onFocus={(e) => e.currentTarget.select()}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
{object && <SqlPreview sql={sql || `-- ${what} keeps its name`} />}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Drop / Truncate -----------------------------------------------------------
|
||||
|
||||
function DropDialog({ id, object }: { id: string; object: DbObjectRef }) {
|
||||
const driver = driverOf(id);
|
||||
const [ifExists, setIfExists] = useState(false);
|
||||
const [cascade, setCascade] = useState(false);
|
||||
const { busy, runAndClose } = useRunner();
|
||||
const sql = dropSql(driver, object, { ifExists, cascade });
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title="Confirm Drop"
|
||||
width={520}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{ label: "OK", role: "default", disabled: busy, onClick: () => void runAndClose(id, [sql]) },
|
||||
]}
|
||||
>
|
||||
<div className={styles.dialogBody}>
|
||||
<div className={styles.dialogLead}>
|
||||
Drop {KIND_LABEL[object.kind]} <strong>{reference(driver, object)}</strong>? This cannot be undone.
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={ifExists}
|
||||
onChange={setIfExists}
|
||||
label="Use IF EXISTS syntax"
|
||||
disabled={(object.kind === "column" && driver !== "postgres") || (object.kind === "index" && driver === "mysql")}
|
||||
/>
|
||||
<Checkbox checked={cascade} onChange={setCascade} label="Use DROP CASCADE syntax" disabled={driver !== "postgres"} />
|
||||
<SqlPreview sql={sql} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function TruncateDialog({ id, schema, table }: { id: string; schema: string; table: string }) {
|
||||
const driver = driverOf(id);
|
||||
const [restartIdentity, setRestart] = useState(false);
|
||||
const [cascade, setCascade] = useState(false);
|
||||
const { busy, runAndClose } = useRunner();
|
||||
const sql = truncateSql(driver, schema, table, { restartIdentity, cascade });
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title="Truncate"
|
||||
width={520}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{ label: "OK", role: "default", disabled: busy, onClick: () => void runAndClose(id, [sql]) },
|
||||
]}
|
||||
>
|
||||
<div className={styles.dialogBody}>
|
||||
<div className={styles.dialogLead}>
|
||||
Delete all rows of <strong>{table}</strong>?
|
||||
</div>
|
||||
{driver === "postgres" && (
|
||||
<>
|
||||
<Checkbox checked={restartIdentity} onChange={setRestart} label="Restart identity" />
|
||||
<Checkbox checked={cascade} onChange={setCascade} label="Cascade" />
|
||||
</>
|
||||
)}
|
||||
<SqlPreview sql={sql} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Modify Table / New Table -----------------------------------------------
|
||||
|
||||
const TYPES: Record<DbDriver, string[]> = {
|
||||
sqlite: ["INTEGER", "TEXT", "REAL", "NUMERIC", "BLOB", "BOOLEAN", "DATE", "DATETIME"],
|
||||
postgres: [
|
||||
"integer",
|
||||
"bigint",
|
||||
"smallint",
|
||||
"serial",
|
||||
"bigserial",
|
||||
"numeric",
|
||||
"real",
|
||||
"double precision",
|
||||
"boolean",
|
||||
"text",
|
||||
"varchar(255)",
|
||||
"uuid",
|
||||
"date",
|
||||
"timestamp",
|
||||
"timestamptz",
|
||||
"jsonb",
|
||||
"bytea",
|
||||
],
|
||||
mysql: ["int", "bigint", "smallint", "tinyint(1)", "decimal(10,2)", "double", "varchar(255)", "text", "date", "datetime", "timestamp", "json", "blob"],
|
||||
};
|
||||
|
||||
interface ModifyProps {
|
||||
id: string;
|
||||
schema: string;
|
||||
table: string | null;
|
||||
focusColumn?: string;
|
||||
addColumn?: boolean;
|
||||
}
|
||||
|
||||
let nextDraftKey = 1000;
|
||||
|
||||
function blankColumn(driver: DbDriver, n: number): ColumnDraft {
|
||||
return {
|
||||
key: nextDraftKey++,
|
||||
original: null,
|
||||
name: `column_${n}`,
|
||||
dataType: TYPES[driver][0]!,
|
||||
notNull: false,
|
||||
default: "",
|
||||
primaryKey: false,
|
||||
dropped: false,
|
||||
};
|
||||
}
|
||||
|
||||
/** Loads the table's columns, then shows the Modify Table form. */
|
||||
function ModifyTableDialog(props: ModifyProps) {
|
||||
const { id, schema, table } = props;
|
||||
const key = table ? detailsKey(id, schema, table) : null;
|
||||
const details = useDatabase((s) => (key ? s.details[key] : undefined));
|
||||
useEffect(() => {
|
||||
if (table && !details) void loadDetails(id, schema, table);
|
||||
}, [details, id, schema, table]);
|
||||
if (!table) return <ModifyTableForm {...props} initial={null} />;
|
||||
if (details?.status === "ok") return <ModifyTableForm {...props} initial={details.value.columns} />;
|
||||
return (
|
||||
<Dialog open onClose={closeDialog} title={`Modify Table ${table}`} width={440} buttons={[{ label: "Cancel", role: "cancel" }]}>
|
||||
<div className={styles.dialogText}>{details?.status === "error" ? details.message : "Loading…"}</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function ModifyTableForm({ id, schema, table, focusColumn, addColumn, initial }: ModifyProps & { initial: DbColumnInfo[] | null }) {
|
||||
const driver = driverOf(id);
|
||||
const [name, setName] = useState(table ?? "table_name");
|
||||
const [columns, setColumns] = useState<ColumnDraft[]>(() => {
|
||||
if (!initial) return [{ ...blankColumn(driver, 1), name: "id", primaryKey: true, notNull: true }];
|
||||
const drafts = initial.map(draftOf);
|
||||
if (addColumn) drafts.push(blankColumn(driver, drafts.length + 1));
|
||||
return drafts;
|
||||
});
|
||||
const [selected, setSelected] = useState<number | null>(() => {
|
||||
const focus = addColumn ? columns.length - 1 : columns.findIndex((c) => c.name === focusColumn);
|
||||
return columns[Math.max(0, focus)]?.key ?? null;
|
||||
});
|
||||
const { busy, runAndClose } = useRunner();
|
||||
|
||||
const statements = useMemo(() => {
|
||||
if (!table) return [createTableSql(driver, schema, name.trim() || "table_name", columns)];
|
||||
return alterTableSql(driver, schema, table, name, columns);
|
||||
}, [columns, driver, schema, table, name]);
|
||||
|
||||
const current = columns.find((c) => c.key === selected) ?? null;
|
||||
const patch = (p: Partial<ColumnDraft>) => setColumns((cs) => cs.map((c) => (c.key === selected ? { ...c, ...p } : c)));
|
||||
const add = () => {
|
||||
const c = blankColumn(driver, columns.length + 1);
|
||||
setColumns((cs) => [...cs, c]);
|
||||
setSelected(c.key);
|
||||
};
|
||||
const remove = () => {
|
||||
if (!current) return;
|
||||
if (current.original) patch({ dropped: !current.dropped });
|
||||
else {
|
||||
const rest = columns.filter((c) => c.key !== current.key);
|
||||
setColumns(rest);
|
||||
setSelected(rest.at(-1)?.key ?? null);
|
||||
}
|
||||
};
|
||||
const locked = !!current && !canAlterColumn(driver, current);
|
||||
const title = table ? `Modify Table ${table}` : "Create Table";
|
||||
const typeList = `db-types-${driver}`;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title={title}
|
||||
width={760}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{
|
||||
label: "OK",
|
||||
role: "default",
|
||||
disabled: busy || !statements.length || !name.trim(),
|
||||
onClick: () => void runAndClose(id, statements),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div className={styles.modifyBody}>
|
||||
<div className={styles.formRow}>
|
||||
<label className={styles.formLabel} htmlFor="db-table-name">
|
||||
Table:
|
||||
</label>
|
||||
<Input id="db-table-name" className={styles.fill} value={name} onChange={(e) => setName(e.target.value)} autoFocus={!table} />
|
||||
<span className={styles.formHint}>{schema}</span>
|
||||
</div>
|
||||
<div className={styles.modifyMain}>
|
||||
<div className={styles.modifyColumns}>
|
||||
<div className={styles.modifyToolbar}>
|
||||
<IconButton icon="plus" title="Add Column" size="small" onClick={add} />
|
||||
<IconButton
|
||||
icon="minus"
|
||||
title={current?.dropped ? "Keep Column" : "Drop Column"}
|
||||
size="small"
|
||||
disabled={!current}
|
||||
onClick={remove}
|
||||
/>
|
||||
</div>
|
||||
<div role="listbox" aria-label="Columns" className={styles.modifyList}>
|
||||
{columns.map((c) => (
|
||||
<div
|
||||
key={c.key}
|
||||
role="option"
|
||||
aria-selected={c.key === selected}
|
||||
tabIndex={-1}
|
||||
className={styles.modifyItem}
|
||||
data-dropped={c.dropped || undefined}
|
||||
onMouseDown={() => setSelected(c.key)}
|
||||
>
|
||||
<Icon name={c.primaryKey ? "db-column-key" : "db-column"} size={16} />
|
||||
<span className={styles.nodeLabel}>{c.name || "<unnamed>"}</span>
|
||||
<span className={styles.treeType}>{c.dataType}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.modifyProps}>
|
||||
{current ? (
|
||||
<>
|
||||
<label className={styles.formLabel} htmlFor="db-col-name">
|
||||
Name
|
||||
</label>
|
||||
<Input id="db-col-name" value={current.name} disabled={current.dropped} onChange={(e) => patch({ name: e.target.value })} />
|
||||
<label className={styles.formLabel} htmlFor="db-col-type">
|
||||
Data type
|
||||
</label>
|
||||
<Input
|
||||
id="db-col-type"
|
||||
mono
|
||||
list={typeList}
|
||||
value={current.dataType}
|
||||
disabled={current.dropped || locked}
|
||||
onChange={(e) => patch({ dataType: e.target.value })}
|
||||
/>
|
||||
<span />
|
||||
<Checkbox checked={current.notNull} disabled={current.dropped || locked} onChange={(notNull) => patch({ notNull })} label="Not null" />
|
||||
{!table && (
|
||||
<>
|
||||
<span />
|
||||
<Checkbox checked={current.primaryKey} onChange={(primaryKey) => patch({ primaryKey })} label="Primary key" />
|
||||
</>
|
||||
)}
|
||||
<label className={styles.formLabel} htmlFor="db-col-default">
|
||||
Default
|
||||
</label>
|
||||
<Input
|
||||
id="db-col-default"
|
||||
mono
|
||||
placeholder="expression"
|
||||
value={current.default}
|
||||
disabled={current.dropped || locked}
|
||||
onChange={(e) => patch({ default: e.target.value })}
|
||||
/>
|
||||
{locked && (
|
||||
<>
|
||||
<span />
|
||||
<span className={styles.formHint}>SQLite can rename or drop an existing column, not change its type, NOT NULL or default.</span>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className={styles.formHint}>Select a column</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<datalist id={typeList}>
|
||||
{TYPES[driver].map((t) => (
|
||||
<option key={t} value={t} />
|
||||
))}
|
||||
</datalist>
|
||||
<SqlPreview sql={statements.join("\n") || "-- no changes"} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Data sources -------------------------------------------------------------
|
||||
|
||||
function RemoveSourceDialog({ source }: { source: DataSource }) {
|
||||
const remove = async () => {
|
||||
const id = source.id ?? "";
|
||||
closeDialog();
|
||||
if (connectedTargets(id).length) await disconnect(id);
|
||||
await deleteSource(id);
|
||||
};
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title="Remove Data Source"
|
||||
width={440}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{ label: "Remove", role: "default", onClick: () => void remove() },
|
||||
]}
|
||||
>
|
||||
<div className={styles.dialogText}>
|
||||
Remove data source <strong>{source.name}</strong>? Its query console file stays on disk.
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function SourceUrlDialog() {
|
||||
const [url, setUrl] = useState("");
|
||||
const parsed = url.trim() ? parseSourceUrl(url) : null;
|
||||
const next = () => {
|
||||
if (!parsed) return;
|
||||
closeDialog();
|
||||
const s = parsed.source;
|
||||
const name = s.driver === "sqlite" ? s.name : `${s.database || s.user || "db"}@${s.host}`;
|
||||
openSourceDialog(null, { ...s, name }, parsed.password);
|
||||
};
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
onClose={closeDialog}
|
||||
title="Data Source from URL"
|
||||
width={560}
|
||||
buttons={[
|
||||
{ label: "Cancel", role: "cancel" },
|
||||
{ label: "OK", role: "default", disabled: !parsed, onClick: next },
|
||||
]}
|
||||
>
|
||||
<div className={styles.dialogBody}>
|
||||
<label className={styles.formLabelLeft} htmlFor="db-url">
|
||||
URL:
|
||||
</label>
|
||||
<Input
|
||||
id="db-url"
|
||||
mono
|
||||
autoFocus
|
||||
invalid={!!url.trim() && !parsed}
|
||||
placeholder="postgresql://user@localhost:5432/db"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<span className={styles.formHint}>
|
||||
{url.trim() && !parsed
|
||||
? "Not a PostgreSQL, MySQL / MariaDB or SQLite URL"
|
||||
: "postgresql://, mysql://, mariadb://, jdbc:…, sqlite:path"}
|
||||
</span>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Quick Documentation ------------------------------------------------------
|
||||
|
||||
function QuickDoc({ node, anchor }: { node: DbNode; anchor: { x: number; y: number } }) {
|
||||
const id = nodeTarget(node) ?? "";
|
||||
const source = sourceById(id);
|
||||
const t = tableOf(node);
|
||||
const [ddl, setDdl] = useState<string | null>(null);
|
||||
const showDdl = node.kind === "table";
|
||||
useEffect(() => {
|
||||
if (!showDdl || !t) return;
|
||||
let live = true;
|
||||
withConnection(t.id, () => call(commands.dbDdl(t.id, t.schema, t.table)))
|
||||
.then((d) => live && setDdl(readableDdl(d, driverOf(t.id))))
|
||||
.catch((e: unknown) => live && setDdl(`-- ${e instanceof Error ? e.message : String(e)}`));
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [node.key]);
|
||||
|
||||
const rows: Array<[string, string]> = [];
|
||||
if (source) rows.push(["Data Source", source.name ?? ""]);
|
||||
const db = splitTarget(id).database;
|
||||
if (db) rows.push(["Database", db]);
|
||||
switch (node.kind) {
|
||||
case "source":
|
||||
rows.push(["Driver", sourceLabel(node.source).replace(/^.*\((.*)\)$/, "$1")]);
|
||||
rows.push(
|
||||
node.source.driver === "sqlite"
|
||||
? ["File", node.source.file ?? ""]
|
||||
: ["Host", `${node.source.host ?? ""}${node.source.port ? `:${node.source.port}` : ""}${node.source.database ? `/${node.source.database}` : ""}`],
|
||||
);
|
||||
rows.push(["Status", connectedTargets(node.source.id ?? "").length ? "Connected" : "Not connected"]);
|
||||
break;
|
||||
case "database":
|
||||
rows.push(["Database", node.name]);
|
||||
break;
|
||||
case "schema":
|
||||
case "folder":
|
||||
rows.push(["Schema", node.schema]);
|
||||
break;
|
||||
default:
|
||||
if (t) rows.push(["Schema", t.schema], [t.view ? "View" : "Table", t.table]);
|
||||
}
|
||||
let definition: string | null = null;
|
||||
if (node.kind === "column") {
|
||||
const c = node.column;
|
||||
const d = driverOf(id);
|
||||
definition = `${ident(d, c.name)} ${c.dataType}${c.nullable ? "" : " not null"}${c.default ? ` default ${c.default}` : ""}${c.primaryKey ? " primary key" : ""}`;
|
||||
} else if (node.kind === "index") {
|
||||
definition = `${node.index.unique ? "unique " : ""}index ${node.index.name} (${node.index.columns.join(", ")})`;
|
||||
} else if (node.kind === "foreignKey") {
|
||||
definition = `foreign key (${node.fk.columns.join(", ")}) references ${node.fk.refTable} (${node.fk.refColumns.join(", ")})`;
|
||||
} else if (node.kind === "key") {
|
||||
definition = `primary key (${node.columns.join(", ")})`;
|
||||
}
|
||||
return (
|
||||
<Popup open onClose={closeDialog} anchor={anchor} placement="bottom-start" className={styles.quickDoc} role="dialog" aria-label="Quick Documentation">
|
||||
<dl className={styles.quickDocRows}>
|
||||
{rows.map(([k, v]) => (
|
||||
<div key={k}>
|
||||
<dt>{k}:</dt>
|
||||
<dd>{v}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
{definition && <SqlPreview sql={definition} label="Definition" />}
|
||||
{showDdl && <SqlPreview sql={ddl ?? "-- loading…"} label="DDL" />}
|
||||
{t && (
|
||||
<div className={styles.quickDocLinks}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
closeDialog();
|
||||
openTableData(t.id, t.schema, t.table);
|
||||
}}
|
||||
>
|
||||
Show table data
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export function ObjectDialogs() {
|
||||
const dialog = useDatabase((s) => s.dialog) as DialogState | null;
|
||||
if (!dialog) return null;
|
||||
switch (dialog.kind) {
|
||||
case "rename":
|
||||
return <RenameDialog key={`${dialog.id}`} id={dialog.id} object={dialog.object} />;
|
||||
case "drop":
|
||||
return <DropDialog id={dialog.id} object={dialog.object} />;
|
||||
case "truncate":
|
||||
return <TruncateDialog id={dialog.id} schema={dialog.schema} table={dialog.table} />;
|
||||
case "modifyTable":
|
||||
return (
|
||||
<ModifyTableDialog
|
||||
id={dialog.id}
|
||||
schema={dialog.schema}
|
||||
table={dialog.table}
|
||||
focusColumn={dialog.focusColumn}
|
||||
addColumn={dialog.addColumn}
|
||||
/>
|
||||
);
|
||||
case "removeSource":
|
||||
return <RemoveSourceDialog source={dialog.source} />;
|
||||
case "sourceUrl":
|
||||
return <SourceUrlDialog />;
|
||||
case "quickDoc":
|
||||
return <QuickDoc key={dialog.node.key} node={dialog.node} anchor={dialog.anchor} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача