181181 # media-paths-container .media-icon-label { margin-bottom : 0 ; flex-shrink : 0 ; width : 24px ; height : 24px ; color : var (--secondary-color ); display : inline-flex; align-items : center; justify-content : center; }
182182
183183 # scene-editor-preview-wrapper { position : relative; width : 100% ; max-width : 720px ; margin : 0 auto 25px auto; aspect-ratio : 16 / 9 ; border : 1px solid var (--border-color ); border-radius : 8px ; overflow : hidden; background-color : # 000 ; }
184+ # scene-editor-preview-wrapper .is-loading ::after {
185+ content : 'Loading...' ;
186+ position : absolute;
187+ top : 0 ;
188+ left : 0 ;
189+ width : 100% ;
190+ height : 100% ;
191+ display : flex;
192+ justify-content : center;
193+ align-items : center;
194+ background-color : rgba (0 , 0 , 0 , 0.5 );
195+ color : white;
196+ font-size : 1.2em ;
197+ z-index : 10 ;
198+ }
184199 # scene-editor-preview { position : absolute; top : 0 ; left : 0 ; width : 200% ; height : 200% ; transform : scale (0.5 ); transform-origin : top left; overflow : auto; background-color : var (--bg-color ); color : var (--text-color ); font-family : var (--font-family ); font-size : var (--font-size ); display : flex; justify-content : center; align-items : center; }
185200 # scene-editor-preview .layout-image-as-bg { align-items : flex-end; }
186201 # scene-editor-preview , # scene-editor-preview # preview-player-inner-container { background-size : cover; background-position : center; }
@@ -829,6 +844,8 @@ <h2 id="about-title"></h2>
829844 currentNavView : 'tree' , // 'tree' or 'list'
830845 navViewsDirty : true ,
831846 missingAssetWarnings : new Set ( ) ,
847+ previewLoadId : 0 ,
848+ thumbnailLoadId : 0 ,
832849 player : {
833850 gameState : null ,
834851 typewriter : { intervalId : null , skipListener : null }
@@ -930,7 +947,6 @@ <h2 id="about-title"></h2>
930947
931948 document . getElementById ( 'select-image-btn' ) . addEventListener ( 'click' , ( ) => App . assets . selectFile ( 'images' , 'scene-image' ) ) ;
932949 document . getElementById ( 'select-sound-btn' ) . addEventListener ( 'click' , ( ) => App . assets . selectFile ( 'sounds' , 'scene-sound' ) ) ;
933- App . elements [ 'scene-image' ] . addEventListener ( 'change' , ( e ) => App . ui . updateImageThumbnail ( e . target . value ) ) ;
934950 App . elements [ 'scene-sound' ] . addEventListener ( 'input' , ( e ) => {
935951 const hasSound = ! ! e . target . value ;
936952 const btn = App . elements [ 'toggle-ambience-btn' ] ;
@@ -947,8 +963,11 @@ <h2 id="about-title"></h2>
947963 App . setDirty ( true ) ;
948964 } ) ;
949965
950- [ 'scene-text' , 'scene-image' ] . forEach ( id => {
951- App . elements [ id ] . addEventListener ( 'input' , ( ) => App . editor . renderPreview ( ) ) ;
966+ App . elements [ 'scene-text' ] . addEventListener ( 'input' , ( ) => App . editor . renderPreview ( ) ) ;
967+ App . elements [ 'scene-image' ] . addEventListener ( 'input' , ( e ) => {
968+ // This now correctly triggers updates for BOTH preview and thumbnail on every input change.
969+ App . editor . renderPreview ( ) ;
970+ App . ui . updateImageThumbnail ( e . target . value ) ;
952971 } ) ;
953972 App . elements [ 'choices-container' ] . addEventListener ( 'input' , ( ) => { App . editor . renderPreview ( ) ; App . setDirty ( true ) ; } ) ;
954973
@@ -1308,6 +1327,10 @@ <h2 id="about-title"></h2>
13081327 }
13091328 } ,
13101329 async updateImageThumbnail ( path ) {
1330+ // 1. Cancellation Token Logic
1331+ App . state . thumbnailLoadId ++ ;
1332+ const currentLoadId = App . state . thumbnailLoadId ;
1333+
13111334 const preview = App . elements [ 'image-preview' ] ;
13121335 const icon = preview . querySelector ( '.thumbnail-fullscreen-icon' ) ;
13131336 preview . innerHTML = '' ;
@@ -1318,7 +1341,15 @@ <h2 id="about-title"></h2>
13181341
13191342 if ( ! path ) return ;
13201343 try {
1344+ // Show loading state
1345+ defaultSpan . textContent = 'Loading...' ;
13211346 const url = await App . io . getAssetDataUrl ( path ) ;
1347+
1348+ // 2. Check if the request is still valid
1349+ if ( currentLoadId !== App . state . thumbnailLoadId ) {
1350+ return ; // Abort if a newer request has started
1351+ }
1352+
13221353 if ( url ) {
13231354 preview . innerHTML = '' ;
13241355 if ( icon ) preview . appendChild ( icon ) ;
@@ -1329,7 +1360,9 @@ <h2 id="about-title"></h2>
13291360 defaultSpan . textContent = 'Invalid Path' ;
13301361 }
13311362 } catch ( e ) {
1332- defaultSpan . textContent = 'Invalid Path' ;
1363+ if ( currentLoadId === App . state . thumbnailLoadId ) {
1364+ defaultSpan . textContent = 'Invalid Path' ;
1365+ }
13331366 console . warn ( 'Thumbnail load failed:' , e ) ;
13341367 }
13351368 } ,
@@ -2621,31 +2654,56 @@ <h3 style="margin-top:0;">Loading Project</h3>
26212654 this . updatePreviewImage ( ) ;
26222655 } ,
26232656 async updatePreviewImage ( ) {
2657+ // 1. Cancellation Token Logic
2658+ App . state . previewLoadId ++ ;
2659+ const currentLoadId = App . state . previewLoadId ;
2660+
2661+ const previewWrapper = App . elements [ 'scene-editor-preview-wrapper' ] ;
26242662 const imageEl = document . getElementById ( 'preview-player-image' ) ;
26252663 const imageContainerEl = document . getElementById ( 'preview-player-image-container' ) ;
26262664 const innerContainer = document . getElementById ( 'preview-player-inner-container' ) ;
26272665 const previewContainer = App . elements [ 'scene-editor-preview' ] ;
26282666
2629- const sceneImage = App . elements [ 'scene-image' ] . value ;
2630- const meta = App . state . storyData . meta ;
2631- const styles = meta . styles ;
2667+ // 2. Add loading state feedback
2668+ previewWrapper . classList . add ( 'is-loading' ) ;
26322669
2633- const sceneImageUrl = await App . io . getAssetDataUrl ( sceneImage ) ;
2634- const globalScreenBgUrl = await App . io . getAssetDataUrl ( styles [ '--screen-bg-image' ] ) ;
2635- const containerBgUrl = await App . io . getAssetDataUrl ( styles [ '--container-bg-image' ] ) ;
2670+ try {
2671+ const sceneImage = App . elements [ 'scene-image' ] . value ;
2672+ const meta = App . state . storyData . meta ;
2673+ const styles = meta . styles ;
2674+
2675+ // Await all asset URLs
2676+ const [ sceneImageUrl , globalScreenBgUrl , containerBgUrl ] = await Promise . all ( [
2677+ App . io . getAssetDataUrl ( sceneImage ) ,
2678+ App . io . getAssetDataUrl ( styles [ '--screen-bg-image' ] ) ,
2679+ App . io . getAssetDataUrl ( styles [ '--container-bg-image' ] )
2680+ ] ) ;
2681+
2682+ // 3. Check if the request is still valid before updating the DOM
2683+ if ( currentLoadId !== App . state . previewLoadId ) {
2684+ return ; // Abort if a newer request has started
2685+ }
26362686
2637- innerContainer . style . backgroundImage = containerBgUrl ? `url("${ containerBgUrl } ")` : 'none' ;
2687+ innerContainer . style . backgroundImage = containerBgUrl ? `url("${ containerBgUrl } ")` : 'none' ;
26382688
2639- if ( meta . layout === 'layout-image-as-bg' ) {
2640- imageContainerEl . style . display = 'none' ;
2641- previewContainer . style . backgroundImage = sceneImageUrl ? `url("${ sceneImageUrl } ")` : ( globalScreenBgUrl ? `url("${ globalScreenBgUrl } ")` : 'none' ) ;
2642- } else {
2643- previewContainer . style . backgroundImage = globalScreenBgUrl ? `url("${ globalScreenBgUrl } ")` : 'none' ;
2644- if ( sceneImageUrl ) {
2645- imageEl . src = sceneImageUrl ;
2646- imageContainerEl . style . display = 'block' ;
2647- } else {
2689+ if ( meta . layout === 'layout-image-as-bg' ) {
26482690 imageContainerEl . style . display = 'none' ;
2691+ previewContainer . style . backgroundImage = sceneImageUrl ? `url("${ sceneImageUrl } ")` : ( globalScreenBgUrl ? `url("${ globalScreenBgUrl } ")` : 'none' ) ;
2692+ } else {
2693+ previewContainer . style . backgroundImage = globalScreenBgUrl ? `url("${ globalScreenBgUrl } ")` : 'none' ;
2694+ if ( sceneImageUrl ) {
2695+ imageEl . src = sceneImageUrl ;
2696+ imageContainerEl . style . display = 'block' ;
2697+ } else {
2698+ imageContainerEl . style . display = 'none' ;
2699+ }
2700+ }
2701+ } catch ( error ) {
2702+ console . error ( "Failed to update preview image:" , error ) ;
2703+ } finally {
2704+ // 4. Always remove loading state, but only if this is the latest request
2705+ if ( currentLoadId === App . state . previewLoadId ) {
2706+ previewWrapper . classList . remove ( 'is-loading' ) ;
26492707 }
26502708 }
26512709 } ,
@@ -3117,7 +3175,7 @@ <h3 style="margin-top:0;">Loading Project</h3>
31173175 App . elements [ 'ambience-player' ] . src = '' ; App . elements [ 'music-player' ] . src = '' ;
31183176 if ( App . state . currentSceneId ) App . editor . render ( ) ;
31193177 } ,
3120- async toggleAmbienceSound ( ) {
3178+ async toggleAmbienceSound ( ) {
31213179 const audio = App . elements [ 'ambience-player' ] ;
31223180 const soundPath = App . elements [ 'scene-sound' ] . value ;
31233181 if ( ! soundPath ) return ;
0 commit comments