Refactor client Leaflet integration to avoid conflicts
This commit is contained in:
@@ -5,25 +5,9 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>SignalMap | Collaborative Hotspot Tracker</title>
|
<title>SignalMap | Collaborative Hotspot Tracker</title>
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
|
||||||
integrity="sha512-sA+e2u1j7mG2mZHg1F9n3u1kVpWwfvX2gYdEx+kt1/3uzMdGII4XESyqSeX5p1+t0NenE2no0LYh3R1n8z+Gxw=="
|
|
||||||
crossorigin=""
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script
|
|
||||||
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
||||||
integrity="sha512-vPO0bXiC7hlHLLANkRb114F8CbnMD4HzyBbs6k8ZZr68Su2Ce279b9EcRWSavwgJeayQMZT6BdS8wP3r3MJ5iw=="
|
|
||||||
crossorigin=""
|
|
||||||
></script>
|
|
||||||
<script
|
|
||||||
src="https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js"
|
|
||||||
integrity="sha384-24E8AI6UK4SlHe/BUOMwfc9yqD0nV9vsxjMSb7ElJ+PmTWk1FLaeL7XggVzfl8z9"
|
|
||||||
crossorigin=""
|
|
||||||
></script>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
+31
@@ -11,12 +11,15 @@
|
|||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet.heat": "^0.2.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
|
"@types/leaflet": "^1.9.12",
|
||||||
"@types/node": "^24.6.0",
|
"@types/node": "^24.6.0",
|
||||||
"@types/react": "^19.1.16",
|
"@types/react": "^19.1.16",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
@@ -1064,6 +1067,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/geojson": {
|
||||||
|
"version": "7946.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||||
|
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1071,6 +1081,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/leaflet": {
|
||||||
|
"version": "1.9.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
|
||||||
|
"integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.7.1",
|
"version": "24.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz",
|
||||||
@@ -2623,6 +2643,17 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/leaflet": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/leaflet.heat": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ=="
|
||||||
|
},
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
|
|||||||
@@ -13,12 +13,15 @@
|
|||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet.heat": "^0.2.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
|
"@types/leaflet": "^1.9.12",
|
||||||
"@types/node": "^24.6.0",
|
"@types/node": "^24.6.0",
|
||||||
"@types/react": "^19.1.16",
|
"@types/react": "^19.1.16",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
|
|||||||
+81
-66
@@ -1,9 +1,12 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Badge } from '@ui/badge'
|
import L, { type LayerGroup, type LeafletMouseEvent, type Map as LeafletMap } from 'leaflet'
|
||||||
import { Button } from '@ui/button'
|
import 'leaflet.heat'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@ui/card'
|
|
||||||
import { Separator } from '@ui/separator'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { cn, distanceInKm, formatCoordinate, formatRelativeTime } from '@lib/utils'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { cn, distanceInKm, formatCoordinate, formatRelativeTime } from '@/lib/utils'
|
||||||
|
|
||||||
const API_BASE = import.meta.env.VITE_API_BASE ?? 'http://localhost:8000/api/signals'
|
const API_BASE = import.meta.env.VITE_API_BASE ?? 'http://localhost:8000/api/signals'
|
||||||
const VISIBLE_RADIUS_KM = 5
|
const VISIBLE_RADIUS_KM = 5
|
||||||
@@ -36,6 +39,25 @@ type ApiResponse = {
|
|||||||
updatedAt?: string
|
updatedAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HeatPoint = [number, number, number?]
|
||||||
|
|
||||||
|
type LeafletHeatLayer = L.Layer & {
|
||||||
|
setLatLngs(points: HeatPoint[]): LeafletHeatLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeatLayerOptions {
|
||||||
|
radius?: number
|
||||||
|
blur?: number
|
||||||
|
maxZoom?: number
|
||||||
|
max?: number
|
||||||
|
minOpacity?: number
|
||||||
|
gradient?: Record<number, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeafletWithHeat = typeof L & {
|
||||||
|
heatLayer?: (points: HeatPoint[], options?: HeatLayerOptions) => LeafletHeatLayer
|
||||||
|
}
|
||||||
|
|
||||||
interface LatLng {
|
interface LatLng {
|
||||||
lat: number
|
lat: number
|
||||||
lng: number
|
lng: number
|
||||||
@@ -70,12 +92,12 @@ function geolocationErrorMessage(error: GeolocationPositionError): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const mapRef = useRef<any>(null)
|
const mapRef = useRef<LeafletMap | null>(null)
|
||||||
const mapContainerRef = useRef<HTMLDivElement | null>(null)
|
const mapContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const heatLayerRef = useRef<any>(null)
|
const heatLayerRef = useRef<LeafletHeatLayer | null>(null)
|
||||||
const markersLayerRef = useRef<any>(null)
|
const markersLayerRef = useRef<LayerGroup | null>(null)
|
||||||
const zonesLayerRef = useRef<any>(null)
|
const zonesLayerRef = useRef<LayerGroup | null>(null)
|
||||||
const userLayerRef = useRef<any>(null)
|
const userLayerRef = useRef<LayerGroup | null>(null)
|
||||||
const locationWatchIdRef = useRef<number | null>(null)
|
const locationWatchIdRef = useRef<number | null>(null)
|
||||||
const statusRef = useRef<Status>('loading')
|
const statusRef = useRef<Status>('loading')
|
||||||
const initialLoadRef = useRef(true)
|
const initialLoadRef = useRef(true)
|
||||||
@@ -229,15 +251,11 @@ export default function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const leaflet = (window as any).L
|
const leaflet = L as LeafletWithHeat
|
||||||
if (!leaflet) {
|
const container = mapContainerRef.current
|
||||||
setErrorMessage('Leaflet failed to load. Refresh the page to try again.')
|
|
||||||
setStatusSafe('error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const map = leaflet
|
const map = leaflet
|
||||||
.map(mapContainerRef.current, {
|
.map(container, {
|
||||||
worldCopyJump: true,
|
worldCopyJump: true,
|
||||||
minZoom: 2,
|
minZoom: 2,
|
||||||
zoomControl: true,
|
zoomControl: true,
|
||||||
@@ -252,20 +270,21 @@ export default function App() {
|
|||||||
})
|
})
|
||||||
.addTo(map)
|
.addTo(map)
|
||||||
|
|
||||||
const heatLayer = typeof leaflet.heatLayer === 'function'
|
const heatLayer =
|
||||||
? leaflet.heatLayer([], {
|
typeof leaflet.heatLayer === 'function'
|
||||||
radius: 32,
|
? leaflet.heatLayer([], {
|
||||||
blur: 24,
|
radius: 32,
|
||||||
maxZoom: 12,
|
blur: 24,
|
||||||
gradient: {
|
maxZoom: 12,
|
||||||
0.2: '#38bdf8',
|
gradient: {
|
||||||
0.4: '#0ea5e9',
|
0.2: '#38bdf8',
|
||||||
0.6: '#fbbf24',
|
0.4: '#0ea5e9',
|
||||||
0.8: '#f97316',
|
0.6: '#fbbf24',
|
||||||
1.0: '#ef4444',
|
0.8: '#f97316',
|
||||||
},
|
1.0: '#ef4444',
|
||||||
})
|
},
|
||||||
: null
|
})
|
||||||
|
: null
|
||||||
|
|
||||||
const markersLayer = leaflet.layerGroup().addTo(map)
|
const markersLayer = leaflet.layerGroup().addTo(map)
|
||||||
const zonesLayer = leaflet.layerGroup().addTo(map)
|
const zonesLayer = leaflet.layerGroup().addTo(map)
|
||||||
@@ -275,8 +294,8 @@ export default function App() {
|
|||||||
heatLayer.addTo(map)
|
heatLayer.addTo(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = (event: any) => {
|
const onClick = (event: LeafletMouseEvent) => {
|
||||||
const { lat, lng } = event.latlng ?? {}
|
const { lat, lng } = event.latlng
|
||||||
if (typeof lat === 'number' && typeof lng === 'number') {
|
if (typeof lat === 'number' && typeof lng === 'number') {
|
||||||
handleMapClick({ lat, lng })
|
handleMapClick({ lat, lng })
|
||||||
}
|
}
|
||||||
@@ -314,7 +333,7 @@ export default function App() {
|
|||||||
zonesLayerRef.current = null
|
zonesLayerRef.current = null
|
||||||
userLayerRef.current = null
|
userLayerRef.current = null
|
||||||
}
|
}
|
||||||
}, [handleMapClick, setStatusSafe])
|
}, [handleMapClick])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cleanup = initialiseMap()
|
const cleanup = initialiseMap()
|
||||||
@@ -382,8 +401,7 @@ export default function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const heatLayer = heatLayerRef.current
|
const heatLayer = heatLayerRef.current
|
||||||
const leaflet = (window as any).L
|
if (!heatLayer) {
|
||||||
if (!heatLayer || !leaflet) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,14 +411,17 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const maxIntensity = Math.max(...visibleDensity.map((entry) => entry.intensity)) || 1
|
const maxIntensity = Math.max(...visibleDensity.map((entry) => entry.intensity)) || 1
|
||||||
const heatPoints = visibleDensity.map((entry) => [entry.lat, entry.lng, Math.max(0.25, entry.intensity / maxIntensity)])
|
const heatPoints: HeatPoint[] = visibleDensity.map((entry) => [
|
||||||
|
entry.lat,
|
||||||
|
entry.lng,
|
||||||
|
Math.max(0.25, entry.intensity / maxIntensity),
|
||||||
|
])
|
||||||
heatLayer.setLatLngs(heatPoints)
|
heatLayer.setLatLngs(heatPoints)
|
||||||
}, [visibleDensity])
|
}, [visibleDensity])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const layer = zonesLayerRef.current
|
const layer = zonesLayerRef.current
|
||||||
const leaflet = (window as any).L
|
if (!layer) {
|
||||||
if (!layer || !leaflet) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +436,7 @@ export default function App() {
|
|||||||
visibleDangerZones.forEach((zone, index) => {
|
visibleDangerZones.forEach((zone, index) => {
|
||||||
const intensityRatio = maxIntensity ? zone.intensity / maxIntensity : 0.5
|
const intensityRatio = maxIntensity ? zone.intensity / maxIntensity : 0.5
|
||||||
const radius = 400 + intensityRatio * 1600
|
const radius = 400 + intensityRatio * 1600
|
||||||
const circle = leaflet.circle([zone.lat, zone.lng], {
|
const circle = L.circle([zone.lat, zone.lng], {
|
||||||
radius,
|
radius,
|
||||||
color: index === 0 ? '#ef4444' : '#f97316',
|
color: index === 0 ? '#ef4444' : '#f97316',
|
||||||
weight: 2,
|
weight: 2,
|
||||||
@@ -432,8 +453,7 @@ export default function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const layer = markersLayerRef.current
|
const layer = markersLayerRef.current
|
||||||
const leaflet = (window as any).L
|
if (!layer) {
|
||||||
if (!layer || !leaflet) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +469,7 @@ export default function App() {
|
|||||||
|
|
||||||
latestMap.forEach((point) => {
|
latestMap.forEach((point) => {
|
||||||
const isSelf = clientKey && point.userKey === clientKey
|
const isSelf = clientKey && point.userKey === clientKey
|
||||||
const marker = leaflet.circleMarker([point.lat, point.lng], {
|
const marker = L.circleMarker([point.lat, point.lng], {
|
||||||
radius: isSelf ? 10 : 6,
|
radius: isSelf ? 10 : 6,
|
||||||
color: isSelf ? '#38bdf8' : '#94a3b8',
|
color: isSelf ? '#38bdf8' : '#94a3b8',
|
||||||
weight: isSelf ? 3 : 1.5,
|
weight: isSelf ? 3 : 1.5,
|
||||||
@@ -470,8 +490,7 @@ export default function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const layer = userLayerRef.current
|
const layer = userLayerRef.current
|
||||||
const leaflet = (window as any).L
|
if (!layer) {
|
||||||
if (!layer || !leaflet) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,28 +500,24 @@ export default function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leaflet
|
L.circle([userLocation.lat, userLocation.lng], {
|
||||||
.circle([userLocation.lat, userLocation.lng], {
|
radius: VISIBLE_RADIUS_KM * 1000,
|
||||||
radius: VISIBLE_RADIUS_KM * 1000,
|
color: '#38bdf8',
|
||||||
color: '#38bdf8',
|
weight: 1.5,
|
||||||
weight: 1.5,
|
opacity: 0.6,
|
||||||
opacity: 0.6,
|
dashArray: '6 6',
|
||||||
dashArray: '6 6',
|
fillColor: '#38bdf8',
|
||||||
fillColor: '#38bdf8',
|
fillOpacity: 0.05,
|
||||||
fillOpacity: 0.05,
|
}).addTo(layer)
|
||||||
})
|
|
||||||
.addTo(layer)
|
|
||||||
|
|
||||||
leaflet
|
L.circleMarker([userLocation.lat, userLocation.lng], {
|
||||||
.circleMarker([userLocation.lat, userLocation.lng], {
|
radius: 7,
|
||||||
radius: 7,
|
color: '#38bdf8',
|
||||||
color: '#38bdf8',
|
weight: 2,
|
||||||
weight: 2,
|
opacity: 0.9,
|
||||||
opacity: 0.9,
|
fillColor: '#38bdf8',
|
||||||
fillColor: '#38bdf8',
|
fillOpacity: 0.5,
|
||||||
fillOpacity: 0.5,
|
}).addTo(layer)
|
||||||
})
|
|
||||||
.addTo(layer)
|
|
||||||
}, [userLocation])
|
}, [userLocation])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { cva, type VariantProps } from 'class-variance-authority'
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
import { cn } from '@lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from 'react'
|
|||||||
import { Slot } from '@radix-ui/react-slot'
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
import { cva, type VariantProps } from 'class-variance-authority'
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
import { cn } from '@lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-offset-background',
|
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-offset-background',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import { cn } from '@lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import { cn } from '@lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const Separator = React.forwardRef<
|
const Separator = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import 'leaflet/dist/leaflet.css'
|
||||||
|
|
||||||
import '@/index.css'
|
import '@/index.css'
|
||||||
import App from '@/App.tsx'
|
import App from '@/App.tsx'
|
||||||
|
|
||||||
|
|||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
declare module 'leaflet.heat' {
|
||||||
|
import type { Layer } from 'leaflet'
|
||||||
|
|
||||||
|
export interface HeatLayer extends Layer {
|
||||||
|
setLatLngs(latlngs: Array<[number, number, number?]>): HeatLayer
|
||||||
|
addLatLng(latlng: [number, number, number?]): HeatLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HeatLayerOptions {
|
||||||
|
radius?: number
|
||||||
|
blur?: number
|
||||||
|
maxZoom?: number
|
||||||
|
max?: number
|
||||||
|
minOpacity?: number
|
||||||
|
gradient?: Record<number, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function heatLayer(
|
||||||
|
latlngs: Array<[number, number, number?]>,
|
||||||
|
options?: HeatLayerOptions,
|
||||||
|
): HeatLayer
|
||||||
|
}
|
||||||
@@ -9,9 +9,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"]
|
||||||
"@ui/*": ["src/components/ui/*"],
|
|
||||||
"@lib/*": ["src/lib/*"]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
'@ui': fileURLToPath(new URL('./src/components/ui', import.meta.url)),
|
|
||||||
'@lib': fileURLToPath(new URL('./src/lib', import.meta.url)),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user