mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 11:56:01 +02:00
Fix: AMS drying popover no longer renders off the bottom of the viewport (#1447 part 1)
The flame-icon onClick on PrintersPage computed popover position as a
fixed { top: rect.bottom + 4, left: Math.max(8, rect.right - 240) }
with no viewport-overflow check. The flame icon sits at the bottom of
the AMS info section on the printer card, so on most realistic viewports
rect.bottom + 4 + popover_height (~320px) overruns viewport.height and
the popover renders partially or entirely off-screen with the Start
button unreachable. Reporter (kleinweby, P1S + AMS-HT) worked around it
via DevTools to confirm the popover was actually there, just clipped.
Extract a computePopoverPosition() helper in utils/popoverPosition.ts:
- defaults to below + right-aligned to the trigger (preserves the
original visual layout when there's room),
- flips ABOVE the trigger when below would overflow AND above fits,
- stays below in the degraded case (popover taller than the viewport) —
at least the top is visible and the user can scroll inside the
popover; flipping to a top-clipped position would lose the action
buttons too,
- clamps the left coordinate so a trigger near either viewport edge
can't push the popover off-screen horizontally either.
Both PrintersPage callsites (the compact AMS row at :3498 and the
dual-nozzle layout at :4011) route through the helper.
This is part 1 of #1447. The functional drying bug — printer receives
the ams_filament_drying MQTT command, ACKs it, but never starts/stops
drying on Bambuddy's request while the printer's own touchscreen works
— is NOT addressed here. Diagnosing it needs the printer's actual
response payload (whether result: "fail" and the specific reason code),
which bambu_mqtt.py:918 currently doesn't log for ams_filament_drying.
Punted to a follow-up where I'll extend the existing extrusion_cali_* /
ams_filament_setting payload-logging path at :919-920 to cover
ams_filament_drying too, ask the reporter to retry, and fix the
command side based on what the printer actually returns.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
export interface PopoverPosition {
|
||||
top: number;
|
||||
left: number;
|
||||
}
|
||||
|
||||
interface RectLike {
|
||||
top: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
}
|
||||
|
||||
export interface ComputePopoverPositionOpts {
|
||||
/** Trigger element's bounding rect (viewport coordinates). */
|
||||
triggerRect: RectLike;
|
||||
/** Popover width in CSS pixels. */
|
||||
popoverWidth: number;
|
||||
/**
|
||||
* Estimated popover height in CSS pixels. Used to detect bottom-edge
|
||||
* overflow so we can flip above the trigger. A conservative over-estimate
|
||||
* is preferable to an under-estimate — over-estimating just flips slightly
|
||||
* sooner, under-estimating leaves the popover clipped off the viewport.
|
||||
*/
|
||||
estimatedHeight: number;
|
||||
/** Viewport height. Defaults to window.innerHeight. Injectable for tests. */
|
||||
viewportHeight?: number;
|
||||
/** Viewport width. Defaults to window.innerWidth. Injectable for tests. */
|
||||
viewportWidth?: number;
|
||||
/** Margin to keep between the popover and the viewport edges. */
|
||||
margin?: number;
|
||||
/** Gap between the trigger and the popover. */
|
||||
gap?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute fixed-positioning coordinates for a popover anchored to a trigger.
|
||||
*
|
||||
* Default placement is BELOW the trigger, right-aligned to the trigger. Flips
|
||||
* to ABOVE the trigger when below would overflow the viewport (#1447 — the
|
||||
* AMS drying popover on the printer card sits at the bottom of the AMS row
|
||||
* and was rendering off the bottom of the viewport with the Start button
|
||||
* unreachable on smaller screens).
|
||||
*
|
||||
* Horizontal axis right-aligns to triggerRect.right and clamps to the
|
||||
* viewport with the configured margin so a trigger near the right edge
|
||||
* doesn't push the popover off-screen.
|
||||
*/
|
||||
export function computePopoverPosition(opts: ComputePopoverPositionOpts): PopoverPosition {
|
||||
const {
|
||||
triggerRect,
|
||||
popoverWidth,
|
||||
estimatedHeight,
|
||||
viewportHeight = window.innerHeight,
|
||||
viewportWidth = window.innerWidth,
|
||||
margin = 8,
|
||||
gap = 4,
|
||||
} = opts;
|
||||
|
||||
// Vertical: prefer below, flip to above only when below overflows AND
|
||||
// above would actually fit. If neither fits (a popover taller than the
|
||||
// viewport), stay below — at least the top of the popover is visible
|
||||
// and the user can scroll inside it, which is better than flipping to a
|
||||
// top-clipped position where the action buttons might also be unreachable.
|
||||
let top = triggerRect.bottom + gap;
|
||||
const wouldOverflowBottom = top + estimatedHeight > viewportHeight - margin;
|
||||
if (wouldOverflowBottom) {
|
||||
const aboveTop = triggerRect.top - gap - estimatedHeight;
|
||||
if (aboveTop >= margin) {
|
||||
top = aboveTop;
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal: right-align to trigger; clamp to viewport bounds.
|
||||
let left = triggerRect.right - popoverWidth;
|
||||
if (left < margin) {
|
||||
left = margin;
|
||||
} else if (left + popoverWidth > viewportWidth - margin) {
|
||||
left = Math.max(margin, viewportWidth - popoverWidth - margin);
|
||||
}
|
||||
|
||||
return { top, left };
|
||||
}
|
||||
Reference in New Issue
Block a user