Merge remote-tracking branch 'upstream/master'
79
android.html
|
|
@ -133,14 +133,19 @@
|
|||
<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>
|
||||
<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="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="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button></p>
|
||||
<p><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><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="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>
|
||||
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</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">
|
||||
|
|
@ -167,7 +172,7 @@
|
|||
<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>
|
||||
<button class="btn" id="defaultsettings">Reset to default settings</button>
|
||||
<button class="btn" id="defaultsettings">Reset to default App Loader settings</button>
|
||||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
|
|
@ -199,6 +204,9 @@
|
|||
<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) {
|
||||
|
|
@ -224,8 +232,10 @@ if (typeof Android!=="undefined") {
|
|||
if (writecb) setTimeout(writecb,10);
|
||||
},
|
||||
close : function() {},
|
||||
on : function(evt,cb) { connection.handlers[evt] = cb; },
|
||||
received : "",
|
||||
hadData : false
|
||||
hadData : false,
|
||||
handlers : []
|
||||
}
|
||||
|
||||
function bangleRx(data) {
|
||||
|
|
@ -233,6 +243,9 @@ if (typeof Android!=="undefined") {
|
|||
connection.received += data;
|
||||
connection.hadData = true;
|
||||
if (connection.cb) connection.cb(data);
|
||||
// call data event
|
||||
if (connection.handlers["data"])
|
||||
connection.handlers["data"](data);
|
||||
}
|
||||
|
||||
function log(level, s) {
|
||||
|
|
@ -317,6 +330,7 @@ if (typeof Android!=="undefined") {
|
|||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
|
||||
Puck = {
|
||||
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
||||
debug : Puck.debug,
|
||||
|
|
@ -327,7 +341,8 @@ if (typeof Android!=="undefined") {
|
|||
/// 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);
|
||||
setTimeout(callback, 10, connection);
|
||||
return connection;
|
||||
},
|
||||
write : write,
|
||||
eval : function(expr, cb) {
|
||||
|
|
@ -362,7 +377,57 @@ if (typeof Android!=="undefined") {
|
|||
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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
A simple clock with perspective scaling.
|
||||
Battery drainer, performance tester, show-off piece work-in-progress.
|
||||
|
||||
Demo.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmZCZmZAAAAAAAAAAAAAAAAAAAAAAAAAACZAAkJkJAAAAAAAAAAAAAAAAAAAAAACQkJmZmQCZkAAAAAAAAAAAAAAAAAAAAACQAAAAAJmQmZAAAAAAAAAAAAAAAAAAAAAAAAAAAJkAAJkAAAAAAAAAAAAAAAAAAAAAAAAAAJCZkACQAAAAAAAAAAAAAAAJAAAAAAAAAACZAACZAAAAAAAAAAAAAAAAAAAAAAAAAAmZAJmZkAAAAAAAAAAAAAAAAAAAAACQmQkAmQAJmQAAAAAAAAAAAAAAAAAAAAmZAJkAAAkJmQAAAAAAAAAAAAAAAAkAAAAA///wAAAJmf///wAAAAmQAAAAAAAJAAD/////8AAJD/////AAAAAAAAAJAAAJCQD//////wCZ//////AAAJAAAAAJAAD//w//8AAP/wCf//kAD/AAAAkAAAAAAAD5CQ8J8AAP/wCZkJkAAPAAAJCQAAAAAJn//w//mQAP/wAAmQCQAPAAAAAAAA//CQ///w8J+ZAP/wAACQCQD/AAAACQAP//CQDw/58P/////wAACQAA//AAAJCQAAD/AAAP+QDwn///8AAAAAAP//AACQAAAAD/AAAP8JCZn////wAAmZ///wAAAACZAJD/AACfCQmZkJmf/wAAAP//8AAAAJkAkAD/AACfAJCQmZCf/wkJD//wAAAAAAAJAP//8ADwCQCZmZmf/wAA//8AAAAAAACQkAAJAJAACQ//mZmf/wAA//CQAAAAAAkAmQAACQAACQn//////5mf//////AAAAkACQAACQAACQn/////8AkP//////AAAAAJkACQAJAJkJAJ////AJmf//////AAAAmQAAAAmZCZmZmZkJmZkJAJCZAAAAAAAAAAAACZCZmZmZmQCZmQmQAJAAAAAAAAAACQkAAJmZmZmZmZmZmZkJmZkAAAAAAAAAAJkJCZkJmZmZmZmQkJCZmQAAAAAAAAAAAAAJAACZmZmZmZmQmZkJmZAAAAAAAAAAAACQkJmZmZmZmZmZmZCZkAAAAAAAAAAAAAAAAAkJmZmZmZkJmQkJkAAAAAAAAAAAAAAJmQmZCZmQCZmZmZAAAAAAAAAAAAAAAAAACQmZmZmZAJkJkJAAAAAAAAAAAAAAAAAAAJmQmZkJkACQkAAAAAAAAAAAAAAAAAAAAAAACZmZmQmZkAAAAAAAAAAAAAAAAAAAAAAAAJkAAJkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
|
After Width: | Height: | Size: 391 B |
|
|
@ -0,0 +1,16 @@
|
|||
{ "id": "3dclock",
|
||||
"name": "3D Clock",
|
||||
"shortName":"3DClock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "This is a simple 3D scalig demo based on Anton Clock",
|
||||
"screenshots" : [ { "url":"screenshot.png" }],
|
||||
"type":"clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"3dclock.app.js","url":"app.js"},
|
||||
{"name":"3dclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Select GNSS systems to use for Bangle.js 2
|
||||
0.04: Now turns GPS off after upload
|
||||
0.05: Fix regression in 0.04 that caused AGPS data not to get loaded
|
||||
0.06: Auto-set GPS output sentences - newer Bangle.js 2 don't include RMC (GPS direction + time) by default
|
||||
|
|
@ -147,7 +147,10 @@
|
|||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
gnss_select=radios[i].value;
|
||||
js += `\x10var t=getTime()+0.5;while (getTime()<t);\n`; // This is nasty - but we just wait here until the GPS has had time to boot
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnss_select)}")\n`; // set GNSS mode
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS03,1,0,0,1,1,0,0,0")}")\n`; // enable GGA,GSV,RMC packets (new Bangle.js 2 GPS firmwares don't include RMC by default!)
|
||||
// Serial1.println("$PCAS06,0*1B") gets the current firmware version
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Updater (AGPS)",
|
||||
"shortName": "AGPS",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"sortorder": -1,
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# GPS Compass course switcher
|
||||
The GPS course and speed is calculated by the difference of positions.
|
||||
However GPS position is noisy and prone to jump around.
|
||||
This results in randomly jumping GPS course values when speed is slow or standing still.
|
||||
So in these cases a user might want to get the moving direction from a compass instead.
|
||||
This is why this service replaces the GPS course with the compass heading when the speed is slower then 6 km/h (threshold is configurable, see settings).
|
||||
You can switch between the built-in compass and "Navigation Compass" (magnav) as the source of the compass heading. When using magnav on Bangle.js 2 at least firmware 2v16.191 is recommended to get a three-dimensional reading.
|
||||
|
||||
## Important Notes
|
||||
* **Watch orientation**
|
||||
When the GPS is calculating the course the orientation of the watch does not matter.
|
||||
When the Compass is used as the source of the current heading its top must obviously point at the moving direction (Usually away from you).
|
||||
* **Compass reset and calibration**
|
||||
When using "Navigation Compass" as compass source (see settings) please remember to calibrate it regularly. The author recommends to calibrate before every use and at least each time after attaching the charge cable.
|
||||
With this service installed the built-in compass calibration is automatically reset when the compass is turned on (deactivatable in settings). It can also be reset with a tap on the Widget (Bangle.js 2 only). Please note that directly after a reset the built-in compass must be turned 360 degrees to provide a useable value.
|
||||
* **True north vs magnetic north**
|
||||
Please note that the compass does not point to the "real north" but depending on your location there is an offset, see [Magnetic declination](https://en.wikipedia.org/wiki/Magnetic_declination)
|
||||
However the error from local magnetic interference and from calibration will probably be higher..
|
||||
|
||||
## Widget
|
||||
The widget indicates if the current GPS course is provided from GPS or compass.
|
||||
It can be turned off in the settings.
|
||||
On Bangle.js 2 a click on the widget does reset the built-in compass, it has only an affect if the built-in compass is used.
|
||||
|
||||
## Settings
|
||||
* **Speed threshold**
|
||||
- (default = 6 km/h) When GPS speed is lower then this threshold use the compass direction. The speed must be for at least 10 seconds this fast to switch back to GPS course. The optimum threshold varies with the quality of the GPS reception.
|
||||
* **Compass source**
|
||||
- off: Disables this service.
|
||||
- built-in (default if "Navigation Compass" is not installed): Uses the built-in compass. Its calibration can be restarted by pressing the Widget. The watch must be orientated horizontally for the compass heading to be used.
|
||||
- magnav (default if "Navigation Compass" is installed and calibrated): Compass heading is provided by "Navigation Compass" (magnav).
|
||||
* **Reset compass when powered on**
|
||||
- Off: Do nothing when compass is turned on.
|
||||
- On (default): The calibration of the built-in compass is reset when it is turned on.
|
||||
* **Show Widget**
|
||||
- Never: Widget is hidden.
|
||||
- Active (default): Widget is only visible when replacing GPS course with compass heading.
|
||||
- GPS on: Widget is shown as soon as GPS is enabled, crossed out when GPS provides the course and displayed normally when the compass heading is used.
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
const settings = Object.assign({
|
||||
speed: 6, // when lower then this use direction from compass
|
||||
compassSrc: 2, // [off, built-in, magnav]
|
||||
resetCompassOnPwr: true, // reset compass on power on
|
||||
}, require("Storage").readJSON("gpsmagcourse.json", true) || {});
|
||||
const CALIBDATA = (settings.compassSrc === 2) ? require("Storage").readJSON("magnav.json",1) : undefined;
|
||||
let cntAboveSpeed = 0;
|
||||
let lastGPS;
|
||||
|
||||
// Check if magnav is installed
|
||||
try {
|
||||
require("magnav");
|
||||
} catch(err) {
|
||||
// not installed, adjust settings to work without magnav
|
||||
if (settings.compassSrc === 2) {
|
||||
settings.compassSrc = 1;
|
||||
}
|
||||
}
|
||||
if (settings.compassSrc === 2 && !CALIBDATA) {
|
||||
// No calibration for magnav, fallback to built-in compass
|
||||
settings.compassSrc = 1;
|
||||
}
|
||||
|
||||
// execute Bangle.resetCompass() after Bangle.setCompassPower();
|
||||
if (settings.resetCompassOnPwr) {
|
||||
const origSetCompassPower = Bangle.setCompassPower;
|
||||
Bangle.setCompassPower = function(on, id) {
|
||||
const isOn = origSetCompassPower(on, id);
|
||||
if (on) {
|
||||
Bangle.resetCompass();
|
||||
}
|
||||
return isOn;
|
||||
};
|
||||
} // if (settings.resetCompassOnPwr)
|
||||
|
||||
if (settings.compassSrc > 0) {
|
||||
const isFaceUp = (acc) => {
|
||||
return (acc.z<-6700/8192) && (acc.z>-9000/8192) && Math.abs(acc.x)<2048/8192 && Math.abs(acc.y)<2048/8192;
|
||||
};
|
||||
|
||||
const changeGpsCourse = (gps) => {
|
||||
cntAboveSpeed = gps.speed < settings.speed ? 0 : cntAboveSpeed+1;
|
||||
if (cntAboveSpeed < 10) { // need to stay x events above or equal threshold
|
||||
if (settings.compassSrc === 1 && isFaceUp(Bangle.getAccel())) { // Use built-in compass heading only if face is up
|
||||
const heading = Bangle.getCompass().heading;
|
||||
if (!isNaN(heading)) {
|
||||
gps.courseOrig = gps.course;
|
||||
gps.course = Bangle.getCompass().heading;
|
||||
}
|
||||
} else if (settings.compassSrc === 2) { // magnav
|
||||
gps.courseOrig = gps.course;
|
||||
gps.course = require("magnav").tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
}
|
||||
}
|
||||
return gps;
|
||||
};
|
||||
|
||||
// Modify GPS event
|
||||
Bangle.on('GPS', gps => {
|
||||
lastGPS = gps;
|
||||
if (!isNaN(gps.course)) {
|
||||
changeGpsCourse(gps);
|
||||
}
|
||||
});
|
||||
const origGetGPSFix = Bangle.getGPSFix;
|
||||
Bangle.getGPSFix = function() {
|
||||
return lastGPS === undefined ? origGetGPSFix() : lastGPS;
|
||||
};
|
||||
|
||||
// Enable Compass with GPS
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
Bangle.setGPSPower = function(on, id) {
|
||||
const isGPSon = origSetGPSPower(on, id);
|
||||
Bangle.setCompassPower(isGPSon, "gpsmagcourse" + (id || ''));
|
||||
return isGPSon;
|
||||
};
|
||||
} // if (settings.compassSrc > 0)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "gpsmagcourse",
|
||||
"name": "GPS Compass course switcher",
|
||||
"shortName":"GPS/Compass course",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Replaces the GPS course with the compass heading when speed is slow or standing still to avoid the value from jumping randomly. For best experience also install \"Navigation Compass\", although not a requirement (see README).",
|
||||
"type": "bootloader",
|
||||
"tags": "outdoors,widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gpsmagcourse.boot.js","url":"boot.js"},
|
||||
{"name":"gpsmagcourse.wid.js","url":"widget.js"},
|
||||
{"name":"gpsmagcourse.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"gpsmagcourse.json"}]
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
(function(back) {
|
||||
var FILE = "gpsmagcourse.json";
|
||||
// Load settings
|
||||
const settings = Object.assign({
|
||||
speed: 6, // when lower then this use direction from compass
|
||||
compassSrc: 2, // [off, built-in, magnav]
|
||||
resetCompassOnPwr: true, // reset compass on power on
|
||||
showWidget: 2, // 0 = never, 1 = when replacing GPS course with compass course, 2 = when GPS is on
|
||||
}, require("Storage").readJSON(FILE, true) || {});
|
||||
|
||||
let magnavInstalled = true;
|
||||
// Check if magnav is installed
|
||||
try {
|
||||
require("magnav");
|
||||
} catch(err) {
|
||||
// not installed
|
||||
magnavInstalled = false;
|
||||
}
|
||||
|
||||
if (!magnavInstalled) {
|
||||
// adjust settings to work without magnav
|
||||
if (settings.compassSrc === 2) {
|
||||
settings.compassSrc = 1;
|
||||
}
|
||||
}
|
||||
const compassSrcOpts = [/*LANG*/"off", /*LANG*/"built-in"];
|
||||
if (magnavInstalled) {
|
||||
compassSrcOpts.push(/*LANG*/"magnav");
|
||||
}
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
const menu = {
|
||||
"" : { "title" : /*LANG*/"GPS/Com.course" },
|
||||
"< Back" : () => back(),
|
||||
/*LANG*/'Speed threshold': {
|
||||
value: settings.speed,
|
||||
min: 1, max: 20, step: 0.5,
|
||||
onchange: v => {
|
||||
settings.speed = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Compass source': {
|
||||
value: settings.compassSrc,
|
||||
min: 0, max: compassSrcOpts.length-1,
|
||||
format: v => compassSrcOpts[v],
|
||||
onchange: v => {
|
||||
settings.compassSrc = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Reset compass when powered on': {
|
||||
value: !!settings.resetCompassOnPwr,
|
||||
onchange: v => {
|
||||
settings.resetCompassOnPwr = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Show Widget': {
|
||||
value: settings.showWidget,
|
||||
min: 0, max: 2,
|
||||
format: v => [/*LANG*/"Never", /*LANG*/"Active", /*LANG*/"GPS on"][v],
|
||||
onchange: v => {
|
||||
settings.showWidget = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Show the menu
|
||||
E.showMenu(menu);
|
||||
})
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
(() => {
|
||||
const settings = Object.assign({
|
||||
compassSrc: 1, // 0 = off
|
||||
showWidget: 2, // 0 = never, 1 = when replacing GPS course with compass course, 2 = when GPS is on
|
||||
}, require("Storage").readJSON("gpsmagcourse.json", true) || {});
|
||||
|
||||
function isInside(rect, e) {
|
||||
return e.x>=rect.x && e.x<rect.x+rect.w
|
||||
&& e.y>=rect.y && e.y<=rect.y+rect.h;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (this.width) {
|
||||
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
||||
|
||||
if (this.show) {
|
||||
this.width = 24;
|
||||
g.reset();
|
||||
g.drawImage(require("heatshrink").decompress(atob("jEYwgrohEN6EwBQ+DBYM4wALFxGA7vdB4IWFxEABYMAnAlECwMNBYPQCIQLDgALDDAI5EBYIFBBYIeBBYRBGA4QnBCAZBDA4ILLEZYLMKYR9FAgaKFNYpgCD4RBFAwQLBCwpOELAwACgeIwbLHK5ILPAAwA=")), this.x, this.y);
|
||||
if (this.show === 2) {
|
||||
// draw stroke
|
||||
g.setColor(1,0,0).fillPoly([this.x+2, 0,
|
||||
this.x+this.width-1,this.y+21,
|
||||
this.x+this.width-3, this.y+23,
|
||||
this.x, 2
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newWidth = this.show ? 24 : 0;
|
||||
if (newWidth !== this.width) {
|
||||
this.width = newWidth;
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.compassSrc > 0 && settings.showWidget > 0) {
|
||||
// add your widget
|
||||
WIDGETS.gpsmagcourse={
|
||||
area:"tr", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
|
||||
width: 0, // hide by default
|
||||
draw:draw,
|
||||
show:0 // 0 = hide, 1 = show, 2 = with stroke
|
||||
};
|
||||
|
||||
// show only when GPS course is replaced
|
||||
Bangle.on('GPS', function(gps) {
|
||||
if (gps.courseOrig && WIDGETS.gpsmagcourse.show !== 1 && Bangle.isGPSOn()) {
|
||||
WIDGETS.gpsmagcourse.show = 1;
|
||||
WIDGETS.gpsmagcourse.draw();
|
||||
} else if (!gps.courseOrig && WIDGETS.gpsmagcourse.show === 1) {
|
||||
WIDGETS.gpsmagcourse.show = settings.showWidget === 1 ? 0 : 2;
|
||||
WIDGETS.gpsmagcourse.draw();
|
||||
}
|
||||
});
|
||||
|
||||
// hide widget if GPS is turned off
|
||||
const origSetGPSPower = Bangle.setGPSPower;
|
||||
Bangle.setGPSPower = function(on, id) {
|
||||
const isGPSon = origSetGPSPower(on, id);
|
||||
if (!isGPSon && WIDGETS.gpsmagcourse.show) {
|
||||
WIDGETS.gpsmagcourse.show = 0;
|
||||
WIDGETS.gpsmagcourse.draw();
|
||||
} else if (isGPSon && !WIDGETS.gpsmagcourse.show) {
|
||||
WIDGETS.gpsmagcourse.show = 2;
|
||||
WIDGETS.gpsmagcourse.draw();
|
||||
}
|
||||
return isGPSon;
|
||||
};
|
||||
|
||||
// reset compass on click on widget
|
||||
Bangle.on('touch', function(button, touch) {
|
||||
if (touch && WIDGETS.gpsmagcourse && WIDGETS.gpsmagcourse.x && WIDGETS.gpsmagcourse.width && isInside({x: WIDGETS.gpsmagcourse.x, y: WIDGETS.gpsmagcourse.y, w: WIDGETS.gpsmagcourse.width, h: 24}, touch)) {
|
||||
Bangle.buzz(50);
|
||||
Bangle.resetCompass();
|
||||
}
|
||||
});
|
||||
} // if (settings.compassSrc > 0 && settings.showWidget)
|
||||
})();
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: First release of clock with faces for mario1-3, kirby, and zelda
|
||||
0.02: Fix issue witih plumbers font where some numbers were cut off.
|
||||
0.03: Fix issue with smb3 font on 24hr time. Better center time.
|
||||
0.04: Rearrange bitmap code to save memory.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "nesclock",
|
||||
"name": "NES Clock",
|
||||
"shortName": "NES Clock",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A clock themed after different NES title screens.",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
Intended for the dark theme.
|
||||
The clock does not show widgets but a custom battery indicator.
|
||||
|
||||
You can revert this in the code easily by uncommenting the respective lines.
|
||||
|
||||

|
||||
|
||||
The top line shows battery status.
|
||||
The line below indicates noon and midnight on 24h timeline.
|
||||
ToDo: show daylight sunshine duration according to geolocation.
|
||||
Number in the middle is hour and minutes, in 12h format.
|
||||
Bottom row: days in the week, with current date,
|
||||
and current moon phase placed on the date of
|
||||
next change of moon quartal (full moon, half moon etc)
|
||||
|
||||
Please report any bugs and feature requests in the forum.
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDCEBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFEQEBEBEQAQEBAAAAAAAAABAAAAAAAAABEQAVEREWUBARYQAAAAAAABEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUEVFRERERAAAAAAAAAAAAAAAAAAAAAAAv////////AAAAAAAAAAAAAAAAAAAAAAAv////////UAAAAAAAAAAAAAAAAAAAAAAv///////yABIhIRAAEiIQAAAAAAAAAAAv/z//M//1ABEiIgABIiIiEAAAAAAAAAAvIQAAET8gAAEiIQARERIiEAAAAAAAAAATEAAAAfMAAAASIAAAAAIiEAAAAAAAAAAAAAAAA/IAAAAiQAAAAAIiAAAAAAAAAAAAAAAAHzAAAAAiIAAAABJAAAAAAAAAAAAAAAABPxAAAAAiEAAAASIWAAAAAAAAAAAAAAAB8wAAAAAiEAAAAUIiEAAAAAAAAAAAAAAD8QAAAAEiQAAAAAEiIAAAAAAAAAAAAAAvMAAAABIiIhAAAAAiIQAAAAAAAAAAAAE/EAAAABERERAAAAEiIAAAAAAAAAAAAALyAAAAAAAAAAAAAAEiEAAAAAAAAAAAAB8xAAAAAAAAAAAAARIhAAAAAAAAAAAAAD8gAAAAAAAAAAABIhEAAAAAAAAAAAAAAf8QAAAAAAAAAAASIRAAAAAAAAAAAAAAAvIAAAAAAAAAAAABAAAAAAAAAAAAAAAAHzEAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAACMwAAAAABMAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAEMAAAAAAAAAAAAAAAABAAAAEAIxAQAAABIAABUQERERAAEQAAACAAAAIAExEgAAAREAABERERIREREwAAACAAAAIBIgFAAAARAAABERERIRERHwAAACAAAAIBEAEQAAARAAACIRERMRERHwAAACAAAAIAAAEQAAARAAABERERIQEREw==")
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
{ "id": "polymath",
|
||||
"name": "A Polymaths Clock",
|
||||
"shortName":"Polymath",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "This is a graphical clock based on Anton Clock with date, day and moon phase. Beta",
|
||||
"screenshots" : [ { "url":"screenshot.png" }, { "url":"polymathclock.jpg" } ],
|
||||
"type":"clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"polymath.app.js","url":"app.js"},
|
||||
{"name":"polymath.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Popcon
|
||||
======
|
||||
|
||||
Display apps sorted by regular use. No config - install the app and all your launchers will sort apps by most popular, based off launch counts within the last month, and then sort them by the most recently launched app.
|
||||
|
||||
:warning: Warning: this app overrides [`Storage.readJSON`], so may slow down your watch when using apps that perform I/O.
|
||||
|
||||
[`Storage.readJSON`]: https://www.espruino.com/ReferenceBANGLEJS2#l_Storage_readJSON
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
var oldRead_1 = require("Storage").readJSON;
|
||||
var monthAgo_1 = Date.now() - 1000 * 86400 * 28;
|
||||
var cache_1;
|
||||
var ensureCache_1 = function () {
|
||||
if (!cache_1) {
|
||||
cache_1 = oldRead_1("popcon.cache.json", true);
|
||||
if (!cache_1)
|
||||
cache_1 = {};
|
||||
}
|
||||
return cache_1;
|
||||
};
|
||||
var saveCache_1 = function (orderChanged) {
|
||||
require("Storage").writeJSON("popcon.cache.json", cache_1);
|
||||
if (orderChanged) {
|
||||
var info = oldRead_1("popcon.info", true);
|
||||
info.cacheBuster = !info.cacheBuster;
|
||||
require("Storage").writeJSON("popcon.info", info);
|
||||
}
|
||||
};
|
||||
var sortCache_1 = function () {
|
||||
var ents = Object.values(cache_1);
|
||||
ents.sort(function (a, b) {
|
||||
var n;
|
||||
var am = (a.last > monthAgo_1);
|
||||
var bm = (b.last > monthAgo_1);
|
||||
n = bm - am;
|
||||
if (n)
|
||||
return n;
|
||||
n = b.pop - a.pop;
|
||||
if (n)
|
||||
return n;
|
||||
n = b.last - a.last;
|
||||
if (n)
|
||||
return n;
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
if (a.name > b.name)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
var i = 0;
|
||||
var orderChanged = false;
|
||||
for (var _i = 0, ents_1 = ents; _i < ents_1.length; _i++) {
|
||||
var ent = ents_1[_i];
|
||||
if (ent.sortorder !== i)
|
||||
orderChanged = true;
|
||||
ent.sortorder = i++;
|
||||
}
|
||||
return orderChanged;
|
||||
};
|
||||
require("Storage").readJSON = (function (fname, skipExceptions) {
|
||||
var _a;
|
||||
var j = oldRead_1(fname, skipExceptions);
|
||||
if (/\.info$/.test(fname)) {
|
||||
var cache_2 = ensureCache_1();
|
||||
var so = void 0;
|
||||
if (j.src && (so = (_a = cache_2[j.src]) === null || _a === void 0 ? void 0 : _a.sortorder) != null)
|
||||
j.sortorder = so;
|
||||
else
|
||||
j.sortorder = 99;
|
||||
}
|
||||
return j;
|
||||
});
|
||||
var oldLoad_1 = load;
|
||||
global.load = function (src) {
|
||||
if (src) {
|
||||
var cache_3 = ensureCache_1();
|
||||
var ent = cache_3[src] || (cache_3[src] = {
|
||||
pop: 0,
|
||||
last: 0,
|
||||
sortorder: -10,
|
||||
});
|
||||
ent.pop++;
|
||||
ent.last = Date.now();
|
||||
var orderChanged = sortCache_1();
|
||||
saveCache_1(orderChanged);
|
||||
}
|
||||
return oldLoad_1(src);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
{
|
||||
type Timestamp = number;
|
||||
|
||||
const oldRead = require("Storage").readJSON;
|
||||
const monthAgo = Date.now() - 1000 * 86400 * 28;
|
||||
let cache: undefined | {
|
||||
[key: string]: {
|
||||
sortorder: number,
|
||||
pop: number, // amount of launches
|
||||
last: Timestamp,
|
||||
}
|
||||
};
|
||||
|
||||
const ensureCache = (): NonNull<typeof cache> => {
|
||||
if(!cache){
|
||||
cache = oldRead("popcon.cache.json", true);
|
||||
if(!cache)
|
||||
cache = {};
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
|
||||
const saveCache = (orderChanged: boolean) => {
|
||||
require("Storage").writeJSON("popcon.cache.json", cache);
|
||||
if(orderChanged){
|
||||
// ensure launchers reload their caches:
|
||||
const info: AppInfo & { cacheBuster?: boolean } = oldRead("popcon.info", true);
|
||||
info.cacheBuster = !info.cacheBuster;
|
||||
require("Storage").writeJSON("popcon.info", info);
|
||||
}
|
||||
};
|
||||
|
||||
const sortCache = () => {
|
||||
const ents = Object.values(cache);
|
||||
|
||||
ents.sort((a, b) => {
|
||||
// group the most recently launched apps in the last month,
|
||||
// then sort by launch count
|
||||
// then by name
|
||||
let n;
|
||||
|
||||
const am = (a.last > monthAgo) as unknown as number;
|
||||
const bm = (b.last > monthAgo) as unknown as number;
|
||||
n = bm - am;
|
||||
if(n) return n;
|
||||
|
||||
n = b.pop - a.pop;
|
||||
if(n) return n;
|
||||
|
||||
// pops are the same, sort by most recent
|
||||
n = b.last - a.last;
|
||||
if(n) return n;
|
||||
|
||||
if(a.name<b.name) return -1;
|
||||
if(a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let i = 0;
|
||||
let orderChanged = false;
|
||||
for(const ent of ents){
|
||||
if(ent.sortorder !== i) orderChanged = true;
|
||||
ent.sortorder = i++;
|
||||
}
|
||||
return orderChanged;
|
||||
};
|
||||
|
||||
require("Storage").readJSON = ((fname, skipExceptions) => {
|
||||
const j: AppInfo = oldRead(fname, skipExceptions);
|
||||
// ^ technically only AppInfo if we're "*.info"
|
||||
|
||||
if(/\.info$/.test(fname)){
|
||||
const cache = ensureCache();
|
||||
let so;
|
||||
|
||||
if(j.src && (so = cache[j.src]?.sortorder) != null)
|
||||
j.sortorder = so;
|
||||
else
|
||||
j.sortorder = 99;
|
||||
}
|
||||
|
||||
return j;
|
||||
}) satisfies typeof oldRead;
|
||||
|
||||
const oldLoad = load;
|
||||
global.load = (src: string) => {
|
||||
if(src){
|
||||
const cache = ensureCache();
|
||||
const ent = cache[src] ||= {
|
||||
pop: 0,
|
||||
last: 0,
|
||||
sortorder: -10,
|
||||
};
|
||||
ent.pop++;
|
||||
ent.last = Date.now();
|
||||
const orderChanged = sortCache();
|
||||
saveCache(orderChanged);
|
||||
}
|
||||
|
||||
return oldLoad(src);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AEvNAAItq6YAFGoYEBF9I1GQ7vTAIIAMS7YqIRAQADBYwunGAyTYFyAyJLzIlJGBYvYXJowcRRaaHCAYveAoYuIF4/NdzQATF6f+45KD6vV6gfD44ME6gNBBgguU3YAB4/U64AD6fHBYQMB6YME6gMDFyoAB6wiE6wLEBhgvWEInX6AvF6ANFF7fREInRF4oMLF6xSLNhgv/GA3JF6PJFywv/F8nCF6PCF8XBBYfBF/4vSQZabLF/4wI6IvP6IuYF4nQF5/QF/4vN5IvP5Iv/F6fIBQfIF8ZUKNRQvVGAYvUFywvz3YvPRzQvE6IvN6IvfACYv/AH4A/AH4Ar"))
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "popconlaunch",
|
||||
"name": "Popcon Launcher",
|
||||
"shortName": "Popcon",
|
||||
"version": "0.01",
|
||||
"description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "tool,system,launcher",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"popcon.boot.js","url":"boot.js"},
|
||||
{"name":"popcon.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"popcon.cache.json"}
|
||||
],
|
||||
"sortorder": -10
|
||||
}
|
||||
|
|
@ -8,4 +8,5 @@
|
|||
Add widget for live monitoring of power use
|
||||
0.07: Convert Yes/No On/Off in settings to checkboxes
|
||||
0.08: Fix the wrapping of intervals/timeouts with parameters
|
||||
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called
|
||||
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called
|
||||
0.09: Cache the app-launch info
|
||||
|
|
@ -108,7 +108,7 @@ function viewDeferredTable(filename) {
|
|||
for (var i in rows) {
|
||||
let c = rows[i];
|
||||
tableRows += `<tr>
|
||||
<td>${(c.time/1000).toFixed(2)}s</td>
|
||||
<td>${timeFormat(c.time)}</td>
|
||||
<td>${(c.time/sum*100).toFixed(2)}%</td>
|
||||
<td><pre>${c.func}</pre></td>`
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ function viewDeferredTable(filename) {
|
|||
var htmlOverview = `<h1>Deferred function calls</h1>
|
||||
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;margin-left: 10px;">Back</button>
|
||||
<div>
|
||||
This are functions used in timeouts and intervals and their accumulated execution times. Recorded in a time span of <b>${Math.round((duration)/1000)}s</b>. Timeouts/intervals have run for <b>${Math.round(sum/1000)}s (${(sum/duration*100).toFixed(2)}%)</b>. Percentages are calculated from summarized timeout/interval running time.
|
||||
This are functions used in timeouts and intervals and their accumulated execution times. Recorded in a time span of <b>${timeFormat(duration)}</b>. Timeouts/intervals have run for <b>${timeFormat(sum)} (${(sum/duration*100).toFixed(2)}%)</b>. Percentages are calculated from summarized timeout/interval running time.
|
||||
</div>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
|
|
@ -185,7 +185,7 @@ function viewHardwareTable(filename) {
|
|||
for (var i in rows) {
|
||||
let c = rows[i];
|
||||
tableRows += `<tr>
|
||||
<td>${(c.time/1000).toFixed(2)}s</td>
|
||||
<td>${timeFormat(c.time)}</td>
|
||||
<td>${(c.time/duration*100).toFixed(2)}%</td>
|
||||
<td>${c.func}</td>`
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ function viewHardwareTable(filename) {
|
|||
var htmlOverview = `<h1>Hardware power</h1>
|
||||
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;margin-left: 10px;">Back</button>
|
||||
<div>
|
||||
Recorded in a time span of <b>${Math.round(duration/1000)}s</b>. Percentages are calculated from recording time.
|
||||
Recorded in a time span of <b>${timeFormat(duration)}</b>. Percentages are calculated from recording time.
|
||||
</div>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
|
|
@ -263,6 +263,27 @@ function onInit() {
|
|||
show();
|
||||
}
|
||||
|
||||
function timeFormat(time) {
|
||||
let secs = time / 1000;
|
||||
|
||||
if (secs < 60)
|
||||
return secs.toFixed(2) + "s";
|
||||
|
||||
let mins = secs / 60;
|
||||
secs %= 60;
|
||||
if (mins < 60)
|
||||
return mins.toFixed(0) + "m" + secs.toFixed(0) + "s";
|
||||
|
||||
let hrs = mins / 60;
|
||||
mins %= 60;
|
||||
if (hrs < 24)
|
||||
return hrs.toFixed(0) + "h" + mins.toFixed(0) + "m" + secs.toFixed(0) + "s";
|
||||
|
||||
let days = hrs / 24;
|
||||
hrs %= 24;
|
||||
return days.toFixed(0) + "d" + hrs.toFixed(0) + "h" + mins.toFixed(0) + "m" + secs.toFixed(0) + "s";
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "powermanager",
|
||||
"name": "Power Manager",
|
||||
"shortName": "Power Manager",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
|||
|
|
@ -9,27 +9,38 @@ var blankImage = Graphics.createImage(` `);
|
|||
var rowHeight = g.getHeight()/3;
|
||||
|
||||
// Load apps list
|
||||
var apps = Storage.list(/\.info$/).map(app=>{
|
||||
var a=Storage.readJSON(app,1);
|
||||
return a&&{
|
||||
name:a.name,
|
||||
type:a.type,
|
||||
icon:a.icon ? Storage.read(a.icon) : a.icon,
|
||||
sortorder:a.sortorder,
|
||||
src:a.src
|
||||
};
|
||||
}).filter(app=>app && (
|
||||
app.type=="app"
|
||||
// || (app.type=="clock" && settings.showClocks)
|
||||
|| !app.type
|
||||
));
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
var apps;
|
||||
|
||||
var launchCache = s.readJSON("launch.cache.json", true)||{};
|
||||
var launchHash = require("Storage").hash(/\.info/);
|
||||
if (launchCache.hash==launchHash) {
|
||||
apps = launchCache.apps;
|
||||
} else {
|
||||
apps = Storage.list(/\.info$/).map(app=>{
|
||||
var a=Storage.readJSON(app,1);
|
||||
return a&&{
|
||||
name:a.name,
|
||||
type:a.type,
|
||||
icon:a.icon ? Storage.read(a.icon) : a.icon,
|
||||
sortorder:a.sortorder,
|
||||
src:a.src
|
||||
};
|
||||
}).filter(app=>app && (
|
||||
app.type=="app"
|
||||
// || (app.type=="clock" && settings.showClocks)
|
||||
|| !app.type
|
||||
));
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
launchCache = { apps, hash: launchHash };
|
||||
s.writeJSON("launch.cache.json", launchCache);
|
||||
}
|
||||
|
||||
// Uncomment for testing in the emulator without apps:
|
||||
// apps = [
|
||||
|
|
|
|||
|
|
@ -65,3 +65,5 @@ of 'Select Clock'
|
|||
0.57: Settings.log = 0,1,2,3 for off,display,log,both
|
||||
0.58: On/Off settings items now use checkboxes
|
||||
0.59: Preserve BLE whitelist even when disabled
|
||||
0.60: Moved LCD calibration to top of menu, and use 12 taps (not 8)
|
||||
LCD calibration will now error if the calibration is obviously wrong
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.59",
|
||||
"version": "0.60",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
|||
|
|
@ -403,6 +403,12 @@ function showLCDMenu() {
|
|||
const lcdMenu = {
|
||||
'': { 'title': 'LCD' },
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
};
|
||||
if (BANGLEJS2)
|
||||
Object.assign(lcdMenu, {
|
||||
/*LANG*/'Calibrate': () => showTouchscreenCalibration()
|
||||
});
|
||||
Object.assign(lcdMenu, {
|
||||
/*LANG*/'LCD Brightness': {
|
||||
value: settings.brightness,
|
||||
min: 0.1,
|
||||
|
|
@ -444,7 +450,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
if (!BANGLEJS2)
|
||||
Object.assign(lcdMenu, {
|
||||
/*LANG*/'Wake on BTN2': {
|
||||
|
|
@ -514,10 +520,7 @@ function showLCDMenu() {
|
|||
}
|
||||
}
|
||||
});
|
||||
if (BANGLEJS2)
|
||||
Object.assign(lcdMenu, {
|
||||
/*LANG*/'Calibrate': () => showTouchscreenCalibration()
|
||||
});
|
||||
|
||||
return E.showMenu(lcdMenu)
|
||||
}
|
||||
|
||||
|
|
@ -852,17 +855,17 @@ function showTouchscreenCalibration() {
|
|||
g.drawLine(spot[0]-32,spot[1],spot[0]+32,spot[1]);
|
||||
g.drawLine(spot[0],spot[1]-32,spot[0],spot[1]+32);
|
||||
g.drawCircle(spot[0],spot[1], 16);
|
||||
var tapsLeft = (1-currentTry)*4+(4-currentCorner);
|
||||
var tapsLeft = (2-currentTry)*4+(4-currentCorner);
|
||||
g.setFont("6x8:2").setFontAlign(0,0).drawString(tapsLeft+/*LANG*/" taps\nto go", g.getWidth()/2, g.getHeight()/2);
|
||||
}
|
||||
|
||||
function calcCalibration() {
|
||||
g.clear(1);
|
||||
// we should now have 4 of each tap in 'pt'
|
||||
pt.x1 /= 4;
|
||||
pt.y1 /= 4;
|
||||
pt.x2 /= 4;
|
||||
pt.y2 /= 4;
|
||||
// we should now have 6 of each tap in 'pt'
|
||||
pt.x1 /= 6;
|
||||
pt.y1 /= 6;
|
||||
pt.x2 /= 6;
|
||||
pt.y2 /= 6;
|
||||
// work out final values
|
||||
var calib = {
|
||||
x1 : Math.round(pt.x1 - (pt.x2-pt.x1)*P/(g.getWidth()-P*2)),
|
||||
|
|
@ -870,13 +873,19 @@ function showTouchscreenCalibration() {
|
|||
x2 : Math.round(pt.x2 + (pt.x2-pt.x1)*P/(g.getWidth()-P*2)),
|
||||
y2 : Math.round(pt.y2 + (pt.y2-pt.y1)*P/(g.getHeight()-P*2))
|
||||
};
|
||||
Bangle.setOptions({
|
||||
touchX1: calib.x1, touchY1: calib.y1, touchX2: calib.x2, touchY2: calib.y2
|
||||
});
|
||||
var s = storage.readJSON("setting.json",1)||{};
|
||||
s.touch = calib;
|
||||
storage.writeJSON("setting.json",s);
|
||||
g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Calibrated!", g.getWidth()/2, g.getHeight()/2);
|
||||
var dx = calib.x2-calib.x1;
|
||||
var dy = calib.y2-calib.y1;
|
||||
if(dx<100 || dx>280 || dy<100 || dy>280) {
|
||||
g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Out of Range.\nPlease\ntry again", g.getWidth()/2, g.getHeight()/2);
|
||||
} else {
|
||||
Bangle.setOptions({
|
||||
touchX1: calib.x1, touchY1: calib.y1, touchX2: calib.x2, touchY2: calib.y2
|
||||
});
|
||||
var s = storage.readJSON("setting.json",1)||{};
|
||||
s.touch = calib;
|
||||
storage.writeJSON("setting.json",s);
|
||||
g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Calibrated!", g.getWidth()/2, g.getHeight()/2);
|
||||
}
|
||||
// now load the main menu again
|
||||
setTimeout(showLCDMenu, 500);
|
||||
}
|
||||
|
|
@ -897,7 +906,7 @@ function showTouchscreenCalibration() {
|
|||
if (currentCorner>=corners.length) {
|
||||
currentCorner = 0;
|
||||
currentTry++;
|
||||
if (currentTry==2) {
|
||||
if (currentTry==3) {
|
||||
Bangle.removeListener('touch', touchHandler);
|
||||
return calcCalibration();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AH4A/AH4A/AH4A/AFGsAAQOUA4OBDBYfHo8jAAOBByOssgICkdkF6AWEF5IOHxAHDAAVALx4VEF5AOHA4oAD0ovNoEjo6BCF5AOHF4ccp9dAoVdFxh2CioUCa44OIwIFCirwBjgFBqyNPqxJCEAOIKwOIBxYJChwfBwIABJQ7dHnIkCEAsVBxovDdiMcVYIgHFQgOJR4WIxBdMCoccshUCkdkRYJ6DBxIvDAAlkF5wWLBxQKJOAIvTroOOBQkcmU5aoYAKhwACjiqDqwOOTQQqDGwQvMAAawCcoOsAAQOL642DBAI0EAB1AC4QfBJIU5BxQ2DAAp5FF6ZJFF4xgFAAMOOwyPKjkcCgWsAoMyBxQAB1k5BAMcZAQuPDIS6EYBAIRAHKHCAIhPLAoWswIMEL6OBDIgYGHA4qCCwIKBDIgvPDggnIAw7lBF4J4KR5gVJKgZDDIgQCDMITvXXA47EHxD2aFQhOCBAY8DRiiWLD4aEBMYzuCLzgvEEQK4BFAQCDB4TACF7QA/AH4A/AH4A/AAQA=="))
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// Clock with large colored digits using the "Londrina" font and a slightly larger "Londrina Shadow" font on top
|
||||
|
||||
Graphics.prototype.setFontLondrinaSolid = function() {
|
||||
// Actual height 59 (64 - 6)
|
||||
return this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||
46,
|
||||
atob("DyEqHigoJikpJygqEQ=="),
|
||||
81|65536
|
||||
);
|
||||
};
|
||||
|
||||
Graphics.prototype.setFontLondrinaShadow = function() {
|
||||
// Actual height 63 (67 - 5)
|
||||
return this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||
46,
|
||||
atob("DyEqHigoJikpJygqEQ=="),
|
||||
81|65536
|
||||
);
|
||||
};
|
||||
|
||||
(function() {
|
||||
let drawTimeout;
|
||||
|
||||
// Actually draw the watch face
|
||||
function draw() {
|
||||
const x = g.getWidth() / 2;
|
||||
const y = g.getHeight() / 2;
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
const date = new Date();
|
||||
var hour = String(date.getHours()).padStart(2, '0');
|
||||
if (hour[0] === '0') hour = hour[1];
|
||||
var minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const timeStr = hour + ':' + minutes;
|
||||
|
||||
g.setFontAlign(0, 0).setFont("LondrinaSolid").setColor(0, 1, 1).drawString(timeStr, x - 1, y);
|
||||
g.reset().setFontAlign(0, 0).setFont("LondrinaShadow").drawString(timeStr, x - 1, y);
|
||||
|
||||
const locale = require("locale");
|
||||
const dateStr = locale.date(date, 0).toUpperCase() + "\n" +
|
||||
locale.dow(date, 0).toUpperCase();
|
||||
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y + 48);
|
||||
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(() => {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
Bangle.setUI({
|
||||
mode: "clock",
|
||||
remove: function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
draw();
|
||||
setTimeout(Bangle.drawWidgets, 0);
|
||||
})();
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "shadowclk",
|
||||
"name": "Shadow Clock",
|
||||
"version": "0.01",
|
||||
"description": "A simple clock using the Londrina font with color and a shadowed outline. Based on the Anton Clock.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"shadowclk.app.js","url":"app.js"},
|
||||
{"name":"shadowclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -11,3 +11,4 @@
|
|||
0.07: Fix when no alarms are present
|
||||
0.08: Selectable font. Allow to disable hour padding.
|
||||
0.09: Match draw() API e.g. to allow wid_edit to alter this widget
|
||||
0.10: Change 4x5 font to 6x8, teletext is now default font
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "widalarmeta",
|
||||
"name": "Alarm & Timer ETA",
|
||||
"shortName": "Alarm ETA",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@
|
|||
drawBell: false,
|
||||
padHours: true,
|
||||
showSeconds: 0, // 0=never, 1=only when display is unlocked, 2=for less than a minute
|
||||
font: 0, // 0=segment style font, 1=teletest font, 2=4x5
|
||||
font: 1, // 0=segment style font, 1=teletext font, 2=6x8:1x2
|
||||
}, require("Storage").readJSON(CONFIGFILE,1) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(CONFIGFILE, settings);
|
||||
WIDGETS["widalarmeta"].reload();
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
/*LANG*/'Font': {
|
||||
value: settings.font,
|
||||
min: 0, max: 2,
|
||||
format: v => [/*LANG*/"Segment", /*LANG*/"Teletext", /*LANG*/"4x5"][v || 0],
|
||||
format: v => [/*LANG*/"Segment", /*LANG*/"Teletext", /*LANG*/"6x8"][v === undefined ? 1 : v],
|
||||
onchange: v => {
|
||||
settings.font = v;
|
||||
writeSettings();
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
(() => {
|
||||
require("Font5x9Numeric7Seg").add(Graphics);
|
||||
require("FontTeletext5x9Ascii").add(Graphics);
|
||||
require("Font4x5").add(Graphics);
|
||||
const config = Object.assign({
|
||||
maxhours: 24,
|
||||
drawBell: false,
|
||||
padHours: true,
|
||||
showSeconds: 0, // 0=never, 1=only when display is unlocked, 2=for less than a minute
|
||||
font: 0, // 0=segment style font, 1=teletest font, 2=4x5
|
||||
}, require("Storage").readJSON("widalarmeta.json",1) || {});
|
||||
let config;
|
||||
|
||||
function loadSettings() {
|
||||
config = Object.assign({
|
||||
maxhours: 24,
|
||||
drawBell: false,
|
||||
padHours: true,
|
||||
showSeconds: 0, // 0=never, 1=only when display is unlocked, 2=for less than a minute
|
||||
font: 1, // 0=segment style font, 1=teletext font, 2=6x8:1x2
|
||||
}, require("Storage").readJSON("widalarmeta.json",1) || {});
|
||||
|
||||
if (config.font == 0) {
|
||||
require("Font5x9Numeric7Seg").add(Graphics);
|
||||
} else if (config.font == 1) {
|
||||
require("FontTeletext5x9Ascii").add(Graphics);
|
||||
}
|
||||
}
|
||||
loadSettings();
|
||||
|
||||
function getNextAlarm(date) {
|
||||
const alarms = (require("Storage").readJSON("sched.json",1) || []).filter(alarm => alarm.on && alarm.hidden !== true);
|
||||
|
|
@ -63,13 +71,13 @@
|
|||
if (drawSeconds) {
|
||||
text += ":" + seconds.padStart(2, '0');
|
||||
}
|
||||
if (config.font == 1) {
|
||||
if (config.font == 0) {
|
||||
g.setFont("5x9Numeric7Seg:1x2");
|
||||
} else if (config.font == 1) {
|
||||
g.setFont("Teletext5x9Ascii:1x2");
|
||||
} else if (config.font == 2) {
|
||||
g.setFont("4x5");
|
||||
} else {
|
||||
// Default to this if no other font is set.
|
||||
g.setFont("5x9Numeric7Seg:1x2");
|
||||
g.setFont("6x8:1x2");
|
||||
}
|
||||
g.drawString(text, this.x+1, this.y+12);
|
||||
|
||||
|
|
@ -112,7 +120,12 @@
|
|||
WIDGETS["widalarmeta"]={
|
||||
area:"tl",
|
||||
width: 0, // hide by default = assume no timer
|
||||
draw:draw
|
||||
draw:draw,
|
||||
reload: () => {
|
||||
loadSettings();
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
},
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
15
backup.js
|
|
@ -70,7 +70,7 @@ function bangleUpload() {
|
|||
var zip = new JSZip();
|
||||
var cmds = "";
|
||||
zip.loadAsync(data).then(function(zip) {
|
||||
return showPrompt("Restore from ZIP","Are you sure? This will remove all existing apps");
|
||||
return showPrompt("Restore from ZIP","Are you sure? This will overwrite existing apps");
|
||||
}).then(()=>{
|
||||
Progress.show({title:`Reading ZIP`});
|
||||
zip.forEach(function (path, file){
|
||||
|
|
@ -91,10 +91,15 @@ function bangleUpload() {
|
|||
});
|
||||
return promise;
|
||||
})
|
||||
.then(() => {
|
||||
Progress.hide({sticky:true});
|
||||
Progress.show({title:`Erasing...`});
|
||||
return Comms.removeAllApps(); })
|
||||
.then(()=>new Promise(resolve => {
|
||||
showPrompt("Erase Storage","Erase Storage? If restoring a complete backup you should erase storage, but in some cases you may want to upload files from a ZIP while keeping your Bangle's existing data.").then(()=>resolve(true), ()=>resolve(false));
|
||||
}))
|
||||
.then(eraseStorage => {
|
||||
if (eraseStorage) {
|
||||
Progress.hide({sticky:true});
|
||||
Progress.show({title:`Erasing...`});
|
||||
return Comms.removeAllApps();
|
||||
}})
|
||||
.then(() => {
|
||||
Progress.hide({sticky:true});
|
||||
Progress.show({title:`Restoring...`, sticky:true});
|
||||
|
|
|
|||
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 83d92f2178901aa3130643e3a580fdda0801f8c1
|
||||
Subproject commit 770b1b71f16399eb3eb86a4c3198e1fbed979110
|
||||
14
index.html
|
|
@ -133,14 +133,18 @@
|
|||
<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>
|
||||
<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="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="newGithubIssue" data-tooltip="Create a new issue on GitHub">New issue on GitHub</button></p>
|
||||
<p><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><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="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>
|
||||
<button class="btn tooltip" id="defaultbanglesettings" data-tooltip="Reset your Bangle's settings to the defaults">Reset Settings</button>
|
||||
</p>
|
||||
<h3>Settings</h3>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
|
|
@ -171,7 +175,7 @@
|
|||
<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>
|
||||
<button class="btn" id="defaultsettings">Reset to default settings</button>
|
||||
<button class="btn" id="defaultsettings">Reset to default App Loader settings</button>
|
||||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
|
|
|
|||
51
loader.js
|
|
@ -107,22 +107,24 @@ function filterAppsForDevice(deviceId) {
|
|||
// set the device dropdown
|
||||
document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps";
|
||||
|
||||
if (!device) {
|
||||
if (deviceId!==undefined)
|
||||
showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning");
|
||||
appJSON = originalAppJSON;
|
||||
} else {
|
||||
// Now filter apps
|
||||
appJSON = originalAppJSON.filter(app => {
|
||||
var supported = ["BANGLEJS"];
|
||||
if (!app.supports) {
|
||||
console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`);
|
||||
if (originalAppJSON) { // JSON might not have loaded yet
|
||||
if (!device) {
|
||||
if (deviceId!==undefined)
|
||||
showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning");
|
||||
appJSON = originalAppJSON;
|
||||
} else {
|
||||
// Now filter apps
|
||||
appJSON = originalAppJSON.filter(app => {
|
||||
var supported = ["BANGLEJS"];
|
||||
if (!app.supports) {
|
||||
console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`);
|
||||
return false;
|
||||
}
|
||||
if (app.supports.includes(deviceId)) return true;
|
||||
//console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`);
|
||||
return false;
|
||||
}
|
||||
if (app.supports.includes(deviceId)) return true;
|
||||
//console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
refreshLibrary();
|
||||
}
|
||||
|
|
@ -204,8 +206,11 @@ window.addEventListener('load', (event) => {
|
|||
});
|
||||
});
|
||||
|
||||
var el;
|
||||
|
||||
// Button to install all default apps in one go
|
||||
document.getElementById("reinstallall").addEventListener("click",event=>{
|
||||
el = document.getElementById("reinstallall");
|
||||
if (el) el.addEventListener("click",event=>{
|
||||
var promise = showPrompt("Reinstall","Really re-install all apps?").then(() => {
|
||||
Comms.reset().then(_ =>
|
||||
getInstalledApps()
|
||||
|
|
@ -231,8 +236,10 @@ window.addEventListener('load', (event) => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
// Button to install all default apps in one go
|
||||
document.getElementById("installdefault").addEventListener("click",event=>{
|
||||
el = document.getElementById("installdefault");
|
||||
if (el) el.addEventListener("click", event=>{
|
||||
getInstalledApps().then(() => {
|
||||
if (device.id == "BANGLEJS")
|
||||
return httpGet("defaultapps_banglejs1.json");
|
||||
|
|
@ -247,6 +254,16 @@ window.addEventListener('load', (event) => {
|
|||
});
|
||||
});
|
||||
|
||||
// Button to reset the Bangle's settings
|
||||
el = document.getElementById("defaultbanglesettings");
|
||||
if (el) el.addEventListener("click", event=>{
|
||||
showPrompt("Reset Settings","Really reset Bangle.js settings?").then(() => {
|
||||
Comms.write("\x10require('Storage').erase('setting.json');load()\n");
|
||||
showToast("Settings reset!", "success");
|
||||
}, function() { /* cancelled */ });
|
||||
});
|
||||
|
||||
|
||||
// BLE Compatibility
|
||||
var selectBLECompat = document.getElementById("settings-ble-compat");
|
||||
if (selectBLECompat) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
type AppInfo = {
|
||||
src: string,
|
||||
img: string,
|
||||
icon: string,
|
||||
name: string,
|
||||
type: AppType,
|
||||
sortorder?: number,
|
||||
};
|
||||
|
||||
type AppType = "app" | "clock" | "widget" | "module" | "bootloader" |
|
||||
"settings" | "clkinfo" | "RAM" | "launch" | "textinput" | "scheduler" |
|
||||
"notify" | "locale";
|
||||