Download frontend/src/app/page.tsx from GerardCB/GeoQuery: direct link, hf CLI and curl.
- Browser
- Download file 2.85 kB
-
https://huggingface.co/spaces/GerardCB/GeoQuery/resolve/main/frontend/src/app/page.tsx
- Command line
-
hf download hf://spaces/GerardCB/GeoQuery/frontend/src/app/page.tsx
-
curl -L -o page.tsx https://huggingface.co/spaces/GerardCB/GeoQuery/resolve/main/frontend/src/app/page.tsx
2.85 kB
| "use client"; | |
| import dynamic from "next/dynamic"; | |
| import ChatPanel from "@/components/ChatPanel"; | |
| import { useState } from "react"; | |
| import type { MapLayer } from "@/components/MapViewer"; | |
| // Dynamic import for MapViewer (client-side only) | |
| const MapViewer = dynamic(() => import("@/components/MapViewer"), { | |
| ssr: false, | |
| loading: () => <div className="w-full h-full bg-slate-100 flex items-center justify-center">Loading Map...</div> | |
| }); | |
| export default function Home() { | |
| const [layers, setLayers] = useState<MapLayer[]>([]); | |
| const handleMapUpdate = (newGeojson: any) => { | |
| // Unique ID generation | |
| const id = Date.now().toString() + Math.random().toString(36).substr(2, 9); | |
| // Extract metadata from backend response | |
| const name = newGeojson.properties?.layer_name || `Layer ${layers.length + 1}`; | |
| const recommendedStyle = newGeojson.properties?.style || {}; | |
| const choroplethConfig = newGeojson.properties?.choropleth || null; | |
| // Construct new layer with defaults if style is missing | |
| const newLayer: MapLayer = { | |
| id, | |
| name, | |
| data: newGeojson, | |
| visible: true, | |
| style: { | |
| color: recommendedStyle.color || "#6366f1", | |
| fillColor: recommendedStyle.fillColor || recommendedStyle.color || "#6366f1", | |
| fillOpacity: recommendedStyle.fillOpacity || 0.3, | |
| weight: recommendedStyle.weight || 1 | |
| }, | |
| // Pass choropleth config if present | |
| ...(choroplethConfig && { choropleth: choroplethConfig }), | |
| // Point marker config | |
| pointMarker: newGeojson.properties?.pointMarker | |
| }; | |
| // Add to layers (newest on top) | |
| setLayers(prev => [...prev, newLayer]); | |
| }; | |
| const handleLayerUpdate = (id: string, updates: Partial<MapLayer>) => { | |
| setLayers(prev => prev.map(layer => | |
| layer.id === id ? { ...layer, ...updates } : layer | |
| )); | |
| }; | |
| const handleLayerRemove = (id: string) => { | |
| setLayers(prev => prev.filter(layer => layer.id !== id)); | |
| }; | |
| const handleLayerReorder = (fromIndex: number, toIndex: number) => { | |
| setLayers(prev => { | |
| const newLayers = [...prev]; | |
| const [movedLayer] = newLayers.splice(fromIndex, 1); | |
| newLayers.splice(toIndex, 0, movedLayer); | |
| return newLayers; | |
| }); | |
| }; | |
| return ( | |
| <main className="flex h-screen w-full overflow-hidden"> | |
| {/* Left Panel: Chat (30-40% width) */} | |
| <div className="w-[400px] md:w-[450px] lg:w-[30%] shrink-0 h-full relative z-10"> | |
| <ChatPanel onMapUpdate={handleMapUpdate} layers={layers} /> | |
| </div> | |
| {/* Right Panel: Map (Remaining width) */} | |
| <div className="flex-1 h-full relative"> | |
| <MapViewer | |
| layers={layers} | |
| onLayerUpdate={handleLayerUpdate} | |
| onLayerRemove={handleLayerRemove} | |
| onLayerReorder={handleLayerReorder} | |
| /> | |
| </div> | |
| </main> | |
| ); | |
| } | |