The bottom-docked cell detail side panel could not be resized and its details view still used the old three-block "value / raw value / formatted JSON" layout, causing the issues reported on macOS: - bottom layout had no resize handle and a fixed, too-small height - "value" showed the grid's (truncated) display value while "raw value" showed the original — redundant for JSON, and shown inconsistently (only when the two happened to differ) - the "formatted value" block rendered the grid-truncated display value, which is incomplete and confusing inside the detail view Changes: - side panel bottom layout: add a top-edge row-resize handle and make the height adjustable (reuse clampCellDetailPanelSize, support vertical drag) - unify the side panel details view with the cell-detail dialog: a single value area showing the original value plus a "format JSON" toggle button - drop the redundant "raw value" / "formatted value" blocks in both the side panel and the dialog (the dialog change also addresses the #528 follow-up where the truncated "formatted value" block was confusing) Co-authored-by: vrustx <vrustx@vrustxdeMac-mini.local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1a4e59875b
commit
ff32e4b00f
|
|
@ -430,6 +430,7 @@ const rowDetailDialogRowId = ref<number | null>(null);
|
|||
const columnDetailDialogOpen = ref(false);
|
||||
const columnDetailDialogColumnIndex = ref<number | null>(null);
|
||||
const cellDetailJsonView = ref(false);
|
||||
const sideDetailJsonView = ref(false);
|
||||
const rowDetailSearch = ref("");
|
||||
const columnDetailSearch = ref("");
|
||||
const isResizingDetail = ref(false);
|
||||
|
|
@ -2853,6 +2854,7 @@ async function prefetchDetailSqlCondition() {
|
|||
|
||||
watch(activeCellDetail, (detail) => {
|
||||
void prefetchDetailSqlCondition();
|
||||
sideDetailJsonView.value = false;
|
||||
if (activeCellDetailTab.value !== "valueEditor") return;
|
||||
if (!detail?.isEditable) {
|
||||
resetDetailEdit();
|
||||
|
|
@ -4470,6 +4472,14 @@ function copyDetailFormattedJson() {
|
|||
copyText(detail.formattedJson);
|
||||
}
|
||||
|
||||
function copyDetailCurrentValue() {
|
||||
if (sideDetailJsonView.value && activeCellDetail.value?.formattedJson) {
|
||||
copyDetailFormattedJson();
|
||||
} else {
|
||||
copyDetailValue();
|
||||
}
|
||||
}
|
||||
|
||||
function copyDetailColumnName() {
|
||||
if (!activeCellDetail.value) return;
|
||||
copyText(activeCellDetail.value.column);
|
||||
|
|
@ -5069,7 +5079,7 @@ const ddlDrawerStyle = computed(() => ({
|
|||
|
||||
const detailPanelStyle = computed(() =>
|
||||
cellDetailPanelIsBottom.value
|
||||
? { maxHeight: `min(42vh, ${CELL_DETAIL_PANEL_MAX_HEIGHT}px)` }
|
||||
? { height: `${detailPanelHeight.value}px`, maxHeight: `min(70vh, ${CELL_DETAIL_PANEL_MAX_HEIGHT}px)` }
|
||||
: { width: `${detailPanelHeight.value}px` },
|
||||
);
|
||||
|
||||
|
|
@ -5306,16 +5316,18 @@ function onDdlResizeEnd() {
|
|||
|
||||
function onDetailResizeStart(event: MouseEvent) {
|
||||
isResizingDetail.value = true;
|
||||
detailResizeStartY = event.clientX;
|
||||
// 底部布局拖顶部边缘(垂直/clientY),右侧布局拖左边缘(水平/clientX)
|
||||
detailResizeStartY = cellDetailPanelIsBottom.value ? event.clientY : event.clientX;
|
||||
detailResizeStartHeight = detailPanelHeight.value;
|
||||
document.body.classList.add("select-none", "cursor-col-resize");
|
||||
document.body.classList.add("select-none", cellDetailPanelIsBottom.value ? "cursor-row-resize" : "cursor-col-resize");
|
||||
window.addEventListener("mousemove", onDetailResizeMove);
|
||||
window.addEventListener("mouseup", onDetailResizeEnd);
|
||||
}
|
||||
|
||||
function onDetailResizeMove(event: MouseEvent) {
|
||||
if (!isResizingDetail.value) return;
|
||||
const nextSize = detailResizeStartHeight + detailResizeStartY - event.clientX;
|
||||
const pos = cellDetailPanelIsBottom.value ? event.clientY : event.clientX;
|
||||
const nextSize = detailResizeStartHeight + detailResizeStartY - pos;
|
||||
detailPanelHeight.value = clampCellDetailPanelSize(nextSize);
|
||||
}
|
||||
|
||||
|
|
@ -7499,6 +7511,11 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
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>
|
||||
|
|
@ -7562,16 +7579,10 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</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 p-3 text-xs"
|
||||
:class="
|
||||
cellDetailPanelIsBottom ? 'grid grid-cols-[minmax(0,1fr)_minmax(220px,30%)] gap-3' : 'space-y-3'
|
||||
"
|
||||
>
|
||||
<div data-native-clipboard class="flex-1 min-h-0 overflow-auto p-3 text-xs space-y-3">
|
||||
<div
|
||||
v-if="cellDetailPanelIsBottom"
|
||||
class="col-span-2 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"
|
||||
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>
|
||||
|
|
@ -7648,6 +7659,17 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="text-muted-foreground">{{ t("grid.cellValue") }}</div>
|
||||
<div v-if="!isEditingDetail" class="flex items-center gap-1">
|
||||
<Button
|
||||
v-if="activeCellDetail.formattedJson"
|
||||
:variant="sideDetailJsonView ? 'secondary' : 'ghost'"
|
||||
size="sm"
|
||||
class="h-6 gap-1 px-2 text-xs"
|
||||
:title="t('grid.formattedJson')"
|
||||
@click="sideDetailJsonView = !sideDetailJsonView"
|
||||
>
|
||||
<Code2 class="h-3 w-3" />
|
||||
{{ t("grid.formattedJson") }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="activeCellDetail.isEditable"
|
||||
variant="ghost"
|
||||
|
|
@ -7663,7 +7685,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
size="icon"
|
||||
class="h-6 w-6"
|
||||
:title="t('grid.copyValue')"
|
||||
@click="copyDetailValue"
|
||||
@click="copyDetailCurrentValue"
|
||||
>
|
||||
<Copy class="h-3 w-3" />
|
||||
</Button>
|
||||
|
|
@ -7756,57 +7778,26 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</template>
|
||||
<pre
|
||||
v-else
|
||||
class="overflow-auto rounded border bg-muted/20 p-2 font-mono text-xs whitespace-pre cursor-pointer hover:border-primary/50"
|
||||
:class="[
|
||||
{ 'cursor-text': activeCellDetail.isEditable },
|
||||
cellDetailPanelIsBottom ? 'max-h-36' : 'max-h-56',
|
||||
]"
|
||||
class="overflow-auto rounded border bg-muted/20 p-2 font-mono text-xs whitespace-pre-wrap break-words cursor-pointer hover:border-primary/50"
|
||||
:class="{ 'cursor-text': activeCellDetail.isEditable }"
|
||||
@dblclick="startDetailEdit"
|
||||
>{{ activeCellDetail.displayValuePreview }}</pre
|
||||
>{{
|
||||
sideDetailJsonView && activeCellDetail.formattedJson
|
||||
? activeCellDetail.formattedJson
|
||||
: activeCellDetail.rawValuePreview
|
||||
}}</pre
|
||||
>
|
||||
<div
|
||||
v-if="activeCellDetail.isValuePreviewTruncated && !sideDetailJsonView"
|
||||
class="text-[11px] text-muted-foreground"
|
||||
>
|
||||
<div v-if="activeCellDetail.isValuePreviewTruncated" class="text-[11px] text-muted-foreground">
|
||||
{{
|
||||
t("grid.largeValuePreviewHint", {
|
||||
count: activeCellDetail.displayValuePreview.length,
|
||||
count: activeCellDetail.rawValuePreview.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="activeCellDetail.displayValuePreview !== activeCellDetail.rawValuePreview"
|
||||
class="space-y-1"
|
||||
:class="cellDetailPanelIsBottom ? 'min-h-0' : ''"
|
||||
>
|
||||
<div class="text-muted-foreground">{{ t("grid.rawValue") }}</div>
|
||||
<pre
|
||||
class="overflow-auto rounded border bg-muted/20 p-2 font-mono text-xs whitespace-pre-wrap break-words"
|
||||
:class="cellDetailPanelIsBottom ? 'max-h-36' : 'max-h-40'"
|
||||
>{{ activeCellDetail.rawValuePreview }}</pre
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="activeCellDetail.formattedJson"
|
||||
class="mt-2 space-y-1"
|
||||
:class="cellDetailPanelIsBottom ? 'min-h-0' : ''"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="text-muted-foreground">{{ t("grid.formattedJson") }}</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 px-2 text-xs"
|
||||
:title="t('grid.copyValue')"
|
||||
@click="copyDetailFormattedJson"
|
||||
>
|
||||
<Copy class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<pre
|
||||
class="overflow-auto rounded border bg-muted/20 p-2 font-mono text-xs whitespace-pre-wrap break-words"
|
||||
:class="cellDetailPanelIsBottom ? 'max-h-36' : 'max-h-72'"
|
||||
>{{ activeCellDetail.formattedJson }}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -8182,17 +8173,6 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
{{ t("grid.largeValuePreviewHint", { count: dialogCellDetail.rawValuePreview.length }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="dialogCellDetail.displayValuePreview !== dialogCellDetail.rawValuePreview && !cellDetailJsonView"
|
||||
class="space-y-1"
|
||||
>
|
||||
<div class="text-muted-foreground">{{ t("grid.formattedValue") }}</div>
|
||||
<pre
|
||||
class="max-h-40 overflow-auto rounded border bg-muted/20 p-3 font-mono text-xs whitespace-pre-wrap break-words"
|
||||
>{{ dialogCellDetail.displayValuePreview }}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="shrink-0 flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
|
|
|||
Loading…
Reference in New Issue