diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index e97507024b..d40844ed7b 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -341,6 +341,7 @@ const showColumnCommentsInHeader = computed(() => settingsStore.editorSettings.s const showColumnTypesInHeader = computed(() => settingsStore.editorSettings.showColumnTypesInHeader); const compactColumnHeaderActions = computed(() => settingsStore.editorSettings.compactColumnHeaderActions); const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode); +const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode); const compactDataGridToolbar = computed(() => dataGridTopbarWidth.value > 0 && dataGridTopbarWidth.value < DATA_GRID_COMPACT_TOPBAR_WIDTH); const infiniteScrollEnabled = computed(() => settingsStore.editorSettings.infiniteScroll); const infiniteScrollMaxRows = computed(() => settingsStore.editorSettings.infiniteScrollMaxRows); @@ -3926,7 +3927,8 @@ function addRow() { const sortedRows = computed(() => { let indices = localFilteredRows.value; const q = deferredClientSearchText.value; - if (q) { + if (q && dataGridSearchMode.value === "filter") { + // Preserve the legacy Ctrl+F behavior when the user chooses row filtering. const rows = props.result.rows; indices = indices.filter((sourceIndex) => { const data = rows[sourceIndex]; @@ -4186,7 +4188,7 @@ const exportContextCell = computed(() => { const deleteRowDetails = computed(() => (props.tableMeta?.tableName ? t("dangerDialog.deleteRowDetails", { table: props.tableMeta.tableName }) : t("dangerDialog.deleteRowDetailsNoTable"))); const hasVisibleRows = computed(() => displayRowCount.value > 0); -const hasActiveFilter = computed(() => !!deferredClientSearchText.value || rowStatusFilter.value !== "all" || hasLocalColumnFilters.value); +const hasActiveFilter = computed(() => (dataGridSearchMode.value === "filter" && !!deferredClientSearchText.value) || rowStatusFilter.value !== "all" || hasLocalColumnFilters.value); const emptyTitle = computed(() => (hasActiveFilter.value ? t("grid.noFilteredRows") : t("grid.noRows"))); const emptyDescription = computed(() => (hasActiveFilter.value ? t("grid.noFilteredRowsDescription") : t("grid.noRowsDescription"))); watch( diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index aab9bc3e66..5ac9871433 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -51,7 +51,7 @@ const ExplainPlanViewer = defineAsyncComponent(() => import("@/components/explai const QueryChart = defineAsyncComponent(() => import("@/components/chart/QueryChart.vue")); import { useQueryStore } from "@/stores/queryStore"; import { useConnectionStore } from "@/stores/connectionStore"; -import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore } from "@/stores/settingsStore"; +import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore, type DataGridSearchMode } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/queryExecutionState"; import { databaseDisplayNameForTab, executionSummaryItems, nextExecutionSummaryView, resultGridCacheKey, resultRunItems, tabularResultItems } from "@/lib/tabPresentation"; @@ -180,6 +180,7 @@ const resultTabsScrollerRef = ref(null); const columnVisibilitySearch = ref(""); const columnVisibilityOptions = computed(() => dataGridRef.value?.filteredColumnVisibilityOptions(columnVisibilitySearch.value) ?? []); const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode); +const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode); const tableFontSize = computed(() => settingsStore.editorSettings.tableFontSize); const redisKeyBrowserRef = ref(); @@ -209,6 +210,10 @@ function setDataGridRenderMode(value: "canvas" | "dom") { settingsStore.updateEditorSettings({ dataGridRenderMode: value }); } +function setDataGridSearchMode(value: DataGridSearchMode) { + settingsStore.updateEditorSettings({ dataGridSearchMode: value }); +} + function setTableFontSize(value: number) { settingsStore.updateEditorSettings({ tableFontSize: value }); } @@ -801,7 +806,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
{{ t("grid.viewOptions") }}
-
+
{{ t("grid.renderMode") }} @@ -810,7 +815,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
-
+
A {{ t("grid.tableFontSize") }}
-
+
-
+
+
+ + {{ t("grid.searchMode") }} +
+ +
+ + +
+
+
+
{{ t("grid.transposeMultiRowToggle") }} @@ -863,7 +894,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
-
+
{{ t("grid.hideNullColumns") }} @@ -1115,7 +1146,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
{{ t("grid.viewOptions") }}
-
+
{{ t("grid.renderMode") }} @@ -1124,7 +1155,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
-
+
A {{ t("grid.tableFontSize") }}
-
+
-
+
+
+ + {{ t("grid.searchMode") }} +
+ +
+ + +
+
+
+
{{ t("grid.transposeMultiRowToggle") }} @@ -1177,7 +1234,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
-
+
{{ t("grid.hideNullColumns") }} diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index d853a99e65..8691efd5a6 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -761,6 +761,10 @@ export default { viewOptions: "View options", hideNullColumns: "Hide NULL", hideNullColumnsHint: "Toggle columns whose values are all NULL in the current result.", + searchMode: "Search mode", + searchModeHint: "Choose whether Ctrl+F filters matching rows or keeps all rows visible and highlights matches.", + searchModeFilter: "Filter", + searchModeHighlight: "Highlight", moreValues: "{count} more values, keep typing to narrow results", filterByValue: "Filter by This Value", filterExcludeValue: "Exclude This Value", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 753fb32329..1363345178 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -763,6 +763,10 @@ export default withEnglishFallback({ viewOptions: "视图选项", hideNullColumns: "隐藏 NULL 列", hideNullColumnsHint: "切换隐藏当前结果中整列均为 NULL 的字段。", + searchMode: "搜索模式", + searchModeHint: "选择 Ctrl+F 搜索时过滤匹配行,或保留全部行并高亮定位命中项。", + searchModeFilter: "过滤", + searchModeHighlight: "高亮", moreValues: "还有 {count} 个值,输入关键词继续缩小范围", filterByValue: "筛选此值", filterExcludeValue: "排除此值", diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 34d512c13f..5edacea695 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -50,4 +50,10 @@ describe("normalizeEditorSettings", () => { expect(normalizeEditorSettings({ updateDownloadSource: "atomgit" }).updateDownloadSource).toBe("atomgit"); expect(normalizeEditorSettings({ updateDownloadSource: "mirror" as any }).updateDownloadSource).toBe("official"); }); + + it("defaults data grid search to row filtering and preserves highlight mode", () => { + expect(normalizeEditorSettings({}).dataGridSearchMode).toBe("filter"); + expect(normalizeEditorSettings({ dataGridSearchMode: "highlight" }).dataGridSearchMode).toBe("highlight"); + expect(normalizeEditorSettings({ dataGridSearchMode: "invalid" as any }).dataGridSearchMode).toBe("filter"); + }); }); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 064e3c51ee..7106197799 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -257,6 +257,8 @@ const CELL_DETAIL_PANEL_LAYOUTS = ["bottom", "right"] as const; export type CellDetailPanelLayout = (typeof CELL_DETAIL_PANEL_LAYOUTS)[number]; const DATA_GRID_RENDER_MODES = ["dom", "canvas"] as const; export type DataGridRenderMode = (typeof DATA_GRID_RENDER_MODES)[number]; +const DATA_GRID_SEARCH_MODES = ["filter", "highlight"] as const; +export type DataGridSearchMode = (typeof DATA_GRID_SEARCH_MODES)[number]; export const TABLE_FONT_SIZE_MIN = 12; export const TABLE_FONT_SIZE_MAX = 16; export const TABLE_FONT_SIZE_DEFAULT = 13; @@ -351,6 +353,7 @@ export interface EditorSettings { compactColumnHeaderActions: boolean; dataGridQuickEntry: boolean; dataGridRenderMode: DataGridRenderMode; + dataGridSearchMode: DataGridSearchMode; tableFontSize: number; structureEditorDensity: StructureEditorDensity; tableInfoDrawerWidth: number; @@ -464,6 +467,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { compactColumnHeaderActions: true, dataGridQuickEntry: false, dataGridRenderMode: "canvas", + dataGridSearchMode: "filter", tableFontSize: TABLE_FONT_SIZE_DEFAULT, structureEditorDensity: "compact", tableInfoDrawerWidth: 320, @@ -529,6 +533,10 @@ function normalizeDataGridRenderMode(value: unknown): DataGridRenderMode { return DATA_GRID_RENDER_MODES.includes(value as DataGridRenderMode) ? (value as DataGridRenderMode) : DEFAULT_EDITOR_SETTINGS.dataGridRenderMode; } +function normalizeDataGridSearchMode(value: unknown): DataGridSearchMode { + return DATA_GRID_SEARCH_MODES.includes(value as DataGridSearchMode) ? (value as DataGridSearchMode) : DEFAULT_EDITOR_SETTINGS.dataGridSearchMode; +} + function normalizeTableFontSize(value: unknown): number { if (typeof value !== "number" || !Number.isFinite(value)) return TABLE_FONT_SIZE_DEFAULT; return Math.min(TABLE_FONT_SIZE_MAX, Math.max(TABLE_FONT_SIZE_MIN, Math.round(value))); @@ -679,6 +687,7 @@ export function normalizeEditorSettings(settings: Partial, exist compactColumnHeaderActions: settings.compactColumnHeaderActions ?? DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions, dataGridQuickEntry: settings.dataGridQuickEntry ?? DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry, dataGridRenderMode: normalizeDataGridRenderMode(settings.dataGridRenderMode), + dataGridSearchMode: normalizeDataGridSearchMode(settings.dataGridSearchMode), tableFontSize: normalizeTableFontSize(settings.tableFontSize), structureEditorDensity: normalizeStructureEditorDensity(settings.structureEditorDensity), tableInfoDrawerWidth: normalizeDrawerWidth(settings.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth), @@ -901,6 +910,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.compactColumnHeaderActions !== undefined) editorSettings.value.compactColumnHeaderActions = partial.compactColumnHeaderActions; if (partial.dataGridQuickEntry !== undefined) editorSettings.value.dataGridQuickEntry = partial.dataGridQuickEntry; if (partial.dataGridRenderMode !== undefined) editorSettings.value.dataGridRenderMode = normalizeDataGridRenderMode(partial.dataGridRenderMode); + if (partial.dataGridSearchMode !== undefined) editorSettings.value.dataGridSearchMode = normalizeDataGridSearchMode(partial.dataGridSearchMode); if (partial.tableFontSize !== undefined) editorSettings.value.tableFontSize = normalizeTableFontSize(partial.tableFontSize); if (partial.structureEditorDensity !== undefined) editorSettings.value.structureEditorDensity = normalizeStructureEditorDensity(partial.structureEditorDensity); if (partial.tableInfoDrawerWidth !== undefined) editorSettings.value.tableInfoDrawerWidth = normalizeDrawerWidth(partial.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth);