diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index af254a6943..2986c1515f 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -1981,7 +1981,8 @@ const { onTableDataGridColumnOrderChanged, frozenColumnCount, freezeToColumn, - freezeSelectedColumns: freezeSelectedColumnsInLayout, + freezeSelectedColumnsIncrementally, + unfreezeSelectedColumns, unfreezeAllColumns: unfreezeAllColumnsInLayout, } = useDataGridColumnLayoutState({ columns: computed(() => props.result.columns), @@ -2329,7 +2330,15 @@ function resetColumnOrder() { } function freezeSelectedColumns(selectedVisibleColumnIndexes: number[]) { - applyColumnOrderChange(() => freezeSelectedColumnsInLayout(selectedVisibleColumnIndexes)); + applyColumnOrderChange(() => freezeSelectedColumnsIncrementally(selectedVisibleColumnIndexes)); +} + +function freezeCurrentOrSelectedColumns(selectedVisibleColumnIndexes: number[]) { + applyColumnOrderChange(() => freezeSelectedColumnsIncrementally(selectedVisibleColumnIndexes)); +} + +function unfreezeCurrentOrSelectedColumns(selectedVisibleColumnIndexes: number[]) { + applyColumnOrderChange(() => unfreezeSelectedColumns(selectedVisibleColumnIndexes)); } function unfreezeAllColumns() { @@ -11614,8 +11623,10 @@ const gridContextMenuItems = computed(() => { localDescending: t("grid.sortCurrentPageDescending"), clearSort: t("grid.clearSort"), freezeToColumn: t("grid.freezeToColumn"), - freezeSelectedColumns: t("grid.freezeSelectedColumns"), - unfreezeColumns: t("grid.unfreezeColumns"), + freezeSelectedColumns: t("grid.freezeSelectedColumns", { count: selectedColumnCount }), + freezeCurrentColumn: t("grid.freezeCurrentColumn"), + unfreezeCurrentColumn: t("grid.unfreezeCurrentColumn"), + unfreezeColumns: t("grid.unfreezeColumns", { count: frozenColumnCount.value }), hideColumn: t("grid.hideColumn"), hideSelectedColumns: t("grid.hideSelectedColumns", { count: selectedColumnCount }), showAllColumnsMenu: t("grid.showAllColumnsMenu"), @@ -11645,6 +11656,18 @@ const gridContextMenuItems = computed(() => { freezeSelectedColumns(selectedVisibleColumnIndexes()); clearCellSelection(); }, + freezeCurrentColumn: () => { + const indexes = selectedVisibleColumnIndexes(); + const idx = contextHeaderVisibleColIdx.value; + freezeCurrentOrSelectedColumns(indexes.length > 1 ? indexes : idx === null ? [] : [idx]); + clearCellSelection(); + }, + unfreezeCurrentColumn: () => { + const indexes = selectedVisibleColumnIndexes(); + const idx = contextHeaderVisibleColIdx.value; + unfreezeCurrentOrSelectedColumns(indexes.length > 1 ? indexes : idx === null ? [] : [idx]); + clearCellSelection(); + }, unfreezeColumns: () => { unfreezeAllColumns(); clearCellSelection(); diff --git a/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts index 437933104f..ebcc8aa191 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts @@ -299,7 +299,7 @@ describe("DataGrid context menu target lifecycle", () => { await settle(); expect(contextMenuLabels()).toContain("Copy Selected Column Names (2)"); - expect(contextMenuLabels()).toContain("Freeze Selected Columns"); + expect(contextMenuLabels()).toContain("Freeze Selected Columns (2)"); }); it("offers snapshots for full-column selections from the column header", async () => { diff --git a/apps/desktop/src/composables/useDataGridColumnLayout.ts b/apps/desktop/src/composables/useDataGridColumnLayout.ts index dc5d572add..1d12f8e1df 100644 --- a/apps/desktop/src/composables/useDataGridColumnLayout.ts +++ b/apps/desktop/src/composables/useDataGridColumnLayout.ts @@ -297,6 +297,38 @@ export function useDataGridColumnLayoutState(options: { setFrozenColumnCount(selectedActualIdxs.length); } + function freezeSelectedColumnsIncrementally(selectedVisibleColIdxs: number[]) { + if (selectedVisibleColIdxs.length === 0) return; + const visibleIdxs = visibleColumnIndexes.value; + const selectedActualIdxs = [...new Set(selectedVisibleColIdxs.map((vIdx) => visibleIdxs[vIdx]).filter((idx): idx is number => idx !== undefined))]; + if (selectedActualIdxs.length === 0) return; + const currentOrder = orderedDisplayableColumnIndexes.value; + const frozenVisibleIndexes = visibleColumnIndexes.value.slice(0, frozenColumnCount.value); + const frozenSet = new Set(frozenVisibleIndexes); + const additions = selectedActualIdxs.filter((idx) => !frozenSet.has(idx)); + if (additions.length === 0) return; + if (columnOrderSnapshotBeforeFreeze.value === null) columnOrderSnapshotBeforeFreeze.value = [...persistedColumnOrderKeys.value]; + const remaining = currentOrder.filter((idx) => !additions.includes(idx)); + const frozenEnd = frozenVisibleIndexes.reduce((end, idx) => Math.max(end, remaining.indexOf(idx) + 1), 0); + persistColumnOrder([...remaining.slice(0, frozenEnd), ...additions, ...remaining.slice(frozenEnd)]); + setFrozenColumnCount(frozenColumnCount.value + additions.length); + } + + function unfreezeSelectedColumns(selectedVisibleColIdxs: number[]) { + if (selectedVisibleColIdxs.length === 0 || frozenColumnCount.value === 0) return; + const visibleIdxs = visibleColumnIndexes.value; + const selected = new Set(selectedVisibleColIdxs.map((vIdx) => visibleIdxs[vIdx]).filter((idx): idx is number => idx !== undefined)); + const currentOrder = orderedDisplayableColumnIndexes.value; + const frozen = visibleColumnIndexes.value.slice(0, frozenColumnCount.value); + const removing = frozen.filter((idx) => selected.has(idx)); + if (removing.length === 0) return; + if (columnOrderSnapshotBeforeFreeze.value === null) columnOrderSnapshotBeforeFreeze.value = [...persistedColumnOrderKeys.value]; + const nextFrozen = frozen.filter((idx) => !selected.has(idx)); + persistColumnOrder(currentOrder.filter((idx) => !removing.includes(idx)).concat(removing)); + setFrozenColumnCount(nextFrozen.length); + if (nextFrozen.length === 0) unfreezeAllColumns(); + } + function unfreezeAllColumns() { setFrozenColumnCount(0); if (columnOrderSnapshotBeforeFreeze.value !== null) { @@ -441,6 +473,8 @@ export function useDataGridColumnLayoutState(options: { frozenColumnCount, freezeToColumn, freezeSelectedColumns, + freezeSelectedColumnsIncrementally, + unfreezeSelectedColumns, unfreezeAllColumns, }; } diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index aedb216d40..b301311074 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -2048,8 +2048,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "Verilənlər bazasını azalan sıra ilə sırala", clearSort: "Sıralamanı təmizlə", freezeToColumn: "Bu sütuna qədər sabitlə", - freezeSelectedColumns: "Seçilmiş sütunları sabitlə", - unfreezeColumns: "Sütunların sabitlənməsini ləğv et", + freezeSelectedColumns: "Seçilmiş sütunları sabitlə ({count})", + freezeCurrentColumn: "Cari sütunu sabitlə", + unfreezeCurrentColumn: "Cari sütunun sabitini aç", + unfreezeColumns: "Sütunların sabitlənməsini ləğv et ({count})", hideColumn: "Bu sütunu gizlət", hideSelectedColumns: "Seçilmiş sütunları gizlət ({count})", showAllColumnsMenu: "Bütün sütunları göstər", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 109530c88b..8dcdf47850 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2199,8 +2199,10 @@ export default { sortDatabaseDescending: "Sort Database Descending", clearSort: "Clear Sort", freezeToColumn: "Freeze to This Column", - freezeSelectedColumns: "Freeze Selected Columns", - unfreezeColumns: "Unfreeze Columns", + freezeSelectedColumns: "Freeze Selected Columns ({count})", + freezeCurrentColumn: "Freeze Current Column", + unfreezeCurrentColumn: "Unfreeze Current Column", + unfreezeColumns: "Unfreeze Columns ({count})", hideColumn: "Hide This Column", hideSelectedColumns: "Hide Selected Columns ({count})", showAllColumnsMenu: "Show All Columns", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 7aef40409a..6b3a6ec938 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2047,8 +2047,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "Ordenar base de datos descendente", clearSort: "Limpiar orden", freezeToColumn: "Inmovilizar hasta esta columna", - freezeSelectedColumns: "Inmovilizar columnas seleccionadas", - unfreezeColumns: "Desinmovilizar columnas", + freezeSelectedColumns: "Inmovilizar columnas seleccionadas ({count})", + freezeCurrentColumn: "Inmovilizar columna actual", + unfreezeCurrentColumn: "Desinmovilizar columna actual", + unfreezeColumns: "Desinmovilizar columnas ({count})", hideColumn: "Ocultar esta columna", hideSelectedColumns: "Ocultar columnas seleccionadas ({count})", showAllColumnsMenu: "Mostrar todas las columnas", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 40cce8056f..60e960e4c6 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2045,8 +2045,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "Ordina database decrescente", clearSort: "Rimuovi Ordinamento", freezeToColumn: "Blocca fino a questa colonna", - freezeSelectedColumns: "Blocca colonne selezionate", - unfreezeColumns: "Sblocca colonne", + freezeSelectedColumns: "Blocca colonne selezionate ({count})", + freezeCurrentColumn: "Blocca colonna corrente", + unfreezeCurrentColumn: "Sblocca colonna corrente", + unfreezeColumns: "Sblocca colonne ({count})", hideColumn: "Nascondi questa colonna", hideSelectedColumns: "Nascondi colonne selezionate ({count})", showAllColumnsMenu: "Mostra tutte le colonne", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 456e133394..d86b5a5b70 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2057,8 +2057,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "データベースで降順に並び替え", clearSort: "並び替えをクリア", freezeToColumn: "この列まで固定", - freezeSelectedColumns: "選択列を固定", - unfreezeColumns: "列の固定を解除", + freezeSelectedColumns: "選択列を固定 ({count})", + freezeCurrentColumn: "現在の列を固定", + unfreezeCurrentColumn: "現在の列の固定を解除", + unfreezeColumns: "列の固定を解除 ({count})", hideColumn: "この列を非表示", hideSelectedColumns: "選択した列を非表示 ({count})", showAllColumnsMenu: "すべての列を表示", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 4dbdae41ba..c5a4a27212 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -2038,8 +2038,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "데이터베이스 내림차순 정렬", clearSort: "정렬 지우기", freezeToColumn: "이 컬럼까지 고정", - freezeSelectedColumns: "선택한 컬럼 고정", - unfreezeColumns: "컬럼 고정 해제", + freezeSelectedColumns: "선택한 컬럼 고정 ({count})", + freezeCurrentColumn: "현재 열 고정", + unfreezeCurrentColumn: "현재 열 고정 해제", + unfreezeColumns: "컬럼 고정 해제 ({count})", hideColumn: "이 컬럼 숨기기", hideSelectedColumns: "선택한 컬럼 숨기기 ({count})", showAllColumnsMenu: "모든 컬럼 표시", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 8c2fa882b2..25bda7890f 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2047,8 +2047,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "Ordenar Banco de Dados Decrescente", clearSort: "Limpar Ordenação", freezeToColumn: "Congelar até esta coluna", - freezeSelectedColumns: "Congelar colunas selecionadas", - unfreezeColumns: "Descongelar colunas", + freezeSelectedColumns: "Congelar colunas selecionadas ({count})", + freezeCurrentColumn: "Congelar coluna atual", + unfreezeCurrentColumn: "Descongelar coluna atual", + unfreezeColumns: "Descongelar colunas ({count})", hideColumn: "Ocultar esta coluna", hideSelectedColumns: "Ocultar colunas selecionadas ({count})", showAllColumnsMenu: "Mostrar todas as colunas", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index 5ad480df4f..4551b005e2 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -2043,8 +2043,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "Veritabanında Azalan Sırala", clearSort: "Sıralamayı Temizle", freezeToColumn: "Bu Sütuna Kadar Dondur", - freezeSelectedColumns: "Seçili Sütunları Dondur", - unfreezeColumns: "Sütun Dondurmayı Kaldır", + freezeSelectedColumns: "Seçili Sütunları Dondur ({count})", + freezeCurrentColumn: "Geçerli sütunu sabitle", + unfreezeCurrentColumn: "Geçerli sütunun sabitlemesini kaldır", + unfreezeColumns: "Sütun Dondurmayı Kaldır ({count})", hideColumn: "Bu Sütunu Gizle", hideSelectedColumns: "Seçili Sütunları Gizle ({count})", showAllColumnsMenu: "Tüm Sütunları Göster", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 2409cd9d70..c108258b69 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2114,8 +2114,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "数据库降序排序", clearSort: "清除排序", freezeToColumn: "冻结到此列", - freezeSelectedColumns: "冻结选中列", - unfreezeColumns: "取消冻结", + freezeSelectedColumns: "冻结选中列({count} 列)", + freezeCurrentColumn: "冻结当前列", + unfreezeCurrentColumn: "取消冻结当前列", + unfreezeColumns: "取消冻结({count} 列)", hideColumn: "隐藏此列", hideSelectedColumns: "隐藏选中列({count} 列)", showAllColumnsMenu: "显示全部列", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index fab0bab0d7..3d5c58d758 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2048,8 +2048,10 @@ export default withEnglishFallback({ sortDatabaseDescending: "資料庫遞減排序", clearSort: "清除排序", freezeToColumn: "凍結到此欄", - freezeSelectedColumns: "凍結選取欄", - unfreezeColumns: "取消凍結", + freezeSelectedColumns: "凍結選取欄({count} 欄)", + freezeCurrentColumn: "凍結目前欄", + unfreezeCurrentColumn: "取消凍結目前欄", + unfreezeColumns: "取消凍結({count} 欄)", hideColumn: "隱藏此欄", hideSelectedColumns: "隱藏選取欄({count} 欄)", showAllColumnsMenu: "顯示全部欄", diff --git a/apps/desktop/src/lib/dataGrid/dataGridContextMenu.ts b/apps/desktop/src/lib/dataGrid/dataGridContextMenu.ts index 8a281f91b0..29823f3fb7 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridContextMenu.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridContextMenu.ts @@ -90,7 +90,8 @@ export function createDataGridColumnContextMenuItems(options: { labels: Record< "copyName" | "copyNames" | "details" | "copyAlterSql" | "databaseAscending" | "databaseDescending" | "localAscending" | "localDescending" | "clearSort" | "freezeToColumn" | "freezeSelectedColumns" | "unfreezeColumns" | "hideColumn" | "hideSelectedColumns" | "showAllColumnsMenu", string - >; + > & + Partial>; icons: Pick; actions: { copyName: () => void; @@ -100,6 +101,8 @@ export function createDataGridColumnContextMenuItems(options: { sort: (direction: "asc" | "desc" | null, mode: "database" | "local") => void; freezeToColumn: () => void; freezeSelectedColumns: () => void; + freezeCurrentColumn?: () => void; + unfreezeCurrentColumn?: () => void; unfreezeColumns: () => void; hideColumn: () => void; hideSelectedColumns: () => void; @@ -142,11 +145,13 @@ export function createDataGridColumnContextMenuItems(options: { items.push({ label: options.labels.showAllColumnsMenu, action: options.actions.showAllColumnsMenu }); } if ((options.frozenColumnCount ?? 0) > 0) { + if ((options.contextVisibleColIdx ?? -1) < (options.frozenColumnCount ?? 0) && options.labels.unfreezeCurrentColumn && options.actions.unfreezeCurrentColumn) items.push({ label: options.labels.unfreezeCurrentColumn, action: options.actions.unfreezeCurrentColumn }); items.push({ label: options.labels.unfreezeColumns, action: options.actions.unfreezeColumns }); } if (options.hasColumnSelection) { items.push({ label: options.labels.freezeSelectedColumns, action: options.actions.freezeSelectedColumns }); } + if (options.labels.freezeCurrentColumn && options.actions.freezeCurrentColumn) items.push({ label: options.labels.freezeCurrentColumn, action: options.actions.freezeCurrentColumn }); items.push({ label: options.labels.freezeToColumn, action: options.actions.freezeToColumn }); } return items;