mirror of
https://github.com/seemueller-io/yachtpit.git
synced 2025-09-08 22:46:45 +00:00
Integrate browser geolocation API (#9)
* 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`. * give map a custom style * shift towards rust exclusivity * `build.rs` streamlines map build. Added an axum server with the map assets embedded. * update readmes * base-map api retrieves geolocation from the navigator of the browser * make map standalone wry that pulls assets from the server to simulate behavior in bevy * wip wasm * wasm build fixed * fix path ref to assets --------- Co-authored-by: geoffsee <>
This commit is contained in:
69
crates/base-map/map/README.md
Normal file
69
crates/base-map/map/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default tseslint.config([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
23
crates/base-map/map/eslint.config.js
Normal file
23
crates/base-map/map/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
|
||||
export default tseslint.config([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
13
crates/base-map/map/index.html
Normal file
13
crates/base-map/map/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
5585
crates/base-map/map/package-lock.json
generated
Normal file
5585
crates/base-map/map/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
crates/base-map/map/package.json
Normal file
42
crates/base-map/map/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "base-map",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint ",
|
||||
"preview": "vite preview",
|
||||
"background-server": "(cd ../ && cargo run &)"
|
||||
},
|
||||
"dependencies": {
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/react": "^3.21.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.2.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"mapbox-gl": "^3.13.0",
|
||||
"react-map-gl": "^8.0.4",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite": "^7.0.0",
|
||||
"@tauri-apps/plugin-geolocation": "^2.3.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"bevy_flurx_api": "^0.1.0",
|
||||
"geojson": "^0.5.0"
|
||||
}
|
||||
}
|
1
crates/base-map/map/public/vite.svg
Normal file
1
crates/base-map/map/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
238
crates/base-map/map/src/App.tsx
Normal file
238
crates/base-map/map/src/App.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
// 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, 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;
|
||||
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() {
|
||||
|
||||
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
|
||||
// });
|
||||
|
||||
// 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 () => {
|
||||
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 () => {
|
||||
setIsSearchOpen(!isSearchOpen);
|
||||
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);
|
||||
// setVesselPosition(vesselStatus);
|
||||
} catch (error) {
|
||||
console.error('Failed to get vessel status:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Poll every 5 seconds
|
||||
const interval = setInterval(pollVesselStatus, 5000);
|
||||
// Also poll immediately
|
||||
pollVesselStatus();
|
||||
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}>
|
||||
<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}
|
||||
>
|
||||
Layer
|
||||
</Button>
|
||||
</HStack>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
115
crates/base-map/map/src/MapNext.tsx
Normal file
115
crates/base-map/map/src/MapNext.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import {useState, useMemo} from 'react';
|
||||
import Map, {
|
||||
Marker,
|
||||
Popup,
|
||||
NavigationControl,
|
||||
FullscreenControl,
|
||||
ScaleControl,
|
||||
GeolocateControl
|
||||
} from 'react-map-gl/mapbox';
|
||||
|
||||
import ControlPanel from './control-panel.tsx';
|
||||
import Pin from './pin.tsx';
|
||||
|
||||
import PORTS from './test_data/nautical-base-data.json';
|
||||
import {Box} from "@chakra-ui/react";
|
||||
|
||||
|
||||
export default function MapNext(props: any = {mapboxPublicKey: ""} as any) {
|
||||
const [popupInfo, setPopupInfo] = useState(null);
|
||||
|
||||
const pins = useMemo(
|
||||
() =>
|
||||
PORTS.map((city, index) => (
|
||||
<Marker
|
||||
key={`marker-${index}`}
|
||||
longitude={city.longitude}
|
||||
latitude={city.latitude}
|
||||
anchor="bottom"
|
||||
onClick={e => {
|
||||
// If we let the click event propagates to the map, it will immediately close the popup
|
||||
// with `closeOnClick: true`
|
||||
e.originalEvent.stopPropagation();
|
||||
/*
|
||||
src/MapNext.tsx:34:38 - error TS2345: Argument of type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' is not assignable to parameter of type 'SetStateAction<null>'.
|
||||
Type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' provides no match for the signature '(prevState: null): null'.
|
||||
*/
|
||||
// @ts-ignore
|
||||
setPopupInfo(city);
|
||||
}}
|
||||
>
|
||||
<Pin />
|
||||
</Marker>
|
||||
)),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Map
|
||||
initialViewState={{
|
||||
latitude: 40,
|
||||
longitude: -100,
|
||||
zoom: 3.5,
|
||||
bearing: 0,
|
||||
pitch: 0
|
||||
}}
|
||||
|
||||
mapStyle="mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y"
|
||||
mapboxAccessToken={props.mapboxPublicKey}
|
||||
style={{position: "fixed", width: '100%', height: '100%', bottom: 0, top: 0, left: 0, right: 0}}
|
||||
>
|
||||
<GeolocateControl showUserHeading={true} showUserLocation={true} position="top-left" />
|
||||
<FullscreenControl position="top-left" />
|
||||
<NavigationControl position="top-left" />
|
||||
<ScaleControl />
|
||||
|
||||
{pins}
|
||||
|
||||
{popupInfo && (
|
||||
<Popup
|
||||
anchor="top"
|
||||
/*
|
||||
src/MapNext.tsx:66:53 - error TS2339: Property 'longitude' does not exist on type 'never'.
|
||||
|
||||
66 longitude={Number(popupInfo.longitude)}
|
||||
*/
|
||||
// @ts-ignore
|
||||
longitude={Number(popupInfo.longitude)}
|
||||
/*
|
||||
src/MapNext.tsx:67:52 - error TS2339: Property 'latitude' does not exist on type 'never'.
|
||||
|
||||
67 latitude={Number(popupInfo.latitude)}
|
||||
~~~~~~~~
|
||||
*/
|
||||
// @ts-ignore
|
||||
latitude={Number(popupInfo.latitude)}
|
||||
onClose={() => setPopupInfo(null)}
|
||||
>
|
||||
<div>
|
||||
{/*src/MapNext.tsx:71:40 - error TS2339: Property 'city' does not exist on type 'never'.
|
||||
|
||||
71 {popupInfo.city}, {popupInfo.state} |{' '}
|
||||
~~~~*/}
|
||||
|
||||
{/*@ts-ignore*/}{/*@ts-ignore*/}
|
||||
{popupInfo.city},{popupInfo.state}
|
||||
{/*@ts-ignore*/}
|
||||
<a
|
||||
target="_new"
|
||||
|
||||
href={`http://en.wikipedia.org/w/index.php?title=Special:Search&search=${(popupInfo as any).city}, ${(popupInfo as any).state}`}
|
||||
>
|
||||
Wikipedia
|
||||
</a>
|
||||
</div>
|
||||
{/*@ts-ignore*/}
|
||||
<img width="100%" src={popupInfo.image} />
|
||||
</Popup>
|
||||
)}
|
||||
</Map>
|
||||
|
||||
<ControlPanel />
|
||||
</Box>
|
||||
);
|
||||
}
|
1
crates/base-map/map/src/assets/react.svg
Normal file
1
crates/base-map/map/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
After Width: | Height: | Size: 4.0 KiB |
108
crates/base-map/map/src/components/ui/color-mode.tsx
Normal file
108
crates/base-map/map/src/components/ui/color-mode.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
"use client"
|
||||
|
||||
import type { IconButtonProps, SpanProps } from "@chakra-ui/react"
|
||||
import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react"
|
||||
import { ThemeProvider, useTheme } from "next-themes"
|
||||
import type { ThemeProviderProps } from "next-themes"
|
||||
import * as React from "react"
|
||||
import { LuMoon, LuSun } from "react-icons/lu"
|
||||
|
||||
export interface ColorModeProviderProps extends ThemeProviderProps {}
|
||||
|
||||
export function ColorModeProvider(props: ColorModeProviderProps) {
|
||||
return (
|
||||
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export type ColorMode = "light" | "dark"
|
||||
|
||||
export interface UseColorModeReturn {
|
||||
colorMode: ColorMode
|
||||
setColorMode: (colorMode: ColorMode) => void
|
||||
toggleColorMode: () => void
|
||||
}
|
||||
|
||||
export function useColorMode(): UseColorModeReturn {
|
||||
const { resolvedTheme, setTheme, forcedTheme } = useTheme()
|
||||
const colorMode = forcedTheme || resolvedTheme
|
||||
const toggleColorMode = () => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
}
|
||||
return {
|
||||
colorMode: colorMode as ColorMode,
|
||||
setColorMode: setTheme,
|
||||
toggleColorMode,
|
||||
}
|
||||
}
|
||||
|
||||
export function useColorModeValue<T>(light: T, dark: T) {
|
||||
const { colorMode } = useColorMode()
|
||||
return colorMode === "dark" ? dark : light
|
||||
}
|
||||
|
||||
export function ColorModeIcon() {
|
||||
const { colorMode } = useColorMode()
|
||||
return colorMode === "dark" ? <LuMoon /> : <LuSun />
|
||||
}
|
||||
|
||||
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
|
||||
|
||||
export const ColorModeButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ColorModeButtonProps
|
||||
>(function ColorModeButton(props, ref) {
|
||||
const { toggleColorMode } = useColorMode()
|
||||
return (
|
||||
<ClientOnly fallback={<Skeleton boxSize="8" />}>
|
||||
<IconButton
|
||||
onClick={toggleColorMode}
|
||||
variant="ghost"
|
||||
aria-label="Toggle color mode"
|
||||
size="sm"
|
||||
ref={ref}
|
||||
{...props}
|
||||
css={{
|
||||
_icon: {
|
||||
width: "5",
|
||||
height: "5",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ColorModeIcon />
|
||||
</IconButton>
|
||||
</ClientOnly>
|
||||
)
|
||||
})
|
||||
|
||||
export const LightMode = React.forwardRef<HTMLSpanElement, SpanProps>(
|
||||
function LightMode(props, ref) {
|
||||
return (
|
||||
<Span
|
||||
color="fg"
|
||||
display="contents"
|
||||
className="chakra-theme light"
|
||||
colorPalette="gray"
|
||||
colorScheme="light"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export const DarkMode = React.forwardRef<HTMLSpanElement, SpanProps>(
|
||||
function DarkMode(props, ref) {
|
||||
return (
|
||||
<Span
|
||||
color="fg"
|
||||
display="contents"
|
||||
className="chakra-theme dark"
|
||||
colorPalette="gray"
|
||||
colorScheme="dark"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
15
crates/base-map/map/src/components/ui/provider.tsx
Normal file
15
crates/base-map/map/src/components/ui/provider.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
|
||||
import {
|
||||
ColorModeProvider,
|
||||
type ColorModeProviderProps,
|
||||
} from "./color-mode.tsx"
|
||||
|
||||
export function Provider(props: ColorModeProviderProps) {
|
||||
return (
|
||||
<ChakraProvider value={defaultSystem}>
|
||||
<ColorModeProvider {...props} />
|
||||
</ChakraProvider>
|
||||
)
|
||||
}
|
43
crates/base-map/map/src/components/ui/toaster.tsx
Normal file
43
crates/base-map/map/src/components/ui/toaster.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Toaster as ChakraToaster,
|
||||
Portal,
|
||||
Spinner,
|
||||
Stack,
|
||||
Toast,
|
||||
createToaster,
|
||||
} from "@chakra-ui/react"
|
||||
|
||||
export const toaster = createToaster({
|
||||
placement: "bottom-end",
|
||||
pauseOnPageIdle: true,
|
||||
})
|
||||
|
||||
export const Toaster = () => {
|
||||
return (
|
||||
<Portal>
|
||||
<ChakraToaster toaster={toaster} insetInline={{ mdDown: "4" }}>
|
||||
{(toast) => (
|
||||
<Toast.Root width={{ md: "sm" }}>
|
||||
{toast.type === "loading" ? (
|
||||
<Spinner size="sm" color="blue.solid" />
|
||||
) : (
|
||||
<Toast.Indicator />
|
||||
)}
|
||||
<Stack gap="1" flex="1" maxWidth="100%">
|
||||
{toast.title && <Toast.Title>{toast.title}</Toast.Title>}
|
||||
{toast.description && (
|
||||
<Toast.Description>{toast.description}</Toast.Description>
|
||||
)}
|
||||
</Stack>
|
||||
{toast.action && (
|
||||
<Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
|
||||
)}
|
||||
{toast.closable && <Toast.CloseTrigger />}
|
||||
</Toast.Root>
|
||||
)}
|
||||
</ChakraToaster>
|
||||
</Portal>
|
||||
)
|
||||
}
|
46
crates/base-map/map/src/components/ui/tooltip.tsx
Normal file
46
crates/base-map/map/src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
|
||||
import * as React from "react"
|
||||
|
||||
export interface TooltipProps extends ChakraTooltip.RootProps {
|
||||
showArrow?: boolean
|
||||
portalled?: boolean
|
||||
portalRef?: React.RefObject<HTMLElement>
|
||||
content: React.ReactNode
|
||||
contentProps?: ChakraTooltip.ContentProps
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
||||
function Tooltip(props, ref) {
|
||||
const {
|
||||
showArrow,
|
||||
children,
|
||||
disabled,
|
||||
portalled = true,
|
||||
content,
|
||||
contentProps,
|
||||
portalRef,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
if (disabled) return children
|
||||
|
||||
return (
|
||||
<ChakraTooltip.Root {...rest}>
|
||||
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
|
||||
<Portal disabled={!portalled} container={portalRef}>
|
||||
<ChakraTooltip.Positioner>
|
||||
<ChakraTooltip.Content ref={ref} {...contentProps}>
|
||||
{showArrow && (
|
||||
<ChakraTooltip.Arrow>
|
||||
<ChakraTooltip.ArrowTip />
|
||||
</ChakraTooltip.Arrow>
|
||||
)}
|
||||
{content}
|
||||
</ChakraTooltip.Content>
|
||||
</ChakraTooltip.Positioner>
|
||||
</Portal>
|
||||
</ChakraTooltip.Root>
|
||||
)
|
||||
},
|
||||
)
|
29
crates/base-map/map/src/control-panel.tsx
Normal file
29
crates/base-map/map/src/control-panel.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Marker, Popup, NavigationControl and FullscreenControl </h3>
|
||||
<p>
|
||||
Map showing top 20 most populated cities of the United States. Click on a marker to learn
|
||||
more.
|
||||
</p>
|
||||
<p>
|
||||
Data source:{' '}
|
||||
<a href="https://en.wikipedia.org/wiki/List_of_United_States_cities_by_population">
|
||||
Wikipedia
|
||||
</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/8.0-release/examples/mapbox/controls"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
12
crates/base-map/map/src/main.tsx
Normal file
12
crates/base-map/map/src/main.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import {StrictMode} from 'react'
|
||||
import {createRoot} from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import {Provider} from "@/components/ui/provider.tsx"
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<Provider>
|
||||
<App/>
|
||||
</Provider>
|
||||
</StrictMode>,
|
||||
)
|
21
crates/base-map/map/src/pin.tsx
Normal file
21
crates/base-map/map/src/pin.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3
|
||||
c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9
|
||||
C20.1,15.8,20.2,15.8,20.2,15.7z`;
|
||||
|
||||
const pinStyle = {
|
||||
cursor: 'pointer',
|
||||
fill: '#d00',
|
||||
stroke: 'none'
|
||||
};
|
||||
|
||||
function Pin({size = 20}) {
|
||||
return (
|
||||
<svg height={size} viewBox="0 0 24 24" style={pinStyle}>
|
||||
<path d={ICON} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Pin);
|
22
crates/base-map/map/src/test_data/cities.json
Normal file
22
crates/base-map/map/src/test_data/cities.json
Normal file
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{"city":"New York","population":"8,175,133","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Above_Gotham.jpg/240px-Above_Gotham.jpg","state":"New York","latitude":40.6643,"longitude":-73.9385},
|
||||
{"city":"Los Angeles","population":"3,792,621","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/5/57/LA_Skyline_Mountains2.jpg/240px-LA_Skyline_Mountains2.jpg","state":"California","latitude":34.0194,"longitude":-118.4108},
|
||||
{"city":"Chicago","population":"2,695,598","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/8/85/2008-06-10_3000x1000_chicago_skyline.jpg/240px-2008-06-10_3000x1000_chicago_skyline.jpg","state":"Illinois","latitude":41.8376,"longitude":-87.6818},
|
||||
{"city":"Houston","population":"2,100,263","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg/240px-Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg","state":"Texas","latitude":29.7805,"longitude":-95.3863},
|
||||
{"city":"Phoenix","population":"1,445,632","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Downtown_Phoenix_Aerial_Looking_Northeast.jpg/207px-Downtown_Phoenix_Aerial_Looking_Northeast.jpg","state":"Arizona","latitude":33.5722,"longitude":-112.0880},
|
||||
{"city":"Philadelphia","population":"1,526,006","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Philly_skyline.jpg/240px-Philly_skyline.jpg","state":"Pennsylvania","latitude":40.0094,"longitude":-75.1333},
|
||||
{"city":"San Antonio","population":"1,327,407","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Downtown_San_Antonio_View.JPG/240px-Downtown_San_Antonio_View.JPG","state":"Texas","latitude":29.4724,"longitude":-98.5251},
|
||||
{"city":"San Diego","population":"1,307,402","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/5/53/US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg/240px-US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg","state":"California","latitude":32.8153,"longitude":-117.1350},
|
||||
{"city":"Dallas","population":"1,197,816","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Dallas_skyline_daytime.jpg/240px-Dallas_skyline_daytime.jpg","state":"Texas","latitude":32.7757,"longitude":-96.7967},
|
||||
{"city":"San Jose","population":"945,942","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Downtown_San_Jose_skyline.PNG/240px-Downtown_San_Jose_skyline.PNG","state":"California","latitude":37.2969,"longitude":-121.8193},
|
||||
{"city":"Austin","population":"790,390","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Austin2012-12-01.JPG/240px-Austin2012-12-01.JPG","state":"Texas","latitude":30.3072,"longitude":-97.7560},
|
||||
{"city":"Jacksonville","population":"821,784","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg/240px-Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg","state":"Florida","latitude":30.3370,"longitude":-81.6613},
|
||||
{"city":"San Francisco","population":"805,235","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/San_Francisco_skyline_from_Coit_Tower.jpg/240px-San_Francisco_skyline_from_Coit_Tower.jpg","state":"California","latitude":37.7751,"longitude":-122.4193},
|
||||
{"city":"Columbus","population":"787,033","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Columbus-ohio-skyline-panorama.jpg/240px-Columbus-ohio-skyline-panorama.jpg","state":"Ohio","latitude":39.9848,"longitude":-82.9850},
|
||||
{"city":"Indianapolis","population":"820,445","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Downtown_indy_from_parking_garage_zoom.JPG/213px-Downtown_indy_from_parking_garage_zoom.JPG","state":"Indiana","latitude":39.7767,"longitude":-86.1459},
|
||||
{"city":"Fort Worth","population":"741,206","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/d/db/FortWorthTexasSkylineW.jpg/240px-FortWorthTexasSkylineW.jpg","state":"Texas","latitude":32.7795,"longitude":-97.3463},
|
||||
{"city":"Charlotte","population":"731,424","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/7/7d/Charlotte_skyline45647.jpg/222px-Charlotte_skyline45647.jpg","state":"North Carolina","latitude":35.2087,"longitude":-80.8307},
|
||||
{"city":"Seattle","population":"608,660","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/3/36/SeattleI5Skyline.jpg/240px-SeattleI5Skyline.jpg","state":"Washington","latitude":47.6205,"longitude":-122.3509},
|
||||
{"city":"Denver","population":"600,158","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/DenverCP.JPG/240px-DenverCP.JPG","state":"Colorado","latitude":39.7618,"longitude":-104.8806},
|
||||
{"city":"El Paso","population":"649,121","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Downtown_El_Paso_at_sunset.jpeg/240px-Downtown_El_Paso_at_sunset.jpeg","state":"Texas","latitude":31.8484,"longitude":-106.4270}
|
||||
]
|
22
crates/base-map/map/src/test_data/nautical-base-data.json
Normal file
22
crates/base-map/map/src/test_data/nautical-base-data.json
Normal file
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{"city":"New York","population":"8,335,897","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Above_Gotham.jpg/240px-Above_Gotham.jpg","state":"New York","latitude":40.7128,"longitude":-74.0060},
|
||||
{"city":"Los Angeles","population":"3,822,238","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/LA_Skyline_Mountains2.jpg/240px-LA_Skyline_Mountains2.jpg","state":"California","latitude":34.0522,"longitude":-118.2437},
|
||||
{"city":"Long Beach","population":"456,062","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Long_Beach_skyline_from_Shoreline_Village.jpg/240px-Long_Beach_skyline_from_Shoreline_Village.jpg","state":"California","latitude":33.7701,"longitude":-118.1937},
|
||||
{"city":"Seattle","population":"749,256","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/SeattleI5Skyline.jpg/240px-SeattleI5Skyline.jpg","state":"Washington","latitude":47.6062,"longitude":-122.3321},
|
||||
{"city":"San Francisco","population":"808,437","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/San_Francisco_skyline_from_Coit_Tower.jpg/240px-San_Francisco_skyline_from_Coit_Tower.jpg","state":"California","latitude":37.7749,"longitude":-122.4194},
|
||||
{"city":"San Diego","population":"1,386,932","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg/240px-US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg","state":"California","latitude":32.7157,"longitude":-117.1611},
|
||||
{"city":"Norfolk","population":"235,089","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6e/Norfolk_Skyline_from_Portsmouth.jpg/240px-Norfolk_Skyline_from_Portsmouth.jpg","state":"Virginia","latitude":36.8508,"longitude":-76.2859},
|
||||
{"city":"Miami","population":"449,514","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Miami_skyline_201807_cat.jpg/240px-Miami_skyline_201807_cat.jpg","state":"Florida","latitude":25.7617,"longitude":-80.1918},
|
||||
{"city":"Boston","population":"675,647","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Boston_skyline_and_Boston_Harbor.jpg/240px-Boston_skyline_and_Boston_Harbor.jpg","state":"Massachusetts","latitude":42.3601,"longitude":-71.0589},
|
||||
{"city":"Baltimore","population":"585,708","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Baltimore_Skyline.jpg/240px-Baltimore_Skyline.jpg","state":"Maryland","latitude":39.2904,"longitude":-76.6122},
|
||||
{"city":"Charleston","population":"151,612","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Charleston_SC_Skyline.jpg/240px-Charleston_SC_Skyline.jpg","state":"South Carolina","latitude":32.7765,"longitude":-79.9311},
|
||||
{"city":"Savannah","population":"147,780","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Savannah_GA%2C_River_Street.jpg/240px-Savannah_GA%2C_River_Street.jpg","state":"Georgia","latitude":32.0809,"longitude":-81.0912},
|
||||
{"city":"Tampa","population":"403,364","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Tampa_skyline_from_South%2C_2022.jpg/240px-Tampa_skyline_from_South%2C_2022.jpg","state":"Florida","latitude":27.9506,"longitude":-82.4572},
|
||||
{"city":"Mobile","population":"187,041","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Mobile_skyline_from_Mobile_River.jpg/240px-Mobile_skyline_from_Mobile_River.jpg","state":"Alabama","latitude":30.6954,"longitude":-88.0399},
|
||||
{"city":"Anchorage","population":"288,121","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Anchorage_skyline_and_susitna.jpg/240px-Anchorage_skyline_and_susitna.jpg","state":"Alaska","latitude":61.2181,"longitude":-149.9003},
|
||||
{"city":"Portland","population":"68,408","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Portland_Maine_skyline.jpg/240px-Portland_Maine_skyline.jpg","state":"Maine","latitude":43.6591,"longitude":-70.2568},
|
||||
{"city":"Honolulu","population":"349,547","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Honolulu_and_Diamond_Head.jpg/240px-Honolulu_and_Diamond_Head.jpg","state":"Hawaii","latitude":21.3069,"longitude":-157.8583},
|
||||
{"city":"New Orleans","population":"376,971","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/New_Orleans_skyline_sunset_Dec_28_2021_PANO_DSC07177-07179.jpg/240px-New_Orleans_skyline_sunset_Dec_28_2021_PANO_DSC07177-07179.jpg","state":"Louisiana","latitude":29.9511,"longitude":-90.0715},
|
||||
{"city":"Jacksonville","population":"971,319","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg/240px-Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg","state":"Florida","latitude":30.3322,"longitude":-81.6557},
|
||||
{"city":"Houston","population":"2,302,878","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg/240px-Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg","state":"Texas","latitude":29.7604,"longitude":-95.3698}
|
||||
]
|
1
crates/base-map/map/src/vite-env.d.ts
vendored
Normal file
1
crates/base-map/map/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
31
crates/base-map/map/tsconfig.app.json
Normal file
31
crates/base-map/map/tsconfig.app.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
7
crates/base-map/map/tsconfig.json
Normal file
7
crates/base-map/map/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json"},
|
||||
{ "path": "./tsconfig.node.json"}
|
||||
],
|
||||
}
|
27
crates/base-map/map/tsconfig.node.json
Normal file
27
crates/base-map/map/tsconfig.node.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
8
crates/base-map/map/vite.config.ts
Normal file
8
crates/base-map/map/vite.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import tsconfigPaths from "vite-tsconfig-paths"
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tsconfigPaths()],
|
||||
})
|
Reference in New Issue
Block a user