Skip to content

Commit

Permalink
feat(pathfinder): add URL state management for map selection (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbeilin authored Dec 29, 2024
2 parents ea0c118 + a044d2d commit 5e0d1f8
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 10 deletions.
101 changes: 101 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
"@vercel/speed-insights": "^1.1.0",
"axios": "^1.7.9",
"clsx": "^2.1.1",
"nuqs": "^2.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-medium-image-zoom": "^5.2.12",
"react-router-dom": "^7.1.1",
"tailwind-merge": "^2.6.0"
},
"devDependencies": {
Expand Down
65 changes: 57 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState, useEffect } from 'react'
import { useQueryState, parseAsInteger } from 'nuqs'
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'
import { MapSearch } from './components/map-search'
import { MapModal } from './components/map-modal'
import { Navbar } from './components/navbar'
import { Footer } from './components/footer'
import { findPath } from './lib/pathfinding'
import type { MapInfo, PathStep } from './types/map'
import { getMapImageUrl, getMapIconUrl } from './lib/api'
import { getMapImageUrl, getMapIconUrl, getAllMaps } from './lib/api'

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -18,8 +19,53 @@ const queryClient = new QueryClient({
})

function PathfinderApp() {
const [sourceMapId, setSourceMapIdRaw] = useQueryState(
'from',
parseAsInteger.withDefault(-1)
)

const [targetMapId, setTargetMapIdRaw] = useQueryState(
'to',
parseAsInteger.withDefault(-1)
)

const setSourceMapId = (value: number | null) =>
setSourceMapIdRaw(value === null ? -1 : value)

const setTargetMapId = (value: number | null) =>
setTargetMapIdRaw(value === null ? -1 : value)

const effectiveSourceMapId = sourceMapId === -1 ? null : sourceMapId
const effectiveTargetMapId = targetMapId === -1 ? null : targetMapId

const [sourceMap, setSourceMap] = useState<MapInfo | null>(null)
const [targetMap, setTargetMap] = useState<MapInfo | null>(null)

// Fetch all maps once and cache them
const { data: maps } = useQuery({
queryKey: ['maps'],
queryFn: getAllMaps,
staleTime: Infinity // Cache the maps permanently
})

// Update maps when IDs change or when maps data is loaded
useEffect(() => {
if (maps) {
if (effectiveSourceMapId !== null) {
const map = maps.find((m: MapInfo) => m.id === effectiveSourceMapId)
if (map) setSourceMap(map)
} else {
setSourceMap(null)
}

if (effectiveTargetMapId !== null) {
const map = maps.find((m: MapInfo) => m.id === effectiveTargetMapId)
if (map) setTargetMap(map)
} else {
setTargetMap(null)
}
}
}, [sourceMapId, targetMapId, maps])
const [path, setPath] = useState<PathStep[] | null>(null)
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
Expand All @@ -44,9 +90,12 @@ function PathfinderApp() {
}
}

function handleSwapMaps() {
setSourceMap(targetMap)
setTargetMap(sourceMap)
async function handleSwapMaps() {
const tempSourceId = effectiveSourceMapId
await Promise.all([
setSourceMapId(effectiveTargetMapId),
setTargetMapId(tempSourceId)
])
setPath(null)
setError(null)
}
Expand All @@ -62,7 +111,7 @@ function PathfinderApp() {
<div className="space-y-2">
<MapSearch
label="Starting Map"
onSelect={setSourceMap}
onSelect={(map) => setSourceMapId(map.id)}
placeholder="Where are you now?"
/>
{sourceMap && (
Expand Down Expand Up @@ -94,7 +143,7 @@ function PathfinderApp() {
<div className="space-y-2">
<MapSearch
label="Target Map"
onSelect={setTargetMap}
onSelect={(map) => setTargetMapId(map.id)}
placeholder="Where do you want to go?"
/>
{targetMap && (
Expand Down
4 changes: 2 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './styles/globals.css'
import { SpeedInsights } from "@vercel/speed-insights/react"
import { Analytics } from "@vercel/analytics/react"
import { initializePathfinding } from './lib/pathfinding'
import { Router } from './router'

// Initialize pathfinding system
initializePathfinding().catch(error => {
Expand All @@ -24,7 +24,7 @@ initializePathfinding().catch(error => {
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<>
<App />
<Router />
<SpeedInsights />
<Analytics />
</>
Expand Down
18 changes: 18 additions & 0 deletions src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NuqsAdapter } from 'nuqs/adapters/react-router'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import App from './App'

const router = createBrowserRouter([
{
path: '/',
element: <App />
}
])

export function Router() {
return (
<NuqsAdapter>
<RouterProvider router={router} />
</NuqsAdapter>
)
}

0 comments on commit 5e0d1f8

Please sign in to comment.