Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion apps/desktop/src/components/grid/DataGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ import {
import { normalizeResultPageSize, resultPageSizeMenuOptions } from "@/lib/dataGrid/paginationPageSize";
import { dataGridPageSizeSettingsPatch, preferredDataGridPageSize, resolveDataGridPageSizePreference, type DataGridPageSizePreference } from "@/lib/dataGrid/dataGridPageSizePreference";
import { continuousQueryResultMaxRows, effectiveQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
import { allNullColumnIndexes } from "@/lib/dataGrid/dataGridColumnVisibility";
import { allNullColumnIndexes, identicalValueColumnIndexes } from "@/lib/dataGrid/dataGridColumnVisibility";
import { buildDataGridColumnLookupItems, dataGridColumnCommentFor, filterDataGridColumnLookupItems } from "@/lib/dataGrid/dataGridColumnLookup";
import { uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
import { dataGridColumnLayoutScopeKey, TABLE_DATA_GRID_COLUMN_ORDER_CHANGED_EVENT, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
Expand Down Expand Up @@ -2744,6 +2744,24 @@ function showAllColumns() {
void nextTick(scheduleColumnLayoutRefresh);
}

function getComparisonRows(): ReadonlyArray<ReadonlyArray<unknown>> {
// 比较行必须取全宽 data:identicalValueColumnIndexes 的候选索引是原始列索引,
// 不能用经过可见列投影(visibleRowData 重排)后的行。
const affected = affectedRowIds();
if (affected.length > 1) {
const affectedSet = new Set(affected);
return displayItems.value.filter((item) => affectedSet.has(item.id) && !item.isDraft).map((item) => item.data);
}
const displayRows = displayItems.value.filter((item) => !item.isDraft).map((item) => item.data);
return displayRows.length > 0 ? displayRows : props.result.rows;
}

function hideIdenticalColumns() {
const identical = identicalValueColumnIndexes(getComparisonRows(), visibleColumnIndexes.value);
if (identical.length === 0) return;
hideColumns(identical);
}

// --- 表头拖拽进 SQL 编辑器:目标导向模式切换的控制器 ---
// 按选择顺序(Set 插入序)取列名;被拖列未选中时仅拖该列。
function columnReferenceDragNames(draggedVisibleColIdx: number): string[] | null {
Expand Down Expand Up @@ -12597,6 +12615,11 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
}
}

const comparisonRows = getComparisonRows();
const identicalColumnIndexes = identicalValueColumnIndexes(comparisonRows, visibleColumnIndexes.value);
const canHideIdenticalColumns = identicalColumnIndexes.length > 0;
const identicalColumnCount = identicalColumnIndexes.length;

return createDataGridContextMenuItems(
createDataGridColumnContextMenuItems({
headerColumn: !!contextHeaderColumn.value,
Expand All @@ -12612,6 +12635,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
selectedColumnCount,
visibleColumnCount: visibleColumnCount.value,
hiddenColumnCount: hiddenColumnCount.value,
canHideIdenticalColumns,
labels: {
copyName:
selectedColumnNamesForCopy.value.length > 1
Expand All @@ -12634,6 +12658,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
unfreezeColumns: t("grid.unfreezeColumns", { count: frozenColumnCount.value }),
hideColumn: t("grid.hideColumn"),
hideSelectedColumns: t("grid.hideSelectedColumns", { count: selectedColumnCount }),
hideIdenticalColumns: identicalColumnCount > 0 ? t("grid.hideIdenticalColumnsCount", { count: identicalColumnCount }) : t("grid.hideIdenticalColumns"),
showAllColumnsMenu: t("grid.showAllColumnsMenu"),
},
icons: {
Expand Down Expand Up @@ -12679,6 +12704,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
},
hideColumn: hideContextColumn,
hideSelectedColumns,
hideIdenticalColumns,
showAllColumnsMenu: showAllColumns,
},
filterSubmenu: filterSubmenu(),
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/az.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,8 @@ export default withEnglishFallback({
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})",
hideIdenticalColumns: "Eyni dəyərlərə malik sütunları gizlət",
hideIdenticalColumnsCount: "Eyni dəyərlərə malik sütunları gizlət ({count})",
showAllColumnsMenu: "Bütün sütunları göstər",
pasted: "Yerləşdirildi!",
batchAppendPasteNotEditable: "Bu nəticəni redaktə etmək mümkün deyil.",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2548,6 +2548,8 @@ export default {
unfreezeColumns: "Unfreeze Columns ({count})",
hideColumn: "Hide This Column",
hideSelectedColumns: "Hide Selected Columns ({count})",
hideIdenticalColumns: "Hide Columns with Identical Values",
hideIdenticalColumnsCount: "Hide Columns with Identical Values ({count})",
showAllColumnsMenu: "Show All Columns",
pasted: "Pasted!",
batchAppendPasteNotEditable: "This result cannot be edited.",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2388,6 +2388,8 @@ export default withEnglishFallback({
unfreezeColumns: "Desinmovilizar columnas ({count})",
hideColumn: "Ocultar esta columna",
hideSelectedColumns: "Ocultar columnas seleccionadas ({count})",
hideIdenticalColumns: "Ocultar columnas con valores idénticos",
hideIdenticalColumnsCount: "Ocultar columnas con valores idénticos ({count})",
showAllColumnsMenu: "Mostrar todas las columnas",
pasted: "¡Pegado!",
search: "Buscar...",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,8 @@ export default withEnglishFallback({
unfreezeColumns: "Batalkan Pembekuan Kolom ({count})",
hideColumn: "Sembunyikan Kolom Ini",
hideSelectedColumns: "Sembunyikan Kolom Terpilih ({count})",
hideIdenticalColumns: "Sembunyikan Kolom dengan Nilai yang Sama",
hideIdenticalColumnsCount: "Sembunyikan Kolom dengan Nilai yang Sama ({count})",
showAllColumnsMenu: "Tampilkan Semua Kolom",
pasted: "Ditempel!",
batchAppendPasteNotEditable: "Hasil ini tidak dapat diedit.",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2282,6 +2282,8 @@ export default withEnglishFallback({
unfreezeColumns: "Sblocca colonne ({count})",
hideColumn: "Nascondi questa colonna",
hideSelectedColumns: "Nascondi colonne selezionate ({count})",
hideIdenticalColumns: "Nascondi colonne con valori identici",
hideIdenticalColumnsCount: "Nascondi colonne con valori identici ({count})",
showAllColumnsMenu: "Mostra tutte le colonne",
pasted: "Incollato!",
search: "Cerca...",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2289,6 +2289,8 @@ export default withEnglishFallback({
unfreezeColumns: "列の固定を解除 ({count})",
hideColumn: "この列を非表示",
hideSelectedColumns: "選択した列を非表示 ({count})",
hideIdenticalColumns: "同一値の列を非表示",
hideIdenticalColumnsCount: "同一値の列を非表示({count} 列)",
showAllColumnsMenu: "すべての列を表示",
pasted: "貼り付けました!",
search: "検索...",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2326,6 +2326,8 @@ export default withEnglishFallback({
unfreezeColumns: "컬럼 고정 해제 ({count})",
hideColumn: "이 컬럼 숨기기",
hideSelectedColumns: "선택한 컬럼 숨기기 ({count})",
hideIdenticalColumns: "동일한 값의 컬럼 숨기기",
hideIdenticalColumnsCount: "동일한 값의 컬럼 숨기기 ({count}개)",
showAllColumnsMenu: "모든 컬럼 표시",
pasted: "붙여넣었습니다!",
replaceText: "바꿀 내용",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2284,6 +2284,8 @@ export default withEnglishFallback({
unfreezeColumns: "Descongelar colunas ({count})",
hideColumn: "Ocultar esta coluna",
hideSelectedColumns: "Ocultar colunas selecionadas ({count})",
hideIdenticalColumns: "Ocultar colunas com valores idênticos",
hideIdenticalColumnsCount: "Ocultar colunas com valores idênticos ({count})",
showAllColumnsMenu: "Mostrar todas as colunas",
pasted: "Colado!",
search: "Pesquisar...",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2442,6 +2442,8 @@ export default withEnglishFallback({
unfreezeColumns: "Открепить столбцы ({count})",
hideColumn: "Скрыть этот столбец",
hideSelectedColumns: "Скрыть выбранные столбцы ({count})",
hideIdenticalColumns: "Скрыть столбцы с одинаковыми значениями",
hideIdenticalColumnsCount: "Скрыть столбцы с одинаковыми значениями ({count})",
showAllColumnsMenu: "Показать все столбцы",
pasted: "Вставлено!",
batchAppendPasteNotEditable: "Этот результат нельзя редактировать.",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,8 @@ export default withEnglishFallback({
unfreezeColumns: "Sütun Dondurmayı Kaldır ({count})",
hideColumn: "Bu Sütunu Gizle",
hideSelectedColumns: "Seçili Sütunları Gizle ({count})",
hideIdenticalColumns: "Aynı Değerlere Sahip Sütunları Gizle",
hideIdenticalColumnsCount: "Aynı Değerlere Sahip Sütunları Gizle ({count})",
showAllColumnsMenu: "Tüm Sütunları Göster",
pasted: "Yapıştırıldı!",
batchAppendPasteNotEditable: "Bu sonuç düzenlenemez.",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2483,6 +2483,8 @@ export default withEnglishFallback({
unfreezeColumns: "取消冻结({count} 列)",
hideColumn: "隐藏此列",
hideSelectedColumns: "隐藏选中列({count} 列)",
hideIdenticalColumns: "隐藏相同数值列",
hideIdenticalColumnsCount: "隐藏相同数值列({count} 列)",
showAllColumnsMenu: "显示全部列",
pasted: "已粘贴!",
batchAppendPasteNotEditable: "当前结果不可编辑。",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2284,6 +2284,8 @@ export default withEnglishFallback({
unfreezeColumns: "取消凍結({count} 欄)",
hideColumn: "隱藏此欄",
hideSelectedColumns: "隱藏選取欄({count} 欄)",
hideIdenticalColumns: "隱藏相同數值欄",
hideIdenticalColumnsCount: "隱藏相同數值欄({count} 欄)",
showAllColumnsMenu: "顯示全部欄",
pasted: "已貼上!",
search: "搜尋……",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ describe("dataGridContextMenu", () => {
expect(rowItems.find((item) => item.label === "delete")?.variant).toBe("destructive");
});

function columnMenuItems(overrides: { hasColumnSelection?: boolean; selectedColumnCount?: number; visibleColumnCount?: number; hiddenColumnCount?: number }) {
function columnMenuItems(overrides: { headerColumn?: boolean; contextColumn?: boolean; contextVisibleColIdx?: number; hasColumnSelection?: boolean; selectedColumnCount?: number; visibleColumnCount?: number; hiddenColumnCount?: number; canHideIdenticalColumns?: boolean }) {
const action = vi.fn();
const filter = createDataGridFilterSubmenu({
label: "filter",
Expand All @@ -149,13 +149,13 @@ describe("dataGridContextMenu", () => {
clear: action,
});
return createDataGridColumnContextMenuItems({
headerColumn: true,
contextColumn: false,
headerColumn: overrides.headerColumn ?? true,
contextColumn: overrides.contextColumn ?? false,
canCopyAlterSql: false,
canFilter: false,
hasSort: false,
sortMode: "database",
contextVisibleColIdx: 0,
contextVisibleColIdx: overrides.contextVisibleColIdx !== undefined ? overrides.contextVisibleColIdx : 0,
hasColumnSelection: false,
selectedColumnCount: 1,
visibleColumnCount: 3,
Expand All @@ -175,10 +175,11 @@ describe("dataGridContextMenu", () => {
unfreezeColumns: "unfreeze",
hideColumn: "hide column",
hideSelectedColumns: "hide selected (3)",
hideIdenticalColumns: "hide identical",
showAllColumnsMenu: "show all columns",
},
icons: { copy: icon, columnDetails: icon, database: icon, ascending: icon, descending: icon, clearSort: icon },
actions: { copyName: action, copyNames: action, details: action, copyAlterSql: action, sort: action, freezeToColumn: action, freezeSelectedColumns: action, unfreezeColumns: action, hideColumn: action, hideSelectedColumns: action, showAllColumnsMenu: action },
actions: { copyName: action, copyNames: action, details: action, copyAlterSql: action, sort: action, freezeToColumn: action, freezeSelectedColumns: action, unfreezeColumns: action, hideColumn: action, hideSelectedColumns: action, hideIdenticalColumns: action, showAllColumnsMenu: action },
filterSubmenu: filter,
...overrides,
});
Expand Down Expand Up @@ -221,4 +222,30 @@ describe("dataGridContextMenu", () => {
expect(hideColumn).toBeDefined();
expect(hideColumn?.disabled).toBe(false);
});

it("offers hide identical columns on a header and disables it when no identical columns exist", () => {
const items = columnMenuItems({ canHideIdenticalColumns: false });
const hideIdentical = items.find((item) => item.label === "hide identical");

expect(hideIdentical).toBeDefined();
expect(hideIdentical?.disabled).toBe(true);
});

it("enables hide identical columns when identical columns exist", () => {
const items = columnMenuItems({ canHideIdenticalColumns: true });
const hideIdentical = items.find((item) => item.label === "hide identical");

expect(hideIdentical).toBeDefined();
expect(hideIdentical?.disabled).toBe(false);
});

it("offers hide identical columns on a cell context menu and shows recovery entry if hidden columns exist", () => {
const items = columnMenuItems({ headerColumn: false, contextColumn: true, contextVisibleColIdx: undefined, canHideIdenticalColumns: true, hiddenColumnCount: 2 });
const hideIdentical = items.find((item) => item.label === "hide identical");
const showAll = items.find((item) => item.label === "show all columns");

expect(hideIdentical).toBeDefined();
expect(hideIdentical?.disabled).toBe(false);
expect(showAll).toBeDefined();
});
});
46 changes: 46 additions & 0 deletions apps/desktop/src/lib/dataGrid/dataGridColumnVisibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,49 @@ export function removeAutoHiddenColumnIndexes(hiddenIndexes: ReadonlySet<number>
}
return next;
}

export function areCellValuesEqual(a: unknown, b: unknown): boolean {
if (a === b) return true;
if (typeof a === "number" && typeof b === "number" && Number.isNaN(a) && Number.isNaN(b)) return true;
if (a === null || b === null || a === undefined || b === undefined) return false;
if (typeof a !== "object" || typeof b !== "object") return false;
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (a instanceof Uint8Array && b instanceof Uint8Array) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
try {
return JSON.stringify(a) === JSON.stringify(b);
} catch {
return false;
}
}

export function identicalValueColumnIndexes(rows: ReadonlyArray<ReadonlyArray<unknown>>, candidateIndexes: readonly number[]): number[] {
if (rows.length < 2) return [];
const candidates = [...candidateIndexes];
let count = candidates.length;
if (count === 0) return [];

const firstRow = rows[0];
if (!firstRow) return [];

for (let rowIndex = 1; rowIndex < rows.length; rowIndex++) {
const row = rows[rowIndex];
if (!row) continue;
let write = 0;
for (let read = 0; read < count; read++) {
const col = candidates[read]!;
if (areCellValuesEqual(row[col], firstRow[col])) {
candidates[write++] = col;
}
}
count = write;
if (count === 0) return [];
}
candidates.length = count;
return candidates;
}
21 changes: 20 additions & 1 deletion apps/desktop/src/lib/dataGrid/dataGridContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ export function createDataGridColumnContextMenuItems(options: {
visibleColumnCount?: number;
/** 当前被隐藏的列数量,用于决定是否显示「显示全部列」。 */
hiddenColumnCount?: number;
canHideIdenticalColumns?: boolean;
labels: Record<
"copyName" | "copyNames" | "details" | "copyAlterSql" | "databaseAscending" | "databaseDescending" | "localAscending" | "localDescending" | "clearSort" | "freezeToColumn" | "freezeSelectedColumns" | "unfreezeColumns" | "hideColumn" | "hideSelectedColumns" | "showAllColumnsMenu",
string
> &
Partial<Record<"freezeCurrentColumn" | "unfreezeCurrentColumn", string>>;
Partial<Record<"freezeCurrentColumn" | "unfreezeCurrentColumn" | "hideIdenticalColumns", string>>;
icons: Pick<DataGridContextMenuIcons, "copy" | "columnDetails" | "database" | "ascending" | "descending" | "clearSort">;
actions: {
copyName: () => void;
Expand All @@ -106,6 +107,7 @@ export function createDataGridColumnContextMenuItems(options: {
unfreezeColumns: () => void;
hideColumn: () => void;
hideSelectedColumns: () => void;
hideIdenticalColumns?: () => void;
showAllColumnsMenu: () => void;
};
filterSubmenu: DataGridContextMenuItem;
Expand Down Expand Up @@ -141,6 +143,13 @@ export function createDataGridColumnContextMenuItems(options: {
if (options.hasColumnSelection && selectedColumnCount > 1) {
items.push({ label: options.labels.hideSelectedColumns, action: options.actions.hideSelectedColumns, disabled: visibleColumnCount > 0 && selectedColumnCount >= visibleColumnCount });
}
if (options.labels.hideIdenticalColumns && options.actions.hideIdenticalColumns) {
items.push({
label: options.labels.hideIdenticalColumns,
action: options.actions.hideIdenticalColumns,
disabled: options.canHideIdenticalColumns === false,
});
}
if ((options.hiddenColumnCount ?? 0) > 0) {
items.push({ label: options.labels.showAllColumnsMenu, action: options.actions.showAllColumnsMenu });
}
Expand All @@ -153,6 +162,16 @@ export function createDataGridColumnContextMenuItems(options: {
}
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 });
} else if (options.labels.hideIdenticalColumns && options.actions.hideIdenticalColumns) {
items.push({ label: "", separator: true });
items.push({
label: options.labels.hideIdenticalColumns,
action: options.actions.hideIdenticalColumns,
disabled: options.canHideIdenticalColumns === false,
});
if ((options.hiddenColumnCount ?? 0) > 0) {
items.push({ label: options.labels.showAllColumnsMenu, action: options.actions.showAllColumnsMenu });
}
}
return items;
}
Expand Down
Loading
Loading