feat : add right sidebar stating the height , depth
This commit is contained in:
parent
8927422283
commit
f051581171
3 changed files with 173 additions and 19 deletions
36
package-lock.json
generated
36
package-lock.json
generated
|
|
@ -1842,12 +1842,16 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
|
|
@ -2569,9 +2573,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
||||
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.assign": {
|
||||
|
|
@ -3035,9 +3039,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.2.tgz",
|
||||
"integrity": "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==",
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
|
||||
"integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
|
|
@ -3057,12 +3061,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.2.tgz",
|
||||
"integrity": "sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==",
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
|
||||
"integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.6.2"
|
||||
"react-router": "7.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
|
|
@ -3199,9 +3203,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import './App.css'
|
||||
import './App.css';
|
||||
import { useState } from 'react';
|
||||
import OpenLayerMap from './components/OpenLayerMap';
|
||||
import OpenLayerEsri from './components/OpenLayerEsri';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Map, View } from 'ol';
|
|||
import 'ol/ol.css';
|
||||
import TileLayer from 'ol/layer/Tile';
|
||||
import XYZ from 'ol/source/XYZ';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
import { fromLonLat, toLonLat } from 'ol/proj';
|
||||
import { Coordinate } from 'ol/coordinate';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import { layerConfigs } from './Methods/Layers';
|
||||
|
|
@ -11,6 +11,7 @@ import { loadAndRenderGeoJsonLayer } from './Methods/GeoJson_Load';
|
|||
import { addMarker } from './Methods/Marker';
|
||||
import gsap from 'gsap';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { easeOut } from 'ol/easing';
|
||||
|
||||
// --- THEME CONFIGURATION ---
|
||||
const themes = {
|
||||
|
|
@ -82,6 +83,12 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
|
|||
Object.fromEntries(layerConfigs.map(layer => [layer.topic, true]))
|
||||
);
|
||||
const [clickedCoordinate, setClickedCoordinate] = useState<Coordinate>();
|
||||
const [mapStats, setMapStats] = useState({
|
||||
zoom: 2,
|
||||
center: [0, 0] as Coordinate,
|
||||
rotation: 0,
|
||||
size: { width: 0, height: 0 }
|
||||
});
|
||||
|
||||
// -- REFS --
|
||||
const mapDivRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -130,6 +137,20 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
|
|||
|
||||
mapRef.current = map;
|
||||
map.on('click', (e) => setClickedCoordinate(e.coordinate));
|
||||
map.on('moveend', () => {
|
||||
const view = map.getView();
|
||||
const center = view.getCenter() ?? [0, 0];
|
||||
const zoom = view.getZoom() ?? 0;
|
||||
const rotation = view.getRotation() ?? 0;
|
||||
const size = map.getSize();
|
||||
|
||||
setMapStats({
|
||||
center,
|
||||
zoom,
|
||||
rotation,
|
||||
size: { width: size?.[0] ?? 0, height: size?.[1] ?? 0 }
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -150,7 +171,7 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
|
|||
const lon = parseFloat(initialAddressData[0].lon);
|
||||
const coords = fromLonLat([lon, lat]);
|
||||
|
||||
mapRef.current!.getView().animate({ center: coords, zoom: 14, duration: 2500, ease: (t) => Math.pow(t, 2) });
|
||||
mapRef.current!.getView().animate({ center: coords, zoom: 14, duration: 2500, easing: easeOut });
|
||||
markerLayerRef.current = addMarker(coords, mapRef.current!, markerLayerRef.current);
|
||||
|
||||
layerConfigs.forEach((layerConfig) => {
|
||||
|
|
@ -170,6 +191,10 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
|
|||
};
|
||||
|
||||
const formatLayerName = (topic: string) => topic.replace(/_/g, ' ').replace(/SD$/, '').trim();
|
||||
const formatNumber = (value: number, digits = 2) => Number.isFinite(value) ? value.toFixed(digits) : '—';
|
||||
const centerLonLat = toLonLat(mapStats.center);
|
||||
const clickedLonLat = clickedCoordinate ? toLonLat(clickedCoordinate) : null;
|
||||
const activeLayerCount = Object.values(layerVisibility).filter(Boolean).length;
|
||||
|
||||
// -- STYLES --
|
||||
const styles = {
|
||||
|
|
@ -245,6 +270,75 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
|
|||
borderRadius: '8px', border: `1px solid ${currentTheme.border}`,
|
||||
boxShadow: currentTheme.shadow,
|
||||
transition: 'background 0.3s, color 0.3s'
|
||||
},
|
||||
rightSidebar: {
|
||||
position: 'absolute' as const,
|
||||
top: '92px',
|
||||
right: '24px',
|
||||
width: '320px',
|
||||
maxHeight: 'calc(100% - 140px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: '12px',
|
||||
pointerEvents: 'auto' as const,
|
||||
zIndex: 1100
|
||||
},
|
||||
sidebarCard: {
|
||||
background: currentTheme.glass,
|
||||
backdropFilter: 'blur(20px)',
|
||||
WebkitBackdropFilter: 'blur(20px)',
|
||||
border: `1px solid ${currentTheme.border}`,
|
||||
borderRadius: '16px',
|
||||
padding: '16px',
|
||||
boxShadow: currentTheme.shadow,
|
||||
transition: 'all 0.3s ease'
|
||||
},
|
||||
sidebarTitle: {
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '1px',
|
||||
color: currentTheme.textMuted,
|
||||
textTransform: 'uppercase' as const,
|
||||
marginBottom: '12px'
|
||||
},
|
||||
statRow: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '6px 0',
|
||||
borderBottom: `1px dashed ${currentTheme.border}`
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: '12px',
|
||||
color: currentTheme.textMuted
|
||||
},
|
||||
statValue: {
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: currentTheme.textMain
|
||||
},
|
||||
layerMetaRow: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
padding: '8px 0',
|
||||
borderBottom: `1px dashed ${currentTheme.border}`
|
||||
},
|
||||
layerMetaName: {
|
||||
fontSize: '12px',
|
||||
color: currentTheme.textMain,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
},
|
||||
layerMetaBadge: {
|
||||
fontSize: '10px',
|
||||
fontWeight: 600,
|
||||
padding: '2px 8px',
|
||||
borderRadius: '999px',
|
||||
border: `1px solid ${currentTheme.border}`,
|
||||
color: currentTheme.textMuted
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -341,6 +435,62 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* RIGHT SIDEBAR */}
|
||||
<div className="floating-ui" style={styles.rightSidebar}>
|
||||
<div style={styles.sidebarCard}>
|
||||
<div style={styles.sidebarTitle}>General Values</div>
|
||||
<div style={styles.statRow}>
|
||||
<span style={styles.statLabel}>Height</span>
|
||||
<span style={styles.statValue}>{mapStats.size.height ? `${mapStats.size.height}px` : '—'}</span>
|
||||
</div>
|
||||
<div style={styles.statRow}>
|
||||
<span style={styles.statLabel}>Depth</span>
|
||||
<span style={styles.statValue}>{mapStats.size.width ? `${mapStats.size.width}px` : '—'}</span>
|
||||
</div>
|
||||
<div style={styles.statRow}>
|
||||
<span style={styles.statLabel}>Zoom</span>
|
||||
<span style={styles.statValue}>{formatNumber(mapStats.zoom, 2)}</span>
|
||||
</div>
|
||||
<div style={styles.statRow}>
|
||||
<span style={styles.statLabel}>Center</span>
|
||||
<span style={styles.statValue}>{formatNumber(centerLonLat[1], 4)} N / {formatNumber(centerLonLat[0], 4)} E</span>
|
||||
</div>
|
||||
<div style={styles.statRow}>
|
||||
<span style={styles.statLabel}>Last Click</span>
|
||||
<span style={styles.statValue}>{clickedLonLat ? `${formatNumber(clickedLonLat[1], 4)} N / ${formatNumber(clickedLonLat[0], 4)} E` : '—'}</span>
|
||||
</div>
|
||||
<div style={{ ...styles.statRow, borderBottom: 'none' }}>
|
||||
<span style={styles.statLabel}>Layers On</span>
|
||||
<span style={styles.statValue}>{activeLayerCount}/{layerConfigs.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.sidebarCard}>
|
||||
<div style={styles.sidebarTitle}>Layer Values</div>
|
||||
<div className="custom-scrollbar" style={{ maxHeight: '36vh', overflowY: 'auto', paddingRight: '6px' }}>
|
||||
{layerConfigs.map((layer) => {
|
||||
const isActive = layerVisibility[layer.topic];
|
||||
const layerColor = extractColorFromStyle(layer.style);
|
||||
|
||||
return (
|
||||
<div key={layer.topic} style={{ ...styles.layerMetaRow, borderBottom: `1px dashed ${currentTheme.border}` }}>
|
||||
<div style={styles.layerMetaName}>
|
||||
<span style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: layerColor, boxShadow: `0 0 8px ${layerColor}`, opacity: isActive ? 1 : 0.3 }} />
|
||||
{formatLayerName(layer.topic)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<span style={styles.layerMetaBadge}>{layer.radius}m</span>
|
||||
<span style={{ ...styles.layerMetaBadge, color: isActive ? currentTheme.textMain : currentTheme.textMuted }}>
|
||||
{isActive ? 'On' : 'Off'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue