|
9 | 9 | <!-- Toggle Buttons --> |
10 | 10 | <div class="btn-group" style="margin: 1em; display: flex; justify-content: center;"> |
11 | 11 | <button id="tableButton" class="btn btn-primary">View Table</button> |
12 | | - <button id="pieChartButton" class="btn">View Pie Chart</button> |
| 12 | + <button id="pieChartButton" class="btn">View Pie Charts</button> |
13 | 13 | </div> |
14 | 14 |
|
15 | 15 | <!-- Table View --> |
16 | 16 | <div id="storageTable"></div> |
17 | 17 |
|
18 | | - <!-- Chart View --> |
19 | | - <div id="storagePieChart" style="display: none; flex-direction: column; align-items: center;"> |
| 18 | + <!-- Charts View --> |
| 19 | + <div id="storagePieCharts" style="display: none; flex-direction: column; align-items: center; gap: 1.5em;"> |
| 20 | + <!-- App Breakdown Chart --> |
20 | 21 | <div id="piechart" style="width: 100%; max-width: 600px; height: 400px;"></div> |
21 | | - <div id="totalStoragePie" style="width: 100%; max-width: 600px; height: 300px; margin-top: 2em;"></div> |
| 22 | + <!-- Total Storage Chart --> |
| 23 | + <div id="totalStoragePie" style="width: 100%; max-width: 600px; height: 300px;"></div> |
22 | 24 | </div> |
23 | 25 |
|
24 | 26 | <script> |
25 | 27 | let globalApps = []; |
26 | 28 | let storageStats = null; |
27 | | - let isBangle2 = false; |
| 29 | + let hasGetStats = true; |
28 | 30 |
|
29 | 31 | function onInit(device) { |
30 | | - isBangle2 = device?.HWVERSION === 2; |
31 | | - |
32 | 32 | Util.showModal("Reading Storage..."); |
33 | 33 |
|
34 | | - const evalCode = `(()=>{ |
| 34 | + // Fetch app list and stats from the watch |
| 35 | + Puck.eval(`(()=>{ |
35 | 36 | const Storage = require("Storage"); |
36 | | - let apps = Storage.list(/\\.info$/).map(infoFile => { |
37 | | - let info = Storage.readJSON(infoFile, 1) || {}; |
38 | | - let codeSize = 0, dataSize = 0; |
39 | | - if (info.files && info.files.length) { |
40 | | - info.files.split(",").forEach(f => { |
41 | | - let fileData = Storage.read(f); |
42 | | - if (fileData) codeSize += fileData.length; |
43 | | - }); |
44 | | - } |
45 | | - let data = (info.data||"").split(";"); |
46 | | - function wcToRegExp(wc) { |
47 | | - return new RegExp("^"+wc.replaceAll(".", "\\\\.").replaceAll("?", ".*")+"$"); |
48 | | - } |
49 | | - if (data[0]) data[0].split(",").forEach(wc => { |
50 | | - Storage.list(wcToRegExp(wc), {sf:false}).forEach(f => { |
51 | | - let d = Storage.read(f); |
52 | | - if (d) dataSize += d.length; |
53 | | - }); |
| 37 | + const getApps = () => Storage.list(/\\.info$/).map(n => { |
| 38 | + const app = Storage.readJSON(n,1)||{}; |
| 39 | + let fileSize=0, dataSize=0; |
| 40 | + if (app.files) app.files.split(",").forEach(f=>{ |
| 41 | + const d = Storage.read(f); if (d) fileSize += d.length; |
| 42 | + }); |
| 43 | + const parts = (app.data||"").split(";"); |
| 44 | + function toRegExp(wc) { return new RegExp("^"+wc.replace(/[.+?^${}()|[\\]\\\\]/g,"\\\\$&").replace(/\\*/g,".*")+"$"); } |
| 45 | + if (parts[0]) parts[0].split(",").forEach(wc=>{ |
| 46 | + Storage.list(toRegExp(wc), {sf:false}).forEach(f=>{const d=Storage.read(f); if (d) dataSize += d.length;}); |
54 | 47 | }); |
55 | | - if (data[1]) data[1].split(",").forEach(wc => { |
56 | | - Storage.list(wcToRegExp(wc), {sf:true}).forEach(f => { |
57 | | - let openFile = Storage.open(f, "r"); |
58 | | - if (openFile) dataSize += openFile.getLength(); |
59 | | - }); |
| 48 | + if (parts[1]) parts[1].split(",").forEach(wc=>{ |
| 49 | + Storage.list(toRegExp(wc), {sf:true}).forEach(f=>{ try{ dataSize += Storage.open(f,"r").getLength(); }catch(e){} }); |
60 | 50 | }); |
61 | | - return [info.id || infoFile.replace(".info",""), codeSize, dataSize]; |
| 51 | + return [app.id||"Unknown", fileSize, dataSize]; |
62 | 52 | }); |
63 | | - ${ |
64 | | - isBangle2 |
65 | | - ? "let stats = Storage.getStats(); return [apps, stats];" |
66 | | - : "return [apps, null];" |
67 | | - } |
68 | | - })()`; |
69 | 53 |
|
70 | | - Puck.eval(evalCode, function(result) { |
| 54 | + let stats; |
| 55 | + try { stats = Storage.getStats(); } catch(e) { stats = null; } |
| 56 | + return [getApps(), stats]; |
| 57 | + })()`, function(result) { |
71 | 58 | Util.hideModal(); |
72 | | - |
73 | | - globalApps = result[0].sort((a,b) => (b[1]+b[2]) - (a[1]+a[2])); |
| 59 | + globalApps = result[0].sort((a,b)=>(b[1]+b[2])-(a[1]+a[2])); |
74 | 60 | storageStats = result[1]; |
75 | | - |
76 | | - if (!isBangle2) { |
77 | | - // Hide total storage pie for Bangle.js 1 |
78 | | - document.getElementById('totalStoragePie').style.display = 'none'; |
79 | | - } else { |
80 | | - document.getElementById('totalStoragePie').style.display = 'block'; |
81 | | - } |
| 61 | + hasGetStats = !!storageStats; |
82 | 62 |
|
83 | 63 | if (globalApps.length === 0) { |
84 | 64 | document.getElementById("storageTable").innerHTML = "<p>No apps found</p>"; |
|
112 | 92 | </table>`; |
113 | 93 | } |
114 | 94 |
|
115 | | - function drawChart() { |
| 95 | + function drawCharts() { |
116 | 96 | if (globalApps.length === 0) return; |
117 | 97 |
|
118 | | - // Draw pie chart for apps |
119 | | - const appChartData = [ |
120 | | - ['App', 'Total Size (KB)'] |
121 | | - ].concat(globalApps.map(app => [app[0], (app[1] + app[2])/1000])); |
| 98 | + // Chart 1: App Breakdown |
| 99 | + const appData = google.visualization.arrayToDataTable([ |
| 100 | + ['App', 'Total Size (KB)'], |
| 101 | + ...globalApps.map(a => [a[0], (a[1]+a[2])/1000]) |
| 102 | + ]); |
122 | 103 |
|
123 | | - const dataApps = google.visualization.arrayToDataTable(appChartData); |
124 | | - const appChartOptions = { |
| 104 | + const appChart = new google.visualization.PieChart(document.getElementById('piechart')); |
| 105 | + appChart.draw(appData, { |
125 | 106 | title: 'App Storage Breakdown', |
126 | 107 | chartArea: { width: '90%', height: '80%' }, |
127 | 108 | legend: { position: 'bottom' } |
128 | | - }; |
| 109 | + }); |
129 | 110 |
|
130 | | - const appChart = new google.visualization.PieChart(document.getElementById('piechart')); |
131 | | - appChart.draw(dataApps, appChartOptions); |
132 | | - |
133 | | - // Draw total storage pie only on Bangle.js 2 |
134 | | - if (isBangle2 && storageStats) { |
135 | | - const usedKB = storageStats.usedBytes / 1000; |
136 | | - const freeKB = storageStats.freeBytes / 1000; |
137 | | - |
138 | | - const totalChartData = google.visualization.arrayToDataTable([ |
139 | | - ['Type', 'KB'], |
140 | | - ['Used', usedKB], |
141 | | - ['Free', freeKB] |
142 | | - ]); |
143 | | - |
144 | | - const totalChartOptions = { |
145 | | - title: 'Total Storage Usage', |
146 | | - pieHole: 0.4, |
147 | | - chartArea: { width: '90%', height: '80%' }, |
148 | | - legend: { position: 'bottom' } |
149 | | - }; |
150 | | - |
151 | | - const totalChart = new google.visualization.PieChart(document.getElementById('totalStoragePie')); |
152 | | - totalChart.draw(totalChartData, totalChartOptions); |
| 111 | + // Chart 2: Total Storage (only if stats available, i.e., Bangle.js 2) |
| 112 | + const totalDiv = document.getElementById('totalStoragePie'); |
| 113 | + if (!hasGetStats) { |
| 114 | + totalDiv.style.display = "none"; // hide for Bangle.js 1 |
| 115 | + return; |
153 | 116 | } |
| 117 | + |
| 118 | + const used = (storageStats.fileBytes||0)/1000; |
| 119 | + const free = (storageStats.freeBytes||0)/1000; |
| 120 | + const trash = (storageStats.garbageBytes||0)/1000; |
| 121 | + |
| 122 | + const totalData = google.visualization.arrayToDataTable([ |
| 123 | + ['Type', 'KB'], |
| 124 | + ['Used', used], |
| 125 | + ['Free', free], |
| 126 | + ['Trash', trash] |
| 127 | + ]); |
| 128 | + |
| 129 | + const totalChart = new google.visualization.PieChart(totalDiv); |
| 130 | + totalChart.draw(totalData, { |
| 131 | + title: 'Total Storage Usage', |
| 132 | + pieHole: 0.4, |
| 133 | + chartArea: { width: '90%', height: '80%' }, |
| 134 | + legend: { position: 'bottom' } |
| 135 | + }); |
154 | 136 | } |
155 | 137 |
|
156 | 138 | // Load Google Charts |
157 | 139 | google.charts.load('current', {'packages':['corechart']}); |
158 | | - google.charts.setOnLoadCallback(() => { |
159 | | - drawTable(); // Start with table view |
160 | | - }); |
161 | 140 |
|
162 | | - // Buttons event listeners |
163 | | - document.getElementById("pieChartButton").addEventListener("click", () => { |
| 141 | + document.getElementById("pieChartButton").addEventListener("click", function() { |
164 | 142 | document.getElementById("storageTable").style.display = "none"; |
165 | | - document.getElementById("storagePieChart").style.display = "flex"; |
166 | | - drawChart(); |
167 | | - |
168 | | - document.getElementById("pieChartButton").classList.add("btn-primary"); |
| 143 | + document.getElementById("storagePieCharts").style.display = "flex"; |
| 144 | + google.charts.setOnLoadCallback(drawCharts); |
| 145 | + this.classList.add("btn-primary"); |
169 | 146 | document.getElementById("tableButton").classList.remove("btn-primary"); |
170 | 147 | }); |
171 | 148 |
|
172 | | - document.getElementById("tableButton").addEventListener("click", () => { |
| 149 | + document.getElementById("tableButton").addEventListener("click", function() { |
173 | 150 | document.getElementById("storageTable").style.display = "block"; |
174 | | - document.getElementById("storagePieChart").style.display = "none"; |
| 151 | + document.getElementById("storagePieCharts").style.display = "none"; |
175 | 152 | drawTable(); |
176 | | - |
177 | | - document.getElementById("tableButton").classList.add("btn-primary"); |
| 153 | + this.classList.add("btn-primary"); |
178 | 154 | document.getElementById("pieChartButton").classList.remove("btn-primary"); |
179 | 155 | }); |
180 | 156 |
|
181 | | - // Register entrypoint for app loader |
182 | 157 | window.onInit = onInit; |
183 | 158 | </script> |
184 | 159 | </body> |
|
0 commit comments