bridge bevy and react-map-gl to exchange gps (#6)

* isolate ipc pattern

* shuffle logic in main for readability, remove unused webview message observer

* renders react map

---------

Co-authored-by: geoffsee <>
This commit is contained in:
Geoff Seemueller
2025-07-08 17:44:37 -04:00
committed by GitHub
parent 59c0474bf9
commit 92e5cfb21c
8 changed files with 366 additions and 49 deletions

View File

@@ -33,6 +33,7 @@
"typescript": "~5.8.3",
"typescript-eslint": "^8.34.1",
"vite": "^7.0.0",
"vite-tsconfig-paths": "^5.1.4"
"vite-tsconfig-paths": "^5.1.4",
"bevy_flurx_api": "^0.1.0"
}
}

View File

@@ -1,28 +1,156 @@
import Map from 'react-map-gl/mapbox'; // ↔ v5+ uses this import path
import 'mapbox-gl/dist/mapbox-gl.css';
import {Box, Button, HStack} from '@chakra-ui/react';
import {useCallback, useEffect, useState} from "react";
// public key
const key =
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
// Types for bevy_flurx_ipc communication
interface GpsPosition {
latitude: number;
longitude: number;
zoom: number;
}
interface VesselStatus {
latitude: number;
longitude: number;
heading: number;
speed: number;
}
interface MapViewParams {
latitude: number;
longitude: number;
zoom: number;
}
interface AuthParams {
authenticated: boolean;
token: string | null;
}
function App() {
// Map state that can be updated from Rust
const [mapView, setMapView] = useState({
longitude: -122.4,
latitude: 37.8,
zoom: 14
});
// Button click handlers
const handleNavigationClick = useCallback(async () => {
if (typeof window !== 'undefined' && (window as any).__FLURX__) {
try {
await (window as any).__FLURX__.invoke("navigation_clicked");
console.log('Navigation clicked');
} catch (error) {
console.error('Failed to invoke navigation_clicked:', error);
}
}
}, []);
const handleSearchClick = useCallback(async () => {
if (typeof window !== 'undefined' && (window as any).__FLURX__) {
try {
await (window as any).__FLURX__.invoke("search_clicked");
console.log('Search clicked');
} catch (error) {
console.error('Failed to invoke search_clicked:', error);
}
}
}, []);
const handleMapViewChange = useCallback(async (evt: any) => {
const { longitude, latitude, zoom } = evt.viewState;
setMapView({ longitude, latitude, zoom });
if (typeof window !== 'undefined' && (window as any).__FLURX__) {
try {
const mapViewParams: MapViewParams = {
latitude,
longitude,
zoom
};
await (window as any).__FLURX__.invoke("map_view_changed", mapViewParams);
console.log('Map view changed:', mapViewParams);
} catch (error) {
console.error('Failed to invoke map_view_changed:', error);
}
}
}, []);
// Poll for vessel status updates
useEffect(() => {
const pollVesselStatus = async () => {
if (typeof window !== 'undefined' && (window as any).__FLURX__) {
try {
const vesselStatus: VesselStatus = await (window as any).__FLURX__.invoke("get_vessel_status");
console.log('Vessel status:', vesselStatus);
// You can update vessel position on map here if needed
} catch (error) {
console.error('Failed to get vessel status:', error);
}
}
};
// Poll every 5 seconds
const interval = setInterval(pollVesselStatus, 5000);
return () => clearInterval(interval);
}, []);
// Initialize map with data from Rust
useEffect(() => {
const initializeMap = async () => {
if (typeof window !== 'undefined' && (window as any).__FLURX__) {
try {
const mapInit: GpsPosition = await (window as any).__FLURX__.invoke("get_map_init");
console.log('Map initialization data:', mapInit);
setMapView({
latitude: mapInit.latitude,
longitude: mapInit.longitude,
zoom: mapInit.zoom
});
} catch (error) {
console.error('Failed to get map initialization data:', error);
}
}
};
initializeMap();
}, []);
return (
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
<Box w="100vw" h="100vh" position="relative" overflow="hidden">
{/* Button bar — absolutely positioned inside the wrapper */}
<HStack position="absolute" top={4} right={4} zIndex={1}>
<Button colorScheme="blue" size="sm" variant="solid">
<Button
colorScheme="blue"
size="sm"
variant="solid"
onClick={handleNavigationClick}
>
Navigation
</Button>
<Button colorScheme="teal" size="sm" variant="solid">
<Button
colorScheme="teal"
size="sm"
variant="solid"
onClick={handleSearchClick}
>
Search
</Button>
</HStack>
<Map
mapboxAccessToken={atob(key)}
initialViewState={{longitude: -122.4, latitude: 37.8, zoom: 14}}
initialViewState={mapView}
onMove={handleMapViewChange}
mapStyle="mapbox://styles/mapbox/dark-v11"
reuseMaps
attributionControl={false}
@@ -32,4 +160,4 @@ function App() {
);
}
export default App;
export default App;