feat: "my files" tab to list and download files

pull/72/head
feugy 2019-12-24 14:47:02 +01:00
parent d306209da7
commit 5278ec9929
3 changed files with 110 additions and 14 deletions

View File

@ -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));
});
});
});
} }
}; };

View File

@ -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>

View File

@ -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
appsInstalled = appJSON; return Comms.getInstalledApps()
handleConnectionChange(true); .then(appJSON => {
refreshMyApps(); appsInstalled = appJSON;
refreshLibrary(); refreshMyApps();
}); 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=>{