@@ -19,13 +19,16 @@ const getDistance = (c1: Coordinates, c2: Coordinates) => {
1919} ;
2020
2121const App : React . FC = ( ) => {
22+ // Use a ref for tracking state to access it synchronously in geolocation callback
23+ const trackingRef = useRef ( true ) ;
2224 const [ state , setState ] = useState < AppState > ( {
2325 location : { lat : 51.5074 , lng : - 0.1278 } ,
2426 radius : 1000 ,
2527 articles : [ ] ,
2628 mode : SonificationMode . AMBIENT ,
2729 isAudioEnabled : false ,
2830 isLoading : false ,
31+ isTracking : true ,
2932 heading : 0 ,
3033 } ) ;
3134
@@ -52,18 +55,27 @@ const App: React.FC = () => {
5255 }
5356 } , [ ] ) ;
5457
58+ useEffect ( ( ) => {
59+ loadArticles ( state . location . lat , state . location . lng , state . radius ) ;
60+ } , [ ] ) ;
61+
5562 useEffect ( ( ) => {
5663 if ( navigator . geolocation ) {
5764 const watchId = navigator . geolocation . watchPosition ( ( pos ) => {
65+ // CRITICAL: Immediately exit if tracking is disabled.
66+ // This prevents desktop geolocation ticks from overwriting manual typing.
67+ if ( ! trackingRef . current ) return ;
68+
5869 const currentCoords = { lat : pos . coords . latitude , lng : pos . coords . longitude } ;
70+
5971 setState ( prev => {
6072 const dist = lastFetchedLocation . current ? getDistance ( lastFetchedLocation . current , currentCoords ) : Infinity ;
6173 if ( dist > 30 ) {
6274 loadArticles ( currentCoords . lat , currentCoords . lng , prev . radius ) ;
6375 }
76+ setManualInput ( mi => ( { ...mi , lat : currentCoords . lat . toString ( ) , lng : currentCoords . lng . toString ( ) } ) ) ;
6477 return { ...prev , location : currentCoords } ;
6578 } ) ;
66- setManualInput ( prev => ( { ...prev , lat : currentCoords . lat . toString ( ) , lng : currentCoords . lng . toString ( ) } ) ) ;
6779 } , ( err ) => {
6880 console . warn ( "Geolocation watch error or denied." , err ) ;
6981 } , { enableHighAccuracy : true , maximumAge : 5000 , timeout : 10000 } ) ;
@@ -127,11 +139,35 @@ const App: React.FC = () => {
127139 const lng = parseFloat ( manualInput . lng ) ;
128140 const rad = parseInt ( manualInput . radius ) ;
129141 if ( ! isNaN ( lat ) && ! isNaN ( lng ) && ! isNaN ( rad ) ) {
130- setState ( prev => ( { ...prev , location : { lat, lng } , radius : rad } ) ) ;
142+ trackingRef . current = false ;
143+ setState ( prev => ( { ...prev , location : { lat, lng } , radius : rad , isTracking : false } ) ) ;
131144 loadArticles ( lat , lng , rad ) ;
132145 }
133146 } ;
134147
148+ const toggleTracking = ( ) => {
149+ const newTracking = ! state . isTracking ;
150+ trackingRef . current = newTracking ;
151+ setState ( prev => {
152+ if ( newTracking && navigator . geolocation ) {
153+ navigator . geolocation . getCurrentPosition ( ( pos ) => {
154+ const c = { lat : pos . coords . latitude , lng : pos . coords . longitude } ;
155+ setManualInput ( mi => ( { ...mi , lat : c . lat . toString ( ) , lng : c . lng . toString ( ) } ) ) ;
156+ loadArticles ( c . lat , c . lng , state . radius ) ;
157+ } ) ;
158+ }
159+ return { ...prev , isTracking : newTracking } ;
160+ } ) ;
161+ } ;
162+
163+ const handleInputChange = ( key : 'lat' | 'lng' , val : string ) => {
164+ setManualInput ( prev => ( { ...prev , [ key ] : val } ) ) ;
165+ if ( trackingRef . current ) {
166+ trackingRef . current = false ;
167+ setState ( s => ( { ...s , isTracking : false } ) ) ;
168+ }
169+ } ;
170+
135171 return (
136172 < div className = "min-h-screen p-4 md:p-8 flex flex-col items-center bg-black selection:bg-violet-500 selection:text-white font-sans" >
137173 < header className = "max-w-2xl w-full text-center mb-12 space-y-6" >
@@ -146,14 +182,28 @@ const App: React.FC = () => {
146182 < main className = "max-w-6xl w-full grid grid-cols-1 lg:grid-cols-12 gap-8 items-start" >
147183 < div className = "lg:col-span-4 space-y-8 bg-neutral-900/40 p-6 rounded-2xl border border-neutral-800/60 backdrop-blur-md" >
148184 < section className = "space-y-4" >
149- < h2 className = "text-[10px] font-bold uppercase tracking-[0.3em] text-violet-400 font-mono" > Environment</ h2 >
185+ < div className = "flex justify-between items-center mb-2" >
186+ < h2 className = "text-[10px] font-bold uppercase tracking-[0.3em] text-violet-400 font-mono" > Environment</ h2 >
187+ < button
188+ onClick = { toggleTracking }
189+ className = { `flex items-center gap-2 px-2 py-1 rounded-md border text-[9px] font-mono uppercase tracking-wider transition-all ${
190+ state . isTracking
191+ ? 'border-emerald-500/50 text-emerald-400 bg-emerald-500/10'
192+ : 'border-neutral-700 text-neutral-500 bg-black/40'
193+ } `}
194+ >
195+ < div className = { `w-1.5 h-1.5 rounded-full ${ state . isTracking ? 'bg-emerald-400 animate-pulse' : 'bg-neutral-600' } ` } />
196+ { state . isTracking ? 'Auto-Follow On' : 'Follow Disabled' }
197+ </ button >
198+ </ div >
199+
150200 < div className = "grid grid-cols-2 gap-4" >
151201 < div className = "flex flex-col gap-1" >
152202 < label className = "text-[9px] text-neutral-500 font-mono uppercase tracking-wider" > Lat</ label >
153203 < input
154204 type = "text"
155205 value = { manualInput . lat }
156- onChange = { e => setManualInput ( { ... manualInput , lat : e . target . value } ) }
206+ onChange = { e => handleInputChange ( 'lat' , e . target . value ) }
157207 className = "bg-black border border-neutral-800 rounded px-3 py-2 text-xs focus:border-violet-500 outline-none transition-colors font-mono"
158208 />
159209 </ div >
@@ -162,7 +212,7 @@ const App: React.FC = () => {
162212 < input
163213 type = "text"
164214 value = { manualInput . lng }
165- onChange = { e => setManualInput ( { ... manualInput , lng : e . target . value } ) }
215+ onChange = { e => handleInputChange ( 'lng' , e . target . value ) }
166216 className = "bg-black border border-neutral-800 rounded px-3 py-2 text-xs focus:border-violet-500 outline-none transition-colors font-mono"
167217 />
168218 </ div >
0 commit comments