diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 46c2932421..624cd041ea 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -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(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(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(() => { {{ t("grid.filterTextView") }} -
+
- -

{{ t("settings.dataGridAutoHideFilterBuilderDescription") }}

+ +

{{ t("settings.dataGridKeepFilterEditorExpandedDescription") }}

- +
diff --git a/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts index 9caa66b5af..4399145013 100644 --- a/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts +++ b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.choiceCardLayout.spec.ts @@ -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("
{ for (const key of affectedChoiceKeys) { const cardClass = classNameFromTag(openingTag(buttonBlockForKey(key), "Button")); diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 519e3c8c31..e99b28dd19 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -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() {
{ 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"'); diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 5d78e21954..7785104ee4 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index aae3e3448c..fa19f8cadb 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -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})", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 77c1fab7be..3db84fbd4a 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -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})", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 8c7b196f02..070483a2e5 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -7214,8 +7214,8 @@ export default withEnglishFallback({ customUiBorder: "枠線", customUiSidebar: "サイドバー", shortcutConvertNamingStyle: "命名スタイルの切り替え", - dataGridAutoHideFilterBuilder: "フィルター適用後に条件エディターを自動的に非表示にする", - dataGridAutoHideFilterBuilderDescription: "オフにすると、フィルター適用後も条件エディターが残り、調整を続けやすくなります。", + dataGridKeepFilterEditorExpanded: "フィルターエディターを常に展開する", + dataGridKeepFilterEditorExpandedDescription: "条件ビューとテキストビューでエディターを常に展開します。", }, driverStore: { jreDirRemoveFailed: "古い JRE ディレクトリを削除できませんでした: {path}(元のエラー: {error})", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 03c727976c..409420a31a 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -6878,8 +6878,8 @@ export default withEnglishFallback({ csvQuoteModeAll: "모든 필드", csvQuoteModeNecessary: "필수 필드만", shortcutConvertNamingStyle: "네이밍 스타일 전환", - dataGridAutoHideFilterBuilder: "필터 적용 후 조건 편집기 자동 숨기기", - dataGridAutoHideFilterBuilderDescription: "끄면 필터를 적용한 후에도 조건 편집기가 유지되어 계속 조정하기 편리합니다.", + dataGridKeepFilterEditorExpanded: "필터 편집기 항상 펼치기", + dataGridKeepFilterEditorExpandedDescription: "조건 및 텍스트 보기에서 편집기를 항상 펼칩니다.", }, driverStore: { jreDirRemoveFailed: "이전 JRE 디렉터리 제거 실패: {path} (원래 오류: {error})", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 1378c69ccf..36c03a7e86 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -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})", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index 4a0c92cd00..4ff32c21e4 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -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", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 0208e10a8a..4689bcb7d4 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -6720,8 +6720,8 @@ export default withEnglishFallback({ dataGridDisplay: "数据表格显示", dataGridFilterView: "表格筛选视图", dataGridFilterViewDescription: "选择弹出式快捷筛选、固定条件面板或紧凑文本条件面板。", - dataGridAutoHideFilterBuilder: "应用筛选后自动隐藏条件编辑器", - dataGridAutoHideFilterBuilderDescription: "关闭后,应用筛选后仍保留条件编辑器,方便继续调整。", + dataGridKeepFilterEditorExpanded: "筛选编辑器常驻展开", + dataGridKeepFilterEditorExpandedDescription: "始终在条件视图和文本视图中展开编辑器。", dataGridFilterViewPreview: "效果预览", dataGridFilterViewPreviewExpand: "展开效果预览", dataGridFilterViewPreviewCollapse: "收起效果预览", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 1ec09f6778..121c5244d2 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -6479,8 +6479,8 @@ export default withEnglishFallback({ customUiBorder: "邊框", customUiSidebar: "側邊欄", shortcutConvertNamingStyle: "切換命名風格", - dataGridAutoHideFilterBuilder: "應用篩選後自動隱藏條件編輯器", - dataGridAutoHideFilterBuilderDescription: "關閉後,應用篩選後仍保留條件編輯器,方便繼續調整。", + dataGridKeepFilterEditorExpanded: "篩選編輯器常駐展開", + dataGridKeepFilterEditorExpandedDescription: "始終在條件檢視和文字檢視中展開編輯器。", }, driverStore: { jreDirRemoveFailed: "無法刪除舊的 JRE 目錄:{path}(原始錯誤:{error})", diff --git a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts index 2abd3c58d3..ca6284bb85 100644 --- a/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts +++ b/apps/desktop/src/lib/settings/__tests__/editorSettingsDraft.spec.ts @@ -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); }); diff --git a/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts b/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts index be87fd843f..e063e8f0f7 100644 --- a/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts +++ b/apps/desktop/src/lib/settings/__tests__/settingsTransfer.spec.ts @@ -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({ diff --git a/apps/desktop/src/lib/settings/editorSettingsDraft.ts b/apps/desktop/src/lib/settings/editorSettingsDraft.ts index e740a45fff..c6966cda37 100644 --- a/apps/desktop/src/lib/settings/editorSettingsDraft.ts +++ b/apps/desktop/src/lib/settings/editorSettingsDraft.ts @@ -50,7 +50,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [ "compactColumnHeaderActions", "dataGridQuickEntry", "dataGridFilterEditorView", - "dataGridAutoHideFilterBuilder", + "dataGridKeepFilterEditorExpanded", "dataGridTextFilterPanelHeight", "multiStatementDefaultView", "dataGridAutoTransposeSingleRow", diff --git a/apps/desktop/src/lib/settings/settingsTransfer.ts b/apps/desktop/src/lib/settings/settingsTransfer.ts index ed54944927..0a8606534c 100644 --- a/apps/desktop/src/lib/settings/settingsTransfer.ts +++ b/apps/desktop/src/lib/settings/settingsTransfer.ts @@ -141,7 +141,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record key in editor); if (presentKeys.length === 0) return { ok: false, error: { code: "empty-settings" } }; diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 37bc4dcb2a..7d81d635d5 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -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, diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index bac636285b..7542a8fb1e 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -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, existing?: EditorSettings): EditorSettings { const legacyTimeoutSettings = settings as Partial & { queryTimeoutSecs?: unknown; queryTimeoutInheritanceMigrationVersion?: unknown }; const legacySidebarOpenDatabaseOnSingleClick = (settings as Partial & { sidebarOpenDatabaseOnSingleClick?: unknown }).sidebarOpenDatabaseOnSingleClick; + const legacyDataGridAutoHideFilterBuilder = (settings as Partial & { 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, 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, 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): Partial { + const patch = partial as Partial & { 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) { - 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; const previousSettings = previous as unknown as Record; 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;