mirror of
https://github.com/seemueller-io/yachtpit.git
synced 2025-09-08 22:46:45 +00:00
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:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user