1- import { useEffect , useMemo , useRef } from 'react' ;
1+ import { useEffect , useMemo , useRef , useState } from 'react' ;
22import { Driver } from '@irdashies/types' ;
33import tracks from './tracks/tracks.json' ;
44import { getColor , getTailwindStyle } from '@irdashies/utils/colors' ;
@@ -44,7 +44,7 @@ const TRACK_DRAWING_HEIGHT = 1080;
4444
4545export const TrackCanvas = ( { trackId, drivers } : TrackProps ) => {
4646 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
47- const animationFrameIdRef = useRef < number | null > ( null ) ;
47+ const [ canvasSize , setCanvasSize ] = useState ( { width : 0 , height : 0 } ) ;
4848
4949 const trackDrawing = ( tracks as unknown as TrackDrawing [ ] ) [ trackId ] ;
5050 const shouldShow = shouldShowTrack ( trackId , trackDrawing ) ;
@@ -158,6 +158,8 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
158158 ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 ) ; // Reset transform
159159 ctx . scale ( dpr , dpr ) ; // Apply DPR scaling
160160 }
161+ // Update state to trigger redraw
162+ setCanvasSize ( { width : rect . width , height : rect . height } ) ;
161163 } ;
162164
163165 // Initial resize
@@ -182,98 +184,80 @@ export const TrackCanvas = ({ trackId, drivers }: TrackProps) => {
182184 const ctx = canvas ?. getContext ( '2d' ) ;
183185 if ( ! canvas || ! ctx || ! path2DObjects ) return ;
184186
185- const draw = ( ) => {
186- const rect = canvas . getBoundingClientRect ( ) ;
187-
188- // Clear the entire canvas
189- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
190-
191- // Calculate scale to fit the 1920x1080 track into the current canvas size
192- const scaleX = rect . width / TRACK_DRAWING_WIDTH ;
193- const scaleY = rect . height / TRACK_DRAWING_HEIGHT ;
194- const scale = Math . min ( scaleX , scaleY ) ; // Maintain aspect ratio
195-
196- // Calculate centering offset
197- const offsetX = ( rect . width - TRACK_DRAWING_WIDTH * scale ) / 2 ;
198- const offsetY = ( rect . height - TRACK_DRAWING_HEIGHT * scale ) / 2 ;
187+ const rect = canvas . getBoundingClientRect ( ) ;
199188
200- // Save context state
201- ctx . save ( ) ;
189+ // Clear the entire canvas
190+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
202191
203- // Apply scaling and centering
204- ctx . translate ( offsetX , offsetY ) ;
205- ctx . scale ( scale , scale ) ;
192+ // Calculate scale to fit the 1920x1080 track into the current canvas size
193+ const scaleX = rect . width / TRACK_DRAWING_WIDTH ;
194+ const scaleY = rect . height / TRACK_DRAWING_HEIGHT ;
195+ const scale = Math . min ( scaleX , scaleY ) ; // Maintain aspect ratio
206196
207- // Shadow
208- ctx . shadowColor = 'black' ;
209- ctx . shadowBlur = 2 ;
210- ctx . shadowOffsetX = 1 ;
211- ctx . shadowOffsetY = 1 ;
197+ // Calculate centering offset
198+ const offsetX = ( rect . width - TRACK_DRAWING_WIDTH * scale ) / 2 ;
199+ const offsetY = ( rect . height - TRACK_DRAWING_HEIGHT * scale ) / 2 ;
212200
213- // Draw track
214- if ( path2DObjects . inside ) {
215- ctx . strokeStyle = 'white' ;
216- ctx . lineWidth = 20 ;
217- ctx . stroke ( path2DObjects . inside ) ;
218- }
201+ // Save context state
202+ ctx . save ( ) ;
219203
220- // Draw start/finish line
221- if ( path2DObjects . startFinish ) {
222- ctx . lineWidth = 10 ;
223- ctx . strokeStyle = getColor ( 'red' ) ;
224- ctx . stroke ( path2DObjects . startFinish ) ;
225- }
204+ // Apply scaling and centering
205+ ctx . translate ( offsetX , offsetY ) ;
206+ ctx . scale ( scale , scale ) ;
226207
227- // Draw turn numbers
228- if ( ENABLE_TURNS ) {
229- trackDrawing . turns ?. forEach ( ( turn ) => {
230- if ( ! turn . content || ! turn . x || ! turn . y ) return ;
231- ctx . textAlign = 'center' ;
232- ctx . textBaseline = 'middle' ;
233- ctx . fillStyle = 'white' ;
234- ctx . font = '2rem sans-serif' ;
235- ctx . fillText ( turn . content , turn . x , turn . y ) ;
236- } ) ;
237- }
208+ // Shadow
209+ ctx . shadowColor = 'black' ;
210+ ctx . shadowBlur = 2 ;
211+ ctx . shadowOffsetX = 1 ;
212+ ctx . shadowOffsetY = 1 ;
238213
239- // Draw drivers
240- Object . values ( calculatePositions )
241- . sort ( ( a , b ) => Number ( a . isPlayer ) - Number ( b . isPlayer ) ) // draws player last to be on top
242- . forEach ( ( { driver, position } ) => {
243- const color = driverColors [ driver . CarIdx ] ;
244- if ( ! color ) return ;
245-
246- ctx . fillStyle = color . fill ;
247- ctx . beginPath ( ) ;
248- ctx . arc ( position . x , position . y , 40 , 0 , 2 * Math . PI ) ;
249- ctx . fill ( ) ;
250- ctx . textAlign = 'center' ;
251- ctx . textBaseline = 'middle' ;
252- ctx . fillStyle = color . text ;
253- ctx . font = '2rem sans-serif' ;
254- ctx . fillText ( driver . CarNumber , position . x , position . y ) ;
255- } ) ;
256-
257- // Restore context state
258- ctx . restore ( ) ;
259- } ;
214+ // Draw track
215+ if ( path2DObjects . inside ) {
216+ ctx . strokeStyle = 'white' ;
217+ ctx . lineWidth = 20 ;
218+ ctx . stroke ( path2DObjects . inside ) ;
219+ }
260220
261- // Simple animation loop - just redraw when needed
262- const animate = ( ) => {
263- draw ( ) ;
264- animationFrameIdRef . current = requestAnimationFrame ( animate ) ;
265- } ;
221+ // Draw start/finish line
222+ if ( path2DObjects . startFinish ) {
223+ ctx . lineWidth = 10 ;
224+ ctx . strokeStyle = getColor ( 'red' ) ;
225+ ctx . stroke ( path2DObjects . startFinish ) ;
226+ }
266227
267- // Start animation
268- animate ( ) ;
228+ // Draw turn numbers
229+ if ( ENABLE_TURNS ) {
230+ trackDrawing . turns ?. forEach ( ( turn ) => {
231+ if ( ! turn . content || ! turn . x || ! turn . y ) return ;
232+ ctx . textAlign = 'center' ;
233+ ctx . textBaseline = 'middle' ;
234+ ctx . fillStyle = 'white' ;
235+ ctx . font = '2rem sans-serif' ;
236+ ctx . fillText ( turn . content , turn . x , turn . y ) ;
237+ } ) ;
238+ }
269239
270- // Cleanup on component unmount
271- return ( ) => {
272- if ( animationFrameIdRef . current ) {
273- cancelAnimationFrame ( animationFrameIdRef . current ) ;
274- }
275- } ;
276- } , [ calculatePositions , path2DObjects , trackDrawing ?. turns , driverColors ] ) ;
240+ // Draw drivers
241+ Object . values ( calculatePositions )
242+ . sort ( ( a , b ) => Number ( a . isPlayer ) - Number ( b . isPlayer ) ) // draws player last to be on top
243+ . forEach ( ( { driver, position } ) => {
244+ const color = driverColors [ driver . CarIdx ] ;
245+ if ( ! color ) return ;
246+
247+ ctx . fillStyle = color . fill ;
248+ ctx . beginPath ( ) ;
249+ ctx . arc ( position . x , position . y , 40 , 0 , 2 * Math . PI ) ;
250+ ctx . fill ( ) ;
251+ ctx . textAlign = 'center' ;
252+ ctx . textBaseline = 'middle' ;
253+ ctx . fillStyle = color . text ;
254+ ctx . font = '2rem sans-serif' ;
255+ ctx . fillText ( driver . CarNumber , position . x , position . y ) ;
256+ } ) ;
257+
258+ // Restore context state
259+ ctx . restore ( ) ;
260+ } , [ calculatePositions , path2DObjects , trackDrawing ?. turns , driverColors , canvasSize ] ) ;
277261
278262 // Development/Storybook mode - show debug info and canvas
279263 if ( import . meta. env ?. DEV || import . meta. env ?. MODE === 'storybook' ) {
0 commit comments