mirror of https://github.com/espruino/BangleApps
feat: "my files" tab to list and download files
parent
d306209da7
commit
5278ec9929
30
comms.js
30
comms.js
|
@ -95,5 +95,35 @@ watchConnectionChange : cb => {
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
listFiles : () => {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
Puck.write("\x03",(result) => {
|
||||||
|
if (result===null) return reject("");
|
||||||
|
//use encodeURIComponent to serialize octal sequence of append files
|
||||||
|
Puck.eval('require("Storage").list().map(encodeURIComponent)', (files,err) => {
|
||||||
|
if (files===null) return reject(err || "");
|
||||||
|
files = files.map(decodeURIComponent);
|
||||||
|
console.log("listFiles", files);
|
||||||
|
resolve(files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
readFile : (file) => {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
//encode name to avoid serialization issue due to octal sequence
|
||||||
|
const name = encodeURIComponent(file);
|
||||||
|
Puck.write("\x03",(result) => {
|
||||||
|
if (result===null) return reject("");
|
||||||
|
//TODO: big files will not fit in RAM.
|
||||||
|
//we should loop and read chunks one by one.
|
||||||
|
//Use btoa for binary content
|
||||||
|
Puck.eval(`btoa(require("Storage").read(decodeURIComponent("${name}"))))`, (content,err) => {
|
||||||
|
if (content===null) return reject(err || "");
|
||||||
|
resolve(atob(content));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
21
index.html
21
index.html
|
@ -56,6 +56,9 @@
|
||||||
<li class="tab-item" id="tab-myappscontainer">
|
<li class="tab-item" id="tab-myappscontainer">
|
||||||
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="tab-item" id="tab-myfscontainer">
|
||||||
|
<a href="javascript:showTab('myfscontainer')">My files</a>
|
||||||
|
</li>
|
||||||
<li class="tab-item" id="tab-aboutcontainer">
|
<li class="tab-item" id="tab-aboutcontainer">
|
||||||
<a href="javascript:showTab('aboutcontainer')">About</a>
|
<a href="javascript:showTab('aboutcontainer')">About</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -87,12 +90,27 @@
|
||||||
<div class="container bangle-tab" id="myappscontainer" style="display:none">
|
<div class="container bangle-tab" id="myappscontainer" style="display:none">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-header" style="text-align:right">
|
<div class="panel-header" style="text-align:right">
|
||||||
<button class="btn" id="myappsrefresh">Refresh...</button>
|
<button class="btn refresh">Refresh...</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body columns"><!-- apps go here --></div>
|
<div class="panel-body columns"><!-- apps go here --></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="container bangle-tab" id="myfscontainer" style="display:none">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header column col-12 container">
|
||||||
|
<div class="columns col-gapless">
|
||||||
|
<p class="column col-9">Files currently stored on the watch. Click row to download.</p>
|
||||||
|
<div class="column col-3" style="text-align:right">
|
||||||
|
<button class="btn refresh">Refresh...</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-hover panel-body">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container bangle-tab" id="aboutcontainer" style="display:none">
|
<div class="container bangle-tab" id="aboutcontainer" style="display:none">
|
||||||
<div class="hero bg-gray">
|
<div class="hero bg-gray">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
|
@ -117,5 +135,6 @@
|
||||||
<script src="comms.js"></script>
|
<script src="comms.js"></script>
|
||||||
<script src="appinfo.js"></script>
|
<script src="appinfo.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js" type="application/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
67
index.js
67
index.js
|
@ -1,5 +1,6 @@
|
||||||
var appJSON = []; // List of apps and info from apps.json
|
var appJSON = []; // List of apps and info from apps.json
|
||||||
var appsInstalled = []; // list of app JSON
|
var appsInstalled = []; // list of app JSON
|
||||||
|
var files = []; // list of files on Bangle
|
||||||
|
|
||||||
httpGet("apps.json").then(apps=>{
|
httpGet("apps.json").then(apps=>{
|
||||||
try {
|
try {
|
||||||
|
@ -264,9 +265,9 @@ function appNameToApp(appName) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoadingIndicator() {
|
function showLoadingIndicator(id) {
|
||||||
var panelbody = document.querySelector("#myappscontainer .panel-body");
|
var panelbody = document.querySelector(`#${id} .panel-body`);
|
||||||
var tab = document.querySelector("#tab-myappscontainer a");
|
var tab = document.querySelector(`#tab-${id} a`);
|
||||||
// set badge up top
|
// set badge up top
|
||||||
tab.classList.add("badge");
|
tab.classList.add("badge");
|
||||||
tab.setAttribute("data-badge", "");
|
tab.setAttribute("data-badge", "");
|
||||||
|
@ -313,14 +314,21 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInstalledApps() {
|
function getInstalledApps() {
|
||||||
showLoadingIndicator();
|
showLoadingIndicator("myappscontainer");
|
||||||
// Get apps
|
showLoadingIndicator("myfscontainer");
|
||||||
return Comms.getInstalledApps().then(appJSON => {
|
// Get apps and files
|
||||||
|
return Comms.getInstalledApps()
|
||||||
|
.then(appJSON => {
|
||||||
appsInstalled = appJSON;
|
appsInstalled = appJSON;
|
||||||
handleConnectionChange(true);
|
|
||||||
refreshMyApps();
|
refreshMyApps();
|
||||||
refreshLibrary();
|
refreshLibrary();
|
||||||
});
|
})
|
||||||
|
.then(Comms.listFiles)
|
||||||
|
.then(list => {
|
||||||
|
files = list;
|
||||||
|
refreshMyFS();
|
||||||
|
})
|
||||||
|
.then(() => handleConnectionChange(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectMyDeviceBtn = document.getElementById("connectmydevice");
|
var connectMyDeviceBtn = document.getElementById("connectmydevice");
|
||||||
|
@ -330,11 +338,11 @@ function handleConnectionChange(connected) {
|
||||||
connectMyDeviceBtn.classList.toggle('is-connected', connected);
|
connectMyDeviceBtn.classList.toggle('is-connected', connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("myappsrefresh").addEventListener("click", () => {
|
htmlToArray(document.querySelectorAll(".btn.refresh")).map(button => button.addEventListener("click", () => {
|
||||||
getInstalledApps().catch(err => {
|
getInstalledApps().catch(err => {
|
||||||
showToast("Getting app list failed, "+err,"error");
|
showToast("Getting app list failed, "+err,"error");
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
connectMyDeviceBtn.addEventListener("click", () => {
|
connectMyDeviceBtn.addEventListener("click", () => {
|
||||||
if (connectMyDeviceBtn.classList.contains('is-connected')) {
|
if (connectMyDeviceBtn.classList.contains('is-connected')) {
|
||||||
Comms.disconnectDevice();
|
Comms.disconnectDevice();
|
||||||
|
@ -364,6 +372,45 @@ librarySearchInput.addEventListener('input', evt => {
|
||||||
refreshLibrary();
|
refreshLibrary();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================== My Files
|
||||||
|
|
||||||
|
function refreshMyFS() {
|
||||||
|
var panelbody = document.querySelector("#myfscontainer .panel-body");
|
||||||
|
var tab = document.querySelector("#tab-myfscontainer a");
|
||||||
|
tab.setAttribute("data-badge", files.length);
|
||||||
|
panelbody.innerHTML = `
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>${
|
||||||
|
files.map(file =>
|
||||||
|
`<tr data-name="${file}"><td>${escapeHtml(file)}</td><td>${fileType(file).name}</td></li>`
|
||||||
|
).join("")}
|
||||||
|
</tbody>`;
|
||||||
|
|
||||||
|
htmlToArray(panelbody.getElementsByTagName("tr")).forEach(row => {
|
||||||
|
row.addEventListener("click",event => {
|
||||||
|
var name = event.target.closest('tr').dataset.name;
|
||||||
|
const type = fileType(name);
|
||||||
|
Comms.readFile(name).then(content => content.length && saveAs(new Blob([content], type), name));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileType(file) {
|
||||||
|
switch (file[0]) {
|
||||||
|
case "+": return { name: "App descriptor", type: "application/json;charset=utf-8" };
|
||||||
|
case "*": return { name: "App icon", type: "text/plain;charset=utf-8" };
|
||||||
|
case "-": return { name: "App code", type: "application/javascript;charset=utf-8" };
|
||||||
|
case "=": return { name: "Boot-time code", type: "application/javascript;charset=utf-8" };
|
||||||
|
default: return { name: "Plain", type: "text/plain;charset=utf-8" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================== About
|
// =========================================== About
|
||||||
|
|
||||||
document.getElementById("settime").addEventListener("click",event=>{
|
document.getElementById("settime").addEventListener("click",event=>{
|
||||||
|
|
Loading…
Reference in New Issue