From f195cdbc13328d0051b774cfba4ea95da77b4712 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 2 Jun 2026 16:33:42 +0800 Subject: [PATCH] feat(grid): support cell detail panel layouts --- apps/desktop/src/components/grid/DataGrid.vue | 201 ++++++++++++++---- apps/desktop/src/i18n/locales/en.ts | 2 + apps/desktop/src/i18n/locales/es.ts | 2 + apps/desktop/src/i18n/locales/zh-CN.ts | 2 + apps/desktop/src/i18n/locales/zh-TW.ts | 2 + apps/desktop/src/stores/settingsStore.ts | 13 ++ packages/app-tests/settingsStore.test.ts | 4 + 7 files changed, 183 insertions(+), 43 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 82bc1678e..d0958f0e3 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -43,6 +43,8 @@ import { Link2, ListTree, Maximize2, + PanelBottom, + PanelRight, TableProperties, LockKeyhole, } from "lucide-vue-next"; @@ -393,7 +395,6 @@ const rowDetailDialogOpen = ref(false); const rowDetailDialogRowId = ref(null); const columnDetailDialogOpen = ref(false); const columnDetailDialogColumnIndex = ref(null); -const detailWidth = ref(settingsStore.editorSettings.cellDetailDrawerWidth); const isResizingDetail = ref(false); const imagePreviewOpen = ref(false); const imagePreviewSrc = ref(""); @@ -3960,19 +3961,27 @@ const sqlOneLiner = computed(() => props.sql?.replace(/\s+/g, " ").trim() || "") type TableInfoTab = "columns" | "indexes" | "foreignKeys" | "triggers" | "ddl"; const TABLE_INFO_DRAWER_MIN_WIDTH = 240; -const CELL_DETAIL_DRAWER_MIN_WIDTH = 260; +const CELL_DETAIL_PANEL_MIN_HEIGHT = 180; +const CELL_DETAIL_PANEL_MIN_WIDTH = 260; +const CELL_DETAIL_PANEL_MAX_HEIGHT = 520; const DRAWER_MAX_WIDTH = 900; +function clampCellDetailPanelSize(value: number, layout = cellDetailPanelLayout.value): number { + const min = layout === "bottom" ? CELL_DETAIL_PANEL_MIN_HEIGHT : CELL_DETAIL_PANEL_MIN_WIDTH; + const max = layout === "bottom" ? CELL_DETAIL_PANEL_MAX_HEIGHT : DRAWER_MAX_WIDTH; + return Math.min(Math.max(value, min), max); +} const showTableInfo = globalDdlOpen; const activeTableInfoTab = ref("columns"); const ddlContent = ref(""); const ddlLoading = ref(false); const ddlWidth = ref(settingsStore.editorSettings.tableInfoDrawerWidth); +const detailPanelHeight = ref(settingsStore.editorSettings.cellDetailDrawerWidth); const ddlWrap = ref(true); const isResizingDdl = ref(false); let ddlResizeStartX = 0; let ddlResizeStartWidth = 0; -let detailResizeStartX = 0; -let detailResizeStartWidth = 0; +let detailResizeStartY = 0; +let detailResizeStartHeight = 0; const indexes = ref([]); const indexesLoaded = ref(false); const indexesLoading = ref(false); @@ -3986,6 +3995,8 @@ const triggersLoaded = ref(false); const triggersLoading = ref(false); const triggersError = ref(""); const searchQuery = ref(""); +const cellDetailPanelLayout = computed(() => settingsStore.editorSettings.cellDetailPanelLayout); +const cellDetailPanelIsBottom = computed(() => cellDetailPanelLayout.value === "bottom"); watch(activeTableInfoTab, () => { searchQuery.value = ""; @@ -4000,18 +4011,46 @@ watch( watch( () => settingsStore.editorSettings.cellDetailDrawerWidth, - (width) => { - if (!isResizingDetail.value) detailWidth.value = width; + (height) => { + if (!isResizingDetail.value) detailPanelHeight.value = clampCellDetailPanelSize(height); }, ); +watch(cellDetailPanelLayout, (layout) => { + if (!isResizingDetail.value) detailPanelHeight.value = clampCellDetailPanelSize(detailPanelHeight.value, layout); +}); + const ddlDrawerStyle = computed(() => ({ width: `${ddlWidth.value}px`, })); -const detailDrawerStyle = computed(() => ({ - width: `${detailWidth.value}px`, -})); +const detailPanelStyle = computed(() => + cellDetailPanelIsBottom.value + ? { maxHeight: `min(42vh, ${CELL_DETAIL_PANEL_MAX_HEIGHT}px)` } + : { width: `${detailPanelHeight.value}px` }, +); + +const contentGridStyle = computed(() => + cellDetailPanelIsBottom.value + ? { + gridTemplateColumns: "minmax(0, 1fr) auto", + gridTemplateRows: "minmax(0, 1fr) auto", + } + : { + gridTemplateColumns: "minmax(0, 1fr) auto auto", + gridTemplateRows: "minmax(0, 1fr)", + }, +); + +function toggleCellDetailPanelLayout() { + const nextLayout = cellDetailPanelIsBottom.value ? "right" : "bottom"; + const nextSize = clampCellDetailPanelSize(detailPanelHeight.value, nextLayout); + detailPanelHeight.value = nextSize; + settingsStore.updateEditorSettings({ + ...(nextLayout === "right" ? { cellDetailDrawerWidth: nextSize } : {}), + cellDetailPanelLayout: nextLayout, + }); +} const tableInfoTabs = computed( () => @@ -4224,8 +4263,8 @@ function onDdlResizeEnd() { function onDetailResizeStart(event: MouseEvent) { isResizingDetail.value = true; - detailResizeStartX = event.clientX; - detailResizeStartWidth = detailWidth.value; + detailResizeStartY = event.clientX; + detailResizeStartHeight = detailPanelHeight.value; document.body.classList.add("select-none", "cursor-col-resize"); window.addEventListener("mousemove", onDetailResizeMove); window.addEventListener("mouseup", onDetailResizeEnd); @@ -4233,16 +4272,16 @@ function onDetailResizeStart(event: MouseEvent) { function onDetailResizeMove(event: MouseEvent) { if (!isResizingDetail.value) return; - const nextWidth = detailResizeStartWidth + detailResizeStartX - event.clientX; - detailWidth.value = Math.min(Math.max(nextWidth, CELL_DETAIL_DRAWER_MIN_WIDTH), DRAWER_MAX_WIDTH); + const nextSize = detailResizeStartHeight + detailResizeStartY - event.clientX; + detailPanelHeight.value = clampCellDetailPanelSize(nextSize); } function onDetailResizeEnd() { if (isResizingDetail.value) { - settingsStore.updateEditorSettings({ cellDetailDrawerWidth: detailWidth.value }); + settingsStore.updateEditorSettings({ cellDetailDrawerWidth: detailPanelHeight.value }); } isResizingDetail.value = false; - document.body.classList.remove("select-none", "cursor-col-resize"); + document.body.classList.remove("select-none", "cursor-row-resize", "cursor-col-resize"); window.removeEventListener("mousemove", onDetailResizeMove); window.removeEventListener("mouseup", onDetailResizeEnd); } @@ -5001,9 +5040,9 @@ const gridContextMenuItems = computed(() => { > {{ t("grid.truncatedHint", { count: pageSize }) }} - -
-
+ +
+
(() => {
(() => {
{{ t("grid.cellDetails") }} +
-
-
-
{{ t("grid.columnName") }}
-
{{ activeCellDetail.column }}
-
-
+
+
+
+
{{ t("grid.columnName") }}
+
+ {{ activeCellDetail.column }} +
+
{{ t("grid.rowNumber") }}
{{ activeCellDetail.rowNumber }}
-
+
{{ t("grid.columnType") }}
{{ activeCellDetail.type || "-" }}
@@ -6212,14 +6277,50 @@ const gridContextMenuItems = computed(() => {
{{ t("grid.valueLength") }}
{{ activeCellDetail.length }}
-
-
-
{{ t("grid.columnComment") }}
-
- {{ activeCellDetail.comment || t("grid.noComment") }} +
+
{{ t("grid.columnComment") }}
+
+ {{ activeCellDetail.comment || t("grid.noComment") }} +
-
+ +
{{ t("grid.cellValue") }}
@@ -6292,7 +6393,8 @@ const gridContextMenuItems = computed(() => { v-else ref="detailsEditorContainer" data-cell-detail-editor-root - class="w-full h-40 rounded border overflow-hidden" + class="w-full rounded border overflow-hidden" + :class="cellDetailPanelIsBottom ? 'h-28' : 'h-40'" />
{{ activeCellDetail.formattedJson }}
-
+