fix(ui): responsive table info sidebar and SQL toolbar layout
This commit is contained in:
5 files changed
+60
-10
No files matched your search
@@ -14332,8 +14332,9 @@ function openGridSnapshot() {
|
|||||||
<Copy class="w-3 h-3" />
|
<Copy class="w-3 h-3" />
|
||||||
<span class="table-info-action-label">{{ t("grid.copyDdl") }}</span>
|
<span class="table-info-action-label">{{ t("grid.copyDdl") }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-6 w-6" :class="{ 'bg-accent': settingsStore.editorSettings.tableDdlWordWrap }" @click="toggleDdlWrap">
|
<Button variant="ghost" size="sm" class="table-info-action-button h-6 px-2 text-xs" :class="{ 'bg-accent': settingsStore.editorSettings.tableDdlWordWrap }" :title="t('settings.wordWrap')" :aria-label="t('settings.wordWrap')" @click="toggleDdlWrap">
|
||||||
<WrapText class="w-3 h-3" />
|
<WrapText class="w-3 h-3" />
|
||||||
|
<span class="table-info-action-label">{{ t("settings.wordWrap") }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="activeTableInfoTab === 'indexes' && canManageMongoIndexes" class="table-info-actions flex min-w-0 shrink-0 items-center gap-1">
|
<div v-else-if="activeTableInfoTab === 'indexes' && canManageMongoIndexes" class="table-info-actions flex min-w-0 shrink-0 items-center gap-1">
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ const toolbarTier = ref<EditorToolbarTier>(0);
|
|||||||
// Available width when the current tier was condensed into; anchors the
|
// Available width when the current tier was condensed into; anchors the
|
||||||
// step-down hysteresis so a static narrow layout cannot oscillate.
|
// step-down hysteresis so a static narrow layout cannot oscillate.
|
||||||
const condensedAtWidth = ref(0);
|
const condensedAtWidth = ref(0);
|
||||||
|
const expandedTierRequiredWidths: Partial<Record<EditorToolbarTier, number>> = {};
|
||||||
let toolbarResizeObserver: ResizeObserver | undefined;
|
let toolbarResizeObserver: ResizeObserver | undefined;
|
||||||
|
|
||||||
function measureToolbarTier() {
|
function measureToolbarTier() {
|
||||||
@@ -99,9 +100,12 @@ function measureToolbarTier() {
|
|||||||
availableWidth: element.clientWidth,
|
availableWidth: element.clientWidth,
|
||||||
contentWidth: element.scrollWidth,
|
contentWidth: element.scrollWidth,
|
||||||
condensedAtWidth: condensedAtWidth.value,
|
condensedAtWidth: condensedAtWidth.value,
|
||||||
|
expandedTierRequiredWidths,
|
||||||
});
|
});
|
||||||
if (next !== toolbarTier.value) {
|
if (next !== toolbarTier.value) {
|
||||||
if (next > toolbarTier.value) {
|
if (next > toolbarTier.value) {
|
||||||
|
const currentTier = toolbarTier.value;
|
||||||
|
expandedTierRequiredWidths[currentTier] = Math.max(expandedTierRequiredWidths[currentTier] ?? 0, element.scrollWidth);
|
||||||
condensedAtWidth.value = element.clientWidth;
|
condensedAtWidth.value = element.clientWidth;
|
||||||
}
|
}
|
||||||
toolbarTier.value = next;
|
toolbarTier.value = next;
|
||||||
|
|||||||
@@ -3592,7 +3592,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Right-side panel: table info or source -->
|
<!-- Right-side panel: table info or source -->
|
||||||
<div v-if="sidePanelRow || isEventEditor" class="object-browser-side-panel relative flex min-h-0 shrink-0 flex-col border-l bg-background" :class="{ 'side-panel-resizing': isResizingSidePanel }" :style="{ width: `${sidePanelWidth}px` }">
|
<div v-if="sidePanelRow || isEventEditor" class="object-browser-side-panel relative flex min-h-0 min-w-0 shrink-0 flex-col border-l bg-background" :class="{ 'side-panel-resizing': isResizingSidePanel }" :style="{ width: `min(${sidePanelWidth}px, 100%)` }">
|
||||||
<div 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="onSidePanelResizeStart" />
|
<div 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="onSidePanelResizeStart" />
|
||||||
<!-- Table info mode -->
|
<!-- Table info mode -->
|
||||||
<template v-if="sidePanelMode === 'table-info'">
|
<template v-if="sidePanelMode === 'table-info'">
|
||||||
@@ -3604,8 +3604,9 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
|||||||
<Copy class="w-3 h-3" />
|
<Copy class="w-3 h-3" />
|
||||||
<span class="table-info-action-label">{{ t("grid.copyDdl") }}</span>
|
<span class="table-info-action-label">{{ t("grid.copyDdl") }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" class="h-6 w-6" :class="{ 'bg-accent': settingsStore.editorSettings.tableDdlWordWrap }" @click="toggleTableDdlWordWrap">
|
<Button variant="ghost" size="sm" class="table-info-action-button h-6 px-2 text-xs" :class="{ 'bg-accent': settingsStore.editorSettings.tableDdlWordWrap }" :title="t('settings.wordWrap')" :aria-label="t('settings.wordWrap')" @click="toggleTableDdlWordWrap">
|
||||||
<WrapText class="w-3 h-3" />
|
<WrapText class="w-3 h-3" />
|
||||||
|
<span class="table-info-action-label">{{ t("settings.wordWrap") }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button v-if="canOpenTableStructureEditor" variant="ghost" size="sm" class="table-info-action-button h-6 px-2 text-xs" :title="t('contextMenu.editStructure')" :aria-label="t('contextMenu.editStructure')" @click="openTableStructureEditor">
|
<Button v-if="canOpenTableStructureEditor" variant="ghost" size="sm" class="table-info-action-button h-6 px-2 text-xs" :title="t('contextMenu.editStructure')" :aria-label="t('contextMenu.editStructure')" @click="openTableStructureEditor">
|
||||||
|
|||||||
@@ -22,6 +22,40 @@ describe("editor toolbar measured condensation", () => {
|
|||||||
expect(resolveNextEditorToolbarTier({ tier: 2, availableWidth: grown, contentWidth: grown - EDITOR_TOOLBAR_STEP_DOWN_SLACK_PX - 10, condensedAtWidth: 400 })).toBe(1);
|
expect(resolveNextEditorToolbarTier({ tier: 2, availableWidth: grown, contentWidth: grown - EDITOR_TOOLBAR_STEP_DOWN_SLACK_PX - 10, condensedAtWidth: 400 })).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("restores the fullest measured tier that fits after the pane expands", () => {
|
||||||
|
expect(
|
||||||
|
resolveNextEditorToolbarTier({
|
||||||
|
tier: 3,
|
||||||
|
availableWidth: 920,
|
||||||
|
contentWidth: 920,
|
||||||
|
condensedAtWidth: 420,
|
||||||
|
expandedTierRequiredWidths: { 0: 840, 1: 700, 2: 560 },
|
||||||
|
}),
|
||||||
|
).toBe(0);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveNextEditorToolbarTier({
|
||||||
|
tier: 3,
|
||||||
|
availableWidth: 760,
|
||||||
|
contentWidth: 760,
|
||||||
|
condensedAtWidth: 420,
|
||||||
|
expandedTierRequiredWidths: { 0: 840, 1: 700, 2: 560 },
|
||||||
|
}),
|
||||||
|
).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not use the condensed flex row width to restore a tier that cannot fit", () => {
|
||||||
|
expect(
|
||||||
|
resolveNextEditorToolbarTier({
|
||||||
|
tier: 2,
|
||||||
|
availableWidth: 720,
|
||||||
|
contentWidth: 720,
|
||||||
|
condensedAtWidth: 420,
|
||||||
|
expandedTierRequiredWidths: { 0: 840, 1: 700 },
|
||||||
|
}),
|
||||||
|
).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("never oscillates: slack alone without pane growth keeps the condensed tier", () => {
|
it("never oscillates: slack alone without pane growth keeps the condensed tier", () => {
|
||||||
// The row fits with plenty of slack, but the pane barely grew since the
|
// The row fits with plenty of slack, but the pane barely grew since the
|
||||||
// toolbar condensed at this width — stepping down would overflow again.
|
// toolbar condensed at this width — stepping down would overflow again.
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
* changes per database type, transaction state, and feature flags, so any
|
* changes per database type, transaction state, and feature flags, so any
|
||||||
* static guess would over- or under-condense. Instead the toolbar measures its
|
* static guess would over- or under-condense. Instead the toolbar measures its
|
||||||
* own row: while the content overflows the available width it steps up one
|
* own row: while the content overflows the available width it steps up one
|
||||||
* tier (moving controls into the overflow menu); it only steps back down when
|
* tier (moving controls into the overflow menu). Each expanded tier's measured
|
||||||
* there is real slack AND the pane has grown meaningfully since the last
|
* requirement is retained, so the toolbar can restore the fullest tier that
|
||||||
* condensation, so a static narrow layout can never oscillate.
|
* fits without relying on scrollWidth from the already-condensed flex row.
|
||||||
*
|
*
|
||||||
* - Tier 0: full layout.
|
* - Tier 0: full layout.
|
||||||
* - Tier 1: secondary edit/entry actions (compress, keyword case, semantic
|
* - Tier 1: secondary edit/entry actions (compress, keyword case, semantic
|
||||||
@@ -36,16 +36,18 @@ export interface EditorToolbarTierInput {
|
|||||||
contentWidth: number;
|
contentWidth: number;
|
||||||
/** Toolbar width when the current tier was condensed into; anchors step-down hysteresis. */
|
/** Toolbar width when the current tier was condensed into; anchors step-down hysteresis. */
|
||||||
condensedAtWidth?: number;
|
condensedAtWidth?: number;
|
||||||
|
/** Required widths captured immediately before each tier was condensed. */
|
||||||
|
expandedTierRequiredWidths?: Partial<Record<EditorToolbarTier, number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the next toolbar tier from real measurements. Monotonic per
|
* Resolves the next toolbar tier from real measurements. Monotonic per
|
||||||
* measurement — repeated calls after a tier change converge instead of
|
* measurement. Known expanded-tier widths allow a widened pane to restore the
|
||||||
* oscillating, because stepping down additionally requires the pane to have
|
* best fitting tier in one render; the growth-based branch remains as a safe
|
||||||
* grown by EDITOR_TOOLBAR_STEP_DOWN_MIN_GROWTH_PX since the last step up.
|
* fallback for callers without historical measurements.
|
||||||
*/
|
*/
|
||||||
export function resolveNextEditorToolbarTier(input: EditorToolbarTierInput): EditorToolbarTier {
|
export function resolveNextEditorToolbarTier(input: EditorToolbarTierInput): EditorToolbarTier {
|
||||||
const { tier, availableWidth, contentWidth, condensedAtWidth = 0 } = input;
|
const { tier, availableWidth, contentWidth, condensedAtWidth = 0, expandedTierRequiredWidths } = input;
|
||||||
if (!Number.isFinite(availableWidth) || !Number.isFinite(contentWidth) || availableWidth <= 0 || contentWidth <= 0) {
|
if (!Number.isFinite(availableWidth) || !Number.isFinite(contentWidth) || availableWidth <= 0 || contentWidth <= 0) {
|
||||||
// Unmeasured (first paint, hosts without ResizeObserver) keeps the current tier.
|
// Unmeasured (first paint, hosts without ResizeObserver) keeps the current tier.
|
||||||
return tier;
|
return tier;
|
||||||
@@ -53,6 +55,14 @@ export function resolveNextEditorToolbarTier(input: EditorToolbarTierInput): Edi
|
|||||||
if (contentWidth > availableWidth && tier < EDITOR_TOOLBAR_MAX_TIER) {
|
if (contentWidth > availableWidth && tier < EDITOR_TOOLBAR_MAX_TIER) {
|
||||||
return (tier + 1) as EditorToolbarTier;
|
return (tier + 1) as EditorToolbarTier;
|
||||||
}
|
}
|
||||||
|
if (tier > 0 && expandedTierRequiredWidths) {
|
||||||
|
for (let candidate = 0; candidate < tier; candidate += 1) {
|
||||||
|
const requiredWidth = expandedTierRequiredWidths[candidate as EditorToolbarTier];
|
||||||
|
if (requiredWidth && availableWidth >= requiredWidth + EDITOR_TOOLBAR_STEP_DOWN_SLACK_PX) {
|
||||||
|
return candidate as EditorToolbarTier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (tier > 0 && availableWidth - contentWidth >= EDITOR_TOOLBAR_STEP_DOWN_SLACK_PX && availableWidth - condensedAtWidth >= EDITOR_TOOLBAR_STEP_DOWN_MIN_GROWTH_PX) {
|
if (tier > 0 && availableWidth - contentWidth >= EDITOR_TOOLBAR_STEP_DOWN_SLACK_PX && availableWidth - condensedAtWidth >= EDITOR_TOOLBAR_STEP_DOWN_MIN_GROWTH_PX) {
|
||||||
return (tier - 1) as EditorToolbarTier;
|
return (tier - 1) as EditorToolbarTier;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user