some fixes
|
|
@ -16,3 +16,8 @@ _site
|
|||
Desktop.ini
|
||||
.sync_*.db*
|
||||
*.swp
|
||||
*_BACKUP_*
|
||||
*_BASE_*
|
||||
*_LOCAL_*
|
||||
*_REMOTE_*
|
||||
*.orig
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
'< Back': back,
|
||||
'Full Screen': {
|
||||
value: settings.fullscreen,
|
||||
format: () => (settings.fullscreen ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.fullscreen = !settings.fullscreen;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"agenda.app.js","url":"agenda.js"},
|
||||
{"name":"agenda.settings.js","url":"settings.js"},
|
||||
{"name":"agenda.app.js","url":"agenda.app.js"},
|
||||
{"name":"agenda.settings.js","url":"agenda.settings.js"},
|
||||
{"name":"agenda.clkinfo.js","url":"agenda.clkinfo.js"},
|
||||
{"name":"agenda.img","url":"agenda-icon.js","evaluate":true}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -32,3 +32,4 @@
|
|||
Allow alarm enable/disable
|
||||
0.31: Implement API for activity fetching
|
||||
0.32: Added support for loyalty cards from gadgetbridge
|
||||
0.33: Fix alarms created in Gadgetbridge not repeating
|
||||
|
|
|
|||
|
|
@ -81,7 +81,12 @@
|
|||
for (var j = 0; j < event.d.length; j++) {
|
||||
// prevents all alarms from going off at once??
|
||||
var dow = event.d[j].rep;
|
||||
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
var rp = false;
|
||||
if (!dow) {
|
||||
dow = 127; //if no DOW selected, set alarm to all DOW
|
||||
} else {
|
||||
rp = true;
|
||||
}
|
||||
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
|
||||
var a = require("sched").newDefaultAlarm();
|
||||
a.id = "gb"+j;
|
||||
|
|
@ -89,6 +94,7 @@
|
|||
a.on = event.d[j].on !== undefined ? event.d[j].on : true;
|
||||
a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
|
||||
a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
|
||||
a.rp = rp;
|
||||
a.last = last;
|
||||
alarms.push(a);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.32",
|
||||
"version": "0.33",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
g.clear().setRotation(1);
|
||||
// g.setRotation ALSO changes accelerometer axes
|
||||
var avrAngle = undefined;
|
||||
var history = [];
|
||||
|
||||
var R = Bangle.appRect;
|
||||
var W = g.getWidth();
|
||||
var H = g.getHeight();
|
||||
var relativeTo = undefined;
|
||||
|
||||
function draw(v) {
|
||||
if (v===undefined) v = Bangle.getAccel();
|
||||
// current angle
|
||||
var d = Math.sqrt(v.y*v.y + v.z*v.z);
|
||||
var ang = Math.atan2(-v.x, d)*180/Math.PI;
|
||||
// Median filter
|
||||
if (history.length > 10) history.shift(); // pull old reading off the start
|
||||
history.push(ang);
|
||||
avrAngle = history.slice().sort()[(history.length-1)>>1]; // median filter
|
||||
// Render
|
||||
var x = R.x + R.w/2;
|
||||
var y = R.y + R.h/2;
|
||||
g.reset().clearRect(R).setFontAlign(0,0);
|
||||
var displayAngle = avrAngle;
|
||||
g.setFont("6x15").drawString("ANGLE (DEGREES)", x, R.y2-8);
|
||||
if (relativeTo!==undefined) {
|
||||
g.drawString("RELATIVE TO", x,y-50);
|
||||
g.setFont("Vector:30").drawString(relativeTo.toFixed(1),x,y-30);
|
||||
y += 20;
|
||||
displayAngle = displayAngle-relativeTo;
|
||||
}
|
||||
g.setFont("Vector:60").drawString(displayAngle.toFixed(1),x,y);
|
||||
|
||||
}
|
||||
|
||||
draw();
|
||||
Bangle.on('accel',draw);
|
||||
|
||||
// Pressing the button turns relative angle on/off
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
btn : function(n) {
|
||||
if (relativeTo===undefined)
|
||||
relativeTo = avrAngle;
|
||||
else
|
||||
relativeTo = undefined;
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA"))
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "angles",
|
||||
"name": "Angles (Spirit Level)",
|
||||
"shortName": "Angles",
|
||||
"version": "0.01",
|
||||
"description": "Shows Angle or Relative angle in degrees (Digital Protractor/Inclinometer). Place Bangle sideways against a surface with the button facing away for best readings.",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"angles.app.js","url":"app.js"},
|
||||
{"name":"angles.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1 @@
|
|||
aviatorclk.json
|
||||
|
|
@ -0,0 +1 @@
|
|||
1.00: initial release
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Aviator Clock
|
||||
|
||||
A clock for aviators, with local time and UTC - and the latest METAR
|
||||
(Meteorological Aerodrome Report) for the nearest airport
|
||||
|
||||

|
||||

|
||||
|
||||
This app depends on the [AVWX module](?id=avwx). Make sure to configure that
|
||||
module after installing this app.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Local time (with optional seconds)
|
||||
- UTC / Zulu time
|
||||
- Weekday and day of the month
|
||||
- Latest METAR for the nearest airport (scrollable)
|
||||
|
||||
Tap the screen in the top or bottom half to scroll the METAR text (in case not
|
||||
the whole report fits on the screen).
|
||||
|
||||
The colour of the METAR text will change to orange if the report is more than
|
||||
1h old, and red if it's older than 1.5h.
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
- **Show Seconds**: to conserve battery power, you can turn the seconds display off
|
||||
- **Invert Scrolling**: swaps the METAR scrolling direction of the top and bottom taps
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
Flaparoo [github](https://github.com/flaparoo)
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwg96iIACCqMBCwYABiAWQiUiAAUhDBwWGDCAWHDAYuMCw4ABGBYWKGBYuLGBcBLpAXNFxhIKFxgwCIyhIJC58hC44WNC5B2NPBIXbBYIAHNgIXKCpAYEC5AhBII8SDAQXJMI5EEC6ZREC6EhFwkRO4zuCC46AFAgLYEC4YCBIoaADF4gXEKgYXDVBAcCXxBZDkcyDRAXHmILCif//4GEC5f/PQQWB//zbAX/C5gAKC78BC6K/In4WJ+YXW+QXHMAURl4XJeQYWEGALhBC4q+BYYLbDFwowCkLTCRIyNHGArNBC48SFxIXCMApHDOwQXIJAIQCAAaWCDYJGIDAipGFwQWKDAUSDAnzUoIWMDAcjn/zUgQWOPYYADOZJjKFqIAp"))
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Aviator Clock - Bangle.js
|
||||
*
|
||||
*/
|
||||
|
||||
const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25)
|
||||
const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5)
|
||||
const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75)
|
||||
const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0)
|
||||
const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1)
|
||||
const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0)
|
||||
const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1)
|
||||
const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0)
|
||||
const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5)
|
||||
const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0)
|
||||
|
||||
const APP_NAME = 'aviatorclk';
|
||||
|
||||
const horizontalCenter = g.getWidth()/2;
|
||||
const mainTimeHeight = 38;
|
||||
const secondaryFontHeight = 22;
|
||||
const dateColour = ( g.theme.dark ? COLOUR_YELLOW : COLOUR_BLUE );
|
||||
const UTCColour = ( g.theme.dark ? COLOUR_LIGHT_CYAN : COLOUR_DARK_CYAN );
|
||||
const separatorColour = ( g.theme.dark ? COLOUR_LIGHT_GREY : COLOUR_DARK_GREY );
|
||||
|
||||
const avwx = require('avwx');
|
||||
|
||||
|
||||
// read in the settings
|
||||
var settings = Object.assign({
|
||||
showSeconds: true,
|
||||
invertScrolling: false,
|
||||
}, require('Storage').readJSON(APP_NAME+'.json', true) || {});
|
||||
|
||||
|
||||
// globals
|
||||
var drawTimeout;
|
||||
var secondsInterval;
|
||||
var avwxTimeout;
|
||||
|
||||
var AVWXrequest;
|
||||
var METAR = '';
|
||||
var METARlinesCount = 0;
|
||||
var METARscollLines = 0;
|
||||
var METARts;
|
||||
|
||||
|
||||
|
||||
// date object to time string in format HH:MM[:SS]
|
||||
// (with a leading 0 for hours if required, unlike the "locale" time() function)
|
||||
function timeStr(date, seconds) {
|
||||
let timeStr = date.getHours().toString();
|
||||
if (timeStr.length == 1) timeStr = '0' + timeStr;
|
||||
let minutes = date.getMinutes().toString();
|
||||
if (minutes.length == 1) minutes = '0' + minutes;
|
||||
timeStr += ':' + minutes;
|
||||
if (seconds) {
|
||||
let seconds = date.getSeconds().toString();
|
||||
if (seconds.length == 1) seconds = '0' + seconds;
|
||||
timeStr += ':' + seconds;
|
||||
}
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
|
||||
// draw the METAR info
|
||||
function drawAVWX() {
|
||||
let now = new Date();
|
||||
let METARage = 0; // in minutes
|
||||
if (METARts) {
|
||||
METARage = Math.floor((now - METARts) / 60000);
|
||||
}
|
||||
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
let y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight + 4;
|
||||
g.clearRect(0, y, g.getWidth(), y + (secondaryFontHeight * 4));
|
||||
|
||||
g.setFontAlign(0, -1).setFont("Vector", secondaryFontHeight);
|
||||
if (METARage > 90) { // older than 1.5h
|
||||
g.setColor(COLOUR_RED);
|
||||
} else if (METARage > 60) { // older than 1h
|
||||
g.setColor( g.theme.dark ? COLOUR_ORANGE : COLOUR_DARK_YELLOW );
|
||||
} else {
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
let METARlines = g.wrapString(METAR, g.getWidth());
|
||||
METARlinesCount = METARlines.length;
|
||||
METARlines.splice(0, METARscollLines);
|
||||
g.drawString(METARlines.join("\n"), horizontalCenter, y, true);
|
||||
|
||||
if (! avwxTimeout) { avwxTimeout = setTimeout(updateAVWX, 5 * 60000); }
|
||||
}
|
||||
|
||||
// update the METAR info
|
||||
function updateAVWX() {
|
||||
if (avwxTimeout) clearTimeout(avwxTimeout);
|
||||
avwxTimeout = undefined;
|
||||
|
||||
METAR = '\nGetting GPS fix';
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
drawAVWX();
|
||||
|
||||
Bangle.setGPSPower(true, APP_NAME);
|
||||
Bangle.on('GPS', fix => {
|
||||
// prevent multiple, simultaneous requests
|
||||
if (AVWXrequest) { return; }
|
||||
|
||||
if ('fix' in fix && fix.fix != 0 && fix.satellites >= 4) {
|
||||
Bangle.setGPSPower(false, APP_NAME);
|
||||
let lat = fix.lat;
|
||||
let lon = fix.lon;
|
||||
|
||||
METAR = '\nRequesting METAR';
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
drawAVWX();
|
||||
|
||||
// get latest METAR from nearest airport (via AVWX API)
|
||||
AVWXrequest = avwx.request('metar/'+lat+','+lon, 'onfail=nearest', data => {
|
||||
if (avwxTimeout) clearTimeout(avwxTimeout);
|
||||
avwxTimeout = undefined;
|
||||
|
||||
let METARjson = JSON.parse(data.resp);
|
||||
|
||||
if ('sanitized' in METARjson) {
|
||||
METAR = METARjson.sanitized;
|
||||
} else {
|
||||
METAR = 'No "sanitized" METAR data found!';
|
||||
}
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
|
||||
if ('time' in METARjson) {
|
||||
METARts = new Date(METARjson.time.dt);
|
||||
let now = new Date();
|
||||
let METARage = Math.floor((now - METARts) / 60000); // in minutes
|
||||
if (METARage <= 30) {
|
||||
// some METARs update every 30 min -> attempt to update after METAR is 35min old
|
||||
avwxTimeout = setTimeout(updateAVWX, (35 - METARage) * 60000);
|
||||
} else if (METARage <= 60) {
|
||||
// otherwise, attempt METAR update after it's 65min old
|
||||
avwxTimeout = setTimeout(updateAVWX, (65 - METARage) * 60000);
|
||||
}
|
||||
} else {
|
||||
METARts = undefined;
|
||||
}
|
||||
|
||||
drawAVWX();
|
||||
AVWXrequest = undefined;
|
||||
|
||||
}, error => {
|
||||
// AVWX API request failed
|
||||
console.log(error);
|
||||
METAR = 'ERR: ' + error;
|
||||
METARlinesCount = 0; METARscollLines = 0;
|
||||
METARts = undefined;
|
||||
drawAVWX();
|
||||
AVWXrequest = undefined;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// draw only the seconds part of the main clock
|
||||
function drawSeconds() {
|
||||
let now = new Date();
|
||||
let seconds = now.getSeconds().toString();
|
||||
if (seconds.length == 1) seconds = '0' + seconds;
|
||||
let y = Bangle.appRect.y + mainTimeHeight - 3;
|
||||
g.setFontAlign(-1, 1).setFont("Vector", secondaryFontHeight).setColor(COLOUR_GREY);
|
||||
g.drawString(seconds, horizontalCenter + 54, y, true);
|
||||
}
|
||||
|
||||
// sync seconds update
|
||||
function syncSecondsUpdate() {
|
||||
drawSeconds();
|
||||
setTimeout(function() {
|
||||
drawSeconds();
|
||||
secondsInterval = setInterval(drawSeconds, 1000);
|
||||
}, 1000 - (Date.now() % 1000));
|
||||
}
|
||||
|
||||
// set timeout for per-minute updates
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
if (METARts) {
|
||||
let now = new Date();
|
||||
let METARage = Math.floor((now - METARts) / 60000);
|
||||
if (METARage > 60) {
|
||||
// the METAR colour might have to be updated:
|
||||
drawAVWX();
|
||||
}
|
||||
}
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
// draw top part of clock (main time, date and UTC)
|
||||
function draw() {
|
||||
let now = new Date();
|
||||
let nowUTC = new Date(now + (now.getTimezoneOffset() * 1000 * 60));
|
||||
|
||||
// prepare main clock area
|
||||
let y = Bangle.appRect.y;
|
||||
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
// main time display
|
||||
g.setFontAlign(0, -1).setFont("Vector", mainTimeHeight).setColor(g.theme.fg);
|
||||
g.drawString(timeStr(now, false), horizontalCenter, y, true);
|
||||
|
||||
// prepare second line (UTC and date)
|
||||
y += mainTimeHeight;
|
||||
g.clearRect(0, y, g.getWidth(), y + secondaryFontHeight - 1);
|
||||
|
||||
// weekday and day of the month
|
||||
g.setFontAlign(-1, -1).setFont("Vector", secondaryFontHeight).setColor(dateColour);
|
||||
g.drawString(require("locale").dow(now, 1).toUpperCase() + ' ' + now.getDate(), 0, y, false);
|
||||
|
||||
// UTC
|
||||
g.setFontAlign(1, -1).setFont("Vector", secondaryFontHeight).setColor(UTCColour);
|
||||
g.drawString(timeStr(nowUTC, false) + "Z", g.getWidth(), y, false);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
|
||||
// initialise
|
||||
g.clear(true);
|
||||
|
||||
// scroll METAR lines on taps
|
||||
Bangle.setUI("clockupdown", action => {
|
||||
switch (action) {
|
||||
case -1: // top tap
|
||||
if (settings.invertScrolling) {
|
||||
if (METARscollLines > 0)
|
||||
METARscollLines--;
|
||||
} else {
|
||||
if (METARscollLines < METARlinesCount - 4)
|
||||
METARscollLines++;
|
||||
}
|
||||
break;
|
||||
case 1: // bottom tap
|
||||
if (settings.invertScrolling) {
|
||||
if (METARscollLines < METARlinesCount - 4)
|
||||
METARscollLines++;
|
||||
} else {
|
||||
if (METARscollLines > 0)
|
||||
METARscollLines--;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
drawAVWX();
|
||||
});
|
||||
|
||||
// load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// draw static separator line
|
||||
y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight;
|
||||
g.setColor(separatorColour);
|
||||
g.drawLine(0, y, g.getWidth(), y);
|
||||
|
||||
// draw times and request METAR
|
||||
draw();
|
||||
if (settings.showSeconds)
|
||||
syncSecondsUpdate();
|
||||
updateAVWX();
|
||||
|
||||
|
||||
// TMP for debugging:
|
||||
//METAR = 'YAAA 011100Z 21014KT CAVOK 23/08 Q1018 RMK RF000/0000';
|
||||
//METAR = 'YAAA 150900Z 14012KT 9999 SCT045 BKN064 26/14 Q1012 RMK RF000/0000 DL-W/DL-NW';
|
||||
//METAR = 'YAAA 020030Z VRB CAVOK';
|
||||
//METARts = new Date(Date.now() - 61 * 60000); // 61 to trigger warning, 91 to trigger alert
|
||||
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -0,0 +1,35 @@
|
|||
(function(back) {
|
||||
var FILE = "aviatorclk.json";
|
||||
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
showSeconds: true,
|
||||
invertScrolling: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"" : { "title" : "AV8R Clock" },
|
||||
"< Back" : () => back(),
|
||||
'Show Seconds': {
|
||||
value: !!settings.showSeconds, // !! converts undefined to false
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.showSeconds = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Invert Scrolling': {
|
||||
value: !!settings.invertScrolling, // !! converts undefined to false
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.invertScrolling = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "aviatorclk",
|
||||
"name": "Aviator Clock",
|
||||
"shortName":"AV8R Clock",
|
||||
"version":"1.00",
|
||||
"description": "A clock for aviators, with local time and UTC - and the latest METAR for the nearest airport",
|
||||
"icon": "aviatorclk.png",
|
||||
"screenshots": [{ "url": "screenshot.png" }, { "url": "screenshot2.png" }],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies" : { "avwx": "module" },
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name":"aviatorclk.app.js", "url":"aviatorclk.app.js" },
|
||||
{ "name":"aviatorclk.settings.js", "url":"aviatorclk.settings.js" },
|
||||
{ "name":"aviatorclk.img", "url":"aviatorclk-icon.js", "evaluate":true }
|
||||
],
|
||||
"data": [{ "name":"aviatorclk.json" }]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -0,0 +1 @@
|
|||
1.00: initial release
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# AVWX Module
|
||||
|
||||
This is a module/library to use the [AVWX](https://account.avwx.rest/) Aviation
|
||||
Weather API. It doesn't include an app.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
You will need an AVWX account (see above for link) and generate an API token.
|
||||
The free "Hobby" plan is normally sufficient, but please consider supporting
|
||||
the AVWX project.
|
||||
|
||||
After installing the module on your Bangle, use the "interface" page (floppy
|
||||
disk icon) in the App Loader to set the API token.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Include the module in your app with:
|
||||
|
||||
const avwx = require('avwx');
|
||||
|
||||
Then use the exported function, for example to get the "sanitized" METAR from
|
||||
the nearest station to a lat/lon coordinate pair:
|
||||
|
||||
reqID = avwx.request('metar/'+lat+','+lon,
|
||||
'filter=sanitized&onfail=nearest',
|
||||
data => { console.log(data); },
|
||||
error => { console.log(error); });
|
||||
|
||||
The returned reqID can be useful to track whether a request has already been
|
||||
made (ie. the app is still waiting on a response).
|
||||
|
||||
Please consult the [AVWX documentation](https://avwx.docs.apiary.io/) for
|
||||
information about the available end-points and request parameters.
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
Flaparoo [github](https://github.com/flaparoo)
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* AVWX Bangle Module
|
||||
*
|
||||
* AVWX doco: https://avwx.docs.apiary.io/
|
||||
* test AVWX API request with eg.: curl -X GET 'https://avwx.rest/api/metar/43.9844,-88.5570?token=...'
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const AVWX_BASE_URL = 'https://avwx.rest/api/'; // must end with a slash
|
||||
const AVWX_CONFIG_FILE = 'avwx.json';
|
||||
|
||||
|
||||
// read in the settings
|
||||
var AVWXsettings = Object.assign({
|
||||
AVWXtoken: '',
|
||||
}, require('Storage').readJSON(AVWX_CONFIG_FILE, true) || {});
|
||||
|
||||
|
||||
/**
|
||||
* Make an AVWX API request
|
||||
*
|
||||
* @param {string} requestPath API path (after /api/), eg. 'meta/KOSH'
|
||||
* @param {string} params optional request parameters, eg. 'onfail=nearest' (use '&' in the string to combine multiple params)
|
||||
* @param {function} successCB callback if the API request was successful - will supply the returned data: successCB(data)
|
||||
* @param {function} failCB callback in case the API request failed - will supply the error: failCB(error)
|
||||
*
|
||||
* @returns {number} the HTTP request ID
|
||||
*
|
||||
* Example:
|
||||
* reqID = avwx.request('metar/'+lat+','+lon,
|
||||
* 'filter=sanitized&onfail=nearest',
|
||||
* data => { console.log(data); },
|
||||
* error => { console.log(error); });
|
||||
*
|
||||
*/
|
||||
exports.request = function(requestPath, optParams, successCB, failCB) {
|
||||
if (! AVWXsettings.AVWXtoken) {
|
||||
failCB('No AVWX API Token defined!');
|
||||
return undefined;
|
||||
}
|
||||
let params = 'token='+AVWXsettings.AVWXtoken;
|
||||
if (optParams)
|
||||
params += '&'+optParams;
|
||||
return Bangle.http(AVWX_BASE_URL+requestPath+'?'+params).then(successCB).catch(failCB);
|
||||
};
|
||||
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,47 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>To use the <a href="https://account.avwx.rest/">AVWX</a> API, you need an account and generate an API token. The free "Hobby" plan is sufficient, but please consider supporting the AVWX project.</p>
|
||||
<p>
|
||||
<label class="form-label" for="AVWXtoken">AVWX API Token:</label>
|
||||
<input class="form-input" type="text" id="AVWXtoken" placeholder="Your personal AVWX API Token" />
|
||||
</p>
|
||||
<p>
|
||||
<button id="upload" class="btn btn-primary">Configure</button>
|
||||
</p>
|
||||
|
||||
<p><div id="status"></div></p>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
var AVWXsettings = {};
|
||||
|
||||
function onInit() {
|
||||
// read in existing settings to preserve them during an update
|
||||
try {
|
||||
Util.readStorageJSON('avwx.json', currentSettings => {
|
||||
if (currentSettings) {
|
||||
AVWXsettings = currentSettings;
|
||||
if ('AVWXtoken' in AVWXsettings) {
|
||||
document.getElementById('AVWXtoken').value = AVWXsettings.AVWXtoken;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Failed to read existing settings: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
AVWXsettings.AVWXtoken = document.getElementById('AVWXtoken').value;
|
||||
Util.writeStorage('avwx.json', JSON.stringify(AVWXsettings), () => {
|
||||
document.getElementById("status").innerHTML = 'AVWX configuration successfully uploaded to Bangle!';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "avwx",
|
||||
"name": "AVWX Module",
|
||||
"shortName":"AVWX",
|
||||
"version":"1.00",
|
||||
"description": "Module/library for the AVWX API",
|
||||
"icon": "avwx.png",
|
||||
"type": "module",
|
||||
"tags": "outdoors",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"provides_modules": ["avwx"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{ "name":"avwx", "url":"avwx.js" }
|
||||
],
|
||||
"data": [{ "name":"avwx.json" }]
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
'< Back': back,
|
||||
'Buzz': {
|
||||
value: "buzz" in settings ? settings.buzz : false,
|
||||
format: () => (settings.buzz ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.buzz = !settings.buzz;
|
||||
save('buzz', settings.buzz);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Added app
|
||||
0.02: Removed unneeded squares
|
||||
0.03: Added settings with fullscreen option
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
var settings = Object.assign({
|
||||
fullscreen: false,
|
||||
}, require('Storage').readJSON("binaryclk.json", true) || {});
|
||||
|
||||
function draw() {
|
||||
var dt = new Date();
|
||||
var h = dt.getHours(), m = dt.getMinutes();
|
||||
|
|
@ -11,10 +15,14 @@ function draw() {
|
|||
g.clearRect(Bangle.appRect);
|
||||
|
||||
let i = 0;
|
||||
var gap = 8;
|
||||
var mgn = 20;
|
||||
if (settings.fullscreen) {
|
||||
gap = 12;
|
||||
mgn = 0;
|
||||
}
|
||||
const sq = 29;
|
||||
const gap = 8;
|
||||
const mgn = 20;
|
||||
const pos = sq + gap;
|
||||
var pos = sq + gap;
|
||||
|
||||
for (let r = 3; r >= 0; r--) {
|
||||
for (let c = 0; c < 4; c++) {
|
||||
|
|
@ -26,14 +34,15 @@ function draw() {
|
|||
}
|
||||
i++;
|
||||
}
|
||||
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
|
||||
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
|
||||
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
|
||||
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
|
||||
}
|
||||
|
||||
|
||||
g.clear();
|
||||
draw();
|
||||
var secondInterval = setInterval(draw, 60000);
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
if (!settings.fullscreen) {
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "binaryclk",
|
||||
"name": "Bin Clock",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Clock face to show binary time in 24 hr format",
|
||||
"icon": "app-icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"binaryclk.app.js","url":"app.js"},
|
||||
{"name":"binaryclk.settings.js","url":"settings.js"},
|
||||
{"name":"binaryclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"binaryclk.json"}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
(function(back) {
|
||||
var FILE = "binaryclk.json";
|
||||
var settings = Object.assign({
|
||||
fullscreen: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "Bin Clock" },
|
||||
"< Back" : () => back(),
|
||||
'Fullscreen': {
|
||||
value: settings.fullscreen,
|
||||
onchange: v => {
|
||||
settings.fullscreen = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
@ -32,7 +32,6 @@
|
|||
},
|
||||
'Show Lock': {
|
||||
value: settings.showLock,
|
||||
format: () => (settings.showLock ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.showLock = !settings.showLock;
|
||||
save();
|
||||
|
|
@ -40,7 +39,6 @@
|
|||
},
|
||||
'Hide Colon': {
|
||||
value: settings.hideColon,
|
||||
format: () => (settings.hideColon ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.hideColon = !settings.hideColon;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
},
|
||||
'Show Lock': {
|
||||
value: settings.showLock,
|
||||
format: () => (settings.showLock ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.showLock = !settings.showLock;
|
||||
save();
|
||||
|
|
@ -40,7 +39,6 @@
|
|||
},
|
||||
'Hide Colon': {
|
||||
value: settings.hideColon,
|
||||
format: () => (settings.hideColon ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.hideColon = !settings.hideColon;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -17,3 +17,4 @@
|
|||
0.15: Edit holidays on device in settings
|
||||
0.16: Add menu to fast open settings to edit holidays
|
||||
Display Widgets in menus
|
||||
0.17: Load holidays before events so the latter is not overpainted
|
||||
|
|
|
|||
|
|
@ -43,24 +43,24 @@ const dowLbls = function() {
|
|||
}();
|
||||
|
||||
const loadEvents = () => {
|
||||
// add holidays & other events
|
||||
events = (require("Storage").readJSON("calendar.days.json",1) || []).map(d => {
|
||||
const date = new Date(d.date);
|
||||
const o = {date: date, msg: d.name, type: d.type};
|
||||
if (d.repeat) {
|
||||
o.repeat = d.repeat;
|
||||
}
|
||||
return o;
|
||||
});
|
||||
// all alarms that run on a specific date
|
||||
events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
|
||||
events = events.concat((require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
|
||||
const date = new Date(a.date);
|
||||
const time = timeutils.decodeTime(a.t);
|
||||
date.setHours(time.h);
|
||||
date.setMinutes(time.m);
|
||||
date.setSeconds(time.s);
|
||||
return {date: date, msg: a.msg, type: "e"};
|
||||
});
|
||||
// add holidays & other events
|
||||
(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
|
||||
const date = new Date(d.date);
|
||||
const o = {date: date, msg: d.name, type: d.type};
|
||||
if (d.repeat) {
|
||||
o.repeat = d.repeat;
|
||||
}
|
||||
events.push(o);
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
const loadSettings = () => {
|
||||
|
|
@ -280,14 +280,12 @@ const showMenu = function() {
|
|||
setUI();
|
||||
},
|
||||
/*LANG*/"Exit": () => load(),
|
||||
/*LANG*/"Settings": () => {
|
||||
const appSettings = eval(require('Storage').read('calendar.settings.js'));
|
||||
appSettings(() => {
|
||||
/*LANG*/"Settings": () =>
|
||||
eval(require('Storage').read('calendar.settings.js'))(() => {
|
||||
loadSettings();
|
||||
loadEvents();
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
}),
|
||||
};
|
||||
if (require("Storage").read("alarm.app.js")) {
|
||||
menu[/*LANG*/"Launch Alarms"] = () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
},
|
||||
/*LANG*/'show widgets': {
|
||||
value: !!settings.showWidgets,
|
||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: x => save('showWidgets', x),
|
||||
},
|
||||
/*LANG*/'update interval': {
|
||||
|
|
@ -45,7 +44,6 @@
|
|||
},
|
||||
/*LANG*/'show big weather': {
|
||||
value: !!settings.showBigWeather,
|
||||
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
||||
onchange: x => save('showBigWeather', x),
|
||||
},
|
||||
/*LANG*/'colorize icons': ()=>showCircleMenus()
|
||||
|
|
@ -87,8 +85,7 @@
|
|||
const colorizeIconKey = circleName + "colorizeIcon";
|
||||
menu[/*LANG*/'circle ' + circleId] = {
|
||||
value: settings[colorizeIconKey] || false,
|
||||
format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
|
||||
onchange: x => save(colorizeIconKey, x),
|
||||
onchange: x => save(colorizeIconKey, x),
|
||||
};
|
||||
}
|
||||
E.showMenu(menu);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
'': { 'title': 'CLI complete clk' },
|
||||
'Show battery': {
|
||||
value: "battery" in settings ? settings.battery : false,
|
||||
format: () => (settings.battery ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.battery = !settings.battery;
|
||||
save('battery', settings.battery);
|
||||
|
|
@ -27,7 +26,6 @@
|
|||
},
|
||||
'Show weather': {
|
||||
value: "weather" in settings ? settings.weather : false,
|
||||
format: () => (settings.weather ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.weather = !settings.weather;
|
||||
save('weather', settings.weather);
|
||||
|
|
@ -35,7 +33,6 @@
|
|||
},
|
||||
'Show steps': {
|
||||
value: "steps" in settings ? settings.steps : false,
|
||||
format: () => (settings.steps ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.steps = !settings.steps;
|
||||
save('steps', settings.steps);
|
||||
|
|
@ -43,7 +40,6 @@
|
|||
},
|
||||
'Show heartrate': {
|
||||
value: "heartrate" in settings ? settings.heartrate : false,
|
||||
format: () => (settings.heartrate ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.heartrate = !settings.heartrate;
|
||||
save('heartrate', settings.heartrate);
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Catch and discard swipe events on fw2v19 and up (as well as some cutting
|
||||
edge 2v18 ones), allowing compatability with the Back Swipe app.
|
||||
0.09: Fix colors settings, where color was stored as string instead of the expected int.
|
||||
0.10: Fix touch region for letters
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ exports.input = function(options) {
|
|||
"ram";
|
||||
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
// Choose character by draging along red rectangle at bottom of screen
|
||||
if (event.y >= ( (R.y+R.h) - 12 )) {
|
||||
if (event.y >= ( (R.y+R.h) - 26 )) {
|
||||
// Translate x-position to character
|
||||
if (event.x < ABCPADDING) { abcHL = 0; }
|
||||
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
||||
|
|
@ -139,7 +139,7 @@ exports.input = function(options) {
|
|||
|
||||
// 12345678901234567890
|
||||
// Choose number or puctuation by draging on green rectangle
|
||||
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
|
||||
else if ((event.y < ( (R.y+R.h) - 26 )) && (event.y > ( (R.y+R.h) - 52 ))) {
|
||||
// Translate x-position to character
|
||||
if (event.x < NUMPADDING) { numHL = 0; }
|
||||
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "dragboard",
|
||||
"name": "Dragboard",
|
||||
"version":"0.09",
|
||||
"version":"0.10",
|
||||
"description": "A library for text input via swiping keyboard",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Allow boot exceptions, e.g. to load DST
|
||||
0.03: Permit exceptions to load in low-power mode, e.g. daylight saving time.
|
||||
Also avoid polluting global scope.
|
||||
0.04: Enhance menu: enable bluetooth, visit settings & visit recovery
|
||||
|
|
|
|||
|
|
@ -61,14 +61,13 @@ var reload = function () {
|
|||
nextDraw = undefined;
|
||||
},
|
||||
btn: function () {
|
||||
E.showPrompt("Restore watch to full power?").then(function (v) {
|
||||
if (v) {
|
||||
drainedRestore();
|
||||
}
|
||||
else {
|
||||
reload();
|
||||
}
|
||||
});
|
||||
var menu = {
|
||||
"Restore to full power": drainedRestore,
|
||||
"Enable BLE": function () { return NRF.wake(); },
|
||||
"Settings": function () { return load("setting.app.js"); },
|
||||
"Recovery": function () { return Bangle.showRecoveryMenu(); },
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
});
|
||||
Bangle.CLOCK = 1;
|
||||
|
|
|
|||
|
|
@ -79,13 +79,13 @@ const reload = () => {
|
|||
nextDraw = undefined;
|
||||
},
|
||||
btn: () => {
|
||||
E.showPrompt("Restore watch to full power?").then(v => {
|
||||
if(v){
|
||||
drainedRestore();
|
||||
}else{
|
||||
reload();
|
||||
}
|
||||
})
|
||||
const menu = {
|
||||
"Restore to full power": drainedRestore,
|
||||
"Enable BLE": () => NRF.wake(),
|
||||
"Settings": () => load("setting.app.js"),
|
||||
"Recovery": () => Bangle.showRecoveryMenu(),
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
});
|
||||
Bangle.CLOCK=1;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
{
|
||||
"id": "drained",
|
||||
"name": "Drained",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals",
|
||||
"readme": "README.md",
|
||||
"icon": "icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: attempt to import
|
||||
0.02: Make it possible for Fastload Utils to fastload into this app.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// App Forge
|
||||
|
||||
"Bangle.loadWidgets()"; // Facilitates fastloading to this app via Fastload Utils, while still not loading widgets on standard `load` calls.
|
||||
|
||||
st = require('Storage');
|
||||
|
||||
l = /^a\..*\.js$/;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "forge",
|
||||
"name": "App Forge",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Easy way to run development versions of your apps",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
'< Back': back,
|
||||
'Show Widgets': {
|
||||
value: settings.showWidgets,
|
||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.showWidgets = !settings.showWidgets;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -88,29 +88,34 @@ function onInit(device) {
|
|||
document.getElementById("fw-unknown").style = "display:none";
|
||||
document.getElementById("fw-ok").style = "";
|
||||
}
|
||||
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
|
||||
console.log("DFU CRC = "+crc);
|
||||
var version = `unknown (CRC ${crc})`;
|
||||
Puck.eval("[E.CRC32(E.memoryArea(0xF7000,0x6000)),E.CRC32(E.memoryArea(0xF7000,0x7000))]", crcs => {
|
||||
console.log("DFU CRC (6 pages) = "+crcs[0]);
|
||||
console.log("DFU CRC (7 pages) = "+crcs[1]);
|
||||
var version = `unknown (CRC ${crcs[1]})`;
|
||||
var ok = true;
|
||||
if (crc==1339551013) { version = "2v10.219"; ok = false; }
|
||||
if (crc==1207580954) { version = "2v10.236"; ok = false; }
|
||||
if (crc==3435933210) version = "2v11.52";
|
||||
if (crc==46757280) version = "2v11.58";
|
||||
if (crc==3508163280 || crc==1418074094) version = "2v12";
|
||||
if (crc==4056371285) version = "2v13";
|
||||
if (crc==1038322422) version = "2v14";
|
||||
if (crc==2560806221) version = "2v15";
|
||||
if (crc==2886730689) version = "2v16";
|
||||
if (crc==156320890) version = "2v17";
|
||||
if (crc==4012421318) version = "2v18";
|
||||
if (crc==1856454048) version = "2v19";
|
||||
if (crc==2893810756 || crc==1273571156) version = "2v20";
|
||||
if (crcs[0] == 1787004733) { // check 6 page CRC - the 7th page isn't used in 2v20
|
||||
version = "2v20";
|
||||
} else { // for other versions all 7 pages are used, check those
|
||||
var crc = crcs[1];
|
||||
if (crc==1339551013) { version = "2v10.219"; ok = false; }
|
||||
if (crc==1207580954) { version = "2v10.236"; ok = false; }
|
||||
if (crc==3435933210) version = "2v11.52";
|
||||
if (crc==46757280) version = "2v11.58";
|
||||
if (crc==3508163280 || crc==1418074094) version = "2v12";
|
||||
if (crc==4056371285) version = "2v13";
|
||||
if (crc==1038322422) version = "2v14";
|
||||
if (crc==2560806221) version = "2v15";
|
||||
if (crc==2886730689) version = "2v16";
|
||||
if (crc==156320890) version = "2v17";
|
||||
if (crc==4012421318) version = "2v18";
|
||||
if (crc==1856454048) version = "2v19";
|
||||
}
|
||||
if (!ok) {
|
||||
version += `(⚠ update required)`;
|
||||
}
|
||||
document.getElementById("boot-version").innerHTML = version;
|
||||
var versionNumber = parseFloat(version.replace(".","").replace("v","."));
|
||||
if (versionNumber>=2.15)
|
||||
if (versionNumber>=2.20)
|
||||
document.getElementById("fw-old-bootloader-msg").style.display = "none";
|
||||
});
|
||||
}
|
||||
|
|
@ -426,7 +431,7 @@ function handleUpload() {
|
|||
storage:[
|
||||
{name:"RAM", content:hexJS},
|
||||
]
|
||||
});
|
||||
}, { noFinish: true });
|
||||
}
|
||||
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@
|
|||
|
||||
/*LANG*/"Step Goal Notification": {
|
||||
value: "stepGoalNotification" in settings ? settings.stepGoalNotification : false,
|
||||
format: () => (settings.stepGoalNotification ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.stepGoalNotification = !settings.stepGoalNotification;
|
||||
setSettings();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
0.01: First Release
|
||||
0.02: Changing resolution to seconds instead of 5 seconds
|
||||
|
|
@ -149,9 +149,9 @@ function showMenu()
|
|||
"START" : function() { startSession(); },
|
||||
"Sets" : { value : settings.sets,min:0,max:20,step:1,onchange : v => { settings.sets=v; } },
|
||||
"Work minutes" : { value : settings.workmin,min:0,max:59,step:1,onchange : v => { settings.workmin=v; } },
|
||||
"Work seconds" : { value : settings.workseg,min:0,max:59,step:5,onchange : v => { settings.workseg=v; } },
|
||||
"Work seconds" : { value : settings.workseg,min:0,max:59,step:1,onchange : v => { settings.workseg=v; } },
|
||||
"Rest minutes" : { value : settings.restmin,min:0,max:59,step:1,onchange : v => { settings.restmin=v; } },
|
||||
"Rest seconds" : { value : settings.restseg,min:0,max:59,step:5,onchange : v => { settings.restseg=v; } },
|
||||
"Rest seconds" : { value : settings.restseg,min:0,max:59,step:1,onchange : v => { settings.restseg=v; } },
|
||||
"Signal type" : { value : settings.buzz,format : v => v?"Buzz":"Beep",onchange : v => { settings.buzz=v; }}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "intervals",
|
||||
"name": "Intervals App",
|
||||
"shortName": "Intervals",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Intervals for training. It is possible to configure work time and rest time and number of sets.",
|
||||
"icon": "intervals.png",
|
||||
"tags": "",
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ var bg_code = [
|
|||
},
|
||||
'Full Screen': {
|
||||
value: settings.fullscreen,
|
||||
format: () => (settings.fullscreen ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.fullscreen = !settings.fullscreen;
|
||||
save();
|
||||
|
|
@ -120,7 +119,6 @@ var bg_code = [
|
|||
},
|
||||
'Disable alarm functionality': {
|
||||
value: settings.disableAlarms,
|
||||
format: () => (settings.disableAlarms ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.disableAlarms = !settings.disableAlarms;
|
||||
save();
|
||||
|
|
@ -128,7 +126,6 @@ var bg_code = [
|
|||
},
|
||||
'Disable data pages functionality': {
|
||||
value: settings.disableData,
|
||||
format: () => (settings.disableData ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.disableData = !settings.disableData;
|
||||
save();
|
||||
|
|
@ -136,7 +133,6 @@ var bg_code = [
|
|||
},
|
||||
'Random colors on open': {
|
||||
value: settings.randomColors,
|
||||
format: () => (settings.randomColors ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.randomColors = !settings.randomColors;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -27,13 +27,12 @@
|
|||
}
|
||||
|
||||
var font_options = ["Limelight","GochiHand","Grenadier","Monoton"];
|
||||
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Limelight Clock' },
|
||||
'< Back': back,
|
||||
'Full Screen': {
|
||||
value: s.fullscreen,
|
||||
format: () => (s.fullscreen ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.fullscreen = !s.fullscreen;
|
||||
save();
|
||||
|
|
@ -50,7 +49,6 @@
|
|||
},
|
||||
'Vector Font': {
|
||||
value: s.vector,
|
||||
format: () => (s.vector ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.vector = !s.vector;
|
||||
save();
|
||||
|
|
@ -68,7 +66,6 @@
|
|||
},
|
||||
'Second Hand': {
|
||||
value: s.secondhand,
|
||||
format: () => (s.secondhand ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.secondhand = !s.secondhand;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.1: init app
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Paul Spenke
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Line Clock
|
||||
|
||||
This app displays a simple, different looking, analog clock. It considers the
|
||||
currently configured "theme" (and may therefore look different than shown in
|
||||
the screenshot on your watch depending on which theme you prefer).
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgYMJh/4AgUD+AeKgIRDj/+n41O/4RQABcfIJYAEKZgAkL4U/8ARNBwIRP/+AGx6YBPSH/4ASPh/A/hfDAAZAHg/8gP/LguSoARHEwIRFiVJkDCFjgRHgEJkg4CcwQjIAAMEHAUDCoIRB46kIHAkH//xLIw4I8eAnCNKHAYAO/xxEABg4ByASPHAkBKAbUE/5xGhP//wRFv4RDOIYIB//ACQr1FHAIRJAA0TCAP/ZwIALgYRJVowRCj/4BIkBLIgABgRHC/KqFaI4RC5MkJBlPR4UECJizJJwoAKCKImVQAwAJv0HL5S6CbwIjLCKMAn4RDh0/LMKMhWaYAKA="))
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -0,0 +1,287 @@
|
|||
const handWidth = 6;
|
||||
const hourRadius = 4;
|
||||
const hourWidth = 8;
|
||||
const hourLength = 40;
|
||||
const hourSLength = 20;
|
||||
const radius = 220;
|
||||
const lineOffset = 115;
|
||||
const hourOffset = 32;
|
||||
const numberOffset = 85;
|
||||
const numberSize = 22;
|
||||
|
||||
const storage = require('Storage');
|
||||
|
||||
const SETTINGS_FILE = "line_clock.setting.json";
|
||||
|
||||
let initialSettings = {
|
||||
showLock: true,
|
||||
showMinute: true,
|
||||
};
|
||||
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || initialSettings;
|
||||
for (const key in saved_settings) {
|
||||
initialSettings[key] = saved_settings[key];
|
||||
}
|
||||
|
||||
let gWidth = g.getWidth(), gCenterX = gWidth/2;
|
||||
let gHeight = g.getHeight(), gCenterY = gHeight/2;
|
||||
|
||||
let currentTime = new Date();
|
||||
let currentHour = currentTime.getHours();
|
||||
let currentMinute = currentTime.getMinutes();
|
||||
|
||||
let drawTimeout;
|
||||
|
||||
function imgLock() {
|
||||
return {
|
||||
width : 16, height : 16, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the angle of the hour hand for the current time.
|
||||
*
|
||||
* @returns {number} The angle of the hour hand in degrees.
|
||||
*/
|
||||
function getHourHandAngle() {
|
||||
let hourHandAngle = 30 * currentHour;
|
||||
hourHandAngle += 0.5 * currentMinute;
|
||||
return hourHandAngle;
|
||||
}
|
||||
|
||||
let hourAngle = getHourHandAngle();
|
||||
|
||||
/**
|
||||
* Converts degrees to radians.
|
||||
*
|
||||
* @param {number} degrees - The degrees to be converted to radians.
|
||||
* @return {number} - The equivalent value in radians.
|
||||
*/
|
||||
function degreesToRadians(degrees) {
|
||||
return degrees * (Math.PI / 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates an array of points around a given angle and radius.
|
||||
*
|
||||
* @param {Array} points - The array of points to be rotated.
|
||||
* @param {number} angle - The angle in degrees to rotate the points.
|
||||
* @param {number} rad - The radius to offset the rotation.
|
||||
* @returns {Array} - The array of rotated points.
|
||||
*/
|
||||
function rotatePoints(points, angle, rad) {
|
||||
const ang = degreesToRadians(angle);
|
||||
const hAng = degreesToRadians(hourAngle);
|
||||
const rotatedPoints = [];
|
||||
points.map(function(point) {
|
||||
return {
|
||||
x: point.x * Math.cos(ang) - point.y * Math.sin(ang),
|
||||
y: point.x * Math.sin(ang) + point.y * Math.cos(ang)
|
||||
};
|
||||
}).forEach(function(point) {
|
||||
rotatedPoints.push(point.x + gCenterX - (rad * Math.sin(hAng)));
|
||||
rotatedPoints.push(point.y + gCenterY + (rad * Math.cos(hAng)));
|
||||
});
|
||||
return rotatedPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a hand on the canvas.
|
||||
*
|
||||
* @function drawHand
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function drawHand() {
|
||||
g.setColor(0xF800);
|
||||
const halfWidth = handWidth / 2;
|
||||
|
||||
const points = [{
|
||||
x: -halfWidth,
|
||||
y: -gHeight
|
||||
}, {
|
||||
x: halfWidth,
|
||||
y: -gHeight
|
||||
}, {
|
||||
x: halfWidth,
|
||||
y: gHeight
|
||||
}, {
|
||||
x: -halfWidth,
|
||||
y: gHeight
|
||||
}];
|
||||
|
||||
g.fillPolyAA(rotatePoints(points, hourAngle, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the hour coordinates for a given small flag.
|
||||
* @param {boolean} small - Determines if the flag is small.
|
||||
* @returns {Array} - An array of hour coordinates.
|
||||
*/
|
||||
function getHourCoordinates(small) {
|
||||
const dist = small ? (hourSLength - hourLength) : 0;
|
||||
const halfWidth = hourWidth / 2;
|
||||
const gh = gHeight + lineOffset;
|
||||
return [{
|
||||
x: -halfWidth,
|
||||
y: -gh - dist
|
||||
}, {
|
||||
x: halfWidth,
|
||||
y: -gh - dist
|
||||
}, {
|
||||
x: halfWidth,
|
||||
y: -gh + hourLength
|
||||
}, {
|
||||
x: -halfWidth,
|
||||
y: -gh + hourLength
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the given time to the hour dot on the clock face.
|
||||
*
|
||||
* @param {number} a - The time value to assign to the hour dot.
|
||||
* @return {void}
|
||||
*/
|
||||
function hourDot(a) {
|
||||
const h = gHeight + lineOffset;
|
||||
const rotatedPoints = rotatePoints(
|
||||
[{
|
||||
x: 0,
|
||||
y: -h + hourLength - (hourRadius / 2)
|
||||
}], a, radius
|
||||
);
|
||||
g.fillCircle(rotatedPoints[0], rotatedPoints[1], hourRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an hour into a number and display it on the clock face.
|
||||
*
|
||||
* @param {number} a - The hour to be converted (between 0 and 360 degrees).
|
||||
*/
|
||||
function hourNumber(a) {
|
||||
const h = gHeight + lineOffset;
|
||||
const rotatedPoints = rotatePoints(
|
||||
[{
|
||||
x: 0,
|
||||
y: -h + hourLength + hourOffset
|
||||
}], a, radius
|
||||
);
|
||||
g.drawString(String(a / 30), rotatedPoints[0], rotatedPoints[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a number on the display.
|
||||
*
|
||||
* @param {number} n - The number to be drawn.
|
||||
* @return {void}
|
||||
*/
|
||||
function drawNumber(n) {
|
||||
const h = gHeight + lineOffset;
|
||||
const halfWidth = handWidth / 2;
|
||||
const rotatedPoints = rotatePoints(
|
||||
[{
|
||||
x: 0,
|
||||
y: -h + hourLength + numberOffset
|
||||
}], hourAngle, radius
|
||||
);
|
||||
g.setColor(0xF800);
|
||||
g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize+ halfWidth);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize - halfWidth);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector:"+numberSize);
|
||||
g.drawString(String(n), rotatedPoints[0], rotatedPoints[1]);
|
||||
}
|
||||
|
||||
const hourPoints = getHourCoordinates(false);
|
||||
const hourSPoints = getHourCoordinates(true);
|
||||
|
||||
/**
|
||||
* Draws an hour on a clock face.
|
||||
*
|
||||
* @param {number} h - The hour to be drawn on the clock face.
|
||||
* @return {undefined}
|
||||
*/
|
||||
function drawHour(h) {
|
||||
if (h === 0) { h= 12; }
|
||||
if (h === 13) { h= 1; }
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector:32");
|
||||
const a = h * 30;
|
||||
g.fillPolyAA(rotatePoints(hourPoints, a, radius));
|
||||
g.fillPolyAA(rotatePoints(hourSPoints, a + 15, radius));
|
||||
hourNumber(a);
|
||||
hourDot(a + 5);
|
||||
hourDot(a + 10);
|
||||
hourDot(a + 20);
|
||||
hourDot(a + 25);
|
||||
}
|
||||
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function lockListenerBw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}
|
||||
Bangle.on('lock', lockListenerBw);
|
||||
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
// TODO implement https://www.espruino.com/Bangle.js+Fast+Load
|
||||
// remove : function() {
|
||||
// Bangle.removeListener('lock', lockListenerBw);
|
||||
// if (drawTimeout) clearTimeout(drawTimeout);
|
||||
// drawTimeout = undefined;
|
||||
// }
|
||||
});
|
||||
|
||||
/**
|
||||
* Draws a clock on the canvas using the current time.
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
function draw() {
|
||||
queueDraw();
|
||||
currentTime = new Date();
|
||||
currentHour = currentTime.getHours();
|
||||
if (currentHour > 12) {
|
||||
currentHour -= 12;
|
||||
}
|
||||
currentMinute = currentTime.getMinutes();
|
||||
|
||||
hourAngle = getHourHandAngle();
|
||||
|
||||
g.clear();
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0, 0, gWidth, gHeight);
|
||||
|
||||
if(initialSettings.showLock && Bangle.isLocked()){
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawImage(imgLock(), gWidth-16, 2);
|
||||
}
|
||||
|
||||
drawHour(currentHour);
|
||||
drawHour(currentHour-1);
|
||||
drawHour(currentHour+1);
|
||||
|
||||
|
||||
drawHand();
|
||||
|
||||
if(initialSettings.showMinute){
|
||||
drawNumber(currentMinute);
|
||||
}
|
||||
}
|
||||
|
||||
draw();
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{ "id": "line_clock",
|
||||
"name": "Line Clock",
|
||||
"shortName":"Line Clock",
|
||||
"version":"0.1",
|
||||
"description": "a readable analog clock",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"line_clock.app.js","url":"app.js"},
|
||||
{"name":"line_clock.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"line_clock.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data":[{"name":"line_clock.setting.json"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "line_clock.setting.json";
|
||||
|
||||
// initialize with default settings...
|
||||
const storage = require('Storage')
|
||||
let settings = {
|
||||
showLock: true,
|
||||
showMinute: true,
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
function save() {
|
||||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Line Clock' },
|
||||
'< Back': back,
|
||||
'Show Lock': {
|
||||
value: settings.showLock,
|
||||
onchange: () => {
|
||||
settings.showLock = !settings.showLock;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Show Minute': {
|
||||
value: settings.showMinute,
|
||||
onchange: () => {
|
||||
settings.showMinute = !settings.showMinute;
|
||||
save();
|
||||
},
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -32,7 +32,6 @@
|
|||
},
|
||||
'Show Lock': {
|
||||
value: settings.showLock,
|
||||
format: () => (settings.showLock ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.showLock = !settings.showLock;
|
||||
save();
|
||||
|
|
@ -40,7 +39,6 @@
|
|||
},
|
||||
'Hide Colon': {
|
||||
value: settings.hideColon,
|
||||
format: () => (settings.hideColon ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.hideColon = !settings.hideColon;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -67,4 +67,4 @@ exports.getColor = function(msg,options) {
|
|||
"youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors
|
||||
}[s]||options.default;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
'< Back': back,
|
||||
'Show Widgets': {
|
||||
value: settings.showWidgets,
|
||||
format: () => (settings.showWidgets ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.showWidgets = !settings.showWidgets;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -32,3 +32,6 @@
|
|||
0.25: Enable scaled image filtering on 2v19+ firmware
|
||||
0.26: Ensure that when redrawing, we always cancel any in-progress track draw
|
||||
0.27: Display message if no map is installed
|
||||
0.28: Fix rounding errors
|
||||
0.29: Keep exit at bottom of menu
|
||||
Speed up latLonToXY for track rendering
|
||||
|
|
@ -65,14 +65,16 @@ function redraw() {
|
|||
|
||||
// Draw the POIs
|
||||
function drawPOI() {
|
||||
let waypoints;
|
||||
try {
|
||||
var waypoints = require("waypoints").load();
|
||||
waypoints = require("waypoints").load();
|
||||
} catch (ex) {
|
||||
// Waypoints module not available.
|
||||
return;
|
||||
}
|
||||
g.setFont("Vector", 18);
|
||||
waypoints.forEach((wp, idx) => {
|
||||
if (wp.lat === undefined || wp.lon === undefined) return;
|
||||
var p = m.latLonToXY(wp.lat, wp.lon);
|
||||
var sz = 2;
|
||||
g.setColor(0,0,0);
|
||||
|
|
@ -80,7 +82,7 @@ function drawPOI() {
|
|||
g.setColor(0,0,0);
|
||||
g.drawString(wp.name, p.x, p.y);
|
||||
//print(wp.name);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function isInside(rect, e, w, h) {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,11 @@
|
|||
<div id="map">
|
||||
</div>
|
||||
<div id="controls">
|
||||
<div style="display:inline-block;text-align:center;vertical-align: top;" id="3bitdiv"> <input type="checkbox" id="3bit"></input><br/><span>3 bit</span></div>
|
||||
<div style="display:inline-block;text-align:left;vertical-align: top;" id="3bitdiv">
|
||||
<input type="checkbox" id="3bit"></input><span>3 bit</span>
|
||||
<br>
|
||||
<input type="checkbox" id="preview"><span>Preview</span>
|
||||
</div>
|
||||
<div class="form-group" style="display:inline-block;">
|
||||
<select class="form-select" id="mapSize">
|
||||
<option value="4">Small (4x4)</option>
|
||||
|
|
@ -143,11 +147,13 @@ TODO:
|
|||
let mapsLoadedContainer = document.getElementById("mapsLoadedContainer");
|
||||
mapsLoadedContainer.innerHTML = "";
|
||||
loadedMaps = [];
|
||||
const mapsLoaded = [];
|
||||
|
||||
Puck.write(`\x10Bluetooth.println(require("Storage").list(/openstmap\\.\\d+\\.json/))\n`,function(files) {
|
||||
Puck.eval(`require("Storage").list(/openstmap\\.\\d+\\.json/)\n`,function(files) {
|
||||
console.log("MAPS:",files);
|
||||
files.sort();
|
||||
let promise = Promise.resolve();
|
||||
files.trim().split(",").forEach(filename => {
|
||||
files.forEach(filename => {
|
||||
if (filename=="") return;
|
||||
promise = promise.then(() => new Promise(resolve => {
|
||||
Util.readStorageJSON(filename, mapInfo => {
|
||||
|
|
@ -155,7 +161,7 @@ TODO:
|
|||
let mapNumber = filename.match(/\d+/)[0]; // figure out what map number we are
|
||||
loadedMaps[mapNumber] = mapInfo;
|
||||
if (mapInfo!==undefined) {
|
||||
let latlon = L.latLng(mapInfo.lat, mapInfo.lon);
|
||||
mapsLoaded.push({mapNumber: mapNumber, mapInfo: mapInfo});
|
||||
mapsLoadedContainer.innerHTML += `
|
||||
<div class="tile">
|
||||
<div class="tile-icon">
|
||||
|
|
@ -171,19 +177,27 @@ TODO:
|
|||
</div>
|
||||
</div>
|
||||
`;
|
||||
setTimeout(function() {
|
||||
let map = L.map(`tile-map-${mapNumber}`);
|
||||
L.tileLayer(PREVIEWTILELAYER, {
|
||||
maxZoom: 18
|
||||
}).addTo(map);
|
||||
let marker = new L.marker(latlon).addTo(map);
|
||||
map.fitBounds(latlon.toBounds(2000/*meters*/), {animation: false});
|
||||
}, 500);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
});
|
||||
promise = promise.then(() => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
mapsLoaded.forEach(mapLoaded => {
|
||||
let latlon = L.latLng(mapLoaded.mapInfo.lat, mapLoaded.mapInfo.lon);
|
||||
let map = L.map(`tile-map-${mapLoaded.mapNumber}`);
|
||||
L.tileLayer(PREVIEWTILELAYER, {
|
||||
maxZoom: 18
|
||||
}).addTo(map);
|
||||
let marker = new L.marker(latlon).addTo(map);
|
||||
const dist = mapLoaded.mapInfo.scale * mapLoaded.mapInfo.tilesize * mapLoaded.mapInfo.w;
|
||||
map.fitBounds(latlon.toBounds(dist/2/*meters*/), {animation: false});
|
||||
});
|
||||
}, 0);
|
||||
|
||||
resolve();
|
||||
}));
|
||||
promise = promise.then(() => new Promise(resolve => {
|
||||
if (!loadedMaps.length) {
|
||||
mapsLoadedContainer.innerHTML += `
|
||||
|
|
@ -275,7 +289,9 @@ TODO:
|
|||
options.width = TILESIZE;
|
||||
options.height = TILESIZE;
|
||||
var imgstr = imageconverter.RGBAtoString(rgba, options);
|
||||
//ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); // write preview
|
||||
if (document.getElementById("preview").checked) {
|
||||
ctx.putImageData(imageData,x*TILESIZE, y*TILESIZE); // write preview
|
||||
}
|
||||
/*var compress = 'require("heatshrink").decompress('
|
||||
if (!imgstr.startsWith(compress)) throw "Data in wrong format";
|
||||
imgstr = imgstr.slice(compress.length,-1);*/
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "openstmap",
|
||||
"name": "OpenStreetMap",
|
||||
"shortName": "OpenStMap",
|
||||
"version": "0.27",
|
||||
"version": "0.29",
|
||||
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -38,17 +38,17 @@ if (m.map) {
|
|||
m.lat = m.map.lat; // position of middle of screen
|
||||
m.lon = m.map.lon; // position of middle of screen
|
||||
}
|
||||
var CX = g.getWidth()/2;
|
||||
var CY = g.getHeight()/2;
|
||||
|
||||
// return number of tiles drawn
|
||||
exports.draw = function() {
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var p = Bangle.project({lat:m.lat,lon:m.lon});
|
||||
let count = 0;
|
||||
m.maps.forEach((map,idx) => {
|
||||
var d = map.scale/m.scale;
|
||||
var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx;
|
||||
var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy;
|
||||
var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - CX;
|
||||
var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - CY;
|
||||
var o = {};
|
||||
var s = map.tilesize;
|
||||
if (d!=1) { // if the two are different, add scaling
|
||||
|
|
@ -76,7 +76,7 @@ exports.draw = function() {
|
|||
for (var x=ox,ttx=tx; x<mx && ttx<map.w; x+=s,ttx++) {
|
||||
for (var y=oy,tty=ty;y<my && tty<map.h;y+=s,tty++) {
|
||||
o.frame = ttx+(tty*map.w);
|
||||
g.drawImage(img,x,y,o);
|
||||
g.drawImage(img,Math.round(x),Math.round(y),o);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
|
@ -85,14 +85,12 @@ exports.draw = function() {
|
|||
};
|
||||
|
||||
/// Convert lat/lon to pixels on the screen
|
||||
exports.latLonToXY = function(lat, lon) {
|
||||
var p = Bangle.project({lat:m.lat,lon:m.lon});
|
||||
var q = Bangle.project({lat:lat, lon:lon});
|
||||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
exports.latLonToXY = function(lat, lon) { "ram"
|
||||
var p = Bangle.project({lat:m.lat,lon:m.lon}),
|
||||
q = Bangle.project({lat:lat, lon:lon});
|
||||
return {
|
||||
x : (q.x-p.x)/m.scale + cx,
|
||||
y : cy - (q.y-p.y)/m.scale
|
||||
x : Math.round((q.x-p.x)/m.scale + CX),
|
||||
y : Math.round(CY - (q.y-p.y)/m.scale)
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -102,4 +100,4 @@ exports.scroll = function(x,y) {
|
|||
var b = Bangle.project({lat:m.lat+1,lon:m.lon+1});
|
||||
this.lon += x * m.scale / (a.x-b.x);
|
||||
this.lat -= y * m.scale / (a.y-b.y);
|
||||
};
|
||||
};
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
|
||||
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
|
||||
var theme_options = ['System', 'Light', 'Dark'];
|
||||
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Pebble Clock' },
|
||||
/*LANG*/'< Back': back,
|
||||
|
|
@ -48,7 +48,6 @@
|
|||
},
|
||||
/*LANG*/'Show Lock': {
|
||||
value: settings.showlock,
|
||||
format: () => (settings.showlock ? /*LANG*/'Yes' : /*LANG*/'No'),
|
||||
onchange: () => {
|
||||
settings.showlock = !settings.showlock;
|
||||
save();
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
Support for fast loading
|
||||
0.04: Localisation request: added Miles and AM/PM
|
||||
0.05: Prevent exceptions from halting the draw cycle
|
||||
0.06: Fix Settings page to ensure that the currently set distance is displayed (not 0.75)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "pebbled",
|
||||
"name": "Pebble Clock with distance",
|
||||
"shortName": "Pebble + distance",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).",
|
||||
"readme": "README.md",
|
||||
"icon": "pebbled.png",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
},
|
||||
'Step length': {
|
||||
value: 0.75 || s.avStep,
|
||||
value: s.avStep || 0.75,
|
||||
min: 0.2,
|
||||
max: 1.5,
|
||||
step: 0.01,
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Major speed improvement. Added more stars. Up to 500!
|
||||
0.03: Added more stars and constellations. Now it shows 20 constellations.
|
||||
0.04: Use default Bangle formatter for booleans
|
||||
0.05: Added more constellations (scorpio and aguila)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "planetarium",
|
||||
"name": "Planetarium",
|
||||
"shortName": "Planetarium",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Planetarium showing up to 500 stars using the watch location and time",
|
||||
"icon": "planetarium.png",
|
||||
"tags": "",
|
||||
|
|
|
|||
|
|
@ -38,3 +38,7 @@ Draco
|
|||
e_15 131,131 70,70 382,382 e_15,382 187,187 423,423 e_16,e_16 207,207 122,122 e_17,e_17 232,232 342,342 452,452 428
|
||||
Pegasus
|
||||
92 85,138 54,54 85,138 92,283 85,283 389,160 85,92 258,258 297,297 83
|
||||
Aguila
|
||||
12 249,249 271,249 170,249 217,12 365,120 12
|
||||
Scorpius
|
||||
14 105,14 80,14 152,14 137,137 76,332 239,239 41,76 188,188 332,41 181,181 27
|
||||
|
|
|
|||
|
|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("MDAEEREREREREURERERERERERBERERERERERERERERERREREREREREREREEREREREREREREREREURERERERERERERERBERERERERERERERFEREREREREREREREREERERERERERERERREREREREREREREREREQREREREREREREURBEhEURERERERERERERBEREREREREREURBESERIkRERERERERERBERERERERERFEQhERIhEiREREREREREREERERERERERFERCERIiIiJEREREREREREQRERERERERREREERASIiEkREREREREREQRERERERERREREQQERIiEkRERERERERERBEREREREUREREQRERAiIiEkRERERERERBEREREREURERERAERERISEkRERERERERBEREREREUREREREEQAREiJEREREREREREERERERFEREREREIiIRESIUREREREREREERERERFEREREREERABERIRREREREREREERERERFEREREREQREREREhREREREREREERERERFEREREREIiJBERESFEREREREREQRERERFEREREREIiQRERERIUREREREREQRERERRERERERCIiIhEQEREhREREREREQRERERRERERERCIiIiIRERESFEREREREQRERERREREREQiIiIiIiQRESJEREREREQRERERREREREQiIiIiIiIkFBJEREREREQRERERREREREQiIiIiIiJBESEkREREREQRERERREREREQiIiIiIiIiRCIiREREREQRERERREREREQiIiIiIiIiIkQiREREREQRERERREREREQiIiIiIiIiIiIiJEREREQRERERREREREQiIiIiIyIiIiIiIkREREQRERERREREREQiIiIkQjMiIiIkIkREREQRERERREREREREREREQiIiIiJEQiIiMkQRERERREQiIkREQRESIiIiIiJCQiMzIkQRERERREIiIgAAABEBERRCMzMzIiIhARERERERFEIiIiIiIiIzMzMzMzMhAAJAEQVRERERQjMzMzMzMzMzMiIhEAAAAAEiBURBERFCIzMzIiIhERAAAAARERERERESFERBEREUERAAAAAAAAARERERERERFREUJEQhERERERFEREREREREREREREREREREJEQkEREREUREREREREREREREREREREREEkREERERFEREREREREREREREQiJEREREQkRBERERFEJERCIiRCIiIiJEIiIkREREQSERARERESREIiIiIiRERERERERERCREVSIAEREREUREREREREREREREREREREREVREBEREREURERERBFERERERERERERERVEAAREREREQAAABEREREREREUREREREEAAAAAABEREREREREREREREREAAAAAAAAAAAABERERERERERERRERERERBEREREREREREREREREREREREUREREREREREQRERERERERERERERERERERRERERERERBERERERERERER"))
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "quoteclock",
|
||||
"name": "Quote Clock",
|
||||
"version": "0.01",
|
||||
"description": "A clock showing quotes every hour",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock,shakespeare",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"quoteclock.app.js","url":"app.js"},
|
||||
{"name":"quoteclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -14,3 +14,4 @@
|
|||
0.14: cleanup code and fix fastload issue
|
||||
0.15: fix draw before widget hide
|
||||
0.16: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
0.17: Add fullscreen option (on by default) to show widgets, adjust sidebar 1 and 2 when fullscreen is off
|
||||
|
|
|
|||
|
|
@ -10,14 +10,22 @@
|
|||
* Tap top or bottom right to instantly cycle to the next sidebar
|
||||
* Uses pedometer widget to get latest step count
|
||||
* Dependant apps are installed when Rebble installs
|
||||
* Uses the whole screen, widgets are made invisible but still run in the background
|
||||
* When in fullscreen widgets are made invisible but still run in the background
|
||||
* The icon is James Dean - 'Rebel Without a Cause'
|
||||
|
||||
## Fullscreen
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## With widgets
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Future Enhancements
|
||||
|
||||
* Support for Weather Icons in the Steps Sidebar
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
"shortName": "Rebble",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||
"readme": "README.md",
|
||||
"icon": "rebble.png",
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"screenshots": [{"url":"screenshot_rebble.png"}],
|
||||
"screenshots": [{"url":"screenshot_rebble.png"}, {"url":"screenshot_rebble2.png"}, {"url":"screenshot_rebble3.png"}, {"url":"screenshot_rebble4.png"}, {"url":"screenshot_rebble_w1.png"}, {"url":"screenshot_rebble_w2.png"}, {"url":"screenshot_rebble_w3.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
}
|
||||
|
||||
let loadSettings=function() {
|
||||
settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true,'sideTap':0};
|
||||
settings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'fullScreen': true, 'sideTap':0};
|
||||
//sideTap 0 = on | 1 = sidebar1...
|
||||
|
||||
let tmp = require('Storage').readJSON(SETTINGS_FILE, 1) || settings;
|
||||
|
|
@ -118,32 +118,60 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
|
||||
if (drawCount % 60 == 0)
|
||||
updateSunRiseSunSet(location.lat, location.lon);
|
||||
|
||||
|
||||
g.reset();
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0, 0, w2, h);
|
||||
g.setColor(settings.bg);
|
||||
g.fillRect(w2, 0, w, h);
|
||||
|
||||
if (settings.fullScreen) {
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0, 0, w2, h);
|
||||
g.setColor(settings.bg);
|
||||
g.fillRect(w2, 0, w, h);
|
||||
|
||||
// time
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontKdamThmor();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(hh, w2/2, 10 + 0);
|
||||
g.drawString(mm, w2/2, 10 + h/2);
|
||||
// time
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontKdamThmor();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(hh, w2/2, 10 + 0);
|
||||
g.drawString(mm, w2/2, 10 + h/2);
|
||||
|
||||
switch(sideBar) {
|
||||
case 0:
|
||||
drawSideBar1();
|
||||
break;
|
||||
case 1:
|
||||
drawSideBar2();
|
||||
break;
|
||||
case 2:
|
||||
drawSideBar3();
|
||||
break;
|
||||
switch(sideBar) {
|
||||
case 0:
|
||||
drawSideBar1();
|
||||
break;
|
||||
case 1:
|
||||
drawSideBar2();
|
||||
break;
|
||||
case 2:
|
||||
drawSideBar3();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0, 24, 113, 176);
|
||||
g.setColor(settings.bg);
|
||||
g.fillRect(113, 24, 176, 176);
|
||||
|
||||
// time
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFontKdamThmor();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(hh, 57, 24);
|
||||
g.drawString(mm, 57, 100);
|
||||
|
||||
switch(sideBar) {
|
||||
case 0:
|
||||
drawSideBar1Alt();
|
||||
break;
|
||||
case 1:
|
||||
drawSideBar2Alt();
|
||||
break;
|
||||
case 2:
|
||||
drawSideBar3();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
drawCount++;
|
||||
queueDraw();
|
||||
}
|
||||
|
|
@ -164,6 +192,16 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
|
||||
drawDateAndCalendar(w3, h/2, dy, dd, mm);
|
||||
}
|
||||
|
||||
let drawSideBar1Alt=function() {
|
||||
let date = new Date();
|
||||
let dy= require("date_utils").dow(date.getDay(),1).toUpperCase();
|
||||
let dd= date.getDate();
|
||||
let mm= require("date_utils").month(date.getMonth()+1,1).toUpperCase();
|
||||
let yy = date.getFullYear();
|
||||
|
||||
drawDateAndCalendarAlt(145, 46, dy, dd, mm, yy);
|
||||
}
|
||||
|
||||
let drawSideBar2=function() {
|
||||
drawBattery(w2 + (w-w2-wb)/2, h/10, wb, 17);
|
||||
|
|
@ -178,6 +216,14 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
setSmallFont();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(formatSteps(), w3, 7*h/8);
|
||||
}
|
||||
|
||||
let drawSideBar2Alt=function() {
|
||||
// steps
|
||||
g.drawImage(boot_img, 113, 59, { scale: 1 });
|
||||
setSmallFont();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(formatSteps(), 145, 122);
|
||||
}
|
||||
|
||||
// sunrise, sunset times
|
||||
|
|
@ -212,6 +258,28 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
g.setFontAlign(0, -1);
|
||||
g.drawString(mm.toUpperCase(), x, y + 70);
|
||||
}
|
||||
|
||||
let drawDateAndCalendarAlt=function(x, y, dy, dd, mm, yy) {
|
||||
// day
|
||||
setTextColor();
|
||||
setSmallFont();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(dy.toUpperCase(), x, y);
|
||||
|
||||
drawCalendar(x - 18, y + 28, 35, 3, dd);
|
||||
|
||||
// month
|
||||
setTextColor();
|
||||
setSmallFont();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(mm.toUpperCase(), x, y + 70);
|
||||
|
||||
// year
|
||||
setTextColor();
|
||||
setSmallFont();
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(yy, x, y + 94);
|
||||
}
|
||||
|
||||
// at x,y width:wi thicknes:th
|
||||
let drawCalendar=function(x,y,wi,th,str) {
|
||||
|
|
@ -311,7 +379,10 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
delete Graphics.prototype.setFontKdamThmor;
|
||||
Bangle.removeListener('charging',chargingListener);
|
||||
|
||||
if (settings.fullScreen) {
|
||||
Bangle.removeListener('charging',chargingListener);
|
||||
}
|
||||
}
|
||||
|
||||
let main=function(){
|
||||
|
|
@ -341,17 +412,17 @@ Graphics.prototype.setFontKdamThmor = function(scale) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Bangle.on('charging',chargingListener);
|
||||
|
||||
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").hide();
|
||||
|
||||
if (settings.fullScreen) {
|
||||
Bangle.on('charging',chargingListener);
|
||||
require("widget_utils").hide();
|
||||
} else {
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
}
|
||||
|
||||
|
||||
main();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
const SETTINGS_FILE = "rebble.json";
|
||||
|
||||
// initialize with default settings...
|
||||
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'sideTap':0};
|
||||
let localSettings = {'bg': '#0f0', 'color': 'Green', 'autoCycle': true, 'fullScreen': true, 'sideTap':0};
|
||||
//sideTap 0 = on| 1= sideBar1 | 2 = ...
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
|
|
@ -37,6 +37,14 @@
|
|||
localSettings.bg = bg_code[v];
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Fullscreen': {
|
||||
value: localSettings.fullScreen,
|
||||
onchange: (v) => {
|
||||
localSettings.fullScreen = v;
|
||||
save();
|
||||
showMenu();
|
||||
}
|
||||
},
|
||||
'Auto Cycle': {
|
||||
value: localSettings.autoCycle,
|
||||
|
|
@ -74,4 +82,4 @@
|
|||
}
|
||||
|
||||
showMenu();
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
|
@ -45,3 +45,4 @@
|
|||
0.36: When recording with 1 second periods, log time with one decimal.
|
||||
0.37: 1 second periods + gps log => log when gps event is received, not with
|
||||
setInterval.
|
||||
0.38: Tweaks to speed up track rendering
|
||||
|
|
@ -213,230 +213,230 @@ function viewTrack(filename, info) {
|
|||
});
|
||||
};
|
||||
menu['< Back'] = () => { viewTracks(); };
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function plotTrack(info) { "ram"
|
||||
function distance(lat1,long1,lat2,long2) { "ram"
|
||||
var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360);
|
||||
var y = lat2 - lat1;
|
||||
return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180;
|
||||
}
|
||||
function plotTrack(info) { "ram"
|
||||
function distance(lat1,long1,lat2,long2) { "ram"
|
||||
var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360);
|
||||
var y = lat2 - lat1;
|
||||
return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180;
|
||||
}
|
||||
|
||||
// Function to convert lat/lon to XY
|
||||
var getMapXY;
|
||||
if (info.qOSTM) {
|
||||
// scale map to view full track
|
||||
const max = Bangle.project({lat: info.maxLat, lon: info.maxLong});
|
||||
const min = Bangle.project({lat: info.minLat, lon: info.minLong});
|
||||
const scaleX = (max.x-min.x)/Bangle.appRect.w;
|
||||
const scaleY = (max.y-min.y)/Bangle.appRect.h;
|
||||
osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin
|
||||
getMapXY = osm.latLonToXY.bind(osm);
|
||||
} else {
|
||||
getMapXY = function(lat, lon) { "ram"
|
||||
return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale),
|
||||
y:cy + Math.round((info.lat - lat)*info.scale)};
|
||||
};
|
||||
}
|
||||
// Function to convert lat/lon to XY
|
||||
var XY;
|
||||
if (info.qOSTM) {
|
||||
// scale map to view full track
|
||||
const max = Bangle.project({lat: info.maxLat, lon: info.maxLong});
|
||||
const min = Bangle.project({lat: info.minLat, lon: info.minLong});
|
||||
const scaleX = (max.x-min.x)/Bangle.appRect.w;
|
||||
const scaleY = (max.y-min.y)/Bangle.appRect.h;
|
||||
osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin
|
||||
XY = osm.latLonToXY.bind(osm);
|
||||
} else {
|
||||
XY = function(lat, lon) { "ram"
|
||||
return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale),
|
||||
y:cy + Math.round((info.lat - lat)*info.scale)};
|
||||
};
|
||||
}
|
||||
|
||||
E.showMenu(); // remove menu
|
||||
E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn);
|
||||
g.flip(); // on buffered screens, draw a not saying we're busy
|
||||
g.clear(1);
|
||||
var s = require("Storage");
|
||||
var W = g.getWidth();
|
||||
var H = g.getHeight();
|
||||
var cx = W/2;
|
||||
var cy = 24 + (H-24)/2;
|
||||
if (!info.qOSTM) {
|
||||
g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]);
|
||||
g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50);
|
||||
} else {
|
||||
osm.lat = info.lat;
|
||||
osm.lon = info.lon;
|
||||
osm.draw();
|
||||
g.setColor("#000");
|
||||
E.showMenu(); // remove menu
|
||||
E.showMessage(/*LANG*/"Drawing...",/*LANG*/"Track "+info.fn);
|
||||
g.flip(); // on buffered screens, draw a not saying we're busy
|
||||
g.clear(1);
|
||||
var s = require("Storage");
|
||||
var G = g;
|
||||
var W = g.getWidth();
|
||||
var H = g.getHeight();
|
||||
var cx = W/2;
|
||||
var cy = 24 + (H-24)/2;
|
||||
if (!info.qOSTM) {
|
||||
g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]);
|
||||
g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50);
|
||||
} else {
|
||||
osm.lat = info.lat;
|
||||
osm.lon = info.lon;
|
||||
osm.draw();
|
||||
g.setColor("#000");
|
||||
}
|
||||
var latIdx = info.fields.indexOf("Latitude");
|
||||
var lonIdx = info.fields.indexOf("Longitude");
|
||||
g.drawString(asTime(info.duration),10,220);
|
||||
var f = require("Storage").open(info.filename,"r");
|
||||
if (f===undefined) return;
|
||||
var l = f.readLine(f);
|
||||
l = f.readLine(f); // skip headers
|
||||
var ox=0;
|
||||
var oy=0;
|
||||
var olat,olong,dist=0;
|
||||
var i=0, c = l.split(",");
|
||||
// skip until we find our first data
|
||||
while(l!==undefined && c[latIdx]=="") {
|
||||
c = l.split(",");
|
||||
l = f.readLine(f);
|
||||
}
|
||||
// now start plotting
|
||||
var lat = +c[latIdx];
|
||||
var long = +c[lonIdx];
|
||||
var mp = XY(lat, long);
|
||||
g.moveTo(mp.x,mp.y);
|
||||
g.setColor("#0f0");
|
||||
g.fillCircle(mp.x,mp.y,5);
|
||||
if (info.qOSTM) g.setColor("#f09");
|
||||
else g.setColor(g.theme.fg);
|
||||
l = f.readLine(f);
|
||||
g.flip(); // force update
|
||||
while(l!==undefined) {
|
||||
c = l.split(",");l = f.readLine(f);
|
||||
if (c[latIdx]=="")continue;
|
||||
lat = +c[latIdx];
|
||||
long = +c[lonIdx];
|
||||
mp = XY(lat, long);
|
||||
G.lineTo(mp.x,mp.y);
|
||||
if (info.qOSTM) G.fillCircle(mp.x,mp.y,2); // make the track more visible
|
||||
var d = distance(olat,olong,lat,long);
|
||||
if (!isNaN(d)) dist+=d;
|
||||
olat = lat;
|
||||
olong = long;
|
||||
ox = mp.x;
|
||||
oy = mp.y;
|
||||
if (++i > 100) { G.flip();i=0; }
|
||||
}
|
||||
g.setColor("#f00");
|
||||
g.fillCircle(ox,oy,5);
|
||||
if (info.qOSTM) g.setColor("#000");
|
||||
else g.setColor(g.theme.fg);
|
||||
g.drawString(require("locale").distance(dist,2),g.getWidth() / 2, g.getHeight() - 20);
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0,3);
|
||||
var isBTN3 = "BTN3" in global;
|
||||
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
|
||||
setWatch(function() {
|
||||
viewTrack(info.fn, info);
|
||||
}, isBTN3?BTN3:BTN1);
|
||||
Bangle.drawWidgets();
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function plotGraph(info, style) { "ram"
|
||||
E.showMenu(); // remove menu
|
||||
E.showMessage(/*LANG*/"Calculating...",/*LANG*/"Track "+info.fn);
|
||||
var filename = info.filename;
|
||||
var infn = new Float32Array(80);
|
||||
var infc = new Uint16Array(80);
|
||||
var title;
|
||||
var lt = 0; // last time
|
||||
var tn = 0; // count for each time period
|
||||
var strt, dur = info.duration;
|
||||
var f = require("Storage").open(filename,"r");
|
||||
if (f===undefined) return;
|
||||
var l = f.readLine(f);
|
||||
l = f.readLine(f); // skip headers
|
||||
var nl = 0, c, i;
|
||||
var factor = 1; // multiplier used for values when graphing
|
||||
var timeIdx = info.fields.indexOf("Time");
|
||||
if (l!==undefined) {
|
||||
c = l.split(",");
|
||||
strt = c[timeIdx];
|
||||
}
|
||||
if (style=="Heartrate") {
|
||||
title = /*LANG*/"Heartrate (bpm)";
|
||||
var hrmIdx = info.fields.indexOf("Heartrate");
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");l = f.readLine(f);
|
||||
if (c[hrmIdx]=="") continue;
|
||||
i = Math.round(80*(c[timeIdx] - strt)/dur);
|
||||
infn[i]+=+c[hrmIdx];
|
||||
infc[i]++;
|
||||
}
|
||||
} else if (style=="Altitude") {
|
||||
title = /*LANG*/"Altitude (m)";
|
||||
var altIdx = info.fields.indexOf("Barometer Altitude");
|
||||
if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");l = f.readLine(f);
|
||||
if (c[altIdx]=="") continue;
|
||||
i = Math.round(80*(c[timeIdx] - strt)/dur);
|
||||
infn[i]+=+c[altIdx];
|
||||
infc[i]++;
|
||||
}
|
||||
} else if (style=="Speed") {
|
||||
// use locate to work out units
|
||||
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
|
||||
let units = localeStr.replace(/[0-9.]*/,"");
|
||||
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
|
||||
// title
|
||||
title = /*LANG*/"Speed"+` (${units})`;
|
||||
var latIdx = info.fields.indexOf("Latitude");
|
||||
var lonIdx = info.fields.indexOf("Longitude");
|
||||
g.drawString(asTime(info.duration),10,220);
|
||||
var f = require("Storage").open(info.filename,"r");
|
||||
if (f===undefined) return;
|
||||
var l = f.readLine(f);
|
||||
l = f.readLine(f); // skip headers
|
||||
var ox=0;
|
||||
var oy=0;
|
||||
var olat,olong,dist=0;
|
||||
var i=0, c = l.split(",");
|
||||
// skip until we find our first data
|
||||
while(l!==undefined && c[latIdx]=="") {
|
||||
c = l.split(",");
|
||||
l = f.readLine(f);
|
||||
}
|
||||
// now start plotting
|
||||
var lat = +c[latIdx];
|
||||
var long = +c[lonIdx];
|
||||
var mp = getMapXY(lat, long);
|
||||
g.moveTo(mp.x,mp.y);
|
||||
g.setColor("#0f0");
|
||||
g.fillCircle(mp.x,mp.y,5);
|
||||
if (info.qOSTM) g.setColor("#f09");
|
||||
else g.setColor(g.theme.fg);
|
||||
l = f.readLine(f);
|
||||
g.flip(); // force update
|
||||
// now iterate
|
||||
var p,lp = Bangle.project({lat:c[1],lon:c[2]});
|
||||
var t,dx,dy,d,lt = c[timeIdx];
|
||||
while(l!==undefined) {
|
||||
c = l.split(",");l = f.readLine(f);
|
||||
if (c[latIdx]=="")continue;
|
||||
lat = +c[latIdx];
|
||||
long = +c[lonIdx];
|
||||
mp = getMapXY(lat, long);
|
||||
g.lineTo(mp.x,mp.y);
|
||||
if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible
|
||||
var d = distance(olat,olong,lat,long);
|
||||
if (!isNaN(d)) dist+=d;
|
||||
olat = lat;
|
||||
olong = long;
|
||||
ox = mp.x;
|
||||
oy = mp.y;
|
||||
if (++i > 100) { g.flip();i=0; }
|
||||
}
|
||||
g.setColor("#f00");
|
||||
g.fillCircle(ox,oy,5);
|
||||
if (info.qOSTM) g.setColor("#000");
|
||||
else g.setColor(g.theme.fg);
|
||||
g.drawString(require("locale").distance(dist,2),g.getWidth() / 2, g.getHeight() - 20);
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0,3);
|
||||
var isBTN3 = "BTN3" in global;
|
||||
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
|
||||
setWatch(function() {
|
||||
viewTrack(info.fn, info);
|
||||
}, isBTN3?BTN3:BTN1);
|
||||
Bangle.drawWidgets();
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function plotGraph(info, style) { "ram"
|
||||
E.showMenu(); // remove menu
|
||||
E.showMessage(/*LANG*/"Calculating...",/*LANG*/"Track "+info.fn);
|
||||
var filename = info.filename;
|
||||
var infn = new Float32Array(80);
|
||||
var infc = new Uint16Array(80);
|
||||
var title;
|
||||
var lt = 0; // last time
|
||||
var tn = 0; // count for each time period
|
||||
var strt, dur = info.duration;
|
||||
var f = require("Storage").open(filename,"r");
|
||||
if (f===undefined) return;
|
||||
var l = f.readLine(f);
|
||||
l = f.readLine(f); // skip headers
|
||||
var nl = 0, c, i;
|
||||
var factor = 1; // multiplier used for values when graphing
|
||||
var timeIdx = info.fields.indexOf("Time");
|
||||
if (l!==undefined) {
|
||||
c = l.split(",");
|
||||
strt = c[timeIdx];
|
||||
}
|
||||
if (style=="Heartrate") {
|
||||
title = /*LANG*/"Heartrate (bpm)";
|
||||
var hrmIdx = info.fields.indexOf("Heartrate");
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");l = f.readLine(f);
|
||||
if (c[hrmIdx]=="") continue;
|
||||
i = Math.round(80*(c[timeIdx] - strt)/dur);
|
||||
infn[i]+=+c[hrmIdx];
|
||||
++nl;c=l.split(",");
|
||||
l = f.readLine(f);
|
||||
if (c[latIdx] == "") {
|
||||
continue;
|
||||
}
|
||||
t = c[timeIdx];
|
||||
i = Math.round(80*(t - strt)/dur);
|
||||
p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
|
||||
dx = p.x-lp.x;
|
||||
dy = p.y-lp.y;
|
||||
d = Math.sqrt(dx*dx+dy*dy);
|
||||
if (t!=lt) {
|
||||
infn[i]+=d / (t-lt); // speed
|
||||
infc[i]++;
|
||||
}
|
||||
} else if (style=="Altitude") {
|
||||
title = /*LANG*/"Altitude (m)";
|
||||
var altIdx = info.fields.indexOf("Barometer Altitude");
|
||||
if (altIdx<0) altIdx = info.fields.indexOf("Altitude");
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");l = f.readLine(f);
|
||||
if (c[altIdx]=="") continue;
|
||||
i = Math.round(80*(c[timeIdx] - strt)/dur);
|
||||
infn[i]+=+c[altIdx];
|
||||
infc[i]++;
|
||||
}
|
||||
} else if (style=="Speed") {
|
||||
// use locate to work out units
|
||||
var localeStr = require("locale").speed(1,5); // get what 1kph equates to
|
||||
let units = localeStr.replace(/[0-9.]*/,"");
|
||||
factor = parseFloat(localeStr)*3.6; // m/sec to whatever out units are
|
||||
// title
|
||||
title = /*LANG*/"Speed"+` (${units})`;
|
||||
var latIdx = info.fields.indexOf("Latitude");
|
||||
var lonIdx = info.fields.indexOf("Longitude");
|
||||
// skip until we find our first data
|
||||
while(l!==undefined && c[latIdx]=="") {
|
||||
c = l.split(",");
|
||||
l = f.readLine(f);
|
||||
}
|
||||
// now iterate
|
||||
var p,lp = Bangle.project({lat:c[1],lon:c[2]});
|
||||
var t,dx,dy,d,lt = c[timeIdx];
|
||||
while(l!==undefined) {
|
||||
++nl;c=l.split(",");
|
||||
l = f.readLine(f);
|
||||
if (c[latIdx] == "") {
|
||||
continue;
|
||||
}
|
||||
t = c[timeIdx];
|
||||
i = Math.round(80*(t - strt)/dur);
|
||||
p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]});
|
||||
dx = p.x-lp.x;
|
||||
dy = p.y-lp.y;
|
||||
d = Math.sqrt(dx*dx+dy*dy);
|
||||
if (t!=lt) {
|
||||
infn[i]+=d / (t-lt); // speed
|
||||
infc[i]++;
|
||||
}
|
||||
lp = p;
|
||||
lt = t;
|
||||
}
|
||||
} else throw new Error("Unknown type "+style);
|
||||
var min=100000,max=-100000;
|
||||
for (var i=0;i<infn.length;i++) {
|
||||
if (infc[i]>0) infn[i]=factor*infn[i]/infc[i];
|
||||
else { // no data - search back and see if we can find something
|
||||
for (var j=i-1;j>=0;j--)
|
||||
if (infc[j]) { infn[i]=infn[j]; break; }
|
||||
}
|
||||
var n = infn[i];
|
||||
if (n>max) max=n;
|
||||
if (n<min) min=n;
|
||||
lp = p;
|
||||
lt = t;
|
||||
}
|
||||
// work out a nice grid value
|
||||
var heightDiff = max-min;
|
||||
var grid = 1;
|
||||
while (heightDiff/grid > 8) {
|
||||
grid*=2;
|
||||
} else throw new Error("Unknown type "+style);
|
||||
var min=100000,max=-100000;
|
||||
for (var i=0;i<infn.length;i++) {
|
||||
if (infc[i]>0) infn[i]=factor*infn[i]/infc[i];
|
||||
else { // no data - search back and see if we can find something
|
||||
for (var j=i-1;j>=0;j--)
|
||||
if (infc[j]) { infn[i]=infn[j]; break; }
|
||||
}
|
||||
// draw
|
||||
g.clear(1).setFont("6x8",1);
|
||||
var r = require("graph").drawLine(g, infn, {
|
||||
x:4,y:24,
|
||||
width: g.getWidth()-24,
|
||||
height: g.getHeight()-(24+8),
|
||||
axes : true,
|
||||
gridy : grid,
|
||||
gridx : infn.length / 3,
|
||||
title: title,
|
||||
miny: min,
|
||||
maxy: max,
|
||||
xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
|
||||
});
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0,3);
|
||||
var isBTN3 = "BTN3" in global;
|
||||
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
|
||||
setWatch(function() {
|
||||
viewTrack(info.filename, info);
|
||||
}, isBTN3?BTN3:BTN1);
|
||||
g.flip();
|
||||
var n = infn[i];
|
||||
if (n>max) max=n;
|
||||
if (n<min) min=n;
|
||||
}
|
||||
|
||||
return E.showMenu(menu);
|
||||
// work out a nice grid value
|
||||
var heightDiff = max-min;
|
||||
var grid = 1;
|
||||
while (heightDiff/grid > 8) {
|
||||
grid*=2;
|
||||
}
|
||||
// draw
|
||||
g.clear(1).setFont("6x8",1);
|
||||
var r = require("graph").drawLine(g, infn, {
|
||||
x:4,y:24,
|
||||
width: g.getWidth()-24,
|
||||
height: g.getHeight()-(24+8),
|
||||
axes : true,
|
||||
gridy : grid,
|
||||
gridx : infn.length / 3,
|
||||
title: title,
|
||||
miny: min,
|
||||
maxy: max,
|
||||
xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
|
||||
});
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(0,0,3);
|
||||
var isBTN3 = "BTN3" in global;
|
||||
g.drawString(/*LANG*/"Back",g.getWidth() - 10, isBTN3 ? (g.getHeight() - 40) : (g.getHeight()/2));
|
||||
setWatch(function() {
|
||||
viewTrack(info.filename, info);
|
||||
}, isBTN3?BTN3:BTN1);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
showMainMenu();
|
||||
showMainMenu();
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.37",
|
||||
"version": "0.38",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget,clkinfo",
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AF8OoMRC6nu8kRiAuT93uGCgWBGCguCGCgsBoMUGCQuBFYMRDAIwQFQUhDAIFCFx/hiUyC4NOGAKMP8MRmYwBjwwBCxkEFAIXBiYwBC4PuC5hxCC4IwCDwPgC5gpBpxxBiMSL4QWMgQpBIIKnEFxsikcxOISODCxkDmUiLIQADFxsjUIQWELp0iLwYuQgMzkUiFydBkcyFycOoMSXoIuST4YuTB4NBZwIuSABAuPAA5dQdSQuBoIXBLwouPiUxGAguOC4imDRh3hC4wuMgBABC44WMgBxBI4wuNgBxCC4MhAoQWNC4IwBU4guOgEBFQVBiguQGAi7PGBCMPGBAuRGAoWSGAYuTAH4AcA="))
|
||||
|
||||
require("heatshrink").decompress(atob("mEw4cA///ov+5lChWMyGuxdzpdj4/lKf4AUkgQPgm0wAiPy2QCBsBkmS6QRNhIRBrVACJlPu2+pdICBcCrVJlvJtIRLifStMl3MtkARKydUyMkzMl0CMKyWWyUk1MkSJXkyR7BogRLgVcydSrVGzLHKgdLyfSpdE3JYKklqTwNJknJYJVkxcSp+pnygKhMs1OSEQOSYhVJl1bCIbBK5Mq7gRCyARJiVbqyPBCIKMKuVM24yBCIIiJnVOqu5CISMKp9JlvJCIRXKpP3nxoCRhUSBwSMNBwaMMgn6yp6DRhUl0mypiMMgM9ksipaMMhMtCINKRhlJmoRBpJuBCBIRGRhUE5I1CpKMLgmZn5ZDGhUAycnRoNMRhTDCsn3tfkRhLnDTwYQLNgSMMUQkyRhbGEkyMKAApFOAH4AGA"))
|
||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 12 KiB |
|
|
@ -21,42 +21,34 @@
|
|||
},
|
||||
/*LANG*/'Red': {
|
||||
value: !!settings.colorRed,
|
||||
format: () => (settings.colorRed ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorRed', x),
|
||||
},
|
||||
/*LANG*/'Green': {
|
||||
value: !!settings.colorGreen,
|
||||
format: () => (settings.colorGreen ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorGreen', x),
|
||||
},
|
||||
/*LANG*/'Blue': {
|
||||
value: !!settings.colorBlue,
|
||||
format: () => (settings.colorBlue ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorBlue', x),
|
||||
},
|
||||
/*LANG*/'Magenta': {
|
||||
value: !!settings.colorMagenta,
|
||||
format: () => (settings.colorMagenta ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorMagenta', x),
|
||||
},
|
||||
/*LANG*/'Cyan': {
|
||||
value: !!settings.colorCyan,
|
||||
format: () => (settings.colorCyan ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorCyan', x),
|
||||
},
|
||||
/*LANG*/'Yellow': {
|
||||
value: !!settings.colorYellow,
|
||||
format: () => (settings.colorYellow ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorYellow', x),
|
||||
},
|
||||
/*LANG*/'Black': {
|
||||
value: !!settings.colorBlack,
|
||||
format: () => (settings.colorBlack ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorBlack', x),
|
||||
},
|
||||
/*LANG*/'White': {
|
||||
value: !!settings.colorWhite,
|
||||
format: () => (settings.colorWhite ? 'Yes' : 'No'),
|
||||
onchange: x => save('colorWhite', x),
|
||||
}
|
||||
};
|
||||
|
|
|
|||