gridloc / src /components /twin /MapPanel.tsx
lassanmonster's picture
GridLoc: Bengaluru parking congestion digital twin
db3dbe1
Raw
History Blame Contribute Delete
4.94 kB
import { useTwinStore } from '../../lib/twin/store'
import type { LayerKey } from '../../lib/twin/store'
import { BASEMAPS } from '../../lib/twin/basemaps'
import {
Eye,
EyeOff,
Type,
Route,
Square,
Building2,
Droplets,
Trees,
Box,
Layers,
} from 'lucide-react'
// Layer toggle rows shown under the basemap list. 3D rows sit at the bottom.
const LAYER_ROWS: Array<{ key: LayerKey; label: string; icon: React.ReactNode }> = [
{ key: 'label', label: 'Label', icon: <Type className="h-4 w-4" /> },
{ key: 'road', label: 'Road', icon: <Route className="h-4 w-4" /> },
{ key: 'border', label: 'Border', icon: <Square className="h-4 w-4" /> },
{ key: 'building', label: 'Building', icon: <Building2 className="h-4 w-4" /> },
{ key: 'water', label: 'Water', icon: <Droplets className="h-4 w-4" /> },
{ key: 'land', label: 'Land', icon: <Trees className="h-4 w-4" /> },
{ key: 'building3d', label: '3D Building', icon: <Box className="h-4 w-4" /> },
{ key: 'road3d', label: '3D Road', icon: <Layers className="h-4 w-4" /> },
]
/**
* Map view of the left panel: choose the basemap style (kepler-style list with
* swatches) and toggle which basemap layers are visible, including extruded 3D
* buildings/roads. All wired to the store; the map applies the changes live.
*/
export function MapPanel() {
const basemapId = useTwinStore((s) => s.basemapId)
const setBasemap = useTwinStore((s) => s.setBasemap)
const layers = useTwinStore((s) => s.layers)
const toggleLayer = useTwinStore((s) => s.toggleLayer)
return (
<div className="twin-scroll min-h-0 flex-1 overflow-y-auto px-3 pb-4 pt-3">
{/* Base map styles */}
<SectionLabel>Base map</SectionLabel>
<div className="mt-2 flex flex-col gap-1">
{BASEMAPS.map((b) => {
const active = b.id === basemapId
return (
<button
key={b.id}
onClick={() => setBasemap(b.id)}
className="flex items-center gap-3 rounded-lg px-2.5 py-2 text-left text-[13px] transition-colors"
style={{
background: active ? 'var(--tw-hover)' : 'transparent',
border: `1px solid ${active ? 'var(--tw-border)' : 'transparent'}`,
color: active ? 'var(--tw-text)' : 'var(--tw-muted)',
fontFamily: 'var(--font-display)',
cursor: 'pointer',
}}
onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--tw-hover)' }}
onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent' }}
>
<span
className="relative inline-block h-9 w-12 flex-none overflow-hidden rounded"
style={{ background: b.swatch, border: '1px solid var(--tw-border)' }}
>
{b.thumb && (
<img
src={b.thumb}
alt=""
loading="lazy"
className="absolute inset-0 h-full w-full object-cover"
onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none' }}
/>
)}
</span>
{b.label}
</button>
)
})}
</div>
{/* Layer toggles */}
<SectionLabel className="mt-5">Map layers</SectionLabel>
<div className="mt-2 flex flex-col gap-0.5">
{LAYER_ROWS.map((row) => {
const on = layers[row.key]
return (
<button
key={row.key}
onClick={() => toggleLayer(row.key)}
className="flex items-center gap-3 rounded-lg px-2.5 py-2 text-left text-[13px] transition-colors"
style={{
background: 'transparent',
color: on ? 'var(--tw-text)' : 'var(--tw-muted)',
fontFamily: 'var(--font-display)',
cursor: 'pointer',
}}
onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--tw-hover)' }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent' }}
>
<span className="flex-none" style={{ opacity: on ? 0.9 : 0.4 }}>{row.icon}</span>
<span className="flex-1">{row.label}</span>
<span className="flex-none" style={{ opacity: on ? 0.9 : 0.4 }}>
{on ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
</span>
</button>
)
})}
</div>
</div>
)
}
function SectionLabel({ children, className }: { children: React.ReactNode; className?: string }) {
return (
<div
className={`px-2 text-[10px] uppercase ${className ?? ''}`}
style={{ fontFamily: 'var(--font-mono)', letterSpacing: '0.16em', color: 'var(--tw-muted)' }}
>
{children}
</div>
)
}