2727 < span class ="brand-subtitle "> Settings HEX Composer</ span >
2828 </ div >
2929 </ div >
30+ < div id ="sw-cache-indicator " class ="sw-cache-indicator hidden " aria-live ="polite " aria-label ="Offline cache status ">
31+ < div class ="sw-spinner " aria-hidden ="true "> </ div >
32+ </ div >
3033 < div class ="app-menu ">
3134 < button id ="app-menu-toggle " class ="menu-toggle " aria-expanded ="false " aria-controls ="app-menu-panel " aria-label ="Open menu ">
3235 < svg class ="menu-toggle-icon " viewBox ="0 0 448 512 " aria-hidden ="true " focusable ="false ">
@@ -199,6 +202,81 @@ <h3 id="import-preview-title">Import preview</h3>
199202 } ) ;
200203 }
201204
205+ function setSwCacheBusy ( isBusy ) {
206+ const indicator = document . getElementById ( 'sw-cache-indicator' ) ;
207+ if ( ! indicator ) {
208+ return ;
209+ }
210+ indicator . classList . toggle ( 'hidden' , ! isBusy ) ;
211+ }
212+
213+ function getRequiredCacheUrls ( ) {
214+ const urls = [
215+ 'settings-meta.json' ,
216+ 'device-version-notes.json'
217+ ] ;
218+ const settingsFiles = Array . isArray ( window . SETTINGS_FILES )
219+ ? window . SETTINGS_FILES . map ( file => `settings/${ file } ` )
220+ : [ ] ;
221+ return Array . from ( new Set ( [ ...urls , ...settingsFiles ] ) ) ;
222+ }
223+
224+ async function getMissingCacheUrls ( urls ) {
225+ if ( ! ( 'caches' in window ) ) {
226+ return [ ] ;
227+ }
228+ const missing = [ ] ;
229+ for ( const url of urls ) {
230+ try {
231+ const match = await caches . match ( url ) ;
232+ if ( ! match ) {
233+ missing . push ( url ) ;
234+ }
235+ } catch ( error ) {
236+ missing . push ( url ) ;
237+ }
238+ }
239+ return missing ;
240+ }
241+
242+ async function warmMissingUrls ( urls ) {
243+ if ( ! navigator . onLine || ! urls . length ) {
244+ return ;
245+ }
246+ await Promise . all (
247+ urls . map ( ( url ) =>
248+ fetch ( url , { cache : 'reload' } ) . catch ( ( ) => null )
249+ )
250+ ) ;
251+ }
252+
253+ let swCacheLoopId = null ;
254+ async function ensureCacheReadyLoop ( ) {
255+ const urls = getRequiredCacheUrls ( ) ;
256+ const missing = await getMissingCacheUrls ( urls ) ;
257+ if ( ! missing . length ) {
258+ setSwCacheBusy ( false ) ;
259+ if ( swCacheLoopId ) {
260+ clearTimeout ( swCacheLoopId ) ;
261+ swCacheLoopId = null ;
262+ }
263+ return ;
264+ }
265+ setSwCacheBusy ( true ) ;
266+ await warmMissingUrls ( missing ) ;
267+ swCacheLoopId = setTimeout ( ensureCacheReadyLoop , 2000 ) ;
268+ }
269+
270+ function initSwCacheIndicator ( ) {
271+ if ( ! ( 'serviceWorker' in navigator ) ) {
272+ return ;
273+ }
274+ setSwCacheBusy ( true ) ;
275+ navigator . serviceWorker . ready
276+ . then ( ( ) => ensureCacheReadyLoop ( ) )
277+ . catch ( ( ) => null ) ;
278+ }
279+
202280 // ---------------------------------------------
203281 // Load and Display Settings
204282 // ---------------------------------------------
@@ -224,6 +302,7 @@ <h3 id="import-preview-title">Import preview</h3>
224302 const COMPOSER_MESSAGING_MAX_LEN = 46 ;
225303
226304 initAppMenu ( ) ;
305+ initSwCacheIndicator ( ) ;
227306
228307 let composerDeviceVersionNotes = null ;
229308 let composerDeviceVersionNotesLoading = null ;
0 commit comments