feat: Add interaction toolbar and block count HUD to Map3D component
This commit is contained in:
@@ -1282,16 +1282,133 @@ function Scene({ interactionMode, onInteraction }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Interaction mode toolbar overlay
|
||||
function InteractionToolbar({ mode, setMode, lastInteraction }) {
|
||||
const modes = [
|
||||
{ key: INTERACTION_MODE.LOOK, icon: '👁️', label: 'Look', desc: 'Orbit camera' },
|
||||
{ key: INTERACTION_MODE.MOVE, icon: '🎯', label: 'Move', desc: 'Click to send turtle' },
|
||||
{ key: INTERACTION_MODE.BUILD, icon: '🧱', label: 'Build', desc: 'Click face to preview' },
|
||||
{ key: INTERACTION_MODE.SELECT, icon: '⬜', label: 'Select', desc: 'Click two corners' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '16px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
background: 'rgba(15, 23, 42, 0.9)',
|
||||
padding: '6px',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid rgba(100, 116, 139, 0.3)',
|
||||
zIndex: 10,
|
||||
backdropFilter: 'blur(10px)',
|
||||
}}>
|
||||
{modes.map(m => (
|
||||
<button
|
||||
key={m.key}
|
||||
onClick={() => setMode(m.key)}
|
||||
title={m.desc}
|
||||
style={{
|
||||
padding: '8px 14px',
|
||||
borderRadius: '8px',
|
||||
border: mode === m.key ? '2px solid #3b82f6' : '1px solid transparent',
|
||||
background: mode === m.key ? 'rgba(59, 130, 246, 0.2)' : 'rgba(30, 41, 59, 0.8)',
|
||||
color: mode === m.key ? '#60a5fa' : '#94a3b8',
|
||||
cursor: 'pointer',
|
||||
fontSize: '13px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '16px' }}>{m.icon}</span>
|
||||
{m.label}
|
||||
</button>
|
||||
))}
|
||||
|
||||
{lastInteraction && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: '100%',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
marginBottom: '8px',
|
||||
padding: '6px 12px',
|
||||
background: 'rgba(15, 23, 42, 0.95)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(100, 116, 139, 0.3)',
|
||||
color: '#94a3b8',
|
||||
fontSize: '12px',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{lastInteraction.mode === 'move' && `🎯 Moving turtle to (${lastInteraction.target.x}, ${lastInteraction.target.y}, ${lastInteraction.target.z})`}
|
||||
{lastInteraction.mode === 'build' && `🧱 Build at (${lastInteraction.target.x}, ${lastInteraction.target.y}, ${lastInteraction.target.z})`}
|
||||
{lastInteraction.mode === 'select' && lastInteraction.end &&
|
||||
`⬜ Selected (${lastInteraction.start.x},${lastInteraction.start.y},${lastInteraction.start.z}) → (${lastInteraction.end.x},${lastInteraction.end.y},${lastInteraction.end.z})`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Block count HUD
|
||||
function BlockCountHUD({ blocks }) {
|
||||
const count = blocks.length;
|
||||
const chunkCount = useMemo(() => {
|
||||
const chunks = new Set();
|
||||
blocks.forEach(b => chunks.add(`${Math.floor(b.x / 16)},${Math.floor(b.z / 16)}`));
|
||||
return chunks.size;
|
||||
}, [blocks]);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '12px',
|
||||
right: '12px',
|
||||
padding: '8px 12px',
|
||||
background: 'rgba(15, 23, 42, 0.85)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(100, 116, 139, 0.2)',
|
||||
color: '#94a3b8',
|
||||
fontSize: '12px',
|
||||
zIndex: 10,
|
||||
}}>
|
||||
🧊 {count.toLocaleString()} blocks · 📦 {chunkCount} chunks
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Main Map3D component
|
||||
export default function Map3D() {
|
||||
const [interactionMode, setInteractionMode] = useState(INTERACTION_MODE.LOOK);
|
||||
const [lastInteraction, setLastInteraction] = useState(null);
|
||||
const worldBlocks = useTurtleStore((state) => state.worldBlocks || []);
|
||||
|
||||
const handleInteraction = useCallback((event) => {
|
||||
setLastInteraction(event);
|
||||
// Clear notification after 5 seconds
|
||||
setTimeout(() => setLastInteraction(null), 5000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', background: '#0a0e1a' }}>
|
||||
<div style={{ width: '100%', height: '100%', background: '#0a0e1a', position: 'relative' }}>
|
||||
<Canvas
|
||||
camera={{ position: [15, 15, 15], fov: 60 }}
|
||||
style={{ background: '#0a0e1a' }}
|
||||
>
|
||||
<Scene />
|
||||
<Scene interactionMode={interactionMode} onInteraction={handleInteraction} />
|
||||
</Canvas>
|
||||
|
||||
<BlockCountHUD blocks={worldBlocks} />
|
||||
<InteractionToolbar
|
||||
mode={interactionMode}
|
||||
setMode={setInteractionMode}
|
||||
lastInteraction={lastInteraction}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user