fix(sidebar): focus object search after opening a database
This commit is contained in:
6 files changed
+117
-10
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { ArrowDown, ArrowUp, Check, Database, GripVertical, LayoutGrid, List, Loader2, RefreshCw, Search, X } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -10,6 +10,7 @@ import { useToolbarOverflow } from "@/composables/useToolbarOverflow";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { filterDatabaseNamesForConnection } from "@/lib/database/visibleDatabases";
|
||||
import { formatObjectBrowserBytes, formatObjectBrowserTimestamp } from "@/lib/table/objectBrowserRows";
|
||||
import { requestObjectBrowserSearchFocus } from "@/lib/tabs/objectBrowserSearchFocus";
|
||||
import { useTabUiState } from "@/lib/tabs/tabUiState";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
@@ -112,7 +113,8 @@ const visibleRows = computed(() => {
|
||||
});
|
||||
|
||||
function openDatabase(database: string) {
|
||||
queryStore.openObjectBrowser(props.connection.id, database);
|
||||
const tabId = queryStore.openObjectBrowser(props.connection.id, database);
|
||||
void nextTick(() => requestObjectBrowserSearchFocus(tabId));
|
||||
}
|
||||
|
||||
function columnLabel(key: DatabaseBrowserColumnKey): string {
|
||||
|
||||
@@ -86,6 +86,7 @@ import { supportsSidebarObjectNameFilter } from "@/lib/sidebar/sidebarObjectName
|
||||
import { connectionGroupDestinationRows } from "@/lib/sidebar/sidebarLayout";
|
||||
import { objectTypesForGroupNode } from "@/lib/table/tableTree";
|
||||
import { loadSidebarObjectGroup } from "@/lib/sidebar/sidebarObjectGroupRouting";
|
||||
import { requestObjectBrowserSearchFocus } from "@/lib/tabs/objectBrowserSearchFocus";
|
||||
import { isXuguTypeMemberContainer } from "@/lib/sidebar/xuguTypeMembers";
|
||||
import { isXuguSyntheticTreeNode } from "@/lib/sidebar/xuguPublicSynonyms";
|
||||
import { buildXuguSchedulerJobSql, type XuguSchedulerJobAction } from "@/lib/database/xuguSchedulerJobSql";
|
||||
@@ -1112,9 +1113,9 @@ function runRowClickAction(clickDetail: number, requestId: number) {
|
||||
if (action === "open-data") {
|
||||
scheduleOpenData(node);
|
||||
} else if (action === "open-object-browser") {
|
||||
void openObjectBrowser();
|
||||
void openObjectBrowser(false, false, true);
|
||||
} else if (action === "open-object-browser-and-expand") {
|
||||
void openObjectBrowser();
|
||||
void openObjectBrowser(false, false, true);
|
||||
if (!node.isExpanded) void toggle();
|
||||
} else if (action === "open-source") {
|
||||
openObjectSourceDialog(false);
|
||||
@@ -1500,9 +1501,9 @@ function onDoubleClick(event: MouseEvent) {
|
||||
if (action === "open-database-browser") {
|
||||
void openDatabaseBrowser();
|
||||
} else if (action === "open-object-browser") {
|
||||
void openObjectBrowser();
|
||||
void openObjectBrowser(false, false, true);
|
||||
} else if (action === "open-object-browser-and-expand") {
|
||||
void openObjectBrowser();
|
||||
void openObjectBrowser(false, false, true);
|
||||
if (!activeNode.value.isExpanded) void toggle();
|
||||
} else if (action === "open-data") {
|
||||
openDataImmediately(activeNode.value);
|
||||
@@ -1649,7 +1650,7 @@ async function confirmDeleteSavedSqlFile() {
|
||||
releaseActiveNodeReference([node.id]);
|
||||
}
|
||||
|
||||
async function openObjectBrowser(eventReadOnly = false, openEventEditor: boolean | "create" = false) {
|
||||
async function openObjectBrowser(eventReadOnly = false, openEventEditor: boolean | "create" = false, focusSearch = false) {
|
||||
const node = activeNode.value;
|
||||
if (!node.connectionId) return;
|
||||
try {
|
||||
@@ -1667,13 +1668,15 @@ async function openObjectBrowser(eventReadOnly = false, openEventEditor: boolean
|
||||
return;
|
||||
}
|
||||
if (hasTreeNodeDatabaseContext(node)) {
|
||||
queryStore.openObjectBrowser(node.connectionId, node.database, node.schema, node.catalog, eventName, eventReadOnly, objectFilter, eventCreateRequestId);
|
||||
const tabId = queryStore.openObjectBrowser(node.connectionId, node.database, node.schema, node.catalog, eventName, eventReadOnly, objectFilter, eventCreateRequestId);
|
||||
if (focusSearch) await nextTick(() => requestObjectBrowserSearchFocus(tabId));
|
||||
return;
|
||||
}
|
||||
const options = await getDatabaseOptions(node.connectionId);
|
||||
const database = resolveDefaultDatabase(connection, options);
|
||||
if (database) {
|
||||
queryStore.openObjectBrowser(node.connectionId, database, undefined, undefined, eventName, eventReadOnly, objectFilter, eventCreateRequestId);
|
||||
const tabId = queryStore.openObjectBrowser(node.connectionId, database, undefined, undefined, eventName, eventReadOnly, objectFilter, eventCreateRequestId);
|
||||
if (focusSearch) await nextTick(() => requestObjectBrowserSearchFocus(tabId));
|
||||
} else {
|
||||
await toggle();
|
||||
}
|
||||
|
||||
+49
-1
@@ -6,6 +6,7 @@ import i18n from "@/i18n";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
import { syncSidebarTreeNodeExpansion } from "@/lib/sidebar/sidebarTreeExpansion";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { OBJECT_BROWSER_SEARCH_FOCUS_EVENT } from "@/lib/tabs/objectBrowserSearchFocus";
|
||||
import SidebarTreeRuntimeHost from "@/components/sidebar/SidebarTreeRuntimeHost.vue";
|
||||
|
||||
const connectionStore = {
|
||||
@@ -36,6 +37,15 @@ const queryStore = {
|
||||
updateSql: vi.fn(),
|
||||
setTableMeta: vi.fn(),
|
||||
openNacosAdmin: vi.fn(),
|
||||
openObjectBrowser: vi.fn(() => "object-browser-tab"),
|
||||
};
|
||||
|
||||
const settingsStore = {
|
||||
editorSettings: {
|
||||
sidebarActivation: "single" as "single" | "double",
|
||||
sidebarBrowseObjectsOnDatabaseActivation: false,
|
||||
shortcuts: { openDataInNewTab: "" },
|
||||
},
|
||||
};
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
@@ -44,7 +54,7 @@ vi.mock("@/stores/connectionStore", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/queryStore", () => ({ useQueryStore: () => queryStore }));
|
||||
vi.mock("@/stores/settingsStore", () => ({ useSettingsStore: () => ({ editorSettings: { sidebarActivation: "single" } }) }));
|
||||
vi.mock("@/stores/settingsStore", () => ({ useSettingsStore: () => settingsStore }));
|
||||
vi.mock("@/stores/savedSqlStore", () => ({ useSavedSqlStore: () => ({}) }));
|
||||
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: vi.fn() }) }));
|
||||
vi.mock("@/composables/useSqlHighlighter", () => ({ useSqlHighlighter: () => ({ highlight: vi.fn() }) }));
|
||||
@@ -66,6 +76,8 @@ afterEach(() => {
|
||||
connectionStore.activeConnectionId = null;
|
||||
connectionStore.connectedIds.clear();
|
||||
queryStore.tabs = [];
|
||||
settingsStore.editorSettings.sidebarActivation = "single";
|
||||
settingsStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation = false;
|
||||
vi.clearAllMocks();
|
||||
connectionStore.canUseLoadedTreeNodeToggle.mockReturnValue(true);
|
||||
connectionStore.getConfig.mockReturnValue({ db_type: "mysql", name: "connection" });
|
||||
@@ -73,6 +85,42 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("SidebarTreeRuntimeHost expansion", () => {
|
||||
it.each(["single", "double"] as const)("focuses object search when %s-click activation opens a database", async (activation) => {
|
||||
const database: TreeNode = {
|
||||
id: "mysql:dbx_test",
|
||||
label: "dbx_test",
|
||||
type: "database",
|
||||
connectionId: "mysql",
|
||||
database: "dbx_test",
|
||||
isExpanded: true,
|
||||
children: [],
|
||||
};
|
||||
settingsStore.editorSettings.sidebarActivation = activation;
|
||||
settingsStore.editorSettings.sidebarBrowseObjectsOnDatabaseActivation = true;
|
||||
const focusRequested = vi.fn();
|
||||
window.addEventListener(OBJECT_BROWSER_SEARCH_FOCUS_EVENT, focusRequested);
|
||||
|
||||
const host = ref<InstanceType<typeof SidebarTreeRuntimeHost> | null>(null);
|
||||
const app = createApp(defineComponent({ setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: database, depth: 0 }) }));
|
||||
mountedApps.push(app);
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
app.use(i18n);
|
||||
app.mount(container);
|
||||
|
||||
if (activation === "single") {
|
||||
host.value?.handleRowClick(database, 1);
|
||||
} else {
|
||||
host.value?.handleRowDoubleClick(database, new MouseEvent("dblclick"));
|
||||
}
|
||||
|
||||
await vi.waitFor(() => expect(queryStore.openObjectBrowser).toHaveBeenCalledWith("mysql", "dbx_test", undefined, undefined, undefined, false, undefined, undefined));
|
||||
await vi.waitFor(() => expect(focusRequested).toHaveBeenCalledOnce());
|
||||
expect((focusRequested.mock.calls[0]?.[0] as CustomEvent<{ tabId: string }>).detail.tabId).toBe("object-browser-tab");
|
||||
|
||||
window.removeEventListener(OBJECT_BROWSER_SEARCH_FOCUS_EVENT, focusRequested);
|
||||
});
|
||||
|
||||
it("reuses a Mongo collection tab by identity without replacing its state", async () => {
|
||||
const collection: TreeNode = {
|
||||
id: "mongo:app:orders",
|
||||
|
||||
Reference in new issue
Block a user