feat(sidebar): batch actions for multi selected nodes
This commit is contained in:
1 parent
2f71a1a8aa
commit
69a032ed63
37 files changed
+1302
-159
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import { COLUMN_NAME_COPY_SEPARATOR_VALUES, loadColumnNameCopySeparator, type ColumnNameCopySeparator, isColumnNameCopySeparator } from "@/lib/dataGrid/dataGridColumnNameCopy";
|
||||
import { qualifiedTableName, quoteTableIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { requiresMysqlIdentifierQuote, requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier.ts";
|
||||
|
||||
@@ -37,18 +38,27 @@ export const DBX_TABLE_REFERENCE_DROP_EVENT = "dbx-table-reference-drop";
|
||||
export const DBX_TABLE_REFERENCE_HOVER_EVENT = "dbx-table-reference-hover";
|
||||
export const DBX_TABLE_REFERENCE_DRAG_END_EVENT = "dbx-table-reference-drag-end";
|
||||
|
||||
export interface QueryEditorTableReferenceEntry {
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
}
|
||||
|
||||
export interface QueryEditorTableReferencePayload {
|
||||
kind: "dbx-table-reference";
|
||||
connectionId: string;
|
||||
database: string;
|
||||
schema?: string;
|
||||
tableName?: string;
|
||||
tableReferences?: QueryEditorTableReferenceEntry[];
|
||||
columnName?: string;
|
||||
/** 多个结果列一起拖入时按选择顺序排列;单列沿用 columnName。 */
|
||||
columnNames?: string[];
|
||||
referenceType?: "database" | "table" | "column";
|
||||
databaseType?: DatabaseType;
|
||||
driverProfile?: string;
|
||||
columnNameSeparator?: ColumnNameCopySeparator;
|
||||
tableNameSeparator?: ColumnNameCopySeparator;
|
||||
includeTableSchema?: boolean;
|
||||
}
|
||||
|
||||
export interface QueryEditorTableReferenceDropDetail {
|
||||
@@ -62,16 +72,38 @@ export interface QueryEditorTableReferenceHoverDetail {
|
||||
clientY: number;
|
||||
}
|
||||
|
||||
export interface TableReferenceInsertOptions {
|
||||
tableNameSeparator?: ColumnNameCopySeparator;
|
||||
includeTableSchema?: boolean;
|
||||
columnNameSeparator?: ColumnNameCopySeparator;
|
||||
}
|
||||
|
||||
function normalizeColumnNames(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.map((name) => (typeof name === "string" ? name.trim() : "")).filter((name) => name.length > 0);
|
||||
}
|
||||
|
||||
function normalizeTableReferences(value: unknown): QueryEditorTableReferenceEntry[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const entries: QueryEditorTableReferenceEntry[] = [];
|
||||
for (const item of value) {
|
||||
if (!item || typeof item !== "object") continue;
|
||||
const tableName = typeof (item as QueryEditorTableReferenceEntry).tableName === "string" ? (item as QueryEditorTableReferenceEntry).tableName.trim() : "";
|
||||
if (!tableName) continue;
|
||||
const schema = typeof (item as QueryEditorTableReferenceEntry).schema === "string" ? (item as QueryEditorTableReferenceEntry).schema : undefined;
|
||||
entries.push(schema ? { schema, tableName } : { tableName });
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/** schema/databaseType/driverProfile 为可选元数据,payload 各构造路径共用同一拷贝规则。 */
|
||||
function applyOptionalReferenceMeta(payload: QueryEditorTableReferencePayload, source: Partial<QueryEditorTableReferencePayload>) {
|
||||
if (typeof source.schema === "string" && source.schema) payload.schema = source.schema;
|
||||
if (source.databaseType) payload.databaseType = source.databaseType;
|
||||
if (typeof source.driverProfile === "string" && source.driverProfile) payload.driverProfile = source.driverProfile;
|
||||
if (isColumnNameCopySeparator(source.columnNameSeparator)) payload.columnNameSeparator = source.columnNameSeparator;
|
||||
if (isColumnNameCopySeparator(source.tableNameSeparator)) payload.tableNameSeparator = source.tableNameSeparator;
|
||||
if (source.includeTableSchema === true) payload.includeTableSchema = true;
|
||||
}
|
||||
|
||||
let activeTableReferencePayload: QueryEditorTableReferencePayload | null = null;
|
||||
@@ -107,7 +139,40 @@ export function createTableReferencePayload(options: {
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function createColumnReferencePayload(options: { connectionId?: string; database?: string; schema?: string; columnNames?: readonly (string | undefined | null)[]; databaseType?: DatabaseType }): QueryEditorTableReferencePayload | null {
|
||||
export function createMultiTableReferencePayload(options: {
|
||||
connectionId?: string;
|
||||
database?: string;
|
||||
tableReferences?: readonly QueryEditorTableReferenceEntry[];
|
||||
databaseType?: DatabaseType;
|
||||
driverProfile?: string;
|
||||
tableNameSeparator?: ColumnNameCopySeparator;
|
||||
includeTableSchema?: boolean;
|
||||
}): QueryEditorTableReferencePayload | null {
|
||||
if (!options.connectionId || options.database == null) return null;
|
||||
const tableReferences = normalizeTableReferences(options.tableReferences);
|
||||
if (tableReferences.length === 0) return null;
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: options.connectionId,
|
||||
database: options.database,
|
||||
referenceType: "table",
|
||||
};
|
||||
if (tableReferences.length === 1) {
|
||||
payload.tableName = tableReferences[0]!.tableName;
|
||||
if (tableReferences[0]!.schema) payload.schema = tableReferences[0]!.schema;
|
||||
} else {
|
||||
payload.tableReferences = tableReferences;
|
||||
}
|
||||
applyOptionalReferenceMeta(payload, {
|
||||
databaseType: options.databaseType,
|
||||
driverProfile: options.driverProfile,
|
||||
tableNameSeparator: options.tableNameSeparator,
|
||||
includeTableSchema: options.includeTableSchema,
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function createColumnReferencePayload(options: { connectionId?: string; database?: string; schema?: string; columnNames?: readonly (string | undefined | null)[]; databaseType?: DatabaseType; columnNameSeparator?: ColumnNameCopySeparator }): QueryEditorTableReferencePayload | null {
|
||||
const columnNames = normalizeColumnNames(options.columnNames);
|
||||
if (!options.connectionId || options.database == null || columnNames.length === 0) return null;
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
@@ -143,10 +208,8 @@ export function parseTableReferencePayload(value: string | undefined | null): Qu
|
||||
if (typeof parsed.driverProfile === "string" && parsed.driverProfile) payload.driverProfile = parsed.driverProfile;
|
||||
return payload;
|
||||
}
|
||||
if (typeof parsed.tableName !== "string" || !parsed.tableName) {
|
||||
// 无来源表的纯列引用(如从查询结果列头拖入),tableName 非必需。
|
||||
const columnNames = normalizeColumnNames(parsed.columnNames);
|
||||
if (columnNames.length === 0) return null;
|
||||
const columnNames = normalizeColumnNames(parsed.columnNames);
|
||||
if (columnNames.length > 0 && (parsed.referenceType === "column" || !parsed.tableName)) {
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: parsed.connectionId,
|
||||
@@ -157,18 +220,34 @@ export function parseTableReferencePayload(value: string | undefined | null): Qu
|
||||
applyOptionalReferenceMeta(payload, parsed);
|
||||
return payload;
|
||||
}
|
||||
const tableReferences = normalizeTableReferences(parsed.tableReferences);
|
||||
if (tableReferences.length > 1) {
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: parsed.connectionId,
|
||||
database: parsed.database,
|
||||
referenceType: "table",
|
||||
tableReferences,
|
||||
};
|
||||
applyOptionalReferenceMeta(payload, parsed);
|
||||
return payload;
|
||||
}
|
||||
if (typeof parsed.tableName !== "string" || !parsed.tableName) {
|
||||
return null;
|
||||
}
|
||||
const columnName = typeof parsed.columnName === "string" && parsed.columnName ? parsed.columnName : undefined;
|
||||
const referenceType = parsed.referenceType === "column" || columnName ? "column" : "table";
|
||||
if (referenceType === "column" && !columnName) return null;
|
||||
const referenceType = parsed.referenceType === "column" || columnName || columnNames.length > 0 ? "column" : "table";
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: parsed.connectionId,
|
||||
database: parsed.database,
|
||||
tableName: parsed.tableName,
|
||||
};
|
||||
if (referenceType === "column" && columnName) {
|
||||
payload.columnName = columnName;
|
||||
if (referenceType === "column") {
|
||||
payload.referenceType = "column";
|
||||
if (columnNames.length > 0) payload.columnNames = columnNames;
|
||||
else if (columnName) payload.columnName = columnName;
|
||||
else return null;
|
||||
}
|
||||
applyOptionalReferenceMeta(payload, parsed);
|
||||
return payload;
|
||||
@@ -211,14 +290,40 @@ export function createTableReferenceDragEndEvent(): Event {
|
||||
return new Event(DBX_TABLE_REFERENCE_DRAG_END_EVENT);
|
||||
}
|
||||
|
||||
export function tableReferenceInsertText(payload: QueryEditorTableReferencePayload, fallbackDatabaseType?: DatabaseType): string {
|
||||
function separatorValue(payload: QueryEditorTableReferencePayload, options: TableReferenceInsertOptions | undefined, kind: "table" | "column"): string {
|
||||
const fromPayload = kind === "table" ? payload.tableNameSeparator : payload.columnNameSeparator;
|
||||
const fromOptions = kind === "table" ? options?.tableNameSeparator : options?.columnNameSeparator;
|
||||
const fallback = kind === "column" ? loadColumnNameCopySeparator() : "comma";
|
||||
const separator = fromOptions ?? fromPayload ?? fallback;
|
||||
return COLUMN_NAME_COPY_SEPARATOR_VALUES[separator] ?? (kind === "column" ? "\t" : ",");
|
||||
}
|
||||
|
||||
export function tableReferenceInsertText(payload: QueryEditorTableReferencePayload, fallbackDatabaseType?: DatabaseType, options?: TableReferenceInsertOptions): string {
|
||||
const databaseType = payload.databaseType ?? fallbackDatabaseType;
|
||||
if (payload.referenceType === "database") {
|
||||
return quoteTableIdentifier(databaseType, payload.database);
|
||||
}
|
||||
const columnNames = payload.columnNames?.length ? payload.columnNames : payload.columnName ? [payload.columnName] : [];
|
||||
if (payload.referenceType === "column" && columnNames.length > 0) {
|
||||
return columnNames.map((name) => quoteColumnReferenceName(databaseType, name)).join(",\n");
|
||||
return columnNames.map((name) => quoteColumnReferenceName(databaseType, name)).join(separatorValue(payload, options, "column"));
|
||||
}
|
||||
const includeSchema = payload.includeTableSchema ?? options?.includeTableSchema ?? false;
|
||||
const tableSeparator = separatorValue(payload, options, "table");
|
||||
const tableReferences = payload.tableReferences?.length ? payload.tableReferences : payload.tableName ? [{ schema: payload.schema, tableName: payload.tableName }] : [];
|
||||
if (tableReferences.length > 1 || (tableReferences.length === 1 && includeSchema)) {
|
||||
return tableReferences
|
||||
.map((entry) =>
|
||||
includeSchema
|
||||
? qualifiedTableName({
|
||||
databaseType,
|
||||
driverProfile: payload.driverProfile,
|
||||
schema: entry.schema,
|
||||
tableName: entry.tableName,
|
||||
database: payload.database,
|
||||
})
|
||||
: entry.tableName,
|
||||
)
|
||||
.join(tableSeparator);
|
||||
}
|
||||
const tableName = payload.tableName || payload.database;
|
||||
return qualifiedTableName({
|
||||
|
||||
Reference in new issue
Block a user