From b5b07ad2c2fd893afacaee2ecbeebbc67ddbaa7b Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Wed, 10 Jul 2024 13:14:21 -0400
Subject: [PATCH 01/67] Start new navbar
---
src/app/new/HotspotsMap/Attribution.tsx | 21 +
src/app/new/HotspotsMap/HexHotspotItem.tsx | 68 ++
src/app/new/HotspotsMap/HexHotspots.tsx | 113 +++
.../new/HotspotsMap/LoadingHexHotspots.tsx | 39 +
.../new/HotspotsMap/NetworkCoverageLayer.tsx | 60 ++
src/app/new/HotspotsMap/index.tsx | 213 ++++++
src/app/new/HotspotsMap/mapLayersDark.tsx | 665 ++++++++++++++++++
src/app/new/HotspotsMap/mapLayersLight.tsx | 665 ++++++++++++++++++
src/app/new/HotspotsMap/utils.ts | 138 ++++
src/app/new/Nav.tsx | 89 +++
src/app/new/layout.tsx | 91 +++
src/app/new/page.tsx | 7 +
src/components/Header/index.tsx | 1 +
src/components/icons/DownCarret.tsx | 15 +
src/components/icons/EnergyIcon.tsx | 17 +
src/components/icons/HeliumIcon2.tsx | 19 +
src/components/icons/HeliumIotIcon.tsx | 5 +-
src/components/icons/IotIcon.tsx | 22 +
src/components/icons/MobileIcon.tsx | 25 +
19 files changed, 2271 insertions(+), 2 deletions(-)
create mode 100644 src/app/new/HotspotsMap/Attribution.tsx
create mode 100644 src/app/new/HotspotsMap/HexHotspotItem.tsx
create mode 100644 src/app/new/HotspotsMap/HexHotspots.tsx
create mode 100644 src/app/new/HotspotsMap/LoadingHexHotspots.tsx
create mode 100644 src/app/new/HotspotsMap/NetworkCoverageLayer.tsx
create mode 100644 src/app/new/HotspotsMap/index.tsx
create mode 100644 src/app/new/HotspotsMap/mapLayersDark.tsx
create mode 100644 src/app/new/HotspotsMap/mapLayersLight.tsx
create mode 100644 src/app/new/HotspotsMap/utils.ts
create mode 100644 src/app/new/Nav.tsx
create mode 100644 src/app/new/layout.tsx
create mode 100644 src/app/new/page.tsx
create mode 100644 src/components/icons/DownCarret.tsx
create mode 100644 src/components/icons/EnergyIcon.tsx
create mode 100644 src/components/icons/HeliumIcon2.tsx
create mode 100644 src/components/icons/IotIcon.tsx
create mode 100644 src/components/icons/MobileIcon.tsx
diff --git a/src/app/new/HotspotsMap/Attribution.tsx b/src/app/new/HotspotsMap/Attribution.tsx
new file mode 100644
index 0000000..6e52d03
--- /dev/null
+++ b/src/app/new/HotspotsMap/Attribution.tsx
@@ -0,0 +1,21 @@
+import clsx from "clsx"
+import Link from "next/link"
+import { HotspottyIcon } from "../icons/HotspottyIcon"
+
+export function Attribution({ className }: { className?: string }) {
+ return (
+
+
+
+
+ Hotspotty
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/HexHotspotItem.tsx b/src/app/new/HotspotsMap/HexHotspotItem.tsx
new file mode 100644
index 0000000..6d6542a
--- /dev/null
+++ b/src/app/new/HotspotsMap/HexHotspotItem.tsx
@@ -0,0 +1,68 @@
+"use client"
+
+import { usePreferences } from "@/context/usePreferences"
+import animalHash from "angry-purple-tiger"
+import Link from "next/link"
+import { gaEvent } from "../GATracker"
+import { HeliumIotIcon } from "../icons/HeliumIotIcon"
+import { HeliumMobileIcon } from "../icons/HeliumMobileIcon"
+import { Hotspot } from "./HexHotspots"
+
+type HexHotSpotItemProps = {
+ hotspot: Hotspot
+}
+
+export const HexHotSpotItem = ({ hotspot }: HexHotSpotItemProps) => {
+ const { provider } = usePreferences()
+
+ const hotspotName = animalHash(hotspot.hotspot_id)
+ const hasSmallCells = hotspot.cells.length > 0
+ const Avatar = hasSmallCells ? HeliumMobileIcon : HeliumIotIcon
+ const subtitle = hasSmallCells
+ ? `${hotspot.cells.length} small cell${
+ hotspot.cells.length === 1 ? "" : "s"
+ }`
+ : "IoT Hotspot"
+
+ return (
+
+
+
{
+ if (!!provider) {
+ gaEvent({
+ action: "outbound_click",
+ event: {
+ description: provider.label,
+ },
+ })
+ }
+ }}
+ >
+
+
+
+
+
+ {hotspotName}
+
+
+ {subtitle}
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/HexHotspots.tsx b/src/app/new/HotspotsMap/HexHotspots.tsx
new file mode 100644
index 0000000..317855c
--- /dev/null
+++ b/src/app/new/HotspotsMap/HexHotspots.tsx
@@ -0,0 +1,113 @@
+import { Tooltip } from "@/app/stats/components/Tooltip"
+import { PreferencesProvider } from "@/context/usePreferences"
+import clsx from "clsx"
+import { HexHotSpotItem } from "./HexHotspotItem"
+
+interface SmallCell {
+ cell_id: string
+}
+
+export interface Hotspot {
+ hotspot_id: string
+ active: boolean
+ cells: SmallCell[]
+}
+
+interface HexData {
+ hex: string
+ resolution: number
+ hotspots: Hotspot[]
+}
+
+const RECENT = "recently rewarded"
+const NOT_RECENT = "not recently rewarded"
+
+const TOOLTIP_DESCRIPTIONS = {
+ [RECENT]: "A hotspot that has received rewards in the past 30 days.",
+ [NOT_RECENT]:
+ "A hotspot that has not received rewards in the past 30 days. Such a hotstop is most likely offline. It is also possible for it to be online but not rewarded if it is not transmitting data, not participating in PoC, or only recently online.",
+}
+
+function getGroupedHotspots(hotspots: Hotspot[]) {
+ const groupedHotspots: {
+ [RECENT]: Hotspot[]
+ [NOT_RECENT]: Hotspot[]
+ } = {
+ [RECENT]: [],
+ [NOT_RECENT]: [],
+ }
+
+ hotspots.forEach((hotspot) => {
+ const group = hotspot.active ? RECENT : NOT_RECENT
+ groupedHotspots[group].push(hotspot)
+ })
+
+ return groupedHotspots
+}
+
+export async function HexHotspots({ hexId }: { hexId: string }) {
+ const { hotspots } = (await fetch(
+ `${process.env.NEXT_PUBLIC_HOTSPOTTY_EXPLORER_API_URL}/hex/${hexId}`,
+ {
+ headers: {
+ Authorization: `bearer ${process.env.NEXT_PUBLIC_HOTSPOTTY_EXPLORER_API_TOKEN}`,
+ },
+ }
+ ).then((res) => res.json())) as HexData
+
+ const groupedList = getGroupedHotspots(hotspots)
+
+ if (hotspots.length === 0) {
+ return (
+
+ This hex contains no Hotspots.
+
+ )
+ }
+
+ return (
+
+ {(Object.keys(groupedList) as Array
).map(
+ (group) => {
+ if (groupedList[group].length === 0) return
+ return (
+
+
+
+ {group}
+
+
+
+ {groupedList[group].length} Hotspots
+
+
+
+
+ {groupedList[group].map((hotspot) => (
+
+ ))}
+
+
+
+ )
+ }
+ )}
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/LoadingHexHotspots.tsx b/src/app/new/HotspotsMap/LoadingHexHotspots.tsx
new file mode 100644
index 0000000..348897f
--- /dev/null
+++ b/src/app/new/HotspotsMap/LoadingHexHotspots.tsx
@@ -0,0 +1,39 @@
+import clsx from "clsx"
+
+export function LoadingHexHotspots({ count }: { count: number }) {
+ return (
+
+
+ Active
+
+
+ {[...Array(count).keys()].map((i) => (
+ -
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/NetworkCoverageLayer.tsx b/src/app/new/HotspotsMap/NetworkCoverageLayer.tsx
new file mode 100644
index 0000000..9560ab9
--- /dev/null
+++ b/src/app/new/HotspotsMap/NetworkCoverageLayer.tsx
@@ -0,0 +1,60 @@
+import { useTheme } from "next-themes"
+import { Fragment } from "react"
+import { Layer, Source } from "react-map-gl"
+import {
+ MIN_HEXES_ZOOM,
+ MIN_HEX_LABELS_ZOOM,
+ NetworkCoverageLayerOption,
+ POINTS_AND_HEXES_OVERLAP,
+ getBlurredPointStyle,
+ getHexFillStyle,
+ getHexLabelStyle,
+ hexLabelLayout,
+} from "./utils"
+
+export function NetworkCoverageLayer({
+ layer: { color, sourceDomain, points, hexes },
+ ...props
+}: {
+ layer: NetworkCoverageLayerOption
+}) {
+ const { resolvedTheme } = useTheme()
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/index.tsx b/src/app/new/HotspotsMap/index.tsx
new file mode 100644
index 0000000..ad4500f
--- /dev/null
+++ b/src/app/new/HotspotsMap/index.tsx
@@ -0,0 +1,213 @@
+"use client"
+
+import maplibregl from "maplibre-gl"
+import "maplibre-gl/dist/maplibre-gl.css"
+import { Protocol } from "pmtiles"
+
+import { cellToLatLng, cellsToMultiPolygon, getResolution } from "h3-js"
+import { useTheme } from "next-themes"
+import {
+ usePathname,
+ useRouter,
+ useSelectedLayoutSegment,
+ useSelectedLayoutSegments,
+} from "next/navigation"
+import { useCallback, useEffect, useMemo, useRef, useState } from "react"
+import Map, {
+ Layer,
+ MapLayerMouseEvent,
+ MapProvider,
+ MapRef,
+ MapStyle,
+ NavigationControl,
+ Source,
+ useMap,
+} from "react-map-gl"
+import { gaEvent } from "../GATracker"
+import { Attribution } from "./Attribution"
+import { NetworkCoverageLayer } from "./NetworkCoverageLayer"
+import { mapLayersDark } from "./mapLayersDark"
+import { mapLayersLight } from "./mapLayersLight"
+import {
+ HexFeatureDetails,
+ INITIAL_MAP_VIEW_STATE,
+ MAP_CONTAINER_STYLE,
+ MAX_MAP_ZOOM,
+ MIN_MAP_ZOOM,
+ ZOOM_BY_HEX_RESOLUTION,
+ getHexOutlineStyle,
+ networkLayers,
+} from "./utils"
+
+export function HotspotsMap({ children }: { children: React.ReactNode }) {
+ const { resolvedTheme } = useTheme()
+ const router = useRouter()
+ const pathname = usePathname()
+ const segments = useSelectedLayoutSegments()
+ const segment = useSelectedLayoutSegment()
+ const mapRef = useRef(null)
+ const [selectedHex, setSelectedHex] = useState(null)
+ const [cursor, setCursor] = useState("")
+
+ useEffect(() => {
+ let protocol = new Protocol()
+ maplibregl.addProtocol("pmtiles", protocol.tile)
+ return () => {
+ maplibregl.removeProtocol("pmtiles")
+ }
+ }, [])
+
+ const mapStyle = useMemo(() => {
+ const style: MapStyle = {
+ version: 8,
+ sources: {
+ protomaps: {
+ type: "vector",
+ tiles: [`${process.env.NEXT_PUBLIC_PMTILES_URL}/{z}/{x}/{y}.mvt`],
+ },
+ },
+ glyphs: "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf",
+ layers: resolvedTheme === "dark" ? mapLayersDark : mapLayersLight,
+ }
+ return style
+ }, [resolvedTheme])
+
+ const selectHex = useCallback((hexId: string | null) => {
+ if (!hexId) {
+ setSelectedHex(null)
+ return
+ }
+
+ const selectedHex = {
+ hexId,
+ geojson: {
+ type: "MultiPolygon",
+ coordinates: cellsToMultiPolygon([hexId], true),
+ } as GeoJSON.Geometry,
+ }
+
+ setSelectedHex(selectedHex)
+
+ if (!mapRef.current) return
+ const map = mapRef.current.getMap()
+ const [lat, lng] = cellToLatLng(hexId)
+ const bounds = map.getBounds()
+ const zoom = map.getZoom()
+ const hexResolution = getResolution(hexId)
+ const newZoom = ZOOM_BY_HEX_RESOLUTION[hexResolution]
+ if (zoom < newZoom - 3 || !bounds.contains([lng, lat])) {
+ // Fly to the hex if it's not visible in the current viewport, or if it's not zoomed in enough
+ map.flyTo({ center: [lng, lat], zoom: newZoom })
+ }
+ }, [])
+
+ const selectHexByPathname = useCallback(() => {
+ if (!mapRef.current) return
+
+ if (segments.length === 2 && segments[0] === "hex") {
+ const hexId = segments[1]
+ if (selectedHex?.hexId !== hexId) {
+ selectHex(hexId)
+ }
+ } else if (pathname === "/" && selectedHex?.hexId) {
+ selectHex(null)
+ }
+ }, [pathname, segments, selectHex, selectedHex?.hexId])
+
+ useEffect(() => {
+ selectHexByPathname()
+ }, [selectHexByPathname])
+
+ const onClick = useCallback(
+ (event: MapLayerMouseEvent) => {
+ event.features?.forEach(({ layer, properties }) => {
+ if (layer.id !== "hexes_layer" || !properties?.id) return
+ if (selectedHex?.hexId === properties.id) {
+ router.push("/")
+ } else {
+ router.push(`/hex/${properties.id}`)
+ }
+ })
+ },
+ [router, selectedHex?.hexId]
+ )
+
+ useEffect(() => {
+ gaEvent({ action: "map_load" })
+ }, [])
+
+ const onMouseEnter = useCallback(() => setCursor("pointer"), [])
+ const onMouseLeave = useCallback(() => setCursor(""), [])
+
+ return (
+
+
+
+ )
+}
+
+const MapTest = () => {
+ const map = useMap()
+ const [currentZoom, setCurrentZoom] = useState(map.current?.getZoom())
+ const zoom = map.current?.getZoom()
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setCurrentZoom(map.current?.getZoom())
+ }, 250)
+ return () => clearInterval(interval)
+ }, [map, setCurrentZoom])
+ console.log(zoom)
+
+ return (
+
+
Some random text
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/mapLayersDark.tsx b/src/app/new/HotspotsMap/mapLayersDark.tsx
new file mode 100644
index 0000000..4691d16
--- /dev/null
+++ b/src/app/new/HotspotsMap/mapLayersDark.tsx
@@ -0,0 +1,665 @@
+import { AnyLayer } from "react-map-gl"
+
+export const mapLayersDark: AnyLayer[] = [
+ {
+ id: "background",
+ type: "background",
+ paint: {
+ "background-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "earth",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "earth",
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "landuse_park",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "park"], ["==", "landuse", "cemetery"]],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "landuse_hospital",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "hospital"]],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "landuse_industrial",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "industrial"]],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "landuse_school",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "school"]],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "natural_wood",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "natural",
+ filter: [
+ "any",
+ ["==", "natural", "wood"],
+ ["==", "leisure", "nature_reserve"],
+ ["==", "landuse", "forest"],
+ ],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "landuse_pedestrian",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "highway", "footway"]],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "natural_glacier",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "natural",
+ filter: ["==", "natural", "glacier"],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "natural_sand",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "natural",
+ filter: ["==", "natural", "sand"],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "landuse_aerodrome",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["==", "aeroway", "aerodrome"],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "transit_runway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "transit",
+ filter: ["has", "aeroway"],
+ paint: {
+ "line-color": "#2A2A2A",
+ "line-width": 6,
+ },
+ },
+ {
+ id: "landuse_runway",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: [
+ "any",
+ ["==", "aeroway", "runway"],
+ ["==", "area:aeroway", "runway"],
+ ["==", "area:aeroway", "taxiway"],
+ ],
+ paint: {
+ "fill-color": "#2A2A2A",
+ },
+ },
+ {
+ id: "water",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "water",
+ paint: {
+ "fill-color": "#202020",
+ },
+ },
+ {
+ id: "roads_tunnels_other",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "other"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-dasharray": [1, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 14,
+ 0,
+ 14.5,
+ 0.5,
+ 20,
+ 12,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_minor",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "minor_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 12,
+ 0,
+ 12.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_medium",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "medium_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_major",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "major_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 19,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_highway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "highway"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 3,
+ 0,
+ 3.5,
+ 0.5,
+ 18,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "physical_line_waterway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "physical_line",
+ filter: ["==", ["get", "pmap:kind"], "waterway"],
+ paint: {
+ "line-color": "#202020",
+ "line-width": 0.5,
+ },
+ },
+ {
+ id: "roads_other",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "other"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-dasharray": [2, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 14,
+ 0,
+ 14.5,
+ 0.5,
+ 20,
+ 12,
+ ],
+ },
+ },
+ {
+ id: "roads_minor",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "minor_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 12,
+ 0,
+ 12.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_medium",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: [
+ "all",
+ ["==", "pmap:level", 0],
+ ["==", "pmap:kind", "medium_road"],
+ ],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_major",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "major_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 19,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_highway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "highway"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 3,
+ 0,
+ 3.5,
+ 0.5,
+ 18,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "transit_railway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "transit",
+ filter: ["all", ["==", "pmap:kind", "railway"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": 2,
+ },
+ },
+ {
+ id: "transit_railway_tracks",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "transit",
+ filter: ["all", ["==", "pmap:kind", "railway"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": 0.8,
+ "line-dasharray": [6, 10],
+ },
+ },
+ {
+ id: "boundaries_country",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "boundaries",
+ filter: ["<=", "pmap:min_admin_level", 2],
+ paint: {
+ "line-color": "#696969",
+ "line-width": 1.5,
+ },
+ },
+ {
+ id: "boundaries",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "boundaries",
+ filter: [">", "pmap:min_admin_level", 2],
+ paint: {
+ "line-color": "#696969",
+ "line-width": 1,
+ "line-dasharray": [1, 2],
+ },
+ },
+ {
+ id: "roads_bridges_other",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "other"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-dasharray": [2, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 14,
+ 0,
+ 14.5,
+ 0.5,
+ 20,
+ 12,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_minor",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "minor_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 12,
+ 0,
+ 12.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_medium",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "medium_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_major",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "major_road"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 19,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_highway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "highway"]],
+ paint: {
+ "line-color": "#3D3D3D",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 3,
+ 0,
+ 3.5,
+ 0.5,
+ 18,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "physical_line_waterway_label",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "physical_line",
+ minzoom: 14,
+ layout: {
+ "symbol-placement": "line",
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "name"],
+ "text-size": 10,
+ "text-letter-spacing": 0.3,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#151515",
+ "text-halo-width": 1,
+ },
+ },
+ {
+ id: "roads_labels",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "roads",
+ layout: {
+ "symbol-placement": "line",
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "name"],
+ "text-size": 12,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#151515",
+ "text-halo-width": 2,
+ },
+ },
+ {
+ id: "mask",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "mask",
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "physical_point_ocean",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "physical_point",
+ filter: ["any", ["==", "place", "sea"], ["==", "place", "ocean"]],
+ layout: {
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "name"],
+ "text-size": 13,
+ "text-letter-spacing": 0.2,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ },
+ },
+ {
+ id: "places_subplace",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "pmap:kind", "neighbourhood"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": 10,
+ "text-transform": "uppercase",
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#151515",
+ "text-halo-width": 0.5,
+ },
+ },
+ {
+ id: "places_city",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "pmap:kind", "city"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": ["step", ["get", "pmap:rank"], 0, 1, 12, 2, 10],
+ "text-variable-anchor": ["bottom-left"],
+ "text-radial-offset": 0.2,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#151515",
+ "text-halo-width": 1,
+ },
+ },
+ {
+ id: "places_state",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "pmap:kind", "state"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": 14,
+ "text-radial-offset": 0.2,
+ "text-anchor": "center",
+ "text-transform": "uppercase",
+ "text-letter-spacing": 0.1,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#151515",
+ "text-halo-width": 1,
+ },
+ },
+ {
+ id: "places_country",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "place", "country"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": 10,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#151515",
+ "text-halo-width": 1,
+ },
+ },
+]
diff --git a/src/app/new/HotspotsMap/mapLayersLight.tsx b/src/app/new/HotspotsMap/mapLayersLight.tsx
new file mode 100644
index 0000000..6981b29
--- /dev/null
+++ b/src/app/new/HotspotsMap/mapLayersLight.tsx
@@ -0,0 +1,665 @@
+import { AnyLayer } from "react-map-gl"
+
+export const mapLayersLight: AnyLayer[] = [
+ {
+ id: "background",
+ type: "background",
+ paint: {
+ "background-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "earth",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "earth",
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "landuse_park",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "park"], ["==", "landuse", "cemetery"]],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "landuse_hospital",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "hospital"]],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "landuse_industrial",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "industrial"]],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "landuse_school",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "pmap:kind", "school"]],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "natural_wood",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "natural",
+ filter: [
+ "any",
+ ["==", "natural", "wood"],
+ ["==", "leisure", "nature_reserve"],
+ ["==", "landuse", "forest"],
+ ],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "landuse_pedestrian",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["any", ["==", "highway", "footway"]],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "natural_glacier",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "natural",
+ filter: ["==", "natural", "glacier"],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "natural_sand",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "natural",
+ filter: ["==", "natural", "sand"],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "landuse_aerodrome",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: ["==", "aeroway", "aerodrome"],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "transit_runway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "transit",
+ filter: ["has", "aeroway"],
+ paint: {
+ "line-color": "#F3F3F1",
+ "line-width": 6,
+ },
+ },
+ {
+ id: "landuse_runway",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "landuse",
+ filter: [
+ "any",
+ ["==", "aeroway", "runway"],
+ ["==", "area:aeroway", "runway"],
+ ["==", "area:aeroway", "taxiway"],
+ ],
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "water",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "water",
+ paint: {
+ "fill-color": "#CAD2D3",
+ },
+ },
+ {
+ id: "roads_tunnels_other",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "other"]],
+ paint: {
+ "line-color": "#fff",
+ "line-dasharray": [1, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 14,
+ 0,
+ 14.5,
+ 0.5,
+ 20,
+ 12,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_minor",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "minor_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 12,
+ 0,
+ 12.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_medium",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "medium_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_major",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "major_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 19,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_tunnels_highway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["<", "pmap:level", 0], ["==", "pmap:kind", "highway"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 3,
+ 0,
+ 3.5,
+ 0.5,
+ 18,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "physical_line_waterway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "physical_line",
+ filter: ["==", ["get", "pmap:kind"], "waterway"],
+ paint: {
+ "line-color": "#fff",
+ "line-width": 0.5,
+ },
+ },
+ {
+ id: "roads_other",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "other"]],
+ paint: {
+ "line-color": "#fff",
+ "line-dasharray": [2, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 14,
+ 0,
+ 14.5,
+ 0.5,
+ 20,
+ 12,
+ ],
+ },
+ },
+ {
+ id: "roads_minor",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "minor_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 12,
+ 0,
+ 12.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_medium",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: [
+ "all",
+ ["==", "pmap:level", 0],
+ ["==", "pmap:kind", "medium_road"],
+ ],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_major",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "major_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 19,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_highway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", ["==", "pmap:level", 0], ["==", "pmap:kind", "highway"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 3,
+ 0,
+ 3.5,
+ 0.5,
+ 18,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "transit_railway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "transit",
+ filter: ["all", ["==", "pmap:kind", "railway"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": 2,
+ },
+ },
+ {
+ id: "transit_railway_tracks",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "transit",
+ filter: ["all", ["==", "pmap:kind", "railway"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": 0.8,
+ "line-dasharray": [6, 10],
+ },
+ },
+ {
+ id: "boundaries_country",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "boundaries",
+ filter: ["<=", "pmap:min_admin_level", 2],
+ paint: {
+ "line-color": "#9e9e9e",
+ "line-width": 1,
+ },
+ },
+ {
+ id: "boundaries",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "boundaries",
+ filter: [">", "pmap:min_admin_level", 2],
+ paint: {
+ "line-color": "#9e9e9e",
+ "line-width": 1,
+ "line-dasharray": [1, 2],
+ },
+ },
+ {
+ id: "roads_bridges_other",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "other"]],
+ paint: {
+ "line-color": "#fff",
+ "line-dasharray": [2, 1],
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 14,
+ 0,
+ 14.5,
+ 0.5,
+ 20,
+ 12,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_minor",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "minor_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 12,
+ 0,
+ 12.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_medium",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "medium_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 20,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_major",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "major_road"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 7,
+ 0,
+ 7.5,
+ 0.5,
+ 19,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "roads_bridges_highway",
+ type: "line",
+ source: "protomaps",
+ "source-layer": "roads",
+ filter: ["all", [">", "pmap:level", 0], ["==", "pmap:kind", "highway"]],
+ paint: {
+ "line-color": "#fff",
+ "line-width": [
+ "interpolate",
+ ["exponential", 1.6],
+ ["zoom"],
+ 3,
+ 0,
+ 3.5,
+ 0.5,
+ 18,
+ 32,
+ ],
+ },
+ },
+ {
+ id: "physical_line_waterway_label",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "physical_line",
+ minzoom: 14,
+ layout: {
+ "symbol-placement": "line",
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "name"],
+ "text-size": 10,
+ "text-letter-spacing": 0.3,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#fff",
+ "text-halo-width": 1,
+ },
+ },
+ {
+ id: "roads_labels",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "roads",
+ layout: {
+ "symbol-placement": "line",
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "name"],
+ "text-size": 12,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#fff",
+ "text-halo-width": 2,
+ },
+ },
+ {
+ id: "mask",
+ type: "fill",
+ source: "protomaps",
+ "source-layer": "mask",
+ paint: {
+ "fill-color": "#F3F3F1",
+ },
+ },
+ {
+ id: "physical_point_ocean",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "physical_point",
+ filter: ["any", ["==", "place", "sea"], ["==", "place", "ocean"]],
+ layout: {
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "name"],
+ "text-size": 13,
+ "text-letter-spacing": 0.2,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ },
+ },
+ {
+ id: "places_subplace",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "pmap:kind", "neighbourhood"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": 10,
+ "text-transform": "uppercase",
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#fff",
+ "text-halo-width": 0.5,
+ },
+ },
+ {
+ id: "places_city",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "pmap:kind", "city"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": ["step", ["get", "pmap:rank"], 0, 1, 12, 2, 10],
+ "text-variable-anchor": ["bottom-left"],
+ "text-radial-offset": 0.2,
+ },
+ paint: {
+ "text-color": "#6D6D6D",
+ "text-halo-color": "#fff",
+ "text-halo-width": 1,
+ },
+ },
+ {
+ id: "places_state",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "pmap:kind", "state"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": 14,
+ "text-radial-offset": 0.2,
+ "text-anchor": "center",
+ "text-transform": "uppercase",
+ "text-letter-spacing": 0.1,
+ },
+ paint: {
+ "text-color": "#A8A8A8",
+ "text-halo-color": "#fff",
+ "text-halo-width": 1,
+ },
+ },
+ {
+ id: "places_country",
+ type: "symbol",
+ source: "protomaps",
+ "source-layer": "places",
+ filter: ["==", "place", "country"],
+ layout: {
+ "text-field": "{name:en}",
+ "text-font": ["NotoSans-Regular"],
+ "text-size": 10,
+ },
+ paint: {
+ "text-color": "#848484",
+ "text-halo-color": "#fff",
+ "text-halo-width": 1,
+ },
+ },
+]
diff --git a/src/app/new/HotspotsMap/utils.ts b/src/app/new/HotspotsMap/utils.ts
new file mode 100644
index 0000000..737558c
--- /dev/null
+++ b/src/app/new/HotspotsMap/utils.ts
@@ -0,0 +1,138 @@
+import { CoordPair } from "h3-js"
+import { HeliumIotIcon } from "../icons/HeliumIotIcon"
+import { HeliumMobileIcon } from "../icons/HeliumMobileIcon"
+
+export const MIN_MAP_ZOOM = 2
+export const MAX_MAP_ZOOM = 14
+
+const WORLD_BOUNDS: [CoordPair, CoordPair] = [
+ [-134.827109, 57.785781],
+ [129.767893, -30.955724],
+]
+
+export const INITIAL_MAP_VIEW_STATE = {
+ bounds: WORLD_BOUNDS,
+}
+
+export const MAP_CONTAINER_STYLE: React.CSSProperties = {
+ height: "100%",
+ width: "100%",
+ overflow: "hidden",
+ position: "relative",
+ backgroundColor: "rgb(19,24,37)",
+}
+
+export const MIN_HEXES_ZOOM = 7
+export const MIN_HEX_LABELS_ZOOM = 11
+export const POINTS_AND_HEXES_OVERLAP = 2
+
+export const HELIUM_IOT_COLOR = "#27EE76"
+export const HELIUM_MOBILE_COLOR = "#009FF9"
+
+export const getHexFillStyle = (color: string): mapboxgl.FillPaint => ({
+ "fill-color": color,
+ "fill-opacity": 0.4,
+})
+
+export const getBlurredPointStyle = (color: string): mapboxgl.CirclePaint => ({
+ "circle-color": color,
+ "circle-opacity": [
+ "interpolate",
+ ["exponential", 2],
+ ["zoom"],
+ MIN_MAP_ZOOM,
+ 0.05,
+ MIN_HEXES_ZOOM + POINTS_AND_HEXES_OVERLAP,
+ 0.4,
+ ],
+ "circle-radius": [
+ "interpolate",
+ ["exponential", 2],
+ ["zoom"],
+ MIN_MAP_ZOOM,
+ 3,
+ MIN_HEXES_ZOOM + POINTS_AND_HEXES_OVERLAP,
+ 2,
+ ],
+})
+
+export const getHexOutlineStyle = (
+ theme: string | undefined
+): mapboxgl.LinePaint => ({
+ "line-color": theme === "dark" ? "#fff" : "rgb(113,113,122)",
+ "line-width": 4,
+})
+
+export const getHexLabelStyle = (
+ theme: string | undefined
+): mapboxgl.SymbolPaint => ({
+ "text-color": theme === "dark" ? "white" : "#6D6D6D",
+})
+
+export const hexLabelLayout: mapboxgl.SymbolLayout = {
+ "text-font": ["NotoSans-Regular"],
+ "text-field": ["get", "count"],
+ "text-allow-overlap": false,
+ "text-size": 23,
+}
+
+export interface HexFeatureDetails {
+ hexId: string
+ geojson: GeoJSON.Geometry
+}
+
+export const ZOOM_BY_HEX_RESOLUTION: { [resolution: number]: number } = {
+ 10: 14,
+ 9: 14,
+ 8: 13,
+ 7: 12,
+ 6: 11,
+ 5: 10,
+ 4: 9,
+}
+
+interface LayerConfig {
+ sourcePath: string
+ sourceLayer: string
+}
+
+export interface NetworkCoverageLayerOption {
+ name: string
+ icon: (props: any) => JSX.Element
+ color: string
+ sourceDomain: string
+ points: LayerConfig
+ hexes: LayerConfig
+}
+
+export const networkLayers: { [network: string]: NetworkCoverageLayerOption } =
+ {
+ mobile: {
+ name: "MOBILE",
+ icon: HeliumMobileIcon,
+ color: HELIUM_MOBILE_COLOR,
+ sourceDomain: process.env.NEXT_PUBLIC_HOTSPOTTY_TILESERVER_URL!,
+ points: {
+ sourcePath: "public.helium_mobile_points.json",
+ sourceLayer: "public.helium_mobile_points",
+ },
+ hexes: {
+ sourcePath: "public.helium_mobile_hexes.json",
+ sourceLayer: "public.helium_mobile_hexes",
+ },
+ },
+ iot: {
+ name: "IOT",
+ icon: HeliumIotIcon,
+ color: HELIUM_IOT_COLOR,
+ sourceDomain: process.env.NEXT_PUBLIC_HOTSPOTTY_TILESERVER_URL!,
+ points: {
+ sourcePath: "public.helium_iot_points.json",
+ sourceLayer: "public.helium_iot_points",
+ },
+ hexes: {
+ sourcePath: "public.helium_iot_hexes.json",
+ sourceLayer: "public.helium_iot_hexes",
+ },
+ },
+ }
diff --git a/src/app/new/Nav.tsx b/src/app/new/Nav.tsx
new file mode 100644
index 0000000..92c73a8
--- /dev/null
+++ b/src/app/new/Nav.tsx
@@ -0,0 +1,89 @@
+"use client"
+
+import { DownCarret } from "@/components/icons/DownCarret"
+import { EnergyIcon } from "@/components/icons/EnergyIcon"
+import { HeliumIcon2 } from "@/components/icons/HeliumIcon2"
+import { IotIcon } from "@/components/icons/IotIcon"
+import { MobileIcon } from "@/components/icons/MobileIcon"
+import clsx from "clsx"
+import Link from "next/link"
+import { useState } from "react"
+
+const Logo = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const NETWORKS = [
+ {
+ Icon: ,
+ name: "IOT",
+ },
+ {
+ Icon: ,
+ name: "MOBILE",
+ },
+ {
+ Icon: (
+
+
+
+ ),
+ name: "ENERGY",
+ },
+]
+
+const Selector = () => {
+ const [selected, setSelected] = useState(NETWORKS[0])
+ const [showOptions, setShowOptions] = useState(false)
+
+ return (
+
+ {
+
+ }
+
+
+ {NETWORKS.map((network) => (
+
+ ))}
+
+
+ )
+}
+
+export const Nav = () => {
+ return (
+
+ )
+}
diff --git a/src/app/new/layout.tsx b/src/app/new/layout.tsx
new file mode 100644
index 0000000..dee4d92
--- /dev/null
+++ b/src/app/new/layout.tsx
@@ -0,0 +1,91 @@
+import { GAScript } from "@/components/GAScript"
+import { GATracker } from "@/components/GATracker"
+import { Header } from "@/components/Header"
+import { HotspotsMap } from "@/components/HotspotsMap"
+import { Providers } from "@/components/Providers"
+import "@/styles/tailwind.css"
+import "focus-visible"
+import Head from "next/head"
+import { Suspense } from "react"
+import "react-tooltip/dist/react-tooltip.css"
+import { Nav } from "./Nav"
+
+export const metadata = {
+ manifest: "/manifest.json",
+ themeColor: "#000000",
+ icons: {
+ icon: [
+ {
+ url: "/favicon.ico",
+ type: "image/x-icon",
+ sizes: "64x64 32x32 24x24 16x16",
+ },
+ {
+ url: "/logo192.png",
+ type: "image/png",
+ sizes: "192x192",
+ },
+ {
+ url: "/logo512.png",
+ type: "image/png",
+ sizes: "512x512",
+ },
+ ],
+ },
+ openGraph: {
+ title: "Helium Explorer",
+ description:
+ "Helium Explorer is an open source network explorer for the Helium network",
+ url: "https://explorer.helium.com",
+ siteName: "Helium Explorer",
+ images: [
+ {
+ url: "/og.png",
+ width: 954,
+ height: 696,
+ },
+ ],
+ locale: "en-US",
+ type: "website",
+ },
+}
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {/*
+ Wrapping in supsense to avoid pages getting deopted into client-side rendering
+ https://nextjs.org/docs/messages/deopted-into-client-rendering
+ */}
+
+
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/src/app/new/page.tsx b/src/app/new/page.tsx
new file mode 100644
index 0000000..a56e2a5
--- /dev/null
+++ b/src/app/new/page.tsx
@@ -0,0 +1,7 @@
+export const metadata = {
+ title: "Helium Hotspots Map",
+}
+
+export default function Page() {
+ return null
+}
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
index 139bb96..99f60d0 100644
--- a/src/components/Header/index.tsx
+++ b/src/components/Header/index.tsx
@@ -27,6 +27,7 @@ function Logo({ className, ...props }: { className?: string }) {
}
export function Header() {
+ if (true) return null
return (
diff --git a/src/components/icons/DownCarret.tsx b/src/components/icons/DownCarret.tsx
new file mode 100644
index 0000000..b14857f
--- /dev/null
+++ b/src/components/icons/DownCarret.tsx
@@ -0,0 +1,15 @@
+export const DownCarret = () => (
+
+)
diff --git a/src/components/icons/EnergyIcon.tsx b/src/components/icons/EnergyIcon.tsx
new file mode 100644
index 0000000..e19316a
--- /dev/null
+++ b/src/components/icons/EnergyIcon.tsx
@@ -0,0 +1,17 @@
+export const EnergyIcon = (props: { className?: string }) => {
+ return (
+
+ )
+}
diff --git a/src/components/icons/HeliumIcon2.tsx b/src/components/icons/HeliumIcon2.tsx
new file mode 100644
index 0000000..2f8064c
--- /dev/null
+++ b/src/components/icons/HeliumIcon2.tsx
@@ -0,0 +1,19 @@
+export const HeliumIcon2 = (props: { className?: string }) => {
+ return (
+
+ )
+}
diff --git a/src/components/icons/HeliumIotIcon.tsx b/src/components/icons/HeliumIotIcon.tsx
index 3786afa..b965846 100644
--- a/src/components/icons/HeliumIotIcon.tsx
+++ b/src/components/icons/HeliumIotIcon.tsx
@@ -1,6 +1,7 @@
import { HELIUM_IOT_COLOR } from "../HotspotsMap/utils"
-export function HeliumIotIcon(props: { className?: string }) {
+export function HeliumIotIcon(props: { className?: string; fill?: string }) {
+ const fill = props.fill || HELIUM_IOT_COLOR
return (
)
diff --git a/src/components/icons/IotIcon.tsx b/src/components/icons/IotIcon.tsx
new file mode 100644
index 0000000..34e4726
--- /dev/null
+++ b/src/components/icons/IotIcon.tsx
@@ -0,0 +1,22 @@
+import { HELIUM_IOT_COLOR } from "../HotspotsMap/utils"
+
+export function IotIcon(props: { className?: string; fill?: string }) {
+ const fill = props.fill || HELIUM_IOT_COLOR
+ return (
+
+ )
+}
diff --git a/src/components/icons/MobileIcon.tsx b/src/components/icons/MobileIcon.tsx
new file mode 100644
index 0000000..09adc91
--- /dev/null
+++ b/src/components/icons/MobileIcon.tsx
@@ -0,0 +1,25 @@
+export const MobileIcon = (props: { className?: string; fill?: string }) => (
+
+)
From ca05fb7b1c7660ac03c10a52d4f000dc467ccdad Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Mon, 15 Jul 2024 10:38:46 -0400
Subject: [PATCH 02/67] More nav
---
src/app/new/Nav.tsx | 89 -----------
src/app/new/Nav/Nav.tsx | 127 +++++++++++++++
src/app/new/Nav/Search.tsx | 224 +++++++++++++++++++++++++++
src/app/new/layout.tsx | 2 +-
src/components/icons/HeliumIcon2.tsx | 2 +-
5 files changed, 353 insertions(+), 91 deletions(-)
delete mode 100644 src/app/new/Nav.tsx
create mode 100644 src/app/new/Nav/Nav.tsx
create mode 100644 src/app/new/Nav/Search.tsx
diff --git a/src/app/new/Nav.tsx b/src/app/new/Nav.tsx
deleted file mode 100644
index 92c73a8..0000000
--- a/src/app/new/Nav.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-"use client"
-
-import { DownCarret } from "@/components/icons/DownCarret"
-import { EnergyIcon } from "@/components/icons/EnergyIcon"
-import { HeliumIcon2 } from "@/components/icons/HeliumIcon2"
-import { IotIcon } from "@/components/icons/IotIcon"
-import { MobileIcon } from "@/components/icons/MobileIcon"
-import clsx from "clsx"
-import Link from "next/link"
-import { useState } from "react"
-
-const Logo = () => {
- return (
-
-
-
-
-
- )
-}
-
-const NETWORKS = [
- {
- Icon: ,
- name: "IOT",
- },
- {
- Icon: ,
- name: "MOBILE",
- },
- {
- Icon: (
-
-
-
- ),
- name: "ENERGY",
- },
-]
-
-const Selector = () => {
- const [selected, setSelected] = useState(NETWORKS[0])
- const [showOptions, setShowOptions] = useState(false)
-
- return (
-
- {
-
- }
-
-
- {NETWORKS.map((network) => (
-
- ))}
-
-
- )
-}
-
-export const Nav = () => {
- return (
-
- )
-}
diff --git a/src/app/new/Nav/Nav.tsx b/src/app/new/Nav/Nav.tsx
new file mode 100644
index 0000000..3a7ab37
--- /dev/null
+++ b/src/app/new/Nav/Nav.tsx
@@ -0,0 +1,127 @@
+"use client"
+
+import { DownCarret } from "@/components/icons/DownCarret"
+import { EnergyIcon } from "@/components/icons/EnergyIcon"
+import { HeliumIcon2 } from "@/components/icons/HeliumIcon2"
+import { IotIcon } from "@/components/icons/IotIcon"
+import { MobileIcon } from "@/components/icons/MobileIcon"
+import clsx from "clsx"
+import Link from "next/link"
+import { ReactElement, useState } from "react"
+import { Search } from "./Search"
+
+const Logo = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+const NETWORKS = [
+ {
+ Icon: ,
+ name: "IOT",
+ },
+ {
+ Icon: ,
+ name: "MOBILE",
+ },
+ {
+ Icon: (
+
+
+
+ ),
+ name: "ENERGY",
+ },
+]
+
+type Option = {
+ Icon?: ReactElement
+ name: string
+}
+
+const Selector = ({
+ options,
+ width,
+}: {
+ options: Option[]
+ width?: string
+}) => {
+ const [selected, setSelected] = useState(options[0])
+ const [showOptions, setShowOptions] = useState(false)
+
+ return (
+
+
+
+
+ {options.map((network) => (
+
+ ))}
+
+
+ )
+}
+
+const Divider = () =>
+
+export const Nav = () => {
+ return (
+
+ )
+}
diff --git a/src/app/new/Nav/Search.tsx b/src/app/new/Nav/Search.tsx
new file mode 100644
index 0000000..ee1f3a4
--- /dev/null
+++ b/src/app/new/Nav/Search.tsx
@@ -0,0 +1,224 @@
+"use client"
+
+import { HeliumIotIcon } from "@/components/icons/HeliumIotIcon"
+import { HeliumMobileIcon } from "@/components/icons/HeliumMobileIcon"
+import { LoadingIcon } from "@/components/icons/LoadingIcon"
+import { Combobox, Dialog, Transition } from "@headlessui/react"
+import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"
+import { QuestionMarkCircleIcon } from "@heroicons/react/24/solid"
+import clsx from "clsx"
+import { isValidCell } from "h3-js"
+import { useRouter } from "next/navigation"
+import { Fragment, useCallback, useState } from "react"
+import { useDebouncedCallback } from "use-debounce"
+
+let controller: AbortController | null = null
+
+const RESULTS_LIMIT = 20
+
+export interface HotspotResult {
+ hotspot_id: string
+ location_res8: string
+ location_res12: string
+ name: string
+ owner: string
+ cell_count: number
+}
+
+export function Search() {
+ const router = useRouter()
+ const [query, setQuery] = useState("")
+ const [open, setOpen] = useState(false)
+ const [searchResults, setSearchResults] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+
+ const searchItemByQuery = useCallback(async (query: string) => {
+ if (controller) controller.abort()
+
+ controller = new AbortController()
+ const signal = controller.signal
+
+ setSearchResults([])
+
+ if (!query) return null
+
+ setIsLoading(true)
+
+ try {
+ const searchUrl = new URL(
+ `${process.env.NEXT_PUBLIC_HOTSPOTTY_EXPLORER_API_URL}/search`
+ )
+ searchUrl.searchParams.append("name", query.trim().replaceAll(" ", "-"))
+
+ const results = (await fetch(searchUrl, {
+ signal,
+ next: { revalidate: 10 },
+ headers: {
+ Authorization: `bearer ${process.env.NEXT_PUBLIC_HOTSPOTTY_EXPLORER_API_TOKEN}`,
+ },
+ }).then((res) => res.json())) as HotspotResult[]
+
+ setSearchResults(results)
+ setIsLoading(false)
+ } catch {
+ setIsLoading(false)
+ }
+ }, [])
+
+ const onChangeSearch = useDebouncedCallback((query: string) => {
+ if (query.length === 15 && isValidCell(query)) {
+ router.push(`/hex/${query}`)
+ setOpen(false)
+ clearSearchModal()
+ } else {
+ searchItemByQuery(query)
+ }
+ }, 400)
+
+ const clearSearchModal = useCallback(() => {
+ setQuery("")
+ setSearchResults([])
+ setIsLoading(false)
+ }, [])
+
+ const handleHotspotSelection = useCallback(
+ (hotspot: HotspotResult) => {
+ router.push(`/hex/${hotspot.location_res8}`)
+ setOpen(false)
+ },
+ [router]
+ )
+
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
diff --git a/src/app/new/layout.tsx b/src/app/new/layout.tsx
index dee4d92..a591716 100644
--- a/src/app/new/layout.tsx
+++ b/src/app/new/layout.tsx
@@ -8,7 +8,7 @@ import "focus-visible"
import Head from "next/head"
import { Suspense } from "react"
import "react-tooltip/dist/react-tooltip.css"
-import { Nav } from "./Nav"
+import { Nav } from "./Nav/Nav"
export const metadata = {
manifest: "/manifest.json",
diff --git a/src/components/icons/HeliumIcon2.tsx b/src/components/icons/HeliumIcon2.tsx
index 2f8064c..6a9e7b6 100644
--- a/src/components/icons/HeliumIcon2.tsx
+++ b/src/components/icons/HeliumIcon2.tsx
@@ -9,7 +9,7 @@ export const HeliumIcon2 = (props: { className?: string }) => {
{...props}
>
Helium Icon
-
+
Date: Mon, 15 Jul 2024 12:20:26 -0400
Subject: [PATCH 03/67] Add nav control
---
public/negative.png | Bin 0 -> 155 bytes
public/positive.png | Bin 0 -> 201 bytes
src/app/new/HotspotsMap/Attribution.tsx | 21 -------------
src/app/new/HotspotsMap/MapZoom.tsx | 38 +++++++++++++++++++++++
src/app/new/HotspotsMap/index.tsx | 39 ++----------------------
src/app/new/HotspotsMap/utils.ts | 4 +--
src/app/new/layout.tsx | 2 +-
7 files changed, 44 insertions(+), 60 deletions(-)
create mode 100644 public/negative.png
create mode 100644 public/positive.png
delete mode 100644 src/app/new/HotspotsMap/Attribution.tsx
create mode 100644 src/app/new/HotspotsMap/MapZoom.tsx
diff --git a/public/negative.png b/public/negative.png
new file mode 100644
index 0000000000000000000000000000000000000000..5ea3a53bb10ead2167d64d059407f0f8650a2903
GIT binary patch
literal 155
zcmeAS@N?(olHy`uVBq!ia0vp^d_c^^!3HEVy56n=Qk(@Ik;M!Q+`=Ht$S`Y;1W=H%
zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFK)!*ei(^Oy7;|44F)`}!iLpX0_W&WfAd6R
zmpk9Yj*gD~?f1OoS-93Keh^k_ST#-ONxF(l%I^86=g(g;u}{i!ay9Em0l}xg4%F1l
s-M#*-uwvS_bVd6^Ru9&uJqdZpvfV~3sq@mkHlR%mp00i_>zopr0Ce<0%K!iX
literal 0
HcmV?d00001
diff --git a/src/app/new/HotspotsMap/Attribution.tsx b/src/app/new/HotspotsMap/Attribution.tsx
deleted file mode 100644
index 6e52d03..0000000
--- a/src/app/new/HotspotsMap/Attribution.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import clsx from "clsx"
-import Link from "next/link"
-import { HotspottyIcon } from "../icons/HotspottyIcon"
-
-export function Attribution({ className }: { className?: string }) {
- return (
-
-
-
-
- Hotspotty
-
-
-
- )
-}
diff --git a/src/app/new/HotspotsMap/MapZoom.tsx b/src/app/new/HotspotsMap/MapZoom.tsx
new file mode 100644
index 0000000..3e61b1c
--- /dev/null
+++ b/src/app/new/HotspotsMap/MapZoom.tsx
@@ -0,0 +1,38 @@
+import Image from "next/image"
+import { useEffect, useState } from "react"
+import { useMap } from "react-map-gl"
+import NegativeIcon from "../../../../public/negative.png"
+import PositiveIcon from "../../../../public/positive.png"
+import { MAX_MAP_ZOOM, MIN_MAP_ZOOM } from "./utils"
+
+export const MapZoom = () => {
+ const map = useMap()
+ const [_currentZoom, setCurrentZoom] = useState(map.current?.getZoom())
+ const zoom = map.current?.getZoom()
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setCurrentZoom(map.current?.getZoom())
+ }, 250)
+ return () => clearInterval(interval)
+ }, [map, setCurrentZoom])
+ console.log(zoom)
+
+ return (
+
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/index.tsx b/src/app/new/HotspotsMap/index.tsx
index ad4500f..fbf7526 100644
--- a/src/app/new/HotspotsMap/index.tsx
+++ b/src/app/new/HotspotsMap/index.tsx
@@ -4,6 +4,7 @@ import maplibregl from "maplibre-gl"
import "maplibre-gl/dist/maplibre-gl.css"
import { Protocol } from "pmtiles"
+import { gaEvent } from "@/components/GATracker"
import { cellToLatLng, cellsToMultiPolygon, getResolution } from "h3-js"
import { useTheme } from "next-themes"
import {
@@ -23,8 +24,6 @@ import Map, {
Source,
useMap,
} from "react-map-gl"
-import { gaEvent } from "../GATracker"
-import { Attribution } from "./Attribution"
import { NetworkCoverageLayer } from "./NetworkCoverageLayer"
import { mapLayersDark } from "./mapLayersDark"
import { mapLayersLight } from "./mapLayersLight"
@@ -38,6 +37,7 @@ import {
getHexOutlineStyle,
networkLayers,
} from "./utils"
+import { MapZoom } from "./MapZoom"
export function HotspotsMap({ children }: { children: React.ReactNode }) {
const { resolvedTheme } = useTheme()
@@ -164,8 +164,6 @@ export function HotspotsMap({ children }: { children: React.ReactNode }) {
)}
{children}
- {segment !== "stats" && }
-
{segment !== "mobile" && (
)}
@@ -175,39 +173,8 @@ export function HotspotsMap({ children }: { children: React.ReactNode }) {
)}
-
+
)
}
-
-const MapTest = () => {
- const map = useMap()
- const [currentZoom, setCurrentZoom] = useState(map.current?.getZoom())
- const zoom = map.current?.getZoom()
- useEffect(() => {
- const interval = setInterval(() => {
- setCurrentZoom(map.current?.getZoom())
- }, 250)
- return () => clearInterval(interval)
- }, [map, setCurrentZoom])
- console.log(zoom)
-
- return (
-
-
Some random text
-
-
-
- )
-}
diff --git a/src/app/new/HotspotsMap/utils.ts b/src/app/new/HotspotsMap/utils.ts
index 737558c..cac37aa 100644
--- a/src/app/new/HotspotsMap/utils.ts
+++ b/src/app/new/HotspotsMap/utils.ts
@@ -1,6 +1,6 @@
+import { HeliumIotIcon } from "@/components/icons/HeliumIotIcon"
+import { HeliumMobileIcon } from "@/components/icons/HeliumMobileIcon"
import { CoordPair } from "h3-js"
-import { HeliumIotIcon } from "../icons/HeliumIotIcon"
-import { HeliumMobileIcon } from "../icons/HeliumMobileIcon"
export const MIN_MAP_ZOOM = 2
export const MAX_MAP_ZOOM = 14
diff --git a/src/app/new/layout.tsx b/src/app/new/layout.tsx
index a591716..a8ca09f 100644
--- a/src/app/new/layout.tsx
+++ b/src/app/new/layout.tsx
@@ -1,13 +1,13 @@
import { GAScript } from "@/components/GAScript"
import { GATracker } from "@/components/GATracker"
import { Header } from "@/components/Header"
-import { HotspotsMap } from "@/components/HotspotsMap"
import { Providers } from "@/components/Providers"
import "@/styles/tailwind.css"
import "focus-visible"
import Head from "next/head"
import { Suspense } from "react"
import "react-tooltip/dist/react-tooltip.css"
+import { HotspotsMap } from "./HotspotsMap"
import { Nav } from "./Nav/Nav"
export const metadata = {
From 969f1a3cd35bbed5fb13827a8e682b504dc4b772 Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Mon, 15 Jul 2024 12:34:47 -0400
Subject: [PATCH 04/67] Add connection power + settings overlays
---
src/app/new/HotspotsMap/ConnectionPower.tsx | 19 +++++++++++++++++++
src/app/new/HotspotsMap/MapZoom.tsx | 1 -
src/app/new/HotspotsMap/SettingsTrigger.tsx | 12 ++++++++++++
src/app/new/HotspotsMap/index.tsx | 7 +++++--
4 files changed, 36 insertions(+), 3 deletions(-)
create mode 100644 src/app/new/HotspotsMap/ConnectionPower.tsx
create mode 100644 src/app/new/HotspotsMap/SettingsTrigger.tsx
diff --git a/src/app/new/HotspotsMap/ConnectionPower.tsx b/src/app/new/HotspotsMap/ConnectionPower.tsx
new file mode 100644
index 0000000..0fc6363
--- /dev/null
+++ b/src/app/new/HotspotsMap/ConnectionPower.tsx
@@ -0,0 +1,19 @@
+export const ConnectionPower = () => {
+ return (
+
+
Connection Power
+
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/MapZoom.tsx b/src/app/new/HotspotsMap/MapZoom.tsx
index 3e61b1c..8cfac82 100644
--- a/src/app/new/HotspotsMap/MapZoom.tsx
+++ b/src/app/new/HotspotsMap/MapZoom.tsx
@@ -15,7 +15,6 @@ export const MapZoom = () => {
}, 250)
return () => clearInterval(interval)
}, [map, setCurrentZoom])
- console.log(zoom)
return (
diff --git a/src/app/new/HotspotsMap/SettingsTrigger.tsx b/src/app/new/HotspotsMap/SettingsTrigger.tsx
new file mode 100644
index 0000000..43ddb06
--- /dev/null
+++ b/src/app/new/HotspotsMap/SettingsTrigger.tsx
@@ -0,0 +1,12 @@
+import { Cog6ToothIcon } from "@heroicons/react/24/outline"
+import Link from "next/link"
+
+export const SettingsTrigger = () => {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/app/new/HotspotsMap/index.tsx b/src/app/new/HotspotsMap/index.tsx
index fbf7526..8546630 100644
--- a/src/app/new/HotspotsMap/index.tsx
+++ b/src/app/new/HotspotsMap/index.tsx
@@ -22,9 +22,11 @@ import Map, {
MapStyle,
NavigationControl,
Source,
- useMap,
} from "react-map-gl"
+import { ConnectionPower } from "./ConnectionPower"
+import { MapZoom } from "./MapZoom"
import { NetworkCoverageLayer } from "./NetworkCoverageLayer"
+import { SettingsTrigger } from "./SettingsTrigger"
import { mapLayersDark } from "./mapLayersDark"
import { mapLayersLight } from "./mapLayersLight"
import {
@@ -37,7 +39,6 @@ import {
getHexOutlineStyle,
networkLayers,
} from "./utils"
-import { MapZoom } from "./MapZoom"
export function HotspotsMap({ children }: { children: React.ReactNode }) {
const { resolvedTheme } = useTheme()
@@ -174,6 +175,8 @@ export function HotspotsMap({ children }: { children: React.ReactNode }) {
)}
+
+
)
From fead644e560de7d7a362e0d520349bcfa8410b28 Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Mon, 15 Jul 2024 12:39:28 -0400
Subject: [PATCH 05/67] Use heroicons
---
public/negative.png | Bin 155 -> 0 bytes
public/positive.png | Bin 201 -> 0 bytes
src/app/new/HotspotsMap/MapZoom.tsx | 8 +++-----
src/app/new/HotspotsMap/SettingsTrigger.tsx | 2 +-
4 files changed, 4 insertions(+), 6 deletions(-)
delete mode 100644 public/negative.png
delete mode 100644 public/positive.png
diff --git a/public/negative.png b/public/negative.png
deleted file mode 100644
index 5ea3a53bb10ead2167d64d059407f0f8650a2903..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 155
zcmeAS@N?(olHy`uVBq!ia0vp^d_c^^!3HEVy56n=Qk(@Ik;M!Q+`=Ht$S`Y;1W=H%
zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFK)!*ei(^Oy7;|44F)`}!iLpX0_W&WfAd6R
zmpk9Yj*gD~?f1OoS-93Keh^k_ST#-ONxF(l%I^86=g(g;u}{i!ay9Em0l}xg4%F1l
s-M#*-uwvS_bVd6^Ru9&uJqdZpvfV~3sq@mkHlR%mp00i_>zopr0Ce<0%K!iX
diff --git a/src/app/new/HotspotsMap/MapZoom.tsx b/src/app/new/HotspotsMap/MapZoom.tsx
index 8cfac82..91492f0 100644
--- a/src/app/new/HotspotsMap/MapZoom.tsx
+++ b/src/app/new/HotspotsMap/MapZoom.tsx
@@ -1,8 +1,6 @@
-import Image from "next/image"
+import { MinusIcon, PlusIcon } from "@heroicons/react/24/outline"
import { useEffect, useState } from "react"
import { useMap } from "react-map-gl"
-import NegativeIcon from "../../../../public/negative.png"
-import PositiveIcon from "../../../../public/positive.png"
import { MAX_MAP_ZOOM, MIN_MAP_ZOOM } from "./utils"
export const MapZoom = () => {
@@ -23,14 +21,14 @@ export const MapZoom = () => {
disabled={zoom === MAX_MAP_ZOOM}
onClick={() => map.current?.zoomIn()}
>
-
+
)
diff --git a/src/app/new/HotspotsMap/SettingsTrigger.tsx b/src/app/new/HotspotsMap/SettingsTrigger.tsx
index 43ddb06..0768492 100644
--- a/src/app/new/HotspotsMap/SettingsTrigger.tsx
+++ b/src/app/new/HotspotsMap/SettingsTrigger.tsx
@@ -5,7 +5,7 @@ export const SettingsTrigger = () => {
return (
-
+
)
From 8f1a0430f7945c9c994961cd9e8c14d1ccfc176b Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Tue, 16 Jul 2024 11:18:08 -0400
Subject: [PATCH 06/67] Add settings trigger
---
src/app/new/HotspotsMap/SettingsTrigger.tsx | 54 +++++++++++++++++++--
src/app/new/Settings/Settings.tsx | 46 ++++++++++++++++++
2 files changed, 97 insertions(+), 3 deletions(-)
create mode 100644 src/app/new/Settings/Settings.tsx
diff --git a/src/app/new/HotspotsMap/SettingsTrigger.tsx b/src/app/new/HotspotsMap/SettingsTrigger.tsx
index 0768492..29ba201 100644
--- a/src/app/new/HotspotsMap/SettingsTrigger.tsx
+++ b/src/app/new/HotspotsMap/SettingsTrigger.tsx
@@ -1,12 +1,60 @@
+import { Dialog, Transition } from "@headlessui/react"
import { Cog6ToothIcon } from "@heroicons/react/24/outline"
-import Link from "next/link"
+import clsx from "clsx"
+import { Fragment, useCallback, useState } from "react"
+import { Settings } from "../Settings/Settings"
export const SettingsTrigger = () => {
+ const [open, setOpen] = useState(false)
+ const closeSettings = useCallback(() => {
+ setOpen(false)
+ }, [])
+
return (
-
+
+
+
+
)
}
diff --git a/src/app/new/Settings/Settings.tsx b/src/app/new/Settings/Settings.tsx
new file mode 100644
index 0000000..3e1fa46
--- /dev/null
+++ b/src/app/new/Settings/Settings.tsx
@@ -0,0 +1,46 @@
+import { PreferencesProvider } from "@/context/usePreferences"
+import {
+ ArrowTopRightOnSquareIcon,
+ ChevronRightIcon,
+ Cog6ToothIcon,
+ MoonIcon,
+ XMarkIcon,
+} from "@heroicons/react/24/outline"
+
+const Divider = () =>
+
+export const Settings = ({ close }: { close: () => void }) => {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
From ad5d07d145de8d4ad3d0314889921cfc49afebca Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Tue, 16 Jul 2024 11:24:19 -0400
Subject: [PATCH 07/67] Update icons
---
src/app/new/HotspotsMap/MapZoom.tsx | 4 ++--
src/app/new/HotspotsMap/SettingsTrigger.tsx | 2 +-
src/app/new/Nav/Nav.tsx | 10 ++++------
src/app/new/Nav/Search.tsx | 2 +-
src/components/icons/DownCarret.tsx | 15 ---------------
5 files changed, 8 insertions(+), 25 deletions(-)
delete mode 100644 src/components/icons/DownCarret.tsx
diff --git a/src/app/new/HotspotsMap/MapZoom.tsx b/src/app/new/HotspotsMap/MapZoom.tsx
index 91492f0..8c0e0ce 100644
--- a/src/app/new/HotspotsMap/MapZoom.tsx
+++ b/src/app/new/HotspotsMap/MapZoom.tsx
@@ -21,14 +21,14 @@ export const MapZoom = () => {
disabled={zoom === MAX_MAP_ZOOM}
onClick={() => map.current?.zoomIn()}
>
-
+
)
diff --git a/src/app/new/HotspotsMap/SettingsTrigger.tsx b/src/app/new/HotspotsMap/SettingsTrigger.tsx
index 29ba201..e015a8e 100644
--- a/src/app/new/HotspotsMap/SettingsTrigger.tsx
+++ b/src/app/new/HotspotsMap/SettingsTrigger.tsx
@@ -13,7 +13,7 @@ export const SettingsTrigger = () => {
return (
+
-
setOpen(true)}
>
-
+
(
-
-)
From f93a8f16028d842e8d206eea93218fe9e78b7d01 Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Thu, 18 Jul 2024 10:53:40 -0400
Subject: [PATCH 08/67] Update HotspotProviders
---
src/app/new/Settings/HotspotProviders.tsx | 124 ++++++++++++++++++++++
src/app/new/Settings/Settings.tsx | 90 +++++++++++-----
src/context/usePreferences.tsx | 8 +-
3 files changed, 193 insertions(+), 29 deletions(-)
create mode 100644 src/app/new/Settings/HotspotProviders.tsx
diff --git a/src/app/new/Settings/HotspotProviders.tsx b/src/app/new/Settings/HotspotProviders.tsx
new file mode 100644
index 0000000..e9c1da6
--- /dev/null
+++ b/src/app/new/Settings/HotspotProviders.tsx
@@ -0,0 +1,124 @@
+"use client"
+
+import { HotspottyIcon } from "@/components/icons/HotspottyIcon"
+import { MokenIcon } from "@/components/icons/MokenIcon"
+import { RelayIcon } from "@/components/icons/RelayIcon"
+import { usePreferences } from "@/context/usePreferences"
+import clsx from "clsx"
+import { useSearchParams } from "next/navigation"
+import { useMemo } from "react"
+
+export type Provider = {
+ Icon: JSX.Element
+ label: string
+ getUrl: (hotspotId: string) => string
+}
+
+export const PROVIDERS: Provider[] = [
+ {
+ Icon: ,
+ label: "Hotspotty",
+ getUrl: (hotspotId: string) =>
+ `https://app.hotspotty.net/hotspots/${hotspotId}/rewards`,
+ },
+ {
+ Icon: ,
+ label: "Moken",
+ getUrl: (hotspotId: string) =>
+ `https://explorer.moken.io/hotspots/${hotspotId}`,
+ },
+ {
+ Icon: ,
+ label: "Relay",
+ getUrl: (hotspotId: string) =>
+ `https://explorer.relaywireless.com/hotspots/${hotspotId}`,
+ },
+]
+
+export const NO_PREFERENCE: Provider = {
+ Icon: (
+
+ ),
+ label: "Ask me before opening",
+ getUrl: () => "",
+}
+
+const shuffle = (arr: T[]) => {
+ let i = arr.length,
+ j,
+ temp
+ while (--i > 0) {
+ j = Math.floor(Math.random() * (i + 1))
+ temp = arr[j]
+ arr[j] = arr[i]
+ arr[i] = temp
+ }
+ return arr
+}
+
+const PROVIDER_KEY = "provider"
+const DEFAULT_HOTSPOT_KEY =
+ "112Y5Vn5wzsreeyCijSEiBWHJekJPJCELvvm9615GvVGWKfu99Ta"
+
+export const HotspotProviders = () => {
+ const { provider, setProvider } = usePreferences()
+ const searchParams = useSearchParams()
+ const hotspotKey = searchParams.get("redirect") || DEFAULT_HOTSPOT_KEY
+
+ const providers = useMemo(() => [...shuffle(PROVIDERS), NO_PREFERENCE], [])
+
+ return (
+ <>
+
+ Select a Third-Party Explorer
+
+
+ You will be redirected to a external page to Helium if you need more
+ information about the hotspot.
+
+ {providers.map((providerItem) => {
+ const { label, Icon } = providerItem
+ const active = provider?.label === label
+ return (
+
+ )
+ })}
+ >
+ )
+}
diff --git a/src/app/new/Settings/Settings.tsx b/src/app/new/Settings/Settings.tsx
index 3e1fa46..0071610 100644
--- a/src/app/new/Settings/Settings.tsx
+++ b/src/app/new/Settings/Settings.tsx
@@ -1,45 +1,81 @@
+import { ThemeToggle } from "@/app/preferences/components/ThemeToggle"
import { PreferencesProvider } from "@/context/usePreferences"
import {
ArrowTopRightOnSquareIcon,
+ ChevronLeftIcon,
ChevronRightIcon,
Cog6ToothIcon,
MoonIcon,
XMarkIcon,
} from "@heroicons/react/24/outline"
+import { useState } from "react"
+import { HotspotProviders } from "./HotspotProviders"
const Divider = () =>
+const SettingsMain = ({
+ close,
+ setSetting,
+}: {
+ close: () => void
+ setSetting: (settingValue: string) => void
+}) => {
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
+
export const Settings = ({ close }: { close: () => void }) => {
+ const [setting, setSetting] = useState("main")
return (
-
-
-
-
-
+ {setting === "main" && (
+
+ )}
+ {setting !== "main" && (
+ <>
+
+ {setting === "provider" &&
}
+ {setting === "theme" &&
}
+ >
+ )}
)
diff --git a/src/context/usePreferences.tsx b/src/context/usePreferences.tsx
index c894f2e..ad47e9a 100644
--- a/src/context/usePreferences.tsx
+++ b/src/context/usePreferences.tsx
@@ -1,4 +1,5 @@
"use client"
+import { NO_PREFERENCE } from "@/app/new/Settings/HotspotProviders"
import { PROVIDERS, Provider } from "@/app/preferences/components/ProviderList"
import {
PropsWithChildren,
@@ -25,7 +26,10 @@ const VERSION_KEY = "version"
const VERSION = "3"
const getProvider = (providerLabel?: string) => {
- return PROVIDERS.find((provider) => provider.label === providerLabel)
+ return (
+ PROVIDERS.find((provider) => provider.label === providerLabel) ||
+ NO_PREFERENCE
+ )
}
const getLocalValue = (key: string) => {
@@ -43,7 +47,7 @@ export const PreferencesProvider = ({ children }: PropsWithChildren) => {
const [provider, setProvider] = useState(
localVersion === VERSION
? getProvider(getLocalValue(PROVIDER_KEY) || "")
- : undefined
+ : NO_PREFERENCE
)
const setProviderCB = useCallback(
From 6fd59a2dab83a065a78c4bebfa586112fda51ccf Mon Sep 17 00:00:00 2001
From: mbthiery
Date: Thu, 18 Jul 2024 11:17:23 -0400
Subject: [PATCH 09/67] Update ThemeToggle
---
src/app/new/Settings/HotspotProviders.tsx | 6 +-
src/app/new/Settings/Settings.tsx | 2 +-
src/app/new/Settings/ThemeToggle.tsx | 90 +++++++++++++++++++++++
3 files changed, 94 insertions(+), 4 deletions(-)
create mode 100644 src/app/new/Settings/ThemeToggle.tsx
diff --git a/src/app/new/Settings/HotspotProviders.tsx b/src/app/new/Settings/HotspotProviders.tsx
index e9c1da6..0c5e011 100644
--- a/src/app/new/Settings/HotspotProviders.tsx
+++ b/src/app/new/Settings/HotspotProviders.tsx
@@ -81,14 +81,14 @@ export const HotspotProviders = () => {
{providers.map((providerItem) => {
const { label, Icon } = providerItem
- const active = provider?.label === label
+ const isActive = provider?.label === label
return (
-
+
-
-
- {/*
- Wrapping in supsense to avoid pages getting deopted into client-side rendering
- https://nextjs.org/docs/messages/deopted-into-client-rendering
- */}
-
-
-
-
-
- {children}
-
-
diff --git a/src/app/new/Nav/Search.tsx b/src/app/new/Nav/Search.tsx
index 1323a65..34cb555 100644
--- a/src/app/new/Nav/Search.tsx
+++ b/src/app/new/Nav/Search.tsx
@@ -95,7 +95,9 @@ export function Search() {
const handleHotspotSelection = useCallback(
(hotspot: HotspotResult) => {
- router.push(`/hex/${hotspot.location.hex}`)
+ router.push(
+ `/new/hex/${hotspot.location.hex}/hotspots/${hotspot.address}`
+ )
setOpen(false)
},
[router]
diff --git a/src/app/new/hex/[hex]/hotspots/[address]/page.tsx b/src/app/new/hex/[hex]/hotspots/[address]/page.tsx
new file mode 100644
index 0000000..dac54b3
--- /dev/null
+++ b/src/app/new/hex/[hex]/hotspots/[address]/page.tsx
@@ -0,0 +1,51 @@
+import { HexOutlineIcon } from "@/components/icons/HexOutlineIcon"
+import { ArrowLeftIcon, XMarkIcon } from "@heroicons/react/24/outline"
+import animalHash from "angry-purple-tiger"
+import Image from "next/image"
+import Link from "next/link"
+import HotspotWaves from "../../../../../../../public/hotspot-waves.png"
+
+export const metadata = {
+ title: "Helium Hotspots Map - Hotspot Details",
+}
+
+type Params = {
+ hex: string
+ address: string
+}
+
+const Divider = () =>
+
+export default function Page({ params }: { params: Params }) {
+ return (
+
+ )
+}
diff --git a/src/app/new/hex/[hex]/page.tsx b/src/app/new/hex/[hex]/page.tsx
new file mode 100644
index 0000000..f1b3ad4
--- /dev/null
+++ b/src/app/new/hex/[hex]/page.tsx
@@ -0,0 +1,31 @@
+import { HexOutlineIcon } from "@/components/icons/HexOutlineIcon"
+import { XMarkIcon } from "@heroicons/react/24/outline"
+import Link from "next/link"
+
+export const metadata = {
+ title: "Helium Hotspots Map - Hotspot Details",
+}
+
+type Params = {
+ hex: string
+}
+
+const Divider = () =>
+
+export default function Page({ params }: { params: Params }) {
+ return (
+
+ )
+}
diff --git a/src/app/new/layout.tsx b/src/app/new/layout.tsx
deleted file mode 100644
index a8ca09f..0000000
--- a/src/app/new/layout.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { GAScript } from "@/components/GAScript"
-import { GATracker } from "@/components/GATracker"
-import { Header } from "@/components/Header"
-import { Providers } from "@/components/Providers"
-import "@/styles/tailwind.css"
-import "focus-visible"
-import Head from "next/head"
-import { Suspense } from "react"
-import "react-tooltip/dist/react-tooltip.css"
-import { HotspotsMap } from "./HotspotsMap"
-import { Nav } from "./Nav/Nav"
-
-export const metadata = {
- manifest: "/manifest.json",
- themeColor: "#000000",
- icons: {
- icon: [
- {
- url: "/favicon.ico",
- type: "image/x-icon",
- sizes: "64x64 32x32 24x24 16x16",
- },
- {
- url: "/logo192.png",
- type: "image/png",
- sizes: "192x192",
- },
- {
- url: "/logo512.png",
- type: "image/png",
- sizes: "512x512",
- },
- ],
- },
- openGraph: {
- title: "Helium Explorer",
- description:
- "Helium Explorer is an open source network explorer for the Helium network",
- url: "https://explorer.helium.com",
- siteName: "Helium Explorer",
- images: [
- {
- url: "/og.png",
- width: 954,
- height: 696,
- },
- ],
- locale: "en-US",
- type: "website",
- },
-}
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
- return (
-
-