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 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
import { Box, HStack, Button, Input, Center } from '@chakra-ui/react';
|
import { Box, Button, HStack, Input } from '@chakra-ui/react';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import clientChatStore from '../../stores/ClientChatStore.ts';
|
||||||
|
|
||||||
import MapNext from './MapNext.tsx';
|
import MapNext from './MapNext.tsx';
|
||||||
|
|
||||||
@@ -30,17 +31,175 @@ interface AuthParams {
|
|||||||
token: string | null;
|
token: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Layer = { name: string; value: string };
|
||||||
|
export type Layers = Layer[];
|
||||||
|
|
||||||
// public key
|
// public key
|
||||||
const key =
|
const key =
|
||||||
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
|
'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 }) {
|
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 (
|
return (
|
||||||
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
|
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
|
||||||
<Box position={'absolute'} top={0} w="100%" h={'100vh'} overflow="hidden">
|
<Box position={'absolute'} top={0} w="100%" h={'100vh'} overflow="hidden">
|
||||||
{/* Button bar — absolutely positioned inside the wrapper */}
|
{/* 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*/}
|
{/*<Map*/}
|
||||||
{/* mapboxAccessToken={atob(key)}*/}
|
{/* mapboxAccessToken={atob(key)}*/}
|
||||||
{/* initialViewState={mapView}*/}
|
{/* initialViewState={mapView}*/}
|
||||||
|
@@ -13,10 +13,13 @@ import Map, {
|
|||||||
|
|
||||||
import clientChatStore from '../../stores/ClientChatStore';
|
import clientChatStore from '../../stores/ClientChatStore';
|
||||||
|
|
||||||
|
import type { Layer } from './Map.tsx';
|
||||||
import PORTS from './nautical-base-data.json';
|
import PORTS from './nautical-base-data.json';
|
||||||
import Pin from './pin';
|
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 [popupInfo, setPopupInfo] = useState(null);
|
||||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||||
const [isTokenLoading, setIsTokenLoading] = 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,
|
bearing: clientChatStore.mapState.bearing,
|
||||||
pitch: clientChatStore.mapState.pitch,
|
pitch: clientChatStore.mapState.pitch,
|
||||||
}}
|
}}
|
||||||
|
viewState={clientChatStore.mapState}
|
||||||
onMove={handleMapViewChange}
|
onMove={handleMapViewChange}
|
||||||
terrain={{ source: 'mapbox-dem', exaggeration: 1.5 }}
|
terrain={{ source: 'mapbox-dem', exaggeration: 1.5 }}
|
||||||
maxPitch={85}
|
maxPitch={85}
|
||||||
mapStyle="mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y"
|
mapStyle={props.layer.value}
|
||||||
attributionControl={false}
|
attributionControl={false}
|
||||||
mapboxAccessToken={props.mapboxPublicKey}
|
mapboxAccessToken={props.mapboxPublicKey}
|
||||||
style={{
|
style={{
|
||||||
|
Reference in New Issue
Block a user