mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
add search and layer control
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import ReactMap from 'react-map-gl/mapbox'; // ↔ v5+ uses this import path
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { Box, HStack, Button, Input, Center } from '@chakra-ui/react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Box, Button, HStack, Input } from '@chakra-ui/react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import clientChatStore from '../../stores/ClientChatStore.ts';
|
||||
|
||||
import MapNext from './MapNext.tsx';
|
||||
|
||||
@@ -30,17 +31,175 @@ interface AuthParams {
|
||||
token: string | null;
|
||||
}
|
||||
|
||||
export type Layer = { name: string; value: string };
|
||||
export type Layers = Layer[];
|
||||
|
||||
// public key
|
||||
const key =
|
||||
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
|
||||
|
||||
const layers = [
|
||||
{ name: 'Bathymetry', value: 'mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y' },
|
||||
{ name: 'Satellite', value: 'mapbox://styles/mapbox/satellite-v9' },
|
||||
];
|
||||
|
||||
function LayerSelector(props: { onClick: (e) => Promise<void> }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Button colorScheme="blue" size="sm" variant="solid" onClick={() => setIsOpen(!isOpen)}>
|
||||
Layer
|
||||
</Button>
|
||||
|
||||
{isOpen && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="100%"
|
||||
left={0}
|
||||
w="200px"
|
||||
bg="background.secondary"
|
||||
boxShadow="md"
|
||||
zIndex={2}
|
||||
>
|
||||
{layers.map(layer => (
|
||||
<Box
|
||||
id={layer.value}
|
||||
p={2}
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'whiteAlpha.200' }}
|
||||
onClick={async e => {
|
||||
setIsOpen(false);
|
||||
await props.onClick(e);
|
||||
}}
|
||||
>
|
||||
{layer.name}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Map(props: { visible: boolean }) {
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
const [selectedLayer, setSelectedLayer] = useState(layers[0]);
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||
|
||||
// const handleSearchClick = useCallback(async () => {
|
||||
// console
|
||||
// }, []);
|
||||
//
|
||||
|
||||
async function selectSearchResult({ lat, lon }) {
|
||||
// clientChatStore.mapState.latitude = searchResult.lat;
|
||||
// clientChatStore.mapState.longitude = searchResult.lon;
|
||||
await clientChatStore.setMapView(lon, lat, 15);
|
||||
}
|
||||
|
||||
async function handleSc(e) {
|
||||
if (isSearchOpen && searchInput.length > 1) {
|
||||
try {
|
||||
console.log(`trying to geocode ${searchInput}`);
|
||||
const geocode = await fetch('https://geocode.geoffsee.com', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify({
|
||||
location: searchInput,
|
||||
}),
|
||||
});
|
||||
const coordinates = await geocode.json();
|
||||
const { lat, lon } = coordinates;
|
||||
console.log(`got geocode coordinates: ${coordinates}`);
|
||||
setSearchResults([{ lat, lon }]);
|
||||
} catch (e) {
|
||||
// continue without
|
||||
}
|
||||
} else {
|
||||
setIsSearchOpen(!isSearchOpen);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(selectedLayer);
|
||||
}, [selectedLayer]);
|
||||
|
||||
function handleLayerChange(e) {
|
||||
setSelectedLayer(layers.find(layer => layer.value === e.target.id));
|
||||
}
|
||||
|
||||
return (
|
||||
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
|
||||
<Box position={'absolute'} top={0} w="100%" h={'100vh'} overflow="hidden">
|
||||
{/* Button bar — absolutely positioned inside the wrapper */}
|
||||
|
||||
<MapNext mapboxPublicKey={atob(key)} visible={props.visible} />
|
||||
<HStack position="relative" zIndex={1}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Button size="sm" variant="solid" onClick={handleSc} mr={2}>
|
||||
Search
|
||||
</Button>
|
||||
{isSearchOpen && (
|
||||
<Box
|
||||
w="200px"
|
||||
transition="all 0.3s"
|
||||
transform={`translateX(${isSearchOpen ? '0' : '100%'})`}
|
||||
background="background.secondary"
|
||||
opacity={isSearchOpen ? 1 : 0}
|
||||
color="white"
|
||||
>
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
size="sm"
|
||||
value={searchInput}
|
||||
onChange={e => setSearchInput(e.target.value)}
|
||||
color="white"
|
||||
bg="background.secondary"
|
||||
border="none"
|
||||
borderRadius="0"
|
||||
_focus={{
|
||||
outline: 'none',
|
||||
}}
|
||||
_placeholder={{
|
||||
color: '#d1cfcf',
|
||||
}}
|
||||
/>
|
||||
{searchResults.length > 0 && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="100%"
|
||||
left={0}
|
||||
w="200px"
|
||||
bg="background.secondary"
|
||||
boxShadow="md"
|
||||
zIndex={2}
|
||||
>
|
||||
{searchResults.map((result, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
p={2}
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'whiteAlpha.200' }}
|
||||
onClick={async () => {
|
||||
// setSearchInput(result);
|
||||
console.log(`selecting result ${result.lat}, ${result.lon}`);
|
||||
await selectSearchResult(result);
|
||||
setSearchResults([]);
|
||||
setIsSearchOpen(false);
|
||||
}}
|
||||
>
|
||||
{`${result.lat}, ${result.lon}`}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<LayerSelector onClick={handleLayerChange} />
|
||||
</HStack>
|
||||
<MapNext mapboxPublicKey={atob(key)} visible={props.visible} layer={selectedLayer} />
|
||||
{/*<Map*/}
|
||||
{/* mapboxAccessToken={atob(key)}*/}
|
||||
{/* initialViewState={mapView}*/}
|
||||
|
@@ -13,10 +13,13 @@ import Map, {
|
||||
|
||||
import clientChatStore from '../../stores/ClientChatStore';
|
||||
|
||||
import type { Layer } from './Map.tsx';
|
||||
import PORTS from './nautical-base-data.json';
|
||||
import Pin from './pin';
|
||||
|
||||
function MapNextComponent(props: any = { mapboxPublicKey: '', visible: true } as any) {
|
||||
function MapNextComponent(
|
||||
props: any = { mapboxPublicKey: '', visible: true, layer: {} as Layer } as any,
|
||||
) {
|
||||
const [popupInfo, setPopupInfo] = useState(null);
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
const [isTokenLoading, setIsTokenLoading] = useState(false);
|
||||
@@ -122,10 +125,11 @@ Type '{ city: string; population: string; image: string; state: string; latitude
|
||||
bearing: clientChatStore.mapState.bearing,
|
||||
pitch: clientChatStore.mapState.pitch,
|
||||
}}
|
||||
viewState={clientChatStore.mapState}
|
||||
onMove={handleMapViewChange}
|
||||
terrain={{ source: 'mapbox-dem', exaggeration: 1.5 }}
|
||||
maxPitch={85}
|
||||
mapStyle="mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y"
|
||||
mapStyle={props.layer.value}
|
||||
attributionControl={false}
|
||||
mapboxAccessToken={props.mapboxPublicKey}
|
||||
style={{
|
||||
|
Reference in New Issue
Block a user