diff --git a/src/app/App.tsx b/src/app/App.tsx index 1438f21..f8dfeef 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -54,6 +54,7 @@ const customRadiusChoice = 'custom-radius'; type RequestStatus = 'idle' | 'loading' | 'success' | 'error'; type NearbyChoice = typeof noNearbyChoice | typeof customChoice | string; type RadiusChoice = string; +type QueryMode = 'generated' | 'custom'; export function App() { const [category, setCategory] = useState(searchPresetCategories[0]); @@ -74,6 +75,8 @@ export function App() { const [namedArea, setNamedArea] = useState(namedAreas[0]); const [results, setResults] = useState([]); const [selectedResultId, setSelectedResultId] = useState(null); + const [queryMode, setQueryMode] = useState('generated'); + const [customQuery, setCustomQuery] = useState(''); const [status, setStatus] = useState('idle'); const [message, setMessage] = useState(null); const activeQueryRef = useRef(null); @@ -170,7 +173,7 @@ export function App() { nearbyRadius, ]); - const queryPreview = useMemo(() => { + const generatedQuery = useMemo(() => { try { return { query: buildOverpassQuery(searchState), @@ -184,34 +187,44 @@ export function App() { } }, [searchState]); + const activeQuery = + queryMode === 'custom' ? customQuery.trim() : generatedQuery.query; + const activeQueryError = + queryMode === 'custom' + ? customQuery.trim() + ? null + : 'Enter a custom Overpass QL query.' + : generatedQuery.error; + const shouldBlockLargeBBox = queryMode === 'generated' && areaMode === 'bbox'; + const canSearch = status !== 'loading' && - Boolean(queryPreview.query) && - !(areaMode === 'bbox' && isBBoxTooLarge(bbox)); + Boolean(activeQuery) && + !(shouldBlockLargeBBox && isBBoxTooLarge(bbox)); async function handleSearch() { - if (!queryPreview.query) { + if (!activeQuery) { setStatus('error'); - setMessage(queryPreview.error); + setMessage(activeQueryError); return; } - if (areaMode === 'bbox' && isBBoxTooLarge(bbox)) { + if (shouldBlockLargeBBox && isBBoxTooLarge(bbox)) { setStatus('error'); setMessage('Zoom in before searching. The current viewport is too large.'); return; } - if (status === 'loading' && activeQueryRef.current === queryPreview.query) { + if (status === 'loading' && activeQueryRef.current === activeQuery) { return; } setStatus('loading'); setMessage(null); - activeQueryRef.current = queryPreview.query; + activeQueryRef.current = activeQuery; try { - const response = await fetchOverpass(queryPreview.query); + const response = await fetchOverpass(activeQuery); const parsedResults = parseOverpassResponse(response); setResults(parsedResults); setSelectedResultId(parsedResults[0]?.id ?? null); @@ -303,16 +316,16 @@ export function App() { } async function handleCopyQuery() { - if (queryPreview.query) { - await navigator.clipboard.writeText(queryPreview.query); + if (activeQuery) { + await navigator.clipboard.writeText(activeQuery); setMessage('Query copied to clipboard.'); } } function handleOpenOverpassTurbo() { - if (queryPreview.query) { + if (activeQuery) { window.open( - `https://overpass-turbo.eu/?Q=${encodeURIComponent(queryPreview.query)}`, + `https://overpass-turbo.eu/?Q=${encodeURIComponent(activeQuery)}`, '_blank', 'noopener,noreferrer', ); @@ -595,23 +608,55 @@ export function App() {
-
-
-            {queryPreview.query || queryPreview.error || 'Choose a preset to generate a query.'}
-          
+
+ + +
+ {queryMode === 'custom' ? ( +