@@ -175,9 +175,26 @@ class App extends React.Component {
175175 this . initializeData ( ) . then ( ( ) => {
176176 this . updateMapAndAssociatedData ( ) ;
177177 } ) ;
178+
179+ // Initialize the Broadcast Channel
180+ this . syncChannel = new BroadcastChannel ( "app_playback_sync" ) ;
181+ this . syncChannel . onmessage = this . handleSyncMessage ;
182+
178183 document . addEventListener ( "keydown" , this . handleKeyPress ) ;
179184 }
180185
186+ // Handle incoming signals from the other tab
187+ handleSyncMessage = ( event ) => {
188+ const { action } = event . data ;
189+
190+ // We pass 'true' to these methods to indicate the action came from a sync.
191+ // This tells the method NOT to broadcast it back, preventing infinite loops.
192+ if ( action === "NEXT" ) this . handleNextEvent ( true ) ;
193+ if ( action === "PREVIOUS" ) this . handlePreviousEvent ( true ) ;
194+ if ( action === "TOGGLE_PLAY" ) this . handlePlayStop ( true ) ;
195+ if ( action === "SET_SPEED" ) this . handleSpeedChange ( event . data . speed , true ) ;
196+ } ;
197+
181198 initializeData = async ( ) => {
182199 const datasets = await this . checkUploadedDatasets ( ) ;
183200 if ( ! datasets [ 0 ] ) {
@@ -345,48 +362,79 @@ class App extends React.Component {
345362 }
346363 } ;
347364
348- handleNextEvent = ( ) => {
365+ handleNextEvent = ( fromSync = false ) => {
349366 this . handleRowChange ( "next" ) ;
367+
368+ // Only broadcast if this action originated from this tab
369+ if ( ! fromSync && this . syncChannel ) {
370+ this . syncChannel . postMessage ( { action : "NEXT" } ) ;
371+ }
350372 } ;
351373
352- handlePreviousEvent = ( ) => {
374+ handlePreviousEvent = ( fromSync = false ) => {
353375 this . handleRowChange ( "previous" ) ;
376+
377+ // Only broadcast if this action originated from this tab
378+ if ( ! fromSync && this . syncChannel ) {
379+ this . syncChannel . postMessage ( { action : "PREVIOUS" } ) ;
380+ }
354381 } ;
355382
356- handlePlayStop = ( ) => {
383+ handlePlayStop = ( fromSync = false ) => {
357384 this . setState ( ( prevState ) => {
358385 if ( ! prevState . isPlaying ) {
359386 this . timerID = setInterval ( ( ) => {
360- this . handleNextEvent ( ) ;
387+ this . handleNextEvent ( true ) ;
361388 } , prevState . playSpeed ) ;
362389 } else {
363390 clearInterval ( this . timerID ) ;
364391 }
365392 return { isPlaying : ! prevState . isPlaying } ;
366393 } ) ;
394+
395+ // Only broadcast the play/stop toggle if it originated from this tab
396+ if ( ! fromSync && this . syncChannel ) {
397+ this . syncChannel . postMessage ( { action : "TOGGLE_PLAY" } ) ;
398+ }
367399 } ;
368400
369- handleSpeedChange = ( event ) => {
370- const newSpeed = parseInt ( event . target . value ) ;
401+ handleSpeedChange = ( eventOrSpeed , fromSync = false ) => {
402+ // Determine the new speed depending on if it came from an event or a direct value
403+ const newSpeed =
404+ typeof eventOrSpeed === "object" && eventOrSpeed . target
405+ ? parseInt ( eventOrSpeed . target . value )
406+ : parseInt ( eventOrSpeed ) ;
407+
371408 this . setState ( { playSpeed : newSpeed } , ( ) => {
372409 if ( this . state . isPlaying ) {
373410 clearInterval ( this . timerID ) ;
374411 this . timerID = setInterval ( ( ) => {
375- this . handleNextEvent ( ) ;
412+ this . handleNextEvent ( true ) ;
376413 } , newSpeed ) ;
377414 }
378415 } ) ;
416+
417+ // Broadcast the speed change to other tabs
418+ if ( ! fromSync && this . syncChannel ) {
419+ this . syncChannel . postMessage ( { action : "SET_SPEED" , speed : newSpeed } ) ;
420+ }
379421 } ;
380422
381423 handleKeyPress = ( event ) => {
424+ // Ignore keystrokes if the user is typing in an input or select box
425+ if ( [ "INPUT" , "TEXTAREA" , "SELECT" ] . includes ( event . target . tagName ) ) return ;
426+
382427 if ( [ "ArrowLeft" , "," , "<" ] . includes ( event . key ) ) {
383- this . handlePreviousEvent ( ) ;
428+ this . handlePreviousEvent ( ) ; // Defaults to fromSync=false, so it broadcasts!
384429 } else if ( [ "ArrowRight" , "." , ">" ] . includes ( event . key ) ) {
385- this . handleNextEvent ( ) ;
430+ this . handleNextEvent ( ) ; // Defaults to fromSync=false, so it broadcasts!
386431 }
387432 } ;
388433
389434 componentWillUnmount ( ) {
435+ if ( this . syncChannel ) {
436+ this . syncChannel . close ( ) ;
437+ }
390438 clearInterval ( this . timerID ) ;
391439 document . removeEventListener ( "keydown" , this . handleKeyPress ) ;
392440 if ( this . _outsideClickHandler ) {
@@ -1113,7 +1161,7 @@ class App extends React.Component {
11131161 < button onClick = { this . handleNextEvent } > Next ></ button >
11141162 </ div >
11151163 < div >
1116- < button onClick = { this . handlePlayStop } > { this . state . isPlaying ? "Stop" : "Play" } </ button >
1164+ < button onClick = { ( ) => this . handlePlayStop ( ) } > { this . state . isPlaying ? "Stop" : "Play" } </ button >
11171165 < select value = { this . state . playSpeed } onChange = { this . handleSpeedChange } >
11181166 < option value = "100" > 0.1s</ option >
11191167 < option value = "250" > 0.25s</ option >
0 commit comments