refactor(grid): extract local filter value logic

This commit is contained in:
t8y2 committed 2026-09-08 21:12:04 +08:00
1 parent 52f23e3c79
commit 92f052fa6c
3 files changed
+110 -46

No files matched your search

+11 -45
View File
@@ -377,7 +377,7 @@ import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout";
import { buildColumnIndexMap, columnIndexColorClass, columnIndexMetadataRequestCurrent, columnIndexNameKey, columnIndexTableIdentity, type ColumnIndexKind } from "@/lib/dataGrid/dataGridColumnIndexIcon";
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
import { buildDataGridLocalFilterOptions, dataGridLocalFilterKey, dataGridLocalFilterLabel, restoreDataGridLocalColumnFilters, rowMatchesDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters, type DataGridLocalFilterOption } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
import { effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint } from "@/lib/database/jdbcDialect";
import { mongoCollectionSupportsIndexes, supportsMongoIndexMutations } from "@/lib/mongo/mongoCapabilities";
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
@@ -1061,12 +1061,7 @@ const conditionHistoryScope = computed(() => ({
tableName: props.tableMeta?.tableName,
}));
type LocalFilterMode = "local" | "server";
type LocalFilterOption = {
key: string;
label: string;
count: number | null;
value: CellValue;
};
type LocalFilterOption = DataGridLocalFilterOption;
type LocalColumnFilterDraft = {
columnIndex: number;
@@ -1300,14 +1295,11 @@ const savedCustomFormatters = computed(() => {
});
function localFilterKey(value: CellValue): string {
if (value === null) return "__dbx_null__";
if (typeof value === "boolean") return `bool:${value}`;
if (typeof value === "number") return `num:${value}`;
return `str:${String(value)}`;
return dataGridLocalFilterKey(value);
}
function localFilterLabel(value: CellValue, columnIndex: number): string {
return value === null ? "NULL" : formatCellCached(value, columnIndex);
return dataGridLocalFilterLabel(value, columnIndex, formatCellCached);
}
function localFilterActive(colIdx: number): boolean {
@@ -1345,9 +1337,7 @@ const localFilterSummaries = computed(() =>
);
function rowMatchesLocalColumnFilters(data: CellValue[]): boolean {
const activeEntries = Object.entries(localColumnFilters.value).filter(([, selected]) => selected.size > 0);
if (activeEntries.length === 0) return true;
return activeEntries.every(([columnIndex, selected]) => selected.has(localFilterKey(data[Number(columnIndex)] ?? null)));
return rowMatchesDataGridLocalColumnFilters(data, localColumnFilters.value);
}
const localFilteredRows = computed(() => {
@@ -1366,36 +1356,12 @@ const localFilteredRows = computed(() => {
});
function buildLocalFilterOptions(columnIndex: number): LocalFilterOption[] {
const byKey = new Map<string, LocalFilterOption>();
const addValue = (value: CellValue) => {
const key = localFilterKey(value);
const current = byKey.get(key);
if (current) {
current.count = (current.count ?? 0) + 1;
} else {
byKey.set(key, {
key,
label: localFilterLabel(value, columnIndex),
count: 1,
value,
});
}
};
for (const [sourceIndex, row] of props.result.rows.entries()) {
addValue(rowDataWithChanges(row, sourceIndex)[columnIndex] ?? null);
}
for (const row of newRows.value) {
addValue(row[columnIndex] ?? null);
}
return [...byKey.values()].sort((a, b) => {
if (a.value === null && b.value !== null) return -1;
if (a.value !== null && b.value === null) return 1;
return a.label.localeCompare(b.label, undefined, {
numeric: true,
sensitivity: "base",
});
return buildDataGridLocalFilterOptions({
rows: props.result.rows,
newRows: newRows.value,
columnIndex,
getRowData: rowDataWithChanges,
formatValue: formatCellCached,
});
}
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
import { buildDataGridLocalFilterOptions, dataGridLocalFilterKey, restoreDataGridLocalColumnFilters, rowMatchesDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
describe("data grid local column filter state", () => {
it("round-trips selected values so a remounted grid restores its filters", () => {
@@ -27,4 +27,30 @@ describe("data grid local column filter state", () => {
expect(restored).toEqual({});
});
it("builds stable keys and filters rows across multiple columns", () => {
expect(dataGridLocalFilterKey(null)).toBe("__dbx_null__");
expect(dataGridLocalFilterKey(true)).toBe("bool:true");
expect(dataGridLocalFilterKey(42)).toBe("num:42");
expect(dataGridLocalFilterKey("active")).toBe("str:active");
expect(rowMatchesDataGridLocalColumnFilters(["active", 42], { 0: new Set(["str:active"]), 1: new Set(["num:42"]) })).toBe(true);
expect(rowMatchesDataGridLocalColumnFilters(["active", 7], { 0: new Set(["str:active"]), 1: new Set(["num:42"]) })).toBe(false);
});
it("deduplicates local filter options and sorts null first", () => {
const options = buildDataGridLocalFilterOptions({
rows: [{ values: ["beta"] }, { values: [null] }, { values: ["beta"] }],
newRows: [["alpha"]],
columnIndex: 0,
getRowData: (row) => row.values,
formatValue: (value) => String(value),
});
expect(options).toEqual([
{ key: "__dbx_null__", label: "NULL", count: 1, value: null },
{ key: "str:alpha", label: "alpha", count: 1, value: "alpha" },
{ key: "str:beta", label: "beta", count: 2, value: "beta" },
]);
});
});
@@ -1,5 +1,77 @@
import type { CellValue } from "./cellValue";
export type SerializedDataGridLocalColumnFilters = Record<string, string[]>;
export type DataGridLocalFilterOption = {
key: string;
label: string;
count: number | null;
value: CellValue;
};
export function dataGridLocalFilterKey(value: CellValue): string {
if (value === null) return "__dbx_null__";
if (typeof value === "boolean") return `bool:${value}`;
if (typeof value === "number") return `num:${value}`;
return `str:${String(value)}`;
}
export function dataGridLocalFilterLabel(value: CellValue, columnIndex: number, formatValue: (value: CellValue, columnIndex: number) => string): string {
return value === null ? "NULL" : formatValue(value, columnIndex);
}
export function rowMatchesDataGridLocalColumnFilters(data: readonly CellValue[], filters: Readonly<Record<number, ReadonlySet<string>>>): boolean {
const activeEntries = Object.entries(filters).filter(([, selected]) => selected.size > 0);
if (activeEntries.length === 0) return true;
return activeEntries.every(([columnIndex, selected]) => selected.has(dataGridLocalFilterKey(data[Number(columnIndex)] ?? null)));
}
export function buildDataGridLocalFilterOptions<TRow>({
rows,
newRows,
columnIndex,
getRowData,
formatValue,
}: {
rows: readonly TRow[];
newRows: readonly (readonly CellValue[])[];
columnIndex: number;
getRowData: (row: TRow, sourceIndex: number) => readonly CellValue[];
formatValue: (value: CellValue, columnIndex: number) => string;
}): DataGridLocalFilterOption[] {
const byKey = new Map<string, DataGridLocalFilterOption>();
const addValue = (value: CellValue) => {
const key = dataGridLocalFilterKey(value);
const current = byKey.get(key);
if (current) {
current.count = (current.count ?? 0) + 1;
} else {
byKey.set(key, {
key,
label: dataGridLocalFilterLabel(value, columnIndex, formatValue),
count: 1,
value,
});
}
};
for (const [sourceIndex, row] of rows.entries()) {
addValue(getRowData(row, sourceIndex)[columnIndex] ?? null);
}
for (const row of newRows) {
addValue(row[columnIndex] ?? null);
}
return [...byKey.values()].sort((a, b) => {
if (a.value === null && b.value !== null) return -1;
if (a.value !== null && b.value === null) return 1;
return a.label.localeCompare(b.label, undefined, {
numeric: true,
sensitivity: "base",
});
});
}
export function restoreDataGridLocalColumnFilters(serialized: SerializedDataGridLocalColumnFilters | undefined, columnCount: number): Record<number, Set<string>> {
if (!serialized || typeof serialized !== "object") return {};