Merge branch 'master' of github.com:espruino/BangleApps
commit
74430167c3
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Added scrollable calendar and swipe gestures
|
||||
0.03: Configurable drag gestures
|
||||
0.04: Use default Bangle formatter for booleans
|
||||
0.05: Improved colors (connected vs disconnected)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ function drawMinutes() {
|
|||
var d = new Date();
|
||||
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
|
||||
var minutes = d.getMinutes().toString().padStart(2, '0');
|
||||
var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
|
||||
var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff';
|
||||
var size = 50;
|
||||
var clock_x = (w - 20) / 2;
|
||||
if (dimSeconds) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "clockcal",
|
||||
"name": "Clock & Calendar",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Clock with Calendar",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@
|
|||
0.04: Increase screen update rate when charging
|
||||
0.05: Deleting Background - making Font larger
|
||||
0.06: Fixing refresh issues
|
||||
0.07: Fixed position after unlocking
|
||||
0.07: Fixed position after unlocking
|
||||
0.08: Handling exceptions
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"name": "A Battery Widget (with percentage) - Hanks Mod",
|
||||
"shortName":"H Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@
|
|||
var y = this.y;
|
||||
if ((typeof x === 'undefined') || (typeof y === 'undefined')) {
|
||||
} else {
|
||||
const l = E.getBattery();
|
||||
const l = E.getBattery(); // debug: Math.floor(Math.random() * 101);
|
||||
let xl = x+4+l*(s-12)/100;
|
||||
if (l != old_l){ // Delete the old value from screen
|
||||
if ((l != old_l) && (typeof old_l != 'undefined') ){ // Delete the old value from screen
|
||||
let xl_old = x+4+old_l*(s-12)/100;
|
||||
g.setColor(COLORS.white);
|
||||
// g.fillRect(x+2,y+5,x+s-6,y+18);
|
||||
|
|
@ -41,9 +41,9 @@
|
|||
//g.fillRect(old_x,old_y,old_x+4+l*(s-12)/100,old_y+16+3); // clear (lazy)
|
||||
g.drawString(old_l, old_x + 14, old_y + 10);
|
||||
g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar
|
||||
old_l = l;
|
||||
|
||||
}
|
||||
|
||||
old_l = l;
|
||||
//console.log(old_x);
|
||||
|
||||
g.setColor(levelColor(l));
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
}
|
||||
|
||||
Bangle.on('charging',function(charging) { draw(); });
|
||||
var id = setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), intervalLow);
|
||||
var id = setInterval(()=>WIDGETS["hwid_a_battery_widget"].draw(), intervalLow);
|
||||
|
||||
WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
||||
WIDGETS["hwid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: first release
|
||||
0.02: added interface for configuration from app loader
|
||||
|
|
|
|||
|
|
@ -12,11 +12,10 @@ when the time for the last slide is approaching,
|
|||
the button becomes red, when it passed,
|
||||
the time will go on for another half a minute and stop automatically.
|
||||
|
||||
The only way to upload personalized timings is
|
||||
by uploading a CSV to the bangle (i.e. from the IDE),
|
||||
in the future I'll possibly figure out a better way.
|
||||
You can set personalized timings from the web interface
|
||||
by uploading a CSV to the bangle (floppy disk button in the app loader).
|
||||
|
||||
Each line in the file (which must be called `presentation_timer.csv`)
|
||||
Each line in the file (`presentation_timer.csv`)
|
||||
contains the time in minutes at which the slide
|
||||
is supposed to finish and the slide number,
|
||||
separated by a semicolon.
|
||||
|
|
@ -25,8 +24,7 @@ is lasting until 1 minutes 30 seconds (yes it's decimal),
|
|||
after another slide will start.
|
||||
The only requirement is that timings are increasing,
|
||||
so slides number don't have to be consecutive,
|
||||
some can be skipped and they can even be short texts
|
||||
(be careful with that, I didn't test it).
|
||||
some can be skipped and they can even be short texts.
|
||||
|
||||
At the moment the app is just quick and dirty
|
||||
but it should do its job.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css" />
|
||||
|
||||
<style type="text/css">
|
||||
.alert {
|
||||
padding: 20px;
|
||||
background-color: #f44336; /* Red */
|
||||
color: white;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="info"></div>
|
||||
|
||||
<button id="btnReload" class="btn btn-primary">Reload from watch</button>
|
||||
<button id="btnUpload" class="btn btn-primary">Upload to watch</button>
|
||||
<button id="btnDownload" class="btn btn-primary">Download</button>
|
||||
|
||||
<pre id="presentation_timer" contenteditable></pre>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
const filePresentationTimer = "presentation_timer.csv";
|
||||
const defaultValue="1.5;1\n2;2\n2.5;3\n3;4\n"
|
||||
|
||||
function errorFormat() {
|
||||
var date = new Date();
|
||||
var error =
|
||||
'<p class="alert">' +
|
||||
date.toUTCString() +
|
||||
" : Wrong format, it should be CSV, refer to the README" +
|
||||
"</p>";
|
||||
return error;
|
||||
}
|
||||
|
||||
function getEditableContent() {
|
||||
//transform any tag in an EOL (should be <br> or <div>), ignore ending tags
|
||||
return document.getElementById("presentation_timer")
|
||||
.innerHTML.replace(/<[^>]*>/g,"\n").replace(/\n\n+/g, '\n').replace(/^\n|\n$/g, '');
|
||||
}
|
||||
|
||||
const re = new RegExp("^([0-9]*[.])?[0-9]+;[A-z0-9]+$");
|
||||
function isCorrectCsvString(str) {
|
||||
let wronglines = str.split("\n").filter(e=>e && !e.match(re));
|
||||
//TODO check for increasing numbers
|
||||
if(wronglines.length) {
|
||||
console.log(wronglines.join("\n"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function uploadFile(fileid, contents) {
|
||||
Puck.write(
|
||||
`\x10(function() {
|
||||
require("Storage").write("${fileid}",\`${contents}\`);
|
||||
Bluetooth.print("OK");
|
||||
})()\n`,
|
||||
(ret) => {
|
||||
console.log("uploadFile", ret);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* Load settings CSV file from the watch.
|
||||
*/
|
||||
function loadTimes() {
|
||||
document.getElementById("info").innerHTML = "";
|
||||
Util.showModal("Loading...");
|
||||
Puck.eval(`require('Storage').read("${filePresentationTimer}")`, (data) => {
|
||||
document.getElementById("presentation_timer").innerHTML = data;
|
||||
Util.hideModal();
|
||||
if(!data) {
|
||||
document.getElementById("presentation_timer").innerHTML = defaultValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* Save settings as a CSV file on the watch.
|
||||
*/
|
||||
function uploadTimes() {
|
||||
document.getElementById("info").innerHTML = "";
|
||||
Util.showModal("Uploading...");
|
||||
let csvTimes = getEditableContent();
|
||||
if (isCorrectCsvString(csvTimes)) {
|
||||
uploadFile(filePresentationTimer, csvTimes);
|
||||
} else {
|
||||
document.getElementById("info").innerHTML = errorFormat();
|
||||
}
|
||||
Util.hideModal();
|
||||
}
|
||||
|
||||
function downloadTimes() {
|
||||
document.getElementById("info").innerHTML = "";
|
||||
Util.showModal("Downloading...");
|
||||
let csvTimes = getEditableContent();
|
||||
if (isCorrectCsvString(csvTimes)) {
|
||||
var a = document.createElement("a"),
|
||||
file = new Blob([csvTimes], { type: "text/csv" });
|
||||
var url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = filePresentationTimer;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
} else {
|
||||
document.getElementById("info").innerHTML = errorFormat();
|
||||
}
|
||||
Util.hideModal();
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("btnUpload")
|
||||
.addEventListener("click", function () {
|
||||
uploadTimes();
|
||||
});
|
||||
document
|
||||
.getElementById("btnDownload")
|
||||
.addEventListener("click", function () {
|
||||
downloadTimes();
|
||||
});
|
||||
document
|
||||
.getElementById("btnReload")
|
||||
.addEventListener("click", function () {
|
||||
loadTimes();
|
||||
});
|
||||
function onInit() {
|
||||
loadTimes();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
{
|
||||
"id": "presentation_timer",
|
||||
"name": "Presentation Timer",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A touch based presentation timer for Bangle JS 2",
|
||||
"icon": "presentation_timer.png",
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
|
||||
"tags": "tools,app",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
|
||||
{"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{ "name": "presentation_timer.csv" }]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ const margin = 0.5; //half a minute tolerance
|
|||
|
||||
//dummy default values
|
||||
var slides = [
|
||||
[0.3, 1],
|
||||
[0.5, 2],
|
||||
[0.7, 3],
|
||||
[1,4]
|
||||
[1.5, 1],
|
||||
[2, 2],
|
||||
[2.5, 3],
|
||||
[3,4]
|
||||
];
|
||||
|
||||
function log_debug(o) {
|
||||
|
|
@ -267,6 +267,6 @@ g.fillRect(0,0,w,h);
|
|||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
readSlides();
|
||||
draw();
|
||||
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
|
||||
readSlides();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
exports.load = (num) => {
|
||||
return require("Storage").readJSON(`waypoints${num?`.${num}`:""}.json`)||[{name:"NONE"}];
|
||||
return require("Storage").readJSON(`waypoints${num?"."+num:""}.json`)||[{name:"NONE"}];
|
||||
};
|
||||
|
||||
exports.save = (waypoints,num) => {
|
||||
require("Storage").writeJSON(`waypoints${num?`.${num}`:""}.json`, waypoints);
|
||||
require("Storage").writeJSON(`waypoints${num?"."+num:""}.json`, waypoints);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Don't poll for GPS status, override setGPSPower handler (fix #1456)
|
||||
0.06: Periodically update so the always on display does show current GPS fix
|
||||
0.07: Alternative marker icon (configurable via settings)
|
||||
0.08: Add ability to hide the icon when GPS is off, for a cleaner appearance.
|
||||
|
|
@ -14,6 +14,7 @@ There are two icons which can be used to visualize the GPS/GNSS status:
|
|||
- Shows in green when the GPS is on and has a fix
|
||||
2. Different place markers depending on GPS/GNSS status
|
||||
|
||||
You can also choose to hide the icon when the GPS is off in the settings.
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
|
|
@ -21,4 +22,6 @@ Forum](http://forum.espruino.com/microcosms/1424/)
|
|||
|
||||
Extended by Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
Extended by khromov ([khromov](https://github.com/khromov))
|
||||
|
||||
Place marker icons from [icons8.com](https://icons8.com/icon/set/maps/material-outlined).
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"crossIcon": "true"}
|
||||
{"crossIcon": true, "hideWhenGpsOff": false}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "widgps",
|
||||
"name": "GPS Widget",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Tiny widget to show the power and fix status of the GPS/GNSS",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
|
|
|||
|
|
@ -20,9 +20,13 @@ var mainmenu = {
|
|||
'' : {'title' : 'GPS widget'},
|
||||
'< Back' : back,
|
||||
"Cross icon" : {
|
||||
value : !!settings.crossIcon ,
|
||||
value : settings.crossIcon ,
|
||||
onchange : v => { writeSettings("crossIcon", v); }
|
||||
},
|
||||
"Hide icon when GPS off" : {
|
||||
value : settings.hideWhenGpsOff ,
|
||||
onchange : v => { writeSettings("hideWhenGpsOff", v); }
|
||||
},
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
(function() {
|
||||
let settings =
|
||||
require("Storage").readJSON("widgps.json", 1) || {crossIcon : true};
|
||||
|
||||
let settings = Object.assign(
|
||||
require('Storage').readJSON("widgps.default.json", true) || {},
|
||||
require('Storage').readJSON("widgps.json", true) || {}
|
||||
);
|
||||
|
||||
var interval;
|
||||
|
||||
|
|
@ -35,13 +38,23 @@ WIDGETS.gps = {
|
|||
} else {
|
||||
g.setColor("#FD0"); // on but no fix = amber
|
||||
}
|
||||
} else {
|
||||
g.setColor("#888"); // off = grey
|
||||
}
|
||||
g.drawImage(
|
||||
|
||||
g.drawImage(
|
||||
atob(
|
||||
"GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="),
|
||||
this.x, 2 + this.y);
|
||||
|
||||
} else {
|
||||
if(!settings.hideWhenGpsOff) {
|
||||
g.setColor("#888"); // off = grey
|
||||
|
||||
g.drawImage(
|
||||
atob(
|
||||
"GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="),
|
||||
this.x, 2 + this.y);
|
||||
}
|
||||
}
|
||||
|
||||
} else { // marker icons
|
||||
if (Bangle.isGPSOn()) {
|
||||
const gpsObject = Bangle.getGPSFix();
|
||||
|
|
@ -59,9 +72,11 @@ WIDGETS.gps = {
|
|||
}
|
||||
} else {
|
||||
// GNSS off
|
||||
g.drawImage(
|
||||
atob("GBiBAAAAAAAAAAB+ABj/ABxDgA4AwAcAwAeMYAfEYAbgYAZwYAM4wAMcQAOOAAGHAAHDgADDwABm4AB+cAA8OAAYGAAAAAAAAAAAAA=="),
|
||||
this.x, 2 + this.y);
|
||||
if(!settings.hideWhenGpsOff) {
|
||||
g.drawImage(
|
||||
atob("GBiBAAAAAAAAAAB+ABj/ABxDgA4AwAcAwAeMYAfEYAbgYAZwYAM4wAMcQAOOAAGHAAHDgADDwABm4AB+cAA8OAAYGAAAAAAAAAAAAA=="),
|
||||
this.x, 2 + this.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue