11/* global acquireVsCodeApi */
22"use strict" ;
33
4+ const OVERLAY_MIN_MS = 3000 ; // keep overlay visible at least this long
5+ let overlayShownAt = 0 ;
6+ let overlayTimerId = null
7+
48const vscode = acquireVsCodeApi ( ) ;
59
610/** @typedef {{ id:string, depId:string, label:string, version:string, installedAt: string, lastUsed:string, path?:string } } Item */
@@ -12,6 +16,8 @@ let state = {
1216 filter : "" ,
1317 selected : new Set ( ) , // of item.id
1418 uninstalling : false ,
19+ overlayVisible : false ,
20+ pendingRender : false ,
1521} ;
1622
1723const $ = ( sel , root = document ) => /** @type {HTMLElement|null } */ ( root . querySelector ( sel ) ) ;
@@ -225,11 +231,15 @@ function renderList() {
225231 ` ;
226232}
227233
228- function renderOverlay ( ) {
234+ function renderOverlay ( visible = false ) {
229235 return `
230- <div id="overlay" class="fixed inset-0 hidden items-center justify-center z-50 bg-black/60 backdrop-blur-sm">
231- <div class="flex flex-col items-center gap-4 p-8 rounded-2xl bg-white/90 dark:bg-zinc-900/90 border border-gray-200 dark:border-zinc-800">
232- <div class="h-10 w-10 rounded-full border-4 border-gray-300 dark:border-zinc-700 border-t-transparent animate-spin"></div>
236+ <div id="overlay"
237+ class="fixed inset-0 ${ visible ? '' : 'hidden' } z-50 grid place-items-center bg-black/60 backdrop-blur-sm">
238+ <div class="pointer-events-auto flex flex-col items-center gap-4 p-8 rounded-2xl
239+ bg-white/90 dark:bg-zinc-900/90 border border-gray-200 dark:border-zinc-800 shadow-lg"
240+ role="status" aria-live="polite">
241+ <div class="h-10 w-10 rounded-full border-4 border-gray-300 dark:border-zinc-700 border-t-transparent animate-spin"
242+ aria-hidden="true"></div>
233243 <div id="overlay-text" class="text-sm text-gray-700 dark:text-gray-200">Uninstalling…</div>
234244 </div>
235245 </div>` ;
@@ -252,7 +262,7 @@ function renderFrame() {
252262 </div>
253263 </main>
254264 </div>
255- ${ renderOverlay ( ) }
265+ ${ renderOverlay ( state . overlayVisible ) }
256266 ` ;
257267 bindEvents ( ) ;
258268 updateSelectAllCheckbox ( ) ;
@@ -400,19 +410,51 @@ function beginUninstall(ids) {
400410}
401411
402412function showOverlay ( text ) {
413+ // reset any previous timer
414+ if ( overlayTimerId ) {
415+ clearTimeout ( overlayTimerId ) ;
416+ overlayTimerId = null ;
417+ }
418+
419+ overlayShownAt = Date . now ( ) ;
420+ state . overlayVisible = true ;
403421 const ov = $ ( "#overlay" ) ;
404422 if ( ! ov ) return ;
405- $ ( "#overlay-text" ) . textContent = text || "Working…" ;
423+ const ovT = $ ( "#overlay-text" ) ;
424+ if ( ! ovT ) return ;
425+ ovT . textContent = text || "Working…" ;
426+
406427 ov . classList . remove ( "hidden" ) ;
407428 // prevent interaction underneath
408429 document . body . style . pointerEvents = "none" ;
409430 ov . style . pointerEvents = "auto" ;
410431}
411432
433+ function scheduleHideOverlay ( ) {
434+ const now = Date . now ( ) ;
435+ const remain = Math . max ( OVERLAY_MIN_MS - ( now - overlayShownAt ) , 0 ) ;
436+ console . debug ( `scheduling overlay hide in ${ remain } ms` ) ;
437+ if ( overlayTimerId ) clearTimeout ( overlayTimerId ) ;
438+
439+ const finish = ( ) => {
440+ hideOverlay ( ) ;
441+ overlayTimerId = null ;
442+ if ( state . pendingRender ) {
443+ state . pendingRender = false ;
444+ update ( ) ;
445+ }
446+ } ;
447+
448+ overlayTimerId = remain <= 0
449+ ? setTimeout ( ( ) => requestAnimationFrame ( finish ) , 0 ) // next frame
450+ : setTimeout ( finish , remain ) ;
451+ }
452+
412453function hideOverlay ( ) {
413454 const ov = $ ( "#overlay" ) ;
414455 if ( ! ov ) return ;
415456 ov . classList . add ( "hidden" ) ;
457+ state . overlayVisible = false ;
416458 document . body . style . pointerEvents = "" ;
417459}
418460
@@ -453,13 +495,17 @@ window.addEventListener("message", (event) => {
453495 state . items = state . items . filter ( i => ! removedIds . has ( i . id ) ) ;
454496 for ( const id of removedIds ) state . selected . delete ( id ) ;
455497 state . uninstalling = false ;
456- hideOverlay ( ) ;
457- update ( ) ;
498+
499+ //hideOverlay();
500+ // don't flicker: wait until min visible time has passed
501+ state . pendingRender = true ;
502+ scheduleHideOverlay ( ) ;
458503 break ;
459504 }
460505 case "uninstall:error" : {
461506 state . uninstalling = false ;
462- hideOverlay ( ) ;
507+ //hideOverlay();
508+ scheduleHideOverlay ( ) ;
463509 alert ( msg . error || "Uninstall failed." ) ; // simple; VS Code shows alerts fine
464510 break ;
465511 }
@@ -469,7 +515,11 @@ window.addEventListener("message", (event) => {
469515 state . items = msg . items ;
470516 // prune selection
471517 state . selected = new Set ( [ ...state . selected ] . filter ( id => state . items . some ( i => i . id === id ) ) ) ;
472- update ( ) ;
518+ if ( state . overlayVisible ) {
519+ state . pendingRender = true ; // defer while overlay is up
520+ } else {
521+ update ( ) ;
522+ }
473523 }
474524 break ;
475525 }
0 commit comments