Files
web-cad/frontend/src/canvas/ZoomPanController.ts
T
Leopoldadmin 1bcfaffdd4 fix: replace hardcoded localhost:3001 with relative API URL + add canvas mock and integration test
- Fix frontend Network error: API_BASE now defaults to empty string (same-origin)
- Fix 3 files: api.ts, AuthContext.tsx, Dashboard.tsx
- Add Canvas 2D context mock in tests/setup.ts for jsdom
- Fix -0 vs +0 in ZoomPanController.getViewport()
- Add IntegrationWorkflow.test.ts (35 tests, full CAD workflow)
2026-06-26 17:48:24 +02:00

100 lines
3.0 KiB
TypeScript

import type { Transform, Viewport } from '../types/cad.types';
export class ZoomPanController {
private scale = 1;
private offsetX = 0;
private offsetY = 0;
private canvas: HTMLCanvasElement;
private isPanning = false;
private lastX = 0;
private lastY = 0;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
}
getTransform(): Transform {
return {
a: this.scale, b: 0, c: 0,
d: this.scale, e: this.offsetX, f: this.offsetY,
};
}
getViewport(): Viewport {
const w = this.canvas.width / this.scale;
const h = this.canvas.height / this.scale;
const minX = -this.offsetX / this.scale || 0;
const minY = -this.offsetY / this.scale || 0;
return {
minX,
minY,
maxX: minX + w,
maxY: minY + h,
};
}
getScale(): number { return this.scale; }
zoomAt(cx: number, cy: number, factor: number): void {
const newScale = Math.max(0.01, Math.min(100, this.scale * factor));
const ratio = newScale / this.scale;
this.offsetX = cx - (cx - this.offsetX) * ratio;
this.offsetY = cy - (cy - this.offsetY) * ratio;
this.scale = newScale;
}
zoomFit(elements: Array<{x:number;y:number;width:number;height:number}>): void {
if (elements.length === 0) return;
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
for (const el of elements) {
minX = Math.min(minX, el.x - el.width/2);
minY = Math.min(minY, el.y - el.height/2);
maxX = Math.max(maxX, el.x + el.width/2);
maxY = Math.max(maxY, el.y + el.height/2);
}
const padding = 40;
const scaleX = (this.canvas.width - padding*2) / (maxX - minX);
const scaleY = (this.canvas.height - padding*2) / (maxY - minY);
this.scale = Math.min(scaleX, scaleY);
this.offsetX = -minX * this.scale + padding;
this.offsetY = -minY * this.scale + padding;
}
zoomToRect(rect: { minX: number; minY: number; maxX: number; maxY: number }): void {
const padding = 20;
const w = rect.maxX - rect.minX;
const h = rect.maxY - rect.minY;
if (w <= 0 || h <= 0) return;
const scaleX = (this.canvas.width - padding * 2) / w;
const scaleY = (this.canvas.height - padding * 2) / h;
this.scale = Math.max(0.01, Math.min(100, Math.min(scaleX, scaleY)));
this.offsetX = -rect.minX * this.scale + padding;
this.offsetY = -rect.minY * this.scale + padding;
}
pan(dx: number, dy: number): void {
this.offsetX += dx;
this.offsetY += dy;
}
screenToWorld(sx: number, sy: number): { x: number; y: number } {
const rect = this.canvas.getBoundingClientRect();
const x = (sx - rect.left - this.offsetX) / this.scale;
const y = (sy - rect.top - this.offsetY) / this.scale;
return { x, y };
}
worldToScreen(wx: number, wy: number): { x: number; y: number } {
return {
x: wx * this.scale + this.offsetX,
y: wy * this.scale + this.offsetY,
};
}
reset(): void {
this.scale = 1;
this.offsetX = 0;
this.offsetY = 0;
}
}