feat(grid): keep filter editor expanded

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-09-09 12:04:32 +08:00
1 parent 08edb60772
commit 004b27a3c2
19 files changed
+157 -45

No files matched your search

@@ -505,7 +505,7 @@ const editShowIndexIndicatorsInHeader = ref(settingsStore.editorSettings.showInd
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEntry);
const editDataGridFilterEditorView = ref<DataGridFilterEditorView>(settingsStore.editorSettings.dataGridFilterEditorView);
const editDataGridAutoHideFilterBuilder = ref(settingsStore.editorSettings.dataGridAutoHideFilterBuilder);
const editDataGridKeepFilterEditorExpanded = ref(settingsStore.editorSettings.dataGridKeepFilterEditorExpanded);
const dataGridFilterViewPreviewExpanded = ref(true);
const editDataGridTextFilterPanelHeight = ref(settingsStore.editorSettings.dataGridTextFilterPanelHeight);
const editMultiStatementDefaultView = ref<MultiStatementDefaultView>(settingsStore.editorSettings.multiStatementDefaultView);
@@ -731,7 +731,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
dataGridQuickEntry: editDataGridQuickEntry.value,
dataGridFilterEditorView: editDataGridFilterEditorView.value,
dataGridAutoHideFilterBuilder: editDataGridAutoHideFilterBuilder.value,
dataGridKeepFilterEditorExpanded: editDataGridKeepFilterEditorExpanded.value,
dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight.value,
multiStatementDefaultView: editMultiStatementDefaultView.value,
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow.value,
@@ -1228,7 +1228,7 @@ function syncEditorSettingsDraftFromStore() {
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
editDataGridQuickEntry.value = settingsStore.editorSettings.dataGridQuickEntry;
editDataGridFilterEditorView.value = settingsStore.editorSettings.dataGridFilterEditorView;
editDataGridAutoHideFilterBuilder.value = settingsStore.editorSettings.dataGridAutoHideFilterBuilder;
editDataGridKeepFilterEditorExpanded.value = settingsStore.editorSettings.dataGridKeepFilterEditorExpanded;
editDataGridTextFilterPanelHeight.value = settingsStore.editorSettings.dataGridTextFilterPanelHeight;
editMultiStatementDefaultView.value = settingsStore.editorSettings.multiStatementDefaultView;
editDataGridAutoTransposeSingleRow.value = settingsStore.editorSettings.dataGridAutoTransposeSingleRow;
@@ -1344,7 +1344,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
compactColumnHeaderActions: editCompactColumnHeaderActions,
dataGridQuickEntry: editDataGridQuickEntry,
dataGridFilterEditorView: editDataGridFilterEditorView,
dataGridAutoHideFilterBuilder: editDataGridAutoHideFilterBuilder,
dataGridKeepFilterEditorExpanded: editDataGridKeepFilterEditorExpanded,
dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight,
multiStatementDefaultView: editMultiStatementDefaultView,
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow,
@@ -1710,7 +1710,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
editDataGridAutoHideFilterBuilder.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoHideFilterBuilder;
editDataGridKeepFilterEditorExpanded.value = DEFAULT_EDITOR_SETTINGS.dataGridKeepFilterEditorExpanded;
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
editMultiStatementDefaultView.value = DEFAULT_EDITOR_SETTINGS.multiStatementDefaultView;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
@@ -1798,7 +1798,7 @@ function resetAllDefaults() {
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
editDataGridAutoHideFilterBuilder.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoHideFilterBuilder;
editDataGridKeepFilterEditorExpanded.value = DEFAULT_EDITOR_SETTINGS.dataGridKeepFilterEditorExpanded;
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
editMultiStatementDefaultView.value = DEFAULT_EDITOR_SETTINGS.multiStatementDefaultView;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
@@ -6631,12 +6631,12 @@ onUnmounted(() => {
<span class="truncate">{{ t("grid.filterTextView") }}</span>
</Button>
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-background px-3 py-2">
<div v-if="editDataGridFilterEditorView !== 'quick'" class="flex items-center justify-between gap-4 rounded-md border bg-background px-3 py-2">
<div class="space-y-1">
<Label for="data-grid-auto-hide-filter-builder">{{ t("settings.dataGridAutoHideFilterBuilder") }}</Label>
<p class="text-xs text-muted-foreground">{{ t("settings.dataGridAutoHideFilterBuilderDescription") }}</p>
<Label for="data-grid-keep-filter-editor-expanded">{{ t("settings.dataGridKeepFilterEditorExpanded") }}</Label>
<p class="text-xs text-muted-foreground">{{ t("settings.dataGridKeepFilterEditorExpandedDescription") }}</p>
</div>
<Switch id="data-grid-auto-hide-filter-builder" v-model="editDataGridAutoHideFilterBuilder" />
<Switch id="data-grid-keep-filter-editor-expanded" v-model="editDataGridKeepFilterEditorExpanded" />
</div>
</div>
@@ -75,6 +75,16 @@ function expectClassTokens(className: string, tokens: readonly string[]) {
}
describe("EditorSettingsDialog choice card containment", () => {
it("shows the persistent filter-editor preference only for fixed filter views", () => {
const key = 't("settings.dataGridKeepFilterEditorExpanded")';
const keyIndex = templateSource.indexOf(key);
const configurationStart = templateSource.indexOf("<div v-if=\"editDataGridFilterEditorView !== 'quick'\"");
expect(keyIndex).toBeGreaterThan(-1);
expect(configurationStart).toBeGreaterThan(-1);
expect(configurationStart).toBeLessThan(keyIndex);
expect(templateSource).not.toContain("dataGridAutoHideFilterBuilder");
});
it("overrides the shared Button nowrap contract on two- and three-column cards", () => {
for (const key of affectedChoiceKeys) {
const cardClass = classNameFromTag(openingTag(buttonBlockForKey(key), "Button"));
+13 -4
View File
@@ -1199,6 +1199,15 @@ const filteredFilterBuilderColumnOptions = filterBuilder.filteredColumns;
const appliedStructuredWhereInput = filterBuilder.appliedWhereInput;
const draftStructuredWhereInput = ref("");
const filterEditorView = computed(() => settingsStore.editorSettings.dataGridFilterEditorView);
const isPersistentFilterView = computed(() => filterEditorView.value === "conditions" || filterEditorView.value === "text");
const isFilterEditorPinnedOpen = computed(() => isPersistentFilterView.value && settingsStore.editorSettings.dataGridKeepFilterEditorExpanded);
const effectiveFilterBuilderOpen = computed({
get: () => isFilterEditorPinnedOpen.value || filterBuilderOpen.value,
set: (open: boolean) => {
if (isFilterEditorPinnedOpen.value) return;
filterBuilderOpen.value = open;
},
});
const structuredFilterCount = computed(() => structuredFilterRules.value.filter((rule) => !rule.disabled && !!rule.columnName && filterModeHasCompleteValue(rule.mode, rule.rawValue, rule.rawEndValue)).length);
const hasStructuredFilters = computed(() => !!combineWhereInputs(undefined, appliedStructuredWhereInput.value));
interface ForeignKeyDisplayLabelState {
@@ -1553,7 +1562,7 @@ function buildGroupedWhere(conditions: string[], rules: StructuredFilterRule[]):
async function applyStructuredFilters() {
if (!canUseWhereSearch.value) return;
appliedStructuredWhereInput.value = await buildStructuredWhereFromRules(structuredFilterRules.value);
if (settingsStore.editorSettings.dataGridAutoHideFilterBuilder) filterBuilderOpen.value = false;
if (!isFilterEditorPinnedOpen.value) filterBuilderOpen.value = false;
await applyWhereFilter();
}
@@ -11127,7 +11136,7 @@ function openGridSnapshot() {
<DataGridQueryControls
v-model:where-input="whereFilterInput"
v-model:order-by-input="orderByInput"
v-model:filter-builder-open="filterBuilderOpen"
v-model:filter-builder-open="effectiveFilterBuilderOpen"
:filter-editor-view="filterEditorView"
:columns="props.tableMeta?.columns.map((column) => column.name) ?? props.result.columns"
:condition-columns="conditionColumns"
@@ -11278,7 +11287,7 @@ function openGridSnapshot() {
</DataGridToolbar>
</div>
<DataGridFilterWorkbench
v-if="canUseWhereSearch && filterEditorView === 'conditions' && filterBuilderOpen"
v-if="canUseWhereSearch && filterEditorView === 'conditions' && effectiveFilterBuilderOpen"
:sql-preview="filterSqlPreview"
:rules="structuredFilterRules"
:columns="filterBuilderColumnOptions"
@@ -11298,7 +11307,7 @@ function openGridSnapshot() {
@update-rule="updateStructuredFilterRule"
/>
<DataGridTextFilterWorkbench
v-if="canUseWhereSearch && filterEditorView === 'text' && filterBuilderOpen"
v-if="canUseWhereSearch && filterEditorView === 'text' && effectiveFilterBuilderOpen"
:height="settingsStore.editorSettings.dataGridTextFilterPanelHeight"
:sql-preview="filterSqlPreview"
:rules="structuredFilterRules"
@@ -138,6 +138,16 @@ beforeEach(() => {
localStorage.removeItem("dbx-filter-builder-value-shortcut-hint-days");
});
describe("DataGrid canvas surfaces", () => {
it("keeps condition and text filter editors open when persistent expansion is enabled", () => {
expect(dataGridSource).toContain('const isPersistentFilterView = computed(() => filterEditorView.value === "conditions" || filterEditorView.value === "text");');
expect(dataGridSource).toContain("const isFilterEditorPinnedOpen = computed(() => isPersistentFilterView.value && settingsStore.editorSettings.dataGridKeepFilterEditorExpanded);");
expect(dataGridSource).toContain("get: () => isFilterEditorPinnedOpen.value || filterBuilderOpen.value,");
expect(dataGridSource).toContain("if (!isFilterEditorPinnedOpen.value) filterBuilderOpen.value = false;");
expect(dataGridSource).toContain('v-model:filter-builder-open="effectiveFilterBuilderOpen"');
expect(dataGridSource).toContain("filterEditorView === 'conditions' && effectiveFilterBuilderOpen");
expect(dataGridSource).toContain("filterEditorView === 'text' && effectiveFilterBuilderOpen");
});
it("asks before an expensive Elasticsearch cursor jump", () => {
expect(dataGridSource).toContain("requestCount >= ELASTICSEARCH_PAGE_JUMP_WARNING_REQUESTS");
expect(dataGridSource).toContain('t("grid.esDeepPageJumpConfirmMessage"');
+2 -2
View File
@@ -6737,8 +6737,8 @@ export default {
dataGridDisplay: "Data grid display",
dataGridFilterView: "Table filter view",
dataGridFilterViewDescription: "Use a filter popover, a fixed conditions panel, or a compact text conditions panel.",
dataGridAutoHideFilterBuilder: "Automatically hide filter editor after applying",
dataGridAutoHideFilterBuilderDescription: "When disabled, the filter editor stays open after applying so you can continue editing.",
dataGridKeepFilterEditorExpanded: "Keep filter editor expanded",
dataGridKeepFilterEditorExpandedDescription: "Always keep the editor expanded in Conditions and Text views.",
dataGridFilterViewPreview: "Effect preview",
dataGridFilterViewPreviewExpand: "Expand effect preview",
dataGridFilterViewPreviewCollapse: "Collapse effect preview",
+2 -2
View File
@@ -7162,8 +7162,8 @@ export default withEnglishFallback({
customUiBorder: "Borde",
customUiSidebar: "Barra lateral",
shortcutConvertNamingStyle: "Cambiar estilo de nomenclatura",
dataGridAutoHideFilterBuilder: "Ocultar automáticamente el editor de condiciones después de aplicar filtros",
dataGridAutoHideFilterBuilderDescription: "Cuando está desactivado, el editor de condiciones permanece después de aplicar filtros para facilitar ajustes adicionales.",
dataGridKeepFilterEditorExpanded: "Mantener expandido el editor de filtros",
dataGridKeepFilterEditorExpandedDescription: "Mantiene siempre expandido el editor en las vistas Condiciones y Texto.",
},
driverStore: {
jreDirRemoveFailed: "No se pudo eliminar el directorio JRE antiguo: {path} (error original: {error})",
+2 -2
View File
@@ -7162,8 +7162,8 @@ export default withEnglishFallback({
customUiBorder: "Bordo",
customUiSidebar: "Barra laterale",
shortcutConvertNamingStyle: "Cambia stile di denominazione",
dataGridAutoHideFilterBuilder: "Nascondi automaticamente l'editor delle condizioni dopo l'applicazione dei filtri",
dataGridAutoHideFilterBuilderDescription: "Se disattivata, dopo l'applicazione dei filtri l'editor delle condizioni rimane per ulteriori modifiche.",
dataGridKeepFilterEditorExpanded: "Mantieni espanso l'editor dei filtri",
dataGridKeepFilterEditorExpandedDescription: "Mantiene sempre espanso l'editor nelle viste Condizioni e Testo.",
},
driverStore: {
jreDirRemoveFailed: "Impossibile rimuovere la vecchia directory JRE: {path} (errore originale: {error})",
+2 -2
View File
@@ -7214,8 +7214,8 @@ export default withEnglishFallback({
customUiBorder: "枠線",
customUiSidebar: "サイドバー",
shortcutConvertNamingStyle: "命名スタイルの切り替え",
dataGridAutoHideFilterBuilder: "フィルター適用後に条件エディターを自動的に非表示にする",
dataGridAutoHideFilterBuilderDescription: "オフにすると、フィルター適用後も条件エディターが残り、調整を続けやすくなります。",
dataGridKeepFilterEditorExpanded: "フィルターエディターを常に展開する",
dataGridKeepFilterEditorExpandedDescription: "条件ビューとテキストビューでエディターを常に展開します。",
},
driverStore: {
jreDirRemoveFailed: "古い JRE ディレクトリを削除できませんでした: {path}(元のエラー: {error})",
+2 -2
View File
@@ -6878,8 +6878,8 @@ export default withEnglishFallback({
csvQuoteModeAll: "모든 필드",
csvQuoteModeNecessary: "필수 필드만",
shortcutConvertNamingStyle: "네이밍 스타일 전환",
dataGridAutoHideFilterBuilder: "필터 적용 후 조건 편집기 자동 숨기기",
dataGridAutoHideFilterBuilderDescription: "끄면 필터를 적용한 후에도 조건 편집기가 유지되어 계속 조정하기 편리합니다.",
dataGridKeepFilterEditorExpanded: "필터 편집기 항상 펼치기",
dataGridKeepFilterEditorExpandedDescription: "조건 및 텍스트 보기에서 편집기를 항상 펼칩니다.",
},
driverStore: {
jreDirRemoveFailed: "이전 JRE 디렉터리 제거 실패: {path} (원래 오류: {error})",
+2 -2
View File
@@ -7164,8 +7164,8 @@ export default withEnglishFallback({
customUiBorder: "Borda",
customUiSidebar: "Barra lateral",
shortcutConvertNamingStyle: "Alternar estilo de nomenclatura",
dataGridAutoHideFilterBuilder: "Ocultar automaticamente o editor de condições após aplicar o filtro",
dataGridAutoHideFilterBuilderDescription: "Quando desativado, o editor de condições permanece após aplicar o filtro, facilitando novos ajustes.",
dataGridKeepFilterEditorExpanded: "Manter o editor de filtros expandido",
dataGridKeepFilterEditorExpandedDescription: "Mantém o editor sempre expandido nas visualizações Condições e Texto.",
},
driverStore: {
jreDirRemoveFailed: "Não foi possível remover o diretório JRE antigo: {path} (erro original: {error})",
+2
View File
@@ -6623,6 +6623,8 @@ export default withEnglishFallback({
dataGridDisplay: "Veri tablosu görünümü",
dataGridFilterView: "Tablo filtresi görünümü",
dataGridFilterViewDescription: "Filtre açılır penceresi, sabit koşullar paneli ya da sıkışık metin koşulları paneli kullanın.",
dataGridKeepFilterEditorExpanded: "Filtre düzenleyicisini açık tut",
dataGridKeepFilterEditorExpandedDescription: "Koşullar ve Metin görünümlerinde düzenleyiciyi her zaman açık tutar.",
dataGridFilterViewPreview: "Etki önizlemesi",
dataGridFilterViewPreviewExpand: "Etki önizlemesini genişlet",
dataGridFilterViewPreviewCollapse: "Etki önizlemesini daralt",
+2 -2
View File
@@ -6720,8 +6720,8 @@ export default withEnglishFallback({
dataGridDisplay: "数据表格显示",
dataGridFilterView: "表格筛选视图",
dataGridFilterViewDescription: "选择弹出式快捷筛选、固定条件面板或紧凑文本条件面板。",
dataGridAutoHideFilterBuilder: "应用筛选后自动隐藏条件编辑器",
dataGridAutoHideFilterBuilderDescription: "关闭后,应用筛选后仍保留条件编辑器,方便继续调整。",
dataGridKeepFilterEditorExpanded: "筛选编辑器常驻展开",
dataGridKeepFilterEditorExpandedDescription: "始终在条件视图和文本视图中展开编辑器。",
dataGridFilterViewPreview: "效果预览",
dataGridFilterViewPreviewExpand: "展开效果预览",
dataGridFilterViewPreviewCollapse: "收起效果预览",
+2 -2
View File
@@ -6479,8 +6479,8 @@ export default withEnglishFallback({
customUiBorder: "邊框",
customUiSidebar: "側邊欄",
shortcutConvertNamingStyle: "切換命名風格",
dataGridAutoHideFilterBuilder: "應用篩選後自動隱藏條件編輯器",
dataGridAutoHideFilterBuilderDescription: "關閉後,應用篩選後仍保留條件編輯器,方便繼續調整。",
dataGridKeepFilterEditorExpanded: "篩選編輯器常駐展開",
dataGridKeepFilterEditorExpandedDescription: "始終在條件檢視和文字檢視中展開編輯器。",
},
driverStore: {
jreDirRemoveFailed: "無法刪除舊的 JRE 目錄:{path}(原始錯誤:{error})",
@@ -110,6 +110,7 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
it("includes the data grid filter view", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridFilterEditorView");
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridKeepFilterEditorExpanded");
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("dataGridTextFilterPanelHeight");
});
@@ -196,9 +197,10 @@ describe("editorSettingsDraftFromSettings", () => {
expect(editorSettingsDraftFromSettings(makeSettings({ colorizeDataGridCellTypes: false })).colorizeDataGridCellTypes).toBe(false);
});
it("maps the data grid filter view from settings", () => {
const draft = editorSettingsDraftFromSettings(makeSettings({ dataGridFilterEditorView: "text", dataGridTextFilterPanelHeight: 224 }));
it("maps the data grid filter view and persistent expansion from settings", () => {
const draft = editorSettingsDraftFromSettings(makeSettings({ dataGridFilterEditorView: "text", dataGridKeepFilterEditorExpanded: true, dataGridTextFilterPanelHeight: 224 }));
expect(draft.dataGridFilterEditorView).toBe("text");
expect(draft.dataGridKeepFilterEditorExpanded).toBe(true);
expect(draft.dataGridTextFilterPanelHeight).toBe(224);
});
@@ -137,6 +137,25 @@ describe("settingsTransfer", () => {
expect(result.value.categories).toEqual(["appearance"]);
});
it("round-trips persistent filter editor expansion as a data setting", () => {
const result = parseSettingsTransferFile(fileWith({ dataGridKeepFilterEditorExpanded: true }));
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.editorSettings.dataGridKeepFilterEditorExpanded).toBe(true);
expect(result.value.categories).toEqual(["data"]);
expect(transferCategoryForKey("dataGridKeepFilterEditorExpanded")).toBe("data");
});
it("migrates the legacy auto-hide filter preference from older exports", () => {
const expanded = parseSettingsTransferFile(fileWith({ dataGridAutoHideFilterBuilder: false }));
expect(expanded.ok).toBe(true);
if (expanded.ok) expect(expanded.value.editorSettings.dataGridKeepFilterEditorExpanded).toBe(true);
const collapsed = parseSettingsTransferFile(fileWith({ dataGridAutoHideFilterBuilder: true }));
expect(collapsed.ok).toBe(true);
if (collapsed.ok) expect(collapsed.value.editorSettings.dataGridKeepFilterEditorExpanded).toBe(false);
});
it("ignores unknown fields instead of importing them", () => {
const result = parseSettingsTransferFile(
fileWith({
@@ -50,7 +50,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"compactColumnHeaderActions",
"dataGridQuickEntry",
"dataGridFilterEditorView",
"dataGridAutoHideFilterBuilder",
"dataGridKeepFilterEditorExpanded",
"dataGridTextFilterPanelHeight",
"multiStatementDefaultView",
"dataGridAutoTransposeSingleRow",
@@ -141,7 +141,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
"compactColumnHeaderActions",
"dataGridQuickEntry",
"dataGridFilterEditorView",
"dataGridAutoHideFilterBuilder",
"dataGridKeepFilterEditorExpanded",
"dataGridTextFilterPanelHeight",
"multiStatementDefaultView",
"dataGridAutoTransposeSingleRow",
@@ -468,6 +468,13 @@ export function parseSettingsTransferFile(text: string): { ok: true; value: Pars
const editor = settings.editor;
if (!isPlainObject(editor)) return { ok: false, error: { code: "empty-settings" } };
// Old exports used the inverse `dataGridAutoHideFilterBuilder` flag. Keep
// accepting it, but normalize it into the single current preference before
// applying the strict whitelist below.
if (!("dataGridKeepFilterEditorExpanded" in editor) && typeof editor.dataGridAutoHideFilterBuilder === "boolean") {
editor.dataGridKeepFilterEditorExpanded = !editor.dataGridAutoHideFilterBuilder;
}
const presentKeys = EDITOR_SETTINGS_DRAFT_KEYS.filter((key) => key in editor);
if (presentKeys.length === 0) return { ok: false, error: { code: "empty-settings" } };
+35
View File
@@ -47,6 +47,21 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ dataGridFilterEditorView: "invalid" } as any).dataGridFilterEditorView).toBe("quick");
});
it("keeps filter editor expansion disabled unless explicitly enabled", () => {
expect(normalizeEditorSettings({}).dataGridKeepFilterEditorExpanded).toBe(false);
expect(normalizeEditorSettings({ dataGridKeepFilterEditorExpanded: true }).dataGridKeepFilterEditorExpanded).toBe(true);
expect(normalizeEditorSettings({ dataGridKeepFilterEditorExpanded: false }).dataGridKeepFilterEditorExpanded).toBe(false);
expect(normalizeEditorSettings({ dataGridKeepFilterEditorExpanded: "true" } as any).dataGridKeepFilterEditorExpanded).toBe(false);
expect(normalizeEditorSettings({ dataGridKeepFilterEditorExpanded: null } as any).dataGridKeepFilterEditorExpanded).toBe(false);
expect(normalizeEditorSettings({ dataGridKeepFilterEditorExpanded: "true", dataGridAutoHideFilterBuilder: false } as any).dataGridKeepFilterEditorExpanded).toBe(false);
});
it("migrates the legacy auto-hide preference when the current preference is absent", () => {
expect(normalizeEditorSettings({ dataGridAutoHideFilterBuilder: false } as any).dataGridKeepFilterEditorExpanded).toBe(true);
expect(normalizeEditorSettings({ dataGridAutoHideFilterBuilder: true } as any).dataGridKeepFilterEditorExpanded).toBe(false);
expect(normalizeEditorSettings({ dataGridKeepFilterEditorExpanded: false, dataGridAutoHideFilterBuilder: false } as any).dataGridKeepFilterEditorExpanded).toBe(false);
});
it("normalizes persisted tab group names and colors", () => {
expect(normalizeEditorSettings({}).tabGroupCustomizations).toEqual({});
expect(
@@ -923,6 +938,26 @@ describe("settingsStore persisted settings initialization", () => {
expect(saveEditorSettings).toHaveBeenCalledWith(expect.objectContaining({ fontSize: 17, theme: "xcode-dark", appLayout: "separated" }));
});
it("migrates the legacy filter-editor preference in incremental settings updates", async () => {
const loadEditorSettings = vi.fn().mockResolvedValue({});
const saveEditorSettings = vi.fn().mockResolvedValue(undefined);
vi.doMock("@/lib/backend/api", () => ({ loadEditorSettings, saveEditorSettings }));
const { useSettingsStore } = await import("@/stores/settingsStore");
const store = useSettingsStore();
await store.initEditorSettings();
saveEditorSettings.mockClear();
store.updateEditorSettings({ dataGridAutoHideFilterBuilder: false } as any);
await vi.waitFor(() => expect(saveEditorSettings).toHaveBeenCalledOnce());
expect(store.editorSettings.dataGridKeepFilterEditorExpanded).toBe(true);
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ dataGridKeepFilterEditorExpanded: true }));
await store.updateEditorSettingsAndPersist({ dataGridAutoHideFilterBuilder: true } as any);
expect(store.editorSettings.dataGridKeepFilterEditorExpanded).toBe(false);
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ dataGridKeepFilterEditorExpanded: false }));
});
it("loads and persists the substitution switch without discarding syntax overrides", async () => {
const loadEditorSettings = vi.fn().mockResolvedValue({
sqlVariableSubstitutionEnabled: false,
+29 -11
View File
@@ -788,7 +788,7 @@ export interface EditorSettings {
columnWidthDensity: ColumnWidthDensity;
dataGridQuickEntry: boolean;
dataGridFilterEditorView: DataGridFilterEditorView;
dataGridAutoHideFilterBuilder: boolean;
dataGridKeepFilterEditorExpanded: boolean;
dataGridTextFilterPanelHeight: number;
localFilterPopoverWidth: number;
dataGridRenderMode: DataGridRenderMode;
@@ -1024,7 +1024,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
columnWidthDensity: "standard",
dataGridQuickEntry: false,
dataGridFilterEditorView: "quick",
dataGridAutoHideFilterBuilder: true,
dataGridKeepFilterEditorExpanded: false,
dataGridTextFilterPanelHeight: DATA_GRID_TEXT_FILTER_PANEL_HEIGHT_DEFAULT,
localFilterPopoverWidth: 360,
dataGridRenderMode: "canvas",
@@ -1368,6 +1368,8 @@ function normalizeTableInfoTab(value: unknown): TableInfoTab {
export function normalizeEditorSettings(settings: Partial<EditorSettings>, existing?: EditorSettings): EditorSettings {
const legacyTimeoutSettings = settings as Partial<EditorSettings> & { queryTimeoutSecs?: unknown; queryTimeoutInheritanceMigrationVersion?: unknown };
const legacySidebarOpenDatabaseOnSingleClick = (settings as Partial<EditorSettings> & { sidebarOpenDatabaseOnSingleClick?: unknown }).sidebarOpenDatabaseOnSingleClick;
const legacyDataGridAutoHideFilterBuilder = (settings as Partial<EditorSettings> & { dataGridAutoHideFilterBuilder?: unknown }).dataGridAutoHideFilterBuilder;
const hasDataGridKeepFilterEditorExpanded = Object.prototype.hasOwnProperty.call(settings, "dataGridKeepFilterEditorExpanded");
const sqlSemanticDiagnosticsMode = normalizeSqlSemanticDiagnosticsMode(settings.sqlSemanticDiagnosticsMode, settings.sqlSemanticDiagnosticsEnabled);
const savedExecuteModeDefaultVersion = settings.executeModeDefaultVersion;
const executeModeDefaultVersion = typeof savedExecuteModeDefaultVersion === "number" && savedExecuteModeDefaultVersion >= EXECUTE_MODE_CURRENT_DEFAULT_VERSION ? savedExecuteModeDefaultVersion : EXECUTE_MODE_CURRENT_DEFAULT_VERSION;
@@ -1479,7 +1481,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
columnWidthDensity: normalizeColumnWidthDensity(settings.columnWidthDensity),
dataGridQuickEntry: settings.dataGridQuickEntry ?? DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry,
dataGridFilterEditorView: normalizeDataGridFilterEditorView(settings.dataGridFilterEditorView),
dataGridAutoHideFilterBuilder: settings.dataGridAutoHideFilterBuilder ?? DEFAULT_EDITOR_SETTINGS.dataGridAutoHideFilterBuilder,
dataGridKeepFilterEditorExpanded: typeof settings.dataGridKeepFilterEditorExpanded === "boolean" ? settings.dataGridKeepFilterEditorExpanded : hasDataGridKeepFilterEditorExpanded ? false : legacyDataGridAutoHideFilterBuilder === false,
dataGridTextFilterPanelHeight: normalizeDataGridTextFilterPanelHeight(settings.dataGridTextFilterPanelHeight),
localFilterPopoverWidth: normalizeDrawerWidth(settings.localFilterPopoverWidth, 240, DEFAULT_EDITOR_SETTINGS.localFilterPopoverWidth),
dataGridRenderMode: normalizeDataGridRenderMode(settings.dataGridRenderMode),
@@ -1613,6 +1615,20 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
};
}
/**
* Cloud snapshots are applied as partial settings updates rather than loaded
* through `normalizeEditorSettings`. Translate the removed inverse flag here
* so older snapshots retain their behavior on every incremental update path.
*/
function migrateLegacyFilterEditorExpansionPatch(partial: Partial<EditorSettings>): Partial<EditorSettings> {
const patch = partial as Partial<EditorSettings> & { dataGridAutoHideFilterBuilder?: unknown };
const { dataGridAutoHideFilterBuilder: legacyAutoHide, ...current } = patch;
if (Object.prototype.hasOwnProperty.call(patch, "dataGridKeepFilterEditorExpanded") || typeof legacyAutoHide !== "boolean") {
return current;
}
return { ...current, dataGridKeepFilterEditorExpanded: !legacyAutoHide };
}
function loadLegacyEditorSettings(): EditorSettings | null {
const raw = safeLocalStorageGet(STORAGE_KEY);
if (raw) {
@@ -2223,7 +2239,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.columnWidthDensity !== undefined) editorSettings.value.columnWidthDensity = normalizeColumnWidthDensity(partial.columnWidthDensity);
if (partial.dataGridQuickEntry !== undefined) editorSettings.value.dataGridQuickEntry = partial.dataGridQuickEntry;
if (partial.dataGridFilterEditorView !== undefined) editorSettings.value.dataGridFilterEditorView = normalizeDataGridFilterEditorView(partial.dataGridFilterEditorView);
if (partial.dataGridAutoHideFilterBuilder !== undefined) editorSettings.value.dataGridAutoHideFilterBuilder = partial.dataGridAutoHideFilterBuilder;
if (partial.dataGridKeepFilterEditorExpanded !== undefined) editorSettings.value.dataGridKeepFilterEditorExpanded = partial.dataGridKeepFilterEditorExpanded === true;
if (partial.dataGridTextFilterPanelHeight !== undefined) editorSettings.value.dataGridTextFilterPanelHeight = normalizeDataGridTextFilterPanelHeight(partial.dataGridTextFilterPanelHeight);
if (partial.localFilterPopoverWidth !== undefined) editorSettings.value.localFilterPopoverWidth = normalizeDrawerWidth(partial.localFilterPopoverWidth, 240, DEFAULT_EDITOR_SETTINGS.localFilterPopoverWidth);
if (partial.dataGridRenderMode !== undefined) editorSettings.value.dataGridRenderMode = normalizeDataGridRenderMode(partial.dataGridRenderMode);
@@ -2309,10 +2325,11 @@ export const useSettingsStore = defineStore("settings", () => {
}
function updateEditorSettings(partial: Partial<EditorSettings>) {
applyEditorSettingsPatch(partial);
markEditorSettingsPatch(partial);
const patch = migrateLegacyFilterEditorExpansionPatch(partial);
applyEditorSettingsPatch(patch);
markEditorSettingsPatch(patch);
if (!isEditorSettingsLoaded.value) {
pendingEditorSettingsPatches.push(editorSettingsPatchSnapshot(partial));
pendingEditorSettingsPatches.push(editorSettingsPatchSnapshot(patch));
return;
}
saveEditorSettings();
@@ -2332,16 +2349,17 @@ export const useSettingsStore = defineStore("settings", () => {
return enqueueEditorSettingsAtomicMutation(async () => {
await initEditorSettings();
const previous = editorSettingsSnapshot(editorSettings.value);
applyEditorSettingsPatch(partial);
const revision = markEditorSettingsPatch(partial);
const patch = migrateLegacyFilterEditorExpansionPatch(partial);
applyEditorSettingsPatch(patch);
const revision = markEditorSettingsPatch(patch);
try {
await persistCurrentEditorSettings();
} catch (error) {
const restored = editorSettingsSnapshot(editorSettings.value) as unknown as Record<string, unknown>;
const previousSettings = previous as unknown as Record<string, unknown>;
let changed = false;
for (const key of Object.keys(partial) as (keyof EditorSettings)[]) {
if (partial[key] === undefined || editorSettingsFieldRevisions.get(key) !== revision) continue;
for (const key of Object.keys(patch) as (keyof EditorSettings)[]) {
if (patch[key] === undefined || editorSettingsFieldRevisions.get(key) !== revision) continue;
restored[key] = previousSettings[key];
editorSettingsFieldRevisions.set(key, ++editorSettingsPatchRevision);
changed = true;