Files
house-plan-maker/apps/client/src/components/editor/overlays/ScaleBar.tsx
T
alexei.dolgolyov af8b9fe00f feat: complete house plan maker application
Full-featured house/apartment floor plan editor with:

- Turborepo monorepo (React/Vite client, Fastify/Prisma server, shared Zod schemas)
- 2D room editor with walls, doors, windows, furniture, electrical elements
- 3D room preview with Three.js (auto-hide nearest walls, bird's eye default)
- Wall projection views with interactive drag (elevation, position)
- Apartment floor plan view with room positioning
- Copy/paste, alignment tools, measurement tool, annotations
- Item-attached annotations with leader lines (visible on projections)
- Door open direction (LEFT/RIGHT/INWARD/OUTWARD) with swing arc
- Floor type textures (wood, tile, concrete, laminate, herringbone)
- Wall color picker for 3D view
- Furniture: bed, desk, wardrobe, sofa, table, chair, shelf, nightstand, dresser, bookcase, TV (with stand toggle), AC unit
- Furniture elevation support (wall-mounted items)
- Auto-save with dirty state tracking, batch save API
- Rotation-aware collision detection (SAT/OBB) with 3D elevation check
- Rotation-aware hit testing
- i18n (English/Russian) with locale-aware number formatting
- Dark mode with system preference detection
- Undo/redo, keyboard shortcuts, scale bar
- PDF/PNG/JSON export and JSON import
- Focus trap modal, toast notifications, tooltips
- Responsive layout with overlay palettes
2026-04-05 22:34:03 +03:00

61 lines
1.5 KiB
TypeScript

import styles from './scale-bar.module.css';
interface ScaleBarProps {
readonly zoom: number;
}
/** Fixed pixel width of the scale bar on screen. */
const BAR_WIDTH_PX = 100;
/**
* Compute a "nice" real-world distance for the scale bar
* and the corresponding pixel width to display.
*/
function computeScaleInfo(zoom: number): { label: string; barPx: number } {
// zoom = pixels per meter
// BAR_WIDTH_PX pixels = BAR_WIDTH_PX / zoom meters
const rawMeters = BAR_WIDTH_PX / zoom;
// Find the nearest "nice" value
const niceValues = [
0.01, 0.02, 0.05, 0.1, 0.2, 0.25, 0.5,
1, 2, 5, 10, 20, 50, 100,
];
let bestValue = niceValues[0];
let bestDiff = Math.abs(rawMeters - bestValue);
for (const v of niceValues) {
const diff = Math.abs(rawMeters - v);
if (diff < bestDiff) {
bestDiff = diff;
bestValue = v;
}
}
const barPx = bestValue * zoom;
let label: string;
if (bestValue >= 1) {
label = `${bestValue}m`;
} else {
label = `${Math.round(bestValue * 100)}cm`;
}
return { label, barPx };
}
export function ScaleBar({ zoom }: ScaleBarProps) {
const { label, barPx } = computeScaleInfo(zoom);
return (
<div className={styles.container}>
<div className={styles.bar} style={{ width: `${Math.round(barPx)}px` }}>
<div className={styles.tickLeft} />
<div className={styles.line} />
<div className={styles.tickRight} />
</div>
<span className={styles.label}>{label}</span>
</div>
);
}