mirror of
https://github.com/seemueller-io/yachtpit.git
synced 2025-09-08 22:46:45 +00:00
Add GPS service and nautical base city data
- Implement `GpsService` with methods for position updates and enabling/disabling GPS. - Introduce test data for nautical base cities with key attributes like population, coordinates, and images. - Update dependencies in `bun.lock` with required packages such as `geojson`.
This commit is contained in:
@@ -1,13 +1,29 @@
|
||||
import Map from 'react-map-gl/mapbox'; // ↔ v5+ uses this import path
|
||||
// import Map from 'react-map-gl/mapbox';
|
||||
// import {Source, Layer} from 'react-map-gl/maplibre';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import {Box, Button, HStack} from '@chakra-ui/react';
|
||||
import {Box, Button, HStack, Input} from '@chakra-ui/react';
|
||||
import {useCallback, useEffect, useState} from "react";
|
||||
import MapNext from "@/MapNext.tsx";
|
||||
// import type {FeatureCollection} from 'geojson';
|
||||
// import type {CircleLayerSpecification} from "mapbox-gl";
|
||||
|
||||
// public key
|
||||
const key =
|
||||
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
|
||||
|
||||
|
||||
// const vesselLayerStyle: CircleLayerSpecification = {
|
||||
// id: 'vessel',
|
||||
// type: 'circle',
|
||||
// paint: {
|
||||
// 'circle-radius': 8,
|
||||
// 'circle-color': '#ff4444',
|
||||
// 'circle-stroke-width': 2,
|
||||
// 'circle-stroke-color': '#ffffff'
|
||||
// },
|
||||
// source: ''
|
||||
// };
|
||||
|
||||
// Types for bevy_flurx_ipc communication
|
||||
interface GpsPosition {
|
||||
latitude: number;
|
||||
@@ -22,25 +38,49 @@ interface VesselStatus {
|
||||
speed: number;
|
||||
}
|
||||
|
||||
interface MapViewParams {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
zoom: number;
|
||||
}
|
||||
// interface MapViewParams {
|
||||
// latitude: number;
|
||||
// longitude: number;
|
||||
// zoom: number;
|
||||
// }
|
||||
|
||||
interface AuthParams {
|
||||
authenticated: boolean;
|
||||
token: string | null;
|
||||
}
|
||||
// interface AuthParams {
|
||||
// authenticated: boolean;
|
||||
// token: string | null;
|
||||
// }
|
||||
|
||||
function App() {
|
||||
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
|
||||
// Map state that can be updated from Rust
|
||||
const [mapView, setMapView] = useState({
|
||||
longitude: -122.4,
|
||||
latitude: 37.8,
|
||||
zoom: 14
|
||||
});
|
||||
// const [mapView, setMapView] = useState({
|
||||
// longitude: -122.4,
|
||||
// latitude: 37.8,
|
||||
// zoom: 14
|
||||
// });
|
||||
|
||||
// Vessel position state
|
||||
// const [vesselPosition, setVesselPosition] = useState<VesselStatus | null>(null);
|
||||
|
||||
// Create vessel geojson data
|
||||
// const vesselGeojson: FeatureCollection = {
|
||||
// type: 'FeatureCollection',
|
||||
// features: vesselPosition ? [
|
||||
// {
|
||||
// type: 'Feature',
|
||||
// geometry: {
|
||||
// type: 'Point',
|
||||
// coordinates: [vesselPosition.longitude, vesselPosition.latitude]
|
||||
// },
|
||||
// properties: {
|
||||
// title: 'Vessel Position',
|
||||
// heading: vesselPosition.heading,
|
||||
// speed: vesselPosition.speed
|
||||
// }
|
||||
// }
|
||||
// ] : []
|
||||
// };
|
||||
|
||||
// Button click handlers
|
||||
const handleNavigationClick = useCallback(async () => {
|
||||
@@ -56,6 +96,7 @@ function App() {
|
||||
|
||||
|
||||
const handleSearchClick = useCallback(async () => {
|
||||
setIsSearchOpen(!isSearchOpen);
|
||||
if (typeof window !== 'undefined' && (window as any).__FLURX__) {
|
||||
try {
|
||||
await (window as any).__FLURX__.invoke("search_clicked");
|
||||
@@ -66,24 +107,24 @@ function App() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
// 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(() => {
|
||||
@@ -92,7 +133,7 @@ function App() {
|
||||
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
|
||||
// setVesselPosition(vesselStatus);
|
||||
} catch (error) {
|
||||
console.error('Failed to get vessel status:', error);
|
||||
}
|
||||
@@ -101,6 +142,8 @@ function App() {
|
||||
|
||||
// Poll every 5 seconds
|
||||
const interval = setInterval(pollVesselStatus, 5000);
|
||||
// Also poll immediately
|
||||
pollVesselStatus();
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
@@ -111,11 +154,11 @@ function App() {
|
||||
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
|
||||
});
|
||||
// setMapView({
|
||||
// latitude: mapInit.latitude,
|
||||
// longitude: mapInit.longitude,
|
||||
// zoom: mapInit.zoom
|
||||
// });
|
||||
} catch (error) {
|
||||
console.error('Failed to get map initialization data:', error);
|
||||
}
|
||||
@@ -125,37 +168,67 @@ function App() {
|
||||
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}>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
>
|
||||
<Button
|
||||
colorScheme="teal"
|
||||
size="sm"
|
||||
variant="solid"
|
||||
onClick={handleSearchClick}
|
||||
mr={2}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
{isSearchOpen && <Box
|
||||
w="200px"
|
||||
transition="all 0.3s"
|
||||
transform={`translateX(${isSearchOpen ? "0" : "100%"})`}
|
||||
opacity={isSearchOpen ? 1 : 0}
|
||||
color="white"
|
||||
>
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
size="sm"
|
||||
_placeholder={{
|
||||
color: "#d1cfcf"
|
||||
}}
|
||||
/>
|
||||
</Box>}
|
||||
</Box>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
size="sm"
|
||||
variant="solid"
|
||||
onClick={handleNavigationClick}
|
||||
>
|
||||
Navigation
|
||||
</Button>
|
||||
<Button
|
||||
colorScheme="teal"
|
||||
size="sm"
|
||||
variant="solid"
|
||||
onClick={handleSearchClick}
|
||||
>
|
||||
Search
|
||||
Layer
|
||||
</Button>
|
||||
</HStack>
|
||||
<Map
|
||||
mapboxAccessToken={atob(key)}
|
||||
initialViewState={mapView}
|
||||
onMove={handleMapViewChange}
|
||||
mapStyle="mapbox://styles/mapbox/dark-v11"
|
||||
reuseMaps
|
||||
attributionControl={false}
|
||||
style={{width: '100%', height: '100%'}} // let the wrapper dictate size
|
||||
/>
|
||||
<MapNext mapboxPublicKey={atob(key)}/>
|
||||
{/*<Map*/}
|
||||
{/* mapboxAccessToken={atob(key)}*/}
|
||||
{/* initialViewState={mapView}*/}
|
||||
{/* onMove={handleMapViewChange}*/}
|
||||
{/* mapStyle="mapbox://styles/mapbox/dark-v11"*/}
|
||||
{/* reuseMaps*/}
|
||||
{/* attributionControl={false}*/}
|
||||
{/* style={{width: '100%', height: '100%'}} // let the wrapper dictate size*/}
|
||||
{/*>*/}
|
||||
{/* /!*{vesselPosition && (*!/*/}
|
||||
{/* /!* <Source id="vessel-data" type="geojson" data={vesselGeojson}>*!/*/}
|
||||
{/* /!* <Layer {...vesselLayerStyle} />*!/*/}
|
||||
{/* /!* </Source>*!/*/}
|
||||
{/* /!*)}*!/*/}
|
||||
{/*</Map>*/}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user