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
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user