Merge remote-tracking branch 'upstream/master'

pull/1633/head
KungPhoo 2022-03-28 23:10:37 +02:00
commit 16f3f4253e
21 changed files with 430 additions and 52 deletions

View File

@ -12,8 +12,8 @@ body.select div.select,body.export div.export{display:block}
body.select div.export,body.export div.select{display:none}
body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr div#showqr,body.export div#tokens{display:block}
#tokens th,#tokens td{padding:5px}
#tokens tr:nth-child(odd){background-color:#ccc}
#tokens tr:nth-child(even){background-color:#eee}
#tokens tr:nth-child(odd){background-color:#f1f1fc}
#tokens tr:nth-child(even){background-color:#fff}
#qr-canvas{margin:auto;width:calc(100%-20px);max-width:400px}
#advbtn,#scan,#tokenqr table{text-align:center}
#edittoken tbody#adv{display:none}
@ -226,15 +226,18 @@ function editToken(id) {
markup += selectMarkup('algorithm', otpAlgos, tokens[id].algorithm);
markup += '</td></tr>';
markup += '</tbody><tr><td id="advbtn" colspan="2">';
markup += '<button type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
markup += '<button class="btn" type="button" onclick="document.getElementById(\'edittoken\').classList.toggle(\'showadv\')">Advanced</button>';
markup += '</td></tr></table></form>';
markup += '<button type="button" onclick="updateTokens()">Cancel Edit</button>';
markup += '<button type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
markup += '<button class="btn" type="button" onclick="updateTokens()">Cancel Edit</button>';
markup += '&nbsp;';
markup += '<button class="btn" type="button" onclick="saveEdit(' + id + ', false)">Save Changes</button>';
markup += '&nbsp;';
if (tokens[id].isnew) {
markup += '<button type="button" onclick="startScan(handleTokenQr,cancelTokenQr)">Scan QR</button>';
markup += '<button class="btn" type="button" onclick="startScan(handleTokenQr,cancelTokenQr)">Scan QR</button>';
} else {
markup += '<button type="button" onclick="showTokenQr()">Show QR</button>';
markup += '<button type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
markup += '<button class="btn" type="button" onclick="showTokenQr()">Show QR</button>';
markup += '&nbsp;';
markup += '<button class="btn" type="button" onclick="saveEdit(' + id + ', true)">Forget Token</button>';
}
document.getElementById('edit').innerHTML = markup;
document.body.className = 'editing';
@ -304,9 +307,23 @@ function updateTokens() {
return '<input name="exp_' + id + '" type="checkbox" onclick="exportTokens(false, \'' + id + '\')">';
};
const tokenButton = function(fn, id, label, dir) {
return '<button type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
return '<button class="btn" type="button" onclick="' + fn + '(' + id + (dir ? ',' + dir : '') + ')">' + label + '</button>';
};
var markup = '<table><tr><th>';
var markup = '';
markup += '<div class="select">';
markup += '<button class="btn" type="button" onclick="addToken()">Add Token</button>';
markup += '&nbsp;';
markup += '<button class="btn" type="button" onclick="saveTokens()">Save to watch</button>';
markup += '&nbsp;';
markup += '<button class="btn" type="button" onclick="startScan(handleImportQr,cancelImportQr)">Import</button>';
markup += '&nbsp;';
markup += '<button class="btn" type="button" onclick="document.body.className=\'export\'">Export</button>';
markup += '</div><div class="export">';
markup += '<button class="btn" type="button" onclick="document.body.className=\'select\'">Cancel</button>';
markup += '&nbsp;';
markup += '<button class="btn" type="button" onclick="exportTokens(true, null)">Show QR</button>';
markup += '</div>';
markup += '<table><tr><th>';
markup += tokenSelect('all');
markup += '</th><th>Token</th><th colspan="2">Order</th></tr>';
/* any tokens marked new are cancelled new additions and must be removed */
@ -331,15 +348,6 @@ function updateTokens() {
markup += '</td></tr>';
}
markup += '</table>';
markup += '<div class="select">';
markup += '<button type="button" onclick="addToken()">Add Token</button>';
markup += '<button type="button" onclick="saveTokens()">Save to watch</button>';
markup += '<button type="button" onclick="startScan(handleImportQr,cancelImportQr)">Import</button>';
markup += '<button type="button" onclick="document.body.className=\'export\'">Export</button>';
markup += '</div><div class="export">';
markup += '<button type="button" onclick="document.body.className=\'select\'">Cancel</button>';
markup += '<button type="button" onclick="exportTokens(true, null)">Show QR</button>';
markup += '</div>';
document.getElementById('tokens').innerHTML = markup;
document.body.className = 'select';
}
@ -604,7 +612,7 @@ function qrBack() {
<div id="scan">
<table>
<tr><td><canvas id="qr-canvas"></canvas></td></tr>
<tr><td><button type="button" onclick="scanBack()">Cancel</button></td></tr>
<tr><td><button class="btn" type="button" onclick="scanBack()">Cancel</button></td></tr>
</table>
</div>
@ -613,7 +621,7 @@ function qrBack() {
<div id="showqr">
<table><tr><td id="qrcode"></td></tr><tr><td>
<button type="button" onclick="qrBack()">Back</button>
<button class="btn" type="button" onclick="qrBack()">Back</button>
</td></tr></table>
</div>

View File

@ -4,3 +4,4 @@
0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed).
0.05: Update calendar weekend colors for start on Sunday
0.06: Use larger font for dates
0.07: Fix off-by-one-error on previous month

View File

@ -171,7 +171,7 @@ function drawCalendar(date) {
let days = [];
let nextMonthDay = 1;
let thisMonthDay = 51;
let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm;
let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
for (let i = 0; i < colN * (rowN - 1) + 1; i++) {
if (i < dowNorm) {
days.push(prevMonthDay);

View File

@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.06",
"version": "0.07",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],

View File

@ -39,4 +39,4 @@
0.24: Remove left-over debug statement
0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550)
0.26: Setting to auto-open music
0.27: Option to auto-unlock the watch when a new message arrives
0.27: Add 'mark all read' option to popup menu (fix #1624)

View File

@ -302,6 +302,11 @@ function showMessageSettings(msg) {
saveMessages();
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
},
/*LANG*/"Mark all read" : () => {
MESSAGES.forEach(msg => msg.new = false);
saveMessages();
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
},
/*LANG*/"Delete all messages" : () => {
E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Delete All Messages"}).then(isYes => {
if (isYes) {

View File

@ -0,0 +1 @@
0.01: Initial version

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("kMigILIgPAAYMD/ADBwcGhkAwM5wcA/+2//Av/Rn/giFoyFggkUrFggEKlAkCiApCx+AAYNGoADBkU4AYMQj4DBvEICANkAoIPBgE2B4MAiMAH4MAwECAYNALYUgBIISCHYMYAoQWBAIMEgAYBAIMBwEDDQNgDwUf/4eBg4DCAA4"))

120
apps/quicklaunch/app.js Normal file
View File

@ -0,0 +1,120 @@
var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {});
var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{name:a.name,type:a.type,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="launch" || app.type=="clock" || !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;
});
function save(key, value) {
settings[key] = value;
require("Storage").write("quicklaunch.json",settings);
}
// Quick Launch menu
function showMainMenu() {
var mainmenu = {
"" : { "title" : "Quick Launch" },
"< Back" : ()=>{load();}
};
//List all selected apps
mainmenu["Left: "+settings.leftapp.name] = function() { E.showMenu(leftmenu); };
mainmenu["Right: "+settings.rightapp.name] = function() { E.showMenu(rightmenu); };
mainmenu["Up: "+settings.upapp.name] = function() { E.showMenu(upmenu); };
mainmenu["Down: "+settings.downapp.name] = function() { E.showMenu(downmenu); };
mainmenu["Tap: "+settings.tapapp.name] = function() { E.showMenu(tapmenu); };
return E.showMenu(mainmenu);
}
//Left swipe menu
var leftmenu = {
"" : { "title" : "Left Swipe" },
"< Back" : showMainMenu
};
leftmenu["(none)"] = function() {
save("leftapp", {"name":"(none)"});
showMainMenu();
};
apps.forEach((a)=>{
leftmenu[a.name] = function() {
save("leftapp", a);
showMainMenu();
};
});
//Right swipe menu
var rightmenu = {
"" : { "title" : "Right Swipe" },
"< Back" : showMainMenu
};
rightmenu["(none)"] = function() {
save("rightapp", {"name":"(none)"});
showMainMenu();
};
apps.forEach((a)=>{
rightmenu[a.name] = function() {
save("rightapp", a);
showMainMenu();
};
});
//Up swipe menu
var upmenu = {
"" : { "title" : "Up Swipe" },
"< Back" : showMainMenu
};
upmenu["(none)"] = function() {
save("upapp", {"name":"(none)"});
showMainMenu();
};
apps.forEach((a)=>{
upmenu[a.name] = function() {
save("upapp", a);
showMainMenu();
};
});
//Down swipe menu
var downmenu = {
"" : { "title" : "Down Swipe" },
"< Back" : showMainMenu
};
downmenu["(none)"] = function() {
save("downapp", {"name":"(none)"});
showMainMenu();
};
apps.forEach((a)=>{
downmenu[a.name] = function() {
save("downapp", a);
showMainMenu();
};
});
//Tap menu
var tapmenu = {
"" : { "title" : "Tap" },
"< Back" : showMainMenu
};
tapmenu["(none)"] = function() {
save("tapapp", {"name":"(none)"});
showMainMenu();
};
apps.forEach((a)=>{
tapmenu[a.name] = function() {
save("tapapp", a);
showMainMenu();
};
});
showMainMenu();

BIN
apps/quicklaunch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

67
apps/quicklaunch/boot.js Normal file
View File

@ -0,0 +1,67 @@
(function() {
var settings = Object.assign(require("Storage").readJSON("quicklaunch.json", true) || {});
//list all sources
var apps = require("Storage").list(/\.info$/).map(app=>{var a=require("Storage").readJSON(app,1);return a&&{src:a.src};});
//populate empty app list
if (!settings.leftapp) {
settings["leftapp"] = {"name":"(none)"};
require("Storage").write("quicklaunch.json",settings);
}
if (!settings.rightapp) {
settings["rightapp"] = {"name":"(none)"};
require("Storage").write("quicklaunch.json",settings);
}
if (!settings.upapp) {
settings["upapp"] = {"name":"(none)"};
require("Storage").write("quicklaunch.json",settings);
}
if (!settings.downapp) {
settings["downapp"] = {"name":"(none)"};
require("Storage").write("quicklaunch.json",settings);
}
if (!settings.tapapp) {
settings["tapapp"] = {"name":"(none)"};
require("Storage").write("quicklaunch.json",settings);
}
//activate on clock faces
var sui = Bangle.setUI;
Bangle.setUI = function(mode, cb) {
sui(mode,cb);
if(!mode) return;
if ("object"==typeof mode) mode = mode.mode;
if (!mode.startsWith("clock")) return;
function tap() {
//tap, check if source exists, launch
if ((settings.tapapp.src) && apps.some(e => e.src === settings.tapapp.src)) load (settings.tapapp.src);
}
let drag;
let e;
Bangle.on("touch",tap);
Bangle.on("drag", e => {
if (!drag) { // start dragging
drag = {x: e.x, y: e.y};
} else if (!e.b) { // released
const dx = e.x-drag.x, dy = e.y-drag.y;
drag = null;
//horizontal swipes, check if source exists, launch
if (Math.abs(dx)>Math.abs(dy)+10) {
if ((settings.leftapp.src) && apps.some(e => e.src === settings.leftapp.src) && dx<0) load(settings.leftapp.src);
if ((settings.rightapp.src) && apps.some(e => e.src === settings.rightapp.src) && dx>0) load(settings.rightapp.src);
}
//vertical swipes, check if source exists, launch
else if (Math.abs(dy)>Math.abs(dx)+10) {
if ((settings.upapp.src) && apps.some(e => e.src === settings.upapp.src) && dy<0) load(settings.upapp.src);
if ((settings.downapp.src) && apps.some(e => e.src === settings.downapp.src) && dy>0) load(settings.downapp.src);
}
}
});
};
})();

View File

@ -0,0 +1,14 @@
{ "id": "quicklaunch",
"name": "Quick Launch",
"icon": "app.png",
"version":"0.01",
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice.",
"tags": "tools, system",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"quicklaunch.app.js","url":"app.js"},
{"name":"quicklaunch.boot.js","url":"boot.js"},
{"name":"quicklaunch.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"quicklaunch.json"}]
}

View File

@ -9,4 +9,5 @@
0.08: Added support for notifications from exstats. Support all stats from exstats
0.09: Fix broken start/stop if recording not enabled (fix #1561)
0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578)
0.11: Notifications fixes
0.11: Notifications fixes
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11

View File

@ -59,7 +59,7 @@ function onStartStop() {
layout.render();
})
);
} else if (!settings.record && WIDGETS["recorder"]) {
} else {
prepPromises.push(
WIDGETS["recorder"].setRecording(false)
);
@ -68,7 +68,7 @@ function onStartStop() {
if (!prepPromises.length) // fix for Promise.all bug in 2v12
prepPromises.push(Promise.resolve());
Promise.all(prepPromises)
.then(() => {
if (running) {

View File

@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
"version":"0.11",
"version":"0.12",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",

View File

@ -91,7 +91,7 @@
];
notificationsMenu[/*LANG*/"Dist Pattern"] = {
value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))),
min: 0, max: vibTimes.length,
min: 0, max: vibTimes.length - 1,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.dist.notifications = vibTimes[v];
@ -101,7 +101,7 @@
}
notificationsMenu[/*LANG*/"Step Pattern"] = {
value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))),
min: 0, max: vibTimes.length,
min: 0, max: vibTimes.length - 1,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.step.notifications = vibTimes[v];
@ -111,7 +111,7 @@
}
notificationsMenu[/*LANG*/"Time Pattern"] = {
value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))),
min: 0, max: vibTimes.length,
min: 0, max: vibTimes.length - 1,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.time.notifications = vibTimes[v];

View File

@ -2,39 +2,63 @@ Todo List
========
This is a simple Todo List application.
The content is loaded from a JSON file.
A task can be marked as completed or uncompleted.
![](screenshot2.png)
The content is loaded from a JSON file.
You can mark a task as completed.
Once installed, the list can be modified via the `Download data from app` icon in the [Bangle.js App Store](https://banglejs.com/apps/) (TodoList app).
![](screenshot4.png)
JSON file content example:
```javascript
[
{
name: "Pro",
children: [
"name": "Pro",
"children": [
{
name: "Read doc",
done: true,
children: [],
"name": "Read doc",
"done": true,
"children": []
}
],
]
},
{
name: "Pers",
children: [
"name": "Pers",
"children": [
{
name: "Grocery",
children: [
{ name: "Milk", done: false, children: [] },
{ name: "Eggs", done: false, children: [] },
{ name: "Cheese", done: false, children: [] },
],
"name": "Grocery",
"children": [
{
"name": "Milk",
"done": false,
"children": []
},
{
"name": "Eggs",
"done": false,
"children": []
},
{
"name": "Cheese",
"done": false,
"children": []
}
]
},
{ name: "Workout", done: false, children: [] },
{ name: "Learn Rust", done: false, children: [] },
],
},
{
"name": "Workout",
"done": false,
"children": []
},
{
"name": "Learn Rust",
"done": false,
"children": []
}
]
}
]
```

View File

@ -0,0 +1,135 @@
<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="todos" contenteditable></pre>
<script src="../../core/lib/interface.js"></script>
<script>
const fileTodoList = "todolist.json";
function errorFormat() {
var date = new Date();
var error =
'<p class="alert">' +
date.toUTCString() +
" : Wrong format, it should be JSON" +
"</p>";
return error;
}
function getEditableContent() {
return document.getElementById("todos").innerHTML.replace(/<[^>]*>/g, '');;
}
function isJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
console.log(str)
console.log(e)
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 JSON file from the watch.
*/
function loadTodos() {
document.getElementById("info").innerHTML = "";
Util.showModal("Loading...");
Puck.eval(`require('Storage').readJSON("${fileTodoList}")`, (data) => {
document.getElementById("todos").innerHTML = JSON.stringify(
data,
null,
2
);
Util.hideModal();
});
}
/* Save settings as a JSON file on the watch.
*/
function uploadTodos() {
document.getElementById("info").innerHTML = "";
Util.showModal("Uploading...");
let jsonTodos = getEditableContent();
if (isJsonString(jsonTodos)) {
let shortJsonTodos = JSON.stringify(JSON.parse(jsonTodos));
uploadFile(fileTodoList, shortJsonTodos);
} else {
document.getElementById("info").innerHTML = errorFormat();
}
Util.hideModal();
}
function downloadTodos() {
document.getElementById("info").innerHTML = "";
Util.showModal("Downloading...");
let jsonTodos = getEditableContent();
if (isJsonString(jsonTodos)) {
var a = document.createElement("a"),
file = new Blob([jsonTodos], { type: "application/json" });
var url = URL.createObjectURL(file);
a.href = url;
a.download = fileTodoList;
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 () {
uploadTodos();
});
document
.getElementById("btnDownload")
.addEventListener("click", function () {
downloadTodos();
});
document
.getElementById("btnReload")
.addEventListener("click", function () {
loadTodos();
});
function onInit() {
loadTodos();
}
</script>
</body>
</html>

View File

@ -10,6 +10,7 @@
"tags": "tool,todo",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{ "name": "todolist.app.js", "url": "app.js" },
{ "name": "todolist.img", "url": "app-icon.js", "evaluate": true }

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -135,7 +135,7 @@ Bangle.on("GPS", function(fix) {
if (stats["dist"]) stats["dist"].emit("changed",stats["dist"]);
var duration = Date.now() - state.startTime; // in ms
state.avrSpeed = state.distance * 1000 / duration; // meters/sec
state.curSpeed = state.curSpeed*0.8 + fix.speed*0.2/3.6; // meters/sec
if (!isNaN(fix.speed)) state.curSpeed = state.curSpeed*0.8 + fix.speed*0.2/3.6; // meters/sec
if (stats["pacea"]) stats["pacea"].emit("changed",stats["pacea"]);
if (stats["pacec"]) stats["pacec"].emit("changed",stats["pacec"]);
if (stats["speed"]) stats["speed"].emit("changed",stats["speed"]);