feat(data-grid): add whitespace visualization toggle

This commit is contained in:
二丫讲梵
2026-09-12 17:40:02 +08:00
committed by GitHub
parent 1191118b5c
commit b6cfb3b728
18 changed files with 64 additions and 8 deletions
@@ -549,6 +549,7 @@ const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
const editRegexMaxMatchCount = ref(settingsStore.editorSettings.regexMaxMatchCount);
const editAutoCalculateTotalRows = ref(settingsStore.editorSettings.autoCalculateTotalRows);
const editFlatteningMultiLineText = ref(settingsStore.editorSettings.flatteningMultiLineText);
const editDataGridShowWhitespace = ref(settingsStore.editorSettings.dataGridShowWhitespace);
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
const editTableColumnTemplateDatabaseType = ref<DatabaseType>(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES[0] ?? "mysql");
const editSqlVariableSubstitutionEnabled = ref(settingsStore.editorSettings.sqlVariableSubstitutionEnabled);
@@ -767,6 +768,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
dataGridCellDetailButtonVisible: editDataGridCellDetailButtonVisible.value,
dataGridCrosshairHighlight: editDataGridCrosshairHighlight.value,
flatteningMultiLineText: editFlatteningMultiLineText.value,
dataGridShowWhitespace: editDataGridShowWhitespace.value,
pageSize: editPageSize.value,
tableOpenPageSize: editTableOpenPageSize.value,
queryResultMaxRowsEnabled: editQueryResultMaxRowsEnabled.value,
@@ -1265,6 +1267,7 @@ function syncEditorSettingsDraftFromStore() {
editDataGridCellDetailButtonVisible.value = settingsStore.editorSettings.dataGridCellDetailButtonVisible;
editDataGridCrosshairHighlight.value = settingsStore.editorSettings.dataGridCrosshairHighlight;
editFlatteningMultiLineText.value = settingsStore.editorSettings.flatteningMultiLineText;
editDataGridShowWhitespace.value = settingsStore.editorSettings.dataGridShowWhitespace;
editPageSize.value = settingsStore.editorSettings.pageSize;
editTableOpenPageSize.value = settingsStore.editorSettings.tableOpenPageSize;
editQueryResultMaxRowsEnabled.value = settingsStore.editorSettings.queryResultMaxRowsEnabled;
@@ -1390,6 +1393,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
regexMaxMatchCount: editRegexMaxMatchCount,
autoCalculateTotalRows: editAutoCalculateTotalRows,
flatteningMultiLineText: editFlatteningMultiLineText,
dataGridShowWhitespace: editDataGridShowWhitespace,
shortcuts: editShortcuts,
sqlFormatter: editSqlFormatter,
sidebarActivation: editSidebarActivation,
@@ -1752,6 +1756,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editDataGridCellDetailButtonVisible.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
editDataGridCrosshairHighlight.value = DEFAULT_EDITOR_SETTINGS.dataGridCrosshairHighlight;
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
editDataGridShowWhitespace.value = DEFAULT_EDITOR_SETTINGS.dataGridShowWhitespace;
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
editQueryResultMaxRowsEnabled.value = DEFAULT_EDITOR_SETTINGS.queryResultMaxRowsEnabled;
@@ -1840,6 +1845,7 @@ function resetAllDefaults() {
editDataGridCellDetailButtonVisible.value = DEFAULT_EDITOR_SETTINGS.dataGridCellDetailButtonVisible;
editDataGridCrosshairHighlight.value = DEFAULT_EDITOR_SETTINGS.dataGridCrosshairHighlight;
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
editDataGridShowWhitespace.value = DEFAULT_EDITOR_SETTINGS.dataGridShowWhitespace;
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
editQueryResultMaxRowsEnabled.value = DEFAULT_EDITOR_SETTINGS.queryResultMaxRowsEnabled;
@@ -7076,6 +7082,13 @@ onUnmounted(() => {
</div>
<Switch id="data-grid-crosshair-highlight" v-model="editDataGridCrosshairHighlight" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="data-grid-show-whitespace">{{ t("settings.dataGridShowWhitespace") }}</Label>
<p class="text-xs text-muted-foreground">{{ t("settings.dataGridShowWhitespaceDescription") }}</p>
</div>
<Switch id="data-grid-show-whitespace" v-model="editDataGridShowWhitespace" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="flattening-multi-line">
+12 -6
View File
@@ -138,7 +138,7 @@ import {
type TransposeScrollAlignment,
} from "@/lib/dataGrid/dataGridTranspose";
import { canApplyGridSelectionValue, canDeleteGridRowItem, canEditGridCellDetail, matchesRowStatusFilter, shouldShowQuickEntryDraftRow, type RowStatus, type RowStatusFilter } from "@/lib/dataGrid/gridRowStatus";
import { displayCellValue, firstLineCellDisplayValue, limitDataGridCellDisplay, SQLSERVER_DATA_GRID_CELL_DISPLAY_MAX_LENGTH, type CellValue } from "@/lib/dataGrid/cellValue";
import { displayCellValue, firstLineCellDisplayValue, gridCellDisplayValue, limitDataGridCellDisplay, SQLSERVER_DATA_GRID_CELL_DISPLAY_MAX_LENGTH, type CellValue } from "@/lib/dataGrid/cellValue";
import { cellExternalUrl } from "@/lib/dataGrid/cellExternalUrl";
import { getApplicablePreviewActions, type PreviewAction } from "@/lib/dataGrid/resultPreviewRegistry";
import "@/lib/dataGrid/geometryMapPreview";
@@ -751,6 +751,7 @@ const infiniteScrollEnabled = computed(() => props.paginationEnabled && settings
const queryResultMaxRows = computed(() => effectiveQueryResultMaxRows(settingsStore.editorSettings.queryResultMaxRowsEnabled, settingsStore.editorSettings.queryResultMaxRows));
const paginationMaxRows = computed(() => (isResultsContext.value ? queryResultMaxRows.value : undefined));
const infiniteScrollMaxRows = computed(() => continuousQueryResultMaxRows(settingsStore.editorSettings.queryResultMaxRowsEnabled, settingsStore.editorSettings.queryResultMaxRows));
const showWhitespaceEnabled = computed(() => settingsStore.editorSettings.dataGridShowWhitespace);
const flatteningMultiLineEnabled = computed(() => settingsStore.editorSettings.flatteningMultiLineText);
const expandedCellEditor = ref<{ rowId: number; col: number } | null>(null);
const readonlyTextCell = ref<{
@@ -2225,7 +2226,8 @@ let columnFormatterForWidth: ((columnIndex: number) => ColumnFormatterConfig | u
function columnWidthDisplayValue(value: CellValue, columnIndex: number): CellValue {
const formatter = columnFormatterForWidth?.(columnIndex);
return formatter ? applyColumnFormatter(value, formatter) : value;
const display = formatter ? applyColumnFormatter(value, formatter) : value;
return showWhitespaceEnabled.value && typeof display === "string" ? gridCellDisplayValue(display, flatteningMultiLineEnabled.value, true) : display;
}
const { initColumnWidths, onResizeStart, autoFitColumn, renderedColumnWidths, totalWidth, columnVars, getIsResizing } = useDataGridColumnResize({
@@ -6848,9 +6850,11 @@ async function onCanvasDblClick(event: MouseEvent) {
function canvasCellContentOverflows(item: RowItem, actualColIdx: number, visibleColIdx: number): boolean {
const cellWidth = renderedColumnWidths.value[visibleColIdx] ?? 0;
if (cellWidth <= 0) return false;
const displayText = formatCellCached(item.data[actualColIdx], actualColIdx);
const formattedText = formatCellCached(item.data[actualColIdx], actualColIdx);
const editText = cellEditorTextForValue(item.data[actualColIdx], actualColIdx);
if (editText.includes("\n") || editText.includes("\r") || editText.length > displayText.length) return true;
// Detect truncated previews before visual whitespace markers add characters.
if (editText.includes("\n") || editText.includes("\r") || editText.length > formattedText.length) return true;
const displayText = showWhitespaceEnabled.value ? gridCellDisplayValue(formattedText, flatteningMultiLineEnabled.value, true) : formattedText;
const canvas = activeCanvasSurface();
const context = canvas?.getContext("2d");
if (!context) {
@@ -7086,6 +7090,7 @@ function drawCanvasGrid() {
columnIsBoolean: isBooleanGridColumn,
booleanDisplayMode: booleanDisplayMode.value,
flatteningMultiLineEnabled: flatteningMultiLineEnabled.value,
showWhitespace: showWhitespaceEnabled.value,
});
flipCanvasSurface();
}
@@ -7110,6 +7115,7 @@ watch(showDataGridTopbar, () => nextTick(observeDataGridTopbarWidth), {
watch(columnAligns, () => scheduleCanvasDraw());
watch(booleanDisplayMode, () => scheduleCanvasDraw());
watch(flatteningMultiLineEnabled, () => scheduleCanvasDraw());
watch(showWhitespaceEnabled, () => scheduleCanvasDraw());
watch(colorizeDataGridCellTypes, () => scheduleCanvasDraw());
watch(
[
@@ -11993,7 +11999,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
<template v-if="newRowCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex)">
<span class="text-muted-foreground/70 italic">{{ firstLineCellDisplayValue(newRowCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex) ?? "", flatteningMultiLineEnabled) }}</span>
</template>
<template v-else>{{ firstLineCellDisplayValue(cell.display, flatteningMultiLineEnabled) }}</template>
<template v-else>{{ gridCellDisplayValue(cell.display, flatteningMultiLineEnabled, showWhitespaceEnabled) }}</template>
<div v-if="cellDetailButtonVisible(cell.recordIndex, cell.valueIndex)" class="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1">
<LightDropdownMenu
v-if="canQuickDownloadCellValue(cell.recordIndex, cell.valueIndex)"
@@ -12913,7 +12919,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
<template v-if="newRowCellPlaceholder(item, col.actualColIdx)">
<span class="text-muted-foreground/70 italic">{{ firstLineCellDisplayValue(newRowCellPlaceholder(item, col.actualColIdx) ?? "", flatteningMultiLineEnabled) }}</span>
</template>
<template v-else>{{ firstLineCellDisplayValue(formatGridItemCell(item, col.actualColIdx), flatteningMultiLineEnabled) }}</template>
<template v-else>{{ gridCellDisplayValue(formatGridItemCell(item, col.actualColIdx), flatteningMultiLineEnabled, showWhitespaceEnabled) }}</template>
<div v-if="cellDetailButtonVisible(item.displayIndex, col.actualColIdx)" class="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1">
<LightDropdownMenu
v-if="canQuickDownloadCellValue(item.displayIndex, col.actualColIdx)"
+2
View File
@@ -6941,6 +6941,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Ümumi sətir sayını avtomatik hesabla",
autoCalculateTotalRowsDescription: "Uyğun gələn sətirlərin ümumi sayını göstərmək üçün hər sorğudan sonra COUNT(*) avtomatik işlədilir. Böyük sorğuların sürətini qorumaq üçün standart olaraq söndürülüb — ehtiyac olduqda nəticənin alt hissəsindən hesablayın.",
infiniteScrollDescription: "Aşağıya sürüşdürdükdə növbəti toplunu avtomatik yüklə.",
dataGridShowWhitespace: "Boşluq simvollarını göstər",
dataGridShowWhitespaceDescription: "Verilənlər xanalarında boşluqları ·, tabulyasiyaları → kimi göstərir. Kopyalanan və redaktə edilən dəyərlər dəyişmir.",
flatteningMultiLineText: "Çoxsətirli mətnin bir sətrə birləşdirilməsi",
flatteningMultiLineTextDescription: "Çoxsətirli mətn bir sətirdə göstərilir, sətir keçidləri ¶ kimi göstərilir.",
regexMaxMatchCount: "Hamısını seçmək üçün maksimum uyğunluq sayı",
+2
View File
@@ -6976,6 +6976,8 @@ export default {
autoCalculateTotalRows: "Auto-calculate total row count",
autoCalculateTotalRowsDescription: "Run COUNT(*) automatically after each query to show the total matching rows. Off by default to keep large queries fast — calculate it on demand from the result footer.",
infiniteScrollDescription: "Automatically load the next batch when scrolling to the bottom.",
dataGridShowWhitespace: "Show whitespace",
dataGridShowWhitespaceDescription: "Show spaces as · and tabs as → in data cells. Copied and edited values remain unchanged.",
flatteningMultiLineText: "Flattening Multi-line Text",
flatteningMultiLineTextDescription: "Multi-line text merged into single line display, with line breaks shown as ¶ .",
regexMaxMatchCount: "Maximum select-all matches",
+2
View File
@@ -6548,6 +6548,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Calcular automáticamente el total de filas",
autoCalculateTotalRowsDescription: "Ejecuta COUNT(*) automáticamente tras cada consulta para mostrar el total de filas coincidentes. Desactivado por defecto para mantener rápidas las consultas grandes; puedes calcularlo cuando quieras desde el pie de resultados.",
infiniteScrollDescription: "Carga automáticamente el siguiente lote al desplazarse hasta el final.",
dataGridShowWhitespace: "Mostrar espacios en blanco",
dataGridShowWhitespaceDescription: "Mostrar espacios como · y tabulaciones como → en las celdas. Los valores copiados y editados no cambian.",
flatteningMultiLineText: "Aplastar texto multilínea",
flatteningMultiLineTextDescription: "El texto multilínea se fusiona en una sola línea de visualización, con los saltos de línea mostrados como ¶ .",
regexMaxMatchCount: "Máximo de coincidencias al seleccionar todas",
+2
View File
@@ -6548,6 +6548,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Calcola automaticamente il totale delle righe",
autoCalculateTotalRowsDescription: "Esegue COUNT(*) automaticamente dopo ogni query per mostrare il totale delle righe corrispondenti. Disattivato per impostazione predefinita per mantenere veloci le query grandi; puoi calcolarlo all'occorrenza dal piè di pagina dei risultati.",
infiniteScrollDescription: "Carica automaticamente il blocco successivo quando scorri fino in fondo.",
dataGridShowWhitespace: "Mostra spazi bianchi",
dataGridShowWhitespaceDescription: "Mostra gli spazi come · e le tabulazioni come → nelle celle. I valori copiati e modificati restano invariati.",
flatteningMultiLineText: "Pianifica automaticamente i testi multi-riga",
flatteningMultiLineTextDescription: "Se attivo, i testi multi-riga nei risultati vengono automaticamente pianificati in una singola colonna.",
regexMaxMatchCount: "Numero massimo di corrispondenze per Seleziona tutte",
+2
View File
@@ -7232,6 +7232,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "総行数を自動計算",
autoCalculateTotalRowsDescription: "クエリごとに COUNT(*) を自動実行し、一致する総行数を表示します。大きなクエリを高速に保つため既定はオフです。結果フッターから必要に応じて計算できます。",
infiniteScrollDescription: "末尾までスクロールしたときに、次のデータを自動的に読み込みます。",
dataGridShowWhitespace: "空白文字を表示",
dataGridShowWhitespaceDescription: "データセルのスペースを ·、タブを → で表示します。コピーや編集する値は変わりません。",
flatteningMultiLineText: "多行テキストを展開",
flatteningMultiLineTextDescription: "複数行のテキストを1行に結合して表示し、改行文字は¶として表示する。",
regexMaxMatchCount: "すべての一致を選択する最大数",
+2
View File
@@ -6289,6 +6289,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "전체 행 수 자동 집계",
autoCalculateTotalRowsDescription: "각 쿼리 후 자동으로 COUNT(*)를 실행하여 일치하는 전체 행 수를 표시합니다. 대규모 쿼리를 빠르게 유지하기 위해 기본적으로 꺼져 있으며, 결과 푸터에서 요청 시 집계할 수 있습니다.",
infiniteScrollDescription: "목록 하단으로 스크롤할 때 다음 데이터 묶음을 자동으로 불러옵니다.",
dataGridShowWhitespace: "공백 문자 표시",
dataGridShowWhitespaceDescription: "데이터 셀의 공백을 ·, 탭을 →로 표시합니다. 복사하거나 편집하는 값은 변경되지 않습니다.",
flatteningMultiLineText: "멀티라인 텍스트 단순화",
flatteningMultiLineTextDescription: "다중 행 텍스트를 단일 행으로 병합하여 표시하고, 개행 문자는 ¶로 표시합니다.",
tableColumnTemplateFields: "새 테이블 미리 정의된 필드",
+2
View File
@@ -6550,6 +6550,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Calcular automaticamente o total de linhas",
autoCalculateTotalRowsDescription: "Executa COUNT(*) automaticamente após cada consulta para mostrar o total de linhas correspondentes. Desativado por padrão para manter consultas grandes rápidas; você pode calculá-lo quando quiser no rodapé dos resultados.",
infiniteScrollDescription: "Carregar automaticamente o próximo lote ao rolar até o final.",
dataGridShowWhitespace: "Mostrar espaços em branco",
dataGridShowWhitespaceDescription: "Mostrar espaços como · e tabulações como → nas células. Os valores copiados e editados permanecem inalterados.",
flatteningMultiLineText: "Achatar texto multilinha",
flatteningMultiLineTextDescription: "Exibir texto multilinha em uma única linha.",
regexMaxMatchCount: "Máximo de correspondências para selecionar todas",
+2
View File
@@ -6823,6 +6823,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Toplam satır sayısını otomatik hesapla",
autoCalculateTotalRowsDescription: "Eşleşen toplam satırı göstermek için her sorgunun ardından otomatik olarak COUNT(*) çalıştır. Büyük sorguların hızlı kalması için varsayılan olarak kapalıdır — sonuç alt bilgisinden istediğinizde hesaplayın.",
infiniteScrollDescription: "En alta kaydırıldığında sonraki grubu otomatik yükle.",
dataGridShowWhitespace: "Boşluk karakterlerini göster",
dataGridShowWhitespaceDescription: "Veri hücrelerinde boşlukları ·, sekmeleri → olarak gösterir. Kopyalanan ve düzenlenen değerler değişmez.",
flatteningMultiLineText: "Çok Satırlı Metni Düzleştirme",
flatteningMultiLineTextDescription: "Çok satırlı metin, satır sonları ¶ ile gösterilerek tek satırda birleştirilir.",
regexMaxMatchCount: 'En fazla "tümünü seç" eşleşmesi',
+2
View File
@@ -6959,6 +6959,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "自动统计总行数",
autoCalculateTotalRowsDescription: "每次查询后自动执行 COUNT(*) 显示匹配的总行数。默认关闭以保证大查询速度 —— 可在结果栏按需手动统计。",
infiniteScrollDescription: "滚动到底部时自动加载下一批数据,无需手动翻页或加载。",
dataGridShowWhitespace: "显示空白字符",
dataGridShowWhitespaceDescription: "在数据单元格中将空格显示为 ·,制表符显示为 →。复制和编辑的值保持不变。",
flatteningMultiLineText: "扁平化多行文本",
flatteningMultiLineTextDescription: "多行文本合并为单行显示,换行符显示为 ¶ 。",
regexMaxMatchCount: "全部选中最大数量",
+2
View File
@@ -5863,6 +5863,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "自動統計總筆數",
autoCalculateTotalRowsDescription: "每次查詢後自動執行 COUNT(*) 顯示符合的總筆數。預設關閉以確保大型查詢速度 —— 可在結果列按需手動統計。",
infiniteScrollDescription: "滾動到底部時自動載入下一批資料,無需手動翻頁或載入。",
dataGridShowWhitespace: "顯示空白字元",
dataGridShowWhitespaceDescription: "在資料儲存格中將空格顯示為 ·,定位字元顯示為 →。複製和編輯的值保持不變。",
flatteningMultiLineText: "扁平多行文本",
flatteningMultiLineTextDescription: "多行文本合併為單行顯示,換行符顯示為 ¶ 。",
regexMaxMatchCount: "全部選取最大數量",
@@ -1,4 +1,4 @@
import { firstLineCellDisplayValue, type CellValue } from "@/lib/dataGrid/cellValue";
import { gridCellDisplayValue, type CellValue } from "@/lib/dataGrid/cellValue";
import { BOOLEAN_CHECKBOX_SIZE, isBooleanCellValue, normalizeBooleanCellValue } from "@/lib/dataGrid/dataGridBooleanColumn";
import { resolveDataGridCellTextRole } from "@/lib/dataGrid/dataGridCellTextVisual";
import type { DataGridTypeVisualKind } from "@/lib/dataGrid/dataGridColumnType";
@@ -100,6 +100,7 @@ export interface DrawCanvasDataGridOptions {
rightAlignedActionCell?: CanvasRightAlignedActionCell | null;
booleanDisplayMode?: "checkbox" | "dropdown";
flatteningMultiLineEnabled: boolean;
showWhitespace?: boolean;
}
type NumericCanvasContext = CanvasRenderingContext2D & {
@@ -361,6 +362,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
columnIsBoolean,
booleanDisplayMode = "dropdown",
flatteningMultiLineEnabled,
showWhitespace = false,
} = options;
// 框选热路径:整次绘制只判断一次。常见情况(单矩形 / 多列且每段都是多格)可跳过逐格 kind 查询
const paintSelectionOuterFrame = dataGridSelectionUsesOuterFrame(selectionFrames);
@@ -596,7 +598,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
}
} else {
const rawDisplayText = (value === null ? newRowCellPlaceholder?.(item, actualColIdx) : null) ?? formatCell(value, actualColIdx, item);
const displayText = isEditingThisCell ? "" : firstLineCellDisplayValue(rawDisplayText, flatteningMultiLineEnabled);
const displayText = isEditingThisCell ? "" : gridCellDisplayValue(rawDisplayText, flatteningMultiLineEnabled, showWhitespace && value !== null);
const text = isEditingThisCell ? displayText : fitCanvasText(ctx, displayText, cellMaxWidth, isBooleanNullCell ? "left" : isRightAlign ? "right" : "left");
const anchorX = isBooleanNullCell ? alignCanvasPixel(drawX + colWidth / 2, scaleX) : textAnchorX;
ctx.fillText(text, anchorX, textY);
@@ -44,3 +44,10 @@ export function limitDataGridCellDisplay(value: string, maxLength = DATA_GRID_CE
if (value.length <= maxLength) return value;
return `${value.slice(0, maxLength)}...`;
}
/** Visual-only cell text; never use whitespace markers for editing or serialization. */
export function gridCellDisplayValue(value: string, flatteningMultiLine: boolean, showWhitespace: boolean): string {
const text = firstLineCellDisplayValue(value, flatteningMultiLine);
// Hair spaces keep adjacent dots legible in both DOM and Canvas rendering.
return showWhitespace ? text.replace(/[ \t]/g, (character) => (character === " " ? "\u200a·\u200a" : "→")) : text;
}
@@ -66,6 +66,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"regexMaxMatchCount",
"autoCalculateTotalRows",
"flatteningMultiLineText",
"dataGridShowWhitespace",
"tableColumnTemplateFields",
"shortcuts",
"sqlFormatter",
@@ -211,6 +211,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "data-grid-quick-entry", category: "data", titleKey: "settings.dataGridQuickEntry", descriptionKey: "settings.dataGridQuickEntryDescription", targetId: "data" },
{ id: "data-grid-filter-view", category: "data", titleKey: "settings.dataGridFilterView", descriptionKey: "settings.dataGridFilterViewDescription", targetId: "data-grid-filter-view" },
{ id: "data-grid-flattening-multi-line", category: "data", titleKey: "settings.flatteningMultiLineText", descriptionKey: "settings.flatteningMultiLineTextDescription", targetId: "data" },
{ id: "data-grid-show-whitespace", category: "data", titleKey: "settings.dataGridShowWhitespace", descriptionKey: "settings.dataGridShowWhitespaceDescription", targetId: "data" },
{ id: "sql-file-editor-max-mb", category: "editor", titleKey: "settings.externalSqlEditorMaxMb", descriptionKey: "settings.externalSqlEditorMaxMbDescription", targetId: "editor-sql-file" },
{ id: "sql-file-web-upload-max-mb", category: "data", titleKey: "settings.webSqlFileUploadMaxMb", descriptionKey: "settings.webSqlFileUploadMaxMbDescription", targetId: "data-sql-file-upload", visible: (context) => context.isWeb },
{ id: "appearance-toolbar", category: "appearance", titleKey: "settings.toolbarTitle", descriptionKey: "settings.toolbarHiddenHint", targetId: "appearance" },
@@ -149,6 +149,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
"dataGridCellDetailButtonVisible",
"dataGridCrosshairHighlight",
"flatteningMultiLineText",
"dataGridShowWhitespace",
"pageSize",
"tableOpenPageSize",
"queryResultMaxRowsEnabled",
@@ -259,6 +260,7 @@ const PASS_THROUGH_BOOLEAN_KEYS = [
"compactColumnHeaderActions",
"dataGridQuickEntry",
"flatteningMultiLineText",
"dataGridShowWhitespace",
"infiniteScroll",
"autoCalculateTotalRows",
"autoSelectActiveSidebarNode",
+4
View File
@@ -795,6 +795,7 @@ export interface EditorSettings {
/** Preserved for downgrade compatibility; current clients use queryResultMaxRows. */
infiniteScrollMaxRows: number;
flatteningMultiLineText: boolean;
dataGridShowWhitespace: boolean;
regexMaxMatchCount: number;
autoCalculateTotalRows: boolean;
mongoViewMode: "document" | "table";
@@ -1032,6 +1033,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
infiniteScroll: false,
infiniteScrollMaxRows: 5000,
flatteningMultiLineText: false,
dataGridShowWhitespace: false,
regexMaxMatchCount: 1000,
autoCalculateTotalRows: false,
mongoViewMode: "document",
@@ -1490,6 +1492,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
infiniteScroll: settings.infiniteScroll ?? DEFAULT_EDITOR_SETTINGS.infiniteScroll,
infiniteScrollMaxRows: typeof settings.infiniteScrollMaxRows === "number" && settings.infiniteScrollMaxRows >= 1000 && settings.infiniteScrollMaxRows <= 50000 ? Math.round(settings.infiniteScrollMaxRows) : DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows,
flatteningMultiLineText: settings.flatteningMultiLineText ?? DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText,
dataGridShowWhitespace: settings.dataGridShowWhitespace ?? DEFAULT_EDITOR_SETTINGS.dataGridShowWhitespace,
regexMaxMatchCount: typeof settings.regexMaxMatchCount === "number" && Number.isFinite(settings.regexMaxMatchCount) && settings.regexMaxMatchCount >= 100 && settings.regexMaxMatchCount <= 10000 ? Math.round(settings.regexMaxMatchCount) : DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount,
autoCalculateTotalRows: settings.autoCalculateTotalRows ?? DEFAULT_EDITOR_SETTINGS.autoCalculateTotalRows,
mongoViewMode: settings.mongoViewMode === "table" ? "table" : DEFAULT_EDITOR_SETTINGS.mongoViewMode,
@@ -2347,6 +2350,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.completionTriggerMode !== undefined) editorSettings.value.completionTriggerMode = normalizeCompletionTriggerMode(partial.completionTriggerMode);
if (partial.defaultTransactionMode !== undefined) editorSettings.value.defaultTransactionMode = normalizeDefaultTransactionMode(partial.defaultTransactionMode);
if (partial.flatteningMultiLineText !== undefined) editorSettings.value.flatteningMultiLineText = partial.flatteningMultiLineText;
if (partial.dataGridShowWhitespace !== undefined) editorSettings.value.dataGridShowWhitespace = partial.dataGridShowWhitespace;
}
function updateEditorSettings(partial: Partial<EditorSettings>) {