mirror of https://github.com/espruino/BangleApps
451 lines
19 KiB
HTML
451 lines
19 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||
<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>
|
||
<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-small.svg" 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="" data-tooltip="Show all apps">All</label>
|
||
<label class="chip tooltip" filterid="clock" data-tooltip="To tell the time!">Clocks</label>
|
||
<label class="chip tooltip" filterid="launch" data-tooltip="Choose which apps to launch">Launchers</label>
|
||
<label class="chip tooltip" filterid="game" data-tooltip="Have fun!">Games</label>
|
||
<label class="chip tooltip" filterid="tool" data-tooltip="Useful applications">Tools</label>
|
||
<label class="chip tooltip" filterid="textinput" data-tooltip="To allow you to enter text">Keyboards</label>
|
||
<label class="chip tooltip" filterid="widget" data-tooltip="Appear in the top bar of Bangle.js apps">Widgets</label>
|
||
<label class="chip tooltip" filterid="bluetooth" data-tooltip="Using Bluetooth Functionality">Bluetooth</label>
|
||
<label class="chip tooltip" filterid="outdoors" data-tooltip="For outdoor use">Outdoors</label>
|
||
<label class="chip tooltip" filterid="ram" data-tooltip="Apps that don't save anything to flash memory">Online</label>
|
||
<label class="chip tooltip" filterid="clkinfo" data-tooltip="Info displayed on clocks, or clocks with info">Clock Info</label>
|
||
<label class="chip tooltip" filterid="health" data-tooltip="Apps for your health">Health</label>
|
||
<label class="chip tooltip" filterid="favourites" data-tooltip="Apps that you've liked ❤️">Favourites</label>
|
||
</div>
|
||
<div class="sort-nav hidden">
|
||
<span>Sort by:</span>
|
||
<label class="chip active" sortid="">None</label>
|
||
<label class="chip hidden tooltip" sortid="created" data-tooltip="Most recent apps">New</label>
|
||
<label class="chip hidden tooltip" sortid="modified" data-tooltip="Most recently changed">Updated</label>
|
||
<label class="chip hidden tooltip" sortid="installs" data-tooltip="Most installed by users">Installed</label>
|
||
<label class="chip hidden tooltip" sortid="favourites" data-tooltip="Most liked by users">Favourited</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 tooltip" id="settime" data-tooltip="Set the Bangle's time to your Browser's time">Set Bangle.js Time</button>
|
||
<button class="btn tooltip" id="screenshot" data-tooltip="Create screenshot">Screenshot</button>
|
||
<button class="btn tooltip" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
|
||
<button class="btn tooltip" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button>
|
||
</p><p>
|
||
<button class="btn tooltip" id="removeall" data-tooltip="Delete everything, leave it blank">Remove all Apps</button>
|
||
<button class="btn tooltip" id="reinstallall" data-tooltip="Re-install every app, leave all data">Reinstall apps</button>
|
||
<button class="btn tooltip" id="installdefault" data-tooltip="Delete everything, install default apps">Install default apps</button>
|
||
<button class="btn tooltip" id="installfavourite" data-tooltip="Delete everything, install your favourites">Install favourite apps</button>
|
||
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
|
||
</p><p>
|
||
<button class="btn tooltip" id="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button>
|
||
<button class="btn tooltip" id="webideremote" data-tooltip="Enable the Web IDE remote server">Web IDE Remote</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>
|
||
<label class="form-switch">
|
||
<input type="checkbox" id="settings-usage-stats">
|
||
<i class="form-icon"></i> Send app analytics to banglejs.com (apps installed, favourites, firmware version).<br/>
|
||
<small>Used for 'Sort by Installed/Favourited' functionality. See the <a href="http://www.espruino.com/Privacy">privacy policy</a></small>.
|
||
</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>
|
||
<details>
|
||
<summary>Advanced Options</summary>
|
||
<label class="form-switch">
|
||
<input type="checkbox" id="settings-minify">
|
||
<i class="form-icon"></i> Minify apps before upload (⚠️DANGER⚠️: Not recommended. Uploads smaller, faster apps but this <b>will</b> break many apps)
|
||
</label>
|
||
<label class="form-switch">
|
||
<input type="checkbox" id="settings-alwaysAllowUpdate">
|
||
<i class="form-icon"></i> Always show "reinstall app" button <i class="icon icon-refresh"></i> regardless of the version
|
||
</label>
|
||
<label class="form-switch">
|
||
<input type="checkbox" id="settings-autoReload">
|
||
<i class="form-icon"></i> Automatically reload watch after app App Loader actions (removes "Hold button" prompt)
|
||
</label>
|
||
<button class="btn" id="defaultsettings">Reset App Loader settings to defaults</button>
|
||
</details>
|
||
</div>
|
||
<div id="more-deviceinfo" style="display:none">
|
||
<h3>Device info</h3>
|
||
<div id="more-deviceinfo-content"></div>
|
||
<div class="editor--terminal">
|
||
<div class="editor__canvas" style="position:relative;height:20rem;display:none;"></div>
|
||
<button class="btn" id="terminalEnable">Enable Terminal</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="floating hidden">
|
||
<!-- PWA Install button, hidden by default -->
|
||
<div id="installContainer" class="hidden">
|
||
<button id="butInstall" type="button">
|
||
Install
|
||
</button>
|
||
</div>
|
||
</footer>
|
||
|
||
<script src="webtools/puck.js"></script>
|
||
<script src="webtools/heatshrink.js"></script>
|
||
<script src="core/lib/marked.min.js"></script>
|
||
<script src="core/lib/espruinotools.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>
|
||
<!-- FIXME - use espruino.com/ide, github -->
|
||
<script src="https://espruino.github.io/EspruinoWebIDE/js/libs/peerjs.min.js"></script>
|
||
<script src="https://espruino.github.io/EspruinoWebIDE/EspruinoTools/libs/webrtc-connection.js"></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() {},
|
||
on : function(evt,cb) { connection.handlers[evt] = cb; },
|
||
received : "",
|
||
hadData : false,
|
||
handlers : []
|
||
}
|
||
connection.on("data", function(d) {
|
||
connection.received += d;
|
||
connection.hadData = true;
|
||
if (connection.cb) connection.cb(d);
|
||
});
|
||
|
||
function bangleRx(data) {
|
||
// document.getElementById("status").innerText = "RX:"+data;
|
||
// call data event
|
||
if (connection.handlers["data"])
|
||
connection.handlers["data"](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, connection);
|
||
return connection;
|
||
},
|
||
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);
|
||
} else {
|
||
showToast("You're running the App Loader version for Gadgetbridge, but you don't seem to be in Gadgetbridge!","error");
|
||
}
|
||
|
||
function showWebRTCID(id) {
|
||
showToast("Bridge's Peer ID: "+id);
|
||
showPrompt("Web IDE Remote Access",`
|
||
Remote access enabled. Peer ID:
|
||
<br/><br/>
|
||
<b>${id}</b>
|
||
<br/><br/>
|
||
Go to <b>espruino.com/ide</b> on your
|
||
desktop and enter this code under
|
||
<b>Remote Connection Bridge Peer ID</b> in Settings.
|
||
Then connect to the <b>Android</b> device.
|
||
`,{ok:1},false/*shouldEscapeHtml*/).then(() => {
|
||
}, function() { /* cancelled */ });
|
||
}
|
||
|
||
// Button to Enable Remote Web IDE
|
||
var el = document.getElementById("webideremote");
|
||
var webrtc;
|
||
if (el) el.addEventListener("click", event=>{
|
||
if (webrtc) showWebRTCID(webrtc.peerId);
|
||
else {
|
||
webrtc = webrtcInit({
|
||
bridge:true,
|
||
onStatus : function(s) {
|
||
showToast(s);
|
||
},
|
||
onPeerID : function(id) {
|
||
showWebRTCID(id);
|
||
},
|
||
onGetPorts : function(cb) {
|
||
cb([{path:"Android",description:"Remote Device Connection",type:"socket"}]);
|
||
},
|
||
onPortConnect : function(serialPort, cb) {
|
||
cb(); // we're already connected...
|
||
},
|
||
onPortDisconnect : function(serialPort) {
|
||
},
|
||
onPortWrite : function(data, cb) {
|
||
Puck.write(data, cb);
|
||
}
|
||
});
|
||
connection.on("data", function(d) {
|
||
webrtc.onPortReceived(d);
|
||
});
|
||
}
|
||
});
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|