@@ -21,6 +21,7 @@ import type { ChartTimeStamp } from "./types";
2121import  type  {  ReplayTimeStamp  }  from  "./types" ; 
2222import  {  catchError  }  from  "@/lib/utils" ; 
2323import  {  useCheckForUserNavigator  }  from  "@/lib/user-system" ; 
24+ import  {  useEffectOnce  }  from  "react-use" ; 
2425
2526type  RacePracticeProps  =  { 
2627 user ?: User ; 
@@ -33,19 +34,37 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
3334 const  [ totalErrors ,  setTotalErrors ]  =  useState ( 0 ) ; 
3435 const  [ chartTimeStamp ,  setChartTimeStamp ]  =  useState < ChartTimeStamp [ ] > ( [ ] ) ; 
3536 const  [ replayTimeStamp ,  setReplayTimeStamp ]  =  useState < ReplayTimeStamp [ ] > ( [ ] ) ; 
36- 
37+  const  [ windowStart ,  setWindowStart ]  =  useState < number > ( 0 ) ; 
38+  const  [ windowEnd ,  setWindowEnd ]  =  useState < number > ( 0 ) ; 
39+  const  [ scrollPosition ,  setScrollPosition ]  =  useState ( 0 ) ; 
3740 const  inputElement  =  useRef < HTMLInputElement  |  null > ( null ) ; 
3841 const  code  =  snippet . code . trimEnd ( ) ; 
3942 const  router  =  useRouter ( ) ; 
4043 const  isRaceFinished  =  input  ===  code ; 
4144
4245 const  isUserOnAdroid  =  useCheckForUserNavigator ( "android" ) ; 
43- 
46+  //for auto scroll 
47+  const  preElement  =  useRef < HTMLPreElement  |  null > ( null ) ; 
48+  const  scrollUpperLimit  =  7 ; 
49+  const  scrollLowerLimit  =  7  -  1 ;  //1 is deducted because when scrolling backwards, we count from the left side 
50+  const  spanElementWidth  =  10.4 ; 
4451 useEffect ( ( )  =>  { 
4552 localStorage . removeItem ( "chartTimeStamp" ) ; 
4653 if  ( ! inputElement . current )  return ; 
4754 inputElement . current . focus ( ) ; 
4855 } ) ; 
56+  useEffectOnce ( ( )  =>  { 
57+  if  ( preElement . current )  { 
58+  setWindowEnd ( 
59+  Math . floor ( 
60+  preElement . current ?. getBoundingClientRect ( ) . width  /  spanElementWidth 
61+  ) 
62+  ) ; 
63+  } 
64+  } ) ; 
65+  useEffect ( ( )  =>  { 
66+  preElement . current ?. scrollTo ( scrollPosition ,  0 ) ; 
67+  } ,  [ scrollPosition ] ) ; 
4968
5069 useEffect ( ( )  =>  { 
5170 if  ( isRaceFinished )  { 
@@ -63,7 +82,7 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
6382 cpm : calculateCPM ( input . length ,  timeTaken ) , 
6483 time : Date . now ( ) , 
6584 } , 
66-  ] ) , 
85+  ] ) 
6786 ) ; 
6887
6988 localStorage . setItem ( 
@@ -75,7 +94,7 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
7594 textIndicatorPosition : input . length , 
7695 time : Date . now ( ) , 
7796 } , 
78-  ] ) , 
97+  ] ) 
7998 ) ; 
8099
81100 if  ( user )  { 
@@ -120,6 +139,8 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
120139 } 
121140
122141 function  handleKeyboardDownEvent ( e : React . KeyboardEvent < HTMLInputElement > )  { 
142+  //scroll to the cursor position 
143+  preElement . current ?. scrollTo ( scrollPosition ,  0 ) ; 
123144 // For ANDROID. 
124145 // since the enter button on a mobile keyboard/keypad actually 
125146 // returns a e.key of "Enter" onkeydown, we just set a condition for that. 
@@ -131,11 +152,11 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
131152 } 
132153 if  ( input  !==  code . slice ( 0 ,  input . length ) )  return ; 
133154 Enter ( ) ; 
134-  break ; 
155+    break ; 
135156 // this is to delete the characters when "Enter" is pressed; 
136157 case  "Backspace" :
137158 Backspace ( ) ; 
138-  break ; 
159+    break ; 
139160 } 
140161 return ; 
141162 } 
@@ -244,9 +265,40 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
244265 if  ( input . length  ===  0 )  { 
245266 return ; 
246267 } 
247-  setInput ( ( prevInput )  =>  prevInput . slice ( 0 ,  - 1 ) ) ; 
248- 
249268 const  character  =  input . slice ( - 1 ) ; 
269+  setInput ( ( prevInput )  =>  { 
270+  const  updatedInput  =  prevInput . slice ( 0 ,  - 1 ) ; 
271+ 
272+  if  ( character  ===  "\n" )  { 
273+  const  lineNumber  =  updatedInput . split ( "\n" ) . length ; 
274+  const  totalCharactersInput  =  Number ( 
275+  updatedInput . split ( "\n" ) [ lineNumber  -  1 ] ?. length 
276+  ) ; 
277+  console . log ( { 
278+  totalCharactersInput, 
279+  scrollUpperLimit, 
280+  spanElementWidth, 
281+  } ) ; 
282+  setScrollPosition ( 
283+  ( totalCharactersInput  - 
284+  Math . floor ( 
285+  // eslint-disable-next-line @typescript-eslint/no-explicit-any 
286+  ( preElement . current ?. getBoundingClientRect ( ) . width  as  any )  / 
287+  spanElementWidth 
288+  )  + 
289+  scrollUpperLimit  + 
290+  1 )  * 
291+  spanElementWidth 
292+  // spanElement?.current?.getBoundingClientRect().width 
293+  ) ; 
294+  setWindowEnd ( totalCharactersInput  +  scrollUpperLimit  +  1 ) ; 
295+  setWindowStart ( totalCharactersInput  +  scrollUpperLimit  +  1  -  67 ) ; 
296+  }  else  { 
297+  handleScrollNegative ( updatedInput ) ; 
298+  } 
299+  return  updatedInput ; 
300+  } ) ; 
301+ 
250302 if  ( character  !==  " "  &&  character  !==  "\n" )  { 
251303 setChartTimeStamp ( ( prevArray )  =>  prevArray . slice ( 0 ,  - 1 ) ) ; 
252304 } 
@@ -268,6 +320,18 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
268320 indent  +=  " " ; 
269321 i ++ ; 
270322 } 
323+  preElement . current ?. scrollTo ( 0 ,  0 ) ; 
324+ 
325+  setWindowEnd ( 
326+  Math . floor ( 
327+  // eslint-disable-next-line @typescript-eslint/no-explicit-any 
328+  ( preElement ?. current ?. getBoundingClientRect ( ) . width  as  any )  / 
329+  spanElementWidth 
330+  ) 
331+  ) ; 
332+ 
333+  setWindowStart ( 0 ) ; 
334+  setScrollPosition ( 0 ) ; 
271335 setInput ( ( prevInput )  =>  prevInput  +  "\n"  +  indent ) ; 
272336 } 
273337 } 
@@ -276,7 +340,13 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
276340 if  ( e . key  !==  code . slice ( input . length ,  input . length  +  1 ) )  { 
277341 setTotalErrors ( ( prevTotalErrors )  =>  prevTotalErrors  +  1 ) ; 
278342 } 
279-  setInput ( ( prevInput )  =>  prevInput  +  e . key ) ; 
343+  setInput ( ( prevInput )  =>  { 
344+  const  updated  =  prevInput  +  e . key ; 
345+ 
346+  handleScrollPositive ( updated ) ; 
347+  console . log ( {  TotalChar : updated . length  } ) ; 
348+  return  updated ; 
349+  } ) ; 
280350
281351 if  ( e . key  !==  " " )  { 
282352 const  currTime  =  Date . now ( ) ; 
@@ -309,6 +379,30 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
309379 setChartTimeStamp ( [ ] ) ; 
310380 } 
311381
382+  function  handleScrollPositive ( updatedInput : string )  { 
383+  const  lineNumber  =  input . split ( "\n" ) . length ; 
384+  const  totalCharactersInput  =  Number ( 
385+  updatedInput . split ( "\n" ) [ lineNumber  -  1 ] ?. length 
386+  ) ; 
387+  if  ( windowEnd  -  totalCharactersInput  <=  scrollUpperLimit )  { 
388+  setWindowEnd ( ( previousValue )  =>  previousValue  +  1 ) ; 
389+  setWindowStart ( ( prev )  =>  prev  +  1 ) ; 
390+  setScrollPosition ( ( prev )  =>  prev  +  spanElementWidth ) ; 
391+  } 
392+  } 
393+  function  handleScrollNegative ( updatedInput : string )  { 
394+  const  lineNumber  =  input . split ( "\n" ) . length ; 
395+  const  totalCharactersInput  =  Number ( 
396+  updatedInput . split ( "\n" ) [ lineNumber  -  1 ] ?. length 
397+  ) ; 
398+ 
399+  if  ( totalCharactersInput  -  windowStart  <=  scrollLowerLimit )  { 
400+  setWindowStart ( ( previousValue )  =>  previousValue  -  1 ) ; 
401+  setWindowEnd ( ( previousValue )  =>  previousValue  -  1 ) ; 
402+  setScrollPosition ( ( prev )  =>  prev  -  spanElementWidth ) ; 
403+  } 
404+  } 
405+ 
312406 return  ( 
313407 < div 
314408 className = "relative flex flex-col w-[clamp(10rem,95%,50rem)] gap-2 p-4 mx-auto rounded-md lg:p-8 bg-accent" 
@@ -325,7 +419,7 @@ export default function RacePractice({ user, snippet }: RacePracticeProps) {
325419 < Header  user = { user }  snippet = { snippet }  handleRestart = { handleRestart }  /> 
326420 < section  className = "flex" > 
327421 < LineNumbers  code = { code }  currentLineNumber = { input . split ( "\n" ) . length }  /> 
328-  < Code  code = { code }  input = { input }  /> 
422+  < Code  code = { code }  input = { input }  preRef = { preElement }   /> 
329423 < input 
330424 type = "text" 
331425 ref = { inputElement } 
0 commit comments