From f45fcdb69d514ada29dc3e7b732f6770730164a6 Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 24 Aug 2026 20:34:04 +0800 Subject: [PATCH] feat(grid): preview text stored in MySQL BLOB cells --- apps/desktop/src/components/grid/DataGrid.vue | 16 +-- .../grid/DataGridCellDetailDialog.vue | 11 ++- .../grid/DataGridCellDetailPanel.vue | 12 ++- .../grid/__tests__/DataGridSurfaces.spec.ts | 99 +++++++++++++++++++ .../src/lib/dataGrid/binaryCellDownload.ts | 32 +++++- .../src/lib/dataGrid/dataGridCellCoercion.ts | 15 +++ packages/app-tests/binaryCellDownload.test.ts | 29 ++++++ .../app-tests/dataGridCellCoercion.test.ts | 18 ++++ 8 files changed, 217 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 1d343bd61d..e0408d99e2 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -141,6 +141,8 @@ import { canDownloadBinaryCellValue, downloadBinaryCellPayload, formatBinaryCellByteSize, + binaryCellUtf8Text, + isBlobCellColumnType, isBinaryCellColumnType, openBinaryCellFile, parseBinaryCellBytes, @@ -4120,8 +4122,8 @@ function cellEditContentNeedsExpandedEditor(options: { displayText: string; edit function cellEditorTextForValue(value: CellValue | undefined, columnIndex: number): string { return dataGridCellEditorText({ value: value ?? null, - databaseType: props.databaseType, - columnInfo: tableColumnForGridColumn(columnIndex), + databaseType: resolvedDatabaseType.value, + columnInfo: tableColumnForGridColumn(columnIndex) ?? resultColumnInfoForGridColumn(columnIndex), }); } @@ -4185,7 +4187,7 @@ function isIoTDBTimestampColumn(columnIndex: number): boolean { } function inlineCellEditorText(value: CellValue, columnIndex: number): string { - const columnInfo = tableColumnForGridColumn(columnIndex); + const columnInfo = tableColumnForGridColumn(columnIndex) ?? resultColumnInfoForGridColumn(columnIndex); const columnType = props.result.column_types?.[columnIndex] ?? columnInfo?.data_type; return ( formatIoTDBTimestampEditorValue(value, resolvedDatabaseType.value, columnType, resolvedConnectionConfig.value?.url_params) ?? @@ -6054,8 +6056,8 @@ watch(activeCellDetail, (detail) => { } const value = dataGridCellEditorText({ value: detail.value, - databaseType: props.databaseType, - columnInfo: tableColumnForGridColumn(detail.colIndex), + databaseType: resolvedDatabaseType.value, + columnInfo: tableColumnForGridColumn(detail.colIndex) ?? resultColumnInfoForGridColumn(detail.colIndex), }); detailEditValue.value = value; detailEditOriginalValue.value = value; @@ -9100,7 +9102,7 @@ async function copyDetailValue() { if (!initialDetail || !(await hydrateLargeValueCell(initialDetail.rowId, initialDetail.colIndex))) return; const detail = activeCellDetail.value; if (!detail) return; - const text = detail.value === null ? "" : displayCellValue(detail.value); + const text = detail.value === null ? "" : isBlobCellColumnType(detail.type) ? (binaryCellUtf8Text(detail.value, detail.type, resolvedDatabaseType.value) ?? displayCellValue(detail.value)) : displayCellValue(detail.value); copyText(text); } @@ -13356,6 +13358,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] { :import-binary-value="importDetailBinaryValue" :open-image-preview="openImagePreview" :can-copy-sql-condition="canCopyPreparedDetailSqlCondition" + :database-type="resolvedDatabaseType" @start-edit="startDetailEdit" @compact-json="compactDetailJson" @toggle-formatted="toggleCellDetailJsonFormatted" @@ -13545,6 +13548,7 @@ function currentGridContextMenuItems(): ContextMenuItem[] { :download-binary-value="downloadDetailBinaryValue" :can-import-binary-value="canImportDetailBinaryValue" :import-binary-value="importDetailBinaryValue" + :database-type="resolvedDatabaseType" @edit="openDialogCellInSidePanel" /> diff --git a/apps/desktop/src/components/grid/DataGridCellDetailDialog.vue b/apps/desktop/src/components/grid/DataGridCellDetailDialog.vue index c8bdc3882f..05ce03f866 100644 --- a/apps/desktop/src/components/grid/DataGridCellDetailDialog.vue +++ b/apps/desktop/src/components/grid/DataGridCellDetailDialog.vue @@ -9,10 +9,11 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { useCellDetailEditor, type UseCellDetailEditorReturn } from "@/composables/useCellDetailEditor"; import { useTheme } from "@/composables/useTheme"; import { useSettingsStore } from "@/stores/settingsStore"; -import { BINARY_CELL_DOWNLOAD_MODES, type BinaryCellDownloadMode } from "@/lib/dataGrid/binaryCellDownload"; +import { BINARY_CELL_DOWNLOAD_MODES, binaryCellUtf8Text, isBlobCellColumnType, type BinaryCellDownloadMode } from "@/lib/dataGrid/binaryCellDownload"; import { isGeometryColumnType } from "@/lib/dataGrid/cellDetailPresentation"; import { isHexGeometry, renderWktOnCanvas } from "@/lib/dataGrid/geometryPreview"; import type { DataGridCellDetail } from "@/lib/dataGrid/dataGridDetail"; +import type { DatabaseType } from "@/types/database"; const { t } = useI18n(); const settingsStore = useSettingsStore(); @@ -27,6 +28,8 @@ const props = defineProps<{ downloadBinaryValue: (detail: DataGridCellDetail | null, mode: BinaryCellDownloadMode) => void | Promise; canImportBinaryValue: (detail: DataGridCellDetail | null) => boolean; importBinaryValue: (detail: DataGridCellDetail | null) => void | Promise; + /** BLOB 文本预览与编辑写回一致,仅在 MySQL 连接开启。 */ + databaseType?: DatabaseType; }>(); const emit = defineEmits<{ @@ -41,6 +44,8 @@ let jsonPreviewEditor: UseCellDetailEditorReturn | null = null; const jsonFormatted = computed(() => settingsStore.editorSettings.cellDetailJsonFormatted); const jsonView = computed(() => jsonFormatted.value && !!props.detail?.formattedJson); +const binaryTextPreview = computed(() => (props.detail && isBlobCellColumnType(props.detail.type) ? binaryCellUtf8Text(props.detail.value, props.detail.type, props.databaseType) : null)); +const presentedValuePreview = computed(() => (binaryTextPreview.value === null ? props.detail?.rawValuePreview : props.detail?.displayValuePreview) ?? ""); function toggleJsonFormatted() { settingsStore.updateEditorSettings({ cellDetailJsonFormatted: !jsonFormatted.value }); @@ -53,7 +58,7 @@ function copyCurrentValue() { props.copyText(detail.formattedJson); return; } - props.copyText(detail.value === null ? "" : detail.rawValue); + props.copyText(detail.value === null ? "" : (binaryTextPreview.value ?? detail.rawValue)); } function copyColumnName() { @@ -186,7 +191,7 @@ watch(
-
{{ detail.rawValuePreview }}
+
{{ presentedValuePreview }}
{{ t("grid.largeValuePreviewHint", { count: detail.rawValuePreview.length }) }}
diff --git a/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue b/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue index f673ce6ea8..fcd54fb690 100644 --- a/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue +++ b/apps/desktop/src/components/grid/DataGridCellDetailPanel.vue @@ -1,5 +1,5 @@