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
@@ -272,6 +272,7 @@ const props = defineProps<{
|
||||
prefillDatabase?: string;
|
||||
prefillSchema?: string;
|
||||
focusTableName?: string;
|
||||
focusTableNames?: string[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -395,6 +396,8 @@ const tableMap = computed(() => new Map(tables.value.map((table) => [table.name,
|
||||
const allRelationships = computed(() => buildDiagramRelationships(tables.value, customRelationships.value));
|
||||
|
||||
const relatedTableNames = computed(() => {
|
||||
const selected = props.focusTableNames?.filter((name) => name.trim());
|
||||
if (selected && selected.length > 1) return new Set(selected);
|
||||
const focus = props.focusTableName;
|
||||
const names = new Set<string>();
|
||||
if (!focus) return names;
|
||||
@@ -411,6 +414,9 @@ const visibleTables = computed(() => {
|
||||
tables.value.filter((table) => !table.pendingDrop),
|
||||
tableSearch.value,
|
||||
);
|
||||
if ((props.focusTableNames?.length ?? 0) > 1 && !showAllTables.value && !tableSearch.value.trim()) {
|
||||
return filtered.filter((table) => relatedTableNames.value.has(table.name));
|
||||
}
|
||||
if (props.focusTableName && !showAllTables.value && !tableSearch.value.trim()) {
|
||||
return filtered.filter((table) => relatedTableNames.value.has(table.name));
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ import {
|
||||
import { eventToModifierOnlyShortcut, eventToShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import { SHORTCUT_DEFINITIONS, findShortcutConflict, normalizeShortcutSettings, type ShortcutActionId } from "@/lib/editor/shortcutRegistry";
|
||||
import { formatShortcutDisplay } from "@/lib/editor/shortcutDisplay";
|
||||
import { COLUMN_NAME_COPY_SEPARATOR_LABELS, COLUMN_NAME_COPY_SEPARATOR_OPTIONS, isColumnNameCopySeparator, type ColumnNameCopySeparator } from "@/lib/dataGrid/dataGridColumnNameCopy";
|
||||
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebar/sidebarTableNameDisplay";
|
||||
import { normalizeRedisKeyTemplates } from "@/lib/redis/redisKeyTemplates";
|
||||
import { currentStatementFrameRangeTo } from "@/lib/sql/currentStatementFrame";
|
||||
@@ -524,6 +525,8 @@ const editShowTableDdlHoverPreview = ref(settingsStore.editorSettings.showTableD
|
||||
const editClickTableNavigationTarget = ref<ClickTableNavigationTarget>(settingsStore.editorSettings.clickTableNavigationTarget);
|
||||
const editUpdateNotificationsEnabled = ref(settingsStore.editorSettings.updateNotificationsEnabled);
|
||||
const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"));
|
||||
const editSidebarCopyTableNameSeparator = ref<ColumnNameCopySeparator>(settingsStore.editorSettings.sidebarCopyTableNameSeparator);
|
||||
const editSidebarCopyTableNameIncludeSchema = ref(settingsStore.editorSettings.sidebarCopyTableNameIncludeSchema);
|
||||
const editRedisKeyTemplates = ref(normalizeRedisKeyTemplates(settingsStore.editorSettings.redisKeyTemplates).join("\n"));
|
||||
const editSidebarObjectInfoMode = ref<SidebarObjectInfoMode>(settingsStore.editorSettings.sidebarObjectInfoMode);
|
||||
const editSidebarAllowHorizontalScroll = ref(settingsStore.editorSettings.sidebarAllowHorizontalScroll);
|
||||
@@ -672,6 +675,8 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
sidebarIndent: editSidebarIndent.value,
|
||||
sidebarFontSize: editSidebarFontSize.value,
|
||||
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value),
|
||||
sidebarCopyTableNameSeparator: editSidebarCopyTableNameSeparator.value,
|
||||
sidebarCopyTableNameIncludeSchema: editSidebarCopyTableNameIncludeSchema.value,
|
||||
redisKeyTemplates: normalizeRedisKeyTemplates(editRedisKeyTemplates.value),
|
||||
exportBatchSize: editExportBatchSize.value,
|
||||
globalDateTimeDisplayFormat: editGlobalDateTimeDisplayFormat.value,
|
||||
@@ -1066,6 +1071,8 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget;
|
||||
editUpdateNotificationsEnabled.value = settingsStore.editorSettings.updateNotificationsEnabled;
|
||||
editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n");
|
||||
editSidebarCopyTableNameSeparator.value = settingsStore.editorSettings.sidebarCopyTableNameSeparator;
|
||||
editSidebarCopyTableNameIncludeSchema.value = settingsStore.editorSettings.sidebarCopyTableNameIncludeSchema;
|
||||
editRedisKeyTemplates.value = normalizeRedisKeyTemplates(settingsStore.editorSettings.redisKeyTemplates).join("\n");
|
||||
editSidebarObjectInfoMode.value = settingsStore.editorSettings.sidebarObjectInfoMode;
|
||||
editSidebarAllowHorizontalScroll.value = settingsStore.editorSettings.sidebarAllowHorizontalScroll;
|
||||
@@ -1332,6 +1339,8 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editSidebarIndent.value = DEFAULT_EDITOR_SETTINGS.sidebarIndent;
|
||||
editSidebarFontSize.value = DEFAULT_EDITOR_SETTINGS.sidebarFontSize;
|
||||
editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n");
|
||||
editSidebarCopyTableNameSeparator.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameSeparator;
|
||||
editSidebarCopyTableNameIncludeSchema.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameIncludeSchema;
|
||||
editToolbarItems.value = { ...DEFAULT_EDITOR_SETTINGS.toolbarItems };
|
||||
} else if (tab === "data") {
|
||||
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
|
||||
@@ -1466,6 +1475,8 @@ function resetAllDefaults() {
|
||||
editSidebarIndent.value = DEFAULT_EDITOR_SETTINGS.sidebarIndent;
|
||||
editSidebarFontSize.value = DEFAULT_EDITOR_SETTINGS.sidebarFontSize;
|
||||
editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n");
|
||||
editSidebarCopyTableNameSeparator.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameSeparator;
|
||||
editSidebarCopyTableNameIncludeSchema.value = DEFAULT_EDITOR_SETTINGS.sidebarCopyTableNameIncludeSchema;
|
||||
editRedisKeyTemplates.value = normalizeRedisKeyTemplates(DEFAULT_EDITOR_SETTINGS.redisKeyTemplates).join("\n");
|
||||
editExportBatchSize.value = DEFAULT_EDITOR_SETTINGS.exportBatchSize;
|
||||
editGlobalDateTimeDisplayFormat.value = DEFAULT_EDITOR_SETTINGS.globalDateTimeDisplayFormat;
|
||||
@@ -5733,6 +5744,40 @@ onUnmounted(() => {
|
||||
{{ t("settings.sidebarHiddenTablePrefixesDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="sidebar-copy-table-name-separator">{{ t("settings.sidebarCopyTableNameSeparator") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.sidebarCopyTableNameSeparatorDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Select
|
||||
:model-value="editSidebarCopyTableNameSeparator"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
if (isColumnNameCopySeparator(value)) editSidebarCopyTableNameSeparator = value;
|
||||
}
|
||||
"
|
||||
>
|
||||
<SelectTrigger id="sidebar-copy-table-name-separator" class="h-8 w-44 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper" align="end">
|
||||
<SelectItem v-for="option in COLUMN_NAME_COPY_SEPARATOR_OPTIONS" :key="option" :value="option" class="font-mono text-xs">
|
||||
{{ COLUMN_NAME_COPY_SEPARATOR_LABELS[option] }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="sidebar-copy-table-name-include-schema">{{ t("settings.sidebarCopyTableNameIncludeSchema") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.sidebarCopyTableNameIncludeSchemaDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="sidebar-copy-table-name-include-schema" v-model="editSidebarCopyTableNameIncludeSchema" class="mt-0.5" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="sidebar-table-page-size">{{ t("settings.sidebarTablePageSize") }}</Label>
|
||||
|
||||
@@ -3360,7 +3360,11 @@ function hasDroppedTableReference(event: DragEvent) {
|
||||
|
||||
function insertTableReferencePayload(currentView: EditorViewType, payload: QueryEditorTableReferencePayload, coords?: { clientX: number; clientY: number }): boolean {
|
||||
if (props.readOnly) return false;
|
||||
const insertText = tableReferenceInsertText(payload, props.databaseType);
|
||||
const insertText = tableReferenceInsertText(payload, props.databaseType, {
|
||||
tableNameSeparator: settingsStore.editorSettings.sidebarCopyTableNameSeparator,
|
||||
columnNameSeparator: settingsStore.editorSettings.sidebarCopyTableNameSeparator,
|
||||
includeTableSchema: settingsStore.editorSettings.sidebarCopyTableNameIncludeSchema,
|
||||
});
|
||||
const dropPos = coords ? currentView.posAtCoords({ x: coords.clientX, y: coords.clientY }) : null;
|
||||
const selection = currentView.state.selection.main;
|
||||
const from = dropPos ?? selection.from;
|
||||
|
||||
@@ -272,6 +272,7 @@ watch(
|
||||
:prefill-database="dialogs.diagramPrefillDatabase.value"
|
||||
:prefill-schema="dialogs.diagramPrefillSchema.value"
|
||||
:focus-table-name="dialogs.diagramFocusTableName.value"
|
||||
:focus-table-names="dialogs.diagramFocusTableNames.value"
|
||||
@open-target="emit('openDiagramTarget', $event)"
|
||||
/>
|
||||
<DatabaseDocsDialog v-if="dialogs.showDocsDialog.value" v-model:open="dialogs.showDocsDialog.value" :prefill-connection-id="dialogs.docsPrefillConnectionId.value" :prefill-database="dialogs.docsPrefillDatabase.value" :prefill-schema="dialogs.docsPrefillSchema.value" />
|
||||
|
||||
@@ -26,7 +26,7 @@ const emit = defineEmits<{
|
||||
startResize: [event: MouseEvent];
|
||||
collapse: [];
|
||||
"open-settings": [initialTab: string];
|
||||
"add-to-ai": [node: TreeNode];
|
||||
"add-to-ai": [nodes: TreeNode | TreeNode[]];
|
||||
}>();
|
||||
|
||||
type ImportSource = "dbx" | "navicat" | "dbeaver" | "datagrip";
|
||||
@@ -293,7 +293,7 @@ defineExpose({ focusSearch, locateTabInSidebar });
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0">
|
||||
<ConnectionTree ref="connectionTreeRef" @open-settings="(initialTab) => emit('open-settings', initialTab)" @add-to-ai="(node) => emit('add-to-ai', node)" />
|
||||
<ConnectionTree ref="connectionTreeRef" @open-settings="(initialTab) => emit('open-settings', initialTab)" @add-to-ai="(nodes) => emit('add-to-ai', nodes)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-resize-handle panel-resize-handle--right" @mousedown="emit('startResize', $event)" />
|
||||
|
||||
@@ -29,10 +29,11 @@ import { createSidebarSearchLoadingTracker } from "@/lib/sidebar/sidebarSearchLo
|
||||
import { buildTableTreeNodes } from "@/lib/table/tableTree";
|
||||
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut, isViewTableDdlShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import { sidebarNodeSupportsDdlView } from "@/lib/sidebar/sidebarTreeDdlShortcut";
|
||||
import { copyNameForTreeNode, objectSourceTargetForTreeNode } from "@/lib/sidebar/treeNodeClick";
|
||||
import { objectSourceTargetForTreeNode } from "@/lib/sidebar/treeNodeClick";
|
||||
import { supportsTypeObjectSource } from "@/lib/database/databaseObjectCapabilities";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
import { connectionPasteTargetGroupId, copySelectedConnectionsToClipboards, selectedConnectionEditTarget } from "@/lib/sidebar/sidebarConnectionSelection";
|
||||
import { formatSidebarTableCopyText } from "@/lib/sidebar/sidebarTableNameCopy";
|
||||
import { pruneTreeSelectionToVisibleNodeIds } from "@/lib/sidebar/sidebarTreeSelection";
|
||||
import { isEditableSidebarTypeSearchTarget, sidebarTypeSearchNextQuery } from "@/lib/sidebar/sidebarTypeSearch";
|
||||
import { isInternalDorisCatalog, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
|
||||
@@ -116,7 +117,7 @@ const sidebarContextMenuRef = ref<{ close: () => void } | null>(null);
|
||||
const sidebarContextMenuItems = ref<ContextMenuItem[]>([]);
|
||||
const emit = defineEmits<{
|
||||
"open-settings": [initialTab: string];
|
||||
"add-to-ai": [node: TreeNode];
|
||||
"add-to-ai": [nodes: TreeNode | TreeNode[]];
|
||||
}>();
|
||||
|
||||
const sidebarContextMenuTarget = ref<SidebarActionTarget | null>(null);
|
||||
@@ -1959,7 +1960,7 @@ function openSidebarDdlForSelection(): boolean {
|
||||
const selectedNodeId = store.selectedTreeNodeId;
|
||||
const node = selectedNodeId ? flatTreeIndex.value.nodeById.get(selectedNodeId) : null;
|
||||
if (!node || !sidebarNodeSupportsDdlView(node)) return false;
|
||||
openSidebarDdl(node);
|
||||
void sidebarTreeRuntimeHostRef.value?.openDdlForSelection?.(node, store.selectedTreeNodeIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2335,7 +2336,17 @@ function copySelectedSidebarNames(): boolean {
|
||||
})),
|
||||
}
|
||||
: null;
|
||||
copyToClipboard(nodes.map(copyNameForTreeNode).join("\n"))
|
||||
const activeNodeId = store.selectedTreeNodeId;
|
||||
const activeNode = activeNodeId ? (flatTreeIndex.value.nodeById.get(activeNodeId) ?? nodes[0]!) : nodes[0]!;
|
||||
const config = activeNode.connectionId ? store.getConfig(activeNode.connectionId) : undefined;
|
||||
const copyText = formatSidebarTableCopyText(activeNode, nodes, {
|
||||
separator: settingsStore.editorSettings.sidebarCopyTableNameSeparator,
|
||||
includeSchema: settingsStore.editorSettings.sidebarCopyTableNameIncludeSchema,
|
||||
databaseType: activeNode.connectionId ? effectiveDatabaseTypeForConnection(config) : undefined,
|
||||
driverProfile: config?.driver_profile,
|
||||
identifierQuote: activeNode.connectionId ? store.connectionIdentifierQuote?.(activeNode.connectionId) : undefined,
|
||||
});
|
||||
copyToClipboard(copyText)
|
||||
.then(() => toast(t("connection.copied"), 2000))
|
||||
.catch((e: any) => toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000));
|
||||
return true;
|
||||
@@ -2420,7 +2431,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes, locateTabInSid
|
||||
@open-visible-schemas="openSidebarVisibleSchemas"
|
||||
@open-visible-nacos-namespaces="openSidebarVisibleNacosNamespaces"
|
||||
@open-table-name-filters="openSidebarTableNameFilters"
|
||||
@add-to-ai="(node) => emit('add-to-ai', node)"
|
||||
@add-to-ai="(nodes) => emit('add-to-ai', nodes)"
|
||||
@request-connection-rename="startRenamingConnectionNode"
|
||||
@request-group-rename="startRenamingCreatedGroup"
|
||||
@request-saved-sql-rename="startRenamingSavedSqlNode"
|
||||
|
||||
@@ -88,6 +88,7 @@ import { buildXuguSchedulerJobSql, type XuguSchedulerJobAction } from "@/lib/dat
|
||||
import { elasticsearchClearIndexPreview, isElasticsearchClearConfirmed, isElasticsearchIndexPattern } from "@/lib/sidebar/elasticsearchIndexActions";
|
||||
import { mysqlObjectTemplateForGroup } from "@/lib/sidebar/mysqlObjectTemplates";
|
||||
import { buildTableDeleteTemplate, buildTableInsertTemplate, buildTableSelectTemplate, buildTableUpdateTemplate } from "@/lib/table/tableSqlTemplates";
|
||||
import { joinExportedDdls } from "@/lib/export/ddlExport";
|
||||
import { qualifiedTableName } from "@/lib/table/tableSelectSql";
|
||||
import { driverStoreFocusForInstallError } from "@/lib/connection/agentDriverInstallHint";
|
||||
import {
|
||||
@@ -177,8 +178,10 @@ import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { batchTableEmptyFeedback, runBatchTableEmpty } from "@/lib/sidebar/batchTableEmpty";
|
||||
import { runBatchTableTruncate } from "@/lib/table/batchTableTruncate";
|
||||
import { runBatchTableDrop } from "@/lib/table/batchTableDrop";
|
||||
import { buildSidebarDdlTemplateSql, sidebarDdlTargetsForExecutionContext } from "@/lib/sidebar/sidebarDdlTemplate";
|
||||
import { sidebarStructureExportTargets } from "@/lib/sidebar/sidebarExportRuntime";
|
||||
import { buildSidebarDdlTemplateSql } from "@/lib/sidebar/sidebarDdlTemplate";
|
||||
import { resolveSidebarDdlTargets } from "@/lib/sidebar/sidebarDdlTargets";
|
||||
import { sidebarTableDataExportTargets } from "@/lib/sidebar/sidebarExportRuntime";
|
||||
import { formatSidebarTableCopyText, type FormatSidebarTableNamesOptions } from "@/lib/sidebar/sidebarTableNameCopy";
|
||||
import { supportsScheduledDatabaseBackup } from "@/lib/backup/scheduledDatabaseBackup";
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
@@ -389,6 +392,7 @@ const { openAllDatabasesExport, openDataCompare, openDatabaseExport, openDatabas
|
||||
queryStore,
|
||||
settingsStore,
|
||||
tableChildObjectName: tableChildDropObjectName,
|
||||
acceptedSelectionIds: () => acceptedSelectionIds,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -409,7 +413,7 @@ const emit = defineEmits<{
|
||||
"open-visible-schemas": [node: TreeNode];
|
||||
"open-visible-nacos-namespaces": [node: TreeNode];
|
||||
"open-table-name-filters": [node: TreeNode];
|
||||
"add-to-ai": [node: TreeNode];
|
||||
"add-to-ai": [nodes: TreeNode | TreeNode[]];
|
||||
"open-danger-dialog": [request: SidebarDangerDialogRequest];
|
||||
"open-dialog-controller": [controller: Record<string, any> | null];
|
||||
"open-install-extension": [node: TreeNode];
|
||||
@@ -637,6 +641,21 @@ function databaseTypeForNode(node: TreeNode): DatabaseType | undefined {
|
||||
return node.connectionId ? effectiveDatabaseTypeForConnection(connectionStore.getConfig(node.connectionId)) : undefined;
|
||||
}
|
||||
|
||||
function sidebarTableCopyFormatOptions(node: TreeNode = activeNode.value): FormatSidebarTableNamesOptions {
|
||||
const config = node.connectionId ? connectionStore.getConfig(node.connectionId) : undefined;
|
||||
return {
|
||||
separator: settingsStore.editorSettings.sidebarCopyTableNameSeparator,
|
||||
includeSchema: settingsStore.editorSettings.sidebarCopyTableNameIncludeSchema,
|
||||
databaseType: node.connectionId ? effectiveDatabaseTypeForConnection(config) : undefined,
|
||||
driverProfile: config?.driver_profile,
|
||||
identifierQuote: node.connectionId ? connectionStore.connectionIdentifierQuote?.(node.connectionId) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function formatSelectedTableNamesForClipboard(selectedNodes: readonly TreeNode[] = selectedTreeNodesInVisibleOrder()): string {
|
||||
return formatSidebarTableCopyText(activeNode.value, selectedNodes, sidebarTableCopyFormatOptions());
|
||||
}
|
||||
|
||||
function tableStructureDatabaseTypeForNode(node: TreeNode): DatabaseType | undefined {
|
||||
return node.connectionId ? tableStructureDatabaseTypeForConnection(connectionStore.getConfig(node.connectionId)) : undefined;
|
||||
}
|
||||
@@ -1755,8 +1774,7 @@ function openRedisInstanceInfo() {
|
||||
queryStore.createTab(node.connectionId, "0", `${dbName} - ${t("contextMenu.instanceInfo")}`, "redis-dashboard");
|
||||
}
|
||||
|
||||
async function loadTemplateContext(allowView = false) {
|
||||
const node = activeNode.value;
|
||||
async function loadTemplateContext(allowView = false, node: TreeNode = activeNode.value) {
|
||||
if (!node.connectionId || !hasTreeNodeDatabaseContext(node)) return null;
|
||||
const isTableNode = node.type === "table";
|
||||
const isReadableObject = isTableNode || (allowView && (node.type === "view" || node.type === "materialized_view"));
|
||||
@@ -1791,6 +1809,20 @@ async function loadTemplateContext(allowView = false) {
|
||||
return { node, dbType, driverProfile: config?.driver_profile, identifierQuote, tableSchema, columns, tableType };
|
||||
}
|
||||
|
||||
async function openMultiTableSqlTemplate(targets: Array<TreeNode & { connectionId: string; database: string }>, buildSql: (context: NonNullable<Awaited<ReturnType<typeof loadTemplateContext>>>) => string, allowView: boolean, titlePrefix: string) {
|
||||
const parts: string[] = [];
|
||||
const tabTarget = targets.find((target) => target.id === activeNode.value.id) ?? targets[0]!;
|
||||
for (const target of targets) {
|
||||
const context = await loadTemplateContext(allowView, target);
|
||||
if (!context) continue;
|
||||
parts.push(buildSql(context));
|
||||
}
|
||||
if (!parts.length) return;
|
||||
const sql = parts.length === 1 ? parts[0]! : joinExportedDdls(parts);
|
||||
const title = targets.length === 1 ? undefined : `${titlePrefix} - ${targets.map((target) => target.label).join(", ")}`;
|
||||
openSqlTemplateTab(tabTarget.connectionId, tabTarget.database, tabTarget.schema, tabTarget.catalog, sql, title);
|
||||
}
|
||||
|
||||
function openSqlTemplateTab(connectionId: string, database: string, schema: string | undefined, catalog: string | undefined, sql: string, title?: string) {
|
||||
const tabId = queryStore.createTab(connectionId, database, title, "query", schema, undefined, catalog, { forceWordWrap: true });
|
||||
queryStore.updateSql(tabId, sql);
|
||||
@@ -1798,6 +1830,27 @@ function openSqlTemplateTab(connectionId: string, database: string, schema: stri
|
||||
|
||||
async function newSelectTemplate() {
|
||||
try {
|
||||
const targets = selectedDdlTargets().filter((target) => target.type === "table" || target.type === "view" || target.type === "materialized_view");
|
||||
if (targets.length > 1) {
|
||||
await openMultiTableSqlTemplate(
|
||||
targets,
|
||||
(context) =>
|
||||
buildTableSelectTemplate({
|
||||
databaseType: context.dbType,
|
||||
driverProfile: context.driverProfile,
|
||||
identifierQuote: context.identifierQuote,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
}),
|
||||
true,
|
||||
"SELECT",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const context = await loadTemplateContext(true);
|
||||
if (!context) return;
|
||||
const sql = buildTableSelectTemplate({
|
||||
@@ -1819,6 +1872,28 @@ async function newSelectTemplate() {
|
||||
|
||||
async function newInsertTemplate() {
|
||||
try {
|
||||
const targets = selectedDdlTargets().filter((target) => target.type === "table");
|
||||
if (targets.length > 1) {
|
||||
await openMultiTableSqlTemplate(
|
||||
targets,
|
||||
(context) =>
|
||||
buildTableInsertTemplate({
|
||||
databaseType: context.dbType,
|
||||
driverProfile: context.driverProfile,
|
||||
identifierQuote: context.identifierQuote,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
tableType: context.tableType,
|
||||
}),
|
||||
false,
|
||||
"INSERT",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const context = await loadTemplateContext(false);
|
||||
if (!context) return;
|
||||
const sql = buildTableInsertTemplate({
|
||||
@@ -1841,6 +1916,27 @@ async function newInsertTemplate() {
|
||||
|
||||
async function newUpdateTemplate() {
|
||||
try {
|
||||
const targets = selectedDdlTargets().filter((target) => target.type === "table");
|
||||
if (targets.length > 1) {
|
||||
await openMultiTableSqlTemplate(
|
||||
targets,
|
||||
(context) =>
|
||||
buildTableUpdateTemplate({
|
||||
databaseType: context.dbType,
|
||||
driverProfile: context.driverProfile,
|
||||
identifierQuote: context.identifierQuote,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
}),
|
||||
false,
|
||||
"UPDATE",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const context = await loadTemplateContext(false);
|
||||
if (!context) return;
|
||||
const sql = buildTableUpdateTemplate({
|
||||
@@ -1862,6 +1958,27 @@ async function newUpdateTemplate() {
|
||||
|
||||
async function newDeleteTemplate() {
|
||||
try {
|
||||
const targets = selectedDdlTargets().filter((target) => target.type === "table");
|
||||
if (targets.length > 1) {
|
||||
await openMultiTableSqlTemplate(
|
||||
targets,
|
||||
(context) =>
|
||||
buildTableDeleteTemplate({
|
||||
databaseType: context.dbType,
|
||||
driverProfile: context.driverProfile,
|
||||
identifierQuote: context.identifierQuote,
|
||||
catalog: context.node.catalog,
|
||||
database: context.node.database,
|
||||
schema: context.tableSchema,
|
||||
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
|
||||
tableName: context.node.label,
|
||||
columns: context.columns,
|
||||
}),
|
||||
false,
|
||||
"DELETE",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const context = await loadTemplateContext(false);
|
||||
if (!context) return;
|
||||
const sql = buildTableDeleteTemplate({
|
||||
@@ -1881,41 +1998,44 @@ async function newDeleteTemplate() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openSidebarMultiTableDdlTab(targets: Array<TreeNode & { connectionId: string; database: string }>) {
|
||||
const tabTarget = targets.find((target) => target.id === activeNode.value.id) ?? targets[0]!;
|
||||
const sql = await buildSidebarDdlTemplateSql(
|
||||
targets,
|
||||
async (target) => {
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
const schema = target.schema || target.database;
|
||||
if (target.type === "table") return api.getTableDisplayDdl(target.connectionId, target.database, schema, target.label, undefined, target.catalog);
|
||||
if (target.type === "materialized_view") return api.getTableDisplayDdl(target.connectionId, target.database, schema, target.label, "MATERIALIZED_VIEW", target.catalog);
|
||||
const result = await api.getObjectSource(target.connectionId, target.database, schema, target.label, "VIEW");
|
||||
return buildViewDdl({
|
||||
databaseType: databaseTypeForNode(target),
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(target.connectionId),
|
||||
schema,
|
||||
name: target.label,
|
||||
source: result.source,
|
||||
});
|
||||
},
|
||||
(ddl, target) => formatSqlForDisplay(ddl, sqlFormatDialectForDbType(databaseTypeForNode(target)), settingsStore.editorSettings.sqlFormatter),
|
||||
);
|
||||
connectionStore.activeConnectionId = tabTarget.connectionId;
|
||||
const title = `DDL - ${targets.map((target) => target.label).join(", ")}`;
|
||||
openSqlTemplateTab(tabTarget.connectionId, tabTarget.database, tabTarget.schema, tabTarget.catalog, sql, title);
|
||||
}
|
||||
|
||||
async function generateDdlTemplate() {
|
||||
if (currentDatabaseType() === "victoriametrics") return;
|
||||
const targets = selectedDdlTargets();
|
||||
if (!targets.length) return;
|
||||
const tabTarget = targets.find((target) => target.id === activeNode.value.id) ?? targets[0]!;
|
||||
try {
|
||||
const sql = await buildSidebarDdlTemplateSql(
|
||||
targets,
|
||||
async (target) => {
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
const schema = target.schema || target.database;
|
||||
if (target.type === "table") return api.getTableDisplayDdl(target.connectionId, target.database, schema, target.label, undefined, target.catalog);
|
||||
if (target.type === "materialized_view") return api.getTableDisplayDdl(target.connectionId, target.database, schema, target.label, "MATERIALIZED_VIEW", target.catalog);
|
||||
const result = await api.getObjectSource(target.connectionId, target.database, schema, target.label, "VIEW");
|
||||
return buildViewDdl({
|
||||
databaseType: databaseTypeForNode(target),
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(target.connectionId),
|
||||
schema,
|
||||
name: target.label,
|
||||
source: result.source,
|
||||
});
|
||||
},
|
||||
(ddl, target) => formatSqlForDisplay(ddl, sqlFormatDialectForDbType(databaseTypeForNode(target)), settingsStore.editorSettings.sqlFormatter),
|
||||
);
|
||||
connectionStore.activeConnectionId = tabTarget.connectionId;
|
||||
const title = `DDL - ${targets.map((target) => target.label).join(", ")}`;
|
||||
openSqlTemplateTab(tabTarget.connectionId, tabTarget.database, tabTarget.schema, tabTarget.catalog, sql, title);
|
||||
await openSidebarMultiTableDdlTab(targets);
|
||||
} catch (e: any) {
|
||||
toast(e?.message || String(e), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function selectedDdlTargets() {
|
||||
const targets = sidebarStructureExportTargets(activeNode.value, connectionStore.treeNodes, acceptedSelectionIds ?? connectionStore.selectedTreeNodeIds);
|
||||
return sidebarDdlTargetsForExecutionContext(activeNode.value as TreeNode & { connectionId: string; database: string }, targets);
|
||||
return resolveSidebarDdlTargets(activeNode.value, connectionStore.treeNodes, acceptedSelectionIds ?? connectionStore.selectedTreeNodeIds);
|
||||
}
|
||||
|
||||
async function openDdl() {
|
||||
@@ -1923,12 +2043,35 @@ async function openDdl() {
|
||||
const targets = selectedDdlTargets();
|
||||
if (!targets.length) return;
|
||||
if (targets.length > 1) {
|
||||
await exportStructure();
|
||||
try {
|
||||
await openSidebarMultiTableDdlTab(targets);
|
||||
} catch (e: any) {
|
||||
toast(e?.message || String(e), 5000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
emit("open-ddl", targets[0]!);
|
||||
}
|
||||
|
||||
async function openDdlForSelection(node: TreeNode, selectedNodeIds: readonly string[]): Promise<boolean> {
|
||||
if (node.type !== "table" && node.type !== "view" && node.type !== "materialized_view") return false;
|
||||
activateRuntimeNode(node);
|
||||
const dbType = node.connectionId ? effectiveDatabaseTypeForConnection(connectionStore.getConfig(node.connectionId)) : undefined;
|
||||
if (dbType === "victoriametrics") return false;
|
||||
const targets = resolveSidebarDdlTargets(node, connectionStore.treeNodes, selectedNodeIds);
|
||||
if (!targets.length) return false;
|
||||
if (targets.length > 1) {
|
||||
try {
|
||||
await openSidebarMultiTableDdlTab(targets);
|
||||
} catch (e: any) {
|
||||
toast(e?.message || String(e), 5000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
emit("open-ddl", targets[0]!);
|
||||
return true;
|
||||
}
|
||||
|
||||
function openElasticsearchIndexMetadata(kind: ElasticsearchIndexMetadataKind) {
|
||||
emit("open-elasticsearch-index-metadata", createSidebarActionTarget(activeNode.value), kind);
|
||||
}
|
||||
@@ -1944,9 +2087,8 @@ async function refresh() {
|
||||
}
|
||||
|
||||
async function copyName() {
|
||||
const node = activeNode.value;
|
||||
try {
|
||||
await copyToClipboard(copyNameForTreeNode(node));
|
||||
await copyToClipboard(formatSelectedTableNamesForClipboard());
|
||||
toast(t("connection.copied"), 2000);
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
@@ -2038,7 +2180,7 @@ async function copySelectedNames() {
|
||||
}
|
||||
updateTreeClipboardForNodes(nodes);
|
||||
try {
|
||||
await copyToClipboard(nodes.map(copyNameForTreeNode).join("\n"));
|
||||
await copyToClipboard(formatSelectedTableNamesForClipboard(selectedNodes));
|
||||
toast(t("connection.copied"), 2000);
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
@@ -4844,7 +4986,26 @@ const shortcutRefresh = "F5";
|
||||
|
||||
const shortcutDelete = "Delete";
|
||||
|
||||
function selectedDataExportTargetCount(): number {
|
||||
return sidebarTableDataExportTargets(activeNode.value, connectionStore.treeNodes, acceptedSelectionIds ?? connectionStore.selectedTreeNodeIds).length;
|
||||
}
|
||||
|
||||
function selectedAiTableTargets(): Array<TreeNode & { connectionId: string; database: string }> {
|
||||
return resolveSidebarDdlTargets(activeNode.value, connectionStore.treeNodes, acceptedSelectionIds ?? connectionStore.selectedTreeNodeIds).filter((target) => target.type === "table");
|
||||
}
|
||||
|
||||
function addToAiMenuItem(node: TreeNode): ContextMenuItem {
|
||||
const tables = node.type === "table" ? selectedAiTableTargets() : [];
|
||||
const count = tables.length;
|
||||
return {
|
||||
label: count > 1 ? t("contextMenu.addToAiMultiple", { count }) : t("contextMenu.addToAi"),
|
||||
action: () => emit("add-to-ai", count > 1 ? tables : node),
|
||||
icon: Sparkles,
|
||||
};
|
||||
}
|
||||
|
||||
function exportDataSubmenu(includeSqlInsert = true): ContextMenuItem {
|
||||
const count = selectedDataExportTargetCount();
|
||||
const children: ContextMenuItem[] = [
|
||||
{ label: "CSV", action: () => exportData("csv") },
|
||||
{ label: "JSON", action: () => exportData("json") },
|
||||
@@ -4852,7 +5013,7 @@ function exportDataSubmenu(includeSqlInsert = true): ContextMenuItem {
|
||||
if (includeSqlInsert) children.push({ label: "SQL INSERT", action: () => exportData("sql") });
|
||||
children.push({ label: "XLSX", action: () => exportDataXlsx() });
|
||||
return {
|
||||
label: t("contextMenu.exportData"),
|
||||
label: count > 1 ? t("contextMenu.exportDataMultiple", { count }) : t("contextMenu.exportData"),
|
||||
icon: Upload,
|
||||
children,
|
||||
};
|
||||
@@ -4974,7 +5135,7 @@ function buildConnectionSidebarMenu(context: SidebarMenuFactoryContext): boolean
|
||||
if (supportsQueryActions) {
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
if (supportsAiContext) {
|
||||
items.push({ label: t("contextMenu.addToAi"), action: () => emit("add-to-ai", node), icon: Sparkles });
|
||||
items.push(addToAiMenuItem(node));
|
||||
}
|
||||
}
|
||||
const connectionWorkspace = node.connectionId ? driverProfileDatabaseWorkspace(connectionStore.getConfig(node.connectionId)?.driver_profile) : undefined;
|
||||
@@ -5190,7 +5351,7 @@ function buildDatabaseSidebarMenu(context: SidebarMenuFactoryContext): boolean {
|
||||
if (supportsConnectionQueryActions(currentDatabaseType())) {
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
if (node.type === "database" && supportsAiAssistantContext(currentDatabaseType())) {
|
||||
items.push({ label: t("contextMenu.addToAi"), action: () => emit("add-to-ai", node), icon: Sparkles });
|
||||
items.push(addToAiMenuItem(node));
|
||||
}
|
||||
const sqlHistoryMenu = savedSqlHistorySubmenu();
|
||||
if (sqlHistoryMenu) items.push(sqlHistoryMenu);
|
||||
@@ -5524,7 +5685,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
|
||||
items.push({ label: t("contextMenu.openInNewDataTab"), action: openDataInNewTabImmediately, icon: CopyPlus });
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
if (supportsAiAssistantContext(currentDatabaseType())) {
|
||||
items.push({ label: t("contextMenu.addToAi"), action: () => emit("add-to-ai", node), icon: Sparkles });
|
||||
items.push(addToAiMenuItem(node));
|
||||
}
|
||||
items.push({ label: "", separator: true });
|
||||
items.push(exportDataSubmenu(false));
|
||||
@@ -5535,7 +5696,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
|
||||
items.push(copyNameMenuItem());
|
||||
items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare });
|
||||
if (node.type === "table" && supportsAiAssistantContext(currentDatabaseType())) {
|
||||
items.push({ label: t("contextMenu.addToAi"), action: () => emit("add-to-ai", node), icon: Sparkles });
|
||||
items.push(addToAiMenuItem(node));
|
||||
}
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({ label: t("contextMenu.viewData"), action: openDataImmediately, icon: TableProperties });
|
||||
@@ -6088,6 +6249,7 @@ defineExpose({
|
||||
handleRowKeydown,
|
||||
openPrimaryVisibleFilter,
|
||||
openDataInNewTab,
|
||||
openDdlForSelection,
|
||||
requestPaste,
|
||||
toggleNode,
|
||||
});
|
||||
|
||||
@@ -53,7 +53,17 @@ import { Switch } from "@/components/ui/switch";
|
||||
import LightTooltip from "@/components/ui/LightTooltip.vue";
|
||||
import type { ColumnInfo, ConnectionConfig, CustomTypeTreeMemberMeta, DatabaseType, TreeNode, TriggerInfo } from "@/types/database";
|
||||
import { alignedCommentLeadingWidth, canTreeNodePin, canTreeNodeShowExpander, sidebarTreeNodeComment, trailingCommentAvailableWidth, trailingCommentGapPx, treeItemPaddingLeft, treeLabelWidthClass, usesFullWidthTreeLabel } from "@/lib/sidebar/sidebarTreeItemLayout";
|
||||
import { clearActiveTableReferencePayload, createTableReferenceDragEndEvent, createTableReferenceDropEvent, createTableReferenceHoverEvent, createTableReferencePayload, setActiveTableReferencePayload, type QueryEditorTableReferencePayload } from "@/lib/editor/queryEditorTableDrop";
|
||||
import {
|
||||
clearActiveTableReferencePayload,
|
||||
createColumnReferencePayload,
|
||||
createMultiTableReferencePayload,
|
||||
createTableReferenceDragEndEvent,
|
||||
createTableReferenceDropEvent,
|
||||
createTableReferenceHoverEvent,
|
||||
createTableReferencePayload,
|
||||
setActiveTableReferencePayload,
|
||||
type QueryEditorTableReferencePayload,
|
||||
} from "@/lib/editor/queryEditorTableDrop";
|
||||
import { AI_ASSISTANT_TABLE_DROP_ROOT_SELECTOR } from "@/lib/ai/aiTableReferenceDrop";
|
||||
import { beginTableReferenceDragFeedback, isOverSqlEditorTarget, type TableReferenceDragFeedback } from "@/lib/editor/tableReferenceDragFeedback";
|
||||
import { formatSidebarObjectStorage } from "@/lib/sidebar/sidebarDatabaseStorage";
|
||||
@@ -64,7 +74,8 @@ import { encodeSpannerResourcePath } from "@/lib/connection/spannerResourcePath"
|
||||
import { hexToRgba } from "@/lib/common/color";
|
||||
import { sidebarDisplayTableName } from "@/lib/sidebar/sidebarTableNameDisplay";
|
||||
import { shouldMeasureSidebarLabelOverflow } from "@/lib/sidebar/sidebarLabelTooltip";
|
||||
import { filterSidebarModifierSelectionIds, supportsSidebarModifierSelection, treeSelectionRangeIdsByIndex, treeSelectionRangeIds } from "@/lib/sidebar/sidebarTreeSelection";
|
||||
import { filterSidebarModifierSelectionIds, selectedTreeNodesInVisibleOrder as orderSelectedTreeNodes, supportsSidebarModifierSelection, treeSelectionRangeIdsByIndex, treeSelectionRangeIds } from "@/lib/sidebar/sidebarTreeSelection";
|
||||
import { resolveSidebarColumnDragNames, resolveSidebarTableCopyTargets, type SidebarTableCopyTarget } from "@/lib/sidebar/sidebarTableNameCopy";
|
||||
import { applyConnectionMultiSelection, applyTreeNodeSelection, connectionMultiSelectionAfterToggle } from "@/lib/sidebar/sidebarConnectionMultiSelect";
|
||||
import { connectionBearingGroupIdsUnder, connectionIdsUnderGroup } from "@/lib/sidebar/sidebarLayout";
|
||||
import { isSidebarDatabaseOpenForVisual } from "@/lib/sidebar/sidebarDatabaseOpenState";
|
||||
@@ -1172,13 +1183,31 @@ let referenceDragFeedback: TableReferenceDragFeedback | null = null;
|
||||
|
||||
let suppressNextTableReferenceClick = false;
|
||||
|
||||
function selectedTreeNodesInVisibleOrder(): TreeNode[] {
|
||||
return orderSelectedTreeNodes(visibleTreeNodes(), connectionStore.selectedTreeNodeIds);
|
||||
}
|
||||
|
||||
function tableReferenceDragLabel(payload: QueryEditorTableReferencePayload): string {
|
||||
if (payload.referenceType === "column" && payload.columnName) return payload.columnName;
|
||||
if (payload.referenceType === "column") {
|
||||
const names = payload.columnNames?.length ? payload.columnNames : payload.columnName ? [payload.columnName] : [];
|
||||
if (names.length <= 3) return names.join(", ");
|
||||
return t("grid.columnDragChipMany", { names: names.slice(0, 2).join(", "), count: names.length });
|
||||
}
|
||||
const tableNames = payload.tableReferences?.map((entry) => entry.tableName) ?? (payload.tableName ? [payload.tableName] : []);
|
||||
if (tableNames.length <= 3) return tableNames.join(", ");
|
||||
if (tableNames.length > 1) return t("grid.columnDragChipMany", { names: tableNames.slice(0, 2).join(", "), count: tableNames.length });
|
||||
return payload.tableName || payload.database;
|
||||
}
|
||||
|
||||
function tableReferenceDragPayload(): QueryEditorTableReferencePayload | null {
|
||||
if (!canDragTableReference.value) return null;
|
||||
const selectedNodes = selectedTreeNodesInVisibleOrder();
|
||||
const tableCopyOptions = {
|
||||
tableNameSeparator: settingsStore.editorSettings.sidebarCopyTableNameSeparator,
|
||||
includeTableSchema: settingsStore.editorSettings.sidebarCopyTableNameIncludeSchema,
|
||||
databaseType: currentDatabaseType(),
|
||||
driverProfile: currentDriverProfile(),
|
||||
};
|
||||
if (activeNode.value.type === "database") {
|
||||
return createTableReferencePayload({
|
||||
connectionId: activeNode.value.connectionId,
|
||||
@@ -1189,16 +1218,24 @@ function tableReferenceDragPayload(): QueryEditorTableReferencePayload | null {
|
||||
});
|
||||
}
|
||||
if (activeNode.value.type === "column") {
|
||||
const columnName = columnNameForDrag(activeNode.value);
|
||||
if (!activeNode.value.tableName || !columnName) return null;
|
||||
return createTableReferencePayload({
|
||||
const columnNames = resolveSidebarColumnDragNames(activeNode.value, selectedNodes);
|
||||
if (!activeNode.value.connectionId || activeNode.value.database == null || columnNames.length === 0) return null;
|
||||
return createColumnReferencePayload({
|
||||
connectionId: activeNode.value.connectionId,
|
||||
database: activeNode.value.database,
|
||||
schema: activeNode.value.schema,
|
||||
tableName: activeNode.value.tableName,
|
||||
columnName,
|
||||
columnNames,
|
||||
databaseType: currentDatabaseType(),
|
||||
driverProfile: currentDriverProfile(),
|
||||
columnNameSeparator: settingsStore.editorSettings.sidebarCopyTableNameSeparator,
|
||||
});
|
||||
}
|
||||
const tableTargets = resolveSidebarTableCopyTargets(activeNode.value, selectedNodes);
|
||||
if (tableTargets.length > 1) {
|
||||
return createMultiTableReferencePayload({
|
||||
connectionId: activeNode.value.connectionId,
|
||||
database: activeNode.value.database,
|
||||
tableReferences: tableTargets.map((target: SidebarTableCopyTarget) => ({ schema: target.schema, tableName: target.label })),
|
||||
...tableCopyOptions,
|
||||
});
|
||||
}
|
||||
const payload = createTableReferencePayload({
|
||||
@@ -1209,15 +1246,13 @@ function tableReferenceDragPayload(): QueryEditorTableReferencePayload | null {
|
||||
databaseType: currentDatabaseType(),
|
||||
driverProfile: currentDriverProfile(),
|
||||
});
|
||||
if (payload && tableCopyOptions.includeTableSchema) {
|
||||
payload.includeTableSchema = true;
|
||||
payload.tableNameSeparator = tableCopyOptions.tableNameSeparator;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function columnNameForDrag(node: TreeNode): string {
|
||||
const column = node.meta as Partial<ColumnInfo> | undefined;
|
||||
if (typeof column?.name === "string" && column.name) return column.name;
|
||||
return node.label.replace(/\s+\([^()]*\)$/, "");
|
||||
}
|
||||
|
||||
function startTableReferenceDrag(payload: QueryEditorTableReferencePayload) {
|
||||
draggingTableReferencePayload = payload;
|
||||
setActiveTableReferencePayload(payload);
|
||||
|
||||
+54
@@ -45,6 +45,8 @@ vi.mock("@/stores/settingsStore", () => ({
|
||||
sidebarAllowHorizontalScroll: false,
|
||||
sidebarHiddenTablePrefixes: [],
|
||||
sidebarObjectInfoMode: "none",
|
||||
sidebarCopyTableNameSeparator: "comma",
|
||||
sidebarCopyTableNameIncludeSchema: false,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
@@ -147,4 +149,56 @@ describe("TreeItem table reference dragging", () => {
|
||||
|
||||
expect(onDrop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("drags multiple selected columns from the same table", async () => {
|
||||
mockEditorDropTarget();
|
||||
const onDrop = listenForTableReferenceDrop();
|
||||
const columnA: TreeNode = {
|
||||
id: "column-id",
|
||||
label: "id (INTEGER)",
|
||||
type: "column",
|
||||
connectionId: "connection-1",
|
||||
database: "main",
|
||||
schema: "main",
|
||||
tableName: "orders",
|
||||
meta: { name: "id" },
|
||||
};
|
||||
const columnB: TreeNode = {
|
||||
id: "column-name",
|
||||
label: "name (TEXT)",
|
||||
type: "column",
|
||||
connectionId: "connection-1",
|
||||
database: "main",
|
||||
schema: "main",
|
||||
tableName: "orders",
|
||||
meta: { name: "name" },
|
||||
};
|
||||
connectionStore.selectedTreeNodeIds = [columnA.id, columnB.id];
|
||||
connectionStore.selectedTreeNodeIdsSet = new Set([columnA.id, columnB.id]);
|
||||
connectionStore.selectedTreeNodeId = columnA.id;
|
||||
connectionStore.treeNodes = [columnA, columnB];
|
||||
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
const app = createApp(
|
||||
defineComponent({
|
||||
setup: () => () => h(TreeItem, { node: columnA, depth: 3 }),
|
||||
}),
|
||||
);
|
||||
mountedApps.push(app);
|
||||
app.use(i18n);
|
||||
app.provide(sidebarTreeRuntimeKey, createSidebarTreeRuntime());
|
||||
app.mount(container);
|
||||
await nextTick();
|
||||
|
||||
const row = container.querySelector<HTMLElement>("[tabindex]");
|
||||
if (!row) throw new Error("Tree item row was not rendered");
|
||||
dragToEditor(row);
|
||||
|
||||
expect(onDrop).toHaveBeenCalledOnce();
|
||||
const detail = (onDrop.mock.calls[0][0] as CustomEvent<QueryEditorTableReferenceDropDetail>).detail;
|
||||
expect(detail.payload.referenceType).toBe("column");
|
||||
expect(detail.payload.columnNames).toEqual(["id", "name"]);
|
||||
expect(detail.payload.columnNameSeparator).toBe("comma");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user