feat(grid): keep filter editor expanded
This commit is contained in:
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>
|
||||
|
||||
|
||||
+10
@@ -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"));
|
||||
|
||||
@@ -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"');
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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})",
|
||||
|
||||
@@ -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})",
|
||||
|
||||
@@ -7214,8 +7214,8 @@ export default withEnglishFallback({
|
||||
customUiBorder: "枠線",
|
||||
customUiSidebar: "サイドバー",
|
||||
shortcutConvertNamingStyle: "命名スタイルの切り替え",
|
||||
dataGridAutoHideFilterBuilder: "フィルター適用後に条件エディターを自動的に非表示にする",
|
||||
dataGridAutoHideFilterBuilderDescription: "オフにすると、フィルター適用後も条件エディターが残り、調整を続けやすくなります。",
|
||||
dataGridKeepFilterEditorExpanded: "フィルターエディターを常に展開する",
|
||||
dataGridKeepFilterEditorExpandedDescription: "条件ビューとテキストビューでエディターを常に展開します。",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "古い JRE ディレクトリを削除できませんでした: {path}(元のエラー: {error})",
|
||||
|
||||
@@ -6878,8 +6878,8 @@ export default withEnglishFallback({
|
||||
csvQuoteModeAll: "모든 필드",
|
||||
csvQuoteModeNecessary: "필수 필드만",
|
||||
shortcutConvertNamingStyle: "네이밍 스타일 전환",
|
||||
dataGridAutoHideFilterBuilder: "필터 적용 후 조건 편집기 자동 숨기기",
|
||||
dataGridAutoHideFilterBuilderDescription: "끄면 필터를 적용한 후에도 조건 편집기가 유지되어 계속 조정하기 편리합니다.",
|
||||
dataGridKeepFilterEditorExpanded: "필터 편집기 항상 펼치기",
|
||||
dataGridKeepFilterEditorExpandedDescription: "조건 및 텍스트 보기에서 편집기를 항상 펼칩니다.",
|
||||
},
|
||||
driverStore: {
|
||||
jreDirRemoveFailed: "이전 JRE 디렉터리 제거 실패: {path} (원래 오류: {error})",
|
||||
|
||||
@@ -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})",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -6720,8 +6720,8 @@ export default withEnglishFallback({
|
||||
dataGridDisplay: "数据表格显示",
|
||||
dataGridFilterView: "表格筛选视图",
|
||||
dataGridFilterViewDescription: "选择弹出式快捷筛选、固定条件面板或紧凑文本条件面板。",
|
||||
dataGridAutoHideFilterBuilder: "应用筛选后自动隐藏条件编辑器",
|
||||
dataGridAutoHideFilterBuilderDescription: "关闭后,应用筛选后仍保留条件编辑器,方便继续调整。",
|
||||
dataGridKeepFilterEditorExpanded: "筛选编辑器常驻展开",
|
||||
dataGridKeepFilterEditorExpandedDescription: "始终在条件视图和文本视图中展开编辑器。",
|
||||
dataGridFilterViewPreview: "效果预览",
|
||||
dataGridFilterViewPreviewExpand: "展开效果预览",
|
||||
dataGridFilterViewPreviewCollapse: "收起效果预览",
|
||||
|
||||
@@ -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" } };
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user