11import { useEffect } from 'react' ;
2+ import { useStore } from '@nanostores/react' ;
23import tutorialStore from "tutorialkit:store" ;
4+ import { webcontainer } from 'tutorialkit:core' ;
5+ import type { PreviewInfo } from '@tutorialkit/runtime' ;
6+
7+ const ensureRailsServerStarted = async ( preview : PreviewInfo ) => {
8+ if ( preview . ready ) return ;
9+
10+ const terminalConfig = tutorialStore . terminalConfig . get ( )
11+ const terminal = terminalConfig . panels . find ( panel => panel . type === 'terminal' ) ;
12+ if ( ! terminal || ! terminal . process ) return ;
13+
14+ terminal . input ( `bin/rails s\n` ) ;
15+
16+ await new Promise < void > ( ( resolve , reject ) => {
17+ const tid = setTimeout ( ( ) => {
18+ clearInterval ( tick ) ;
19+ reject ( ) ;
20+ } , 10000 ) ;
21+
22+ const tick = setInterval ( ( ) => {
23+ if ( preview . ready ) {
24+ clearInterval ( tick ) ;
25+ clearTimeout ( tid ) ;
26+ resolve ( ) ;
27+ }
28+ } , 200 ) ;
29+ } ) ;
30+ }
331
432export default function RailsPathLinkHandler ( ) {
33+ const previews = useStore ( tutorialStore . previews ) ;
34+
535 useEffect ( ( ) => {
6- function handleClick ( event : MouseEvent ) {
36+ async function handleClick ( event : MouseEvent ) {
737 const target = event . target as HTMLElement ;
838 const link = target . closest ( '.rails-path-link' ) ;
939
@@ -14,6 +44,41 @@ export default function RailsPathLinkHandler() {
1444 if ( railsPath ) {
1545 tutorialStore . setSelectedFile ( `/workspace/store/${ railsPath } ` ) ;
1646 }
47+ return ;
48+ }
49+
50+ if ( target . tagName === 'A' ) {
51+ const linkTarget = target as HTMLAnchorElement ;
52+
53+ if ( linkTarget . href . startsWith ( 'http://localhost:3000' ) ) {
54+ event . preventDefault ( ) ;
55+
56+ const railsPreview = previews . find ( ( pr ) => pr . port === 3000 ) ;
57+
58+ if ( ! railsPreview ) return ;
59+
60+ try {
61+ await ensureRailsServerStarted ( railsPreview )
62+ } catch ( e ) {
63+ console . error ( "failed to start Rails server" , e ) ;
64+ return ;
65+ }
66+
67+ const input = document . querySelector ( 'input[type="text"][name="tutorialkit-preview-navigation"]' ) as HTMLInputElement ;
68+ if ( ! input ) return ;
69+
70+ const newPath = linkTarget . href . replace ( 'http://localhost:3000/' , '' ) ;
71+
72+ input . value = newPath ;
73+ const ev = new KeyboardEvent ( 'keydown' , {
74+ key : 'Enter' ,
75+ code : 'Enter' ,
76+ keyCode : 13 ,
77+ bubbles : true ,
78+ cancelable : true ,
79+ } ) ;
80+ input . dispatchEvent ( ev ) ;
81+ }
1782 }
1883 }
1984
@@ -22,7 +87,7 @@ export default function RailsPathLinkHandler() {
2287 return ( ) => {
2388 document . removeEventListener ( 'click' , handleClick ) ;
2489 } ;
25- } , [ ] ) ;
90+ } , [ previews ] ) ;
2691
2792 return null ;
2893}
0 commit comments