refactor(redis): center full value dialog

This commit is contained in:
Spencer.Chang 2026-07-17 01:15:24 +08:00 committed by GitHub
parent a1d728092c
commit 89fca90dbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 30 additions and 46 deletions

View File

@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
import JsonTree from "@/components/common/JsonTree.vue";
import RedisJsonEditor from "@/components/redis/RedisJsonEditor.vue";
@ -24,7 +24,6 @@ import { computeAutoRefreshTick, computeDisplayTtl, shouldStopAutoRefresh } from
import {
canRenderRedisValueFormat,
canEditRedisMemberDetail,
clampRedisMemberDetailSheetWidth,
formatRedisMemberDetail,
getRedisMemberSelectionKey,
isRedisBlob,
@ -92,8 +91,6 @@ const selectedMemberContext = ref<RedisMemberContext | null>(null);
const isEditingMember = ref(false);
const savingMember = ref(false);
const memberEditValue = ref("");
const memberDetailSheetWidth = ref(420);
const isResizingMemberSheet = ref(false);
const hashTableRef = ref<HTMLElement | null>(null);
const hashFieldWidth = ref(280);
const isResizingHashColumns = ref(false);
@ -309,8 +306,6 @@ const zsetRows = computed<RedisCollectionRow<RedisZsetItem>[]>(() =>
let hashSearchTimer: ReturnType<typeof setTimeout> | null = null;
let hashSearchRequestId = 0;
let memberSheetResizeStartX = 0;
let memberSheetResizeStartWidth = 0;
let hashResizeStartX = 0;
let hashResizeStartWidth = 0;
let zsetResizeStartX = 0;
@ -844,26 +839,6 @@ function finishMemberDetailClose() {
isEditingMember.value = false;
}
function stopResizeMemberSheet() {
isResizingMemberSheet.value = false;
window.removeEventListener("pointermove", resizeMemberSheet);
window.removeEventListener("pointerup", stopResizeMemberSheet);
}
function resizeMemberSheet(event: PointerEvent) {
if (!isResizingMemberSheet.value) return;
const delta = memberSheetResizeStartX - event.clientX;
memberDetailSheetWidth.value = clampRedisMemberDetailSheetWidth(memberSheetResizeStartWidth + delta, window.innerWidth);
}
function startResizeMemberSheet(event: PointerEvent) {
isResizingMemberSheet.value = true;
memberSheetResizeStartX = event.clientX;
memberSheetResizeStartWidth = memberDetailSheetWidth.value;
window.addEventListener("pointermove", resizeMemberSheet);
window.addEventListener("pointerup", stopResizeMemberSheet);
}
function clampHashFieldWidth(width: number) {
const containerWidth = hashTableRef.value?.clientWidth ?? 900;
const min = 120;
@ -1244,7 +1219,6 @@ onMounted(() => {
});
onBeforeUnmount(() => {
stopAutoRefresh();
stopResizeMemberSheet();
stopResizeHashColumns();
stopResizeZsetColumns();
if (hashSearchTimer) clearTimeout(hashSearchTimer);
@ -1616,23 +1590,14 @@ onBeforeUnmount(() => {
<DangerConfirmDialog v-model:open="showDeleteConfirm" :message="t('dangerDialog.deleteMessage')" :details="deleteDetails" :confirm-label="t('dangerDialog.deleteConfirm')" @confirm="confirmDelete" />
<Sheet :open="showMemberDetail" @update:open="handleMemberDetailOpenChange">
<SheetContent
side="right"
class="gap-0 p-0 sm:max-w-[calc(100vw-2rem)]"
:class="{ 'select-none': isResizingMemberSheet }"
:style="[editorFontFamilyStyle, { width: `${memberDetailSheetWidth}px`, maxWidth: 'calc(100vw - 2rem)' }]"
@close-auto-focus="finishMemberDetailClose"
@pointer-down-outside.prevent
@interact-outside.prevent
>
<div class="absolute inset-y-0 left-0 z-10 w-2 -translate-x-1 cursor-col-resize border-l border-transparent hover:border-primary/60" @pointerdown.prevent="startResizeMemberSheet" />
<SheetHeader class="border-b px-5 py-4 pr-12">
<SheetTitle class="flex items-center gap-2">
<Dialog :open="showMemberDetail" @update:open="handleMemberDetailOpenChange">
<DialogContent class="flex h-[min(760px,85vh)] w-[calc(100vw-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-[960px]" :style="editorFontFamilyStyle" @close-auto-focus="finishMemberDetailClose" @pointer-down-outside.prevent @interact-outside.prevent>
<DialogHeader class="border-b px-5 py-4 pr-12">
<DialogTitle class="flex items-center gap-2">
<span class="truncate">{{ selectedMemberTitle ? formatValue(selectedMemberTitle) : t("redis.memberDetail") }}</span>
<Badge variant="outline" class="shrink-0 text-xs">{{ redisFormatLabel(memberValueView, selectedMemberDetail.rawLabel) }}</Badge>
</SheetTitle>
</SheetHeader>
</DialogTitle>
</DialogHeader>
<template v-if="isEditingMember">
<textarea v-model="memberEditValue" class="dbx-editor-font-family min-h-0 flex-1 resize-none bg-background p-5 text-[13px] leading-6 outline-none" :readonly="savingMember" spellcheck="false" />
</template>
@ -1677,7 +1642,7 @@ onBeforeUnmount(() => {
<pre v-else-if="memberValueView === 'base64'" class="dbx-editor-font-family min-h-0 w-full min-w-0 max-w-full flex-1 overflow-auto bg-background p-5 text-[13px] leading-6 whitespace-pre-wrap break-all">{{ selectedMemberDetail.base64Text }}</pre>
<pre v-else class="dbx-editor-font-family min-h-0 w-full min-w-0 max-w-full flex-1 overflow-auto bg-background p-5 text-[13px] leading-6" :class="detailTextClass(memberValueView)">{{ detailTextForFormat(selectedMemberDetail, memberValueView) }}</pre>
</template>
<SheetFooter class="shrink-0 border-t px-5 py-3">
<DialogFooter class="mx-0 mb-0 shrink-0 border-t px-5 py-3">
<template v-if="isEditingMember">
<Button variant="ghost" :disabled="savingMember" @click="cancelEditMember">
{{ t("grid.discard") }}
@ -1706,8 +1671,8 @@ onBeforeUnmount(() => {
<Copy class="h-4 w-4" />
{{ t("redis.copyMember") }}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</template>

View File

@ -24,6 +24,7 @@ type TemplateElement = {
name?: string;
arg?: { content?: string };
exp?: { content?: string };
value?: { content?: string };
}>;
};
@ -36,6 +37,10 @@ function directiveExpression(element: TemplateElement, name: string, arg?: strin
return element.props.find((prop) => prop.type === 7 && prop.name === name && (arg == null || prop.arg?.content === arg))?.exp?.content;
}
function staticAttribute(element: TemplateElement, name: string): string | undefined {
return element.props.find((prop) => prop.type === 6 && prop.name === name)?.value?.content;
}
function templateElements(node: TemplateNode): TemplateElement[] {
const children = (node.children ?? []).flatMap(templateElements);
return node.type === 1 ? [node as TemplateElement, ...children] : children;
@ -102,6 +107,20 @@ function calledName(call: ts.CallExpression): string | undefined {
}
describe("native RedisJSON editor", () => {
it("opens full member values in a centered dialog instead of an app-height side sheet", () => {
const memberDialog = findTemplateElement((element) => element.tag === "Dialog" && directiveExpression(element, "bind", "open") === "showMemberDetail");
const memberDetailTags = templateElements(memberDialog).map((element) => element.tag);
const memberDetailFooter = templateElements(memberDialog).find((element) => element.tag === "DialogFooter");
expect(memberDetailTags).toContain("DialogContent");
expect(memberDetailTags).toContain("DialogHeader");
expect(memberDetailTags).toContain("DialogFooter");
expect(memberDetailTags).not.toContain("SheetContent");
expect(staticAttribute(memberDetailFooter!, "class")).toContain("mx-0 mb-0");
expect(viewerSource).not.toContain("memberDetailSheetWidth");
expect(viewerSource).not.toContain("startResizeMemberSheet");
});
it("uses the same foldable source editor as JSON strings and hash fields", () => {
const stringEditor = findTemplateElement((element) => element.tag === "RedisJsonEditor" && directiveExpression(element, "if") === "stringValueView === 'json' && stringValueDetail.json");
const nativeJsonBranch = findTemplateElement((element) => directiveExpression(element, "else-if") === "redisKind === 'json'");