File size: 7,711 Bytes
191b322 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
import React from 'react';
import { BimModel } from '../types';
import { Box, Layers, Maximize2, RotateCcw, ZoomIn, ZoomOut, Search, Info, Settings, Eye, EyeOff } from 'lucide-react';
interface BimViewerProps {
models: BimModel[];
}
const BimViewer: React.FC<BimViewerProps> = ({ models }) => {
const [selectedModel, setSelectedModel] = React.useState(models[0] || null);
const [layers, setLayers] = React.useState([
{ id: 'arch', name: 'Architectural', visible: true },
{ id: 'struct', name: 'Structural', visible: true },
{ id: 'mep', name: 'MEP', visible: false },
{ id: 'elec', name: 'Electrical', visible: false },
]);
const toggleLayer = (id: string) => {
setLayers(layers.map(l => l.id === id ? { ...l, visible: !l.visible } : l));
};
return (
<div className="h-[calc(100vh-12rem)] flex flex-col lg:flex-row gap-6">
{/* Model List & Layers */}
<div className="w-full lg:w-80 flex flex-col gap-6 shrink-0">
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden flex flex-col h-full">
<div className="p-4 border-b border-slate-100 bg-slate-50/50">
<h3 className="font-bold text-slate-800 flex items-center gap-2">
<Box className="w-4 h-4 text-blue-600" />
BIM Models
</h3>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{models.map(model => (
<button
key={model.id}
onClick={() => setSelectedModel(model)}
className={`w-full p-3 rounded-xl border text-left transition-all ${
selectedModel?.id === model.id
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-500/10'
: 'border-slate-200 hover:border-slate-300'
}`}
>
<p className="text-sm font-bold text-slate-800 truncate">{model.name}</p>
<div className="flex items-center justify-between mt-1">
<span className="text-[10px] font-bold text-slate-400 uppercase">v{model.version}</span>
<span className="text-[10px] text-slate-400">{model.uploadedAt}</span>
</div>
</button>
))}
</div>
<div className="p-4 border-t border-slate-100 bg-slate-50/50">
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-3">Model Layers</h4>
<div className="space-y-2">
{layers.map(layer => (
<button
key={layer.id}
onClick={() => toggleLayer(layer.id)}
className="w-full flex items-center justify-between p-2 hover:bg-white rounded-lg transition-all group"
>
<div className="flex items-center gap-2">
<Layers className={`w-3 h-3 ${layer.visible ? 'text-blue-600' : 'text-slate-300'}`} />
<span className={`text-xs font-medium ${layer.visible ? 'text-slate-700' : 'text-slate-400'}`}>
{layer.name}
</span>
</div>
{layer.visible ? (
<Eye className="w-3 h-3 text-blue-500" />
) : (
<EyeOff className="w-3 h-3 text-slate-300" />
)}
</button>
))}
</div>
</div>
</div>
</div>
{/* Viewer Stage */}
<div className="flex-1 bg-slate-900 rounded-2xl shadow-2xl relative overflow-hidden group">
{/* Mock 3D Canvas */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="relative w-full h-full flex items-center justify-center">
{/* Simple CSS 3D Cube / Shape to simulate a model */}
<div className="w-64 h-64 relative preserve-3d animate-slow-spin">
<div className="absolute inset-0 border-2 border-blue-500/30 bg-blue-500/10 backdrop-blur-sm rounded-lg transform rotate-x-45 rotate-y-45"></div>
<div className="absolute inset-0 border-2 border-blue-400/20 bg-blue-400/5 backdrop-blur-sm rounded-lg transform -rotate-x-45 -rotate-y-45"></div>
<div className="absolute inset-0 flex items-center justify-center">
<Box className="w-32 h-32 text-blue-500/20 animate-pulse" />
</div>
</div>
{/* Grid Lines */}
<div className="absolute inset-0 opacity-10 pointer-events-none"
style={{ backgroundImage: 'radial-gradient(circle, #3b82f6 1px, transparent 1px)', backgroundSize: '40px 40px' }}></div>
</div>
</div>
{/* Viewer Controls */}
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-2 bg-slate-800/80 backdrop-blur-md p-2 rounded-2xl border border-slate-700 shadow-2xl transition-all opacity-0 group-hover:opacity-100">
<button className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded-xl transition-all"><ZoomIn className="w-4 h-4" /></button>
<button className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded-xl transition-all"><ZoomOut className="w-4 h-4" /></button>
<div className="w-px h-4 bg-slate-700 mx-1"></div>
<button className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded-xl transition-all"><RotateCcw className="w-4 h-4" /></button>
<button className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded-xl transition-all"><Maximize2 className="w-4 h-4" /></button>
<div className="w-px h-4 bg-slate-700 mx-1"></div>
<button className="p-2 text-slate-300 hover:text-white hover:bg-slate-700 rounded-xl transition-all"><Settings className="w-4 h-4" /></button>
</div>
{/* Info Overlay */}
<div className="absolute top-6 left-6 bg-slate-800/80 backdrop-blur-md p-4 rounded-xl border border-slate-700 shadow-xl">
<p className="text-[10px] font-bold text-blue-400 uppercase tracking-widest mb-1">Active Model</p>
<p className="text-sm font-bold text-white">{selectedModel?.name || 'No Model Selected'}</p>
<div className="flex items-center gap-4 mt-3">
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 rounded-full bg-emerald-500"></div>
<span className="text-[10px] text-slate-400">FPS: 60</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 rounded-full bg-blue-500"></div>
<span className="text-[10px] text-slate-400">Vertices: 1.2M</span>
</div>
</div>
</div>
{/* Search / Navigation */}
<div className="absolute top-6 right-6 flex items-center gap-2">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-slate-400" />
<input
type="text"
placeholder="Find element..."
className="bg-slate-800/80 backdrop-blur-md border border-slate-700 rounded-xl pl-9 pr-4 py-2 text-xs text-white outline-none focus:ring-2 focus:ring-blue-500 w-48"
/>
</div>
<button className="p-2 bg-slate-800/80 backdrop-blur-md border border-slate-700 text-slate-300 hover:text-white rounded-xl transition-all">
<Info className="w-4 h-4" />
</button>
</div>
</div>
</div>
);
};
export default BimViewer;
|