mirror of https://github.com/espruino/BangleApps
353 lines
14 KiB
HTML
353 lines
14 KiB
HTML
|
<!doctype html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="utf-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=0.8,maximum-scale=0.8, minimum-scale=0.8, shrink-to-fit=no">
|
||
|
<link rel="stylesheet" href="css/spectre.min.css">
|
||
|
<link rel="stylesheet" href="css/spectre-exp.min.css">
|
||
|
<link rel="stylesheet" href="css/spectre-icons.min.css">
|
||
|
<link rel="stylesheet" href="css/pwa.css">
|
||
|
<link rel="stylesheet" href="css/main.css">
|
||
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||
|
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
||
|
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
|
||
|
<link rel="manifest" href="site.webmanifest">
|
||
|
<link rel="mask-icon" href="img/safari-pinned-tab.svg" color="#5755d9">
|
||
|
<meta name="apple-mobile-web-app-title" content="BangleApps">
|
||
|
<meta name="application-name" content="BangleApps">
|
||
|
<meta name="msapplication-TileColor" content="#5755d9">
|
||
|
<meta name="theme-color" content="#5755d9">
|
||
|
<title>Bangle.js App Loader</title>
|
||
|
</head>
|
||
|
<body>
|
||
|
<!--<button id="test">Test</button>
|
||
|
<div id="status"></div>-->
|
||
|
|
||
|
<header class="navbar-primary navbar">
|
||
|
<section class="navbar-section" >
|
||
|
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2" ><img src="img/banglejs-logo-sml.png" alt="Bangle.js">
|
||
|
<div>App Loader</div></a>
|
||
|
<!-- <a href="#" class="btn btn-link">...</a> -->
|
||
|
</section>
|
||
|
<section class="navbar-section">
|
||
|
<button class="btn" id="connectmydevice">Connect</button>
|
||
|
</section>
|
||
|
<!--<section class="navbar-section">
|
||
|
<div class="input-group input-inline">
|
||
|
<input class="form-input" type="text" placeholder="search">
|
||
|
<button class="btn btn-primary input-group-btn">Search</button>
|
||
|
</div>
|
||
|
</section>-->
|
||
|
</header>
|
||
|
|
||
|
<div class="container" style="padding-top:4px">
|
||
|
<p id="requireHTTPS" class="hidden">
|
||
|
<b>STOP!</b> This page <b>must</b> be served over HTTPS. Please <a>reload this page via HTTPS</a>.
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
|
||
|
<ul class="tab tab-block" id="tab-navigate">
|
||
|
<li class="tab-item active" id="tab-librarycontainer">
|
||
|
<a href="javascript:showTab('librarycontainer')">Library</a>
|
||
|
</li>
|
||
|
<li class="tab-item" id="tab-myappscontainer">
|
||
|
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
||
|
</li>
|
||
|
<li class="tab-item" id="tab-morecontainer">
|
||
|
<a href="javascript:showTab('morecontainer')">More...</a>
|
||
|
</li>
|
||
|
</ul>
|
||
|
|
||
|
<div class="container" id="toastcontainer">
|
||
|
</div>
|
||
|
|
||
|
<div class="container apploader-tab" id="librarycontainer">
|
||
|
<div class="dropdown-container">
|
||
|
<div class="dropdown devicetype-nav">
|
||
|
<a href="#" class="btn btn-link dropdown-toggle" tabindex="0">
|
||
|
<span>All apps</span><i class="icon icon-caret"></i>
|
||
|
</a>
|
||
|
<!-- menu component -->
|
||
|
<ul class="menu">
|
||
|
<li class="menu-item"><a>All apps</a></li>
|
||
|
<li class="menu-item"><a dt="BANGLEJS">Bangle.js 1</a></li>
|
||
|
<li class="menu-item"><a dt="BANGLEJS2">Bangle.js 2</a></li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
<div class="filter-nav">
|
||
|
<label class="chip active" filterid="">Default</label>
|
||
|
<label class="chip" filterid="clock">Clocks</label>
|
||
|
<label class="chip" filterid="game">Games</label>
|
||
|
<label class="chip" filterid="tool">Tools</label>
|
||
|
<label class="chip" filterid="widget">Widgets</label>
|
||
|
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||
|
<label class="chip" filterid="outdoors">Outdoors</label>
|
||
|
<label class="chip" filterid="favourites">Favourites</label>
|
||
|
</div>
|
||
|
<div class="sort-nav hidden">
|
||
|
<span>Sort by:</span>
|
||
|
<label class="chip active" sortid="">None</label>
|
||
|
<label class="chip" sortid="created">New</label>
|
||
|
<label class="chip" sortid="modified">Updated</label>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="panel" style="clear:both">
|
||
|
<div class="panel-header">
|
||
|
<div class="input-group" id="searchform">
|
||
|
<input class="form-input" type="text" placeholder="Keywords...">
|
||
|
<button class="btn btn-primary input-group-btn">Search</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="panel-body columns"><!-- apps go here --></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="container apploader-tab" id="myappscontainer" style="display:none">
|
||
|
<div class="panel">
|
||
|
<div class="panel-header" style="text-align:right">
|
||
|
<button class="btn refresh">Refresh...</button>
|
||
|
<button class="btn btn-primary updateapps hidden">Update X apps</button>
|
||
|
</div>
|
||
|
<div class="panel-body columns"><!-- apps go here --></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="container apploader-tab" id="morecontainer" style="display:none">
|
||
|
<div class="hero bg-gray">
|
||
|
<div class="hero-body">
|
||
|
<a href="https://banglejs.com" target="_blank"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
|
||
|
<h2>App Loader</h2>
|
||
|
<p>A tool for uploading and removing apps from <a href="https://banglejs.com" target="_blank">Bangle.js Smart Watches</a></p>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="container" style="padding-top: 8px;">
|
||
|
<p><b>Can't connect?</b> Check out the <a href="https://www.espruino.com/Troubleshooting+Bangle.js" target="_blank">Bangle.js Troubleshooting page</a>
|
||
|
<p id="apploaderlinks"></p>
|
||
|
<p>Check out <a href="https://github.com/espruino/BangleApps" target="_blank">the Source on GitHub</a>, or
|
||
|
find out <a href="https://www.espruino.com/Bangle.js+App+Loader" target="_blank">how to add your own app</a></p>
|
||
|
<p>Using <a href="https://espruino.com/" target="_blank">Espruino</a>, Icons from <a href="https://icons8.com/" target="_blank">icons8.com</a></p>
|
||
|
|
||
|
<h3>Utilities</h3>
|
||
|
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
||
|
<button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button>
|
||
|
<button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button>
|
||
|
<button class="btn" id="installdefault">Install default apps</button>
|
||
|
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button></p>
|
||
|
<p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
|
||
|
<button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
|
||
|
<h3>Settings</h3>
|
||
|
<div class="form-group">
|
||
|
<label class="form-switch">
|
||
|
<input type="checkbox" id="settings-pretokenise">
|
||
|
<i class="form-icon"></i> Pretokenise apps before upload (smaller, faster apps)
|
||
|
</label>
|
||
|
<label class="form-switch">
|
||
|
<input type="checkbox" id="settings-settime">
|
||
|
<i class="form-icon"></i> Always update time when we connect
|
||
|
</label>
|
||
|
<div class="form-group">
|
||
|
<select class="form-select form-inline" id="settings-lang" style="width: 10em">
|
||
|
<option value="">None (English)</option>
|
||
|
</select> <span>Translations (<a href="https://github.com/espruino/BangleApps/issues/1311" target="_blank">BETA - more info</a>). Any apps that are uploaded to Bangle.js after changing this will have any text automatically translated.</span>
|
||
|
</div>
|
||
|
<button class="btn" id="defaultsettings">Default settings</button>
|
||
|
</div>
|
||
|
<div id="more-deviceinfo" style="display:none">
|
||
|
<h3>Device info</h3>
|
||
|
<div id="more-deviceinfo-content"></div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<footer class="floating hidden">
|
||
|
<!-- Install button, hidden by default -->
|
||
|
<div id="installContainer" class="hidden">
|
||
|
<button id="butInstall" type="button">
|
||
|
Install
|
||
|
</button>
|
||
|
</div>
|
||
|
</footer>
|
||
|
|
||
|
<script src="https://www.puck-js.com/puck.js"></script>
|
||
|
<script src="core/lib/marked.min.js"></script>
|
||
|
<script src="core/lib/espruinotools.js"></script>
|
||
|
<script src="core/lib/heatshrink.js"></script>
|
||
|
<script src="core/js/utils.js"></script>
|
||
|
<script src="loader.js"></script>
|
||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
||
|
<script src="backup.js"></script>
|
||
|
<script src="core/js/ui.js"></script>
|
||
|
<script src="core/js/comms.js"></script>
|
||
|
<script src="core/js/appinfo.js"></script>
|
||
|
<script src="core/js/index.js"></script>
|
||
|
<script src="core/js/pwa.js" defer></script>
|
||
|
<script>
|
||
|
/*Android = {
|
||
|
bangleTx : function(data) {
|
||
|
console.log("TX : "+JSON.stringify(data));
|
||
|
}
|
||
|
};*/
|
||
|
|
||
|
/*document.getElementById("test").addEventListener("click", function() {
|
||
|
console.log("Pressed");
|
||
|
Android.bangleTx("LED1.toggle();\n");
|
||
|
});*/
|
||
|
|
||
|
if (typeof Android!=="undefined") {
|
||
|
console.log("Running in Android, overwrite Puck library");
|
||
|
|
||
|
var isBusy = false;
|
||
|
var queue = [];
|
||
|
var connection = {
|
||
|
cb : function(data) {},
|
||
|
write : function(data, writecb) {
|
||
|
Android.bangleTx(data);
|
||
|
Puck.writeProgress(data.length, data.length);
|
||
|
if (writecb) setTimeout(writecb,10);
|
||
|
},
|
||
|
close : function() {},
|
||
|
received : "",
|
||
|
hadData : false
|
||
|
}
|
||
|
|
||
|
function bangleRx(data) {
|
||
|
// document.getElementById("status").innerText = "RX:"+data;
|
||
|
connection.received += data;
|
||
|
connection.hadData = true;
|
||
|
if (connection.cb) connection.cb(data);
|
||
|
}
|
||
|
|
||
|
function log(level, s) {
|
||
|
if (Puck.log) Puck.log(level, s);
|
||
|
}
|
||
|
|
||
|
function handleQueue() {
|
||
|
if (!queue.length) return;
|
||
|
var q = queue.shift();
|
||
|
log(3,"Executing "+JSON.stringify(q)+" from queue");
|
||
|
if (q.type == "write") Puck.write(q.data, q.callback, q.callbackNewline);
|
||
|
else log(1,"Unknown queue item "+JSON.stringify(q));
|
||
|
}
|
||
|
|
||
|
/* convenience function... Write data, call the callback with data:
|
||
|
callbackNewline = false => if no new data received for ~0.2 sec
|
||
|
callbackNewline = true => after a newline */
|
||
|
function write(data, callback, callbackNewline) {
|
||
|
let result;
|
||
|
/// If there wasn't a callback function, then promisify
|
||
|
if (typeof callback !== 'function') {
|
||
|
callbackNewline = callback;
|
||
|
|
||
|
result = new Promise((resolve, reject) => callback = (value, err) => {
|
||
|
if (err) reject(err);
|
||
|
else resolve(value);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (isBusy) {
|
||
|
log(3, "Busy - adding Puck.write to queue");
|
||
|
queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
var cbTimeout;
|
||
|
function onWritten() {
|
||
|
if (callbackNewline) {
|
||
|
connection.cb = function(d) {
|
||
|
var newLineIdx = connection.received.indexOf("\n");
|
||
|
if (newLineIdx>=0) {
|
||
|
var l = connection.received.substr(0,newLineIdx);
|
||
|
connection.received = connection.received.substr(newLineIdx+1);
|
||
|
connection.cb = undefined;
|
||
|
if (cbTimeout) clearTimeout(cbTimeout);
|
||
|
cbTimeout = undefined;
|
||
|
if (callback)
|
||
|
callback(l);
|
||
|
isBusy = false;
|
||
|
handleQueue();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
// wait for any received data if we have a callback...
|
||
|
var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
|
||
|
var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
|
||
|
var maxDataTime = dataWaitTime; // max time we wait after having received data
|
||
|
cbTimeout = setTimeout(function timeout() {
|
||
|
cbTimeout = undefined;
|
||
|
if (maxTime) maxTime--;
|
||
|
if (maxDataTime) maxDataTime--;
|
||
|
if (connection.hadData) maxDataTime=dataWaitTime;
|
||
|
if (maxDataTime && maxTime) {
|
||
|
cbTimeout = setTimeout(timeout, 100);
|
||
|
} else {
|
||
|
connection.cb = undefined;
|
||
|
if (callback)
|
||
|
callback(connection.received);
|
||
|
isBusy = false;
|
||
|
handleQueue();
|
||
|
connection.received = "";
|
||
|
}
|
||
|
connection.hadData = false;
|
||
|
}, 100);
|
||
|
}
|
||
|
|
||
|
if (!connection.txInProgress) connection.received = "";
|
||
|
isBusy = true;
|
||
|
connection.write(data, onWritten);
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------------------
|
||
|
|
||
|
Puck = {
|
||
|
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||
|
debug : Puck.debug,
|
||
|
/// Should we use flow control? Default is true
|
||
|
flowControl : true,
|
||
|
/// Used internally to write log information - you can replace this with your own function
|
||
|
log : function(level, s) { if (level <= this.debug) console.log("<BLE> "+s)},
|
||
|
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
||
|
writeProgress : Puck.writeProgress,
|
||
|
connect : function(callback) {
|
||
|
setTimeout(callback, 10);
|
||
|
},
|
||
|
write : write,
|
||
|
eval : function(expr, cb) {
|
||
|
const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
|
||
|
.then(function (d) {
|
||
|
try {
|
||
|
return JSON.parse(d);
|
||
|
} catch (e) {
|
||
|
log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
|
||
|
return Promise.reject(d);
|
||
|
}
|
||
|
});
|
||
|
if (cb) {
|
||
|
return void response.then(cb, (err) => cb(null, err));
|
||
|
} else {
|
||
|
return response;
|
||
|
}
|
||
|
},
|
||
|
isConnected : function() { return true; },
|
||
|
getConnection : function() { return connection; },
|
||
|
close : function() {
|
||
|
if (connection)
|
||
|
connection.close();
|
||
|
},
|
||
|
};
|
||
|
// no need for header
|
||
|
document.getElementsByTagName("header")[0].style="display:none";
|
||
|
// force connection attempt automatically
|
||
|
setTimeout(function() {
|
||
|
getInstalledApps(true).catch(err => {
|
||
|
showToast("Device connection failed, "+err,"error");
|
||
|
if ("object"==typeof err) console.log(err.stack);
|
||
|
});
|
||
|
}, 500);
|
||
|
}
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|