Ver código fonte

Progress bar for config import

Jari 3 meses atrás
pai
commit
7a981b9ceb
1 arquivos alterados com 30 adições e 9 exclusões
  1. 30 9
      docs/index.html

+ 30 - 9
docs/index.html

@@ -1024,38 +1024,52 @@ async function importBackup(file){
   if(!confirm("Restore this backup onto the CONNECTED puck (serial "+(dev.serialNumber||"?")+")?\n\nMake sure this is the TARGET puck, not the one you exported from — the panel only talks to the puck you last connected.\n\nThis OVERWRITES its "+bondedN+" controller pairing(s) and ALL settings, then reboots it.\n\nResult: any controller paired to the original puck will connect to this one with no re-pairing.")) return;
   log("importing onto puck "+(dev.serialNumber||"?"));
   backupBusy=true; await waitIdle();
+  // Blocking progress modal (same surface the firmware updater uses): covers the page so nothing else can be
+  // driven while the import replays every setting onto the puck, then closes on Done / on error.
+  modalOpen("Restore backup");
+  const c=obj.config;
+  // Pre-count every write so the bar tracks real progress: settings (9 base fields), per-type config
+  // (8 each), the lizard bindings, 4 bond slots, and the final commit.
+  const typeN=Array.isArray(c.types)?Math.min(c.types.length,4):0;
+  const lizardN=(Array.isArray(c.lizardMap) && lizardCapable())?Math.min(c.lizardMap.length,LZ_MAX):0;
+  const total=9 + typeN*8 + lizardN + 4 + 1;
+  let step=0, stage="";
+  const tick=()=>modalStage(stage, Math.min(99,Math.floor(step*100/total)));
   try{
     // Capability probe: the connected puck MUST run firmware new enough to answer 0x09 with a 0xA7 frame,
     // otherwise it will silently ignore the 0x0D bond-import below. This is the #1 import failure: the panel
     // is connected to a puck still on old firmware. Catch it loudly instead of "nothing happened".
+    modalStage("Checking puck firmware", null);
     await send([0x09]);
     let probe=null; for(let t=0;t<8 && !probe;t++) probe=await readFrame(0xA7, 2+4*24);
     if(!probe){
       log("import ABORTED — the connected puck does not support import (old firmware).");
-      alert("This puck's firmware is too old to import a backup.\n\nFlash it with the latest OpenPuck build (the same one that produced the export), reconnect, then import again.");
+      modalDone(false, "This puck's firmware is too old to import a backup. Flash it with the latest "
+        +"OpenPuck build (the same one that produced the export), reconnect, then import again.", "Import failed");
       return;
     }
-    const c=obj.config;
     // Every step is a write-then-read transaction (same shape as the proven 0x02 sliders): the firmware acks
     // each with a status blob, which we read back to keep the OUT pipe flowing and naturally pace the writes.
-    const w=async(bytes)=>{ await send(bytes); await readBlob(); };
+    const w=async(bytes)=>{ await send(bytes); await readBlob(); step++; tick(); };
     const sf=async(f,v)=>{ await w([0x02, f, (v|0)&0xff]); };
     // replay every setting (each persists; none reboot). mode is applied by the 0x0E commit below.
+    stage="Restoring settings"; tick();
     await sf(1,c.mDiv); await sf(2,c.mFric); await sf(22,c.rumble); await sf(16,c.persistMode?1:0);
     await sf(17,c.chord[0]); await sf(18,c.chord[1]); await sf(19,c.chord[2]);
     await sf(23,c.swProRate); await sf(24,c.swGyro10);
     // c.types is absent in some very old backups created before per-type paddle/haptic config
     // was added to the panel export; guard so a missing field never crashes the import mid-flight.
-    if(Array.isArray(c.types)){
-      for(let et=0; et<c.types.length && et<4; et++){ const t=c.types[et];
+    if(typeN){
+      for(let et=0; et<typeN; et++){ const t=c.types[et];
         for(let k=0;k<4;k++) await sf(40+et*8+k, t.back[k]);
         await sf(40+et*8+4, t.qam); await sf(40+et*8+5, t.abSwap?1:0);
         await sf(40+et*8+6, t.pad?1:0); await sf(40+et*8+7, t.led); }
-      log("settings replayed ("+(9+c.types.length*8)+" fields)");
+      log("settings replayed ("+(9+typeN*8)+" fields)");
     } else { log("settings replayed (9 fields — no per-type config in this backup)"); }
     // restore the lizard map if the backup includes it (version 2+) and the puck speaks v16+.
     // pre-lizard-map backups simply lack c.lizardMap; skip silently so they still import cleanly.
-    if(Array.isArray(c.lizardMap) && lizardCapable()){
+    if(lizardN){
+      stage="Restoring button map"; tick();
       const map=c.lizardMap.slice(0,LZ_MAX);
       await send([0x13, map.length&0xff]); // begin edit (set count)
       for(let i=0;i<map.length;i++){
@@ -1064,12 +1078,14 @@ async function importBackup(file){
         await send([0x12, i, (b.outType||0)&0xff, ...od.map(x=>(x||0)&0xff),
           t&0xff,(t>>>8)&0xff,(t>>>16)&0xff,(t>>>24)&0xff,
           h&0xff,(h>>>8)&0xff,(h>>>16)&0xff,(h>>>24)&0xff]);
+        step++; tick();
       }
       await send([0x14]); // commit + persist to flash
       await readLizard(); // drain the 0xAA echo so the pipe is clean for bond writes
       log("lizard map restored — "+map.length+" bindings");
     }
     // write each bond slot with its own small command [0x0D][slot][used][24-byte rec]
+    stage="Restoring controller pairings"; tick();
     let n=0;
     for(let s=0;s<4;s++){
       const b=obj.bonds.find(x=>x.slot===s) || {used:false, rec:""};
@@ -1080,8 +1096,13 @@ async function importBackup(file){
     }
     log("wrote "+n+" bond slot(s) — committing + rebooting");
     // commit: persist bonds, apply mode, reboot (no ack — the device reboots)
+    stage="Committing + rebooting"; step++; tick();
     await send([0x0E, (c.mode!==undefined?c.mode:0xFF)&0xff]);
     log("import sent — puck is cloning and rebooting. Reconnect after it returns.");
+    modalDone(true, "Backup restored — the puck is cloning and rebooting. Reconnect after it returns.");
+  } catch(e){
+    log("import FAILED — "+e.message);
+    modalDone(false, e.message+" — the import stopped partway; reconnect and retry.", "Import failed");
   } finally { backupBusy=false; }
 }
 async function setField(field,value){
@@ -1564,12 +1585,12 @@ function modalOpen(title){
   $("#updModal").classList.remove("hide");
   modalStage("Preparing…",null);
 }
-function modalDone(ok,msg){
+function modalDone(ok,msg,failTitle){
   const f=$("#updFill");
   f.classList.remove("pulse");
   f.style.width="100%";
   f.classList.toggle("bad",!ok);
-  $("#updStage").textContent = ok ? "Done" : "Update failed";
+  $("#updStage").textContent = ok ? "Done" : (failTitle||"Update failed");
   $("#updPct").textContent="";
   $("#updDetail").textContent=msg;
   $("#updClose").classList.remove("hide");