add simple map view

This commit is contained in:
hzrd149 2023-08-02 15:46:36 -05:00
parent f8b3b9a3c3
commit ff12567a90
7 changed files with 189 additions and 0 deletions

View File

@ -23,7 +23,10 @@
"hls.js": "^1.4.7",
"idb": "^7.1.1",
"identicon.js": "^2.3.3",
"leaflet": "^1.9.4",
"leaflet.locatecontrol": "^0.79.0",
"light-bolt11-decoder": "^3.0.0",
"ngeohash": "^0.6.3",
"noble-secp256k1": "^1.2.14",
"nostr-tools": "^1.12.1",
"react": "^18.2.0",
@ -41,6 +44,9 @@
"@testing-library/cypress": "^9.0.0",
"@types/debug": "^4.1.8",
"@types/identicon.js": "^2.3.1",
"@types/leaflet": "^1.9.3",
"@types/leaflet.locatecontrol": "^0.74.1",
"@types/ngeohash": "^0.6.4",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^4.0.1",

View File

@ -39,6 +39,7 @@ import { PageProviders } from "./providers";
const StreamsView = React.lazy(() => import("./views/streams"));
const StreamView = React.lazy(() => import("./views/streams/stream"));
const SearchView = React.lazy(() => import("./views/search"));
const MapView = React.lazy(() => import("./views/map"));
const RootPage = () => {
useSetColorMode();
@ -74,6 +75,10 @@ const router = createHashRouter([
</PageProviders>
),
},
{
path: "map",
element: <MapView />,
},
{
path: "/",
element: <RootPage />,

View File

@ -277,3 +277,9 @@ export const TextTimelineIcon = createIcon({
d: "M8 4H21V6H8V4ZM4.5 6.5C3.67157 6.5 3 5.82843 3 5C3 4.17157 3.67157 3.5 4.5 3.5C5.32843 3.5 6 4.17157 6 5C6 5.82843 5.32843 6.5 4.5 6.5ZM4.5 13.5C3.67157 13.5 3 12.8284 3 12C3 11.1716 3.67157 10.5 4.5 10.5C5.32843 10.5 6 11.1716 6 12C6 12.8284 5.32843 13.5 4.5 13.5ZM4.5 20.4C3.67157 20.4 3 19.7284 3 18.9C3 18.0716 3.67157 17.4 4.5 17.4C5.32843 17.4 6 18.0716 6 18.9C6 19.7284 5.32843 20.4 4.5 20.4ZM8 11H21V13H8V11ZM8 18H21V20H8V18Z",
defaultProps,
});
export const MapIcon = createIcon({
displayName: "MapIcon",
d: "M4 6.14286V18.9669L9.06476 16.7963L15.0648 19.7963L20 17.6812V4.85714L21.303 4.2987C21.5569 4.18992 21.8508 4.30749 21.9596 4.56131C21.9862 4.62355 22 4.69056 22 4.75827V19L15 22L9 19L2.69696 21.7013C2.44314 21.8101 2.14921 21.6925 2.04043 21.4387C2.01375 21.3765 2 21.3094 2 21.2417V7L4 6.14286ZM16.2426 11.2426L12 15.4853L7.75736 11.2426C5.41421 8.89949 5.41421 5.10051 7.75736 2.75736C10.1005 0.414214 13.8995 0.414214 16.2426 2.75736C18.5858 5.10051 18.5858 8.89949 16.2426 11.2426ZM12 12.6569L14.8284 9.82843C16.3905 8.26633 16.3905 5.73367 14.8284 4.17157C13.2663 2.60948 10.7337 2.60948 9.17157 4.17157C7.60948 5.73367 7.60948 8.26633 9.17157 9.82843L12 12.6569Z",
defaultProps,
});

View File

@ -10,6 +10,7 @@ import {
FeedIcon,
LiveStreamIcon,
LogoutIcon,
MapIcon,
NotificationIcon,
ProfileIcon,
RelayIcon,
@ -49,6 +50,9 @@ export default function DesktopSideNav(props: Omit<FlexProps, "children">) {
<Button onClick={() => navigate("/streams")} leftIcon={<LiveStreamIcon />}>
Streams
</Button>
<Button onClick={() => navigate("/map")} leftIcon={<MapIcon />}>
Map
</Button>
<Button onClick={() => navigate("/profile")} leftIcon={<ProfileIcon />}>
Profile
</Button>

View File

@ -15,6 +15,7 @@ export type NostrQuery = {
"#p"?: string[];
"#d"?: string[];
"#t"?: string[];
"#g"?: string[];
since?: number;
until?: number;
limit?: number;

128
src/views/map/index.tsx Normal file
View File

@ -0,0 +1,128 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import { Box, Button, Flex } from "@chakra-ui/react";
import { Kind } from "nostr-tools";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import "leaflet.locatecontrol/dist/L.Control.Locate.min.css";
import "leaflet.locatecontrol";
import ngeohash from "ngeohash";
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import GenericNoteTimeline from "../../components/timeline-page/generic-note-timeline";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import useSubject from "../../hooks/use-subject";
import { NostrEvent } from "../../types/nostr-event";
function getPrecision(zoom: number) {
if (zoom <= 4) return 1;
if (zoom <= 8) return 2;
if (zoom <= 10) return 3;
if (zoom <= 12) return 4;
if (zoom <= 14) return 5;
if (zoom <= 16) return 6;
if (zoom <= 18) return 7;
return 7;
}
function getEventGeohash(event: NostrEvent) {
let hash = "";
for (const tag of event.tags) {
if (tag[0] === "g" && tag[1] && tag[1].length > hash.length) {
hash = tag[1];
}
}
return hash || null;
}
export default function MapView() {
const ref = useRef<HTMLDivElement | null>(null);
const [map, setMap] = useState<L.Map>();
useEffect(() => {
if (!ref.current) return;
const map = L.map(ref.current).setView([39, -97], 4);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap",
}).addTo(map);
L.control.locate().addTo(map);
setMap(map);
return () => {
map.remove();
setMap(undefined);
};
}, []);
const [cells, setCells] = useState<string[]>([]);
const readRelays = useReadRelayUrls();
const timeline = useTimelineLoader(
"geo-events",
readRelays,
{ "#g": cells, kinds: [Kind.Text] },
{ enabled: cells.length > 0 }
);
const setCellsFromMap = useCallback(() => {
if (!map) return;
const bbox = map.getBounds();
const hashes = ngeohash.bboxes(
bbox.getSouth(),
bbox.getWest(),
bbox.getNorth(),
bbox.getEast(),
getPrecision(map.getZoom())
);
setCells(hashes);
}, [map]);
const events = useSubject(timeline.timeline);
useEffect(() => {
if (!map) return;
const markers: L.Marker[] = [];
for (const event of events) {
const geohash = getEventGeohash(event);
if (!geohash) continue;
const latLng = ngeohash.decode(geohash);
const marker = L.marker([latLng.latitude, latLng.longitude]).addTo(map);
markers.push(marker);
}
return () => {
for (const marker of markers) {
marker.remove();
}
};
}, [map, events]);
return (
<Flex overflow={{ lg: "hidden" }} h={{ lg: "full" }} direction={{ base: "column-reverse", lg: "row" }}>
<Flex w={{ base: "full", lg: "xl" }} direction="column" p="2" gap="2">
<Flex gap="2">
<Button as={RouterLink} to="/" flexShrink={0}>
Back
</Button>
<Button colorScheme="brand" onClick={setCellsFromMap} flex={1}>
Search this area
</Button>
</Flex>
<Flex overflowY="auto" overflowX="hidden" gap="2" direction="column" h="full">
<GenericNoteTimeline timeline={timeline} />
{cells.length > 0 && <TimelineActionAndStatus timeline={timeline} />}
</Flex>
</Flex>
<Box w="full" ref={ref} h={{ base: "50vh", lg: "100vh" }} />
</Flex>
);
}

View File

@ -2545,6 +2545,11 @@
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.29.tgz#a48795ecadf957f6c0d10e0c34af86c098fa5bee"
integrity sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==
"@types/geojson@*":
version "7946.0.10"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249"
integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==
"@types/identicon.js@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@types/identicon.js/-/identicon.js-2.3.1.tgz#bbfe440a3229d6930c12d933ebcbe8603957ea75"
@ -2562,6 +2567,20 @@
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
"@types/leaflet.locatecontrol@^0.74.1":
version "0.74.1"
resolved "https://registry.yarnpkg.com/@types/leaflet.locatecontrol/-/leaflet.locatecontrol-0.74.1.tgz#1a1074cd720a68655ffdb70d8104539a71416615"
integrity sha512-4rqWfKadQ+Rksv4uq7Ab68CdOlaDt8z5bP+EMbXk5Ea82XMV9yLzQpAvVZ2xdARa8fpF2Atk+fheQ04Lik6ziA==
dependencies:
"@types/leaflet" "*"
"@types/leaflet@*", "@types/leaflet@^1.9.3":
version "1.9.3"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.9.3.tgz#7aac302189eb3aa283f444316167995df42a5467"
integrity sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==
dependencies:
"@types/geojson" "*"
"@types/lodash.mergewith@4.6.7":
version "4.6.7"
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz#eaa65aa5872abdd282f271eae447b115b2757212"
@ -2584,6 +2603,11 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/ngeohash@^0.6.4":
version "0.6.4"
resolved "https://registry.yarnpkg.com/@types/ngeohash/-/ngeohash-0.6.4.tgz#a1ba2c25c4d1ef71f067de247a61490019ea0757"
integrity sha512-rr20mmx41OkWx4q5du2dv2sESR/6xH2tzScUQXwO8SiaQWa6PYTuan1nqBtA76FR9qkVfZY7nwQwZNC9StX/Ww==
"@types/node@*":
version "20.4.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"
@ -4686,6 +4710,16 @@ lazy-ass@^1.6.0:
resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==
leaflet.locatecontrol@^0.79.0:
version "0.79.0"
resolved "https://registry.yarnpkg.com/leaflet.locatecontrol/-/leaflet.locatecontrol-0.79.0.tgz#0236b87c699a49f9ddb2f289941fbc0d3c3f8b62"
integrity sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==
leaflet@^1.9.4:
version "1.9.4"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d"
integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@ -4964,6 +4998,11 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
ngeohash@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/ngeohash/-/ngeohash-0.6.3.tgz#10b1e80be5488262ec95c56cf2dbb6c45fbdf245"
integrity sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==
noble-secp256k1@^1.2.14:
version "1.2.14"
resolved "https://registry.yarnpkg.com/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz#39429c941d51211ca40161569cee03e61d72599e"