Added more serious linting for app loader (#451)

master
Gordon Williams 2020-05-28 08:20:41 +01:00
parent 2fd2caaacc
commit 324c93de36
8 changed files with 167 additions and 137 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
js/espruinotools.js

View File

@ -1 +0,0 @@
espruinotools.js

View File

@ -10,10 +10,40 @@
{ {
"SwitchCase": 1 "SwitchCase": 1
} }
] ],
"no-undef": "warn",
"no-redeclare": "warn",
"no-var": "warn",
"no-unused-vars":"off" // we define stuff to use in other scripts
}, },
"env": { "env": {
"browser": true, "browser": true,
"node": true "node": true
},
"extends": "eslint:recommended",
"globals": {
"btoa": "writable",
"Espruino": "writable",
"htmlElement": "readonly",
"Puck": "readonly",
"escapeHtml": "readonly",
"htmlToArray": "readonly",
"heatshrink": "readonly",
"Puck": "readonly",
"Promise": "readonly",
"Comms": "readonly",
"Progress": "readonly",
"showToast": "readonly",
"showPrompt": "readonly",
"httpGet": "readonly",
"getVersionInfo": "readonly",
"AppInfo": "readonly",
"marked": "readonly",
"appSorter": "readonly",
"Uint8Array" : "readonly",
"SETTINGS" : "readonly",
"globToRegex" : "readonly",
"toJS" : "readonly"
} }
} }

View File

@ -7,16 +7,16 @@ if (typeof btoa==="undefined") {
// Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64) // Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64)
function toJS(txt) { function toJS(txt) {
var json = JSON.stringify(txt); let json = JSON.stringify(txt);
var b64 = "atob("+JSON.stringify(btoa(json))+")"; let b64 = "atob("+JSON.stringify(btoa(json))+")";
var js = b64.length < json.length ? b64 : json; let js = b64.length < json.length ? b64 : json;
if (typeof heatshrink !== "undefined") { if (typeof heatshrink !== "undefined") {
var ua = new Uint8Array(txt.length); let ua = new Uint8Array(txt.length);
for (var i=0;i<txt.length;i++) ua[i] = txt.charCodeAt(i); for (let i=0;i<txt.length;i++) ua[i] = txt.charCodeAt(i);
var c = heatshrink.compress(ua); let c = heatshrink.compress(ua);
var cs = ""; let cs = "";
for (var i=0;i<c.length;i++) for (let i=0;i<c.length;i++)
cs += String.fromCharCode(c[i]); cs += String.fromCharCode(c[i]);
cs = 'require("heatshrink").decompress(atob("'+btoa(cs)+'"))'; cs = 'require("heatshrink").decompress(atob("'+btoa(cs)+'"))';
// if it's more than a little smaller, use compressed version // if it's more than a little smaller, use compressed version
@ -30,7 +30,7 @@ function toJS(txt) {
if ("undefined"!=typeof module) if ("undefined"!=typeof module)
Espruino = require("./espruinotools.js"); Espruino = require("./espruinotools.js");
var AppInfo = { let AppInfo = {
/* Get files needed for app. /* Get files needed for app.
options = { options = {
fileGetter : callback for getting URL, fileGetter : callback for getting URL,
@ -79,9 +79,9 @@ var AppInfo = {
} else { } else {
let code = storageFile.content; let code = storageFile.content;
// write code in chunks, in case it is too big to fit in RAM (fix #157) // write code in chunks, in case it is too big to fit in RAM (fix #157)
var CHUNKSIZE = 4096; let CHUNKSIZE = 4096;
storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`; storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`;
for (var i=CHUNKSIZE;i<code.length;i+=CHUNKSIZE) for (let i=CHUNKSIZE;i<code.length;i+=CHUNKSIZE)
storageFile.cmd += `\n\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`; storageFile.cmd += `\n\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`;
} }
}); });
@ -91,12 +91,12 @@ var AppInfo = {
}, },
createAppJSON : (app, fileContents) => { createAppJSON : (app, fileContents) => {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var appJSONName = app.id+".info"; let appJSONName = app.id+".info";
// Check we don't already have a JSON file! // Check we don't already have a JSON file!
var appJSONFile = fileContents.find(f=>f.name==appJSONName); let appJSONFile = fileContents.find(f=>f.name==appJSONName);
if (appJSONFile) reject("App JSON file explicitly specified!"); if (appJSONFile) reject("App JSON file explicitly specified!");
// Now actually create the app JSON // Now actually create the app JSON
var json = { let json = {
id : app.id id : app.id
}; };
if (app.shortName) json.name = app.shortName; if (app.shortName) json.name = app.shortName;
@ -108,7 +108,7 @@ var AppInfo = {
json.icon = app.id+".img"; json.icon = app.id+".img";
if (app.sortorder) json.sortorder = app.sortorder; if (app.sortorder) json.sortorder = app.sortorder;
if (app.version) json.version = app.version; if (app.version) json.version = app.version;
var fileList = fileContents.map(storageFile=>storageFile.name); let fileList = fileContents.map(storageFile=>storageFile.name);
fileList.unshift(appJSONName); // do we want this? makes life easier! fileList.unshift(appJSONName); // do we want this? makes life easier!
json.files = fileList.join(","); json.files = fileList.join(",");
if ('data' in app) { if ('data' in app) {

View File

@ -1,7 +1,7 @@
Puck.debug=3; Puck.debug=3;
// FIXME: use UART lib so that we handle errors properly // FIXME: use UART lib so that we handle errors properly
var Comms = { let Comms = {
reset : (opt) => new Promise((resolve,reject) => { reset : (opt) => new Promise((resolve,reject) => {
Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => { Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => {
if (result===null) return reject("Connection failed"); if (result===null) return reject("Connection failed");
@ -16,13 +16,13 @@ var Comms = {
}).then(fileContents => { }).then(fileContents => {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
console.log("uploadApp",fileContents.map(f=>f.name).join(", ")); console.log("uploadApp",fileContents.map(f=>f.name).join(", "));
var maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1; let maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1;
var currentBytes = 0; let currentBytes = 0;
var appInfoFileName = app.id+".info"; let appInfoFileName = app.id+".info";
var appInfoFile = fileContents.find(f=>f.name==appInfoFileName); let appInfoFile = fileContents.find(f=>f.name==appInfoFileName);
if (!appInfoFile) reject(`${appInfoFileName} not found`); if (!appInfoFile) reject(`${appInfoFileName} not found`);
var appInfo = JSON.parse(appInfoFile.content); let appInfo = JSON.parse(appInfoFile.content);
// Upload each file one at a time // Upload each file one at a time
function doUploadFiles() { function doUploadFiles() {
@ -35,14 +35,14 @@ var Comms = {
}); });
return; return;
} }
var f = fileContents.shift(); let f = fileContents.shift();
console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`); console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`);
// Chould check CRC here if needed instead of returning 'OK'... // Chould check CRC here if needed instead of returning 'OK'...
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) // E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
var cmds = f.cmd.split("\n"); let cmds = f.cmd.split("\n");
function uploadCmd() { function uploadCmd() {
if (!cmds.length) return doUploadFiles(); if (!cmds.length) return doUploadFiles();
var cmd = cmds.shift(); let cmd = cmds.shift();
Progress.show({ Progress.show({
min:currentBytes / maxBytes, min:currentBytes / maxBytes,
max:(currentBytes+cmd.length) / maxBytes}); max:(currentBytes+cmd.length) / maxBytes});
@ -84,7 +84,7 @@ var Comms = {
Progress.hide({sticky:true}); Progress.hide({sticky:true});
return reject(""); return reject("");
} }
Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => { Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => {
Progress.hide({sticky:true}); Progress.hide({sticky:true});
try { try {
appList = JSON.parse(appList); appList = JSON.parse(appList);
@ -152,9 +152,9 @@ var Comms = {
}, },
setTime : () => { setTime : () => {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var d = new Date(); let d = new Date();
var tz = d.getTimezoneOffset()/-60 let tz = d.getTimezoneOffset()/-60
var cmd = '\x03\x10setTime('+(d.getTime()/1000)+');'; let cmd = '\x03\x10setTime('+(d.getTime()/1000)+');';
// in 1v93 we have timezones too // in 1v93 we have timezones too
cmd += 'E.setTimeZone('+tz+');'; cmd += 'E.setTimeZone('+tz+');';
cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n"; cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n";
@ -165,17 +165,17 @@ var Comms = {
}); });
}, },
disconnectDevice: () => { disconnectDevice: () => {
var connection = Puck.getConnection(); let connection = Puck.getConnection();
if (!connection) return; if (!connection) return;
connection.close(); connection.close();
}, },
watchConnectionChange : cb => { watchConnectionChange : cb => {
var connected = Puck.isConnected(); let connected = Puck.isConnected();
//TODO Switch to an event listener when Puck will support it //TODO Switch to an event listener when Puck will support it
var interval = setInterval(() => { let interval = setInterval(() => {
if (connected === Puck.isConnected()) return; if (connected === Puck.isConnected()) return;
connected = Puck.isConnected(); connected = Puck.isConnected();
@ -220,20 +220,20 @@ var Comms = {
readStorageFile : (filename) => { // StorageFiles are different to normal storage entries readStorageFile : (filename) => { // StorageFiles are different to normal storage entries
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
// Use "\xFF" to signal end of file (can't occur in files anyway) // Use "\xFF" to signal end of file (can't occur in files anyway)
var fileContent = ""; let fileContent = "";
var fileSize = undefined; let fileSize = undefined;
var connection = Puck.getConnection(); let connection = Puck.getConnection();
connection.received = ""; connection.received = "";
connection.cb = function(d) { connection.cb = function(d) {
var finished = false; let finished = false;
var eofIndex = d.indexOf("\xFF"); let eofIndex = d.indexOf("\xFF");
if (eofIndex>=0) { if (eofIndex>=0) {
finished = true; finished = true;
d = d.substr(0,eofIndex); d = d.substr(0,eofIndex);
} }
fileContent += d; fileContent += d;
if (fileSize === undefined) { if (fileSize === undefined) {
var newLineIdx = fileContent.indexOf("\n"); let newLineIdx = fileContent.indexOf("\n");
if (newLineIdx>=0) { if (newLineIdx>=0) {
fileSize = parseInt(fileContent.substr(0,newLineIdx)); fileSize = parseInt(fileContent.substr(0,newLineIdx));
console.log("File size is "+fileSize); console.log("File size is "+fileSize);

View File

@ -1,12 +1,12 @@
var appJSON = []; // List of apps and info from apps.json let appJSON = []; // List of apps and info from apps.json
var appsInstalled = []; // list of app JSON let appsInstalled = []; // list of app JSON
var appSortInfo = {}; // list of data to sort by, from appdates.csv { created, modified } let appSortInfo = {}; // list of data to sort by, from appdates.csv { created, modified }
var files = []; // list of files on Bangle let files = []; // list of files on Bangle
var DEFAULTSETTINGS = { let DEFAULTSETTINGS = {
pretokenise : true, pretokenise : true,
favourites : ["boot","launch","setting"] favourites : ["boot","launch","setting"]
}; };
var SETTINGS = JSON.parse(JSON.stringify(DEFAULTSETTINGS)); // clone let SETTINGS = JSON.parse(JSON.stringify(DEFAULTSETTINGS)); // clone
httpGet("apps.json").then(apps=>{ httpGet("apps.json").then(apps=>{
try { try {
@ -23,7 +23,7 @@ httpGet("apps.json").then(apps=>{
httpGet("appdates.csv").then(csv=>{ httpGet("appdates.csv").then(csv=>{
document.querySelector(".sort-nav").classList.remove("hidden"); document.querySelector(".sort-nav").classList.remove("hidden");
csv.split("\n").forEach(line=>{ csv.split("\n").forEach(line=>{
var l = line.split(","); let l = line.split(",");
appSortInfo[l[0]] = { appSortInfo[l[0]] = {
created : Date.parse(l[1]), created : Date.parse(l[1]),
modified : Date.parse(l[2]) modified : Date.parse(l[2])
@ -35,7 +35,7 @@ httpGet("appdates.csv").then(csv=>{
// =========================================== Top Navigation // =========================================== Top Navigation
function showChangeLog(appid) { function showChangeLog(appid) {
var app = appNameToApp(appid); let app = appNameToApp(appid);
function show(contents) { function show(contents) {
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{}); showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});
} }
@ -43,9 +43,9 @@ function showChangeLog(appid) {
then(show).catch(()=>show("No Change Log available")); then(show).catch(()=>show("No Change Log available"));
} }
function showReadme(appid) { function showReadme(appid) {
var app = appNameToApp(appid); let app = appNameToApp(appid);
var appPath = `apps/${appid}/`; let appPath = `apps/${appid}/`;
var markedOptions = { baseUrl : appPath }; let markedOptions = { baseUrl : appPath };
function show(contents) { function show(contents) {
if (!contents) return; if (!contents) return;
showPrompt(app.name + " Documentation", marked(contents, markedOptions), {ok: true}, false).catch(() => {}); showPrompt(app.name + " Documentation", marked(contents, markedOptions), {ok: true}, false).catch(() => {});
@ -56,7 +56,7 @@ function handleCustomApp(appTemplate) {
// Pops up an IFRAME that allows an app to be customised // Pops up an IFRAME that allows an app to be customised
if (!appTemplate.custom) throw new Error("App doesn't have custom HTML"); if (!appTemplate.custom) throw new Error("App doesn't have custom HTML");
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active"> let modal = htmlElement(`<div class="modal active">
<a href="#close" class="modal-overlay " aria-label="Close"></a> <a href="#close" class="modal-overlay " aria-label="Close"></a>
<div class="modal-container" style="height:100%"> <div class="modal-container" style="height:100%">
<div class="modal-header"> <div class="modal-header">
@ -79,10 +79,10 @@ function handleCustomApp(appTemplate) {
}); });
}); });
var iframe = modal.getElementsByTagName("iframe")[0]; let iframe = modal.getElementsByTagName("iframe")[0];
iframe.contentWindow.addEventListener("message", function(event) { iframe.contentWindow.addEventListener("message", function(event) {
var appFiles = event.data; let appFiles = event.data;
var app = JSON.parse(JSON.stringify(appTemplate)); // clone template let app = JSON.parse(JSON.stringify(appTemplate)); // clone template
// copy extra keys from appFiles // copy extra keys from appFiles
Object.keys(appFiles).forEach(k => { Object.keys(appFiles).forEach(k => {
if (k!="storage") app[k] = appFiles[k] if (k!="storage") app[k] = appFiles[k]
@ -108,7 +108,7 @@ function handleAppInterface(app) {
// IFRAME interface window that can be used to get data from the app // IFRAME interface window that can be used to get data from the app
if (!app.interface) throw new Error("App doesn't have interface HTML"); if (!app.interface) throw new Error("App doesn't have interface HTML");
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active"> let modal = htmlElement(`<div class="modal active">
<a href="#close" class="modal-overlay " aria-label="Close"></a> <a href="#close" class="modal-overlay " aria-label="Close"></a>
<div class="modal-container" style="height:100%"> <div class="modal-container" style="height:100%">
<div class="modal-header"> <div class="modal-header">
@ -130,11 +130,11 @@ function handleAppInterface(app) {
//reject("Window closed"); //reject("Window closed");
}); });
}); });
var iframe = modal.getElementsByTagName("iframe")[0]; let iframe = modal.getElementsByTagName("iframe")[0];
iframe.onload = function() { iframe.onload = function() {
var iwin = iframe.contentWindow; let iwin = iframe.contentWindow;
iwin.addEventListener("message", function(event) { iwin.addEventListener("message", function(event) {
var msg = event.data; let msg = event.data;
if (msg.type=="eval") { if (msg.type=="eval") {
Puck.eval(msg.data, function(result) { Puck.eval(msg.data, function(result) {
iwin.postMessage({ iwin.postMessage({
@ -168,7 +168,7 @@ function handleAppInterface(app) {
} }
function changeAppFavourite(favourite, app) { function changeAppFavourite(favourite, app) {
var favourites = SETTINGS.favourites; let favourites = SETTINGS.favourites;
if (favourite) { if (favourite) {
SETTINGS.favourites = SETTINGS.favourites.concat([app.id]); SETTINGS.favourites = SETTINGS.favourites.concat([app.id]);
} else { } else {
@ -197,29 +197,29 @@ function showTab(tabname) {
// =========================================== Library // =========================================== Library
// Can't use chip.attributes.filterid.value here because Safari/Apple's WebView doesn't handle it // Can't use chip.attributes.filterid.value here because Safari/Apple's WebView doesn't handle it
var chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.getAttribute("filterid")); let chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.getAttribute("filterid"));
var hash = window.location.hash ? window.location.hash.slice(1) : ''; let hash = window.location.hash ? window.location.hash.slice(1) : '';
var activeFilter = !!~chips.indexOf(hash) ? hash : ''; let activeFilter = ~chips.indexOf(hash) ? hash : '';
var activeSort = ''; let activeSort = '';
var currentSearch = activeFilter ? '' : hash; let currentSearch = activeFilter ? '' : hash;
function refreshFilter(){ function refreshFilter(){
var filtersContainer = document.querySelector("#librarycontainer .filter-nav"); let filtersContainer = document.querySelector("#librarycontainer .filter-nav");
filtersContainer.querySelector('.active').classList.remove('active'); filtersContainer.querySelector('.active').classList.remove('active');
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active'); if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active');
else filtersContainer.querySelector('.chip[filterid]').classList.add('active'); else filtersContainer.querySelector('.chip[filterid]').classList.add('active');
} }
function refreshSort(){ function refreshSort(){
var sortContainer = document.querySelector("#librarycontainer .sort-nav"); let sortContainer = document.querySelector("#librarycontainer .sort-nav");
sortContainer.querySelector('.active').classList.remove('active'); sortContainer.querySelector('.active').classList.remove('active');
if(activeSort) sortContainer.querySelector('.chip[sortid="'+activeSort+'"]').classList.add('active'); if(activeSort) sortContainer.querySelector('.chip[sortid="'+activeSort+'"]').classList.add('active');
else sortContainer.querySelector('.chip[sortid]').classList.add('active'); else sortContainer.querySelector('.chip[sortid]').classList.add('active');
} }
function refreshLibrary() { function refreshLibrary() {
var panelbody = document.querySelector("#librarycontainer .panel-body"); let panelbody = document.querySelector("#librarycontainer .panel-body");
var visibleApps = appJSON; let visibleApps = appJSON;
var favourites = SETTINGS.favourites; let favourites = SETTINGS.favourites;
if (activeFilter) { if (activeFilter) {
if ( activeFilter == "favourites" ) { if ( activeFilter == "favourites" ) {
@ -241,17 +241,17 @@ function refreshLibrary() {
} }
panelbody.innerHTML = visibleApps.map((app,idx) => { panelbody.innerHTML = visibleApps.map((app,idx) => {
var appInstalled = appsInstalled.find(a=>a.id==app.id); let appInstalled = appsInstalled.find(a=>a.id==app.id);
var version = getVersionInfo(app, appInstalled); let version = getVersionInfo(app, appInstalled);
var versionInfo = version.text; let versionInfo = version.text;
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>"; if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
var readme = `<a class="c-hand" onclick="showReadme('${app.id}')">Read more...</a>`; let readme = `<a class="c-hand" onclick="showReadme('${app.id}')">Read more...</a>`;
var favourite = favourites.find(e => e == app.id); let favourite = favourites.find(e => e == app.id);
var username = "espruino"; let username = "espruino";
var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
if(githubMatch) username = githubMatch[1]; if(githubMatch) username = githubMatch[1];
var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`; let url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`;
return `<div class="tile column col-6 col-sm-12 col-xs-12"> return `<div class="tile column col-6 col-sm-12 col-xs-12">
<div class="tile-icon"> <div class="tile-icon">
@ -274,27 +274,27 @@ function refreshLibrary() {
</div> </div>
`;}).join(""); `;}).join("");
// set badge up top // set badge up top
var tab = document.querySelector("#tab-librarycontainer a"); let tab = document.querySelector("#tab-librarycontainer a");
tab.classList.add("badge"); tab.classList.add("badge");
tab.setAttribute("data-badge", appJSON.length); tab.setAttribute("data-badge", appJSON.length);
htmlToArray(panelbody.getElementsByTagName("button")).forEach(button => { htmlToArray(panelbody.getElementsByTagName("button")).forEach(button => {
button.addEventListener("click",event => { button.addEventListener("click",event => {
var button = event.currentTarget; let button = event.currentTarget;
var icon = button.firstChild; let icon = button.firstChild;
var appid = button.getAttribute("appid"); let appid = button.getAttribute("appid");
var app = appNameToApp(appid); let app = appNameToApp(appid);
if (!app) throw new Error("App "+appid+" not found"); if (!app) throw new Error("App "+appid+" not found");
// check icon to figure out what we should do // check icon to figure out what we should do
if (icon.classList.contains("icon-share")) { if (icon.classList.contains("icon-share")) {
// emulator // emulator
var file = app.storage.find(f=>f.name.endsWith('.js')); let file = app.storage.find(f=>f.name.endsWith('.js'));
if (!file) { if (!file) {
console.error("No entrypoint found for "+appid); console.error("No entrypoint found for "+appid);
return; return;
} }
var baseurl = window.location.href; let baseurl = window.location.href;
baseurl = baseurl.substr(0,baseurl.lastIndexOf("/")); baseurl = baseurl.substr(0,baseurl.lastIndexOf("/"));
var url = baseurl+"/apps/"+app.id+"/"+file.url; let url = baseurl+"/apps/"+app.id+"/"+file.url;
window.open(`https://espruino.com/ide/emulator.html?codeurl=${url}&upload`); window.open(`https://espruino.com/ide/emulator.html?codeurl=${url}&upload`);
} else if (icon.classList.contains("icon-upload")) { } else if (icon.classList.contains("icon-upload")) {
// upload // upload
@ -421,7 +421,7 @@ function updateApp(app) {
function appNameToApp(appName) { function appNameToApp(appName) {
var app = appJSON.find(app=>app.id==appName); let app = appJSON.find(app=>app.id==appName);
if (app) return app; if (app) return app;
/* If app not known, add just one file /* If app not known, add just one file
which is the JSON - so we'll remove it from which is the JSON - so we'll remove it from
@ -436,8 +436,8 @@ function appNameToApp(appName) {
} }
function showLoadingIndicator(id) { function showLoadingIndicator(id) {
var panelbody = document.querySelector(`#${id} .panel-body`); let panelbody = document.querySelector(`#${id} .panel-body`);
var tab = document.querySelector(`#tab-${id} a`); let 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", "");
@ -446,9 +446,9 @@ function showLoadingIndicator(id) {
} }
function getAppsToUpdate() { function getAppsToUpdate() {
var appsToUpdate = []; let appsToUpdate = [];
appsInstalled.forEach(appInstalled => { appsInstalled.forEach(appInstalled => {
var app = appNameToApp(appInstalled.id); let app = appNameToApp(appInstalled.id);
if (app.version != appInstalled.version) if (app.version != appInstalled.version)
appsToUpdate.push(app); appsToUpdate.push(app);
}); });
@ -456,14 +456,14 @@ function getAppsToUpdate() {
} }
function refreshMyApps() { function refreshMyApps() {
var panelbody = document.querySelector("#myappscontainer .panel-body"); let panelbody = document.querySelector("#myappscontainer .panel-body");
panelbody.innerHTML = appsInstalled.map(appInstalled => { panelbody.innerHTML = appsInstalled.map(appInstalled => {
var app = appNameToApp(appInstalled.id); let app = appNameToApp(appInstalled.id);
var version = getVersionInfo(app, appInstalled); let version = getVersionInfo(app, appInstalled);
var username = "espruino"; let username = "espruino";
var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
if(githubMatch) username = githubMatch[1]; if(githubMatch) username = githubMatch[1];
var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`; let url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`;
return `<div class="tile column col-6 col-sm-12 col-xs-12"> return `<div class="tile column col-6 col-sm-12 col-xs-12">
<div class="tile-icon"> <div class="tile-icon">
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure> <figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure>
@ -482,10 +482,10 @@ function refreshMyApps() {
`}).join(""); `}).join("");
htmlToArray(panelbody.getElementsByTagName("button")).forEach(button => { htmlToArray(panelbody.getElementsByTagName("button")).forEach(button => {
button.addEventListener("click",event => { button.addEventListener("click",event => {
var button = event.currentTarget; let button = event.currentTarget;
var icon = button.firstChild; let icon = button.firstChild;
var appid = button.getAttribute("appid"); let appid = button.getAttribute("appid");
var app = appNameToApp(appid); let app = appNameToApp(appid);
if (!app) throw new Error("App "+appid+" not found"); if (!app) throw new Error("App "+appid+" not found");
// check icon to figure out what we should do // check icon to figure out what we should do
if (icon.classList.contains("icon-delete")) removeApp(app); if (icon.classList.contains("icon-delete")) removeApp(app);
@ -493,9 +493,9 @@ function refreshMyApps() {
if (icon.classList.contains("icon-download")) handleAppInterface(app); if (icon.classList.contains("icon-download")) handleAppInterface(app);
}); });
}); });
var appsToUpdate = getAppsToUpdate(); let appsToUpdate = getAppsToUpdate();
var tab = document.querySelector("#tab-myappscontainer a"); let tab = document.querySelector("#tab-myappscontainer a");
var updateApps = document.querySelector("#myappscontainer .updateapps"); let updateApps = document.querySelector("#myappscontainer .updateapps");
if (appsToUpdate.length) { if (appsToUpdate.length) {
updateApps.innerHTML = `Update ${appsToUpdate.length} apps`; updateApps.innerHTML = `Update ${appsToUpdate.length} apps`;
updateApps.classList.remove("hidden"); updateApps.classList.remove("hidden");
@ -529,10 +529,10 @@ function getInstalledApps(refresh) {
/// Removes everything and install the given apps, eg: installMultipleApps(["boot","mclock"], "minimal") /// Removes everything and install the given apps, eg: installMultipleApps(["boot","mclock"], "minimal")
function installMultipleApps(appIds, promptName) { function installMultipleApps(appIds, promptName) {
var apps = appIds.map( appid => appJSON.find(app=>app.id==appid) ); let apps = appIds.map( appid => appJSON.find(app=>app.id==appid) );
if (apps.some(x=>x===undefined)) if (apps.some(x=>x===undefined))
return Promise.reject("Not all apps found"); return Promise.reject("Not all apps found");
var appCount = apps.length; let appCount = apps.length;
return showPrompt("Install Defaults",`Remove everything and install ${promptName} apps?`).then(() => { return showPrompt("Install Defaults",`Remove everything and install ${promptName} apps?`).then(() => {
return Comms.removeAllApps(); return Comms.removeAllApps();
}).then(()=>{ }).then(()=>{
@ -541,7 +541,7 @@ function installMultipleApps(appIds, promptName) {
showToast(`Existing apps removed. Installing ${appCount} apps...`); showToast(`Existing apps removed. Installing ${appCount} apps...`);
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
function upload() { function upload() {
var app = apps.shift(); let app = apps.shift();
if (app===undefined) return resolve(); if (app===undefined) return resolve();
Progress.show({title:`${app.name} (${appCount-apps.length}/${appCount})`,sticky:true}); Progress.show({title:`${app.name} (${appCount-apps.length}/${appCount})`,sticky:true});
Comms.uploadApp(app,"skip_reset").then((appJSON) => { Comms.uploadApp(app,"skip_reset").then((appJSON) => {
@ -564,7 +564,7 @@ function installMultipleApps(appIds, promptName) {
}); });
} }
var connectMyDeviceBtn = document.getElementById("connectmydevice"); let connectMyDeviceBtn = document.getElementById("connectmydevice");
function handleConnectionChange(connected) { function handleConnectionChange(connected) {
connectMyDeviceBtn.textContent = connected ? 'Disconnect' : 'Connect'; connectMyDeviceBtn.textContent = connected ? 'Disconnect' : 'Connect';
@ -577,11 +577,11 @@ htmlToArray(document.querySelectorAll(".btn.refresh")).map(button => button.addE
}); });
})); }));
htmlToArray(document.querySelectorAll(".btn.updateapps")).map(button => button.addEventListener("click", () => { htmlToArray(document.querySelectorAll(".btn.updateapps")).map(button => button.addEventListener("click", () => {
var appsToUpdate = getAppsToUpdate(); let appsToUpdate = getAppsToUpdate();
var count = appsToUpdate.length; let count = appsToUpdate.length;
function updater() { function updater() {
if (!appsToUpdate.length) return; if (!appsToUpdate.length) return;
var app = appsToUpdate.pop(); let app = appsToUpdate.pop();
return updateApp(app).then(function() { return updateApp(app).then(function() {
return updater(); return updater();
}); });
@ -603,7 +603,7 @@ connectMyDeviceBtn.addEventListener("click", () => {
}); });
Comms.watchConnectionChange(handleConnectionChange); Comms.watchConnectionChange(handleConnectionChange);
var filtersContainer = document.querySelector("#librarycontainer .filter-nav"); let filtersContainer = document.querySelector("#librarycontainer .filter-nav");
filtersContainer.addEventListener('click', ({ target }) => { filtersContainer.addEventListener('click', ({ target }) => {
if (target.classList.contains('active')) return; if (target.classList.contains('active')) return;
@ -613,14 +613,14 @@ filtersContainer.addEventListener('click', ({ target }) => {
window.location.hash = activeFilter; window.location.hash = activeFilter;
}); });
var librarySearchInput = document.querySelector("#searchform input"); let librarySearchInput = document.querySelector("#searchform input");
librarySearchInput.value = currentSearch; librarySearchInput.value = currentSearch;
librarySearchInput.addEventListener('input', evt => { librarySearchInput.addEventListener('input', evt => {
currentSearch = evt.target.value.toLowerCase(); currentSearch = evt.target.value.toLowerCase();
refreshLibrary(); refreshLibrary();
}); });
var sortContainer = document.querySelector("#librarycontainer .sort-nav"); let sortContainer = document.querySelector("#librarycontainer .sort-nav");
sortContainer.addEventListener('click', ({ target }) => { sortContainer.addEventListener('click', ({ target }) => {
if (target.classList.contains('active')) return; if (target.classList.contains('active')) return;
@ -646,13 +646,13 @@ if (window.location.host=="banglejs.com") {
} }
// Settings // Settings
var SETTINGS_HOOKS = {}; // stuff to get called when a setting is loaded let SETTINGS_HOOKS = {}; // stuff to get called when a setting is loaded
/// Load settings and update controls /// Load settings and update controls
function loadSettings() { function loadSettings() {
var j = localStorage.getItem("settings"); let j = localStorage.getItem("settings");
if (typeof j != "string") return; if (typeof j != "string") return;
try { try {
var s = JSON.parse(j); let s = JSON.parse(j);
Object.keys(s).forEach( k => { Object.keys(s).forEach( k => {
SETTINGS[k]=s[k]; SETTINGS[k]=s[k];
if (SETTINGS_HOOKS[k]) SETTINGS_HOOKS[k](); if (SETTINGS_HOOKS[k]) SETTINGS_HOOKS[k]();
@ -668,7 +668,7 @@ function saveSettings() {
} }
// Link in settings DOM elements // Link in settings DOM elements
function settingsCheckbox(id, name) { function settingsCheckbox(id, name) {
var setting = document.getElementById(id); let setting = document.getElementById(id);
function update() { function update() {
setting.checked = SETTINGS[name]; setting.checked = SETTINGS[name];
} }
@ -720,7 +720,7 @@ document.getElementById("installdefault").addEventListener("click",event=>{
// Install all favourite apps in one go // Install all favourite apps in one go
document.getElementById("installfavourite").addEventListener("click",event=>{ document.getElementById("installfavourite").addEventListener("click",event=>{
var favApps = SETTINGS.favourites; let favApps = SETTINGS.favourites;
installMultipleApps(favApps, "favourite").catch(err=>{ installMultipleApps(favApps, "favourite").catch(err=>{
Progress.hide({sticky:true}); Progress.hide({sticky:true});
showToast("App Install failed, "+err,"error"); showToast("App Install failed, "+err,"error");

View File

@ -1,7 +1,7 @@
// General UI tools (progress bar, toast, prompt) // General UI tools (progress bar, toast, prompt)
/// Handle progress bars /// Handle progress bars
var Progress = { let Progress = {
domElement : null, // the DOM element domElement : null, // the DOM element
sticky : false, // Progress.show({..., sticky:true}) don't remove until Progress.hide({sticky:true}) sticky : false, // Progress.show({..., sticky:true}) don't remove until Progress.hide({sticky:true})
interval : undefined, // the interval used if Progress.show({progress:"animate"}) interval : undefined, // the interval used if Progress.show({progress:"animate"})
@ -18,11 +18,11 @@ var Progress = {
}) */ }) */
show : function(options) { show : function(options) {
options = options||{}; options = options||{};
var text = options.title; let text = options.title;
if (options.sticky) Progress.sticky = true; if (options.sticky) Progress.sticky = true;
if (options.min!==undefined) Progress.min = options.min; if (options.min!==undefined) Progress.min = options.min;
if (options.max!==undefined) Progress.max = options.max; if (options.max!==undefined) Progress.max = options.max;
var percent = options.percent; let percent = options.percent;
if (percent!==undefined) if (percent!==undefined)
percent = Progress.min*100 + (Progress.max-Progress.min)*percent; percent = Progress.min*100 + (Progress.max-Progress.min)*percent;
if (!Progress.domElement) { if (!Progress.domElement) {
@ -39,7 +39,7 @@ var Progress = {
percent = 0; percent = 0;
} }
var toastcontainer = document.getElementById("toastcontainer"); let toastcontainer = document.getElementById("toastcontainer");
Progress.domElement = htmlElement(`<div class="toast"> Progress.domElement = htmlElement(`<div class="toast">
${text ? `<div>${text}</div>`:``} ${text ? `<div>${text}</div>`:``}
<div class="bar bar-sm"> <div class="bar bar-sm">
@ -48,7 +48,7 @@ var Progress = {
</div>`); </div>`);
toastcontainer.append(Progress.domElement); toastcontainer.append(Progress.domElement);
} else { } else {
var pt=document.getElementById("Progress.domElement"); let pt=document.getElementById("Progress.domElement");
pt.setAttribute("aria-valuenow",percent); pt.setAttribute("aria-valuenow",percent);
pt.style.width = percent+"%"; pt.style.width = percent+"%";
} }
@ -76,20 +76,20 @@ Puck.writeProgress = function(charsSent, charsTotal) {
Progress.hide(); Progress.hide();
return; return;
} }
var percent = Math.round(charsSent*100/charsTotal); let percent = Math.round(charsSent*100/charsTotal);
Progress.show({percent: percent}); Progress.show({percent: percent});
} }
/// Show a 'toast' message for status /// Show a 'toast' message for status
function showToast(message, type) { function showToast(message, type) {
// toast-primary, toast-success, toast-warning or toast-error // toast-primary, toast-success, toast-warning or toast-error
var style = "toast-primary"; let style = "toast-primary";
if (type=="success") style = "toast-success"; if (type=="success") style = "toast-success";
else if (type=="error") style = "toast-error"; else if (type=="error") style = "toast-error";
else if (type=="warning") style = "toast-warning"; else if (type=="warning") style = "toast-warning";
else if (type!==undefined) console.log("showToast: unknown toast "+type); else if (type!==undefined) console.log("showToast: unknown toast "+type);
var toastcontainer = document.getElementById("toastcontainer"); let toastcontainer = document.getElementById("toastcontainer");
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`); let msgDiv = htmlElement(`<div class="toast ${style}"></div>`);
msgDiv.innerHTML = message; msgDiv.innerHTML = message;
toastcontainer.append(msgDiv); toastcontainer.append(msgDiv);
setTimeout(function() { setTimeout(function() {
@ -103,7 +103,7 @@ function showPrompt(title, text, buttons, shouldEscapeHtml) {
if (typeof(shouldEscapeHtml) === 'undefined' || shouldEscapeHtml === null) shouldEscapeHtml = true; if (typeof(shouldEscapeHtml) === 'undefined' || shouldEscapeHtml === null) shouldEscapeHtml = true;
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active"> let modal = htmlElement(`<div class="modal active">
<!--<a href="#close" class="modal-overlay" aria-label="Close"></a>--> <!--<a href="#close" class="modal-overlay" aria-label="Close"></a>-->
<div class="modal-container"> <div class="modal-container">
<div class="modal-header"> <div class="modal-header">
@ -133,7 +133,7 @@ function showPrompt(title, text, buttons, shouldEscapeHtml) {
htmlToArray(modal.getElementsByTagName("button")).forEach(button => { htmlToArray(modal.getElementsByTagName("button")).forEach(button => {
button.addEventListener("click",event => { button.addEventListener("click",event => {
event.preventDefault(); event.preventDefault();
var isYes = event.target.getAttribute("isyes")=="1"; let isYes = event.target.getAttribute("isyes")=="1";
if (isYes) resolve(); if (isYes) resolve();
else reject("User cancelled"); else reject("User cancelled");
modal.remove(); modal.remove();

View File

@ -1,5 +1,5 @@
function escapeHtml(text) { function escapeHtml(text) {
var map = { let map = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
@ -24,13 +24,13 @@ function htmlToArray(collection) {
return [].slice.call(collection); return [].slice.call(collection);
} }
function htmlElement(str) { function htmlElement(str) {
var div = document.createElement('div'); let div = document.createElement('div');
div.innerHTML = str.trim(); div.innerHTML = str.trim();
return div.firstChild; return div.firstChild;
} }
function httpGet(url) { function httpGet(url) {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var oReq = new XMLHttpRequest(); let oReq = new XMLHttpRequest();
oReq.addEventListener("load", () => { oReq.addEventListener("load", () => {
if (oReq.status==200) resolve(oReq.responseText) if (oReq.status==200) resolve(oReq.responseText)
else reject(oReq.status+" - "+oReq.statusText); else reject(oReq.status+" - "+oReq.statusText);
@ -51,8 +51,8 @@ function toJS(txt) {
function appSorter(a,b) { function appSorter(a,b) {
if (a.unknown || b.unknown) if (a.unknown || b.unknown)
return (a.unknown)? 1 : -1; return (a.unknown)? 1 : -1;
var sa = 0|a.sortorder; let sa = 0|a.sortorder;
var sb = 0|b.sortorder; let sb = 0|b.sortorder;
if (sa<sb) return -1; if (sa<sb) return -1;
if (sa>sb) return 1; if (sa>sb) return 1;
return (a.name==b.name) ? 0 : ((a.name<b.name) ? -1 : 1); return (a.name==b.name) ? 0 : ((a.name<b.name) ? -1 : 1);
@ -61,8 +61,8 @@ function appSorter(a,b) {
/* Given 2 JSON structures (1st from apps.json, 2nd from an installed app) /* Given 2 JSON structures (1st from apps.json, 2nd from an installed app)
work out what to display re: versions and if we can update */ work out what to display re: versions and if we can update */
function getVersionInfo(appListing, appInstalled) { function getVersionInfo(appListing, appInstalled) {
var versionText = ""; let versionText = "";
var canUpdate = false; let canUpdate = false;
function clicky(v) { function clicky(v) {
return `<a class="c-hand" onclick="showChangeLog('${appListing.id}')">${v}</a>`; return `<a class="c-hand" onclick="showChangeLog('${appListing.id}')">${v}</a>`;
} }