181181 < h1 data-lang-key ="header "> Recovery Mode</ h1 >
182182 < p data-lang-key ="description "> Your Polygol instance may be corrupted or broken. Use these tools to perform a factory reset.</ p >
183183
184- < div style ="display: grid;gap: 20px;grid-auto-flow: column; grid-template-columns: repeat(2 , minmax(0 , 1fr)); ">
184+ < div style ="display: grid; gap: 20px; grid-template-columns: repeat(auto-fit , minmax(300px , 1fr)); text-align: left ; ">
185185 < div class ="card ">
186186 < span class ="material-symbols-rounded " style ="font-size: 32px; "> info</ span >
187187 < h2 > Information</ h2 >
188- < p > This tool will not affect your device. These actions are irreversible.</ p >
188+ < p > Recovery mode will not affect your device.< br > These actions are irreversible.< br > Back up your data at polygol.github.io/transfer</ p >
189+ </ div >
190+
191+ < div class ="card ">
192+ < span class ="material-symbols-rounded " style ="font-size: 32px; "> build</ span >
193+ < h2 > System data</ h2 >
194+ < p > Fix issues without losing data.</ p >
195+ < div style ="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 15px; ">
196+ < button id ="btn-clear-offline " class ="btn-action "> Clear offline data or cache</ button >
197+ < button id ="btn-del-script " class ="btn-action "> Remove startup script</ button >
198+ < button id ="btn-reset-waves " class ="btn-action "> Reset Waves</ button >
199+ </ div >
200+ </ div >
201+
202+ < div class ="card ">
203+ < span class ="material-symbols-rounded " style ="font-size: 32px; "> account_circle</ span >
204+ < h2 > User data</ h2 >
205+ < p > Fix issues with apps or customization.</ p >
206+ < div style ="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 15px; ">
207+ < button id ="btn-repair-wp " class ="btn-action "> Repair wallpapers</ button >
208+ < button id ="btn-clear-apps " class ="btn-destructive "> Clear app data</ button >
209+ </ div >
189210 </ div >
190211
191212 < div class ="card ">
192213 < span class ="material-symbols-rounded " style ="font-size: 32px; "> delete_forever</ span >
193214 < h2 > Factory Reset</ h2 >
194215 < p > Deletes all your settings, wallpapers and app data and restores them to factory settings.< br > This will delete all current data on this device.</ p >
195216 < div style ="display: flex;gap: 14px; ">
196- < button id ="full-reset " class ="btn-destructive "> Reset </ button >
217+ < button id ="full-reset " class ="btn-destructive "> Erase everything </ button >
197218 </ div >
198219 </ div >
199220 </ div >
@@ -218,15 +239,19 @@ <h2>Factory Reset</h2>
218239 } ) ;
219240 }
220241
242+ const btnClearCache = document . getElementById ( 'btn-clear-offline' ) ;
243+ const btnDelScript = document . getElementById ( 'btn-del-script' ) ;
244+ const btnResetWaves = document . getElementById ( 'btn-reset-waves' ) ;
245+ const btnRepairWp = document . getElementById ( 'btn-repair-wp' ) ;
246+ const btnClearApps = document . getElementById ( 'btn-clear-apps' ) ;
221247 const fullResetBtn = document . getElementById ( 'full-reset' ) ;
222248 const returnHomeBtn = document . getElementById ( 'return-home' ) ;
223249 const statusDiv = document . getElementById ( 'status' ) ;
224- const translatableElements = document . querySelectorAll ( '[data-lang-key]' ) ;
225250
226251 function logStatus ( message , isError = false ) {
227252 console . log ( message ) ;
228- statusDiv . textContent = `> ${ message } ` ;
229- statusDiv . style . color = isError ? '#ef9a9a' : '#a5d6a7 ' ;
253+ statusDiv . textContent = `${ message } ` ;
254+ statusDiv . style . color = isError ? '#ef9a9a' : 'var(--text-color) ' ;
230255 }
231256
232257 // Helper to spawn app and wipe it
@@ -259,82 +284,210 @@ <h2>Factory Reset</h2>
259284 } ) ;
260285 }
261286
262- async function performFullReset ( ) {
263- const confirmationText = 'Confirm that you want to erase all user data. This action is not reversible.' ;
264-
265- if ( ! confirm ( confirmationText ) ) {
266- logStatus ( 'Reset cancelled by user.' ) ;
287+ // Action: Clear Cache & Swap
288+ btnClearCache . addEventListener ( 'click' , async ( ) => {
289+ if ( ! confirm ( 'Clear offline data? This will redownload the latest version of Polygol from the internet.' ) ) return ;
290+ try {
291+ if ( 'serviceWorker' in navigator ) {
292+ const registrations = await navigator . serviceWorker . getRegistrations ( ) ;
293+ await Promise . all ( registrations . map ( reg => reg . unregister ( ) ) ) ;
294+ }
295+ if ( 'caches' in window ) {
296+ const cacheNames = await caches . keys ( ) ;
297+ await Promise . all ( cacheNames . map ( name => caches . delete ( name ) ) ) ;
298+ }
299+ if ( 'indexedDB' in window ) {
300+ await new Promise ( ( resolve ) => {
301+ const req = window . indexedDB . deleteDatabase ( 'PolygolSystemVaultDB' ) ;
302+ req . onsuccess = resolve ;
303+ req . onerror = resolve ; // Ignore errors
304+ req . onblocked = resolve ;
305+ } ) ;
306+ }
307+ logStatus ( 'Offline data cleared.' ) ;
308+ } catch ( e ) {
309+ logStatus ( `Failed to clear cache: ${ e . message } ` , true ) ;
310+ }
311+ } ) ;
312+
313+ // Action: Remove Startup Script
314+ btnDelScript . addEventListener ( 'click' , ( ) => {
315+ if ( localStorage . getItem ( 'customStartupScript' ) ) {
316+ localStorage . removeItem ( 'customStartupScript' ) ;
317+ logStatus ( 'Startup script removed.' ) ;
318+ } else {
319+ logStatus ( 'No startup script found.' ) ;
320+ }
321+ } ) ;
322+
323+ // Action: Reset Waves Remote
324+ btnResetWaves . addEventListener ( 'click' , ( ) => {
325+ localStorage . removeItem ( 'waves_host_config' ) ;
326+ localStorage . removeItem ( 'waves_known_devices' ) ;
327+ localStorage . removeItem ( 'waves_discovery_enabled' ) ;
328+ logStatus ( 'Waves pairings reset.' ) ;
329+ } ) ;
330+
331+ // Action: Repair Wallpapers
332+ btnRepairWp . addEventListener ( 'click' , ( ) => {
333+ const wps = localStorage . getItem ( 'recentWallpapers' ) ;
334+ if ( ! wps ) {
335+ logStatus ( 'No wallpapers found to repair.' ) ;
267336 return ;
268337 }
269338
270339 try {
271- // 0. Wipe External Apps (Must be done before clearing LS which holds the app list)
272- logStatus ( 'Scanning for installed apps...' ) ;
340+ let parsed = JSON . parse ( wps ) ;
341+ if ( ! Array . isArray ( parsed ) ) throw new Error ( "Invalid format" ) ;
342+
343+ const defaults = {
344+ font : 'Inter' , weight : '700' , color : '#ffffff' ,
345+ colorEnabled : false , stackEnabled : false ,
346+ showSeconds : true , showWeather : true ,
347+ alignment : 'center' , clockSize : '0' ,
348+ clockPosX : '50' , clockPosY : '50' ,
349+ roundness : '0' , letterSpacing : '0' , textCase : 'none'
350+ } ;
351+
352+ const defaultEffects = { blur : '0' , brightness : '100' , contrast : '100' , saturate : '100' , hue : '0' , vignette : '0' } ;
353+
354+ parsed . forEach ( wp => {
355+ // 1. Ensure clockStyles object exists
356+ if ( ! wp . clockStyles || typeof wp . clockStyles !== 'object' ) wp . clockStyles = { } ;
357+
358+ // 2. Fill missing basic properties
359+ Object . entries ( defaults ) . forEach ( ( [ key , val ] ) => {
360+ if ( wp . clockStyles [ key ] === undefined || wp . clockStyles [ key ] === null ) {
361+ wp . clockStyles [ key ] = val ;
362+ }
363+ } ) ;
364+
365+ // 3. Heal wallpaper effects structure
366+ if ( ! wp . clockStyles . wallpaperEffects || typeof wp . clockStyles . wallpaperEffects !== 'object' ) {
367+ wp . clockStyles . wallpaperEffects = {
368+ light : { ...defaultEffects } ,
369+ dark : { ...defaultEffects }
370+ } ;
371+ } else {
372+ [ 'light' , 'dark' ] . forEach ( theme => {
373+ if ( ! wp . clockStyles . wallpaperEffects [ theme ] ) {
374+ wp . clockStyles . wallpaperEffects [ theme ] = { ...defaultEffects } ;
375+ } else {
376+ Object . entries ( defaultEffects ) . forEach ( ( [ k , v ] ) => {
377+ if ( wp . clockStyles . wallpaperEffects [ theme ] [ k ] === undefined ) {
378+ wp . clockStyles . wallpaperEffects [ theme ] [ k ] = v ;
379+ }
380+ } ) ;
381+ }
382+ } ) ;
383+ }
384+
385+ // 4. Validate widgetLayout
386+ if ( ! Array . isArray ( wp . widgetLayout ) ) wp . widgetLayout = [ ] ;
387+ } ) ;
388+
389+ localStorage . setItem ( 'recentWallpapers' , JSON . stringify ( parsed ) ) ;
390+ logStatus ( 'Wallpapers repaired.' ) ;
391+ } catch ( e ) {
392+ alert ( 'Could not repair wallpapers, as their structures are critically corrupted. Back up your data and perform a factory reset.' , true ) ;
393+ }
394+ } ) ;
395+
396+ async function secureConfirmation ( actionName , warningText ) {
397+ for ( let i = 1 ; i <= 4 ; i ++ ) {
398+ const confirmed = confirm ( `${ warningText } \n\nStep ${ i } of 5: Press OK to continue.` ) ;
399+ if ( ! confirmed ) return false ;
400+ }
401+
402+ const safetyKeys = [ "RESET" , "CONFIRM" , "DELETE" , "PROCEED" ] ;
403+ const randomKey = safetyKeys [ Math . floor ( Math . random ( ) * safetyKeys . length ) ] ;
404+
405+ const finalInput = prompt (
406+ `Please type the word below to finish ${ actionName } :\n\n` +
407+ `Type this word: ${ randomKey } `
408+ ) ;
409+
410+ return finalInput === randomKey ;
411+ }
412+
413+ // Action: Clear App Data
414+ btnClearApps . addEventListener ( 'click' , async ( ) => {
415+ const warning = 'CLEAR APP DATA: This clears all in-app data and permissions. This cannot be undone.' ;
416+ const isVerified = await secureConfirmation ( "App Data Deletion" , warning ) ;
417+
418+ if ( isVerified ) {
419+ logStatus ( 'Deleting data from apps...' ) ;
273420 const appsJson = localStorage . getItem ( 'userInstalledApps' ) ;
274421 if ( appsJson ) {
275422 const apps = JSON . parse ( appsJson ) ;
276423 const urls = Object . values ( apps ) . map ( a => a . url ) ;
277- logStatus ( `Found ${ urls . length } apps. Wiping external data...` ) ;
278-
279424 for ( const url of urls ) {
280- logStatus ( `Wiping ${ url } ...` ) ;
281- await wipeExternalApp ( url ) ;
425+ try { await wipeExternalApp ( url ) ; } catch ( e ) { }
282426 }
283427 }
284428
285- // 1. Clear Storages
286- logStatus ( 'Clearing System Local Storage & Session Storage...' ) ;
287- localStorage . clear ( ) ;
288- sessionStorage . clear ( ) ;
289- logStatus ( 'Storages cleared.' ) ;
429+ localStorage . removeItem ( 'appUsage' ) ;
430+ localStorage . removeItem ( 'appLastOpened' ) ;
431+ localStorage . removeItem ( 'appPermissions' ) ;
432+ localStorage . removeItem ( 'appUsageHourly' ) ;
433+ localStorage . removeItem ( 'registeredOSKs' ) ;
434+
435+ logStatus ( 'App data cleared.' ) ;
436+ alert ( 'App data cleared.' ) ;
437+ } else {
438+ logStatus ( 'App data deletion cancelled.' ) ;
439+ }
440+ } ) ;
290441
291- // 2. Unregister Service Workers
292- if ( 'serviceWorker' in navigator ) {
293- logStatus ( 'Unregistering service workers...' ) ;
294- const registrations = await navigator . serviceWorker . getRegistrations ( ) ;
295- if ( registrations . length ) {
296- await Promise . all ( registrations . map ( reg => reg . unregister ( ) ) ) ;
297- logStatus ( `${ registrations . length } service worker(s) unregistered.` ) ;
298- } else {
299- logStatus ( 'No active service workers found.' ) ;
442+ async function performFullReset ( ) {
443+ const warning = 'FACTORY RESET: This erases EVERY setting and app. This cannot be undone.' ;
444+ const isVerified = await secureConfirmation ( "Factory Reset" , warning ) ;
445+
446+ if ( isVerified ) {
447+ logStatus ( 'All confirmations accepted. Proceeding with reset...' ) ;
448+ try {
449+ // 0. Wipe External Apps
450+ logStatus ( 'Deleting data from apps...' ) ;
451+ const appsJson = localStorage . getItem ( 'userInstalledApps' ) ;
452+ if ( appsJson ) {
453+ const apps = JSON . parse ( appsJson ) ;
454+ const urls = Object . values ( apps ) . map ( a => a . url ) ;
455+ for ( const url of urls ) {
456+ try { await wipeExternalApp ( url ) ; } catch ( e ) { }
457+ }
300458 }
301- }
302459
303- // 3 . Clear All Caches
304- if ( 'caches' in window ) {
305- logStatus ( 'Purging all caches...' ) ;
306- const cacheNames = await caches . keys ( ) ;
307- if ( cacheNames . length ) {
308- await Promise . all ( cacheNames . map ( name => caches . delete ( name ) ) ) ;
309- logStatus ( ` ${ cacheNames . length } cache(s) purged.` ) ;
310- } else {
311- logStatus ( 'No caches found.' ) ;
460+ // 1 . Clear All Storage
461+ logStatus ( 'Deleting LocalStorage...' ) ;
462+ localStorage . clear ( ) ;
463+ sessionStorage . clear ( ) ;
464+
465+ // 2. Unregister SW & Caches
466+ if ( 'serviceWorker' in navigator ) {
467+ const regs = await navigator . serviceWorker . getRegistrations ( ) ;
468+ await Promise . all ( regs . map ( reg => reg . unregister ( ) ) ) ;
312469 }
313- }
314-
315- // 4. Clear IndexedDB
316- if ( 'indexedDB' in window && window . indexedDB . databases ) {
317- logStatus ( 'Deleting IndexedDB databases...' ) ;
318- const dbs = await window . indexedDB . databases ( ) ;
319- if ( dbs . length ) {
320- await Promise . all ( dbs . map ( db => new Promise ( ( resolve , reject ) => {
321- const req = window . indexedDB . deleteDatabase ( db . name ) ;
322- req . onsuccess = resolve ;
323- req . onerror = reject ;
324- req . onblocked = ( ) => console . warn ( `IndexedDB ${ db . name } is blocked and cannot be deleted right now.` ) ;
470+ if ( 'caches' in window ) {
471+ const names = await caches . keys ( ) ;
472+ await Promise . all ( names . map ( n => caches . delete ( n ) ) ) ;
473+ }
474+
475+ // 3. Delete All Databases
476+ if ( 'indexedDB' in window && window . indexedDB . databases ) {
477+ const dbs = await window . indexedDB . databases ( ) ;
478+ await Promise . all ( dbs . map ( db => new Promise ( ( res ) => {
479+ const req = indexedDB . deleteDatabase ( db . name ) ;
480+ req . onsuccess = res ; req . onerror = res ; req . onblocked = res ;
325481 } ) ) ) ;
326- logStatus ( `${ dbs . length } IndexedDB database(s) deleted.` ) ;
327- } else {
328- logStatus ( 'No IndexedDB databases found.' ) ;
329482 }
330- }
331-
332- logStatus ( 'Full system reset complete. You can now safely return home.' ) ;
333- alert ( 'Recovery complete! Please return to the main page.' ) ;
334483
335- } catch ( err ) {
336- logStatus ( `An error occurred during reset: ${ err . message } ` , true ) ;
337- alert ( 'An error occurred. Check the status log for details.' ) ;
484+ logStatus ( 'Factory reset complete.' , false ) ;
485+ alert ( 'System restored to factory settings.' ) ;
486+ } catch ( err ) {
487+ logStatus ( `Reset failed: ${ err . message } ` , true ) ;
488+ }
489+ } else {
490+ logStatus ( 'Reset cancelled.' ) ;
338491 }
339492 }
340493
0 commit comments