feat(objects): open database objects in side panel

This commit is contained in:
gggaiitx authored and GitHub committed 2026-07-11 12:48:57 +08:00
1 parent ce7739fcda
commit 5bcd24df1c
5 files changed
+1101 -245

No files matched your search

@@ -0,0 +1,62 @@
import type { ObjectBrowserRow } from "@/lib/table/objectBrowserRows";
export type ObjectBrowserRowAction = "table-info" | "open-table" | "open-source" | "none";
/**
* Determine the action for a single click on an object browser row.
* - TABLE → table-info (show table properties panel)
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/SEQUENCE/PACKAGE/PACKAGE_BODY → open-source
* - otherwise → none
*/
export function singleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
if (!row) return "none";
if (row.type === "TABLE") return "table-info";
if (canOpenSource(row)) return "open-source";
return "none";
}
/**
* Determine the action for a double click on an object browser row.
* - TABLE → open-table (open table data tab)
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/SEQUENCE/PACKAGE/PACKAGE_BODY → open-source
* - otherwise → none
*/
export function doubleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
if (!row) return "none";
if (row.type === "TABLE") return "open-table";
if (canOpenSource(row)) return "open-source";
return "none";
}
/**
* Resolve a row click event into a single or double action based on click detail
* and the sidebar activation setting.
*/
export function resolveRowClickAction(row: ObjectBrowserRow | null | undefined, detail: number, activation: "single" | "double"): { action: ObjectBrowserRowAction; isDouble: boolean } {
if (activation === "double") {
if (detail === 2) return { action: doubleClickRowAction(row), isDouble: true };
return { action: "none", isDouble: false };
}
// single-click activation
if (detail > 1) return { action: doubleClickRowAction(row), isDouble: true };
return { action: singleClickRowAction(row), isDouble: false };
}
/**
* Whether a single-click action should be deferred to distinguish it from a
* possible upcoming double-click. Only applies in single-click activation mode
* and when the row's single-click and double-click actions differ (e.g. TABLE:
* single → table-info, double → open-table). For rows whose single and double
* actions are identical (e.g. VIEW → open-source both), no deferral is needed.
*/
export function shouldDeferSingleClick(row: ObjectBrowserRow | null | undefined, action: ObjectBrowserRowAction, activation: "single" | "double"): boolean {
if (activation !== "single") return false;
if (action === "none") return false;
const single = singleClickRowAction(row);
const double = doubleClickRowAction(row);
return single !== double && action === single;
}
function canOpenSource(row: ObjectBrowserRow): boolean {
return row.type === "VIEW" || row.type === "MATERIALIZED_VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
}
@@ -0,0 +1,33 @@
/**
* Side panel async request guard.
*
* Prevents stale async results (from a previous object or database context)
* from overwriting the current panel state. Each context change bumps the
* epoch; in-flight requests capture the epoch at start and compare before
* writing results.
*/
export interface SidePanelRequestGuard {
/** Bump the epoch, invalidating all previously captured epochs. */
bump: () => void;
/** Start a new request context and return its epoch. */
start: () => number;
/** Capture the current epoch for later staleness comparison. */
capture: () => number;
/** Returns true if the captured epoch is no longer current (request is stale). */
isStale: (capturedEpoch: number) => boolean;
/** Returns true if the captured epoch is still current (request is fresh). */
isFresh: (capturedEpoch: number) => boolean;
}
export function createSidePanelRequestGuard(): SidePanelRequestGuard {
let epoch = 0;
return {
bump: () => {
epoch++;
},
start: () => ++epoch,
capture: () => epoch,
isStale: (capturedEpoch: number) => capturedEpoch !== epoch,
isFresh: (capturedEpoch: number) => capturedEpoch === epoch,
};
}