feat(grid): improve cell detail panel

This commit is contained in:
zipg 2026-07-13 17:16:38 +08:00 committed by GitHub
parent 4c7205abe7
commit 16724074c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 86 additions and 27 deletions

View File

@ -4923,6 +4923,7 @@ const dialogCellDetail = computed(() => {
});
const cellDetailJsonFormatted = computed(() => settingsStore.editorSettings.cellDetailJsonFormatted);
const cellDetailMetadataCollapsed = computed(() => settingsStore.editorSettings.cellDetailMetadataCollapsed);
const sideDetailJsonView = computed(() => cellDetailJsonFormatted.value && !!activeCellDetail.value?.formattedJson);
const cellDetailJsonView = computed(() => cellDetailJsonFormatted.value && !!dialogCellDetail.value?.formattedJson);
@ -5149,7 +5150,6 @@ const detailTemporalEditorConfig = computed(() => {
const detail = activeCellDetail.value;
return detail ? temporalEditorConfigForColumn(detail.colIndex) : undefined;
});
const sideDetailPrioritizesValue = computed(() => !cellDetailPanelIsBottom.value && !isEditingDetail.value && !!activeCellDetail.value?.formattedJson);
const sideDetailValueFillsHeight = computed(() => cellDetailPanelIsBottom.value || isEditingDetail.value || (!cellDetailPanelIsBottom.value && !activeCellDetail.value?.imagePreviewUrl));
const sideJsonPreviewText = computed(() => {
const detail = activeCellDetail.value;
@ -5354,6 +5354,10 @@ function toggleCellDetailJsonFormatted() {
settingsStore.updateEditorSettings({ cellDetailJsonFormatted: !cellDetailJsonFormatted.value });
}
function toggleCellDetailMetadataCollapsed() {
settingsStore.updateEditorSettings({ cellDetailMetadataCollapsed: !cellDetailMetadataCollapsed.value });
}
function startDetailEdit() {
const detail = activeCellDetail.value;
if (!detail || !detail.isEditable) return;
@ -10586,31 +10590,22 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
@contextmenu="onDrawerContextMenu"
>
<div v-if="!cellDetailPanelIsBottom" class="absolute left-0 top-0 bottom-0 z-20 w-1.5 -translate-x-1/2 cursor-col-resize hover:bg-primary/30" @mousedown.prevent="onDetailResizeStart" />
<div v-else class="absolute left-0 right-0 top-0 z-20 h-1.5 -translate-y-1/2 cursor-row-resize hover:bg-primary/30" @mousedown.prevent="onDetailResizeStart" />
<div class="h-9 flex items-center gap-2 px-3 border-b shrink-0 bg-muted/20">
<Info class="w-3.5 h-3.5 text-muted-foreground" />
<span class="text-xs font-medium flex-1 min-w-0 truncate">{{ t("grid.cellDetails") }}</span>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="cellDetailPanelIsBottom ? t('grid.cellDetailLayoutRight') : t('grid.cellDetailLayoutBottom')" @click="toggleCellDetailPanelLayout">
<PanelRight v-if="cellDetailPanelIsBottom" class="w-3 h-3" />
<PanelBottom v-else class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('grid.openCellDetailsDialog')" @click="openActiveCellDetailDialog">
<Maximize2 class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('grid.openRowDetailsDialog')" @click="openActiveRowDetailDialog">
<ListTree class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('grid.openColumnDetailsDialog')" @click="openActiveColumnDetailDialog">
<TableProperties class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="closeCellDetails">
<X class="w-3 h-3" />
</Button>
</div>
<div v-else class="data-grid-detail-resize-handle data-grid-detail-resize-handle--bottom absolute left-0 right-0 top-0 z-20 h-2 -translate-y-1/2 cursor-row-resize" @mousedown.prevent="onDetailResizeStart" />
<Tabs v-model="activeCellDetailTab" class="flex-1 min-h-0 gap-0">
<div class="shrink-0 border-b px-3 py-2">
<TabsList class="grid h-7 w-full p-0.5" :class="activeCellDetailTabsGridClass">
<div class="h-9 flex items-center gap-2 px-3 border-b shrink-0 bg-muted/20">
<Button
variant="ghost"
size="icon"
class="h-5 w-5 shrink-0"
:title="cellDetailMetadataCollapsed ? t('grid.expandCellDetailMetadata') : t('grid.collapseCellDetailMetadata')"
:aria-label="cellDetailMetadataCollapsed ? t('grid.expandCellDetailMetadata') : t('grid.collapseCellDetailMetadata')"
:aria-expanded="!cellDetailMetadataCollapsed"
@click="toggleCellDetailMetadataCollapsed"
>
<ChevronRight v-if="cellDetailMetadataCollapsed" class="w-3 h-3" />
<ChevronDown v-else class="w-3 h-3" />
</Button>
<TabsList class="grid h-7 min-w-0 flex-1 p-0.5" :class="activeCellDetailTabsGridClass">
<TabsTrigger value="details" class="h-6 text-xs">{{ t("grid.cellDetails") }}</TabsTrigger>
<TabsTrigger v-if="activeCellDetailTabs.includes('hexViewer')" value="hexViewer" class="h-6 text-xs">
{{ t("grid.hexViewer") }}
@ -10619,11 +10614,29 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
{{ t("grid.valueEditor") }}
</TabsTrigger>
</TabsList>
<div class="ml-auto flex shrink-0 items-center gap-1">
<Button variant="ghost" size="icon" class="h-5 w-5" :title="cellDetailPanelIsBottom ? t('grid.cellDetailLayoutRight') : t('grid.cellDetailLayoutBottom')" @click="toggleCellDetailPanelLayout">
<PanelRight v-if="cellDetailPanelIsBottom" class="w-3 h-3" />
<PanelBottom v-else class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('grid.openCellDetailsDialog')" @click="openActiveCellDetailDialog">
<Maximize2 class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('grid.openRowDetailsDialog')" @click="openActiveRowDetailDialog">
<ListTree class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('grid.openColumnDetailsDialog')" @click="openActiveColumnDetailDialog">
<TableProperties class="w-3 h-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="closeCellDetails">
<X class="w-3 h-3" />
</Button>
</div>
</div>
<TabsContent value="details" class="m-0 min-h-0 flex-1 flex flex-col">
<div data-native-clipboard class="flex-1 min-h-0 overflow-auto px-3 pt-3 text-xs" :class="[sideDetailValueFillsHeight ? 'flex flex-col gap-3' : 'space-y-3', isEditingDetail && !cellDetailPanelIsBottom ? 'pb-1' : 'pb-3']">
<div v-if="cellDetailPanelIsBottom" class="grid grid-cols-[minmax(180px,1.6fr)_repeat(4,minmax(74px,0.55fr))_minmax(160px,1fr)] gap-3 rounded border bg-muted/20 p-2">
<div v-if="cellDetailPanelIsBottom && !cellDetailMetadataCollapsed" class="grid grid-cols-[minmax(180px,1.6fr)_repeat(4,minmax(74px,0.55fr))_minmax(160px,1fr)] gap-3 rounded border bg-muted/20 p-2">
<div class="min-w-0 space-y-1">
<div class="text-muted-foreground">{{ t("grid.columnName") }}</div>
<div class="truncate font-medium" :title="activeCellDetail.column">
@ -10655,7 +10668,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
</div>
</div>
</div>
<template v-else-if="!isEditingDetail && !sideDetailPrioritizesValue">
<template v-else-if="!cellDetailMetadataCollapsed && !isEditingDetail">
<div class="space-y-1">
<div class="text-muted-foreground">{{ t("grid.columnName") }}</div>
<div class="font-medium break-all">{{ activeCellDetail.column }}</div>
@ -11912,6 +11925,29 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
background: var(--data-grid-scrollbar-thumb-hover);
}
.data-grid-detail-resize-handle--bottom {
background: transparent;
}
.data-grid-detail-resize-handle--bottom::after {
content: "";
position: absolute;
left: 50%;
top: 50%;
width: 42px;
height: 2px;
border-radius: 999px;
background: var(--data-grid-scrollbar-thumb-hover);
opacity: 0;
pointer-events: none;
transform: translate(-50%, -50%);
transition: opacity 120ms ease;
}
.detail-drawer-resizing > .data-grid-detail-resize-handle--bottom::after {
opacity: 0.7;
}
.data-grid-vertical-scrollbar {
position: absolute;
top: 10px;

View File

@ -903,6 +903,8 @@ export default {
cellDetails: "Cell Details",
cellDetailLayoutBottom: "Move to Bottom",
cellDetailLayoutRight: "Move to Right",
collapseCellDetailMetadata: "Collapse cell metadata",
expandCellDetailMetadata: "Expand cell metadata",
openCellDetailsDialog: "Open Cell Details",
rowDetails: "Row Details",
rowDetailsFor: "Row {row} Details",

View File

@ -846,6 +846,8 @@ export default withEnglishFallback({
cellDetails: "Detalles de celda",
cellDetailLayoutBottom: "Mover abajo",
cellDetailLayoutRight: "Mover a la derecha",
collapseCellDetailMetadata: "Contraer metadatos de celda",
expandCellDetailMetadata: "Expandir metadatos de celda",
openCellDetailsDialog: "Abrir detalles de celda",
rowDetails: "Detalles de fila",
rowDetailsFor: "Detalles de fila {row}",

View File

@ -844,6 +844,8 @@ export default withEnglishFallback({
cellDetails: "Dettagli Cella",
cellDetailLayoutBottom: "Sposta in Basso",
cellDetailLayoutRight: "Sposta a Destra",
collapseCellDetailMetadata: "Comprimi metadati cella",
expandCellDetailMetadata: "Espandi metadati cella",
openCellDetailsDialog: "Apri Dettagli Cella",
rowDetails: "Dettagli Riga",
rowDetailsFor: "Dettagli Riga {row}",

View File

@ -841,6 +841,8 @@ export default withEnglishFallback({
cellDetails: "セル詳細",
cellDetailLayoutBottom: "下部に表示",
cellDetailLayoutRight: "右側に表示",
collapseCellDetailMetadata: "セルのメタ情報を折りたたむ",
expandCellDetailMetadata: "セルのメタ情報を展開",
openCellDetailsDialog: "セル詳細を開く",
rowDetails: "行詳細",
rowDetailsFor: "行 {row} の詳細",

View File

@ -846,6 +846,8 @@ export default withEnglishFallback({
cellDetails: "Detalhes da Célula",
cellDetailLayoutBottom: "Mover para Baixo",
cellDetailLayoutRight: "Mover para a Direita",
collapseCellDetailMetadata: "Recolher metadados da célula",
expandCellDetailMetadata: "Expandir metadados da célula",
openCellDetailsDialog: "Abrir Detalhes da Célula",
rowDetails: "Detalhes da Linha",
rowDetailsFor: "Detalhes da Linha {row}",

View File

@ -904,6 +904,8 @@ export default withEnglishFallback({
cellDetails: "单元格详情",
cellDetailLayoutBottom: "移动到底部",
cellDetailLayoutRight: "移动到右侧",
collapseCellDetailMetadata: "收起单元格元信息",
expandCellDetailMetadata: "展开单元格元信息",
openCellDetailsDialog: "单元格详情",
rowDetails: "行详情",
rowDetailsFor: "第 {row} 行详情",

View File

@ -845,6 +845,8 @@ export default withEnglishFallback({
cellDetails: "儲存格詳情",
cellDetailLayoutBottom: "移到底部",
cellDetailLayoutRight: "移到右側",
collapseCellDetailMetadata: "收合儲存格中繼資訊",
expandCellDetailMetadata: "展開儲存格中繼資訊",
openCellDetailsDialog: "儲存格詳情",
rowDetails: "資料詳情",
rowDetailsFor: "第 {row} 筆資料詳情",

View File

@ -79,6 +79,11 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ dataGridSearchMode: "invalid" as any }).dataGridSearchMode).toBe("filter");
});
it("shows cell detail metadata by default and preserves collapsed state", () => {
expect(normalizeEditorSettings({}).cellDetailMetadataCollapsed).toBe(false);
expect(normalizeEditorSettings({ cellDetailMetadataCollapsed: true }).cellDetailMetadataCollapsed).toBe(true);
});
it("normalizes toolbar item settings from older saved settings", () => {
const settings = normalizeEditorSettings({
toolbarItems: {

View File

@ -404,6 +404,7 @@ export interface EditorSettings {
cellDetailDrawerWidth: number;
cellDetailPanelLayout: CellDetailPanelLayout;
cellDetailJsonFormatted: boolean;
cellDetailMetadataCollapsed: boolean;
shortcuts: ShortcutSettings;
sqlFormatter: SqlFormatterSettings;
sidebarActivation: SidebarActivation;
@ -540,6 +541,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
cellDetailDrawerWidth: 380,
cellDetailPanelLayout: "bottom",
cellDetailJsonFormatted: false,
cellDetailMetadataCollapsed: false,
shortcuts: normalizeShortcutSettings(),
sqlFormatter: normalizeSqlFormatterSettings(DEFAULT_SQL_FORMATTER_SETTINGS),
sidebarActivation: "single",
@ -776,6 +778,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
cellDetailDrawerWidth: normalizeDrawerWidth(settings.cellDetailDrawerWidth, 260, DEFAULT_EDITOR_SETTINGS.cellDetailDrawerWidth),
cellDetailPanelLayout: normalizeCellDetailPanelLayout(settings.cellDetailPanelLayout),
cellDetailJsonFormatted: typeof settings.cellDetailJsonFormatted === "boolean" ? settings.cellDetailJsonFormatted : DEFAULT_EDITOR_SETTINGS.cellDetailJsonFormatted,
cellDetailMetadataCollapsed: typeof settings.cellDetailMetadataCollapsed === "boolean" ? settings.cellDetailMetadataCollapsed : DEFAULT_EDITOR_SETTINGS.cellDetailMetadataCollapsed,
shortcuts: normalizeShortcutSettings(settings.shortcuts),
sqlFormatter: normalizeSqlFormatterSettings(settings.sqlFormatter),
sidebarActivation: settings.sidebarActivation === "single" || settings.sidebarActivation === "double" ? settings.sidebarActivation : DEFAULT_EDITOR_SETTINGS.sidebarActivation,
@ -1024,6 +1027,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.cellDetailDrawerWidth !== undefined) editorSettings.value.cellDetailDrawerWidth = normalizeDrawerWidth(partial.cellDetailDrawerWidth, 260, DEFAULT_EDITOR_SETTINGS.cellDetailDrawerWidth);
if (partial.cellDetailPanelLayout !== undefined) editorSettings.value.cellDetailPanelLayout = normalizeCellDetailPanelLayout(partial.cellDetailPanelLayout);
if (partial.cellDetailJsonFormatted !== undefined) editorSettings.value.cellDetailJsonFormatted = partial.cellDetailJsonFormatted === true;
if (partial.cellDetailMetadataCollapsed !== undefined) editorSettings.value.cellDetailMetadataCollapsed = partial.cellDetailMetadataCollapsed === true;
if (partial.shortcuts !== undefined) editorSettings.value.shortcuts = normalizeShortcutSettings(partial.shortcuts);
if (partial.sqlFormatter !== undefined) editorSettings.value.sqlFormatter = normalizeSqlFormatterSettings(partial.sqlFormatter);
if (partial.sidebarActivation !== undefined) editorSettings.value.sidebarActivation = partial.sidebarActivation;