fix(dashboard): nie scrollen – Inhalt hoeher als die Leinwand wird eingepasst
Die Einpassung nimmt max(Leinwandhoehe, Rasterhoehe) (fitCanvasToContent). Anlass: Dashboard des Nutzers 940 px hoch bei Leinwand 849 px, dadurch scrollte es ueberall. Im Bearbeitungsmodus bleibt die Hoehe vom Beginn des Bearbeitens stehen, damit der Massstab beim Ziehen nicht wandert. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { stubResizeObserver } from '@/test/fake-resize-observer';
|
||||
import {
|
||||
computeCanvasScale,
|
||||
fitCanvasToContent,
|
||||
DASHBOARD_BOTTOM_GAP_PX,
|
||||
RESIZE_AXIS_FALLBACK,
|
||||
scaledPositionStrategy,
|
||||
@@ -692,6 +693,22 @@ describe('Leinwand: Dashboard wie ein Bild mitskalieren', () => {
|
||||
return { outer, inner };
|
||||
}
|
||||
|
||||
describe('fitCanvasToContent', () => {
|
||||
it('Inhalt passt in die Leinwand: Leinwand bleibt (1:1 am Original-PC)', () => {
|
||||
expect(fitCanvasToContent({ w: 1641, h: 849 }, 700)).toEqual({ w: 1641, h: 849 });
|
||||
});
|
||||
it('Inhalt hoeher als die Leinwand: Inhaltshoehe zaehlt, damit nie gescrollt wird', () => {
|
||||
expect(fitCanvasToContent({ w: 1641, h: 849 }, 940)).toEqual({ w: 1641, h: 940 });
|
||||
// am Original-PC selbst: 849 / 940 -> alles passt hinein
|
||||
expect(
|
||||
computeCanvasScale({ w: 1641, h: 849 }, fitCanvasToContent({ w: 1641, h: 849 }, 940)),
|
||||
).toBeCloseTo(849 / 940);
|
||||
});
|
||||
it('unbrauchbare Hoehe: Leinwand bleibt', () => {
|
||||
expect(fitCanvasToContent({ w: 1600, h: 800 }, Number.NaN)).toEqual({ w: 1600, h: 800 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeCanvasScale', () => {
|
||||
it('breitenbegrenzt: freier Platz unten', () => {
|
||||
expect(computeCanvasScale({ w: 1280, h: 900 }, { w: 1600, h: 800 })).toBe(0.8);
|
||||
@@ -758,12 +775,23 @@ describe('Leinwand: Dashboard wie ein Bild mitskalieren', () => {
|
||||
cleanup();
|
||||
|
||||
setInnerHeight(768);
|
||||
// Rasterinhalt niedriger als die Leinwand (300 < 374) — sonst zaehlte die
|
||||
// Inhaltshoehe (fitCanvasToContent).
|
||||
stubResizeObserver({ width: 1000, height: 300 });
|
||||
const second = await renderGrid({ canvas: { w: 800, h: 374 } });
|
||||
// min(1000 / 800, 748 / 374) = 1.25
|
||||
expect(second.inner.style.transform).toBe('scale(1.25)');
|
||||
expect(captured.props?.width).toBe(800);
|
||||
});
|
||||
|
||||
it('Inhalt hoeher als die Leinwand: eingepasst, damit nie gescrollt wird', async () => {
|
||||
stubResizeObserver({ width: 1000, height: 1600 });
|
||||
setInnerHeight(820); // verfuegbar 800
|
||||
const { inner } = await renderGrid({ canvas: { w: 1000, h: 800 } });
|
||||
// min(1000 / 1000, 800 / 1600) = 0.5
|
||||
expect(inner.style.transform).toBe('scale(0.5)');
|
||||
});
|
||||
|
||||
it('schmal (< 768): wie bisher — Containerbreite, kein transform, keine Erfassung', async () => {
|
||||
stubResizeObserver({ width: 600, height: 800 });
|
||||
const onCaptureCanvas = vi.fn();
|
||||
|
||||
@@ -147,6 +147,16 @@ export function computeCanvasScale(available: GridCanvas, canvas: GridCanvas): n
|
||||
return Number.isFinite(s) && s > 0 ? s : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leinwand fuer die Einpassung: ist der Inhalt (unskalierte Rasterhoehe)
|
||||
* hoeher als die Leinwand, gilt die Inhaltshoehe, sonst die Leinwand selbst —
|
||||
* so bleibt es am Original-PC bei 1:1, solange alles draufpasst.
|
||||
*/
|
||||
export function fitCanvasToContent(canvas: GridCanvas, contentHeight: number): GridCanvas {
|
||||
if (!Number.isFinite(contentHeight) || contentHeight <= canvas.h) return canvas;
|
||||
return { w: canvas.w, h: contentHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* Positionsstrategie fuer ein per `transform: scale(s)` skaliertes Raster.
|
||||
*
|
||||
@@ -409,7 +419,19 @@ export function DashboardGrid({
|
||||
// Desktop = verfuegbare Breite ab CANVAS_MIN_WIDTH; nur dort Leinwand.
|
||||
const isDesktop = width >= CANVAS_MIN_WIDTH;
|
||||
const scaled = isDesktop && canvas !== null;
|
||||
const scale = scaled ? computeCanvasScale({ w: width, h: availableHeight }, canvas) : 1;
|
||||
// "Nie scrollen" (Nutzerentscheidung): ist der Inhalt hoeher als die
|
||||
// Leinwand (schon am Original-PC laenger als der Bildschirm), zaehlt die
|
||||
// Inhaltshoehe — dann passt auch dort alles hinein, und jeder andere
|
||||
// Bildschirm zeigt dasselbe Bild. Im Bearbeitungsmodus bleibt die Hoehe vom
|
||||
// Beginn des Bearbeitens stehen, sonst wuerde das Raster beim Ziehen nach
|
||||
// unten laufend kleiner und die Kachel liefe dem Mauszeiger davon.
|
||||
const editHeightRef = useRef<number | null>(null);
|
||||
if (isEditMode && editHeightRef.current === null) editHeightRef.current = innerHeight;
|
||||
if (!isEditMode) editHeightRef.current = null;
|
||||
const contentHeight = isEditMode ? (editHeightRef.current ?? innerHeight) : innerHeight;
|
||||
const scale = scaled
|
||||
? computeCanvasScale({ w: width, h: availableHeight }, fitCanvasToContent(canvas, contentHeight))
|
||||
: 1;
|
||||
const positionStrategy = useMemo(() => scaledPositionStrategy(scale), [scale]);
|
||||
|
||||
// Leinwand erfassen: nur mit eingehaengtem Raster (also mit Kacheln), am
|
||||
|
||||
Reference in New Issue
Block a user