feat: support Oracle package sources

This commit is contained in:
t8y2 committed 2026-06-01 18:43:20 +08:00
1 parent 76378f3a65
commit 7eca75b325
22 files changed
+429 -36

No files matched your search

@@ -24,6 +24,7 @@ import {
PencilLine,
PencilRuler,
Play,
Package,
RefreshCw,
Scissors,
Search,
@@ -89,7 +90,7 @@ import {
type ObjectBrowserSortKey,
} from "@/lib/objectBrowserRows";
type ObjectFilter = "all" | "tables" | "views" | "procedures" | "functions";
type ObjectFilter = "all" | "tables" | "views" | "procedures" | "functions" | "packages";
const props = defineProps<{
connection: ConnectionConfig;
@@ -158,6 +159,9 @@ const tableCount = computed(() => rows.value.filter((row) => row.type === "TABLE
const viewCount = computed(() => rows.value.filter((row) => row.type === "VIEW").length);
const procedureCount = computed(() => rows.value.filter((row) => row.type === "PROCEDURE").length);
const functionCount = computed(() => rows.value.filter((row) => row.type === "FUNCTION").length);
const packageCount = computed(
() => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length,
);
const canOpenStructureEditor = computed(() => supportsTableStructureEditing(props.connection.db_type));
const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram(props.connection.db_type));
const canOpenTableImport = computed(() => !!props.database && supportsTableImport(props.connection.db_type));
@@ -186,6 +190,7 @@ const objectFilters = computed<ObjectFilter[]>(() =>
["views", viewCount.value],
["procedures", procedureCount.value],
["functions", functionCount.value],
["packages", packageCount.value],
] as Array<[ObjectFilter, number]>
)
.filter(([filter, count]) => filter === "all" || count > 0)
@@ -230,6 +235,7 @@ function iconFor(row: ObjectBrowserRow) {
if (row.type === "VIEW") return Eye;
if (row.type === "PROCEDURE") return ScrollText;
if (row.type === "FUNCTION") return Braces;
if (row.type === "PACKAGE" || row.type === "PACKAGE_BODY") return Package;
return Table2;
}
@@ -237,6 +243,8 @@ function typeLabel(type: ObjectBrowserRow["type"]) {
if (type === "VIEW") return t("objects.view");
if (type === "PROCEDURE") return t("objects.procedure");
if (type === "FUNCTION") return t("objects.function");
if (type === "PACKAGE") return t("objects.package");
if (type === "PACKAGE_BODY") return t("objects.packageBody");
return t("objects.table");
}
@@ -259,6 +267,7 @@ function rowMatchesObjectFilter(row: ObjectBrowserRow) {
if (objectFilter.value === "views") return row.type === "VIEW";
if (objectFilter.value === "procedures") return row.type === "PROCEDURE";
if (objectFilter.value === "functions") return row.type === "FUNCTION";
if (objectFilter.value === "packages") return row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
return true;
}
@@ -298,6 +307,7 @@ function iconClass(type: ObjectBrowserRow["type"]) {
if (type === "VIEW") return "text-purple-500";
if (type === "PROCEDURE") return "text-blue-500";
if (type === "FUNCTION") return "text-amber-500";
if (type === "PACKAGE" || type === "PACKAGE_BODY") return "text-cyan-500";
return "text-green-500";
}
@@ -314,7 +324,13 @@ function togglePartitionParent(row: ObjectBrowserRow) {
}
function canOpenSource(row: ObjectBrowserRow) {
return row.type === "VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION";
return (
row.type === "VIEW" ||
row.type === "PROCEDURE" ||
row.type === "FUNCTION" ||
row.type === "PACKAGE" ||
row.type === "PACKAGE_BODY"
);
}
function canRename(row: ObjectBrowserRow) {
@@ -1063,6 +1079,7 @@ function filterCount(filter: ObjectFilter) {
if (filter === "views") return viewCount.value;
if (filter === "procedures") return procedureCount.value;
if (filter === "functions") return functionCount.value;
if (filter === "packages") return packageCount.value;
return rows.value.length;
}
@@ -1076,7 +1093,9 @@ function filterLabel(filter: ObjectFilter) {
? "objects.procedures"
: filter === "functions"
? "objects.functions"
: "objects.all";
: filter === "packages"
? "objects.packages"
: "objects.all";
return `${t(key)} ${filterCount(filter)}`;
}
@@ -1224,9 +1243,18 @@ function getProcFuncMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
];
}
function getPackageMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
return [
{ label: t("contextMenu.viewSource"), action: () => openSource(item), icon: Code2 },
{ label: "", separator: true },
{ label: t("contextMenu.copyName"), action: () => copyName(item), icon: Copy },
];
}
function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
if (item.type === "TABLE") return getTableMenuItems(item);
if (item.type === "VIEW") return getViewMenuItems(item);
if (item.type === "PACKAGE" || item.type === "PACKAGE_BODY") return getPackageMenuItems(item);
return getProcFuncMenuItems(item);
}
</script>
@@ -45,6 +45,7 @@ import {
Braces,
Code2,
ListFilter,
Package,
} from "lucide-vue-next";
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
import { useConnectionStore } from "@/stores/connectionStore";
@@ -223,6 +224,10 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: ScrollText, colorClass: "text-blue-500" };
case "function":
return { icon: Braces, colorClass: "text-amber-500" };
case "package":
return { icon: Package, colorClass: "text-cyan-500" };
case "package-body":
return { icon: FileCode, colorClass: "text-cyan-400" };
case "group-tables":
return { icon: Table, colorClass: "text-green-500" };
case "group-views":
@@ -231,6 +236,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: ScrollText, colorClass: "text-blue-500" };
case "group-functions":
return { icon: Braces, colorClass: "text-amber-500" };
case "group-packages":
return { icon: Package, colorClass: "text-cyan-500" };
case "group-partitions":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
default:
@@ -247,6 +254,7 @@ const groupTypes: Set<TreeNodeType> = new Set([
"group-views",
"group-procedures",
"group-functions",
"group-packages",
"group-partitions",
"saved-sql-root",
"saved-sql-folder",
@@ -307,6 +315,7 @@ async function toggle() {
node.type === "group-views" ||
node.type === "group-procedures" ||
node.type === "group-functions" ||
node.type === "group-packages" ||
node.type === "group-partitions"
) {
node.isExpanded = !node.isExpanded;
@@ -400,7 +409,12 @@ function runRowClickAction() {
const action = treeNodeRowAction(node.type, canExpand.value, settingsStore.editorSettings.sidebarActivation);
if (action === "open-data") {
openData();
} else if (node.type === "procedure" || node.type === "function") {
} else if (
node.type === "procedure" ||
node.type === "function" ||
node.type === "package" ||
node.type === "package-body"
) {
void viewObjectSource();
} else if (node.type === "saved-sql-file") {
openSavedSqlFile();
@@ -1628,6 +1642,8 @@ const hasTypeMenu = computed(() => {
t === "column" ||
t === "procedure" ||
t === "function" ||
t === "package" ||
t === "package-body" ||
t === "saved-sql-root" ||
t === "saved-sql-folder" ||
t === "saved-sql-file" ||
@@ -2224,7 +2240,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
return items;
}
// 8. Procedure / Function
// 8. Procedure / Function / Package
if (node.type === "procedure" || node.type === "function") {
if (node.type === "procedure") {
items.push({ label: t("contextMenu.executeProcedure"), action: openProcedureExecution, icon: Play });
@@ -2243,6 +2259,13 @@ function treeItemMenuItems(): ContextMenuItem[] {
return items;
}
if (node.type === "package" || node.type === "package-body") {
items.push({ label: t("contextMenu.viewSource"), action: viewObjectSource, icon: Code2 });
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy });
return items;
}
// 9. Group Labels (saved-sql-root, saved-sql-folder, group-columns, etc.)
if (isGroupLabel(node)) {
if (node.type === "saved-sql-root") {
@@ -2369,6 +2392,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
node.type === 'group-views' ||
node.type === 'group-procedures' ||
node.type === 'group-functions' ||
node.type === 'group-packages' ||
node.type === 'group-partitions') &&
node.objectCount != null
"