diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 02822a996..32f5cae3f 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -66,6 +66,7 @@ import { import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue"; import ImagePreviewDialog from "@/components/grid/ImagePreviewDialog.vue"; @@ -98,6 +99,13 @@ import { import { matchesRowStatusFilter, type RowStatus, type RowStatusFilter } from "@/lib/gridRowStatus"; import { displayCellValue, type CellValue } from "@/lib/cellValue"; import { cellImagePreviewUrl } from "@/lib/cellImageUrl"; +import { + cellDetailEditorText, + defaultCellDetailTab, + valueEditorActions, + visibleCellDetailTabs, + type CellDetailTab, +} from "@/lib/cellDetailPresentation"; import { applyColumnFormatter, buildColumnFormatterKey, @@ -272,6 +280,7 @@ function typeColorClass(t: string): string { const contextCell = ref<{ rowId: number; rowIndex: number; col: number } | null>(null); const detailCell = ref<{ rowIndex: number; col: number } | null>(null); const showCellDetail = ref(false); +const activeCellDetailTab = ref(defaultCellDetailTab()); const detailWidth = ref(320); const isResizingDetail = ref(false); const imagePreviewOpen = ref(false); @@ -1731,6 +1740,30 @@ const activeCellDetail = computed(() => { }; }); +const activeCellDetailTabs = computed(() => { + const detail = activeCellDetail.value; + return visibleCellDetailTabs({ isEditable: !!detail?.isEditable }); +}); + +watch(activeCellDetailTabs, (tabs) => { + if (!tabs.includes(activeCellDetailTab.value)) { + activeCellDetailTab.value = defaultCellDetailTab(); + } +}); + +watch(activeCellDetailTab, (tab) => { + if (tab === "valueEditor") { + startDetailEdit(); + } else { + resetDetailEdit(); + } +}); + +const activeValueEditorActions = computed(() => { + const detail = activeCellDetail.value; + return valueEditorActions({ canSetNull: !!detail?.isEditable && detail.value !== null }); +}); + const detailEditValue = ref(""); const isEditingDetail = ref(false); const detailTemporalEditorKind = computed(() => { @@ -1752,8 +1785,7 @@ function closeCellDetails() { function startDetailEdit() { const detail = activeCellDetail.value; if (!detail || !detail.isEditable) return; - detailEditValue.value = - detail.value === null ? "" : typeof detail.value === "object" ? JSON.stringify(detail.value) : String(detail.value); + detailEditValue.value = cellDetailEditorText(detail.value); isEditingDetail.value = true; } @@ -1794,6 +1826,51 @@ function cancelDetailEdit() { resetDetailEdit(); } +function cancelValueEditorEdit() { + const detail = activeCellDetail.value; + if (!detail || !detail.isEditable) return; + detailEditValue.value = cellDetailEditorText(detail.value); + isEditingDetail.value = true; +} + +function commitValueEditorEdit() { + commitDetailEdit(); + if (activeCellDetailTab.value === "valueEditor") { + isEditingDetail.value = true; + } +} + +function restoreDetailOriginalValue() { + const detail = activeCellDetail.value; + if (!detail || !detail.isEditable) return; + + const item = getRowItem(detail.rowId); + if (!item || item.isDeleted) return; + + let restoredValue: CellValue = null; + + if (item.isNew && item.newIndex !== undefined) { + newRows.value[item.newIndex][detail.colIndex] = null; + newRows.value = [...newRows.value]; + } else if (item.sourceIndex !== undefined) { + restoredValue = props.result.rows[item.sourceIndex]?.[detail.colIndex] ?? null; + const rowChanges = dirtyRows.value.get(item.sourceIndex); + rowChanges?.delete(detail.colIndex); + if (rowChanges?.size === 0) dirtyRows.value.delete(item.sourceIndex); + dirtyRows.value = new Map(dirtyRows.value); + } + + detailEditValue.value = cellDetailEditorText(restoredValue); + isEditingDetail.value = activeCellDetailTab.value === "valueEditor"; + detailCell.value = { ...detailCell.value! }; +} + +function setValueEditorNull() { + setDetailNull(); + detailEditValue.value = cellDetailEditorText(null); + isEditingDetail.value = activeCellDetailTab.value === "valueEditor"; +} + function setDetailNull() { const detail = activeCellDetail.value; if (!detail || !detail.isEditable) return; @@ -2036,6 +2113,7 @@ const { function showCellDetails(rowIndex: number, colIndex: number) { resetDetailEdit(); detailCell.value = { rowIndex, col: colIndex }; + activeCellDetailTab.value = defaultCellDetailTab(); showCellDetail.value = true; } @@ -3972,149 +4050,219 @@ defineExpose({ -
-
-
{{ t("grid.columnName") }}
-
{{ activeCellDetail.column }}
-
-
-
-
{{ t("grid.rowNumber") }}
-
{{ activeCellDetail.rowNumber }}
-
-
-
{{ t("grid.columnType") }}
-
+
+ + {{ t("grid.cellDetails") }} + - {{ activeCellDetail.type || "-" }} + {{ t("grid.valueEditor") }} + + +
+ + +
+
+
{{ t("grid.columnName") }}
+
{{ activeCellDetail.column }}
+
+
+
+
{{ t("grid.rowNumber") }}
+
{{ activeCellDetail.rowNumber }}
+
+
+
{{ t("grid.columnType") }}
+
+ {{ activeCellDetail.type || "-" }} +
+
+
+
{{ t("grid.nullValue") }}
+
{{ activeCellDetail.value === null ? "true" : "false" }}
+
+
+
{{ t("grid.valueLength") }}
+
{{ activeCellDetail.length }}
+
+
+
+
{{ t("grid.columnComment") }}
+
+ {{ activeCellDetail.comment || t("grid.noComment") }} +
+
+
+
{{ t("grid.cellValue") }}
+
+
{{ t("grid.imagePreview") }}
+ + + +
+