fix(grid): support multiline bulk edit values
This commit is contained in:
parent
fa5e55a376
commit
90bdacfd12
|
|
@ -109,7 +109,7 @@ import {
|
|||
visibleTransposeRecordWindow,
|
||||
} from "@/lib/dataGrid/dataGridTranspose";
|
||||
import { canApplyGridSelectionValue, canDeleteGridRowItem, canEditGridCellDetail, matchesRowStatusFilter, shouldShowQuickEntryDraftRow, type RowStatus, type RowStatusFilter } from "@/lib/dataGrid/gridRowStatus";
|
||||
import { displayCellValue, type CellValue } from "@/lib/dataGrid/cellValue";
|
||||
import { displayCellValue, firstLineCellDisplayValue, type CellValue } from "@/lib/dataGrid/cellValue";
|
||||
import { getApplicablePreviewActions } from "@/lib/dataGrid/resultPreviewRegistry";
|
||||
import "@/lib/dataGrid/geometryMapPreview";
|
||||
import { BINARY_CELL_DOWNLOAD_MODES, binaryCellDisplayText, binaryCellDownloadFileName, binaryCellDownloadPayload, canDownloadBinaryCellValue, downloadBinaryCellPayload, isBinaryCellColumnType, parseBinaryCellBytes, type BinaryCellDownloadMode } from "@/lib/dataGrid/binaryCellDownload";
|
||||
|
|
@ -8838,7 +8838,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
<template v-if="draftCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex)">
|
||||
<span class="text-muted-foreground/70 italic">{{ draftCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex) }}</span>
|
||||
</template>
|
||||
<template v-else>{{ cell.display }}</template>
|
||||
<template v-else>{{ firstLineCellDisplayValue(cell.display) }}</template>
|
||||
<div v-if="cellDetailButtonVisible(cell.recordIndex, cell.valueIndex)" class="absolute right-2 top-0.5 flex items-center gap-1">
|
||||
<LightDropdownMenu
|
||||
v-if="canQuickDownloadCellValue(cell.recordIndex, cell.valueIndex)"
|
||||
|
|
@ -9455,7 +9455,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
<template v-if="draftCellPlaceholder(item, col.actualColIdx)">
|
||||
<span class="text-muted-foreground/70 italic">{{ draftCellPlaceholder(item, col.actualColIdx) }}</span>
|
||||
</template>
|
||||
<template v-else>{{ formatCellCached(item.data[col.actualColIdx], col.actualColIdx) }}</template>
|
||||
<template v-else>{{ firstLineCellDisplayValue(formatCellCached(item.data[col.actualColIdx], col.actualColIdx)) }}</template>
|
||||
<div v-if="cellDetailButtonVisible(item.displayIndex, col.actualColIdx)" class="absolute right-2 top-0.5 flex items-center gap-1">
|
||||
<LightDropdownMenu
|
||||
v-if="canQuickDownloadCellValue(item.displayIndex, col.actualColIdx)"
|
||||
|
|
@ -10336,7 +10336,18 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
<p class="text-sm text-muted-foreground">
|
||||
{{ t("grid.bulkEditDescription", { count: selectedCellCount }) }}
|
||||
</p>
|
||||
<Input v-model="bulkEditValue" :placeholder="t('grid.bulkEditValuePlaceholder')" @keydown.enter.prevent="applyBulkEditValue" />
|
||||
<textarea
|
||||
v-model="bulkEditValue"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
rows="5"
|
||||
class="min-h-24 w-full min-w-0 resize-y rounded-[6px] border border-input bg-transparent px-2.5 py-1.5 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 md:text-sm"
|
||||
:placeholder="t('grid.bulkEditValuePlaceholder')"
|
||||
@keydown.ctrl.enter.prevent="applyBulkEditValue"
|
||||
@keydown.meta.enter.prevent="applyBulkEditValue"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="bulkEditDialogOpen = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { firstLineCellDisplayValue } from "@/lib/dataGrid/cellValue";
|
||||
|
||||
describe("firstLineCellDisplayValue", () => {
|
||||
it("shows only the first line in fixed-height cells", () => {
|
||||
expect(firstLineCellDisplayValue("111\n222")).toBe("111");
|
||||
expect(firstLineCellDisplayValue("111\r\n222")).toBe("111");
|
||||
expect(firstLineCellDisplayValue("111\r222")).toBe("111");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { CellValue } from "@/lib/dataGrid/cellValue";
|
||||
import { firstLineCellDisplayValue, type CellValue } from "@/lib/dataGrid/cellValue";
|
||||
import type { RowStatus } from "@/lib/dataGrid/gridRowStatus";
|
||||
import { DATA_GRID_DARK_SEARCH_COLORS, resolveDataGridPaintTheme, type DataGridPaintTheme } from "@/lib/dataGrid/dataGridPaintTheme";
|
||||
|
||||
|
|
@ -374,7 +374,8 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
|||
const textLeft = alignCanvasPixel(x + 12, dpr);
|
||||
const paddedMaxWidth = Math.max(0, x + colWidth - textLeft - 12);
|
||||
const isEditingThisCell = editingCell?.rowId === item.id && editingCell.col === actualColIdx;
|
||||
const displayText = isEditingThisCell ? "" : item.isDraft && value === null ? (draftCellPlaceholder ?? "") : formatCell(value, actualColIdx);
|
||||
const rawDisplayText = item.isDraft && value === null ? (draftCellPlaceholder ?? "") : formatCell(value, actualColIdx);
|
||||
const displayText = isEditingThisCell ? "" : firstLineCellDisplayValue(rawDisplayText);
|
||||
const needsTruncation = ctx.measureText(displayText).width > paddedMaxWidth;
|
||||
const textMaxWidth = needsTruncation ? Math.max(0, x + colWidth - textLeft) : paddedMaxWidth;
|
||||
const text = isEditingThisCell ? displayText : fitCanvasText(ctx, displayText, textMaxWidth - 12);
|
||||
|
|
|
|||
|
|
@ -6,3 +6,8 @@ export function displayCellValue(value: CellValue): string {
|
|||
if (typeof value === "object") return JSON.stringify(value);
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function firstLineCellDisplayValue(value: string): string {
|
||||
const lineBreakIndex = value.search(/\r\n|\r|\n/);
|
||||
return lineBreakIndex === -1 ? value : value.slice(0, lineBreakIndex);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2627,6 +2627,32 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_mysql_text_cell_line_breaks() {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
table_meta: DataGridTableMeta {
|
||||
schema: None,
|
||||
table_name: "employees".to_string(),
|
||||
primary_keys: vec!["id".to_string()],
|
||||
columns: Some(vec![column("id", "int(11)", false, None), column("name", "varchar(50)", true, None)]),
|
||||
},
|
||||
columns: vec!["id".to_string(), "name".to_string()],
|
||||
source_columns: None,
|
||||
rows: vec![vec![json!(2), json!("Ada")]],
|
||||
dirty_rows: vec![(0, vec![(1, json!("111\n222"))])],
|
||||
deleted_rows: vec![],
|
||||
new_rows: vec![],
|
||||
});
|
||||
|
||||
assert_eq!(result.validation_error, None);
|
||||
assert_eq!(result.statements, vec!["UPDATE `employees` SET `name` = '111\n222' WHERE `id` = 2;"]);
|
||||
assert_eq!(
|
||||
result.rollback_statements,
|
||||
vec!["UPDATE `employees` SET `name` = 'Ada' WHERE `id` = 2 AND BINARY `name` = '111\n222';"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepares_sqlserver_save_statements() {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
|
|
|
|||
Loading…
Reference in New Issue