tasmota-esp-web-tools 10.0.0 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,7 @@ export declare class EwtLittleFSManager extends LitElement {
17
17
  private _selectedFile;
18
18
  private _flashProgress;
19
19
  private _isFlashing;
20
+ private _flashOperation;
20
21
  connectedCallback(): Promise<void>;
21
22
  disconnectedCallback(): void;
22
23
  private _openFilesystem;
@@ -50,11 +50,13 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
50
50
  this._diskVersion = "";
51
51
  this._busy = false;
52
52
  this._selectedFile = null;
53
- this._flashProgress = 0; // 0-100 for flash progress, -1 when not flashing
53
+ this._flashProgress = 0; // 0-100 for flash progress
54
54
  this._isFlashing = false;
55
+ this._flashOperation = null; // Track operation type
55
56
  }
56
57
  async connectedCallback() {
57
58
  super.connectedCallback();
59
+ this.logger.log("LittleFS Manager: connectedCallback called");
58
60
  await this._openFilesystem();
59
61
  }
60
62
  disconnectedCallback() {
@@ -64,9 +66,21 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
64
66
  async _openFilesystem() {
65
67
  try {
66
68
  this._busy = true;
69
+ this._isFlashing = true;
70
+ this._flashProgress = 0;
71
+ this._flashOperation = "reading";
67
72
  this.logger.log(`Reading LittleFS partition "${this.partition.name}" (${this._formatSize(this.partition.size)})...`);
68
- // Read entire partition
69
- const data = await this.espStub.readFlash(this.partition.offset, this.partition.size);
73
+ if (!this.espStub.IS_STUB) {
74
+ throw new Error("ESP stub loader is not running. Cannot read flash.");
75
+ }
76
+ // Read entire partition with progress callback
77
+ const data = await this.espStub.readFlash(this.partition.offset, this.partition.size, (_packet, progress, totalSize) => {
78
+ const progressPercent = Math.floor((progress / totalSize) * 100);
79
+ this._flashProgress = progressPercent;
80
+ });
81
+ if (data.length === 0) {
82
+ throw new Error("Read 0 bytes from partition");
83
+ }
70
84
  this.logger.log("Mounting LittleFS filesystem...");
71
85
  // Load LittleFS module dynamically
72
86
  const { createLittleFSFromImage, formatDiskVersion } = await loadLittleFS();
@@ -105,10 +119,15 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
105
119
  // Get disk version
106
120
  try {
107
121
  const diskVer = fs.getDiskVersion();
108
- this._diskVersion = formatDiskVersion(diskVer);
122
+ if (diskVer && diskVer !== 0) {
123
+ this._diskVersion = formatDiskVersion(diskVer);
124
+ }
125
+ else {
126
+ this._diskVersion = "Unknown";
127
+ }
109
128
  }
110
129
  catch (e) {
111
- this._diskVersion = "";
130
+ this._diskVersion = "Unknown";
112
131
  }
113
132
  this._refreshFiles();
114
133
  this.logger.log("LittleFS filesystem opened successfully");
@@ -121,11 +140,15 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
121
140
  }
122
141
  finally {
123
142
  this._busy = false;
143
+ this._isFlashing = false;
144
+ this._flashProgress = 0;
145
+ this._flashOperation = null;
124
146
  }
125
147
  }
126
148
  _refreshFiles() {
127
- if (!this._fs)
149
+ if (!this._fs) {
128
150
  return;
151
+ }
129
152
  try {
130
153
  // Calculate usage
131
154
  const allFiles = this._fs.list("/");
@@ -150,6 +173,7 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
150
173
  }
151
174
  catch (e) {
152
175
  this.logger.error(`Failed to refresh file list: ${e.message || e}`);
176
+ this._files = [];
153
177
  }
154
178
  }
155
179
  _estimateUsage(entries) {
@@ -343,6 +367,7 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
343
367
  this._busy = true;
344
368
  this._isFlashing = true;
345
369
  this._flashProgress = 0;
370
+ this._flashOperation = "writing"; // Set operation type
346
371
  this.logger.log("Creating LittleFS image...");
347
372
  const image = this._fs.toImage();
348
373
  this.logger.log(`Image created: ${this._formatSize(image.length)}`);
@@ -357,7 +382,6 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
357
382
  await this.espStub.flashData(imageBuffer, (bytesWritten, totalBytes) => {
358
383
  const percent = Math.floor((bytesWritten / totalBytes) * 100);
359
384
  this._flashProgress = percent;
360
- this.logger.log(`Writing: ${percent}%`);
361
385
  }, this.partition.offset);
362
386
  this.logger.log(`✓ LittleFS successfully written to flash!`);
363
387
  this.logger.log(`To use the new filesystem, reset your device.`);
@@ -369,6 +393,7 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
369
393
  this._busy = false;
370
394
  this._isFlashing = false;
371
395
  this._flashProgress = 0;
396
+ this._flashOperation = null;
372
397
  }
373
398
  }
374
399
  _cleanup() {
@@ -412,7 +437,11 @@ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
412
437
  <div class="usage-text">
413
438
  ${this._isFlashing
414
439
  ? html `<span class="flash-status">
415
- Writing to flash: ${this._flashProgress}%
440
+
441
+ ${this._flashOperation === "reading"
442
+ ? "Reading from"
443
+ : "Writing to"}
444
+ flash: ${this._flashProgress}%
416
445
  </span>`
417
446
  : html `<span
418
447
  >Used: ${this._formatSize(this._usage.usedBytes)} /
@@ -813,6 +842,9 @@ __decorate([
813
842
  __decorate([
814
843
  state()
815
844
  ], EwtLittleFSManager.prototype, "_isFlashing", void 0);
845
+ __decorate([
846
+ state()
847
+ ], EwtLittleFSManager.prototype, "_flashOperation", void 0);
816
848
  EwtLittleFSManager = __decorate([
817
849
  customElement("ewt-littlefs-manager")
818
850
  ], EwtLittleFSManager);
@@ -867,6 +867,17 @@ export class EwtInstallDialog extends LitElement {
867
867
  this.logger.log("Running stub...");
868
868
  const espStub = await esploader.runStub();
869
869
  this._espStub = espStub;
870
+ // Set baudrate for reading flash (use user-selected baudrate if available)
871
+ if (this.baudRate) {
872
+ this.logger.log(`Setting baudrate to ${this.baudRate} for flash reading...`);
873
+ try {
874
+ await espStub.setBaudrate(this.baudRate);
875
+ this.logger.log(`Baudrate set to ${this.baudRate}`);
876
+ }
877
+ catch (baudErr) {
878
+ this.logger.log(`Failed to set baudrate: ${baudErr.message}, continuing with default`);
879
+ }
880
+ }
870
881
  // Add a small delay after stub is running
871
882
  await sleep(500);
872
883
  this.logger.log("Reading flash data...");
@@ -1 +1 @@
1
- const e=async t=>{let n;import("./install-dialog-CVebVk1R.js");try{n=await navigator.serial.requestPort()}catch(n){return"NotFoundError"===n.name?void import("./index-t2Vsxqjj.js").then(n=>n.openNoPortPickedDialog(()=>e(t))):void alert(`Error: ${n.message}`)}if(!n)return;try{await n.open({baudRate:115200})}catch(e){return void alert(e.message)}const o=document.createElement("ewt-install-dialog");o.port=n,o.manifestPath=t.manifest||t.getAttribute("manifest"),o.overrides=t.overrides,o.firmwareFile=t.firmwareFile;const r=t.getAttribute("baud-rate");if(r){const e=parseInt(r,10);isNaN(e)||(o.baudRate=e)}else void 0!==t.baudRate&&(o.baudRate=t.baudRate);o.addEventListener("closed",()=>{n.close()},{once:!0}),document.body.appendChild(o)};class t extends HTMLElement{connectedCallback(){if(this.renderRoot)return;if(this.renderRoot=this.attachShadow({mode:"open"}),!t.isSupported||!t.isAllowed)return this.toggleAttribute("install-unsupported",!0),void(this.renderRoot.innerHTML=t.isAllowed?"<slot name='unsupported'>Your browser does not support installing things on ESP devices. Use Google Chrome or Microsoft Edge.</slot>":"<slot name='not-allowed'>You can only install ESP devices on HTTPS websites or on the localhost.</slot>");this.toggleAttribute("install-supported",!0);const n=document.createElement("slot");n.addEventListener("click",async t=>{t.preventDefault(),e(this)}),n.name="activate";const o=document.createElement("button");if(o.innerText="CONNECT",n.append(o),"adoptedStyleSheets"in Document.prototype&&"replaceSync"in CSSStyleSheet.prototype){const e=new CSSStyleSheet;e.replaceSync(t.style),this.renderRoot.adoptedStyleSheets=[e]}else{const e=document.createElement("style");e.innerText=t.style,this.renderRoot.append(e)}this.renderRoot.append(n)}}t.isSupported="serial"in navigator,t.isAllowed=window.isSecureContext,t.style='\n button {\n position: relative;\n cursor: pointer;\n font-size: 14px;\n padding: 8px 28px;\n color: var(--esp-tools-button-text-color, #fff);\n background-color: var(--esp-tools-button-color, #03a9f4);\n border: none;\n border-radius: 4px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);\n }\n button::before {\n content: " ";\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n opacity: 0.2;\n border-radius: 4px;\n }\n button:hover {\n box-shadow: 0 4px 8px 0 rgba(0,0,0,.14), 0 1px 7px 0 rgba(0,0,0,.12), 0 3px 1px -1px rgba(0,0,0,.2);\n }\n button:hover::before {\n background-color: rgba(255,255,255,.8);\n }\n button:focus {\n outline: none;\n }\n button:focus::before {\n background-color: white;\n }\n button:active::before {\n background-color: grey;\n }\n :host([active]) button {\n color: rgba(0, 0, 0, 0.38);\n background-color: rgba(0, 0, 0, 0.12);\n box-shadow: none;\n cursor: unset;\n pointer-events: none;\n }\n improv-wifi-launch-button {\n display: block;\n margin-top: 16px;\n }\n .hidden {\n display: none;\n }',customElements.define("esp-web-install-button",t);
1
+ const e=async t=>{let n;import("./install-dialog-Bt_kBJJW.js");try{n=await navigator.serial.requestPort()}catch(n){return"NotFoundError"===n.name?void import("./index-t2Vsxqjj.js").then(n=>n.openNoPortPickedDialog(()=>e(t))):void alert(`Error: ${n.message}`)}if(!n)return;try{await n.open({baudRate:115200})}catch(e){return void alert(e.message)}const o=document.createElement("ewt-install-dialog");o.port=n,o.manifestPath=t.manifest||t.getAttribute("manifest"),o.overrides=t.overrides,o.firmwareFile=t.firmwareFile;const r=t.getAttribute("baud-rate");if(r){const e=parseInt(r,10);isNaN(e)||(o.baudRate=e)}else void 0!==t.baudRate&&(o.baudRate=t.baudRate);o.addEventListener("closed",()=>{n.close()},{once:!0}),document.body.appendChild(o)};class t extends HTMLElement{connectedCallback(){if(this.renderRoot)return;if(this.renderRoot=this.attachShadow({mode:"open"}),!t.isSupported||!t.isAllowed)return this.toggleAttribute("install-unsupported",!0),void(this.renderRoot.innerHTML=t.isAllowed?"<slot name='unsupported'>Your browser does not support installing things on ESP devices. Use Google Chrome or Microsoft Edge.</slot>":"<slot name='not-allowed'>You can only install ESP devices on HTTPS websites or on the localhost.</slot>");this.toggleAttribute("install-supported",!0);const n=document.createElement("slot");n.addEventListener("click",async t=>{t.preventDefault(),e(this)}),n.name="activate";const o=document.createElement("button");if(o.innerText="CONNECT",n.append(o),"adoptedStyleSheets"in Document.prototype&&"replaceSync"in CSSStyleSheet.prototype){const e=new CSSStyleSheet;e.replaceSync(t.style),this.renderRoot.adoptedStyleSheets=[e]}else{const e=document.createElement("style");e.innerText=t.style,this.renderRoot.append(e)}this.renderRoot.append(n)}}t.isSupported="serial"in navigator,t.isAllowed=window.isSecureContext,t.style='\n button {\n position: relative;\n cursor: pointer;\n font-size: 14px;\n padding: 8px 28px;\n color: var(--esp-tools-button-text-color, #fff);\n background-color: var(--esp-tools-button-color, #03a9f4);\n border: none;\n border-radius: 4px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);\n }\n button::before {\n content: " ";\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n opacity: 0.2;\n border-radius: 4px;\n }\n button:hover {\n box-shadow: 0 4px 8px 0 rgba(0,0,0,.14), 0 1px 7px 0 rgba(0,0,0,.12), 0 3px 1px -1px rgba(0,0,0,.2);\n }\n button:hover::before {\n background-color: rgba(255,255,255,.8);\n }\n button:focus {\n outline: none;\n }\n button:focus::before {\n background-color: white;\n }\n button:active::before {\n background-color: grey;\n }\n :host([active]) button {\n color: rgba(0, 0, 0, 0.38);\n background-color: rgba(0, 0, 0, 0.12);\n box-shadow: none;\n cursor: unset;\n pointer-events: none;\n }\n improv-wifi-launch-button {\n display: block;\n margin-top: 16px;\n }\n .hidden {\n display: none;\n }',customElements.define("esp-web-install-button",t);
@@ -301,7 +301,7 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
301
301
  .mdc-floating-label {
302
302
  line-height: 1.15em;
303
303
  }
304
- `],customElements.define("ewt-select",li);class ci extends Te{}ci.styles=[Ae],customElements.define("ewt-list-item",ci);let hi=null,mi=null;let pi=class extends x{constructor(){super(...arguments),this.logger=console,this._currentPath="/",this._files=[],this._fs=null,this._blockSize=4096,this._usage={capacityBytes:0,usedBytes:0,freeBytes:0},this._diskVersion="",this._busy=!1,this._selectedFile=null,this._flashProgress=0,this._isFlashing=!1}async connectedCallback(){super.connectedCallback(),await this._openFilesystem()}disconnectedCallback(){super.disconnectedCallback(),this._cleanup()}async _openFilesystem(){try{this._busy=!0,this.logger.log(`Reading LittleFS partition "${this.partition.name}" (${this._formatSize(this.partition.size)})...`);const e=await this.espStub.readFlash(this.partition.offset,this.partition.size);this.logger.log("Mounting LittleFS filesystem...");const{createLittleFSFromImage:t,formatDiskVersion:i}=await async function(){if(mi)return mi;if(!hi){const e=new URL(import.meta.url),t=e.href.substring(0,e.href.lastIndexOf("/")+1);hi=t+"wasm/littlefs/"}try{const e=hi+"index.js";return console.log("[LittleFS] Loading module from:",e),mi=await import(e),mi}catch(e){console.error("[LittleFS] Failed to load from calculated path:",hi,e);try{return mi=await import("./wasm/littlefs/index.js"),mi}catch(t){throw console.error("[LittleFS] Fallback import also failed:",t),new Error(`Failed to load LittleFS module: ${e}`)}}}(),n=[4096,2048,1024,512];let o=null,r=0;for(const i of n)try{const n={blockSize:i,blockCount:Math.floor(this.partition.size/i)};hi&&(n.wasmURL=new URL("littlefs.wasm",hi).href),o=await t(e,n),o.list("/"),r=i,this.logger.log(`Successfully mounted LittleFS with block size ${i}`);break}catch(e){o=null}if(!o)throw new Error("Failed to mount LittleFS with any block size");this._fs=o,this._blockSize=r;try{const e=o.getDiskVersion();this._diskVersion=i(e)}catch(e){this._diskVersion=""}this._refreshFiles(),this.logger.log("LittleFS filesystem opened successfully")}catch(e){this.logger.error(`Failed to open LittleFS: ${e.message||e}`),this.onClose&&this.onClose()}finally{this._busy=!1}}_refreshFiles(){if(this._fs)try{const e=this._fs.list("/"),t=this._estimateUsage(e),i=this.partition.size;this._usage={capacityBytes:i,usedBytes:t,freeBytes:i-t};const n=this._fs.list(this._currentPath);n.sort((e,t)=>"dir"===e.type&&"dir"!==t.type?-1:"dir"!==e.type&&"dir"===t.type?1:e.path.localeCompare(t.path)),this._files=n}catch(e){this.logger.error(`Failed to refresh file list: ${e.message||e}`)}}_estimateUsage(e){const t=this._blockSize||4096;let i=2*t;for(const n of e||[])if("dir"===n.type)i+=t;else{i+=Math.max(1,Math.ceil((n.size||0)/t))*t+t}return i}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}_navigateUp(){if("/"===this._currentPath||!this._currentPath)return;const e=this._currentPath.split("/").filter(Boolean);e.pop(),this._currentPath="/"+e.join("/"),"/"===this._currentPath||this._currentPath.endsWith("/")||(this._currentPath+="/"),this._refreshFiles()}_navigateTo(e){this._currentPath=e,this._refreshFiles()}async _uploadFile(){if(this._fs&&this._selectedFile)try{this._busy=!0,this.logger.log(`Uploading file "${this._selectedFile.name}"...`);const e=await this._selectedFile.arrayBuffer(),t=new Uint8Array(e);let i=this._currentPath;i.endsWith("/")||(i+="/"),i+=this._selectedFile.name;const n=i.split("/").filter(Boolean);if(n.length>1){let e="";for(let t=0;t<n.length-1;t++){e+=`/${n[t]}`;try{this._fs.mkdir(e)}catch(e){}}}"function"==typeof this._fs.writeFile?this._fs.writeFile(i,t):"function"==typeof this._fs.addFile&&this._fs.addFile(i,t);const o=this._fs.readFile(i);this.logger.log(`✓ File written: ${o.length} bytes at ${i}`);const r=this._selectedFile.name;this._selectedFile=null,this._refreshFiles(),this.logger.log(`File "${r}" uploaded successfully`)}catch(e){this.logger.error(`Failed to upload file: ${e.message||e}`)}finally{this._busy=!1}}_createFolder(){if(!this._fs)return;const e=prompt("Enter directory name:");if(e&&e.trim())try{let t=this._currentPath;t.endsWith("/")||(t+="/"),t+=e.trim(),this._fs.mkdir(t),this._refreshFiles(),this.logger.log(`Directory "${e}" created successfully`)}catch(e){this.logger.error(`Failed to create directory: ${e.message||e}`)}}async _downloadFile(e){if(this._fs)try{this.logger.log(`Downloading file "${e}"...`);const t=this._fs.readFile(e),i=e.split("/").filter(Boolean).pop()||"file.bin",n=new Blob([t],{type:"application/octet-stream"}),o=URL.createObjectURL(n),r=document.createElement("a");r.href=o,r.download=i,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(o),this.logger.log(`File "${i}" downloaded successfully`)}catch(e){this.logger.error(`Failed to download file: ${e.message||e}`)}}_deleteFile(e,t){if(!this._fs)return;const i=e.split("/").filter(Boolean).pop()||e;if(confirm(`Delete ${t} "${i}"?`))try{"dir"===t?this._fs.delete(e,{recursive:!0}):this._fs.deleteFile(e),this._refreshFiles(),this.logger.log(`${"dir"===t?"Directory":"File"} "${i}" deleted successfully`)}catch(e){this.logger.error(`Failed to delete ${t}: ${e.message||e}`)}}async _backupImage(){if(this._fs)try{this.logger.log("Creating LittleFS backup image...");const e=this._fs.toImage(),t=`${this.partition.name}_littlefs_backup.bin`,i=new Blob([e],{type:"application/octet-stream"}),n=URL.createObjectURL(i),o=document.createElement("a");o.href=n,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n),this.logger.log(`LittleFS backup saved as "${t}"`)}catch(e){this.logger.error(`Failed to backup LittleFS: ${e.message||e}`)}}async _writeToFlash(){if(!this._fs)return;if(confirm(`Write modified LittleFS to flash?\n\nPartition: ${this.partition.name}\nOffset: 0x${this.partition.offset.toString(16)}\nSize: ${this._formatSize(this.partition.size)}\n\nThis will overwrite the current filesystem on the device!`))try{this._busy=!0,this._isFlashing=!0,this._flashProgress=0,this.logger.log("Creating LittleFS image...");const e=this._fs.toImage();if(this.logger.log(`Image created: ${this._formatSize(e.length)}`),e.length>this.partition.size)return void this.logger.error(`Image size (${this._formatSize(e.length)}) exceeds partition size (${this._formatSize(this.partition.size)})`);this.logger.log(`Writing ${this._formatSize(e.length)} to partition "${this.partition.name}" at 0x${this.partition.offset.toString(16)}...`);const t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);await this.espStub.flashData(t,(e,t)=>{const i=Math.floor(e/t*100);this._flashProgress=i,this.logger.log(`Writing: ${i}%`)},this.partition.offset),this.logger.log("✓ LittleFS successfully written to flash!"),this.logger.log("To use the new filesystem, reset your device.")}catch(e){this.logger.error(`Failed to write LittleFS to flash: ${e.message||e}`)}finally{this._busy=!1,this._isFlashing=!1,this._flashProgress=0}}_cleanup(){this._fs&&(this._fs=null)}_handleFileSelect(e){var t;const i=e.target;this._selectedFile=(null===(t=i.files)||void 0===t?void 0:t[0])||null}render(){const e=Math.round(this._usage.usedBytes/this._usage.capacityBytes*100);return h`
304
+ `],customElements.define("ewt-select",li);class ci extends Te{}ci.styles=[Ae],customElements.define("ewt-list-item",ci);let hi=null,mi=null;let pi=class extends x{constructor(){super(...arguments),this.logger=console,this._currentPath="/",this._files=[],this._fs=null,this._blockSize=4096,this._usage={capacityBytes:0,usedBytes:0,freeBytes:0},this._diskVersion="",this._busy=!1,this._selectedFile=null,this._flashProgress=0,this._isFlashing=!1,this._flashOperation=null}async connectedCallback(){super.connectedCallback(),this.logger.log("LittleFS Manager: connectedCallback called"),await this._openFilesystem()}disconnectedCallback(){super.disconnectedCallback(),this._cleanup()}async _openFilesystem(){try{if(this._busy=!0,this._isFlashing=!0,this._flashProgress=0,this._flashOperation="reading",this.logger.log(`Reading LittleFS partition "${this.partition.name}" (${this._formatSize(this.partition.size)})...`),!this.espStub.IS_STUB)throw new Error("ESP stub loader is not running. Cannot read flash.");const e=await this.espStub.readFlash(this.partition.offset,this.partition.size,(e,t,i)=>{const n=Math.floor(t/i*100);this._flashProgress=n});if(0===e.length)throw new Error("Read 0 bytes from partition");this.logger.log("Mounting LittleFS filesystem...");const{createLittleFSFromImage:t,formatDiskVersion:i}=await async function(){if(mi)return mi;if(!hi){const e=new URL(import.meta.url),t=e.href.substring(0,e.href.lastIndexOf("/")+1);hi=t+"wasm/littlefs/"}try{const e=hi+"index.js";return console.log("[LittleFS] Loading module from:",e),mi=await import(e),mi}catch(e){console.error("[LittleFS] Failed to load from calculated path:",hi,e);try{return mi=await import("./wasm/littlefs/index.js"),mi}catch(t){throw console.error("[LittleFS] Fallback import also failed:",t),new Error(`Failed to load LittleFS module: ${e}`)}}}(),n=[4096,2048,1024,512];let o=null,r=0;for(const i of n)try{const n={blockSize:i,blockCount:Math.floor(this.partition.size/i)};hi&&(n.wasmURL=new URL("littlefs.wasm",hi).href),o=await t(e,n),o.list("/"),r=i,this.logger.log(`Successfully mounted LittleFS with block size ${i}`);break}catch(e){o=null}if(!o)throw new Error("Failed to mount LittleFS with any block size");this._fs=o,this._blockSize=r;try{const e=o.getDiskVersion();this._diskVersion=e&&0!==e?i(e):"Unknown"}catch(e){this._diskVersion="Unknown"}this._refreshFiles(),this.logger.log("LittleFS filesystem opened successfully")}catch(e){this.logger.error(`Failed to open LittleFS: ${e.message||e}`),this.onClose&&this.onClose()}finally{this._busy=!1,this._isFlashing=!1,this._flashProgress=0,this._flashOperation=null}}_refreshFiles(){if(this._fs)try{const e=this._fs.list("/"),t=this._estimateUsage(e),i=this.partition.size;this._usage={capacityBytes:i,usedBytes:t,freeBytes:i-t};const n=this._fs.list(this._currentPath);n.sort((e,t)=>"dir"===e.type&&"dir"!==t.type?-1:"dir"!==e.type&&"dir"===t.type?1:e.path.localeCompare(t.path)),this._files=n}catch(e){this.logger.error(`Failed to refresh file list: ${e.message||e}`),this._files=[]}}_estimateUsage(e){const t=this._blockSize||4096;let i=2*t;for(const n of e||[])if("dir"===n.type)i+=t;else{i+=Math.max(1,Math.ceil((n.size||0)/t))*t+t}return i}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}_navigateUp(){if("/"===this._currentPath||!this._currentPath)return;const e=this._currentPath.split("/").filter(Boolean);e.pop(),this._currentPath="/"+e.join("/"),"/"===this._currentPath||this._currentPath.endsWith("/")||(this._currentPath+="/"),this._refreshFiles()}_navigateTo(e){this._currentPath=e,this._refreshFiles()}async _uploadFile(){if(this._fs&&this._selectedFile)try{this._busy=!0,this.logger.log(`Uploading file "${this._selectedFile.name}"...`);const e=await this._selectedFile.arrayBuffer(),t=new Uint8Array(e);let i=this._currentPath;i.endsWith("/")||(i+="/"),i+=this._selectedFile.name;const n=i.split("/").filter(Boolean);if(n.length>1){let e="";for(let t=0;t<n.length-1;t++){e+=`/${n[t]}`;try{this._fs.mkdir(e)}catch(e){}}}"function"==typeof this._fs.writeFile?this._fs.writeFile(i,t):"function"==typeof this._fs.addFile&&this._fs.addFile(i,t);const o=this._fs.readFile(i);this.logger.log(`✓ File written: ${o.length} bytes at ${i}`);const r=this._selectedFile.name;this._selectedFile=null,this._refreshFiles(),this.logger.log(`File "${r}" uploaded successfully`)}catch(e){this.logger.error(`Failed to upload file: ${e.message||e}`)}finally{this._busy=!1}}_createFolder(){if(!this._fs)return;const e=prompt("Enter directory name:");if(e&&e.trim())try{let t=this._currentPath;t.endsWith("/")||(t+="/"),t+=e.trim(),this._fs.mkdir(t),this._refreshFiles(),this.logger.log(`Directory "${e}" created successfully`)}catch(e){this.logger.error(`Failed to create directory: ${e.message||e}`)}}async _downloadFile(e){if(this._fs)try{this.logger.log(`Downloading file "${e}"...`);const t=this._fs.readFile(e),i=e.split("/").filter(Boolean).pop()||"file.bin",n=new Blob([t],{type:"application/octet-stream"}),o=URL.createObjectURL(n),r=document.createElement("a");r.href=o,r.download=i,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(o),this.logger.log(`File "${i}" downloaded successfully`)}catch(e){this.logger.error(`Failed to download file: ${e.message||e}`)}}_deleteFile(e,t){if(!this._fs)return;const i=e.split("/").filter(Boolean).pop()||e;if(confirm(`Delete ${t} "${i}"?`))try{"dir"===t?this._fs.delete(e,{recursive:!0}):this._fs.deleteFile(e),this._refreshFiles(),this.logger.log(`${"dir"===t?"Directory":"File"} "${i}" deleted successfully`)}catch(e){this.logger.error(`Failed to delete ${t}: ${e.message||e}`)}}async _backupImage(){if(this._fs)try{this.logger.log("Creating LittleFS backup image...");const e=this._fs.toImage(),t=`${this.partition.name}_littlefs_backup.bin`,i=new Blob([e],{type:"application/octet-stream"}),n=URL.createObjectURL(i),o=document.createElement("a");o.href=n,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n),this.logger.log(`LittleFS backup saved as "${t}"`)}catch(e){this.logger.error(`Failed to backup LittleFS: ${e.message||e}`)}}async _writeToFlash(){if(!this._fs)return;if(confirm(`Write modified LittleFS to flash?\n\nPartition: ${this.partition.name}\nOffset: 0x${this.partition.offset.toString(16)}\nSize: ${this._formatSize(this.partition.size)}\n\nThis will overwrite the current filesystem on the device!`))try{this._busy=!0,this._isFlashing=!0,this._flashProgress=0,this._flashOperation="writing",this.logger.log("Creating LittleFS image...");const e=this._fs.toImage();if(this.logger.log(`Image created: ${this._formatSize(e.length)}`),e.length>this.partition.size)return void this.logger.error(`Image size (${this._formatSize(e.length)}) exceeds partition size (${this._formatSize(this.partition.size)})`);this.logger.log(`Writing ${this._formatSize(e.length)} to partition "${this.partition.name}" at 0x${this.partition.offset.toString(16)}...`);const t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);await this.espStub.flashData(t,(e,t)=>{const i=Math.floor(e/t*100);this._flashProgress=i},this.partition.offset),this.logger.log("✓ LittleFS successfully written to flash!"),this.logger.log("To use the new filesystem, reset your device.")}catch(e){this.logger.error(`Failed to write LittleFS to flash: ${e.message||e}`)}finally{this._busy=!1,this._isFlashing=!1,this._flashProgress=0,this._flashOperation=null}}_cleanup(){this._fs&&(this._fs=null)}_handleFileSelect(e){var t;const i=e.target;this._selectedFile=(null===(t=i.files)||void 0===t?void 0:t[0])||null}render(){const e=Math.round(this._usage.usedBytes/this._usage.capacityBytes*100);return h`
305
305
  <div class="littlefs-manager">
306
306
  <h3>LittleFS Filesystem Manager</h3>
307
307
 
@@ -321,7 +321,9 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
321
321
  </div>
322
322
  <div class="usage-text">
323
323
  ${this._isFlashing?h`<span class="flash-status">
324
- Writing to flash: ${this._flashProgress}%
324
+
325
+ ${"reading"===this._flashOperation?"Reading from":"Writing to"}
326
+ flash: ${this._flashProgress}%
325
327
  </span>`:h`<span
326
328
  >Used: ${this._formatSize(this._usage.usedBytes)} /
327
329
  ${this._formatSize(this._usage.capacityBytes)}
@@ -658,7 +660,7 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
658
660
  .danger {
659
661
  --mdc-theme-primary: var(--improv-danger-color, #db4437);
660
662
  }
661
- `,i([n({type:Object})],pi.prototype,"partition",void 0),i([n({type:Object})],pi.prototype,"espStub",void 0),i([n({type:Function})],pi.prototype,"logger",void 0),i([n({type:Function})],pi.prototype,"onClose",void 0),i([s()],pi.prototype,"_currentPath",void 0),i([s()],pi.prototype,"_files",void 0),i([s()],pi.prototype,"_fs",void 0),i([s()],pi.prototype,"_blockSize",void 0),i([s()],pi.prototype,"_usage",void 0),i([s()],pi.prototype,"_diskVersion",void 0),i([s()],pi.prototype,"_busy",void 0),i([s()],pi.prototype,"_selectedFile",void 0),i([s()],pi.prototype,"_flashProgress",void 0),i([s()],pi.prototype,"_isFlashing",void 0),pi=i([y("ewt-littlefs-manager")],pi);class ui extends x{constructor(){super(...arguments),this.indeterminate=!1,this.progress=0,this.density=0,this.closed=!1}open(){this.closed=!1}close(){this.closed=!0}render(){const e={"mdc-circular-progress--closed":this.closed,"mdc-circular-progress--indeterminate":this.indeterminate},t=48+4*this.density,i={width:`${t}px`,height:`${t}px`};return h`
663
+ `,i([n({type:Object})],pi.prototype,"partition",void 0),i([n({type:Object})],pi.prototype,"espStub",void 0),i([n({type:Function})],pi.prototype,"logger",void 0),i([n({type:Function})],pi.prototype,"onClose",void 0),i([s()],pi.prototype,"_currentPath",void 0),i([s()],pi.prototype,"_files",void 0),i([s()],pi.prototype,"_fs",void 0),i([s()],pi.prototype,"_blockSize",void 0),i([s()],pi.prototype,"_usage",void 0),i([s()],pi.prototype,"_diskVersion",void 0),i([s()],pi.prototype,"_busy",void 0),i([s()],pi.prototype,"_selectedFile",void 0),i([s()],pi.prototype,"_flashProgress",void 0),i([s()],pi.prototype,"_isFlashing",void 0),i([s()],pi.prototype,"_flashOperation",void 0),pi=i([y("ewt-littlefs-manager")],pi);class ui extends x{constructor(){super(...arguments),this.indeterminate=!1,this.progress=0,this.density=0,this.closed=!1}open(){this.closed=!1}close(){this.closed=!0}render(){const e={"mdc-circular-progress--closed":this.closed,"mdc-circular-progress--indeterminate":this.indeterminate},t=48+4*this.density,i={width:`${t}px`,height:`${t}px`};return h`
662
664
  <div
663
665
  class="mdc-circular-progress ${m(e)}"
664
666
  style="${F(i)}"
@@ -1168,7 +1170,7 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
1168
1170
  label="Cancel"
1169
1171
  @click=${()=>{this._state="DASHBOARD"}}
1170
1172
  ></ewt-button>
1171
- `,!1]}async _handleESP32S2ReconnectClick(){try{this._busy=!0,this.logger.log("Requesting new port selection...");const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.logger.log("New port selected, updating..."),this.port=e,this._state="PARTITIONS",await this._readPartitionTable()}catch(e){"NotFoundError"===e.name?(this.logger.log("Port selection cancelled"),this._state="DASHBOARD"):(this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR")}finally{this._busy=!1}}async _readPartitionTable(){this._busy=!0,this._partitions=void 0;try{this.logger.log("Reading partition table from 0x8000...");const{ESPLoader:e}=await Promise.resolve().then(function(){return Sr});let t=this.port,i=new e(t,{log:(e,...t)=>this.logger.log(e,...t),debug:(e,...t)=>{var i,n;return null===(n=(i=this.logger).debug)||void 0===n?void 0:n.call(i,e,...t)},error:(e,...t)=>this.logger.error(e,...t)});const n=async e=>{this.logger.log("ESP32-S2 USB reconnect event:",e.detail.message),await Tr(t),this.logger.log("Please select the new ESP32-S2 USB CDC port")};i.addEventListener("esp32s2-usb-reconnect",n,{once:!0}),this.logger.log("Initializing ESP loader...");try{await i.initialize(),this.logger.log("ESP loader initialized successfully")}catch(e){if(Cr(t)&&Rr(e))return this.logger.log("ESP32-S2 USB port changed - user needs to select new port"),await Tr(t),this._busy=!1,void(this._state="ESP32S2_RECONNECT");throw e}this.logger.log("Running stub...");const o=await i.runStub();this._espStub=o,await V(500),this.logger.log("Reading flash data...");const r=function(e){const t=[];for(let i=0;i<e.length;i+=32){const n=zr(e.slice(i,i+32));if(null===n)break;t.push(n)}return t}(await o.readFlash(32768,4096));0===r.length?(this.logger.error("No valid partition table found"),this._partitions=[]):(this.logger.log(`Found ${r.length} partition(s)`),this._partitions=r)}catch(e){this.logger.error(`Failed to read partition table: ${e.message||e}`),"Port selection cancelled"===e.message?this._error="Port selection cancelled":this._error=`Failed to read partition table: ${e.message||e}. Try resetting your device.`,this._state="ERROR"}finally{this._busy=!1}}async _openFilesystem(e){try{if(this._busy=!0,this.logger.log(`Detecting filesystem type for partition "${e.name}"...`),!this._espStub)throw new Error("ESP stub not available. Please reconnect.");const t=await async function(e,t,i,n=console){try{const o=Math.min(8192,i),r=await e.readFlash(t,o);if(r.length<32)return n.log("Partition too small, assuming SPIFFS"),"spiffs";if(new TextDecoder("ascii",{fatal:!1}).decode(r).includes("littlefs"))return n.log('✓ LittleFS detected: Found "littlefs" signature'),"littlefs";const a=new DataView(r.buffer,r.byteOffset,r.byteLength),s=[4096,2048,1024,512];for(const e of s)if(r.length>=2*e)try{for(let t=0;t<Math.min(e,r.length-4);t+=4){const e=a.getUint32(t,!0),i=1023&e;if((e>>20&4095)<=2047&&i>0&&i<=1022&&t+i+4<=r.length)return n.log("✓ LittleFS detected: Found valid metadata structure"),"littlefs"}}catch(e){}for(let e=0;e<Math.min(4096,r.length-4);e+=4){const t=a.getUint32(e,!0);if(538182953===t||538314025===t)return n.log("✓ SPIFFS detected: Found SPIFFS magic number"),"spiffs"}return n.log("⚠ No clear filesystem signature found, assuming SPIFFS"),"spiffs"}catch(e){return n.error(`Failed to detect filesystem type: ${e.message||e}`),"spiffs"}}(this._espStub,e.offset,e.size,this.logger);this.logger.log(`Detected filesystem: ${t}`),"littlefs"===t?(this._selectedPartition=e,this._state="LITTLEFS"):"spiffs"===t?(this.logger.error("SPIFFS support not yet implemented. Use LittleFS partitions."),this._error="SPIFFS support not yet implemented",this._state="ERROR"):(this.logger.error("Unknown filesystem type. Cannot open partition."),this._error="Unknown filesystem type",this._state="ERROR")}catch(e){this.logger.error(`Failed to open filesystem: ${e.message||e}`),this._error=`Failed to open filesystem: ${e.message||e}`,this._state="ERROR"}finally{this._busy=!1}}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}willUpdate(e){e.has("_state")&&("ERROR"!==this._state&&(this._error=void 0),"PROVISION"===this._state?(this._ssids=void 0,this._busy=!0,this._client.scan().then(e=>{this._busy=!1,this._ssids=e,this._selectedSsid=e.length?0:-1},()=>{this._busy=!1,this._ssids=null,this._selectedSsid=-1})):this._provisionForce=!1,"INSTALL"===this._state&&(this._installConfirmed=!1,this._installState=void 0))}firstUpdated(e){super.firstUpdated(e),this._initialize()}updated(e){super.updated(e),e.has("_state")&&this.setAttribute("state",this._state),"PROVISION"===this._state&&(e.has("_selectedSsid")&&-1===this._selectedSsid?this._focusFormElement("ewt-textfield[name=ssid]"):e.has("_ssids")&&this._focusFormElement())}_focusFormElement(e="ewt-textfield, ewt-select"){const t=this.shadowRoot.querySelector(e);t&&t.updateComplete.then(()=>setTimeout(()=>t.focus(),100))}async _initialize(e=!1){if(null===this.port.readable||null===this.port.writable)return this._state="ERROR",void(this._error="Serial port is not readable/writable. Close any other application using it and try again.");try{this._manifest=JSON.parse(this.manifestPath)}catch{try{this._manifest=await(async e=>{const t=new URL(e,location.toString()).toString(),i=await fetch(t),n=await i.json();return"new_install_skip_erase"in n&&(console.warn('Manifest option "new_install_skip_erase" is deprecated. Use "new_install_prompt_erase" instead.'),n.new_install_skip_erase&&(n.new_install_prompt_erase=!0)),n})(this.manifestPath)}catch(e){return this._state="ERROR",void(this._error="Failed to download manifest")}}if(0===this._manifest.new_install_improv_wait_time)return void(this._client=null);const t=new Ri(this.port,this.logger);t.addEventListener("state-changed",()=>{this.requestUpdate()}),t.addEventListener("error-changed",()=>this.requestUpdate());try{const i=e?void 0!==this._manifest.new_install_improv_wait_time?1e3*this._manifest.new_install_improv_wait_time:1e4:1e3;this._info=await t.initialize(i),this._client=t,t.addEventListener("disconnect",this._handleDisconnect)}catch(e){this._info=void 0,e instanceof Si?(this._state="ERROR",this._error="Serial port is not ready. Close any other application using it and try again."):(this._client=null,this.logger.error("Improv initialization failed.",e))}}_startInstall(e){this._state="INSTALL",this._installErase=e,this._installConfirmed=!1,this._esp32s2ReconnectInProgress=!1}async _handleESP32S2Reconnect(){if(this._esp32s2ReconnectInProgress)return this.logger.log("ESP32-S2 reconnect already in progress, ignoring"),this._error="Reconnection failed. Please try again manually.",void(this._state="ERROR");this._esp32s2ReconnectInProgress=!0;try{try{await this.port.close()}catch{}try{await this.port.forget()}catch{}const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.port=e,this._installState=void 0,this._installConfirmed=!1,this._confirmInstall()}catch(e){if(this._esp32s2ReconnectInProgress=!1,"NotFoundError"===e.name)return void this.logger.log("User cancelled port selection");this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR"}}async _confirmInstall(){this._installConfirmed=!0,this._installState=void 0,this._esp32s2ReconnectInProgress=!1,this._client&&await this._closeClientWithoutEvents(this._client),this._client=void 0,null!=this.firmwareFile?new Blob([this.firmwareFile]).arrayBuffer().then(e=>this._flashFilebuffer(new Uint8Array(e))):Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,new Uint8Array(0),this.baudRate)}async _flashFilebuffer(e){Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,e,this.baudRate)}async _doProvision(){this._busy=!0,this._wasProvisioned=this._client.state===Ei.PROVISIONED;const e=-1===this._selectedSsid?this.shadowRoot.querySelector("ewt-textfield[name=ssid]").value:this._ssids[this._selectedSsid].name,t=this.shadowRoot.querySelector("ewt-textfield[name=password]").value;try{await this._client.provision(e,t)}catch(e){return}finally{this._busy=!1,this._provisionForce=!1}}async _handleClose(){this._client&&await this._closeClientWithoutEvents(this._client),((e,t,i,n)=>{n=n||{};const o=new CustomEvent(t,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed,detail:i});e.dispatchEvent(o)})(this,"closed"),this.parentNode.removeChild(this)}get _isSameFirmware(){var e;return!!this._info&&((null===(e=this.overrides)||void 0===e?void 0:e.checkSameFirmware)?this.overrides.checkSameFirmware(this._manifest,this._info):this._info.firmware===this._manifest.name)}get _isSameVersion(){return this._isSameFirmware&&this._info.version===this._manifest.version}async _closeClientWithoutEvents(e){e.removeEventListener("disconnect",this._handleDisconnect),await e.close()}}Dr.styles=[L,u`
1173
+ `,!1]}async _handleESP32S2ReconnectClick(){try{this._busy=!0,this.logger.log("Requesting new port selection...");const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.logger.log("New port selected, updating..."),this.port=e,this._state="PARTITIONS",await this._readPartitionTable()}catch(e){"NotFoundError"===e.name?(this.logger.log("Port selection cancelled"),this._state="DASHBOARD"):(this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR")}finally{this._busy=!1}}async _readPartitionTable(){this._busy=!0,this._partitions=void 0;try{this.logger.log("Reading partition table from 0x8000...");const{ESPLoader:e}=await Promise.resolve().then(function(){return Sr});let t=this.port,i=new e(t,{log:(e,...t)=>this.logger.log(e,...t),debug:(e,...t)=>{var i,n;return null===(n=(i=this.logger).debug)||void 0===n?void 0:n.call(i,e,...t)},error:(e,...t)=>this.logger.error(e,...t)});const n=async e=>{this.logger.log("ESP32-S2 USB reconnect event:",e.detail.message),await Tr(t),this.logger.log("Please select the new ESP32-S2 USB CDC port")};i.addEventListener("esp32s2-usb-reconnect",n,{once:!0}),this.logger.log("Initializing ESP loader...");try{await i.initialize(),this.logger.log("ESP loader initialized successfully")}catch(e){if(Cr(t)&&Rr(e))return this.logger.log("ESP32-S2 USB port changed - user needs to select new port"),await Tr(t),this._busy=!1,void(this._state="ESP32S2_RECONNECT");throw e}this.logger.log("Running stub...");const o=await i.runStub();if(this._espStub=o,this.baudRate){this.logger.log(`Setting baudrate to ${this.baudRate} for flash reading...`);try{await o.setBaudrate(this.baudRate),this.logger.log(`Baudrate set to ${this.baudRate}`)}catch(e){this.logger.log(`Failed to set baudrate: ${e.message}, continuing with default`)}}await V(500),this.logger.log("Reading flash data...");const r=function(e){const t=[];for(let i=0;i<e.length;i+=32){const n=zr(e.slice(i,i+32));if(null===n)break;t.push(n)}return t}(await o.readFlash(32768,4096));0===r.length?(this.logger.error("No valid partition table found"),this._partitions=[]):(this.logger.log(`Found ${r.length} partition(s)`),this._partitions=r)}catch(e){this.logger.error(`Failed to read partition table: ${e.message||e}`),"Port selection cancelled"===e.message?this._error="Port selection cancelled":this._error=`Failed to read partition table: ${e.message||e}. Try resetting your device.`,this._state="ERROR"}finally{this._busy=!1}}async _openFilesystem(e){try{if(this._busy=!0,this.logger.log(`Detecting filesystem type for partition "${e.name}"...`),!this._espStub)throw new Error("ESP stub not available. Please reconnect.");const t=await async function(e,t,i,n=console){try{const o=Math.min(8192,i),r=await e.readFlash(t,o);if(r.length<32)return n.log("Partition too small, assuming SPIFFS"),"spiffs";if(new TextDecoder("ascii",{fatal:!1}).decode(r).includes("littlefs"))return n.log('✓ LittleFS detected: Found "littlefs" signature'),"littlefs";const a=new DataView(r.buffer,r.byteOffset,r.byteLength),s=[4096,2048,1024,512];for(const e of s)if(r.length>=2*e)try{for(let t=0;t<Math.min(e,r.length-4);t+=4){const e=a.getUint32(t,!0),i=1023&e;if((e>>20&4095)<=2047&&i>0&&i<=1022&&t+i+4<=r.length)return n.log("✓ LittleFS detected: Found valid metadata structure"),"littlefs"}}catch(e){}for(let e=0;e<Math.min(4096,r.length-4);e+=4){const t=a.getUint32(e,!0);if(538182953===t||538314025===t)return n.log("✓ SPIFFS detected: Found SPIFFS magic number"),"spiffs"}return n.log("⚠ No clear filesystem signature found, assuming SPIFFS"),"spiffs"}catch(e){return n.error(`Failed to detect filesystem type: ${e.message||e}`),"spiffs"}}(this._espStub,e.offset,e.size,this.logger);this.logger.log(`Detected filesystem: ${t}`),"littlefs"===t?(this._selectedPartition=e,this._state="LITTLEFS"):"spiffs"===t?(this.logger.error("SPIFFS support not yet implemented. Use LittleFS partitions."),this._error="SPIFFS support not yet implemented",this._state="ERROR"):(this.logger.error("Unknown filesystem type. Cannot open partition."),this._error="Unknown filesystem type",this._state="ERROR")}catch(e){this.logger.error(`Failed to open filesystem: ${e.message||e}`),this._error=`Failed to open filesystem: ${e.message||e}`,this._state="ERROR"}finally{this._busy=!1}}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}willUpdate(e){e.has("_state")&&("ERROR"!==this._state&&(this._error=void 0),"PROVISION"===this._state?(this._ssids=void 0,this._busy=!0,this._client.scan().then(e=>{this._busy=!1,this._ssids=e,this._selectedSsid=e.length?0:-1},()=>{this._busy=!1,this._ssids=null,this._selectedSsid=-1})):this._provisionForce=!1,"INSTALL"===this._state&&(this._installConfirmed=!1,this._installState=void 0))}firstUpdated(e){super.firstUpdated(e),this._initialize()}updated(e){super.updated(e),e.has("_state")&&this.setAttribute("state",this._state),"PROVISION"===this._state&&(e.has("_selectedSsid")&&-1===this._selectedSsid?this._focusFormElement("ewt-textfield[name=ssid]"):e.has("_ssids")&&this._focusFormElement())}_focusFormElement(e="ewt-textfield, ewt-select"){const t=this.shadowRoot.querySelector(e);t&&t.updateComplete.then(()=>setTimeout(()=>t.focus(),100))}async _initialize(e=!1){if(null===this.port.readable||null===this.port.writable)return this._state="ERROR",void(this._error="Serial port is not readable/writable. Close any other application using it and try again.");try{this._manifest=JSON.parse(this.manifestPath)}catch{try{this._manifest=await(async e=>{const t=new URL(e,location.toString()).toString(),i=await fetch(t),n=await i.json();return"new_install_skip_erase"in n&&(console.warn('Manifest option "new_install_skip_erase" is deprecated. Use "new_install_prompt_erase" instead.'),n.new_install_skip_erase&&(n.new_install_prompt_erase=!0)),n})(this.manifestPath)}catch(e){return this._state="ERROR",void(this._error="Failed to download manifest")}}if(0===this._manifest.new_install_improv_wait_time)return void(this._client=null);const t=new Ri(this.port,this.logger);t.addEventListener("state-changed",()=>{this.requestUpdate()}),t.addEventListener("error-changed",()=>this.requestUpdate());try{const i=e?void 0!==this._manifest.new_install_improv_wait_time?1e3*this._manifest.new_install_improv_wait_time:1e4:1e3;this._info=await t.initialize(i),this._client=t,t.addEventListener("disconnect",this._handleDisconnect)}catch(e){this._info=void 0,e instanceof Si?(this._state="ERROR",this._error="Serial port is not ready. Close any other application using it and try again."):(this._client=null,this.logger.error("Improv initialization failed.",e))}}_startInstall(e){this._state="INSTALL",this._installErase=e,this._installConfirmed=!1,this._esp32s2ReconnectInProgress=!1}async _handleESP32S2Reconnect(){if(this._esp32s2ReconnectInProgress)return this.logger.log("ESP32-S2 reconnect already in progress, ignoring"),this._error="Reconnection failed. Please try again manually.",void(this._state="ERROR");this._esp32s2ReconnectInProgress=!0;try{try{await this.port.close()}catch{}try{await this.port.forget()}catch{}const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.port=e,this._installState=void 0,this._installConfirmed=!1,this._confirmInstall()}catch(e){if(this._esp32s2ReconnectInProgress=!1,"NotFoundError"===e.name)return void this.logger.log("User cancelled port selection");this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR"}}async _confirmInstall(){this._installConfirmed=!0,this._installState=void 0,this._esp32s2ReconnectInProgress=!1,this._client&&await this._closeClientWithoutEvents(this._client),this._client=void 0,null!=this.firmwareFile?new Blob([this.firmwareFile]).arrayBuffer().then(e=>this._flashFilebuffer(new Uint8Array(e))):Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,new Uint8Array(0),this.baudRate)}async _flashFilebuffer(e){Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,e,this.baudRate)}async _doProvision(){this._busy=!0,this._wasProvisioned=this._client.state===Ei.PROVISIONED;const e=-1===this._selectedSsid?this.shadowRoot.querySelector("ewt-textfield[name=ssid]").value:this._ssids[this._selectedSsid].name,t=this.shadowRoot.querySelector("ewt-textfield[name=password]").value;try{await this._client.provision(e,t)}catch(e){return}finally{this._busy=!1,this._provisionForce=!1}}async _handleClose(){this._client&&await this._closeClientWithoutEvents(this._client),((e,t,i,n)=>{n=n||{};const o=new CustomEvent(t,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed,detail:i});e.dispatchEvent(o)})(this,"closed"),this.parentNode.removeChild(this)}get _isSameFirmware(){var e;return!!this._info&&((null===(e=this.overrides)||void 0===e?void 0:e.checkSameFirmware)?this.overrides.checkSameFirmware(this._manifest,this._info):this._info.firmware===this._manifest.name)}get _isSameVersion(){return this._isSameFirmware&&this._info.version===this._manifest.version}async _closeClientWithoutEvents(e){e.removeEventListener("disconnect",this._handleDisconnect),await e.close()}}Dr.styles=[L,u`
1172
1174
  :host {
1173
1175
  --mdc-dialog-max-width: 390px;
1174
1176
  }
@@ -68,13 +68,31 @@ export interface LittleFS {
68
68
  readFile(path: string): Uint8Array;
69
69
  /**
70
70
  * Get the disk version of the mounted filesystem.
71
- * @returns Version as 32-bit number (e.g., 0x00020000 for v2.0)
71
+ * @returns Version as 32-bit number (e.g., 0x00020000 for v2.0, 0x00020001 for v2.1)
72
72
  */
73
73
  getDiskVersion(): number;
74
+ /**
75
+ * Set the disk version for new filesystems.
76
+ * Must be called before formatting.
77
+ * @param version - Version as 32-bit number (use DISK_VERSION_2_0 or DISK_VERSION_2_1)
78
+ */
79
+ setDiskVersion(version: number): void;
74
80
  /**
75
81
  * Get filesystem usage statistics.
76
82
  */
77
83
  getUsage(): { capacityBytes: number; usedBytes: number; freeBytes: number };
84
+ /**
85
+ * Check if a file of given size can fit in the filesystem.
86
+ * @param path - File path (currently unused, reserved for future use)
87
+ * @param size - Size in bytes
88
+ * @returns true if the file can fit, false otherwise
89
+ */
90
+ canFit(path: string, size: number): boolean;
91
+ /**
92
+ * Cleanup and unmount the filesystem.
93
+ * Should be called when done using the filesystem to free resources.
94
+ */
95
+ cleanup(): void;
78
96
  }
79
97
 
80
98
  export declare class LittleFSError extends Error {
@@ -599,13 +599,3 @@ function createClient(Module, blockSize, blockCount) {
599
599
 
600
600
  return client;
601
601
  }
602
-
603
- // Default export for CommonJS compatibility
604
- export default {
605
- createLittleFS,
606
- createLittleFSFromImage,
607
- DISK_VERSION_2_0,
608
- DISK_VERSION_2_1,
609
- LFS_NAME_MAX,
610
- formatDiskVersion,
611
- };
@@ -1 +1 @@
1
- const e=async t=>{let n;import("./install-dialog-CVebVk1R.js");try{n=await navigator.serial.requestPort()}catch(n){return"NotFoundError"===n.name?void import("./index-t2Vsxqjj.js").then(n=>n.openNoPortPickedDialog(()=>e(t))):void alert(`Error: ${n.message}`)}if(!n)return;try{await n.open({baudRate:115200})}catch(e){return void alert(e.message)}const o=document.createElement("ewt-install-dialog");o.port=n,o.manifestPath=t.manifest||t.getAttribute("manifest"),o.overrides=t.overrides,o.firmwareFile=t.firmwareFile;const r=t.getAttribute("baud-rate");if(r){const e=parseInt(r,10);isNaN(e)||(o.baudRate=e)}else void 0!==t.baudRate&&(o.baudRate=t.baudRate);o.addEventListener("closed",()=>{n.close()},{once:!0}),document.body.appendChild(o)};class t extends HTMLElement{connectedCallback(){if(this.renderRoot)return;if(this.renderRoot=this.attachShadow({mode:"open"}),!t.isSupported||!t.isAllowed)return this.toggleAttribute("install-unsupported",!0),void(this.renderRoot.innerHTML=t.isAllowed?"<slot name='unsupported'>Your browser does not support installing things on ESP devices. Use Google Chrome or Microsoft Edge.</slot>":"<slot name='not-allowed'>You can only install ESP devices on HTTPS websites or on the localhost.</slot>");this.toggleAttribute("install-supported",!0);const n=document.createElement("slot");n.addEventListener("click",async t=>{t.preventDefault(),e(this)}),n.name="activate";const o=document.createElement("button");if(o.innerText="CONNECT",n.append(o),"adoptedStyleSheets"in Document.prototype&&"replaceSync"in CSSStyleSheet.prototype){const e=new CSSStyleSheet;e.replaceSync(t.style),this.renderRoot.adoptedStyleSheets=[e]}else{const e=document.createElement("style");e.innerText=t.style,this.renderRoot.append(e)}this.renderRoot.append(n)}}t.isSupported="serial"in navigator,t.isAllowed=window.isSecureContext,t.style='\n button {\n position: relative;\n cursor: pointer;\n font-size: 14px;\n padding: 8px 28px;\n color: var(--esp-tools-button-text-color, #fff);\n background-color: var(--esp-tools-button-color, #03a9f4);\n border: none;\n border-radius: 4px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);\n }\n button::before {\n content: " ";\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n opacity: 0.2;\n border-radius: 4px;\n }\n button:hover {\n box-shadow: 0 4px 8px 0 rgba(0,0,0,.14), 0 1px 7px 0 rgba(0,0,0,.12), 0 3px 1px -1px rgba(0,0,0,.2);\n }\n button:hover::before {\n background-color: rgba(255,255,255,.8);\n }\n button:focus {\n outline: none;\n }\n button:focus::before {\n background-color: white;\n }\n button:active::before {\n background-color: grey;\n }\n :host([active]) button {\n color: rgba(0, 0, 0, 0.38);\n background-color: rgba(0, 0, 0, 0.12);\n box-shadow: none;\n cursor: unset;\n pointer-events: none;\n }\n improv-wifi-launch-button {\n display: block;\n margin-top: 16px;\n }\n .hidden {\n display: none;\n }',customElements.define("esp-web-install-button",t);
1
+ const e=async t=>{let n;import("./install-dialog-Bt_kBJJW.js");try{n=await navigator.serial.requestPort()}catch(n){return"NotFoundError"===n.name?void import("./index-t2Vsxqjj.js").then(n=>n.openNoPortPickedDialog(()=>e(t))):void alert(`Error: ${n.message}`)}if(!n)return;try{await n.open({baudRate:115200})}catch(e){return void alert(e.message)}const o=document.createElement("ewt-install-dialog");o.port=n,o.manifestPath=t.manifest||t.getAttribute("manifest"),o.overrides=t.overrides,o.firmwareFile=t.firmwareFile;const r=t.getAttribute("baud-rate");if(r){const e=parseInt(r,10);isNaN(e)||(o.baudRate=e)}else void 0!==t.baudRate&&(o.baudRate=t.baudRate);o.addEventListener("closed",()=>{n.close()},{once:!0}),document.body.appendChild(o)};class t extends HTMLElement{connectedCallback(){if(this.renderRoot)return;if(this.renderRoot=this.attachShadow({mode:"open"}),!t.isSupported||!t.isAllowed)return this.toggleAttribute("install-unsupported",!0),void(this.renderRoot.innerHTML=t.isAllowed?"<slot name='unsupported'>Your browser does not support installing things on ESP devices. Use Google Chrome or Microsoft Edge.</slot>":"<slot name='not-allowed'>You can only install ESP devices on HTTPS websites or on the localhost.</slot>");this.toggleAttribute("install-supported",!0);const n=document.createElement("slot");n.addEventListener("click",async t=>{t.preventDefault(),e(this)}),n.name="activate";const o=document.createElement("button");if(o.innerText="CONNECT",n.append(o),"adoptedStyleSheets"in Document.prototype&&"replaceSync"in CSSStyleSheet.prototype){const e=new CSSStyleSheet;e.replaceSync(t.style),this.renderRoot.adoptedStyleSheets=[e]}else{const e=document.createElement("style");e.innerText=t.style,this.renderRoot.append(e)}this.renderRoot.append(n)}}t.isSupported="serial"in navigator,t.isAllowed=window.isSecureContext,t.style='\n button {\n position: relative;\n cursor: pointer;\n font-size: 14px;\n padding: 8px 28px;\n color: var(--esp-tools-button-text-color, #fff);\n background-color: var(--esp-tools-button-color, #03a9f4);\n border: none;\n border-radius: 4px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);\n }\n button::before {\n content: " ";\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n opacity: 0.2;\n border-radius: 4px;\n }\n button:hover {\n box-shadow: 0 4px 8px 0 rgba(0,0,0,.14), 0 1px 7px 0 rgba(0,0,0,.12), 0 3px 1px -1px rgba(0,0,0,.2);\n }\n button:hover::before {\n background-color: rgba(255,255,255,.8);\n }\n button:focus {\n outline: none;\n }\n button:focus::before {\n background-color: white;\n }\n button:active::before {\n background-color: grey;\n }\n :host([active]) button {\n color: rgba(0, 0, 0, 0.38);\n background-color: rgba(0, 0, 0, 0.12);\n box-shadow: none;\n cursor: unset;\n pointer-events: none;\n }\n improv-wifi-launch-button {\n display: block;\n margin-top: 16px;\n }\n .hidden {\n display: none;\n }',customElements.define("esp-web-install-button",t);
@@ -301,7 +301,7 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
301
301
  .mdc-floating-label {
302
302
  line-height: 1.15em;
303
303
  }
304
- `],customElements.define("ewt-select",li);class ci extends Te{}ci.styles=[Ae],customElements.define("ewt-list-item",ci);let hi=null,mi=null;let pi=class extends x{constructor(){super(...arguments),this.logger=console,this._currentPath="/",this._files=[],this._fs=null,this._blockSize=4096,this._usage={capacityBytes:0,usedBytes:0,freeBytes:0},this._diskVersion="",this._busy=!1,this._selectedFile=null,this._flashProgress=0,this._isFlashing=!1}async connectedCallback(){super.connectedCallback(),await this._openFilesystem()}disconnectedCallback(){super.disconnectedCallback(),this._cleanup()}async _openFilesystem(){try{this._busy=!0,this.logger.log(`Reading LittleFS partition "${this.partition.name}" (${this._formatSize(this.partition.size)})...`);const e=await this.espStub.readFlash(this.partition.offset,this.partition.size);this.logger.log("Mounting LittleFS filesystem...");const{createLittleFSFromImage:t,formatDiskVersion:i}=await async function(){if(mi)return mi;if(!hi){const e=new URL(import.meta.url),t=e.href.substring(0,e.href.lastIndexOf("/")+1);hi=t+"wasm/littlefs/"}try{const e=hi+"index.js";return console.log("[LittleFS] Loading module from:",e),mi=await import(e),mi}catch(e){console.error("[LittleFS] Failed to load from calculated path:",hi,e);try{return mi=await import("./wasm/littlefs/index.js"),mi}catch(t){throw console.error("[LittleFS] Fallback import also failed:",t),new Error(`Failed to load LittleFS module: ${e}`)}}}(),n=[4096,2048,1024,512];let o=null,r=0;for(const i of n)try{const n={blockSize:i,blockCount:Math.floor(this.partition.size/i)};hi&&(n.wasmURL=new URL("littlefs.wasm",hi).href),o=await t(e,n),o.list("/"),r=i,this.logger.log(`Successfully mounted LittleFS with block size ${i}`);break}catch(e){o=null}if(!o)throw new Error("Failed to mount LittleFS with any block size");this._fs=o,this._blockSize=r;try{const e=o.getDiskVersion();this._diskVersion=i(e)}catch(e){this._diskVersion=""}this._refreshFiles(),this.logger.log("LittleFS filesystem opened successfully")}catch(e){this.logger.error(`Failed to open LittleFS: ${e.message||e}`),this.onClose&&this.onClose()}finally{this._busy=!1}}_refreshFiles(){if(this._fs)try{const e=this._fs.list("/"),t=this._estimateUsage(e),i=this.partition.size;this._usage={capacityBytes:i,usedBytes:t,freeBytes:i-t};const n=this._fs.list(this._currentPath);n.sort((e,t)=>"dir"===e.type&&"dir"!==t.type?-1:"dir"!==e.type&&"dir"===t.type?1:e.path.localeCompare(t.path)),this._files=n}catch(e){this.logger.error(`Failed to refresh file list: ${e.message||e}`)}}_estimateUsage(e){const t=this._blockSize||4096;let i=2*t;for(const n of e||[])if("dir"===n.type)i+=t;else{i+=Math.max(1,Math.ceil((n.size||0)/t))*t+t}return i}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}_navigateUp(){if("/"===this._currentPath||!this._currentPath)return;const e=this._currentPath.split("/").filter(Boolean);e.pop(),this._currentPath="/"+e.join("/"),"/"===this._currentPath||this._currentPath.endsWith("/")||(this._currentPath+="/"),this._refreshFiles()}_navigateTo(e){this._currentPath=e,this._refreshFiles()}async _uploadFile(){if(this._fs&&this._selectedFile)try{this._busy=!0,this.logger.log(`Uploading file "${this._selectedFile.name}"...`);const e=await this._selectedFile.arrayBuffer(),t=new Uint8Array(e);let i=this._currentPath;i.endsWith("/")||(i+="/"),i+=this._selectedFile.name;const n=i.split("/").filter(Boolean);if(n.length>1){let e="";for(let t=0;t<n.length-1;t++){e+=`/${n[t]}`;try{this._fs.mkdir(e)}catch(e){}}}"function"==typeof this._fs.writeFile?this._fs.writeFile(i,t):"function"==typeof this._fs.addFile&&this._fs.addFile(i,t);const o=this._fs.readFile(i);this.logger.log(`✓ File written: ${o.length} bytes at ${i}`);const r=this._selectedFile.name;this._selectedFile=null,this._refreshFiles(),this.logger.log(`File "${r}" uploaded successfully`)}catch(e){this.logger.error(`Failed to upload file: ${e.message||e}`)}finally{this._busy=!1}}_createFolder(){if(!this._fs)return;const e=prompt("Enter directory name:");if(e&&e.trim())try{let t=this._currentPath;t.endsWith("/")||(t+="/"),t+=e.trim(),this._fs.mkdir(t),this._refreshFiles(),this.logger.log(`Directory "${e}" created successfully`)}catch(e){this.logger.error(`Failed to create directory: ${e.message||e}`)}}async _downloadFile(e){if(this._fs)try{this.logger.log(`Downloading file "${e}"...`);const t=this._fs.readFile(e),i=e.split("/").filter(Boolean).pop()||"file.bin",n=new Blob([t],{type:"application/octet-stream"}),o=URL.createObjectURL(n),r=document.createElement("a");r.href=o,r.download=i,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(o),this.logger.log(`File "${i}" downloaded successfully`)}catch(e){this.logger.error(`Failed to download file: ${e.message||e}`)}}_deleteFile(e,t){if(!this._fs)return;const i=e.split("/").filter(Boolean).pop()||e;if(confirm(`Delete ${t} "${i}"?`))try{"dir"===t?this._fs.delete(e,{recursive:!0}):this._fs.deleteFile(e),this._refreshFiles(),this.logger.log(`${"dir"===t?"Directory":"File"} "${i}" deleted successfully`)}catch(e){this.logger.error(`Failed to delete ${t}: ${e.message||e}`)}}async _backupImage(){if(this._fs)try{this.logger.log("Creating LittleFS backup image...");const e=this._fs.toImage(),t=`${this.partition.name}_littlefs_backup.bin`,i=new Blob([e],{type:"application/octet-stream"}),n=URL.createObjectURL(i),o=document.createElement("a");o.href=n,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n),this.logger.log(`LittleFS backup saved as "${t}"`)}catch(e){this.logger.error(`Failed to backup LittleFS: ${e.message||e}`)}}async _writeToFlash(){if(!this._fs)return;if(confirm(`Write modified LittleFS to flash?\n\nPartition: ${this.partition.name}\nOffset: 0x${this.partition.offset.toString(16)}\nSize: ${this._formatSize(this.partition.size)}\n\nThis will overwrite the current filesystem on the device!`))try{this._busy=!0,this._isFlashing=!0,this._flashProgress=0,this.logger.log("Creating LittleFS image...");const e=this._fs.toImage();if(this.logger.log(`Image created: ${this._formatSize(e.length)}`),e.length>this.partition.size)return void this.logger.error(`Image size (${this._formatSize(e.length)}) exceeds partition size (${this._formatSize(this.partition.size)})`);this.logger.log(`Writing ${this._formatSize(e.length)} to partition "${this.partition.name}" at 0x${this.partition.offset.toString(16)}...`);const t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);await this.espStub.flashData(t,(e,t)=>{const i=Math.floor(e/t*100);this._flashProgress=i,this.logger.log(`Writing: ${i}%`)},this.partition.offset),this.logger.log("✓ LittleFS successfully written to flash!"),this.logger.log("To use the new filesystem, reset your device.")}catch(e){this.logger.error(`Failed to write LittleFS to flash: ${e.message||e}`)}finally{this._busy=!1,this._isFlashing=!1,this._flashProgress=0}}_cleanup(){this._fs&&(this._fs=null)}_handleFileSelect(e){var t;const i=e.target;this._selectedFile=(null===(t=i.files)||void 0===t?void 0:t[0])||null}render(){const e=Math.round(this._usage.usedBytes/this._usage.capacityBytes*100);return h`
304
+ `],customElements.define("ewt-select",li);class ci extends Te{}ci.styles=[Ae],customElements.define("ewt-list-item",ci);let hi=null,mi=null;let pi=class extends x{constructor(){super(...arguments),this.logger=console,this._currentPath="/",this._files=[],this._fs=null,this._blockSize=4096,this._usage={capacityBytes:0,usedBytes:0,freeBytes:0},this._diskVersion="",this._busy=!1,this._selectedFile=null,this._flashProgress=0,this._isFlashing=!1,this._flashOperation=null}async connectedCallback(){super.connectedCallback(),this.logger.log("LittleFS Manager: connectedCallback called"),await this._openFilesystem()}disconnectedCallback(){super.disconnectedCallback(),this._cleanup()}async _openFilesystem(){try{if(this._busy=!0,this._isFlashing=!0,this._flashProgress=0,this._flashOperation="reading",this.logger.log(`Reading LittleFS partition "${this.partition.name}" (${this._formatSize(this.partition.size)})...`),!this.espStub.IS_STUB)throw new Error("ESP stub loader is not running. Cannot read flash.");const e=await this.espStub.readFlash(this.partition.offset,this.partition.size,(e,t,i)=>{const n=Math.floor(t/i*100);this._flashProgress=n});if(0===e.length)throw new Error("Read 0 bytes from partition");this.logger.log("Mounting LittleFS filesystem...");const{createLittleFSFromImage:t,formatDiskVersion:i}=await async function(){if(mi)return mi;if(!hi){const e=new URL(import.meta.url),t=e.href.substring(0,e.href.lastIndexOf("/")+1);hi=t+"wasm/littlefs/"}try{const e=hi+"index.js";return console.log("[LittleFS] Loading module from:",e),mi=await import(e),mi}catch(e){console.error("[LittleFS] Failed to load from calculated path:",hi,e);try{return mi=await import("./wasm/littlefs/index.js"),mi}catch(t){throw console.error("[LittleFS] Fallback import also failed:",t),new Error(`Failed to load LittleFS module: ${e}`)}}}(),n=[4096,2048,1024,512];let o=null,r=0;for(const i of n)try{const n={blockSize:i,blockCount:Math.floor(this.partition.size/i)};hi&&(n.wasmURL=new URL("littlefs.wasm",hi).href),o=await t(e,n),o.list("/"),r=i,this.logger.log(`Successfully mounted LittleFS with block size ${i}`);break}catch(e){o=null}if(!o)throw new Error("Failed to mount LittleFS with any block size");this._fs=o,this._blockSize=r;try{const e=o.getDiskVersion();this._diskVersion=e&&0!==e?i(e):"Unknown"}catch(e){this._diskVersion="Unknown"}this._refreshFiles(),this.logger.log("LittleFS filesystem opened successfully")}catch(e){this.logger.error(`Failed to open LittleFS: ${e.message||e}`),this.onClose&&this.onClose()}finally{this._busy=!1,this._isFlashing=!1,this._flashProgress=0,this._flashOperation=null}}_refreshFiles(){if(this._fs)try{const e=this._fs.list("/"),t=this._estimateUsage(e),i=this.partition.size;this._usage={capacityBytes:i,usedBytes:t,freeBytes:i-t};const n=this._fs.list(this._currentPath);n.sort((e,t)=>"dir"===e.type&&"dir"!==t.type?-1:"dir"!==e.type&&"dir"===t.type?1:e.path.localeCompare(t.path)),this._files=n}catch(e){this.logger.error(`Failed to refresh file list: ${e.message||e}`),this._files=[]}}_estimateUsage(e){const t=this._blockSize||4096;let i=2*t;for(const n of e||[])if("dir"===n.type)i+=t;else{i+=Math.max(1,Math.ceil((n.size||0)/t))*t+t}return i}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}_navigateUp(){if("/"===this._currentPath||!this._currentPath)return;const e=this._currentPath.split("/").filter(Boolean);e.pop(),this._currentPath="/"+e.join("/"),"/"===this._currentPath||this._currentPath.endsWith("/")||(this._currentPath+="/"),this._refreshFiles()}_navigateTo(e){this._currentPath=e,this._refreshFiles()}async _uploadFile(){if(this._fs&&this._selectedFile)try{this._busy=!0,this.logger.log(`Uploading file "${this._selectedFile.name}"...`);const e=await this._selectedFile.arrayBuffer(),t=new Uint8Array(e);let i=this._currentPath;i.endsWith("/")||(i+="/"),i+=this._selectedFile.name;const n=i.split("/").filter(Boolean);if(n.length>1){let e="";for(let t=0;t<n.length-1;t++){e+=`/${n[t]}`;try{this._fs.mkdir(e)}catch(e){}}}"function"==typeof this._fs.writeFile?this._fs.writeFile(i,t):"function"==typeof this._fs.addFile&&this._fs.addFile(i,t);const o=this._fs.readFile(i);this.logger.log(`✓ File written: ${o.length} bytes at ${i}`);const r=this._selectedFile.name;this._selectedFile=null,this._refreshFiles(),this.logger.log(`File "${r}" uploaded successfully`)}catch(e){this.logger.error(`Failed to upload file: ${e.message||e}`)}finally{this._busy=!1}}_createFolder(){if(!this._fs)return;const e=prompt("Enter directory name:");if(e&&e.trim())try{let t=this._currentPath;t.endsWith("/")||(t+="/"),t+=e.trim(),this._fs.mkdir(t),this._refreshFiles(),this.logger.log(`Directory "${e}" created successfully`)}catch(e){this.logger.error(`Failed to create directory: ${e.message||e}`)}}async _downloadFile(e){if(this._fs)try{this.logger.log(`Downloading file "${e}"...`);const t=this._fs.readFile(e),i=e.split("/").filter(Boolean).pop()||"file.bin",n=new Blob([t],{type:"application/octet-stream"}),o=URL.createObjectURL(n),r=document.createElement("a");r.href=o,r.download=i,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(o),this.logger.log(`File "${i}" downloaded successfully`)}catch(e){this.logger.error(`Failed to download file: ${e.message||e}`)}}_deleteFile(e,t){if(!this._fs)return;const i=e.split("/").filter(Boolean).pop()||e;if(confirm(`Delete ${t} "${i}"?`))try{"dir"===t?this._fs.delete(e,{recursive:!0}):this._fs.deleteFile(e),this._refreshFiles(),this.logger.log(`${"dir"===t?"Directory":"File"} "${i}" deleted successfully`)}catch(e){this.logger.error(`Failed to delete ${t}: ${e.message||e}`)}}async _backupImage(){if(this._fs)try{this.logger.log("Creating LittleFS backup image...");const e=this._fs.toImage(),t=`${this.partition.name}_littlefs_backup.bin`,i=new Blob([e],{type:"application/octet-stream"}),n=URL.createObjectURL(i),o=document.createElement("a");o.href=n,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n),this.logger.log(`LittleFS backup saved as "${t}"`)}catch(e){this.logger.error(`Failed to backup LittleFS: ${e.message||e}`)}}async _writeToFlash(){if(!this._fs)return;if(confirm(`Write modified LittleFS to flash?\n\nPartition: ${this.partition.name}\nOffset: 0x${this.partition.offset.toString(16)}\nSize: ${this._formatSize(this.partition.size)}\n\nThis will overwrite the current filesystem on the device!`))try{this._busy=!0,this._isFlashing=!0,this._flashProgress=0,this._flashOperation="writing",this.logger.log("Creating LittleFS image...");const e=this._fs.toImage();if(this.logger.log(`Image created: ${this._formatSize(e.length)}`),e.length>this.partition.size)return void this.logger.error(`Image size (${this._formatSize(e.length)}) exceeds partition size (${this._formatSize(this.partition.size)})`);this.logger.log(`Writing ${this._formatSize(e.length)} to partition "${this.partition.name}" at 0x${this.partition.offset.toString(16)}...`);const t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);await this.espStub.flashData(t,(e,t)=>{const i=Math.floor(e/t*100);this._flashProgress=i},this.partition.offset),this.logger.log("✓ LittleFS successfully written to flash!"),this.logger.log("To use the new filesystem, reset your device.")}catch(e){this.logger.error(`Failed to write LittleFS to flash: ${e.message||e}`)}finally{this._busy=!1,this._isFlashing=!1,this._flashProgress=0,this._flashOperation=null}}_cleanup(){this._fs&&(this._fs=null)}_handleFileSelect(e){var t;const i=e.target;this._selectedFile=(null===(t=i.files)||void 0===t?void 0:t[0])||null}render(){const e=Math.round(this._usage.usedBytes/this._usage.capacityBytes*100);return h`
305
305
  <div class="littlefs-manager">
306
306
  <h3>LittleFS Filesystem Manager</h3>
307
307
 
@@ -321,7 +321,9 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
321
321
  </div>
322
322
  <div class="usage-text">
323
323
  ${this._isFlashing?h`<span class="flash-status">
324
- Writing to flash: ${this._flashProgress}%
324
+
325
+ ${"reading"===this._flashOperation?"Reading from":"Writing to"}
326
+ flash: ${this._flashProgress}%
325
327
  </span>`:h`<span
326
328
  >Used: ${this._formatSize(this._usage.usedBytes)} /
327
329
  ${this._formatSize(this._usage.capacityBytes)}
@@ -658,7 +660,7 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
658
660
  .danger {
659
661
  --mdc-theme-primary: var(--improv-danger-color, #db4437);
660
662
  }
661
- `,i([n({type:Object})],pi.prototype,"partition",void 0),i([n({type:Object})],pi.prototype,"espStub",void 0),i([n({type:Function})],pi.prototype,"logger",void 0),i([n({type:Function})],pi.prototype,"onClose",void 0),i([s()],pi.prototype,"_currentPath",void 0),i([s()],pi.prototype,"_files",void 0),i([s()],pi.prototype,"_fs",void 0),i([s()],pi.prototype,"_blockSize",void 0),i([s()],pi.prototype,"_usage",void 0),i([s()],pi.prototype,"_diskVersion",void 0),i([s()],pi.prototype,"_busy",void 0),i([s()],pi.prototype,"_selectedFile",void 0),i([s()],pi.prototype,"_flashProgress",void 0),i([s()],pi.prototype,"_isFlashing",void 0),pi=i([y("ewt-littlefs-manager")],pi);class ui extends x{constructor(){super(...arguments),this.indeterminate=!1,this.progress=0,this.density=0,this.closed=!1}open(){this.closed=!1}close(){this.closed=!0}render(){const e={"mdc-circular-progress--closed":this.closed,"mdc-circular-progress--indeterminate":this.indeterminate},t=48+4*this.density,i={width:`${t}px`,height:`${t}px`};return h`
663
+ `,i([n({type:Object})],pi.prototype,"partition",void 0),i([n({type:Object})],pi.prototype,"espStub",void 0),i([n({type:Function})],pi.prototype,"logger",void 0),i([n({type:Function})],pi.prototype,"onClose",void 0),i([s()],pi.prototype,"_currentPath",void 0),i([s()],pi.prototype,"_files",void 0),i([s()],pi.prototype,"_fs",void 0),i([s()],pi.prototype,"_blockSize",void 0),i([s()],pi.prototype,"_usage",void 0),i([s()],pi.prototype,"_diskVersion",void 0),i([s()],pi.prototype,"_busy",void 0),i([s()],pi.prototype,"_selectedFile",void 0),i([s()],pi.prototype,"_flashProgress",void 0),i([s()],pi.prototype,"_isFlashing",void 0),i([s()],pi.prototype,"_flashOperation",void 0),pi=i([y("ewt-littlefs-manager")],pi);class ui extends x{constructor(){super(...arguments),this.indeterminate=!1,this.progress=0,this.density=0,this.closed=!1}open(){this.closed=!1}close(){this.closed=!0}render(){const e={"mdc-circular-progress--closed":this.closed,"mdc-circular-progress--indeterminate":this.indeterminate},t=48+4*this.density,i={width:`${t}px`,height:`${t}px`};return h`
662
664
  <div
663
665
  class="mdc-circular-progress ${m(e)}"
664
666
  style="${F(i)}"
@@ -1168,7 +1170,7 @@ import{l as e,o as t,_ as i,n,B as o,a as r,c as a,t as s,f as d,g as l,R as c,x
1168
1170
  label="Cancel"
1169
1171
  @click=${()=>{this._state="DASHBOARD"}}
1170
1172
  ></ewt-button>
1171
- `,!1]}async _handleESP32S2ReconnectClick(){try{this._busy=!0,this.logger.log("Requesting new port selection...");const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.logger.log("New port selected, updating..."),this.port=e,this._state="PARTITIONS",await this._readPartitionTable()}catch(e){"NotFoundError"===e.name?(this.logger.log("Port selection cancelled"),this._state="DASHBOARD"):(this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR")}finally{this._busy=!1}}async _readPartitionTable(){this._busy=!0,this._partitions=void 0;try{this.logger.log("Reading partition table from 0x8000...");const{ESPLoader:e}=await Promise.resolve().then(function(){return Sr});let t=this.port,i=new e(t,{log:(e,...t)=>this.logger.log(e,...t),debug:(e,...t)=>{var i,n;return null===(n=(i=this.logger).debug)||void 0===n?void 0:n.call(i,e,...t)},error:(e,...t)=>this.logger.error(e,...t)});const n=async e=>{this.logger.log("ESP32-S2 USB reconnect event:",e.detail.message),await Tr(t),this.logger.log("Please select the new ESP32-S2 USB CDC port")};i.addEventListener("esp32s2-usb-reconnect",n,{once:!0}),this.logger.log("Initializing ESP loader...");try{await i.initialize(),this.logger.log("ESP loader initialized successfully")}catch(e){if(Cr(t)&&Rr(e))return this.logger.log("ESP32-S2 USB port changed - user needs to select new port"),await Tr(t),this._busy=!1,void(this._state="ESP32S2_RECONNECT");throw e}this.logger.log("Running stub...");const o=await i.runStub();this._espStub=o,await V(500),this.logger.log("Reading flash data...");const r=function(e){const t=[];for(let i=0;i<e.length;i+=32){const n=zr(e.slice(i,i+32));if(null===n)break;t.push(n)}return t}(await o.readFlash(32768,4096));0===r.length?(this.logger.error("No valid partition table found"),this._partitions=[]):(this.logger.log(`Found ${r.length} partition(s)`),this._partitions=r)}catch(e){this.logger.error(`Failed to read partition table: ${e.message||e}`),"Port selection cancelled"===e.message?this._error="Port selection cancelled":this._error=`Failed to read partition table: ${e.message||e}. Try resetting your device.`,this._state="ERROR"}finally{this._busy=!1}}async _openFilesystem(e){try{if(this._busy=!0,this.logger.log(`Detecting filesystem type for partition "${e.name}"...`),!this._espStub)throw new Error("ESP stub not available. Please reconnect.");const t=await async function(e,t,i,n=console){try{const o=Math.min(8192,i),r=await e.readFlash(t,o);if(r.length<32)return n.log("Partition too small, assuming SPIFFS"),"spiffs";if(new TextDecoder("ascii",{fatal:!1}).decode(r).includes("littlefs"))return n.log('✓ LittleFS detected: Found "littlefs" signature'),"littlefs";const a=new DataView(r.buffer,r.byteOffset,r.byteLength),s=[4096,2048,1024,512];for(const e of s)if(r.length>=2*e)try{for(let t=0;t<Math.min(e,r.length-4);t+=4){const e=a.getUint32(t,!0),i=1023&e;if((e>>20&4095)<=2047&&i>0&&i<=1022&&t+i+4<=r.length)return n.log("✓ LittleFS detected: Found valid metadata structure"),"littlefs"}}catch(e){}for(let e=0;e<Math.min(4096,r.length-4);e+=4){const t=a.getUint32(e,!0);if(538182953===t||538314025===t)return n.log("✓ SPIFFS detected: Found SPIFFS magic number"),"spiffs"}return n.log("⚠ No clear filesystem signature found, assuming SPIFFS"),"spiffs"}catch(e){return n.error(`Failed to detect filesystem type: ${e.message||e}`),"spiffs"}}(this._espStub,e.offset,e.size,this.logger);this.logger.log(`Detected filesystem: ${t}`),"littlefs"===t?(this._selectedPartition=e,this._state="LITTLEFS"):"spiffs"===t?(this.logger.error("SPIFFS support not yet implemented. Use LittleFS partitions."),this._error="SPIFFS support not yet implemented",this._state="ERROR"):(this.logger.error("Unknown filesystem type. Cannot open partition."),this._error="Unknown filesystem type",this._state="ERROR")}catch(e){this.logger.error(`Failed to open filesystem: ${e.message||e}`),this._error=`Failed to open filesystem: ${e.message||e}`,this._state="ERROR"}finally{this._busy=!1}}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}willUpdate(e){e.has("_state")&&("ERROR"!==this._state&&(this._error=void 0),"PROVISION"===this._state?(this._ssids=void 0,this._busy=!0,this._client.scan().then(e=>{this._busy=!1,this._ssids=e,this._selectedSsid=e.length?0:-1},()=>{this._busy=!1,this._ssids=null,this._selectedSsid=-1})):this._provisionForce=!1,"INSTALL"===this._state&&(this._installConfirmed=!1,this._installState=void 0))}firstUpdated(e){super.firstUpdated(e),this._initialize()}updated(e){super.updated(e),e.has("_state")&&this.setAttribute("state",this._state),"PROVISION"===this._state&&(e.has("_selectedSsid")&&-1===this._selectedSsid?this._focusFormElement("ewt-textfield[name=ssid]"):e.has("_ssids")&&this._focusFormElement())}_focusFormElement(e="ewt-textfield, ewt-select"){const t=this.shadowRoot.querySelector(e);t&&t.updateComplete.then(()=>setTimeout(()=>t.focus(),100))}async _initialize(e=!1){if(null===this.port.readable||null===this.port.writable)return this._state="ERROR",void(this._error="Serial port is not readable/writable. Close any other application using it and try again.");try{this._manifest=JSON.parse(this.manifestPath)}catch{try{this._manifest=await(async e=>{const t=new URL(e,location.toString()).toString(),i=await fetch(t),n=await i.json();return"new_install_skip_erase"in n&&(console.warn('Manifest option "new_install_skip_erase" is deprecated. Use "new_install_prompt_erase" instead.'),n.new_install_skip_erase&&(n.new_install_prompt_erase=!0)),n})(this.manifestPath)}catch(e){return this._state="ERROR",void(this._error="Failed to download manifest")}}if(0===this._manifest.new_install_improv_wait_time)return void(this._client=null);const t=new Ri(this.port,this.logger);t.addEventListener("state-changed",()=>{this.requestUpdate()}),t.addEventListener("error-changed",()=>this.requestUpdate());try{const i=e?void 0!==this._manifest.new_install_improv_wait_time?1e3*this._manifest.new_install_improv_wait_time:1e4:1e3;this._info=await t.initialize(i),this._client=t,t.addEventListener("disconnect",this._handleDisconnect)}catch(e){this._info=void 0,e instanceof Si?(this._state="ERROR",this._error="Serial port is not ready. Close any other application using it and try again."):(this._client=null,this.logger.error("Improv initialization failed.",e))}}_startInstall(e){this._state="INSTALL",this._installErase=e,this._installConfirmed=!1,this._esp32s2ReconnectInProgress=!1}async _handleESP32S2Reconnect(){if(this._esp32s2ReconnectInProgress)return this.logger.log("ESP32-S2 reconnect already in progress, ignoring"),this._error="Reconnection failed. Please try again manually.",void(this._state="ERROR");this._esp32s2ReconnectInProgress=!0;try{try{await this.port.close()}catch{}try{await this.port.forget()}catch{}const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.port=e,this._installState=void 0,this._installConfirmed=!1,this._confirmInstall()}catch(e){if(this._esp32s2ReconnectInProgress=!1,"NotFoundError"===e.name)return void this.logger.log("User cancelled port selection");this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR"}}async _confirmInstall(){this._installConfirmed=!0,this._installState=void 0,this._esp32s2ReconnectInProgress=!1,this._client&&await this._closeClientWithoutEvents(this._client),this._client=void 0,null!=this.firmwareFile?new Blob([this.firmwareFile]).arrayBuffer().then(e=>this._flashFilebuffer(new Uint8Array(e))):Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,new Uint8Array(0),this.baudRate)}async _flashFilebuffer(e){Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,e,this.baudRate)}async _doProvision(){this._busy=!0,this._wasProvisioned=this._client.state===Ei.PROVISIONED;const e=-1===this._selectedSsid?this.shadowRoot.querySelector("ewt-textfield[name=ssid]").value:this._ssids[this._selectedSsid].name,t=this.shadowRoot.querySelector("ewt-textfield[name=password]").value;try{await this._client.provision(e,t)}catch(e){return}finally{this._busy=!1,this._provisionForce=!1}}async _handleClose(){this._client&&await this._closeClientWithoutEvents(this._client),((e,t,i,n)=>{n=n||{};const o=new CustomEvent(t,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed,detail:i});e.dispatchEvent(o)})(this,"closed"),this.parentNode.removeChild(this)}get _isSameFirmware(){var e;return!!this._info&&((null===(e=this.overrides)||void 0===e?void 0:e.checkSameFirmware)?this.overrides.checkSameFirmware(this._manifest,this._info):this._info.firmware===this._manifest.name)}get _isSameVersion(){return this._isSameFirmware&&this._info.version===this._manifest.version}async _closeClientWithoutEvents(e){e.removeEventListener("disconnect",this._handleDisconnect),await e.close()}}Dr.styles=[L,u`
1173
+ `,!1]}async _handleESP32S2ReconnectClick(){try{this._busy=!0,this.logger.log("Requesting new port selection...");const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.logger.log("New port selected, updating..."),this.port=e,this._state="PARTITIONS",await this._readPartitionTable()}catch(e){"NotFoundError"===e.name?(this.logger.log("Port selection cancelled"),this._state="DASHBOARD"):(this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR")}finally{this._busy=!1}}async _readPartitionTable(){this._busy=!0,this._partitions=void 0;try{this.logger.log("Reading partition table from 0x8000...");const{ESPLoader:e}=await Promise.resolve().then(function(){return Sr});let t=this.port,i=new e(t,{log:(e,...t)=>this.logger.log(e,...t),debug:(e,...t)=>{var i,n;return null===(n=(i=this.logger).debug)||void 0===n?void 0:n.call(i,e,...t)},error:(e,...t)=>this.logger.error(e,...t)});const n=async e=>{this.logger.log("ESP32-S2 USB reconnect event:",e.detail.message),await Tr(t),this.logger.log("Please select the new ESP32-S2 USB CDC port")};i.addEventListener("esp32s2-usb-reconnect",n,{once:!0}),this.logger.log("Initializing ESP loader...");try{await i.initialize(),this.logger.log("ESP loader initialized successfully")}catch(e){if(Cr(t)&&Rr(e))return this.logger.log("ESP32-S2 USB port changed - user needs to select new port"),await Tr(t),this._busy=!1,void(this._state="ESP32S2_RECONNECT");throw e}this.logger.log("Running stub...");const o=await i.runStub();if(this._espStub=o,this.baudRate){this.logger.log(`Setting baudrate to ${this.baudRate} for flash reading...`);try{await o.setBaudrate(this.baudRate),this.logger.log(`Baudrate set to ${this.baudRate}`)}catch(e){this.logger.log(`Failed to set baudrate: ${e.message}, continuing with default`)}}await V(500),this.logger.log("Reading flash data...");const r=function(e){const t=[];for(let i=0;i<e.length;i+=32){const n=zr(e.slice(i,i+32));if(null===n)break;t.push(n)}return t}(await o.readFlash(32768,4096));0===r.length?(this.logger.error("No valid partition table found"),this._partitions=[]):(this.logger.log(`Found ${r.length} partition(s)`),this._partitions=r)}catch(e){this.logger.error(`Failed to read partition table: ${e.message||e}`),"Port selection cancelled"===e.message?this._error="Port selection cancelled":this._error=`Failed to read partition table: ${e.message||e}. Try resetting your device.`,this._state="ERROR"}finally{this._busy=!1}}async _openFilesystem(e){try{if(this._busy=!0,this.logger.log(`Detecting filesystem type for partition "${e.name}"...`),!this._espStub)throw new Error("ESP stub not available. Please reconnect.");const t=await async function(e,t,i,n=console){try{const o=Math.min(8192,i),r=await e.readFlash(t,o);if(r.length<32)return n.log("Partition too small, assuming SPIFFS"),"spiffs";if(new TextDecoder("ascii",{fatal:!1}).decode(r).includes("littlefs"))return n.log('✓ LittleFS detected: Found "littlefs" signature'),"littlefs";const a=new DataView(r.buffer,r.byteOffset,r.byteLength),s=[4096,2048,1024,512];for(const e of s)if(r.length>=2*e)try{for(let t=0;t<Math.min(e,r.length-4);t+=4){const e=a.getUint32(t,!0),i=1023&e;if((e>>20&4095)<=2047&&i>0&&i<=1022&&t+i+4<=r.length)return n.log("✓ LittleFS detected: Found valid metadata structure"),"littlefs"}}catch(e){}for(let e=0;e<Math.min(4096,r.length-4);e+=4){const t=a.getUint32(e,!0);if(538182953===t||538314025===t)return n.log("✓ SPIFFS detected: Found SPIFFS magic number"),"spiffs"}return n.log("⚠ No clear filesystem signature found, assuming SPIFFS"),"spiffs"}catch(e){return n.error(`Failed to detect filesystem type: ${e.message||e}`),"spiffs"}}(this._espStub,e.offset,e.size,this.logger);this.logger.log(`Detected filesystem: ${t}`),"littlefs"===t?(this._selectedPartition=e,this._state="LITTLEFS"):"spiffs"===t?(this.logger.error("SPIFFS support not yet implemented. Use LittleFS partitions."),this._error="SPIFFS support not yet implemented",this._state="ERROR"):(this.logger.error("Unknown filesystem type. Cannot open partition."),this._error="Unknown filesystem type",this._state="ERROR")}catch(e){this.logger.error(`Failed to open filesystem: ${e.message||e}`),this._error=`Failed to open filesystem: ${e.message||e}`,this._state="ERROR"}finally{this._busy=!1}}_formatSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(2)} KB`:`${(e/1048576).toFixed(2)} MB`}willUpdate(e){e.has("_state")&&("ERROR"!==this._state&&(this._error=void 0),"PROVISION"===this._state?(this._ssids=void 0,this._busy=!0,this._client.scan().then(e=>{this._busy=!1,this._ssids=e,this._selectedSsid=e.length?0:-1},()=>{this._busy=!1,this._ssids=null,this._selectedSsid=-1})):this._provisionForce=!1,"INSTALL"===this._state&&(this._installConfirmed=!1,this._installState=void 0))}firstUpdated(e){super.firstUpdated(e),this._initialize()}updated(e){super.updated(e),e.has("_state")&&this.setAttribute("state",this._state),"PROVISION"===this._state&&(e.has("_selectedSsid")&&-1===this._selectedSsid?this._focusFormElement("ewt-textfield[name=ssid]"):e.has("_ssids")&&this._focusFormElement())}_focusFormElement(e="ewt-textfield, ewt-select"){const t=this.shadowRoot.querySelector(e);t&&t.updateComplete.then(()=>setTimeout(()=>t.focus(),100))}async _initialize(e=!1){if(null===this.port.readable||null===this.port.writable)return this._state="ERROR",void(this._error="Serial port is not readable/writable. Close any other application using it and try again.");try{this._manifest=JSON.parse(this.manifestPath)}catch{try{this._manifest=await(async e=>{const t=new URL(e,location.toString()).toString(),i=await fetch(t),n=await i.json();return"new_install_skip_erase"in n&&(console.warn('Manifest option "new_install_skip_erase" is deprecated. Use "new_install_prompt_erase" instead.'),n.new_install_skip_erase&&(n.new_install_prompt_erase=!0)),n})(this.manifestPath)}catch(e){return this._state="ERROR",void(this._error="Failed to download manifest")}}if(0===this._manifest.new_install_improv_wait_time)return void(this._client=null);const t=new Ri(this.port,this.logger);t.addEventListener("state-changed",()=>{this.requestUpdate()}),t.addEventListener("error-changed",()=>this.requestUpdate());try{const i=e?void 0!==this._manifest.new_install_improv_wait_time?1e3*this._manifest.new_install_improv_wait_time:1e4:1e3;this._info=await t.initialize(i),this._client=t,t.addEventListener("disconnect",this._handleDisconnect)}catch(e){this._info=void 0,e instanceof Si?(this._state="ERROR",this._error="Serial port is not ready. Close any other application using it and try again."):(this._client=null,this.logger.error("Improv initialization failed.",e))}}_startInstall(e){this._state="INSTALL",this._installErase=e,this._installConfirmed=!1,this._esp32s2ReconnectInProgress=!1}async _handleESP32S2Reconnect(){if(this._esp32s2ReconnectInProgress)return this.logger.log("ESP32-S2 reconnect already in progress, ignoring"),this._error="Reconnection failed. Please try again manually.",void(this._state="ERROR");this._esp32s2ReconnectInProgress=!0;try{try{await this.port.close()}catch{}try{await this.port.forget()}catch{}const e=await navigator.serial.requestPort();await e.open({baudRate:115200}),this.port=e,this._installState=void 0,this._installConfirmed=!1,this._confirmInstall()}catch(e){if(this._esp32s2ReconnectInProgress=!1,"NotFoundError"===e.name)return void this.logger.log("User cancelled port selection");this._error=`Failed to reconnect: ${e.message}`,this._state="ERROR"}}async _confirmInstall(){this._installConfirmed=!0,this._installState=void 0,this._esp32s2ReconnectInProgress=!1,this._client&&await this._closeClientWithoutEvents(this._client),this._client=void 0,null!=this.firmwareFile?new Blob([this.firmwareFile]).arrayBuffer().then(e=>this._flashFilebuffer(new Uint8Array(e))):Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,new Uint8Array(0),this.baudRate)}async _flashFilebuffer(e){Ar(e=>{this._installState=e,"finished"===e.state&&V(100).then(()=>this._initialize(!0)).then(()=>this.requestUpdate())},this.port,this.logger,this.manifestPath,this._installErase,e,this.baudRate)}async _doProvision(){this._busy=!0,this._wasProvisioned=this._client.state===Ei.PROVISIONED;const e=-1===this._selectedSsid?this.shadowRoot.querySelector("ewt-textfield[name=ssid]").value:this._ssids[this._selectedSsid].name,t=this.shadowRoot.querySelector("ewt-textfield[name=password]").value;try{await this._client.provision(e,t)}catch(e){return}finally{this._busy=!1,this._provisionForce=!1}}async _handleClose(){this._client&&await this._closeClientWithoutEvents(this._client),((e,t,i,n)=>{n=n||{};const o=new CustomEvent(t,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed,detail:i});e.dispatchEvent(o)})(this,"closed"),this.parentNode.removeChild(this)}get _isSameFirmware(){var e;return!!this._info&&((null===(e=this.overrides)||void 0===e?void 0:e.checkSameFirmware)?this.overrides.checkSameFirmware(this._manifest,this._info):this._info.firmware===this._manifest.name)}get _isSameVersion(){return this._isSameFirmware&&this._info.version===this._manifest.version}async _closeClientWithoutEvents(e){e.removeEventListener("disconnect",this._handleDisconnect),await e.close()}}Dr.styles=[L,u`
1172
1174
  :host {
1173
1175
  --mdc-dialog-max-width: 390px;
1174
1176
  }
@@ -68,13 +68,31 @@ export interface LittleFS {
68
68
  readFile(path: string): Uint8Array;
69
69
  /**
70
70
  * Get the disk version of the mounted filesystem.
71
- * @returns Version as 32-bit number (e.g., 0x00020000 for v2.0)
71
+ * @returns Version as 32-bit number (e.g., 0x00020000 for v2.0, 0x00020001 for v2.1)
72
72
  */
73
73
  getDiskVersion(): number;
74
+ /**
75
+ * Set the disk version for new filesystems.
76
+ * Must be called before formatting.
77
+ * @param version - Version as 32-bit number (use DISK_VERSION_2_0 or DISK_VERSION_2_1)
78
+ */
79
+ setDiskVersion(version: number): void;
74
80
  /**
75
81
  * Get filesystem usage statistics.
76
82
  */
77
83
  getUsage(): { capacityBytes: number; usedBytes: number; freeBytes: number };
84
+ /**
85
+ * Check if a file of given size can fit in the filesystem.
86
+ * @param path - File path (currently unused, reserved for future use)
87
+ * @param size - Size in bytes
88
+ * @returns true if the file can fit, false otherwise
89
+ */
90
+ canFit(path: string, size: number): boolean;
91
+ /**
92
+ * Cleanup and unmount the filesystem.
93
+ * Should be called when done using the filesystem to free resources.
94
+ */
95
+ cleanup(): void;
78
96
  }
79
97
 
80
98
  export declare class LittleFSError extends Error {
@@ -599,13 +599,3 @@ function createClient(Module, blockSize, blockCount) {
599
599
 
600
600
  return client;
601
601
  }
602
-
603
- // Default export for CommonJS compatibility
604
- export default {
605
- createLittleFS,
606
- createLittleFSFromImage,
607
- DISK_VERSION_2_0,
608
- DISK_VERSION_2_1,
609
- LFS_NAME_MAX,
610
- formatDiskVersion,
611
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tasmota-esp-web-tools",
3
- "version": "10.0.0",
3
+ "version": "10.0.1",
4
4
  "description": "Web tools for ESP devices",
5
5
  "main": "dist/install-button.js",
6
6
  "repository": {