fix(ui): support legacy WebView dialog viewports

This commit is contained in:
zipg authored and GitHub committed 2026-07-22 15:26:23 +08:00
1 parent 4d2b60326d
commit 127328a4ed
16 files changed
+74 -17

No files matched your search

@@ -381,7 +381,7 @@ function restoreBackup(run: DatabaseBackupRun, file: DatabaseBackupFile) {
</div>
<Dialog v-model:open="scheduleDialogOpen">
<DialogContent class="max-h-[min(760px,calc(100dvh-32px))] max-w-[min(720px,calc(100vw-32px))] overflow-y-auto">
<DialogContent class="max-h-[min(760px,calc(var(--dbx-viewport-height)-32px))] max-w-[min(720px,calc(100vw-32px))] overflow-y-auto">
<DialogHeader>
<DialogTitle>{{ editingScheduleId ? t("databaseBackup.editSchedule") : t("databaseBackup.addSchedule") }}</DialogTitle>
</DialogHeader>
@@ -6377,7 +6377,7 @@ function openExternalUrl(url: string) {
.connection-dialog-content {
display: flex;
flex-direction: column;
max-height: calc(100dvh - 2rem);
max-height: calc(var(--dbx-viewport-height) - 2rem);
}
@media (min-width: 640px) {
@@ -106,10 +106,10 @@ const isMaximized = ref(false);
const dialogStyle = computed(() => {
if (isMaximized.value) {
return {
width: "100vw",
height: "100vh",
maxWidth: "100vw",
maxHeight: "100vh",
width: "100%",
height: "100%",
maxWidth: "100%",
maxHeight: "100%",
borderRadius: "0",
};
}
@@ -133,6 +133,13 @@ function toggleMaximize() {
isMaximized.value = !isMaximized.value;
}
function handleDialogEscape(event: KeyboardEvent) {
if (!showOptionsPanel.value) return;
event.preventDefault();
showOptionsPanel.value = false;
}
let resizeObserver: ResizeObserver | null = null;
let saveTimeout: number | null = null;
@@ -679,7 +686,7 @@ const targetConnectionInfo = computed(() => {
<template>
<Dialog v-model:open="open">
<DialogContent :class="['min-w-[800px] flex flex-col overflow-hidden', isMaximized ? '' : 'resize']" :style="dialogStyle" @interact-outside.prevent>
<DialogContent :class="['flex flex-col overflow-hidden', isMaximized ? 'min-w-0' : 'min-w-[800px] resize']" :portal-class="isMaximized ? 'p-0' : undefined" :style="dialogStyle" @interact-outside.prevent @escape-key-down="handleDialogEscape">
<Button variant="ghost" size="icon-sm" class="absolute top-2 right-10 z-10" @click="toggleMaximize">
<Maximize2 v-if="!isMaximized" class="w-4 h-4" />
<Minimize2 v-else class="w-4 h-4" />
@@ -0,0 +1,25 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const dialogSource = readFileSync(new URL("../SchemaDiffDialog.vue", import.meta.url), "utf8");
describe("SchemaDiffDialog fullscreen layout", () => {
it("fits the dialog to its portal instead of the viewport width", () => {
expect(dialogSource).toContain('width: "100%"');
expect(dialogSource).toContain('height: "100%"');
expect(dialogSource).not.toContain('width: "100vw"');
expect(dialogSource).not.toContain('height: "100vh"');
});
it("removes the normal dialog gutter and minimum width while maximized", () => {
expect(dialogSource).toContain(":portal-class=\"isMaximized ? 'p-0' : undefined\"");
expect(dialogSource).toContain("isMaximized ? 'min-w-0' : 'min-w-[800px] resize'");
});
it("closes the options panel before allowing Escape to dismiss the dialog", () => {
expect(dialogSource).toContain('@escape-key-down="handleDialogEscape"');
expect(dialogSource).toContain("if (!showOptionsPanel.value) return;");
expect(dialogSource).toContain("event.preventDefault();");
expect(dialogSource).toContain("showOptionsPanel.value = false;");
});
});
@@ -172,7 +172,9 @@ const settingsRootComponent = computed(() => (isSettingsPage.value ? "div" : Dia
const settingsRootProps = computed(() => (isSettingsPage.value ? {} : { open: props.open === true }));
const settingsRootClass = computed(() => (isSettingsPage.value ? "h-full min-h-0 overflow-hidden bg-background" : ""));
const settingsContentComponent = computed(() => (isSettingsPage.value ? "div" : DialogContent));
const settingsContentClass = computed(() => (isSettingsPage.value ? "flex h-full min-h-0 flex-col gap-4 overflow-hidden bg-background p-4" : "h-[min(660px,calc(100dvh-80px))] !max-w-[min(920px,calc(100vw-32px))] grid-rows-[auto_minmax(0,1fr)] gap-3 p-4 sm:!max-w-[min(920px,calc(100vw-48px))]"));
const settingsContentClass = computed(() =>
isSettingsPage.value ? "flex h-full min-h-0 flex-col gap-4 overflow-hidden bg-background p-4" : "h-[min(660px,calc(var(--dbx-viewport-height)-80px))] !max-w-[min(920px,calc(100vw-32px))] grid-rows-[auto_minmax(0,1fr)] gap-3 p-4 sm:!max-w-[min(920px,calc(100vw-48px))]",
);
const settingsTitleComponent = computed(() => (isSettingsPage.value ? "h2" : DialogTitle));
function onSettingsRootOpenChange(value: boolean) {
@@ -822,7 +822,7 @@ watch(targetMode, (mode) => {
<template>
<Dialog v-model:open="open">
<DialogScrollContent class="flex max-h-[calc(100dvh-6rem)] min-h-0 flex-col overflow-hidden sm:max-w-[980px]" :trap-focus="false" @interact-outside.prevent>
<DialogScrollContent class="flex max-h-[calc(var(--dbx-viewport-height)-6rem)] min-h-0 flex-col overflow-hidden sm:max-w-[980px]" :trap-focus="false" @interact-outside.prevent>
<DialogHeader class="shrink-0 pr-8">
<DialogTitle class="flex items-center gap-2 text-base">
<FileUp class="h-4 w-4" />
@@ -133,7 +133,7 @@ defineExpose({ show });
<template>
<Dialog v-model:open="open">
<DialogContent class="h-[min(760px,calc(100dvh-2rem))] flex flex-col overflow-hidden sm:max-w-3xl">
<DialogContent class="h-[min(760px,calc(var(--dbx-viewport-height)-2rem))] flex flex-col overflow-hidden sm:max-w-3xl">
<DialogHeader>
<DialogTitle>{{ t("extension.manageTitle") }}</DialogTitle>
</DialogHeader>
@@ -182,7 +182,7 @@ function closeDialog() {
<template>
<Dialog :open="props.open" @update:open="(value) => emit('update:open', value)">
<DialogContent class="h-[min(760px,calc(100dvh-2rem))] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-[900px]">
<DialogContent class="h-[min(760px,calc(var(--dbx-viewport-height)-2rem))] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-[900px]">
<DialogHeader>
<DialogTitle>{{ title }}</DialogTitle>
</DialogHeader>
@@ -268,7 +268,7 @@ function openResult(item: SearchResultItem) {
<template>
<Dialog v-model:open="dialogOpen">
<DialogScrollContent class="flex max-h-[calc(100dvh-6rem)] min-h-0 max-w-4xl flex-col overflow-hidden gap-0 p-0">
<DialogScrollContent class="flex max-h-[calc(var(--dbx-viewport-height)-6rem)] min-h-0 max-w-4xl flex-col overflow-hidden gap-0 p-0">
<DialogHeader class="shrink-0 border-b px-5 py-4">
<DialogTitle class="flex items-center gap-2">
<Search class="h-5 w-5" />
@@ -5,7 +5,7 @@ const dialogSource = readFileSync(new URL("../DatabaseSearchDialog.vue", import.
describe("DatabaseSearchDialog layout", () => {
it("keeps the header and footer visible within the dynamic viewport", () => {
expect(dialogSource).toContain('class="flex max-h-[calc(100dvh-6rem)] min-h-0 max-w-4xl flex-col overflow-hidden gap-0 p-0"');
expect(dialogSource).toContain('class="flex max-h-[calc(var(--dbx-viewport-height)-6rem)] min-h-0 max-w-4xl flex-col overflow-hidden gap-0 p-0"');
expect(dialogSource).toContain('<DialogHeader class="shrink-0 border-b px-5 py-4">');
expect(dialogSource).toContain('<DialogFooter class="shrink-0 border-t px-5 py-3">');
});
@@ -438,7 +438,7 @@ watch(
<template>
<Dialog :open="open" @update:open="handleOpenChange">
<DialogScrollContent class="flex max-h-[calc(100dvh-6rem)] min-h-0 min-w-0 flex-col overflow-hidden sm:max-w-[860px]" :trap-focus="false" @interact-outside.prevent>
<DialogScrollContent class="flex max-h-[calc(var(--dbx-viewport-height)-6rem)] min-h-0 min-w-0 flex-col overflow-hidden sm:max-w-[860px]" :trap-focus="false" @interact-outside.prevent>
<DialogHeader class="shrink-0">
<DialogTitle class="flex items-center gap-2">
<FileCode class="w-4 h-4" />
@@ -42,7 +42,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 relative grid max-h-[calc(100dvh-2rem)] w-full max-w-sm gap-4 overflow-hidden rounded-xl border border-border p-4 text-sm shadow-lg duration-100 outline-none pointer-events-auto',
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 relative grid max-h-[calc(var(--dbx-viewport-height)-2rem)] w-full max-w-sm gap-4 overflow-hidden rounded-xl border border-border p-4 text-sm shadow-lg duration-100 outline-none pointer-events-auto',
props.class,
)
"
@@ -26,7 +26,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<div class="fixed inset-0 z-50 grid place-items-center p-4 pointer-events-none">
<DialogContent
data-slot="dialog-content"
:class="cn('relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-4 shadow-lg duration-200 sm:rounded-lg md:w-full pointer-events-auto', props.class)"
:class="cn('relative z-50 grid max-h-[calc(var(--dbx-viewport-height)-6rem)] w-full max-w-lg my-8 gap-4 border border-border bg-background p-4 shadow-lg duration-200 sm:rounded-lg md:w-full pointer-events-auto', props.class)"
v-bind="{ ...$attrs, ...forwarded }"
@pointer-down-outside="
(event) => {