feat(sidebar): copy MySQL object display paths

Closes #5903
This commit is contained in:
t8y2 committed 2026-08-13 14:35:09 +00:00
1 parent 34f6389503
commit 0b530145ba
12 files changed
+176 -6

No files matched your search

@@ -101,7 +101,7 @@ import {
usesTreeSchemaMode,
isSingleDatabase,
} from "@/lib/database/databaseCapabilities";
import { copyNameForTreeNode, isDirectNavigationTreeNode, isDocumentBrowserTreeNode, objectSourceTargetForTreeNode, shouldRunTreeNodeRowAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick";
import { copyDisplayPathForTreeNode, copyNameForTreeNode, isDirectNavigationTreeNode, isDocumentBrowserTreeNode, objectSourceTargetForTreeNode, shouldRunTreeNodeRowAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick";
import { customTypeCapabilities, supportsTypeObjectSource } from "@/lib/database/databaseObjectCapabilities";
import { mongoCollectionTableTypeFromNode, mongoDropIndexFailureCount } from "@/lib/sidebar/mongoCollectionMutation";
import { dataTabOpenModeFromTreeClick, type DataTabOpenMode } from "@/lib/sidebar/dataTabOpenPolicy";
@@ -1679,6 +1679,35 @@ async function copyName() {
}
}
async function copyDisplayPath() {
const node = activeNode.value;
const connectionName = node.connectionId ? connectionStore.getConfig(node.connectionId)?.name || "" : "";
const path = copyDisplayPathForTreeNode(node, connectionName);
if (!path) return;
try {
await copyToClipboard(path);
toast(t("connection.copied"), 2000);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function copyNameMenuItem(): ContextMenuItem {
const node = activeNode.value;
const connectionName = node.connectionId ? connectionStore.getConfig(node.connectionId)?.name || "" : "";
if (currentDatabaseType() === "mysql" && copyDisplayPathForTreeNode(node, connectionName)) {
return {
label: t("contextMenu.copyName"),
icon: Copy,
children: [
{ label: t("contextMenu.name"), action: copyName, icon: Copy },
{ label: t("contextMenu.fullPath"), action: copyDisplayPath, icon: Copy },
],
};
}
return { label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value };
}
async function copyCustomTypeDdl() {
const node = createSidebarActionTarget(activeNode.value);
if (!node.connectionId || !node.database) return;
@@ -4490,7 +4519,7 @@ function buildDatabaseSidebarMenu(context: SidebarMenuFactoryContext): boolean {
items.push({ label: t("contextMenu.closeDatabaseConnection"), action: closeDatabaseConnection, icon: Unplug });
items.push({ label: "", separator: true });
}
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push(copyNameMenuItem());
items.push({ label: "", separator: true });
if (canOpenObjectBrowser.value) {
items.push({ label: t("contextMenu.openObjectBrowser"), action: openObjectBrowser, icon: TableProperties });
@@ -4782,7 +4811,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
return true;
}
const destructiveActions: ContextMenuItem[] = [];
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push(copyNameMenuItem());
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.viewData"), action: openDataImmediately, icon: TableProperties });
items.push({
@@ -4929,7 +4958,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
}
if (node.type === "index" || node.type === "fkey" || (node.type === "trigger" && !!node.tableName)) {
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push(node.type === "trigger" ? copyNameMenuItem() : { label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
if (currentDatabaseType() === "xugu" && buildXuguCompileSql({ objectType: node.type, schema: node.schema, name: node.objectName || node.label })) {
items.push({ label: t("contextMenu.compileObject"), action: compileXuguObject, icon: Wrench });
}
@@ -4969,6 +4998,9 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
items.push({ label: t("contextMenu.compileObject"), action: compileXuguObject, icon: Wrench });
}
items.push({ label: t("contextMenu.viewSource"), action: () => openObjectSourceDialog(false), icon: Code2 });
if (currentDatabaseType() === "mysql") {
items.push(copyNameMenuItem());
}
if (!isPackageMember && canRenameObject.value) {
items.push({
label: t("contextMenu.renameObject"),
@@ -5011,7 +5043,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
icon: Code2,
});
}
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push(node.type === "trigger" ? copyNameMenuItem() : { label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push({ label: t("contextMenu.changeOpenMode"), action: () => emit("open-settings", "navigation"), icon: Settings2 });
return true;
}
+2
View File
@@ -2630,6 +2630,8 @@ export default {
closeAllRegularTabs: "Close All Regular Tabs",
closeAllFixedTabs: "Close All Fixed Tabs",
copyName: "Copy Name",
name: "Name",
fullPath: "Full Path",
copyFinalProxyPort: "Copy Final Proxy Port",
finalProxyPortCopied: "Final proxy port {port} copied",
setDefaultDatabase: "Set as Default Database",
+2
View File
@@ -2560,6 +2560,8 @@ export default withEnglishFallback({
closeAllRegularTabs: "Cerrar todas las pestañas normales",
closeAllFixedTabs: "Cerrar todas las pestañas fijadas",
copyName: "Copiar nombre",
name: "Nombre",
fullPath: "Ruta completa",
copyFinalProxyPort: "Copiar puerto proxy final",
finalProxyPortCopied: "Puerto proxy final {port} copiado",
setDefaultDatabase: "Establecer como base de datos predeterminada",
+2
View File
@@ -2558,6 +2558,8 @@ export default withEnglishFallback({
closeAllRegularTabs: "Chiudi Tutte le Schede Normali",
closeAllFixedTabs: "Chiudi Tutte le Schede Fisse",
copyName: "Copia Nome",
name: "Nome",
fullPath: "Percorso completo",
copyFinalProxyPort: "Copia porta proxy finale",
finalProxyPortCopied: "Porta proxy finale {port} copiata",
setDefaultDatabase: "Imposta come Database Predefinito",
+2
View File
@@ -2583,6 +2583,8 @@ export default withEnglishFallback({
closeAllRegularTabs: "すべての通常タブを閉じる",
closeAllFixedTabs: "すべての固定タブを閉じる",
copyName: "名前をコピー",
name: "名前",
fullPath: "フルパス",
copyFinalProxyPort: "最終プロキシポートをコピー",
finalProxyPortCopied: "最終プロキシポート {port} をコピーしました",
setDefaultDatabase: "デフォルトデータベースに設定",
+2
View File
@@ -2483,6 +2483,8 @@ export default withEnglishFallback({
closeAllRegularTabs: "모든 일반 탭 닫기",
closeAllFixedTabs: "모든 고정 탭 닫기",
copyName: "이름 복사",
name: "이름",
fullPath: "전체 경로",
copyFinalProxyPort: "최종 프록시 포트 복사",
finalProxyPortCopied: "최종 프록시 포트 {port} 복사됨",
setDefaultDatabase: "기본 데이터베이스로 설정",
+2
View File
@@ -2562,6 +2562,8 @@ export default withEnglishFallback({
copyFinalProxyPort: "Copiar porta final do proxy",
finalProxyPortCopied: "Porta final do proxy {port} copiada",
copyName: "Copiar Nome",
name: "Nome",
fullPath: "Caminho completo",
setDefaultDatabase: "Definir como Banco de Dados Padrão",
clearDefaultDatabase: "Limpar Banco de Dados Padrão",
setDefaultSchema: "Definir como Schema Padrão",
+2
View File
@@ -2554,6 +2554,8 @@ export default withEnglishFallback({
closeAllRegularTabs: "关闭全部普通标签页",
closeAllFixedTabs: "关闭全部固定标签页",
copyName: "复制名称",
name: "名称",
fullPath: "完整路径",
copyFinalProxyPort: "复制最终代理端口",
finalProxyPortCopied: "最终代理端口 {port} 已复制",
setDefaultDatabase: "设为默认数据库",
+2
View File
@@ -2559,6 +2559,8 @@ export default withEnglishFallback({
closeAllRegularTabs: "關閉全部普通分頁",
closeAllFixedTabs: "關閉全部固定分頁",
copyName: "複製名稱",
name: "名稱",
fullPath: "完整路徑",
copyFinalProxyPort: "複製最終代理連接埠",
finalProxyPortCopied: "最終代理連接埠 {port} 已複製",
setDefaultDatabase: "設為預設資料庫",
@@ -45,6 +45,7 @@ const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure",
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-constraints", "group-partitions", "group-table-partitions", "group-table-subpartitions"]);
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
const displayPathObjectNodeTypes = new Set<TreeNodeType>(["table", "view", "materialized_view", "procedure", "function", "trigger"]);
export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null {
if (type === "view") return "VIEW";
@@ -156,3 +157,13 @@ export function copyNameForTreeNode(node: TreeNode): string {
}
return node.label;
}
export function copyDisplayPathForTreeNode(node: TreeNode, connectionName: string): string | null {
const connection = connectionName.trim();
const database = node.database?.trim();
if (!connection || !database) return null;
if (node.type === "database") return `${connection}.${database}`;
if (!displayPathObjectNodeTypes.has(node.type)) return null;
const objectName = (node.objectName || (node.type === "table" ? node.tableName : undefined) || node.label).trim();
return objectName ? `${connection}.${database}.${objectName}` : null;
}