feat(editor): add table copy/paste keyboard shortcuts
* feat: add table copy/paste with configurable structure and data options - Implement Navicat-style table copy/paste functionality - Support multi-table selection and batch operations - Configurable paste modes: structure+data (default), structure only, data only - Backend: add build_copy_table_data_sql API (INSERT INTO ... SELECT) - TreeItem.vue: copy/paste context menus, paste dialog with mode selection - ObjectBrowser.vue: batch toolbar buttons, context menu, paste dialog - Refactor TreeClipboard type to support multi-table entries - Add i18n keys for all 7 locale files (en, zh-CN, zh-TW, ja, es, it, pt-BR) * Fix table paste shortcuts * Fix Kafka MQ sidebar detection * Restrict table paste scope * Support data copy for primary SQL databases
This commit is contained in:
1 parent
5d2ce9f189
commit
0c91fecf5d
26 files changed
+943
-34
No files matched your search
@@ -168,6 +168,7 @@ export const buildDropDatabaseSql = forward("buildDropDatabaseSql");
|
||||
export const buildCreateSchemaSql = forward("buildCreateSchemaSql");
|
||||
export const buildDropSchemaSql = forward("buildDropSchemaSql");
|
||||
export const buildDuplicateTableStructureSql = forward("buildDuplicateTableStructureSql");
|
||||
export const buildCopyTableDataSql = forward("buildCopyTableDataSql");
|
||||
export const buildExecutableObjectSourceStatements = forward("buildExecutableObjectSourceStatements");
|
||||
export const buildExecutableObjectSourceSql = forward("buildExecutableObjectSourceSql");
|
||||
export const buildEditableObjectSource = forward("buildEditableObjectSource");
|
||||
|
||||
@@ -86,6 +86,11 @@ export function eventTargetAllowsNativeClipboard(event: ClipboardShortcutEvent,
|
||||
return isPlainClipboardShortcut(event, "c") && hasNativeClipboardSelection(env);
|
||||
}
|
||||
|
||||
export function eventTargetAllowsAppClipboardShortcut(event: ClipboardShortcutEvent, key = "v"): boolean {
|
||||
if (!isPlainClipboardShortcut(event, key)) return false;
|
||||
return !closestElement(event.target, EDITABLE_CLIPBOARD_TARGET_SELECTOR);
|
||||
}
|
||||
|
||||
export async function readTextFromClipboard(env: ClipboardEnvironment = globalThis as unknown as ClipboardEnvironment): Promise<string> {
|
||||
if (isTauriRuntime(env as unknown as Record<string, unknown>)) {
|
||||
try {
|
||||
|
||||
@@ -45,6 +45,16 @@ export interface DuplicateTableStructureSqlOptions {
|
||||
targetName: string;
|
||||
}
|
||||
|
||||
export interface CopyTableDataSqlOptions {
|
||||
databaseType?: DatabaseType;
|
||||
schema?: string | null;
|
||||
sourceName: string;
|
||||
targetName: string;
|
||||
columns?: string[];
|
||||
postgresOverridingSystemValue?: boolean;
|
||||
sqlserverIdentityInsert?: boolean;
|
||||
}
|
||||
|
||||
export function buildDropObjectSql(options: DropObjectSqlOptions): Promise<string> {
|
||||
return api.buildDropObjectSql(options);
|
||||
}
|
||||
@@ -101,6 +111,10 @@ export function buildDuplicateTableStructureSql(options: DuplicateTableStructure
|
||||
return api.buildDuplicateTableStructureSql(options);
|
||||
}
|
||||
|
||||
export function buildCopyTableDataSql(options: CopyTableDataSqlOptions): Promise<string> {
|
||||
return api.buildCopyTableDataSql(options);
|
||||
}
|
||||
|
||||
function quotePostgresIdentifier(value: string): string {
|
||||
return `"${value.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ import type { BuildEditableObjectSourceSqlInput, BuildRoutineRenameObjectSourceI
|
||||
import type { BuildViewDdlInput } from "@/lib/viewDdl";
|
||||
import type { BuildRenameObjectSqlOptions } from "@/lib/objectRenameSql";
|
||||
import type { CreateDatabaseSqlOptions } from "@/lib/createDatabaseSql";
|
||||
import type { DatabaseNameSqlOptions, DropTableChildObjectSqlOptions, DropObjectSqlOptions, DuplicateTableStructureSqlOptions, SchemaNameSqlOptions, TableAdminSqlOptions } from "@/lib/dbAdminSql";
|
||||
import type { DatabaseNameSqlOptions, DropTableChildObjectSqlOptions, DropObjectSqlOptions, DuplicateTableStructureSqlOptions, CopyTableDataSqlOptions, SchemaNameSqlOptions, TableAdminSqlOptions } from "@/lib/dbAdminSql";
|
||||
import type { BuildDatabaseSqlExportOptions, BuildExportInsertStatementsOptions } from "@/lib/databaseExport";
|
||||
import type { DataCompareFromTablesOptions, DataCompareFromTablesPreparation, DataCompareSyncPlan, DataCompareSyncPlanOptions, DataComparePreparation, DataComparePreparationOptions } from "@/lib/dataCompare";
|
||||
import { apiUrl } from "@/lib/webPath";
|
||||
@@ -760,6 +760,10 @@ export async function buildDuplicateTableStructureSql(options: DuplicateTableStr
|
||||
return post("/api/query/build-duplicate-table-structure-sql", { options });
|
||||
}
|
||||
|
||||
export async function buildCopyTableDataSql(options: CopyTableDataSqlOptions): Promise<string> {
|
||||
return post("/api/query/build-copy-table-data-sql", { options });
|
||||
}
|
||||
|
||||
export async function buildExecutableObjectSourceStatements(input: BuildEditableObjectSourceSqlInput): Promise<string[]> {
|
||||
return post("/api/query/build-executable-object-source-statements", { input });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { ColumnInfo, DatabaseType } from "@/types/database";
|
||||
|
||||
export type PasteTableMode = "structure-and-data" | "structure-only" | "data-only";
|
||||
|
||||
export interface TableClipboardContext {
|
||||
connectionId: string;
|
||||
database: string;
|
||||
schema?: string | null;
|
||||
}
|
||||
|
||||
export interface TableDataCopyColumnOptions {
|
||||
columns: string[];
|
||||
postgresOverridingSystemValue: boolean;
|
||||
sqlserverIdentityInsert: boolean;
|
||||
}
|
||||
|
||||
function normalizeSchema(schema: string | null | undefined): string {
|
||||
return schema?.trim() ?? "";
|
||||
}
|
||||
|
||||
export function tableClipboardEntryMatchesTarget(entry: TableClipboardContext, target: TableClipboardContext): boolean {
|
||||
return entry.connectionId === target.connectionId && entry.database === target.database && normalizeSchema(entry.schema) === normalizeSchema(target.schema);
|
||||
}
|
||||
|
||||
export function tableClipboardMatchesTarget(entries: TableClipboardContext[], target: TableClipboardContext | null): boolean {
|
||||
return !!target && entries.length > 0 && entries.every((entry) => tableClipboardEntryMatchesTarget(entry, target));
|
||||
}
|
||||
|
||||
export function supportsWholeRowTableDataCopy(databaseType: DatabaseType | undefined): boolean {
|
||||
return !!databaseType;
|
||||
}
|
||||
|
||||
export function defaultPasteTableMode(databaseType: DatabaseType | undefined): PasteTableMode {
|
||||
return supportsWholeRowTableDataCopy(databaseType) ? "structure-and-data" : "structure-only";
|
||||
}
|
||||
|
||||
export function pasteTableModeCopiesData(mode: PasteTableMode): boolean {
|
||||
return mode === "structure-and-data" || mode === "data-only";
|
||||
}
|
||||
|
||||
export function tableDataCopyColumnOptions(databaseType: DatabaseType | undefined, columns: ColumnInfo[]): TableDataCopyColumnOptions {
|
||||
const writableColumns = columns.filter((column) => isWritableTableDataCopyColumn(databaseType, column));
|
||||
return {
|
||||
columns: writableColumns.map((column) => column.name),
|
||||
postgresOverridingSystemValue: databaseType === "postgres" && writableColumns.some(isIdentityColumn),
|
||||
sqlserverIdentityInsert: databaseType === "sqlserver" && writableColumns.some(isIdentityColumn),
|
||||
};
|
||||
}
|
||||
|
||||
function isWritableTableDataCopyColumn(databaseType: DatabaseType | undefined, column: ColumnInfo): boolean {
|
||||
const extra = (column.extra ?? "").toLowerCase();
|
||||
if (databaseType === "mysql") {
|
||||
return !extra.includes("generated");
|
||||
}
|
||||
if (databaseType === "postgres") {
|
||||
return !extra.includes("generated always as (");
|
||||
}
|
||||
if (databaseType === "sqlserver") {
|
||||
return !extra.includes("computed");
|
||||
}
|
||||
return !extra.includes("computed") && !(extra.includes("generated") && !extra.includes("identity"));
|
||||
}
|
||||
|
||||
function isIdentityColumn(column: ColumnInfo): boolean {
|
||||
return (column.extra ?? "").toLowerCase().includes("identity");
|
||||
}
|
||||
@@ -55,7 +55,7 @@ import type { BuildEditableObjectSourceSqlInput, BuildRoutineRenameObjectSourceI
|
||||
import type { BuildViewDdlInput } from "@/lib/viewDdl";
|
||||
import type { BuildRenameObjectSqlOptions } from "@/lib/objectRenameSql";
|
||||
import type { CreateDatabaseSqlOptions } from "@/lib/createDatabaseSql";
|
||||
import type { DatabaseNameSqlOptions, DropTableChildObjectSqlOptions, DropObjectSqlOptions, DuplicateTableStructureSqlOptions, SchemaNameSqlOptions, TableAdminSqlOptions } from "@/lib/dbAdminSql";
|
||||
import type { DatabaseNameSqlOptions, DropTableChildObjectSqlOptions, DropObjectSqlOptions, DuplicateTableStructureSqlOptions, CopyTableDataSqlOptions, SchemaNameSqlOptions, TableAdminSqlOptions } from "@/lib/dbAdminSql";
|
||||
import type { BuildDatabaseSqlExportOptions, BuildExportInsertStatementsOptions } from "@/lib/databaseExport";
|
||||
|
||||
export interface AgentDriverInfo {
|
||||
@@ -747,6 +747,10 @@ export async function buildDuplicateTableStructureSql(options: DuplicateTableStr
|
||||
return invoke("build_duplicate_table_structure_sql", { options });
|
||||
}
|
||||
|
||||
export async function buildCopyTableDataSql(options: CopyTableDataSqlOptions): Promise<string> {
|
||||
return invoke("build_copy_table_data_sql", { options });
|
||||
}
|
||||
|
||||
export async function buildExecutableObjectSourceStatements(input: BuildEditableObjectSourceSqlInput): Promise<string[]> {
|
||||
return invoke("build_executable_object_source_statements", { input });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user