tasmota-esp-web-tools 9.0.2 → 10.0.0

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.
Files changed (85) hide show
  1. package/dist/components/ewt-littlefs-manager.d.ts +43 -0
  2. package/dist/components/ewt-littlefs-manager.js +819 -0
  3. package/dist/flash.js +4 -30
  4. package/dist/install-dialog.d.ts +11 -0
  5. package/dist/install-dialog.js +336 -0
  6. package/dist/partition.d.ts +26 -0
  7. package/dist/partition.js +129 -0
  8. package/dist/util/esp32s2-reconnect.d.ts +22 -0
  9. package/dist/util/esp32s2-reconnect.js +45 -0
  10. package/dist/util/partition.d.ts +4 -0
  11. package/dist/util/partition.js +59 -0
  12. package/{js/modules/index-BK6drzlv.js → dist/web/index-t2Vsxqjj.js} +1 -1
  13. package/dist/web/install-button.js +1 -1
  14. package/dist/web/{install-dialog-Xo7JovVA.js → install-dialog-CVebVk1R.js} +515 -36
  15. package/dist/web/{styles-ntwzvT31.js → styles-CWxbqh-J.js} +1 -1
  16. package/dist/web/wasm/littlefs/index.d.ts +91 -0
  17. package/dist/web/wasm/littlefs/index.js +611 -0
  18. package/dist/web/wasm/littlefs/littlefs.js +505 -0
  19. package/dist/web/wasm/littlefs/littlefs.wasm +0 -0
  20. package/{dist/web/index-BK6drzlv.js → js/modules/index-t2Vsxqjj.js} +1 -1
  21. package/js/modules/install-button.js +1 -1
  22. package/js/modules/{install-dialog-Xo7JovVA.js → install-dialog-CVebVk1R.js} +515 -36
  23. package/js/modules/{styles-ntwzvT31.js → styles-CWxbqh-J.js} +1 -1
  24. package/js/modules/wasm/littlefs/index.d.ts +91 -0
  25. package/js/modules/wasm/littlefs/index.js +611 -0
  26. package/js/modules/wasm/littlefs/littlefs.js +505 -0
  27. package/js/modules/wasm/littlefs/littlefs.wasm +0 -0
  28. package/package.json +3 -2
  29. package/.devcontainer/Dockerfile +0 -16
  30. package/.devcontainer/devcontainer.json +0 -21
  31. package/.github/dependabot.yml +0 -10
  32. package/.github/workflows/build_upload.yml +0 -78
  33. package/.github/workflows/ci.yml +0 -24
  34. package/.prettierignore +0 -1
  35. package/BAUD_RATE_CONFIGURATION.md +0 -291
  36. package/BAUD_RATE_IMPROVEMENT.md +0 -195
  37. package/CHANGELOG_IMPROVEMENTS.md +0 -246
  38. package/ESP32_S2_USB_RECONNECT.md +0 -108
  39. package/FINAL_SUMMARY.md +0 -153
  40. package/example-baud-rate.html +0 -276
  41. package/manifest-example-p4-variants.json +0 -61
  42. package/rollup.config.mjs +0 -36
  43. package/script/build +0 -8
  44. package/script/develop +0 -17
  45. package/src/components/ewt-button.ts +0 -25
  46. package/src/components/ewt-checkbox.ts +0 -14
  47. package/src/components/ewt-circular-progress.ts +0 -14
  48. package/src/components/ewt-console.ts +0 -166
  49. package/src/components/ewt-dialog.ts +0 -22
  50. package/src/components/ewt-formfield.ts +0 -14
  51. package/src/components/ewt-icon-button.ts +0 -14
  52. package/src/components/ewt-list-item.ts +0 -14
  53. package/src/components/ewt-select.ts +0 -23
  54. package/src/components/ewt-textfield.ts +0 -23
  55. package/src/components/svg.ts +0 -27
  56. package/src/connect.ts +0 -55
  57. package/src/const.ts +0 -122
  58. package/src/flash.ts +0 -361
  59. package/src/install-button.ts +0 -126
  60. package/src/install-dialog.ts +0 -1114
  61. package/src/no-port-picked/index.ts +0 -10
  62. package/src/no-port-picked/no-port-picked-dialog.ts +0 -167
  63. package/src/pages/ewt-page-message.ts +0 -39
  64. package/src/pages/ewt-page-progress.ts +0 -44
  65. package/src/styles.ts +0 -34
  66. package/src/util/chip-family-name.ts +0 -46
  67. package/src/util/console-color.ts +0 -284
  68. package/src/util/file-download.ts +0 -17
  69. package/src/util/fire-event.ts +0 -20
  70. package/src/util/line-break-transformer.ts +0 -20
  71. package/src/util/manifest.ts +0 -18
  72. package/src/util/sleep.ts +0 -2
  73. package/static/logos/2smart.png +0 -0
  74. package/static/logos/canairio.png +0 -0
  75. package/static/logos/clockwise.png +0 -0
  76. package/static/logos/espeasy.png +0 -0
  77. package/static/logos/esphome.svg +0 -1
  78. package/static/logos/luciferin_logo.png +0 -0
  79. package/static/logos/squeezelite-esp32.png +0 -0
  80. package/static/logos/tasmota.svg +0 -1
  81. package/static/logos/wled.png +0 -0
  82. package/static/screenshots/dashboard.png +0 -0
  83. package/static/screenshots/logs.png +0 -0
  84. package/static/social.png +0 -0
  85. package/tsconfig.json +0 -20
@@ -0,0 +1,819 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html, css } from "lit";
3
+ import { customElement, property, state } from "lit/decorators.js";
4
+ import "./ewt-button";
5
+ import "./ewt-textfield";
6
+ // Dynamic import for LittleFS WASM module
7
+ let _wasmBasePath = null;
8
+ let _littleFSModule = null;
9
+ async function loadLittleFS() {
10
+ // Cache the module to avoid reloading
11
+ if (_littleFSModule) {
12
+ return _littleFSModule;
13
+ }
14
+ // Determine WASM base path from the current script location
15
+ if (!_wasmBasePath) {
16
+ const scriptUrl = new URL(import.meta.url);
17
+ // Remove the filename to get the directory
18
+ const scriptDir = scriptUrl.href.substring(0, scriptUrl.href.lastIndexOf("/") + 1);
19
+ _wasmBasePath = scriptDir + "wasm/littlefs/";
20
+ }
21
+ try {
22
+ // Try to import from the calculated path
23
+ const indexUrl = _wasmBasePath + "index.js";
24
+ console.log("[LittleFS] Loading module from:", indexUrl);
25
+ _littleFSModule = await import(/* @vite-ignore */ indexUrl);
26
+ return _littleFSModule;
27
+ }
28
+ catch (err) {
29
+ console.error("[LittleFS] Failed to load from calculated path:", _wasmBasePath, err);
30
+ // Fallback to relative import (for local development)
31
+ try {
32
+ _littleFSModule = await import("../wasm/littlefs/index.js");
33
+ return _littleFSModule;
34
+ }
35
+ catch (fallbackErr) {
36
+ console.error("[LittleFS] Fallback import also failed:", fallbackErr);
37
+ throw new Error(`Failed to load LittleFS module: ${err}`);
38
+ }
39
+ }
40
+ }
41
+ let EwtLittleFSManager = class EwtLittleFSManager extends LitElement {
42
+ constructor() {
43
+ super(...arguments);
44
+ this.logger = console;
45
+ this._currentPath = "/";
46
+ this._files = [];
47
+ this._fs = null;
48
+ this._blockSize = 4096;
49
+ this._usage = { capacityBytes: 0, usedBytes: 0, freeBytes: 0 };
50
+ this._diskVersion = "";
51
+ this._busy = false;
52
+ this._selectedFile = null;
53
+ this._flashProgress = 0; // 0-100 for flash progress, -1 when not flashing
54
+ this._isFlashing = false;
55
+ }
56
+ async connectedCallback() {
57
+ super.connectedCallback();
58
+ await this._openFilesystem();
59
+ }
60
+ disconnectedCallback() {
61
+ super.disconnectedCallback();
62
+ this._cleanup();
63
+ }
64
+ async _openFilesystem() {
65
+ try {
66
+ this._busy = true;
67
+ 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);
70
+ this.logger.log("Mounting LittleFS filesystem...");
71
+ // Load LittleFS module dynamically
72
+ const { createLittleFSFromImage, formatDiskVersion } = await loadLittleFS();
73
+ // Try to mount with different block sizes
74
+ const blockSizes = [4096, 2048, 1024, 512];
75
+ let fs = null;
76
+ let blockSize = 0;
77
+ for (const bs of blockSizes) {
78
+ try {
79
+ const blockCount = Math.floor(this.partition.size / bs);
80
+ // Pass WASM URL if available
81
+ const options = {
82
+ blockSize: bs,
83
+ blockCount: blockCount,
84
+ };
85
+ if (_wasmBasePath) {
86
+ options.wasmURL = new URL("littlefs.wasm", _wasmBasePath).href;
87
+ }
88
+ fs = await createLittleFSFromImage(data, options);
89
+ // Try to list root to verify it works
90
+ fs.list("/");
91
+ blockSize = bs;
92
+ this.logger.log(`Successfully mounted LittleFS with block size ${bs}`);
93
+ break;
94
+ }
95
+ catch (err) {
96
+ // Try next block size
97
+ fs = null;
98
+ }
99
+ }
100
+ if (!fs) {
101
+ throw new Error("Failed to mount LittleFS with any block size");
102
+ }
103
+ this._fs = fs;
104
+ this._blockSize = blockSize;
105
+ // Get disk version
106
+ try {
107
+ const diskVer = fs.getDiskVersion();
108
+ this._diskVersion = formatDiskVersion(diskVer);
109
+ }
110
+ catch (e) {
111
+ this._diskVersion = "";
112
+ }
113
+ this._refreshFiles();
114
+ this.logger.log("LittleFS filesystem opened successfully");
115
+ }
116
+ catch (e) {
117
+ this.logger.error(`Failed to open LittleFS: ${e.message || e}`);
118
+ if (this.onClose) {
119
+ this.onClose();
120
+ }
121
+ }
122
+ finally {
123
+ this._busy = false;
124
+ }
125
+ }
126
+ _refreshFiles() {
127
+ if (!this._fs)
128
+ return;
129
+ try {
130
+ // Calculate usage
131
+ const allFiles = this._fs.list("/");
132
+ const usedBytes = this._estimateUsage(allFiles);
133
+ const totalBytes = this.partition.size;
134
+ this._usage = {
135
+ capacityBytes: totalBytes,
136
+ usedBytes: usedBytes,
137
+ freeBytes: totalBytes - usedBytes,
138
+ };
139
+ // List files in current directory
140
+ const entries = this._fs.list(this._currentPath);
141
+ // Sort: directories first, then files
142
+ entries.sort((a, b) => {
143
+ if (a.type === "dir" && b.type !== "dir")
144
+ return -1;
145
+ if (a.type !== "dir" && b.type === "dir")
146
+ return 1;
147
+ return a.path.localeCompare(b.path);
148
+ });
149
+ this._files = entries;
150
+ }
151
+ catch (e) {
152
+ this.logger.error(`Failed to refresh file list: ${e.message || e}`);
153
+ }
154
+ }
155
+ _estimateUsage(entries) {
156
+ const block = this._blockSize || 4096;
157
+ let total = block * 2; // root metadata copies
158
+ for (const entry of entries || []) {
159
+ if (entry.type === "dir") {
160
+ total += block;
161
+ }
162
+ else {
163
+ const dataBytes = Math.max(1, Math.ceil((entry.size || 0) / block)) * block;
164
+ const metadataBytes = block;
165
+ total += dataBytes + metadataBytes;
166
+ }
167
+ }
168
+ return total;
169
+ }
170
+ _formatSize(bytes) {
171
+ if (bytes < 1024) {
172
+ return `${bytes} B`;
173
+ }
174
+ else if (bytes < 1024 * 1024) {
175
+ return `${(bytes / 1024).toFixed(2)} KB`;
176
+ }
177
+ else {
178
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
179
+ }
180
+ }
181
+ _navigateUp() {
182
+ if (this._currentPath === "/" || !this._currentPath)
183
+ return;
184
+ const parts = this._currentPath.split("/").filter(Boolean);
185
+ parts.pop();
186
+ this._currentPath = "/" + parts.join("/");
187
+ if (this._currentPath !== "/" && !this._currentPath.endsWith("/")) {
188
+ this._currentPath += "/";
189
+ }
190
+ this._refreshFiles();
191
+ }
192
+ _navigateTo(path) {
193
+ this._currentPath = path;
194
+ this._refreshFiles();
195
+ }
196
+ async _uploadFile() {
197
+ if (!this._fs || !this._selectedFile)
198
+ return;
199
+ try {
200
+ this._busy = true;
201
+ this.logger.log(`Uploading file "${this._selectedFile.name}"...`);
202
+ const data = await this._selectedFile.arrayBuffer();
203
+ const uint8Data = new Uint8Array(data);
204
+ // Construct target path
205
+ let targetPath = this._currentPath;
206
+ if (!targetPath.endsWith("/"))
207
+ targetPath += "/";
208
+ targetPath += this._selectedFile.name;
209
+ // Ensure parent directories exist
210
+ const segments = targetPath.split("/").filter(Boolean);
211
+ if (segments.length > 1) {
212
+ let built = "";
213
+ for (let i = 0; i < segments.length - 1; i++) {
214
+ built += `/${segments[i]}`;
215
+ try {
216
+ this._fs.mkdir(built);
217
+ }
218
+ catch (e) {
219
+ // Ignore if directory already exists
220
+ }
221
+ }
222
+ }
223
+ // Write file
224
+ if (typeof this._fs.writeFile === "function") {
225
+ this._fs.writeFile(targetPath, uint8Data);
226
+ }
227
+ else if (typeof this._fs.addFile === "function") {
228
+ this._fs.addFile(targetPath, uint8Data);
229
+ }
230
+ // Verify by reading back
231
+ const readBack = this._fs.readFile(targetPath);
232
+ this.logger.log(`✓ File written: ${readBack.length} bytes at ${targetPath}`);
233
+ // Clear input
234
+ const uploadedFileName = this._selectedFile.name;
235
+ this._selectedFile = null;
236
+ this._refreshFiles();
237
+ this.logger.log(`File "${uploadedFileName}" uploaded successfully`);
238
+ }
239
+ catch (e) {
240
+ this.logger.error(`Failed to upload file: ${e.message || e}`);
241
+ }
242
+ finally {
243
+ this._busy = false;
244
+ }
245
+ }
246
+ _createFolder() {
247
+ if (!this._fs)
248
+ return;
249
+ const dirName = prompt("Enter directory name:");
250
+ if (!dirName || !dirName.trim())
251
+ return;
252
+ try {
253
+ let targetPath = this._currentPath;
254
+ if (!targetPath.endsWith("/"))
255
+ targetPath += "/";
256
+ targetPath += dirName.trim();
257
+ this._fs.mkdir(targetPath);
258
+ this._refreshFiles();
259
+ this.logger.log(`Directory "${dirName}" created successfully`);
260
+ }
261
+ catch (e) {
262
+ this.logger.error(`Failed to create directory: ${e.message || e}`);
263
+ }
264
+ }
265
+ async _downloadFile(path) {
266
+ if (!this._fs)
267
+ return;
268
+ try {
269
+ this.logger.log(`Downloading file "${path}"...`);
270
+ const data = this._fs.readFile(path);
271
+ const filename = path.split("/").filter(Boolean).pop() || "file.bin";
272
+ // Create download
273
+ const blob = new Blob([data], { type: "application/octet-stream" });
274
+ const url = URL.createObjectURL(blob);
275
+ const a = document.createElement("a");
276
+ a.href = url;
277
+ a.download = filename;
278
+ document.body.appendChild(a);
279
+ a.click();
280
+ document.body.removeChild(a);
281
+ URL.revokeObjectURL(url);
282
+ this.logger.log(`File "${filename}" downloaded successfully`);
283
+ }
284
+ catch (e) {
285
+ this.logger.error(`Failed to download file: ${e.message || e}`);
286
+ }
287
+ }
288
+ _deleteFile(path, type) {
289
+ if (!this._fs)
290
+ return;
291
+ const name = path.split("/").filter(Boolean).pop() || path;
292
+ const confirmed = confirm(`Delete ${type} "${name}"?`);
293
+ if (!confirmed)
294
+ return;
295
+ try {
296
+ if (type === "dir") {
297
+ this._fs.delete(path, { recursive: true });
298
+ }
299
+ else {
300
+ this._fs.deleteFile(path);
301
+ }
302
+ this._refreshFiles();
303
+ this.logger.log(`${type === "dir" ? "Directory" : "File"} "${name}" deleted successfully`);
304
+ }
305
+ catch (e) {
306
+ this.logger.error(`Failed to delete ${type}: ${e.message || e}`);
307
+ }
308
+ }
309
+ async _backupImage() {
310
+ if (!this._fs)
311
+ return;
312
+ try {
313
+ this.logger.log("Creating LittleFS backup image...");
314
+ const image = this._fs.toImage();
315
+ const filename = `${this.partition.name}_littlefs_backup.bin`;
316
+ // Create download
317
+ const blob = new Blob([image], { type: "application/octet-stream" });
318
+ const url = URL.createObjectURL(blob);
319
+ const a = document.createElement("a");
320
+ a.href = url;
321
+ a.download = filename;
322
+ document.body.appendChild(a);
323
+ a.click();
324
+ document.body.removeChild(a);
325
+ URL.revokeObjectURL(url);
326
+ this.logger.log(`LittleFS backup saved as "${filename}"`);
327
+ }
328
+ catch (e) {
329
+ this.logger.error(`Failed to backup LittleFS: ${e.message || e}`);
330
+ }
331
+ }
332
+ async _writeToFlash() {
333
+ if (!this._fs)
334
+ return;
335
+ const confirmed = confirm(`Write modified LittleFS to flash?\n\n` +
336
+ `Partition: ${this.partition.name}\n` +
337
+ `Offset: 0x${this.partition.offset.toString(16)}\n` +
338
+ `Size: ${this._formatSize(this.partition.size)}\n\n` +
339
+ `This will overwrite the current filesystem on the device!`);
340
+ if (!confirmed)
341
+ return;
342
+ try {
343
+ this._busy = true;
344
+ this._isFlashing = true;
345
+ this._flashProgress = 0;
346
+ this.logger.log("Creating LittleFS image...");
347
+ const image = this._fs.toImage();
348
+ this.logger.log(`Image created: ${this._formatSize(image.length)}`);
349
+ if (image.length > this.partition.size) {
350
+ this.logger.error(`Image size (${this._formatSize(image.length)}) exceeds partition size (${this._formatSize(this.partition.size)})`);
351
+ return;
352
+ }
353
+ this.logger.log(`Writing ${this._formatSize(image.length)} to partition "${this.partition.name}" at 0x${this.partition.offset.toString(16)}...`);
354
+ // Convert Uint8Array to ArrayBuffer
355
+ const imageBuffer = image.buffer.slice(image.byteOffset, image.byteOffset + image.byteLength);
356
+ // Write the image to flash with progress callback
357
+ await this.espStub.flashData(imageBuffer, (bytesWritten, totalBytes) => {
358
+ const percent = Math.floor((bytesWritten / totalBytes) * 100);
359
+ this._flashProgress = percent;
360
+ this.logger.log(`Writing: ${percent}%`);
361
+ }, this.partition.offset);
362
+ this.logger.log(`✓ LittleFS successfully written to flash!`);
363
+ this.logger.log(`To use the new filesystem, reset your device.`);
364
+ }
365
+ catch (e) {
366
+ this.logger.error(`Failed to write LittleFS to flash: ${e.message || e}`);
367
+ }
368
+ finally {
369
+ this._busy = false;
370
+ this._isFlashing = false;
371
+ this._flashProgress = 0;
372
+ }
373
+ }
374
+ _cleanup() {
375
+ if (this._fs) {
376
+ try {
377
+ // Don't call destroy() - just let garbage collection handle it
378
+ }
379
+ catch (e) {
380
+ console.error("Error cleaning up LittleFS:", e);
381
+ }
382
+ this._fs = null;
383
+ }
384
+ }
385
+ _handleFileSelect(e) {
386
+ var _a;
387
+ const input = e.target;
388
+ this._selectedFile = ((_a = input.files) === null || _a === void 0 ? void 0 : _a[0]) || null;
389
+ }
390
+ render() {
391
+ const usedPercent = Math.round((this._usage.usedBytes / this._usage.capacityBytes) * 100);
392
+ return html `
393
+ <div class="littlefs-manager">
394
+ <h3>LittleFS Filesystem Manager</h3>
395
+
396
+ <div class="littlefs-info">
397
+ <div class="littlefs-partition-info">
398
+ <strong>Partition:</strong> ${this.partition.name}
399
+ <span class="littlefs-size"
400
+ >(${this._formatSize(this.partition.size)})</span
401
+ >
402
+ </div>
403
+ <div class="littlefs-usage">
404
+ <div class="usage-bar">
405
+ <div
406
+ class="usage-fill ${this._isFlashing ? "flashing" : ""}"
407
+ style="width: ${this._isFlashing
408
+ ? this._flashProgress
409
+ : usedPercent}%"
410
+ ></div>
411
+ </div>
412
+ <div class="usage-text">
413
+ ${this._isFlashing
414
+ ? html `<span class="flash-status">
415
+ ⚡ Writing to flash: ${this._flashProgress}%
416
+ </span>`
417
+ : html `<span
418
+ >Used: ${this._formatSize(this._usage.usedBytes)} /
419
+ ${this._formatSize(this._usage.capacityBytes)}
420
+ (${usedPercent}%)</span
421
+ >
422
+ ${this._diskVersion
423
+ ? html `<span class="disk-version"
424
+ >${this._diskVersion}</span
425
+ >`
426
+ : ""}`}
427
+ </div>
428
+ </div>
429
+ </div>
430
+
431
+ <div class="littlefs-controls">
432
+ <ewt-button
433
+ label="Refresh"
434
+ @click=${this._refreshFiles}
435
+ ?disabled=${this._busy}
436
+ ></ewt-button>
437
+ <ewt-button
438
+ label="Backup Image"
439
+ @click=${this._backupImage}
440
+ ?disabled=${this._busy}
441
+ ></ewt-button>
442
+ <ewt-button
443
+ label="Write to Flash"
444
+ @click=${this._writeToFlash}
445
+ ?disabled=${this._busy}
446
+ ></ewt-button>
447
+ <ewt-button
448
+ label="Close"
449
+ @click=${() => {
450
+ this._cleanup();
451
+ if (this.onClose)
452
+ this.onClose();
453
+ }}
454
+ ?disabled=${this._busy}
455
+ ></ewt-button>
456
+ </div>
457
+
458
+ <div class="littlefs-breadcrumb">
459
+ <ewt-button
460
+ label="↑ Up"
461
+ @click=${this._navigateUp}
462
+ ?disabled=${this._currentPath === "/" || this._busy}
463
+ ></ewt-button>
464
+ <span>${this._currentPath || "/"}</span>
465
+ </div>
466
+
467
+ <div class="littlefs-file-upload">
468
+ <input
469
+ type="file"
470
+ @change=${this._handleFileSelect}
471
+ ?disabled=${this._busy}
472
+ />
473
+ <ewt-button
474
+ label="Upload File"
475
+ @click=${this._uploadFile}
476
+ ?disabled=${!this._selectedFile || this._busy}
477
+ ></ewt-button>
478
+ <ewt-button
479
+ label="New Folder"
480
+ @click=${this._createFolder}
481
+ ?disabled=${this._busy}
482
+ ></ewt-button>
483
+ </div>
484
+
485
+ <div class="littlefs-files">
486
+ <table class="file-table">
487
+ <thead>
488
+ <tr>
489
+ <th>Name</th>
490
+ <th>Type</th>
491
+ <th>Size</th>
492
+ <th>Actions</th>
493
+ </tr>
494
+ </thead>
495
+ <tbody>
496
+ ${this._files.length === 0
497
+ ? html `
498
+ <tr>
499
+ <td colspan="4" class="empty-state">
500
+ No files in this directory
501
+ </td>
502
+ </tr>
503
+ `
504
+ : this._files.map((entry) => html `
505
+ <tr>
506
+ <td>
507
+ <div
508
+ class="file-name ${entry.type === "dir"
509
+ ? "clickable"
510
+ : ""}"
511
+ @click=${entry.type === "dir"
512
+ ? () => this._navigateTo(entry.path)
513
+ : null}
514
+ >
515
+ <span class="file-icon"
516
+ >${entry.type === "dir" ? "📁" : "📄"}</span
517
+ >
518
+ <span
519
+ >${entry.path.split("/").filter(Boolean).pop() ||
520
+ "/"}</span
521
+ >
522
+ </div>
523
+ </td>
524
+ <td>${entry.type === "dir" ? "Directory" : "File"}</td>
525
+ <td>
526
+ ${entry.type === "file"
527
+ ? this._formatSize(entry.size)
528
+ : "-"}
529
+ </td>
530
+ <td>
531
+ <div class="file-actions">
532
+ ${entry.type === "file"
533
+ ? html `
534
+ <ewt-button
535
+ label="Download"
536
+ @click=${() => this._downloadFile(entry.path)}
537
+ ?disabled=${this._busy}
538
+ ></ewt-button>
539
+ `
540
+ : ""}
541
+ <ewt-button
542
+ class="danger"
543
+ label="Delete"
544
+ @click=${() => this._deleteFile(entry.path, entry.type)}
545
+ ?disabled=${this._busy}
546
+ ></ewt-button>
547
+ </div>
548
+ </td>
549
+ </tr>
550
+ `)}
551
+ </tbody>
552
+ </table>
553
+ </div>
554
+ </div>
555
+ `;
556
+ }
557
+ };
558
+ EwtLittleFSManager.styles = css `
559
+ :host {
560
+ display: block;
561
+ }
562
+
563
+ .littlefs-manager {
564
+ width: 100%;
565
+ max-width: 100%;
566
+ margin: 0 auto;
567
+ padding: 15px;
568
+ border: 2px solid var(--mdc-theme-primary, #03a9f4);
569
+ border-radius: 10px;
570
+ background-color: rgba(3, 169, 244, 0.05);
571
+ box-sizing: border-box;
572
+ }
573
+
574
+ h3 {
575
+ margin: 0 0 15px 0;
576
+ color: var(--mdc-theme-primary, #03a9f4);
577
+ font-size: 18px;
578
+ font-weight: 600;
579
+ }
580
+
581
+ .littlefs-info {
582
+ margin-bottom: 15px;
583
+ padding: 12px;
584
+ background-color: rgba(255, 255, 255, 0.5);
585
+ border-radius: 8px;
586
+ }
587
+
588
+ .littlefs-partition-info {
589
+ margin-bottom: 10px;
590
+ font-size: 13px;
591
+ }
592
+
593
+ .littlefs-size {
594
+ color: #666;
595
+ margin-left: 8px;
596
+ }
597
+
598
+ .littlefs-usage {
599
+ margin-top: 8px;
600
+ }
601
+
602
+ .usage-bar {
603
+ width: 100%;
604
+ height: 18px;
605
+ background-color: #e0e0e0;
606
+ border-radius: 10px;
607
+ overflow: hidden;
608
+ margin-bottom: 6px;
609
+ }
610
+
611
+ .usage-fill {
612
+ height: 100%;
613
+ background: linear-gradient(
614
+ 90deg,
615
+ var(--mdc-theme-primary, #03a9f4) 0%,
616
+ var(--mdc-theme-primary, #03a9f4) 100%
617
+ );
618
+ transition: width 0.3s ease;
619
+ }
620
+
621
+ .usage-fill.flashing {
622
+ background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
623
+ animation: pulse 1s ease-in-out infinite;
624
+ }
625
+
626
+ @keyframes pulse {
627
+ 0%,
628
+ 100% {
629
+ opacity: 1;
630
+ }
631
+ 50% {
632
+ opacity: 0.7;
633
+ }
634
+ }
635
+
636
+ .flash-status {
637
+ font-weight: 600;
638
+ color: #ff5722;
639
+ }
640
+
641
+ .usage-text {
642
+ display: flex;
643
+ justify-content: space-between;
644
+ align-items: center;
645
+ font-size: 12px;
646
+ color: #555;
647
+ flex-wrap: wrap;
648
+ gap: 5px;
649
+ }
650
+
651
+ .disk-version {
652
+ font-size: 11px;
653
+ padding: 2px 6px;
654
+ background-color: var(--mdc-theme-primary, #03a9f4);
655
+ color: white;
656
+ border-radius: 4px;
657
+ }
658
+
659
+ .littlefs-controls {
660
+ display: flex;
661
+ gap: 8px;
662
+ margin-bottom: 15px;
663
+ flex-wrap: wrap;
664
+ }
665
+
666
+ .littlefs-breadcrumb {
667
+ display: flex;
668
+ align-items: center;
669
+ gap: 8px;
670
+ margin-bottom: 12px;
671
+ padding: 8px;
672
+ background-color: rgba(255, 255, 255, 0.5);
673
+ border-radius: 8px;
674
+ }
675
+
676
+ .littlefs-breadcrumb span {
677
+ font-family: monospace;
678
+ font-size: 13px;
679
+ color: #333;
680
+ overflow: hidden;
681
+ text-overflow: ellipsis;
682
+ white-space: nowrap;
683
+ }
684
+
685
+ .littlefs-file-upload {
686
+ display: flex;
687
+ gap: 8px;
688
+ margin-bottom: 12px;
689
+ align-items: center;
690
+ flex-wrap: wrap;
691
+ }
692
+
693
+ .littlefs-file-upload input[type="file"] {
694
+ flex: 1;
695
+ min-width: 150px;
696
+ padding: 4px;
697
+ border: 2px solid #ccc;
698
+ border-radius: 8px;
699
+ font-size: 13px;
700
+ }
701
+
702
+ .littlefs-files {
703
+ max-height: 350px;
704
+ overflow-y: auto;
705
+ overflow-x: auto;
706
+ border: 1px solid #ccc;
707
+ border-radius: 8px;
708
+ }
709
+
710
+ .file-table {
711
+ width: 100%;
712
+ min-width: 500px;
713
+ border-collapse: collapse;
714
+ }
715
+
716
+ .file-table thead {
717
+ position: sticky;
718
+ top: 0;
719
+ background-color: #f5f5f5;
720
+ z-index: 10;
721
+ }
722
+
723
+ .file-table th {
724
+ padding: 8px;
725
+ text-align: left;
726
+ font-weight: 600;
727
+ border-bottom: 2px solid #ccc;
728
+ }
729
+
730
+ .file-table td {
731
+ padding: 8px 10px;
732
+ border-bottom: 1px solid #e0e0e0;
733
+ }
734
+
735
+ .file-table tbody tr:hover {
736
+ background-color: rgba(3, 169, 244, 0.1);
737
+ }
738
+
739
+ .file-table .empty-state {
740
+ text-align: center;
741
+ color: #999;
742
+ padding: 30px;
743
+ font-style: italic;
744
+ }
745
+
746
+ .file-name {
747
+ display: flex;
748
+ align-items: center;
749
+ gap: 8px;
750
+ }
751
+
752
+ .file-name.clickable {
753
+ cursor: pointer;
754
+ }
755
+
756
+ .file-name.clickable:hover {
757
+ color: var(--mdc-theme-primary, #03a9f4);
758
+ text-decoration: underline;
759
+ }
760
+
761
+ .file-icon {
762
+ font-size: 16px;
763
+ }
764
+
765
+ .file-actions {
766
+ display: flex;
767
+ gap: 5px;
768
+ }
769
+
770
+ .danger {
771
+ --mdc-theme-primary: var(--improv-danger-color, #db4437);
772
+ }
773
+ `;
774
+ __decorate([
775
+ property({ type: Object })
776
+ ], EwtLittleFSManager.prototype, "partition", void 0);
777
+ __decorate([
778
+ property({ type: Object })
779
+ ], EwtLittleFSManager.prototype, "espStub", void 0);
780
+ __decorate([
781
+ property({ type: Function })
782
+ ], EwtLittleFSManager.prototype, "logger", void 0);
783
+ __decorate([
784
+ property({ type: Function })
785
+ ], EwtLittleFSManager.prototype, "onClose", void 0);
786
+ __decorate([
787
+ state()
788
+ ], EwtLittleFSManager.prototype, "_currentPath", void 0);
789
+ __decorate([
790
+ state()
791
+ ], EwtLittleFSManager.prototype, "_files", void 0);
792
+ __decorate([
793
+ state()
794
+ ], EwtLittleFSManager.prototype, "_fs", void 0);
795
+ __decorate([
796
+ state()
797
+ ], EwtLittleFSManager.prototype, "_blockSize", void 0);
798
+ __decorate([
799
+ state()
800
+ ], EwtLittleFSManager.prototype, "_usage", void 0);
801
+ __decorate([
802
+ state()
803
+ ], EwtLittleFSManager.prototype, "_diskVersion", void 0);
804
+ __decorate([
805
+ state()
806
+ ], EwtLittleFSManager.prototype, "_busy", void 0);
807
+ __decorate([
808
+ state()
809
+ ], EwtLittleFSManager.prototype, "_selectedFile", void 0);
810
+ __decorate([
811
+ state()
812
+ ], EwtLittleFSManager.prototype, "_flashProgress", void 0);
813
+ __decorate([
814
+ state()
815
+ ], EwtLittleFSManager.prototype, "_isFlashing", void 0);
816
+ EwtLittleFSManager = __decorate([
817
+ customElement("ewt-littlefs-manager")
818
+ ], EwtLittleFSManager);
819
+ export { EwtLittleFSManager };