1
0
Fork 0

Merge pull request #1 from espruino/master

Sync Repo changes
master
OmegaRogue 2020-04-03 19:12:29 +02:00 committed by GitHub
commit 37238239a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 2047 additions and 536 deletions

View File

@ -15,8 +15,8 @@
{ "id": "moonphase", { "id": "moonphase",
"name": "Moonphase", "name": "Moonphase",
"icon": "app.png", "icon": "app.png",
"version":"0.01", "version":"0.02",
"description": "Shows current moon phase. Currently only with fixed coordinates (northern hemisphere).", "description": "Shows current moon phase. Now with GPS function.",
"tags": "", "tags": "",
"allow_emulator":true, "allow_emulator":true,
"storage": [ "storage": [
@ -91,7 +91,7 @@
{ "id": "gbridge", { "id": "gbridge",
"name": "Gadgetbridge", "name": "Gadgetbridge",
"icon": "app.png", "icon": "app.png",
"version":"0.04", "version":"0.06",
"description": "The default notification handler for Gadgetbridge notifications from Android", "description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget", "tags": "tool,system,android,widget",
"storage": [ "storage": [
@ -392,7 +392,8 @@
{ "id": "swatch", { "id": "swatch",
"name": "Stopwatch", "name": "Stopwatch",
"icon": "stopwatch.png", "icon": "stopwatch.png",
"version":"0.03", "version":"0.05",
"interface": "interface.html",
"description": "Simple stopwatch with Lap Time logging to a JSON file", "description": "Simple stopwatch with Lap Time logging to a JSON file",
"tags": "health", "tags": "health",
"allow_emulator":true, "allow_emulator":true,
@ -892,7 +893,7 @@
{ "id": "marioclock", { "id": "marioclock",
"name": "Mario Clock", "name": "Mario Clock",
"icon": "marioclock.png", "icon": "marioclock.png",
"version":"0.04", "version":"0.05",
"description": "Animated Mario clock, jumps to change the time!", "description": "Animated Mario clock, jumps to change the time!",
"tags": "clock,mario,retro", "tags": "clock,mario,retro",
"type": "clock", "type": "clock",
@ -963,7 +964,28 @@
{"name":"chrono.img","url":"chrono-icon.js","evaluate":true} {"name":"chrono.img","url":"chrono-icon.js","evaluate":true}
] ]
}, },
{ "id": "widhwt", { "id": "astrocalc",
"name": "Astrocalc",
"icon": "astrocalc.png",
"version":"0.01",
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
"tags": "app,sun,moon,cycles,tool,outdoors",
"allow_emulator":true,
"storage": [
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
{"name":"suncalc.js","url":"suncalc.js"},
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
{"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true},
{"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true},
{"name":"waning-crescent.img","url":"waning-crescent-icon.js","evaluate":true},
{"name":"waning-gibbous.img","url":"waning-gibbous-icon.js","evaluate":true},
{"name":"full.img","url":"full-icon.js","evaluate":true},
{"name":"new.img","url":"new-icon.js","evaluate":true},
{"name":"waxing-gibbous.img","url":"waxing-gibbous-icon.js","evaluate":true},
{"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true}
]
},
{ "id": "widhwt",
"name": "Hand Wash Timer", "name": "Hand Wash Timer",
"icon": "widget.png", "icon": "widget.png",
"version":"0.01", "version":"0.01",
@ -973,5 +995,56 @@
"storage": [ "storage": [
{"name":"widhwt.wid.js","url":"widget.js"} {"name":"widhwt.wid.js","url":"widget.js"}
] ]
},
{ "id": "toucher",
"name": "Touch Launcher",
"shortName":"Menu",
"icon": "app.png",
"version":"0.02",
"description": "Touch enable left to right launcher.",
"tags": "tool,system,launcher",
"type":"launch",
"storage": [
{"name":"toucher.app.js","url":"app.js"}
],
"sortorder" : -10
},
{
"id": "balltastic",
"name": "Balltastic",
"icon": "app.png",
"version": "0.01",
"description": "Simple but fun ball eats dots game.",
"tags": "game,fun",
"type": "app",
"storage": [
{"name":"balltastic.app.js","url":"app.js"},
{"name":"balltastic.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "rpgdice",
"name": "RPG dice",
"icon": "rpgdice.png",
"version": "0.01",
"description": "Simple RPG dice rolling app.",
"tags": "game,fun",
"type": "app",
"allow_emulator": true,
"storage": [
{"name":"rpgdice.app.js","url": "app.js"},
{"name":"rpgdice.img","url": "app-icon.js","evaluate":true}
]
},
{ "id": "widmp",
"name": "Moon Phase Widget",
"icon": "widget.png",
"version":"0.01",
"description": "Display the current moon phase in blueish for the northern hemisphere in eight phases",
"tags": "widget,tools",
"type":"widget",
"storage": [
{"name":"widmp.wid.js","url":"widget.js"}
]
} }
] ]

1
apps/astrocalc/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Create astrocalc app

View File

@ -0,0 +1,348 @@
/**
* Inspired by: https://www.timeanddate.com
*/
const SunCalc = require("suncalc.js");
function drawMoon(phase, x, y) {
const moonImgFiles = [
"new",
"waxing-crescent",
"first-quarter",
"waxing-gibbous",
"full",
"waning-gibbous",
"last-quarter",
"waning-crescent",
];
img = require("Storage").read(`${moonImgFiles[phase]}.img`);
// image width & height = 92px
g.drawImage(img, x - parseInt(92 / 2), y);
}
// linear interpolation between two values a and b
// u controls amount of a/b and is in range [0.0,1.0]
function lerp(a,b,u) {
return (1-u) * a + u * b;
}
function titlizeKey(key) {
return (key[0].toUpperCase() + key.slice(1)).match(/[A-Z][a-z]+/g).join(" ");
}
function dateToTimeString(date) {
const hrs = ("0" + date.getHours()).substr(-2);
const mins = ("0" + date.getMinutes()).substr(-2);
const secs = ("0" + date.getMinutes()).substr(-2);
return `${hrs}:${mins}:${secs}`;
}
function drawTitle(key) {
const fontHeight = 16;
const x = 0;
const x2 = g.getWidth() - 1;
const y = fontHeight + 26;
const y2 = g.getHeight() - 1;
const title = titlizeKey(key);
g.setFont("6x8", 2);
g.setFontAlign(0,-1);
g.drawString(title,(x+x2)/2,y-fontHeight-2);
g.drawLine(x,y-2,x2,y-2);
}
/**
* @params {Number} angle Angle of point around a radius
* @params {Number} radius Radius of the point to be drawn, default 2
* @params {Object} color Color of the point
* @params {Number} color.r Red 0-1
* @params {Number} color.g Green 0-1
* @params {Number} color.b Blue 0-1
*/
function drawPoint(angle, radius, color) {
const pRad = Math.PI / 180;
const faceWidth = 80; // watch face radius
const centerPx = g.getWidth() / 2;
const a = angle * pRad;
const x = centerPx + Math.sin(a) * faceWidth;
const y = centerPx - Math.cos(a) * faceWidth;
if (!radius) radius = 2;
g.setColor(color.r, color.g, color.b);
g.fillCircle(x, y + 20, radius);
}
function drawPoints() {
const startColor = {r: 140, g: 255, b: 255}; // light blue
const endColor = {r: 0, g: 0, b: 140}; // dark turquoise
const steps = 60;
const step_u = 1.0 / (steps / 2);
let u = 0.0;
for (let i = 0; i < steps; i++) {
const colR = lerp(startColor.r, endColor.r, u) / 255;
const colG = lerp(startColor.g, endColor.g, u) / 255;
const colB = lerp(startColor.b, endColor.b, u) / 255;
const col = {r: colR, g: colG, b: colB};
if (i >= 0 && i <= 30) {
u += step_u;
} else {
u -= step_u;
}
drawPoint((360 * i) / steps, 2, col);
}
}
function drawData(title, obj, startX, startY) {
g.clear();
drawTitle(title);
let xPos, yPos;
if (typeof(startX) === "undefined" || startX === null) {
// Center text
g.setFontAlign(0,-1);
xPos = (0 + g.getWidth() - 2) / 2;
} else {
xPos = startX;
}
if (typeof(startY) === "undefined") {
yPos = 5;
} else {
yPos = startY;
}
g.setFont("6x8", 1);
Object.keys(obj).forEach((key) => {
g.drawString(`${key}: ${obj[key]}`, xPos, yPos += 20);
});
g.flip();
}
function drawMoonPositionPage(gps, title) {
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
const pageData = {
Azimuth: pos.azimuth.toFixed(2),
Altitude: pos.altitude.toFixed(2),
Distance: `${pos.distance.toFixed(0)} km`,
"Parallactic Ang": pos.parallacticAngle.toFixed(2),
};
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
drawData(title, pageData, null, 80);
drawPoints();
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1});
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BTN3, {repeat: false, edge: "falling"});
}
function drawMoonIlluminationPage(gps, title) {
const phaseNames = [
"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
"Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent",
];
const phase = SunCalc.getMoonIllumination(new Date());
const pageData = {
Phase: phaseNames[phase.phase],
};
drawData(title, pageData, null, 35);
drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BTN3, {repease: false, edge: "falling"});
}
function drawMoonTimesPage(gps, title) {
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
const pageData = {
Rise: dateToTimeString(times.rise),
Set: dateToTimeString(times.set),
};
drawData(title, pageData, null, 105);
drawPoints();
// Draw the moon rise position
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
// Draw the moon set position
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BTN3, {repease: false, edge: "falling"});
}
function drawSunShowPage(gps, key, date) {
const pos = SunCalc.getPosition(date, gps.lat, gps.lon);
const hrs = ("0" + date.getHours()).substr(-2);
const mins = ("0" + date.getMinutes()).substr(-2);
const secs = ("0" + date.getMinutes()).substr(-2);
const time = `${hrs}:${mins}:${secs}`;
const azimuth = Number(pos.azimuth.toFixed(2));
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
const altitude = Number(pos.altitude.toFixed(2));
const pageData = {
Time: time,
Altitude: altitude,
Azimumth: azimuth,
Degrees: azimuthDegrees
};
drawData(key, pageData, null, 85);
drawPoints();
// Draw the suns position
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 0});
m = setWatch(() => {
m = sunIndexPageMenu(gps);
}, BTN3, {repeat: false, edge: "falling"});
return null;
}
function sunIndexPageMenu(gps) {
const sunTimes = SunCalc.getTimes(new Date(), gps.lat, gps.lon);
const sunMenu = {
"": {
"title": "-- Sun --",
},
"Current Pos": () => {
m = E.showMenu();
drawSunShowPage(gps, "Current Pos", new Date());
},
};
Object.keys(sunTimes).sort().reduce((menu, key) => {
const title = titlizeKey(key);
menu[title] = () => {
m = E.showMenu();
drawSunShowPage(gps, key, sunTimes[key]);
};
return menu;
}, sunMenu);
sunMenu["< Back"] = () => m = indexPageMenu(gps);
return E.showMenu(sunMenu);
}
function moonIndexPageMenu(gps) {
const moonMenu = {
"": {
"title": "-- Moon --",
},
"Times": () => {
m = E.showMenu();
drawMoonTimesPage(gps, "Times");
},
"Position": () => {
m = E.showMenu();
drawMoonPositionPage(gps, "Position");
},
"Illumination": () => {
m = E.showMenu();
drawMoonIlluminationPage(gps, "Illumination");
},
"< Back": () => m = indexPageMenu(gps),
};
return E.showMenu(moonMenu);
}
function indexPageMenu(gps) {
const menu = {
"": {
"title": "Select",
},
"Sun": () => {
m = sunIndexPageMenu(gps);
},
"Moon": () => {
m = moonIndexPageMenu(gps);
},
"< Exit": () => { load(); }
};
return E.showMenu(menu);
}
/**
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
*/
function drawGPSWaitPage() {
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))
g.clear();
g.drawImage(img, 100, 50);
g.setFont("6x8", 1);
g.drawString("Astrocalc v0.01", 80, 105);
g.drawString("Locating GPS", 85, 140);
g.drawString("Please wait...", 80, 155);
g.flip();
const DEBUG = false;
if (DEBUG) {
const gps = {
"lat": 56.45783133333,
"lon": -3.02188583333,
"alt": 75.3,
"speed": 0.070376,
"course": NaN,
"time":new Date(),
"satellites": 4,
"fix": 1
};
m = indexPageMenu(gps);
return;
}
Bangle.on('GPS', (gps) => {
if (gps.fix === 0) return;
Bangle.setGPSPower(0);
Bangle.buzz();
Bangle.setLCDPower(true);
m = indexPageMenu(gps);
});
}
function init() {
Bangle.setGPSPower(1);
drawGPSWaitPage();
}
let m;
init();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI1ygf4BZM/BZMD//wCxP/8AWJ/+ACxP+CxQ6ICwP/4AWJERAWCEQ4WCERAWCEQ4WDOg4WCNA4WD/gWKRYwWDHI4WDHIwWDHI4WDHIwWEOYwWDHIwWEKAwWD/4WKKAwWEKAoWEYgwWPM4wWEM4oWQM4oWEPwwWbPwoWESowW/C34WOZ1vACxP8Cyv4CxWACyoKFCwiUFCwhmGCwh9FCwhmGCwhmFCwhPGCwgKFCwg4GCwZPGCwg4GCwY4GCwgKGCwY4GCwZxGCwjBFCwghHCwQhHCwYhHCwQhHCwRlHCwSHHCwYKICwI3HCwQKJAFAA=="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgJC/AD8B//4BRILJBQP/+AKGn4LC4AKFh4KC/4KFgYKD/gLFv4LD8AKEj4KD/+AEJAiGEIgiFIYhFFOAQADOghlDNA0HBQv+Q4wADRYZaFLgg4GHIg4GHIY4GHIhxFOYhxGOYgKHKARPHKARPHKAZPHKATBFYgoWKMw5nDMw5nCCyx9IPwQKIPwIW/C34WJZ1sDBQ/8CwM/BY/ACxkfBY+AgEBBQ/4CwJ+IBQJ+IPoJnIMwRnIMwJQIJ4RQIJ4JQIJ4RQIBQQ5HHAQ5HHAY5HHARzHOIRzHOIbEHYIIACLgpaDEQwhFEQohEIopDENAplERYwKGOgZwEBYoKIAH4AXA=="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI0xgP8BRP/4ALI/4WJv4WJj4WJg//CxA3BCxM/CxIhCCw4hCCxAhCCw4hCCxAKCCw5lBCxEDCxSHBCxA4DCw4KCCw44DCww4DCw5xCCw44DCw5PDCw0PCxQKDCwxPDCwzBDCyRmECwxmDCyRmDCwx9ECzoKDCwyUEC34W/CyDOtn4WJgYWVgIWKj4WVPwgWFSogWGM4gWGPwYWGM4gWGM4YWGKAgWGKAYWGHIgWGKAYWHHIYWGHIYWHHIYWGHIYWHOYYWHYgQWHEQYWHEQQWIEQQWHEQQWINAQWIRYIWIOgQWIHQIWJBYIWJAFI="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgIGDh///4RHBQQLHg4KC/wKFgIKC//4BYt/BYfgBQkfBQf/wAsHFw4HCBwXwBQc/AwYLB4AhEIARIBEQn//gECgYiEIYJ2FIoQQBE4YzBDgd/NoguBNAUPKoo/BB4YhEEQIdCAYYiECQMHUwwHDEIweBLgMPWIwiBAQSlENwQTBDIQAFFQMDHAw5BOYN/HAwfB8ANCAAofCHA45B+EPHA4UBKQQAGMgMfUYQAFv+DJ45QCn5PHKAPDJ45QB/hmICwPnT4yhC/1/Mw5nBCxZmIM4P/PpB+BC34WEVZCsB/7CIYYIWWOX4WbfiwWL/gKHgf+n/ABY8/4YWJ/k/VhF/4LDIg/4j5nI/+APxEP+EPM48BCgN/KA5CBg5QHMwINCJ4/AgY5Hh4fBj45GHAKeBAQSfFMgIZCHAoqCv45GA4QOBEQsfDwQDDEIgSC/4iFv6dCg4iFj60Dn4iEEIKRCL4K5E/5uDh4QDDgKFEv4uDj4/EE4IRCDYIzEAwIvBAQKnFEQIADMIhFBAAayFNAIACMoZtDBYa9GFwbrHBQR2EBYoKEA=="))

328
apps/astrocalc/suncalc.js Normal file
View File

@ -0,0 +1,328 @@
/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
*/
(function () { 'use strict';
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; }
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only.
h = 0; // if h = -0.08901179 a div/0 would occur.
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
function sunCoords(d) {
var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
return {
dec: declination(L, 0),
ra: rightAscension(L, 0)
};
}
var SunCalc = {};
// calculates sun position for a given date and latitude/longitude
SunCalc.getPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = sunCoords(d),
H = siderealTime(d, lw) - c.ra;
return {
azimuth: azimuth(H, phi, c.dec),
altitude: altitude(H, phi, c.dec)
};
};
// sun times configuration (angle, morning name, evening name)
var times = SunCalc.times = [
[-0.833, 'sunrise', 'sunset' ],
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
[ -6, 'dawn', 'dusk' ],
[ -12, 'nauticalDawn', 'nauticalDusk'],
[ -18, 'nightEnd', 'night' ],
[ 6, 'goldenHourEnd', 'goldenHour' ]
];
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) {
times.push([angle, riseName, setName]);
};
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
SunCalc.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: new Date(fromJulian(Jnoon)),
nadir: new Date(fromJulian(Jnoon - 0.5))
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2));
result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2));
}
return result;
};
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
F = rad * (93.272 + 13.229350 * d), // mean distance
l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
dt = 385001 - 20905 * cos(M); // distance to the moon in km
return {
ra: rightAscension(l, b),
dec: declination(l, b),
dist: dt
};
}
SunCalc.getMoonPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c
SunCalc.getMoonIllumination = function (date) {
let month = date.getMonth();
let year = date.getFullYear();
let day = date.getDate();
let c = 0;
let e = 0;
let jd = 0;
let b = 0;
if (month < 3) {
year--;
month += 12;
}
++month;
c = 365.25 * year;
e = 30.6 * month;
jd = c + e + day - 694039.09; // jd is total days elapsed
jd /= 29.5305882; // divide by the moon cycle
b = parseInt(jd); // int(jd) -> b, take integer part of jd
jd -= b; // subtract integer part to leave fractional part of original jd
b = Math.round(jd * 8); // scale fraction from 0-8 and round
if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0
return {phase: b};
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = date;
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
else if (typeof define === 'function' && define.amd) define(SunCalc);
else global.SunCalc = SunCalc;
}());

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgJC/ABHgBRN8BRMfwAKIg/4CxP/BRM/HBMH/wKIgP/4AhJ/ghJ/5PJ/5PJj4WJgf/+AWIv5mJHAIWJ/5mJHAJ9IHAIWJn59JHAJ9JJ4IWIh4WK/4WJJ4KUIYIKUJJ4IWIMwIWgMwIWIPoLCJCwLCICxYKBCxCUBC34W/Cya3WCxr8In78JgYWhj4WJgIWKPwP8SpXAM5IWJPwIWIKAIWJM4PgKBP+CxBQBCxA5CBRBQBYZA5CBRA5BSpA5CSpA5BCxJzBPxDEBPxIiBM5MDPxJFBM5IiBKBMBKBKLBKBMAhwKJAH4ABA="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI0xgP/wAKJ/wWI///+AKHv4LBEQ8fBQP8BQ0HBQP/8A3HAAQWGn4KCHIwhDHIwhE/AhJ//AEJJQGBQZQGMoQABRQsDCwhQFQ4RnHHAgWGBQhnFHAhnFHAoWFOIhnFHAp+FJ4oWEh4WKBQp+EJ4qVEYIgWRMwwWEMwoWLVghmFVgh9GCzYKGCwaUGC34W/CxzOtn4WJgYKF/wWK8AKCgIWKj4WVPwwWDSo38BQZnG4B+JCwhnGCwhnF/AKDKA2AKBIWEHIwKEKAqrDHI4KEHIp9EHIqUEHIxmEOYp9EYgxmEEQpmFEQoKFEQhmFEQhPGNAhPFRYg4GOggKHHQSIFBYghIAFQ="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgJC/ABdwBRMD8ALJj+ABREB/wWJh/wBZN/4AKIg4iKn/4KBP/ERMfERMB/5FJj//NBP//hnJ/6LJ/45Jg45Kv45JCwI5Jn5zJPwI5JCwJQICwP/CxRQISoJQJSoLEICwRQICwJnICzJnIYYJ+JCzB+ICwKVJC34W/CxbOffgIWIfgXACxP8Cyv4CxWACyUDPpU/ShIWBPpIWBPpEHMxMAv5mJCwJPICwQKIYQI4IYQJPJCwI4ISgI4JSgIKICwI4Jn5xJSgLBIMwIhJg4hJMwIKJj4hJgJlJgE+BRMHBRIA+A"))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI1yj/4BREH/4LJ/4LJj4LB8AKGgYKB/+ABY1/BQP+BQ0PCwQuHBQX/4A4IEQ8BCwYiGn4iJJ4YiHJ4QAB+CIGAAZoFBQn8MxCLHBQg5FMwY5GMwg5GCwo5EMwhzGPog5FCwxQECwv/PpJQFSghQFCwzEECyJnECwxnDVYoWFBQpnECwx+ECzp+DCwyVEC34W/CyDOt4AKCg4KF/gWDv4WQ/AWKwAWVBQcDShMAn5mJCwx9DCwxmEgJmJgEfJ5IWGBQasGHAisFJ4gWGHAh+FHAiVGBQhnFHAp+EOIhnGYIZnGEIpQEEIxnEEIpQEEIxQDMoo5EQ4o5FFgyKDBRAiBBRAApA="))

View File

@ -0,0 +1 @@
0.01: Initial version of Balltastic released! Happy!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEogAIkUzmciBpIVIkYWBAAUyCx0hiIXFAAMkCxhUBC4fzDAYWLiAXFAAP//5KKoMRC4UTC4k/DAPzJJERiKcCC5H/GA4uBWwp6DC4YwHCwMBDI0SMAYwHoIWBiXxdIwYCMJCjBiM/C46VDC4M/GAkRgMf//ySAQAEgKrDC4lBgMCHIQXHSwxICIwIuBAAIXIGAYABiQXBkEBTYcgC473FkQXBiETTQZ4IgECC4cholCiJGDMAIXIWgIXCmMkC4JGDJBbEDC4UACwn/mAtGSYsxilCgIXFSAqDBkMRiIFBkcxiUiC4sxXowIBC4QGBkIXBiJ2EFwsDBIPyC4ILBgMRiUyiCmJgSCC+YXDgAXDR4YuEcAn/MAIXEmcgBoXyFwjIEMAQXFkIOCUgoXF+J3CC4cxBwR1IQQx3BkUzmUSBQKkFC5IuBkVDJAJeGRwLhHFwUkC4Mxl6lFC48gFwYXCmcTOwomBC4swYIMikU0C4UxkJ3FC40xFoIXCogXBmaxDC5MyCwUiogXDmIXTJASSBC4kRU4oXDkgXFmQwDNwIWEBoIXFJAYKBZggWFC4YWCC4g7BkIWBkYWBBYYXCkYXDJAYjDkQUEEYZGEGA4XIIwwwGDAQuOGAomCFo4uGGARoBE4ZOGFxAABBwgAICxAABCyxJBGJJFJJRgVNPggsMA="))

186
apps/balltastic/app.js Normal file
View File

@ -0,0 +1,186 @@
Bangle.setLCDBrightness(1);
Bangle.setLCDMode("doublebuffered");
let points = 0;
let level = 1;
let levelSpeedStart = 0.8;
let nextLevelPoints = 20;
let levelSpeedFactor = 0.2;
let counterWidth = 10;
let gWidth = g.getWidth() - counterWidth;
let gHeight = g.getHeight();
let counter = 160;
let counterMax = 160;
let ballDims = 20;
let ballx = g.getWidth() / 2 - ballDims;
let bally = g.getHeight() / 2 - ballDims;
let dotx = g.getWidth() / 2;
let doty = g.getWidth() / 2;
let ballBuzzTime = 5;
let ballSpeedFactor = 40;
let redrawspeed = 5;
let dotwidth = 5;
let running = false;
let drawInterval;
let xBuzzed = false;
let yBuzzed = false;
let BALL = require("heatshrink").decompress(
atob(
"ikUyAROvkQ3v4405AIYHBGq9KpMhktz1/W7feAJAtBEZ9jhkhs0ZgkQ8lKxW+jAdB516627E4X8AIPWzelmolKlpJBjMFEYIpC4kQ0YBBqWKynTFYPe7gpE3ec6gnHkNFrXL7372u2E4WjhGCAIliqWrUIPeKoIpB7h9HoUoqWq999///FIJ3BhGDEIIBBgFBAoWCoUI3vY62aQIW7ymSJooLBEoIADwkQEYVhEoInEGIOjR4O1y/OrIrBUYdr198iH/74nF88cE4gpCA4MY8k59CzBAINrx2164nBtduufPWYIlF++/xkxNoMAAIJPBoSdB52a30ZkNGE4IvBoUpwkxLIOMyWEmAmE7+MqKbEsLLBH4P3zw1BAYJFBFIMY8sQ4cx44nB0tVHYITBEoO967lDgDDC1tVQ4QBD37xBjMmJ4I3BE4IxBPoOMuSrBHYL1BJYbrDvfPLoYBD889jMlEoMhkpJBwkRE4O+jB7B405LoJPEYYUx0xPG7/3vxvBmOnrXsdIOc6jxBE4JfBvfwHIafDFoMRgh3H99+zsUDIOMqWU2YlBAAO1/AnBToN76EhgpTBFYKPBGIIhBEovOrWliuc2YlBE4oABE4etu2UyVrpqJBMoKvBEIPnjvWze97ATBE4YPBEopRC64BC27nBzn0znTAIOlimtq21y4BCEoM1HYOMqIVBE44AB0tVCYIBEigVBE4U1GYIFBymywkwEoJzHABIRBMIIXBWoIDCqOEmOEiABCmIjPAA51BFoVSEoUwAIIZNA"
)
);
function reset() {
g.clear();
level = 1;
points = 0;
ballx = g.getWidth() / 2 - ballDims;
bally = g.getHeight() / 2 - ballDims;
counter = counterMax;
createRandomDot();
drawInterval = setInterval(play, redrawspeed);
running = true;
}
function collide() {
try {
Bangle.buzz(ballBuzzTime, 0.8);
} catch (e) {}
}
function createRandomDot() {
dotx = Math.floor(
Math.random() * Math.floor(gWidth - dotwidth / 2) + dotwidth / 2
);
doty = Math.floor(
Math.random() * Math.floor(gHeight - dotwidth / 2) + dotwidth / 2
);
}
function checkIfDotEaten() {
if (
ballx + ballDims > dotx &&
ballx <= dotx + dotwidth &&
bally + ballDims > doty &&
bally <= doty + dotwidth
) {
collide();
createRandomDot();
counter = counterMax;
points++;
if (points % nextLevelPoints == 0) {
level++;
}
}
}
function drawLevelText() {
g.setColor("#26b6c7");
g.setFontAlign(0, 0);
g.setFont("4x6", 5);
g.drawString("Level " + level, 120, 80);
}
function draw() {
//bg
g.setColor("#71c6cf");
g.fillRect(0, 0, g.getWidth(), g.getHeight());
//counter
drawCounter();
//draw level
drawLevelText();
//dot
g.setColor("#ff0000");
g.fillCircle(dotx, doty, dotwidth);
//ball
g.drawImage(BALL, ballx, bally);
g.flip();
}
function drawCounter() {
g.setColor("#000000");
g.fillRect(g.getWidth() - counterWidth, 0, g.getWidth(), gHeight);
if(counter < 40 ) g.setColor("#fc0303");
else if (counter < 80 ) g.setColor("#fc9803");
else g.setColor("#0318fc");
g.fillRect(
g.getWidth() - counterWidth,
gHeight,
g.getWidth(),
gHeight - counter
);
}
function checkCollision() {
if (ballx < 0) {
ballx = 0;
if (!xBuzzed) collide();
xBuzzed = true;
} else if (ballx > gWidth - ballDims) {
ballx = gWidth - ballDims;
if (!xBuzzed) collide();
xBuzzed = true;
} else {
xBuzzed = false;
}
if (bally < 0) {
bally = 0;
if (!yBuzzed) collide();
yBuzzed = true;
} else if (bally > gHeight - ballDims) {
bally = gHeight - ballDims;
if (!yBuzzed) collide();
yBuzzed = true;
} else {
yBuzzed = false;
}
}
function count() {
counter -= levelSpeedStart + level * levelSpeedFactor;
if (counter <= 0) {
running = false;
clearInterval(drawInterval);
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50);
}
}
function accel(values) {
ballx -= values.x * ballSpeedFactor;
bally -= values.y * ballSpeedFactor;
}
function play() {
if (running) {
accel(Bangle.getAccel());
checkCollision();
checkIfDotEaten();
count();
draw();
}
}
setTimeout(() => {
reset();
drawInterval = setInterval(play, redrawspeed);
setWatch(
() => {
if(!running) reset();
},
BTN1,
{ repeat: true }
);
running = true;
}, 10);

BIN
apps/balltastic/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -2,3 +2,6 @@
0.02: Increase contrast (darker notification background, white text) 0.02: Increase contrast (darker notification background, white text)
0.03: Gadgetbridge widget now shows connection state 0.03: Gadgetbridge widget now shows connection state
0.04: Tweaks for variable size widget system 0.04: Tweaks for variable size widget system
0.05: Show incoming call notification
Optimize animation, limit title length
0.06: Gadgetbridge App 'Connected' state is no longer toggleable

View File

@ -4,7 +4,7 @@ function gb(j) {
var mainmenu = { var mainmenu = {
"" : { "title" : "Gadgetbridge" }, "" : { "title" : "Gadgetbridge" },
"Connected" : { value : NRF.getSecurityStatus().connected }, "Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
"Find Phone" : function() { E.showMenu(findPhone); }, "Find Phone" : function() { E.showMenu(findPhone); },
"Exit" : ()=> {load();}, "Exit" : ()=> {load();},
}; };

View File

@ -1,125 +1,196 @@
(function() { (() => {
var musicState = "stop";
var musicInfo = {"artist":"","album":"","track":""}; const state = {
var scrollPos = 0; music: "stop",
function gb(j) {
Bluetooth.println(JSON.stringify(j)); musicInfo: {
artist: "",
album: "",
track: ""
},
scrollPos: 0
};
function gbSend(message) {
Bluetooth.println(JSON.stringify(message));
} }
function show(size,render) {
function showNotification(size, render) {
var oldMode = Bangle.getLCDMode(); var oldMode = Bangle.getLCDMode();
Bangle.setLCDMode("direct"); Bangle.setLCDMode("direct");
g.setClipRect(0,240,239,319); g.setClipRect(0, 240, 239, 319);
g.setColor("#222222"); g.setColor("#222222");
g.fillRect(1,241,238,318); g.fillRect(1, 241, 238, 318);
render(320-size);
render(320 - size);
g.setColor("#ffffff"); g.setColor("#ffffff");
g.fillRect(0,240,1,319); g.fillRect(0, 240, 1, 319);
g.fillRect(238,240,239,319); g.fillRect(238, 240, 239, 319);
g.fillRect(2,318,238,319); g.fillRect(2, 318, 238, 319);
Bangle.setLCDPower(1); // light up Bangle.setLCDPower(1); // light up
Bangle.setLCDMode(oldMode); // clears cliprect Bangle.setLCDMode(oldMode); // clears cliprect
function anim() { function anim() {
scrollPos-=2; state.scrollPos -= 2;
if (scrollPos<-size) scrollPos=-size; if (state.scrollPos < -size) {
Bangle.setLCDOffset(scrollPos); state.scrollPos = -size;
if (scrollPos>-size) setTimeout(anim,10); }
} Bangle.setLCDOffset(state.scrollPos);
anim(); if (state.scrollPos > -size) setTimeout(anim, 15);
}
function hide() {
function anim() {
scrollPos+=4;
if (scrollPos>0) scrollPos=0;
Bangle.setLCDOffset(scrollPos);
if (scrollPos<0) setTimeout(anim,10);
} }
anim(); anim();
} }
Bangle.on('touch',function() { function hideNotification() {
if (scrollPos) hide(); function anim() {
}); state.scrollPos += 4;
Bangle.on('swipe',function(dir) { if (state.scrollPos > 0) state.scrollPos = 0;
if (musicState=="play") { Bangle.setLCDOffset(state.scrollPos);
gb({t:"music",n:dir>0?"next":"previous"}); if (state.scrollPos < 0) setTimeout(anim, 10);
} }
}); anim();
gb({t:"status",bat:E.getBattery()}); }
global.GB = function(j) { function handleNotificationEvent(event) {
switch (j.t) {
// split text up at word boundaries
var txt = event.body.split("\n");
var MAXCHARS = 38;
for (var i = 0; i < txt.length; i++) {
txt[i] = txt[i].trim();
var l = txt[i];
if (l.length > MAXCHARS) {
var p = MAXCHARS;
while (p > MAXCHARS - 8 && !" \t-_".includes(l[p]))
p--;
if (p == MAXCHARS - 8) p = MAXCHARS;
txt[i] = l.substr(0, p);
txt.splice(i + 1, 0, l.substr(p));
}
}
showNotification(80, (y) => {
// TODO: icon based on src?
var x = 120;
g.setFontAlign(0, 0);
g.setFont("6x8", 1);
g.setColor("#40d040");
g.drawString(event.src, x, y + 7);
g.setColor("#ffffff");
g.setFont("6x8", 2);
if (event.title)
g.drawString(event.title.slice(0,17), x, y + 25);
g.setFont("6x8", 1);
g.setColor("#ffffff");
g.setFontAlign(-1, -1);
g.drawString(txt.join("\n"), 10, y + 40);
});
Bangle.buzz();
}
function handleMusicStateUpdate(event) {
state.music = event.state
if (state.music == "play") {
showNotification(40, (y) => {
g.setColor("#ffffff");
g.drawImage(require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")), 8, y + 8);
g.setFontAlign(-1, -1);
var x = 40;
g.setFont("4x6", 2);
g.setColor("#ffffff");
g.drawString(state.musicInfo.artist, x, y + 8);
g.setFont("6x8", 1);
g.setColor("#ffffff");
g.drawString(state.musicInfo.track, x, y + 22);
});
}
if (state.music == "pause") {
hideNotification();
}
}
function handleCallEvent(event) {
if (event.cmd == "accept") {
showNotification(40, (y) => {
g.setColor("#ffffff");
g.drawImage(require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw=")), 8, y + 8);
g.setFontAlign(-1, -1);
var x = 40;
g.setFont("4x6", 2);
g.setColor("#ffffff");
g.drawString(event.name, x, y + 8);
g.setFont("6x8", 1);
g.setColor("#ffffff");
g.drawString(event.number, x, y + 22);
});
Bangle.buzz();
}
}
global.GB = (event) => {
switch (event.t) {
case "notify": case "notify":
show(80,function(y) { handleNotificationEvent(event);
// TODO: icon based on src? break;
var x = 120;
g.setFontAlign(0,0);
g.setFont("6x8",1);
g.setColor("#40d040");
g.drawString(j.src,x,y+7);
g.setColor("#ffffff");
g.setFont("6x8",2);
g.drawString(j.title,x,y+25);
g.setFont("6x8",1);
g.setColor("#ffffff");
// split text up a word boundaries
var txt = j.body.split("\n");
var MAXCHARS = 38;
for (var i=0;i<txt.length;i++) {
txt[i] = txt[i].trim();
var l = txt[i];
if (l.length>MAXCHARS) {
var p = MAXCHARS;
while (p>MAXCHARS-8 && !" \t-_".includes(l[p]))
p--;
if (p==MAXCHARS-8) p=MAXCHARS;
txt[i] = l.substr(0,p);
txt.splice(i+1,0,l.substr(p));
}
}
g.setFontAlign(-1,-1);
g.drawString(txt.join("\n"),10,y+40);
Bangle.buzz();
});
break;
case "musicinfo": case "musicinfo":
musicInfo = j; state.musicInfo = event;
break; break;
case "musicstate": case "musicstate":
musicState = j.state; handleMusicStateUpdate(event);
if (musicState=="play") break;
show(40,function(y) { case "call":
g.setColor("#ffffff"); handleCallEvent(event);
g.drawImage( require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")),8,y+8); break;
g.setFontAlign(-1,-1);
g.setFont("6x8",1);
var x = 40;
g.setFont("4x6",2);
g.setColor("#ffffff");
g.drawString(musicInfo.artist,x,y+8);
g.setFont("6x8",1);
g.setColor("#ffffff");
g.drawString(musicInfo.track,x,y+22);
});
if (musicState=="pause")
hide();
break;
} }
}; };
function draw() { // Touch control
g.setColor(-1); Bangle.on("touch", () => {
if (NRF.getSecurityStatus().connected) if (state.scrollPos) {
g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")),this.x+1,this.y+1); hideNotification();
else }
g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")),this.x+1,this.y+1); });
}
function changed() {
WIDGETS["gbridgew"].draw();
g.flip();// turns screen on
}
NRF.on('connected',changed);
NRF.on('disconnected',changed);
WIDGETS["gbridgew"]={area:"tl",width:24,draw:draw}; Bangle.on("swipe", (dir) => {
if (state.music == "play") {
const command = dir > 0 ? "next" : "previous"
gbSend({ t: "music", n: command });
}
});
function draw() {
g.setColor(-1);
if (NRF.getSecurityStatus().connected)
g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")), this.x + 1, this.y + 1);
else
g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")), this.x + 1, this.y + 1);
}
function changedConnectionState() {
WIDGETS["gbridgew"].draw();
g.flip(); // turns screen on
}
NRF.on("connected", changedConnectionState);
NRF.on("disconnected", changedConnectionState);
WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: draw };
gbSend({ t: "status", bat: E.getBattery() });
})(); })();

View File

@ -11,17 +11,7 @@ var domRecords = document.getElementById("records");
function saveRecord(record,name) { function saveRecord(record,name) {
var csv = `${record.map(rec=>[rec.time, rec.bpm, rec.confidence].join(",")).join("\n")}`; var csv = `${record.map(rec=>[rec.time, rec.bpm, rec.confidence].join(",")).join("\n")}`;
var a = document.createElement("a"), Util.saveCSV(name, csv);
file = new Blob([csv], {type: "Comma-separated value file"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = name+".csv";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} }

View File

@ -56,7 +56,14 @@ exports = { name : "en_GB", currencySym:"£",
}); });
var languageSelector = document.getElementById("languages"); var languageSelector = document.getElementById("languages");
languageSelector.innerHTML = Object.keys(locales).map(l=>`<option value="${l}">${l}</option>`).join("\n"); languageSelector.innerHTML = Object.keys(locales).map(l=>{
var localeParts = l.split("_"); // en_GB -> ["en","GB"]
var icon = "";
// If we have a 2 char ISO country code, use it to get the unicode flag
if (localeParts[1] && localeParts[1].length==2)
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
return `<option value="${l}">${icon}${l}</option>`
}).join("\n");
document.getElementById("upload").addEventListener("click", function() { document.getElementById("upload").addEventListener("click", function() {

View File

@ -370,4 +370,21 @@ var locales = {
abday: "Vas,Hét,Ke,Szer,Csüt,Pén,Szom", abday: "Vas,Hét,Ke,Szer,Csüt,Pén,Szom",
day: "Vasárnap,Hétfő,Kedd,Szerda,Csütörtök,Péntek,Szombat", day: "Vasárnap,Hétfő,Kedd,Szerda,Csütörtök,Péntek,Szombat",
trans: { yes: "igen", Yes: "Igen", no: "nem", No: "Nem", ok: "ok", on: "be", off: "ki" }}, trans: { yes: "igen", Yes: "Igen", no: "nem", No: "Nem", ok: "ok", on: "be", off: "ki" }},
"pt_BR": {
lang: "pt_BR",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "R$", currency_first:true,
int_curr_symbol: "BRL",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"am",1:"pm"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "", 1: "%d/%m/%y" },
abmonth: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez",
month: "Janeiro,Fevereiro,Março,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro",
abday: "Dom,Seg,Ter,Qua,Qui,Sex,Sab",
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "certo", on: "ligado", off: "desligado" }},
}; };

View File

@ -2,3 +2,4 @@
0.02: Fix day of the week and add padding 0.02: Fix day of the week and add padding
0.03: use short date format from locale, take timeout from settings 0.03: use short date format from locale, take timeout from settings
0.04: modify date to display to be more at the original idea but still localized 0.04: modify date to display to be more at the original idea but still localized
0.05: use 12/24 hour clock from settings

View File

@ -1,14 +1,15 @@
/********************************** /**********************************
BangleJS MARIO CLOCK V0.1.0 BangleJS MARIO CLOCK
+ Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock + Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock
+ Converting images to 1bit BMP: Image > Mode > Indexed and tick the "Use black and white (1-bit) palette", Then export as BMP. + Converting images to 1bit BMP: Image > Mode > Indexed and tick the "Use black and white (1-bit) palette", Then export as BMP.
+ Online Image convertor: https://www.espruino.com/Image+Converter + Online Image convertor: https://www.espruino.com/Image+Converter
**********************************/ **********************************/
var locale = require("locale"); const locale = require("locale");
const storage = require('Storage'); const storage = require('Storage');
const settings = (storage.readJSON('setting.json',1)||{}); const settings = (storage.readJSON('setting.json', 1) || {});
const timeout = settings.timeout||10; const timeout = settings.timeout || 10;
const is12Hour = settings["12hour"] || false;
// Screen dimensions // Screen dimensions
let W, H; let W, H;
@ -273,7 +274,8 @@ function drawTime() {
drawBrick(42, 25); drawBrick(42, 25);
const t = new Date(); const t = new Date();
const hours = ("0" + t.getHours()).substr(-2); const h = t.getHours();
const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
const mins = ("0" + t.getMinutes()).substr(-2); const mins = ("0" + t.getMinutes()).substr(-2);
g.setFont("6x8"); g.setFont("6x8");
@ -374,8 +376,9 @@ function init() {
Bangle.setLCDPower(true); Bangle.setLCDPower(true);
} }
}); });
startTimers();
} }
// Initialise! // Initialise!
init(); init();
startTimers();

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Added GPS to obtain coordinates, added buttons

View File

@ -1,218 +1,215 @@
//Icons from https://icons8.com //Icons from https://icons8.com
//Sun and Moon calculations from https://github.com/mourner/suncalc and https://gist.github.com/endel/dfe6bb2fbe679781948c //Sun and Moon calculations from https://github.com/mourner/suncalc and https://gist.github.com/endel/dfe6bb2fbe679781948c
//varibales
const storage = require('Storage');
let coords;
var timer;
var fix;
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180,
dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
var SunCalc = {};
//pictures //pictures
function getImg(i) { function getImg(i) {
var data = { var data = {
"NewMoon": "AD8AAH/4AHwPgDwA8BwADg4AAcMAADHAAA5gAAGYAABsAAAPAAADwAAA8AAAPAAADwAAA2AAAZgAAGcAADjAAAw4AAcHAAOA8APAHwPgAf/gAA/AAA==", "NewMoon": "AD8AAH/4AHwPgDwA8BwADg4AAcMAADHAAA5gAAGYAABsAAAPAAADwAAA8AAAPAAADwAAA2AAAZgAAGcAADjAAAw4AAcHAAOA8APAHwPgAf/gAA/AAA==",
"WaxingCrescentNorth" : "AD8AAH/4AHw/gDwH8BwA/g4AH8MAB/HAAf5gAD+YAA/sAAP/AAD/wAA/8AAP/AAD/wAA/2AAP5gAD+cAB/jAAfw4AH8HAD+A8B/AHw/gAf/gAA/AAA==", "WaxingCrescentNorth" : "AD8AAH/4AHw/gDwH8BwA/g4AH8MAB/HAAf5gAD+YAA/sAAP/AAD/wAA/8AAP/AAD/wAA/2AAP5gAD+cAB/jAAfw4AH8HAD+A8B/AHw/gAf/gAA/AAA==",
"WaningCrescentSouth" : "AD8AAH/4AHw/gDwH8BwA/g4AH8MAB/HAAf5gAD+YAA/sAAP/AAD/wAA/8AAP/AAD/wAA/2AAP5gAD+cAB/jAAfw4AH8HAD+A8B/AHw/gAf/gAA/AAA==", "WaningCrescentSouth" : "AD8AAH/4AHw/gDwH8BwA/g4AH8MAB/HAAf5gAD+YAA/sAAP/AAD/wAA/8AAP/AAD/wAA/2AAP5gAD+cAB/jAAfw4AH8HAD+A8B/AHw/gAf/gAA/AAA==",
"FirstQuarterNorth" : "AD8AAH/4AHx/gDwf8BwH/g4B/8MAf/HAH/5gB/+YAf/sAH//AB//wAf/8AH//AB//wAf/2AH/5gB/+cAf/jAH/w4B/8HAf+A8H/AHx/gAf/gAA/AAA==", "FirstQuarterNorth" : "AD8AAH/4AHx/gDwf8BwH/g4B/8MAf/HAH/5gB/+YAf/sAH//AB//wAf/8AH//AB//wAf/2AH/5gB/+cAf/jAH/w4B/8HAf+A8H/AHx/gAf/gAA/AAA==",
"FirstQuarterSouth" : "AD8AAH/4AH+PgD/g8B/4Dg/+AcP/gDH/4A5/+AGf/gBv/4AP/+AD//gA//4AP/+AD//gA3/4AZ/+AGf/gDj/4Aw/+AcH/gOA/4PAH+PgAf/gAA/AAA==", "FirstQuarterSouth" : "AD8AAH/4AH+PgD/g8B/4Dg/+AcP/gDH/4A5/+AGf/gBv/4AP/+AD//gA//4AP/+AD//gA3/4AZ/+AGf/gDj/4Aw/+AcH/gOA/4PAH+PgAf/gAA/AAA==",
"WaxingGibbousNorth" : "AD8AAH/4AH3/gDz/8Bw//g4f/8MH//HB//5g//+YP//sD///A///wP//8D///A///wP//2D//5g//+cH//jB//w4f/8HD/+A8//AH3/gAf/gAA/AAA==", "WaxingGibbousNorth" : "AD8AAH/4AH3/gDz/8Bw//g4f/8MH//HB//5g//+YP//sD///A///wP//8D///A///wP//2D//5g//+cH//jB//w4f/8HD/+A8//AH3/gAf/gAA/AAA==",
"WaxingGibbousSouth" : "AD8AAH/4AH/vgD/88B//Dg//4cP/+DH//g5//8Gf//Bv//wP//8D///A///wP//8D///A3//wZ//8Gf/+Dj//gw//4cH/8OA//PAH/vgAf/gAA/AAA==", "WaxingGibbousSouth" : "AD8AAH/4AH/vgD/88B//Dg//4cP/+DH//g5//8Gf//Bv//wP//8D///A///wP//8D///A3//wZ//8Gf/+Dj//gw//4cH/8OA//PAH/vgAf/gAA/AAA==",
"FullMoon" : "AD8AAH/4AH//gD//8B///g///8P///H///5///+f///v/////////////////////////3///5///+f///j///w///8H//+A///AH//gAf/gAA/AAA==", "FullMoon" : "AD8AAH/4AH//gD//8B///g///8P///H///5///+f///v/////////////////////////3///5///+f///j///w///8H//+A///AH//gAf/gAA/AAA==",
"WaningGibbousNorth" : "AD8AAH/4AH/vgD/88B//Dg//4cP/+DH//g5//8Gf//Bv//wP//8D///A///wP//8D///A3//wZ//8Gf/+Dj//gw//4cH/8OA//PAH/vgAf/gAA/AAA==", "WaningGibbousNorth" : "AD8AAH/4AH/vgD/88B//Dg//4cP/+DH//g5//8Gf//Bv//wP//8D///A///wP//8D///A3//wZ//8Gf/+Dj//gw//4cH/8OA//PAH/vgAf/gAA/AAA==",
"WaningGibbousSouth" : "AD8AAH/4AH3/gDz/8Bw//g4f/8MH//HB//5g//+YP//sD///A///wP//8D///A///wP//2D//5g//+cH//jB//w4f/8HD/+A8//AH3/gAf/gAA/AAA==", "WaningGibbousSouth" : "AD8AAH/4AH3/gDz/8Bw//g4f/8MH//HB//5g//+YP//sD///A///wP//8D///A///wP//2D//5g//+cH//jB//w4f/8HD/+A8//AH3/gAf/gAA/AAA==",
"LastQuarterNorth" : "AD8AAH/4AH+PgD/g8B/4Dg/+AcP/gDH/4A5/+AGf/gBv/4AP/+AD//gA//4AP/+AD//gA3/4AZ/+AGf/gDj/4Aw/+AcH/gOA/4PAH+PgAf/gAA/AAA==", "LastQuarterNorth" : "AD8AAH/4AH+PgD/g8B/4Dg/+AcP/gDH/4A5/+AGf/gBv/4AP/+AD//gA//4AP/+AD//gA3/4AZ/+AGf/gDj/4Aw/+AcH/gOA/4PAH+PgAf/gAA/AAA==",
"LastQuarterSouth" : "AD8AAH/4AHx/gDwf8BwH/g4B/8MAf/HAH/5gB/+YAf/sAH//AB//wAf/8AH//AB//wAf/2AH/5gB/+cAf/jAH/w4B/8HAf+A8H/AHx/gAf/gAA/AAA==", "LastQuarterSouth" : "AD8AAH/4AHx/gDwf8BwH/g4B/8MAf/HAH/5gB/+YAf/sAH//AB//wAf/8AH//AB//wAf/2AH/5gB/+cAf/jAH/w4B/8HAf+A8H/AHx/gAf/gAA/AAA==",
"WaningCrescentNorth" : "AD8AAH/4AH8PgD+A8B/ADg/gAcP4ADH+AA5/AAGfwABv8AAP/AAD/wAA/8AAP/AAD/wAA38AAZ/AAGf4ADj+AAw/gAcH8AOA/gPAH8PgAf/gAA/AAA==", "WaningCrescentNorth" : "AD8AAH/4AH8PgD+A8B/ADg/gAcP4ADH+AA5/AAGfwABv8AAP/AAD/wAA/8AAP/AAD/wAA38AAZ/AAGf4ADj+AAw/gAcH8AOA/gPAH8PgAf/gAA/AAA==",
"WaxingCrescentSouth" : "AD8AAH/4AH8PgD+A8B/ADg/gAcP4ADH+AA5/AAGfwABv8AAP/AAD/wAA/8AAP/AAD/wAA38AAZ/AAGf4ADj+AAw/gAcH8AOA/gPAH8PgAf/gAA/AAA==" "WaxingCrescentSouth" : "AD8AAH/4AH8PgD+A8B/ADg/gAcP4ADH+AA5/AAGfwABv8AAP/AAD/wAA/8AAP/AAD/wAA38AAZ/AAGf4ADj+AAw/gAcH8AOA/gPAH8PgAf/gAA/AAA=="
}; };
return { return {
width : 26, height : 26, bpp : 1, width : 26, height : 26, bpp : 1,
transparent : 0, transparent : 0,
buffer : E.toArrayBuffer(atob(data[i])) buffer : E.toArrayBuffer(atob(data[i]))
}; };
} }
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
//coordinates (will get from GPS later on real device) // date/time constants and conversions
var lat = 52.96236, function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
lon = 7.62571; function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
function toDays(date) { return toJulian(date) - J2000; }
var PI = Math.PI,
sin = Math.sin, // general calculations for position
cos = Math.cos, var e = rad * 23.4397; // obliquity of the Earth
tan = Math.tan, function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
asin = Math.asin, function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
atan = Math.atan2, function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
acos = Math.acos, function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
rad = PI / 180; function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas if (h < 0) // the following formula works for positive altitudes only.
// date/time constants and conversions h = 0; // if h = -0.08901179 a div/0 would occur.
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588, // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
J2000 = 2451545; // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } }
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
function toDays(date) { return toJulian(date) - J2000; } // general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
// general calculations for position function eclipticLongitude(M) {
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } P = rad * 102.9372; // perihelion of the Earth
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } return M + C + P + PI;
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only. function sunCoords(d) {
h = 0; // if h = -0.08901179 a div/0 would occur. var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. return {
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: dec: declination(L, 0),
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); ra: rightAscension(L, 0)
} };
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
// adds a custom time to the times config
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center SunCalc.addTime = function (angle, riseName, setName) {
P = rad * 102.9372; // perihelion of the Earth times.push([angle, riseName, setName]);
};
return M + C + P + PI;
} // moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
function sunCoords(d) { var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
var M = solarMeanAnomaly(d), F = rad * (93.272 + 13.229350 * d), // mean distance
L = eclipticLongitude(M); l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
return { dt = 385001 - 20905 * cos(M); // distance to the moon in km
dec: declination(L, 0),
ra: rightAscension(L, 0) return {
}; ra: rightAscension(l, b),
} dec: declination(l, b),
dist: dt
var SunCalc = {}; };
}
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) { SunCalc.getMoonPosition = function (date, lat, lng) {
times.push([angle, riseName, setName]);
}; var lw = rad * -lng,
phi = rad * lat,
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas d = toDays(date),
function moonCoords(d) { // geocentric ecliptic coordinates of the moon c = moonCoords(d),
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude H = siderealTime(d, lw) - c.ra,
M = rad * (134.963 + 13.064993 * d), // mean anomaly h = altitude(H, phi, c.dec),
F = rad * (93.272 + 13.229350 * d), // mean distance // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
l = L + rad * 6.289 * sin(M), // longitude pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
b = rad * 5.128 * sin(F), // latitude h = h + astroRefraction(h); // altitude correction for refraction
dt = 385001 - 20905 * cos(M); // distance to the moon in km return {
azimuth: azimuth(H, phi, c.dec),
return { altitude: h,
ra: rightAscension(l, b), distance: c.dist,
dec: declination(l, b), parallacticAngle: pa
dist: dt };
}; };
}
// calculations for illumination parameters of the moon,
SunCalc.getMoonPosition = function (date, lat, lng) { // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
var lw = rad * -lng, SunCalc.getMoonIllumination = function (date) {
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
SunCalc.getMoonIllumination = function (date) {
var year = date.getFullYear(); var year = date.getFullYear();
var month = date.getMonth(); var month = date.getMonth();
var day = date.getDate(); var day = date.getDate();
var Moon = { var Moon = {
phases: ['new', 'waxing-crescent', 'first-quarter', 'waxing-gibbous', 'full', 'waning-gibbous', 'last-quarter', 'waning-crescent'], phases: ['new', 'waxing-crescent', 'first-quarter', 'waxing-gibbous', 'full', 'waning-gibbous', 'last-quarter', 'waning-crescent'],
phase: function (year, month, day) { phase: function (year, month, day) {
let c = 0; let c = 0;
let e = 0; let e = 0;
let jd = 0; let jd = 0;
let b = 0; let b = 0;
if (month < 3) { if (month < 3) {
year--; year--;
month += 12; month += 12;
} }
++month; ++month;
c = 365.25 * year; c = 365.25 * year;
e = 30.6 * month; e = 30.6 * month;
jd = c + e + day - 694039.09; // jd is total days elapsed jd = c + e + day - 694039.09; // jd is total days elapsed
jd /= 29.5305882; // divide by the moon cycle jd /= 29.5305882; // divide by the moon cycle
b = parseInt(jd); // int(jd) -> b, take integer part of jd b = parseInt(jd); // int(jd) -> b, take integer part of jd
jd -= b; // subtract integer part to leave fractional part of original jd jd -= b; // subtract integer part to leave fractional part of original jd
b = Math.round(jd * 8); // scale fraction from 0-8 and round b = Math.round(jd * 8); // scale fraction from 0-8 and round
if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0 if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0
//print ({phase: b, name: Moon.phases[b]}); return {phase: b, name: Moon.phases[b]};
return {phase: b, name: Moon.phases[b]}; }
};
return (Moon.phase(year, month, day));
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = new Date(date);
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
} }
}; var result = {};
return (Moon.phase(year, month, day)); if (rise) result.rise = hoursLater(t, rise);
}; if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
function hoursLater(date, h) { return result;
return new Date(date.valueOf() + h * dayMs / 24); };
}
function getMPhaseComp (offset) {
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = new Date(date);
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};
function getMPhaseComp (offset) {
var date = new Date(); var date = new Date();
date.setDate(date.getDate() + offset); date.setDate(date.getDate() + offset);
var dd = String(date.getDate()); var dd = String(date.getDate());
@ -222,9 +219,9 @@ function getImg(i) {
var yyyy = date.getFullYear(); var yyyy = date.getFullYear();
var phase = SunCalc.getMoonIllumination(date); var phase = SunCalc.getMoonIllumination(date);
return dd + "." + mm + "." + yyyy + ": "+ phase.name; return dd + "." + mm + "." + yyyy + ": "+ phase.name;
} }
function getMPhaseSim (offset) { function getMPhaseSim (offset) {
var date = new Date(); var date = new Date();
date.setDate(date.getDate() + offset); date.setDate(date.getDate() + offset);
var dd = String(date.getDate()); var dd = String(date.getDate());
@ -234,63 +231,123 @@ function getImg(i) {
var yyyy = date.getFullYear(); var yyyy = date.getFullYear();
var phase = SunCalc.getMoonIllumination(date); var phase = SunCalc.getMoonIllumination(date);
return phase.name; return phase.name;
} }
function drawMoonPhase(offset, x, y){ function drawMoonPhase(offset, x, y){
if (lat >= 0 && lat <= 90){ //Northern hemisphere if (coords.lat >= 0 && coords.lat <= 90){ //Northern hemisphere
if (getMPhaseSim(offset) == "new") {g.drawImage(getImg("NewMoon"), x, y);} if (getMPhaseSim(offset) == "new") {g.drawImage(getImg("NewMoon"), x, y);}
if (getMPhaseSim(offset) == "waxing-crescent") {g.drawImage(getImg("WaxingCrescentNorth"), x, y);} if (getMPhaseSim(offset) == "waxing-crescent") {g.drawImage(getImg("WaxingCrescentNorth"), x, y);}
if (getMPhaseSim(offset) == "first-quarter") {g.drawImage(getImg("FirstQuarterNorth"), x, y);} if (getMPhaseSim(offset) == "first-quarter") {g.drawImage(getImg("FirstQuarterNorth"), x, y);}
if (getMPhaseSim(offset) == "waxing-gibbous") {g.drawImage(getImg("WaxingGibbousNorth"), x, y);} if (getMPhaseSim(offset) == "waxing-gibbous") {g.drawImage(getImg("WaxingGibbousNorth"), x, y);}
if (getMPhaseSim(offset) == "full") {g.drawImage(getImg("FullMoon"), x, y);} if (getMPhaseSim(offset) == "full") {g.drawImage(getImg("FullMoon"), x, y);}
if (getMPhaseSim(offset) == "waning-gibbous") {g.drawImage(getImg("WaningGibbousNorth"), x, y);} if (getMPhaseSim(offset) == "waning-gibbous") {g.drawImage(getImg("WaningGibbousNorth"), x, y);}
if (getMPhaseSim(offset) == "last-quarter") {g.drawImage(getImg("LastQuarterNorth"), x, y);} if (getMPhaseSim(offset) == "last-quarter") {g.drawImage(getImg("LastQuarterNorth"), x, y);}
if (getMPhaseSim(offset) == "waning-crescent") {g.drawImage(getImg("WaningCrescentNorth"), x, y);} if (getMPhaseSim(offset) == "waning-crescent") {g.drawImage(getImg("WaningCrescentNorth"), x, y);}
} }
else { //Southern hemisphere else { //Southern hemisphere
if (getMPhaseSim(offset) == "new") {g.drawImage(getImg("NewMoon"), x, y);} if (getMPhaseSim(offset) == "new") {g.drawImage(getImg("NewMoon"), x, y);}
if (getMPhaseSim(offset) == "waxing-crescent") {g.drawImage(getImg("WaxingCrescentSouth"), x, y);} if (getMPhaseSim(offset) == "waxing-crescent") {g.drawImage(getImg("WaxingCrescentSouth"), x, y);}
if (getMPhaseSim(offset) == "first-quarter") {g.drawImage(getImg("FirstQuarterSouth"), x, y);} if (getMPhaseSim(offset) == "first-quarter") {g.drawImage(getImg("FirstQuarterSouth"), x, y);}
if (getMPhaseSim(offset) == "waxing-gibbous") {g.drawImage(getImg("WaxingGibbousSouth"), x, y);} if (getMPhaseSim(offset) == "waxing-gibbous") {g.drawImage(getImg("WaxingGibbousSouth"), x, y);}
if (getMPhaseSim(offset) == "full") {g.drawImage(getImg("FullMoon"), x, y);} if (getMPhaseSim(offset) == "full") {g.drawImage(getImg("FullMoon"), x, y);}
if (getMPhaseSim(offset) == "waning-gibbous") {g.drawImage(getImg("WaningGibbousSouth"), x, y);} if (getMPhaseSim(offset) == "waning-gibbous") {g.drawImage(getImg("WaningGibbousSouth"), x, y);}
if (getMPhaseSim(offset) == "last-quarter") {g.drawImage(getImg("LastQuarterSouth"), x, y);} if (getMPhaseSim(offset) == "last-quarter") {g.drawImage(getImg("LastQuarterSouth"), x, y);}
if (getMPhaseSim(offset) == "waning-crescent") {g.drawImage(getImg("WaningCrescentSouth"), x, y);} if (getMPhaseSim(offset) == "waning-crescent") {g.drawImage(getImg("WaningCrescentSouth"), x, y);}
} }
} }
function drawMoon(offset, x, y) { function drawMoon(offset, x, y) {
g.setFont("6x8"); g.setFont("6x8");
g.clear(); g.clear();
g.drawString("Key1: increase day, Key3:decrease day",10,10); g.drawString("Key1: day+, Key2:today, Key3:day-",x,y-30);
g.drawString(getMPhaseComp(offset),x,y-10); g.drawString("Last known coordinates: " + coords.lat.toFixed(4) + " " + coords.lon.toFixed(4), x, y-20);
drawMoonPhase(offset, x, y); g.drawString("Press BTN4 to update",x, y-10);
g.drawString(getMPhaseComp(offset),x,y+30);
drawMoonPhase(offset, x+35, y+40);
g.drawString(getMPhaseComp(offset+2),x,y+40); g.drawString(getMPhaseComp(offset+2),x,y+70);
drawMoonPhase(offset+2, x, y+50); drawMoonPhase(offset+2, x+35, y+80);
g.drawString(getMPhaseComp(offset+4),x,y+90); g.drawString(getMPhaseComp(offset+4),x,y+110);
drawMoonPhase(offset+4, x, y+100); drawMoonPhase(offset+4, x+35, y+120);
g.drawString(getMPhaseComp(offset+6),x,y+140); g.drawString(getMPhaseComp(offset+6),x,y+150);
drawMoonPhase(offset+6, x, y+150); drawMoonPhase(offset+6, x+35, y+160);
} }
function start() { //Write coordinates to file
function updateCoords() {
storage.write('coords.json', coords);
}
//set coordinates to default (city where I live)
function resetCoords() {
coords = {
lat : 52.96236,
lon : 7.62571,
};
updateCoords();
}
function getGpsFix() {
Bangle.on('GPS', function(fix) {
g.clear();
if (fix.fix == 1) {
var gpsString = "lat: " + fix.lat.toFixed(4) + " lon: " + fix.lon.toFixed(4);
coords.lat = fix.lat;
coords.lon = fix.lon;
updateCoords();
g.drawString("Got GPS fix and wrote coords to file",10,20);
g.drawString(gpsString,10,30);
g.drawString("Press BTN5 to return to app",10,40);
clearInterval(timer);
timer = undefined;
}
else {
g.drawString("Searching satellites...",10,20);
g.drawString("Press BTN5 to stop GPS",10, 30);
}
});
}
function start() {
var x = 10; var x = 10;
var y = 40; var y = 50;
var offsetMoon = 0; var offsetMoon = 0;
coords = storage.readJSON('coords.json',1); //read coordinates from file
if (!coords) resetCoords(); //if coordinates could not be read, reset them
drawMoon(offsetMoon, x, y); //offset, x, y drawMoon(offsetMoon, x, y); //offset, x, y
//define button functions //define button functions
setWatch(function() { setWatch(function() { //BTN1
offsetMoon++; //jump to next day offsetMoon++; //jump to next day
drawMoon(offsetMoon, x, y); //offset, x, y drawMoon(offsetMoon, x, y); //offset, x, y
}, BTN1, {edge:"rising", debounce:50, repeat:true}); }, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function() {
setWatch(function() { //BTN2
offsetMoon = 0; //jump to today
drawMoon(offsetMoon, x, y); //offset, x, y
}, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(function() { //BTN3
offsetMoon--; //jump to next day offsetMoon--; //jump to next day
drawMoon(offsetMoon, x, y); //offset, x, y drawMoon(offsetMoon, x, y); //offset, x, y
}, BTN3, {edge:"rising", debounce:50, repeat:true}); }, BTN3, {edge:"rising", debounce:50, repeat:true});
}
setWatch(function() { //BTN4
start(); g.drawString("--- Getting GPS signal ---",x, y);
Bangle.setGPSPower(1);
timer = setInterval(getGpsFix, 10000);
}, BTN4, {edge:"rising", debounce:50, repeat:true});
setWatch(function() { //BTN5
if (timer) clearInterval(timer);
timer = undefined;
Bangle.setGPSPower(0);
drawMoon(offsetMoon, x, y); //offset, x, y
}, BTN5, {edge:"rising", debounce:50, repeat:true});
}
start();

1
apps/rpgdice/ChangeLog Executable file
View File

@ -0,0 +1 @@
0.01: First release

1
apps/rpgdice/app-icon.js Executable file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgMJgMQgMZzOREaERiERzIACiIVOAIIUCz///ORgIXNIQIAC/4ABJJYsBCogYEAYMQiAWGLAoAJJI8JLAYoCAAgJBJIIwGBohxBJI4YBJIwOFC4w5EC4hdOzIgCyLFDC45hHAAZJDgJAKMQwyBSYSOBxIXGPRTdChOfxChHbpRhBC4P5GAgAOgEZFAKjIBAz1EC5YYJxAvBJ4IXQzGIxEQB4RbPCoOIwEAOKAsCC4QvCFiAXDdwwsMC5eebogVGAALWBC42f/AWLC4zwCUgIEBCxK+DE4bsFC5+f/IrBC4RzHXwkZzATEDgP/RZAXFz5ECf4oXMCYKICC6hABMAQXOgAXBLgLrHRxZfCC6sBCo4XLLwIXBbAgXRMIQAGRxgwChIXVgEQIYimOGAZ6CSgOJC6CrCC4TZBC6IwCC4QWQPQYXKOggAFPQOfC5AWKPQgXGCpR6FOwoWOPQQXDIZYwHC4QVRAAQXBBxgA="))

86
apps/rpgdice/app.js Executable file
View File

@ -0,0 +1,86 @@
const dice = [4, 6, 8, 10, 12, 20, 100];
const nFlips = 20;
const delay = 500;
let dieIndex = 1;
let face = 0;
let rolling = false;
let bgColor;
let fgColor;
function getDie() {
return dice[dieIndex];
}
function setColors(lastBounce) {
if (lastBounce) {
bgColor = 0xFFFF;
fgColor = 0x0000;
} else {
bgColor = 0x0000
fgColor = 0xFFFF;
}
}
function flipFace() {
while(true) {
let newFace = Math.floor(Math.random() * getDie()) + 1;
if (newFace !== face) {
face = newFace;
break;
}
}
}
function draw() {
g.setColor(bgColor);
g.fillRect(0, 0, g.getWidth(), g.getHeight());
g.setColor(fgColor);
g.setFontAlign(0, 0);
g.setFontVector(40);
g.drawString('d' + getDie(), 180, 30);
g.setFontVector(100);
g.drawString(face, 120, 120);
}
function roll(bounces) {
flipFace();
setColors(bounces === 0);
draw();
if (bounces > 0) {
setTimeout(() => roll(bounces - 1), delay / bounces);
} else {
rolling = false;
}
}
function startRolling() {
if (rolling) return;
rolling = true;
roll(nFlips);
}
function changeDie() {
if (rolling) return;
dieIndex = (dieIndex + 1) % dice.length;
draw();
}
Bangle.on('lcdPower',function(on) {
if (on) {
startRolling();
}
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
startRolling();
// Top button rolls the die, bottom button changes it
setWatch(startRolling, BTN1, {repeat:true});
setWatch(changeDie, BTN3, {repeat:true});
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});

BIN
apps/rpgdice/rpgdice.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -3,3 +3,5 @@
Lap log now scrolls into 2nd column after 18th entry, able to display 36 entries before going off screen Lap log now scrolls into 2nd column after 18th entry, able to display 36 entries before going off screen
0.03: Added ability to save Lap log as a date named JSON file into memory 0.03: Added ability to save Lap log as a date named JSON file into memory
Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running
0.04: Changed save file filename, add interface.html to allow laps to be loaded
0.05: Added widgets

View File

@ -0,0 +1,90 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="records"></div>
<script src="../../lib/interface.js"></script>
<script>
var domRecords = document.getElementById("records");
function getLapTimes() {
Util.showModal("Loading Lap Times...");
domRecords.innerHTML = "";
Puck.eval('require("Storage").list(/^swatch.*\.json/).map(fn=>({n:fn,d:require("Storage").readJSON(fn,1)}))',lapData=>{
var html = `<div class="container">
<div class="columns">\n`;
lapData.forEach((lap,lapIndex) => {
lap.date = lap.n.substr(7,16).replace("_"," ");
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">${lap.date}</div>
<div class="card-subtitle text-gray">${lap.d.length} Laps</div>
</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>lap</th>
<th>time</th>
</tr>
</thead>
<tbody>
${ lap.d.map((d,n)=>`<tr><td>${n+1}</td><td>${d}</td></tr>`).join("\n") }
</tbody>
</table>
</div>
<div class="card-footer">
<button class="btn btn-primary" idx="${lapIndex}" task="download">Download</button>
<button class="btn btn-default" idx="${lapIndex}" task="delete">Delete</button>
</div>
</div>
`;
});
if (lapData.length==0) {
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">No record</div>
<div class="card-subtitle text-gray">No laps recorded</div>
</div>
</div>
`;
}
html += `
</div>
</div>`;
domRecords.innerHTML = html;
Util.hideModal();
var buttons = domRecords.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var lapIndex = parseInt(button.getAttribute("idx"));
var lap = lapData[lapIndex];
if (!lap) throw new Error("Invalid index!");
var task = button.getAttribute("task");
if (task=="delete") {
Util.showModal("Deleting lap time...");
Util.eraseStorage(lap.n,()=>{
Util.hideModal();
getLapTimes();
});
}
if (task=="download") {
Util.saveCSV(lap.n.slice(0,-5)+".csv", lap.d.map((d,n)=>[n+1,d].join(",")).join("\n"));
}
});
}
})
}
function onInit() {
getLapTimes();
}
</script>
</body>
</html>

View File

@ -4,7 +4,6 @@ var started = false;
var timeY = 60; var timeY = 60;
var hsXPos = 0; var hsXPos = 0;
var lapTimes = []; var lapTimes = [];
var saveTimes = [];
var displayInterval; var displayInterval;
function timeToText(t) { function timeToText(t) {
@ -14,24 +13,26 @@ function timeToText(t) {
return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2); return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2);
} }
function updateLabels() { function updateLabels() {
g.clear(); g.reset(1);
g.clearRect(0,23,g.getWidth()-1,g.getHeight()-24);
g.setFont("6x8",2); g.setFont("6x8",2);
g.setFontAlign(0,0,3); g.setFontAlign(0,0,3);
g.drawString(started?"STOP":"GO",230,120); g.drawString(started?"STOP":"GO",230,120);
if (!started) g.drawString("RESET",230,190); if (!started) g.drawString("RESET",230,180);
g.drawString(started?"LAP":"SAVE",230,50); g.drawString(started?"LAP":"SAVE",230,50);
g.setFont("6x8",1); g.setFont("6x8",1);
g.setFontAlign(-1,-1); g.setFontAlign(-1,-1);
for (var i in lapTimes) { for (var i in lapTimes) {
if (i<18) if (i<16)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);} {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);}
else else if (i<32)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-18)*8);} {g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-16)*8);}
} }
drawsecs(); drawsecs();
} }
function drawsecs() { function drawsecs() {
var t = tCurrent-tStart; var t = tCurrent-tStart;
g.reset(1);
g.setFont("Vector",48); g.setFont("Vector",48);
g.setFontAlign(0,0); g.setFontAlign(0,0);
var secs = Math.floor(t/1000)%60; var secs = Math.floor(t/1000)%60;
@ -51,10 +52,8 @@ function drawms() {
g.clearRect(hsXPos,timeY,220,timeY+20); g.clearRect(hsXPos,timeY,220,timeY+20);
g.drawString("."+("0"+hs).substr(-2),hsXPos,timeY+10); g.drawString("."+("0"+hs).substr(-2),hsXPos,timeY+10);
} }
function saveconvert() { function getLapTimesArray() {
for (var v in lapTimes){ return lapTimes.map(timeToText).reverse();
saveTimes[v]=v+1+"-"+timeToText(lapTimes[(lapTimes.length-1)-v]);
}
} }
setWatch(function() { // Start/stop setWatch(function() { // Start/stop
@ -80,16 +79,21 @@ setWatch(function() { // Start/stop
}, BTN2, {repeat:true}); }, BTN2, {repeat:true});
setWatch(function() { // Lap setWatch(function() { // Lap
Bangle.beep(); Bangle.beep();
if (started) tCurrent = Date.now(); if (started) {
lapTimes.unshift(tCurrent-tStart); tCurrent = Date.now();
tStart = tCurrent; lapTimes.unshift(tCurrent-tStart);
if (!started) }
{ tStart = tCurrent;
var timenow= Date(); if (!started) { // save
saveconvert(); var timenow= Date();
require("Storage").writeJSON("StpWch-"+timenow.toString(), saveTimes); var filename = "swatch-"+(new Date()).toISOString().substr(0,16).replace("T","_")+".json";
// this maxes out the 28 char maximum
require("Storage").writeJSON(filename, getLapTimesArray());
E.showMessage("Laps Saved","Stopwatch");
setTimeout(updateLabels, 1000);
} else {
updateLabels();
} }
updateLabels();
}, BTN1, {repeat:true}); }, BTN1, {repeat:true});
setWatch(function() { // Reset setWatch(function() { // Reset
if (!started) { if (!started) {
@ -101,3 +105,5 @@ setWatch(function() { // Reset
}, BTN3, {repeat:true}); }, BTN3, {repeat:true});
updateLabels(); updateLabels();
Bangle.loadWidgets();
Bangle.drawWidgets();

2
apps/toucher/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Add swipe support and doucle tap to run application

130
apps/toucher/app.js Normal file
View File

@ -0,0 +1,130 @@
g.clear();
const Storage = require("Storage");
function getApps(){
return Storage.list(/\.info$/).filter(app => app.endsWith('.info')).map(app => Storage.readJSON(app,1) || { name: "DEAD: "+app.substr(1) })
.filter(app=>app.type=="app" || app.type=="clock" || !app.type)
.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first
if (a.name<b.name) return -1;
if (a.name>b.name) return 1;
return 0;
});
}
const selected = 0;
const apps = getApps();
function prev(){
if (selected>=0) {
selected--;
}
drawMenu();
}
function next() {
if (selected+1<apps.length) {
selected++;
}
drawMenu();
}
function run() {
if(selected < 0) return load();
if (!apps[selected].src) return;
if (Storage.read(apps[selected].src)===undefined) {
E.showMessage("App Source\nNot found");
setTimeout(drawMenu, 2000);
} else {
E.showMessage("Loading...");
load(apps[selected].src);
}
}
function getCurrentApp(){
return apps[selected];
}
function getNextApp(){
return apps[selected+1];
}
function drawFallbackIcon(){
g.setColor(1,1,1);
g.fillRect(72, 40, 168, 136);
g.setColor(0,0,0);
g.setFont('6x8', 8);
g.drawString('?', 124, 88);
}
function drawArrow(x, y, size, dir){
size = size || 10;
dir = dir || 1;
g.moveTo(x, y).lineTo(x+(size*dir), y-size).lineTo(x+(size*dir),y+size).lineTo(x, y);
}
function drawMenu(){
if(selected < 0){
g.clear();
g.setFontAlign(0,0);
g.setFont('6x8', 2);
g.drawString('Back', 120, 120);
drawArrow(220, 120, 10, -1);
return;
}
const app = getCurrentApp();
g.clear();
g.setFontAlign(0,0);
g.setFont('6x8', 2);
if(!app) return g.drawString('???', 120, 120);
g.drawString(app.name, 120, 160);
if (app.icon) icon = Storage.read(app.icon);
if (icon) try {g.drawImage(icon, 120-48, 40, { scale: 2 });} catch(e){ drawFallbackIcon(); }
else drawFallbackIcon();
g.setFont('6x8', 1);
const type = app.type ? app.type : 'App';
const version = app.version ? app.version : '0.00';
const info = type+' v'+version;
g.setFontAlign(-1,1);
g.drawString(info, 20, 220);
const count = (selected+1)+'/'+apps.length;
g.setFontAlign(1,1);
g.drawString(count, 220, 220);
drawArrow(20, 120, 10, 1);
if(getNextApp()) drawArrow(220, 120, 10, -1);
}
drawMenu();
// Physical buttons
setWatch(prev, BTN1, {repeat:true});
setWatch(next, BTN3, {repeat:true});
setWatch(run, BTN2, {repeat:true,edge:"falling"});
// Screen event
Bangle.on('touch', function(button){
switch(button){
case 1:
prev();
break;
case 2:
next();
break;
case 3:
run();
break;
}
});
Bangle.on('swipe', dir => {
if(dir == 1) prev();
else next();
});

BIN
apps/toucher/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

1
apps/widmp/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

33
apps/widmp/widget.js Normal file
View File

@ -0,0 +1,33 @@
/* jshint esversion: 6 */
(() => {
const BLACK = 0, MOON = 0x41f, MC = 29.5305882, NM = 694039.09;
var r = 12, mx = 0, my = 0;
var moon = {
0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);},
1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);},
2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);},
3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);},
4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);},
5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);},
6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);},
7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r + r, my + r);},
8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}
};
function moonPhase(d) {
var tmp, month = d.getMonth(), year = d.getFullYear(), day = d.getDate();
if (month < 3) {year--; month += 12;}
tmp = ((365.25 * year + 30.6 * ++month + day - NM) / MC);
return Math.round(((tmp - (tmp | 0)) * 7)+1);
}
function draw() {
mx = this.x; my = this.y + 12;
moon[moonPhase(Date())]();
}
WIDGETS["widmoon"] = { area: "tr", width: 24, draw: draw };
})();

BIN
apps/widmp/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -138,6 +138,7 @@
<script src="https://www.puck-js.com/puck.js"></script> <script src="https://www.puck-js.com/puck.js"></script>
<script src="js/utils.js"></script> <script src="js/utils.js"></script>
<script src="js/ui.js"></script>
<script src="js/comms.js"></script> <script src="js/comms.js"></script>
<script src="js/appinfo.js"></script> <script src="js/appinfo.js"></script>
<script src="js/index.js"></script> <script src="js/index.js"></script>

View File

@ -9,14 +9,19 @@ reset : (opt) => new Promise((resolve,reject) => {
}); });
}), }),
uploadApp : (app,skipReset) => { uploadApp : (app,skipReset) => {
Progress.show({title:`Uploading ${app.name}`,sticky:true});
return AppInfo.getFiles(app, httpGet).then(fileContents => { return AppInfo.getFiles(app, httpGet).then(fileContents => {
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
console.log("uploadApp",fileContents.map(f=>f.name).join(", ")); console.log("uploadApp",fileContents.map(f=>f.name).join(", "));
var maxBytes = fileContents.reduce((b,f)=>b+f.content.length, 0)||1;
var currentBytes = 0;
// Upload each file one at a time // Upload each file one at a time
function doUploadFiles() { function doUploadFiles() {
// No files left - print 'reboot' message // No files left - print 'reboot' message
if (fileContents.length==0) { if (fileContents.length==0) {
Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
Progress.hide({sticky:true});
if (result===null) return reject(""); if (result===null) return reject("");
resolve(app); resolve(app);
}); });
@ -24,17 +29,27 @@ uploadApp : (app,skipReset) => {
} }
var f = fileContents.shift(); var f = fileContents.shift();
console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`); console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`);
Progress.show({
min:currentBytes / maxBytes,
max:(currentBytes+f.content.length) / maxBytes});
currentBytes += f.content.length;
// Chould check CRC here if needed instead of returning 'OK'... // Chould check CRC here if needed instead of returning 'OK'...
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) // E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
Puck.write(`\x10${f.cmd};Bluetooth.println("OK")\n`,(result) => { Puck.write(`\x10${f.cmd};Bluetooth.println("OK")\n`,(result) => {
if (!result || result.trim()!="OK") return reject("Unexpected response "+(result||"")); if (!result || result.trim()!="OK") {
Progress.hide({sticky:true});
return reject("Unexpected response "+(result||""));
}
doUploadFiles(); doUploadFiles();
}, true); // wait for a newline }, true); // wait for a newline
} }
// Start the upload // Start the upload
function doUpload() { function doUpload() {
Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => { Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => {
if (result===null) return reject(""); if (result===null) {
Progress.hide({sticky:true});
return reject("");
}
doUploadFiles(); doUploadFiles();
}); });
} }
@ -48,10 +63,15 @@ uploadApp : (app,skipReset) => {
}); });
}, },
getInstalledApps : () => { getInstalledApps : () => {
Progress.show({title:`Getting app list...`,sticky:true});
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
Puck.write("\x03",(result) => { Puck.write("\x03",(result) => {
if (result===null) return reject(""); if (result===null) {
Progress.hide({sticky:true});
return reject("");
}
Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => { Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => {
Progress.hide({sticky:true});
if (appList===null) return reject(err || ""); if (appList===null) return reject(err || "");
console.log("getInstalledApps", appList); console.log("getInstalledApps", appList);
resolve(appList); resolve(appList);
@ -60,6 +80,7 @@ getInstalledApps : () => {
}); });
}, },
removeApp : app => { // expects an app structure removeApp : app => { // expects an app structure
Progress.show({title:`Removing ${app.name}`,sticky:true});
var storage = [{name:app.id+".info"}].concat(app.storage); var storage = [{name:app.id+".info"}].concat(app.storage);
var cmds = storage.map(file=>{ var cmds = storage.map(file=>{
return `\x10require("Storage").erase(${toJS(file.name)});\n`; return `\x10require("Storage").erase(${toJS(file.name)});\n`;
@ -67,15 +88,21 @@ removeApp : app => { // expects an app structure
console.log("removeApp", cmds); console.log("removeApp", cmds);
return Comms.reset().then(new Promise((resolve,reject) => { return Comms.reset().then(new Promise((resolve,reject) => {
Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
Progress.hide({sticky:true});
if (result===null) return reject(""); if (result===null) return reject("");
resolve(); resolve();
}); });
})); })).catch(function(reason) {
Progress.hide({sticky:true});
return Promise.reject(reason);
});
}, },
removeAllApps : () => { removeAllApps : () => {
Progress.show({title:"Removing all apps",progess:"animate",sticky:true});
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
// Use write with newline here so we wait for it to finish // Use write with newline here so we wait for it to finish
Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => { Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => {
Progress.hide({sticky:true});
if (!result || result.trim()!="OK") return reject(err || ""); if (!result || result.trim()!="OK") return reject(err || "");
resolve(); resolve();
}, true /* wait for newline */); }, true /* wait for newline */);
@ -171,10 +198,10 @@ readStorageFile : (filename) => { // StorageFiles are different to normal storag
fileContent = fileContent.substr(newLineIdx+1); fileContent = fileContent.substr(newLineIdx+1);
} }
} else { } else {
showProgress(undefined,100*fileContent.length / (fileSize||1000000)); Progress.show({percent:100*fileContent.length / (fileSize||1000000)});
} }
if (finished) { if (finished) {
hideProgress(); Progress.hide();
connection.received = ""; connection.received = "";
connection.cb = undefined; connection.cb = undefined;
resolve(fileContent); resolve(fileContent);
@ -188,7 +215,7 @@ readStorageFile : (filename) => { // StorageFiles are different to normal storag
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); } while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
Bluetooth.print("\xFF"); Bluetooth.print("\xFF");
})()\n`,() => { })()\n`,() => {
showProgress(`Reading ${JSON.stringify(filename)}`,0); Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0});
console.log(`StorageFile read started...`); console.log(`StorageFile read started...`);
}); });
}); });

View File

@ -14,119 +14,7 @@ httpGet("apps.json").then(apps=>{
refreshFilter(); refreshFilter();
}); });
// Status
// =========================================== Top Navigation // =========================================== Top Navigation
function showToast(message, type) {
// toast-primary, toast-success, toast-warning or toast-error
var style = "toast-primary";
if (type=="success") style = "toast-success";
else if (type=="error") style = "toast-error";
else if (type!==undefined) console.log("showToast: unknown toast "+type);
var toastcontainer = document.getElementById("toastcontainer");
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`);
msgDiv.innerHTML = message;
toastcontainer.append(msgDiv);
setTimeout(function() {
msgDiv.remove();
}, 5000);
}
var progressToast; // the DOM element
var progressSticky; // showProgress(,,"sticky") don't remove until hideProgress("sticky")
var progressInterval; // the interval used if showProgress(..., "animate")
var progressPercent; // the current progress percentage
function showProgress(text, percent, sticky) {
if (sticky=="sticky")
progressSticky = true;
if (!progressToast) {
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = undefined;
}
if (percent == "animate") {
progressInterval = setInterval(function() {
progressPercent += 2;
if (progressPercent>100) progressPercent=0;
showProgress(undefined, progressPercent);
}, 100);
percent = 0;
}
progressPercent = percent;
var toastcontainer = document.getElementById("toastcontainer");
progressToast = htmlElement(`<div class="toast">
${text ? `<div>${text}</div>`:``}
<div class="bar bar-sm">
<div class="bar-item" id="progressToast" role="progressbar" style="width:${percent}%;" aria-valuenow="${percent}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>`);
toastcontainer.append(progressToast);
} else {
var pt=document.getElementById("progressToast");
pt.setAttribute("aria-valuenow",percent);
pt.style.width = percent+"%";
}
}
function hideProgress(sticky) {
if (progressSticky && sticky!="sticky")
return;
progressSticky = false;
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = undefined;
}
if (progressToast) progressToast.remove();
progressToast = undefined;
}
Puck.writeProgress = function(charsSent, charsTotal) {
if (charsSent===undefined) {
hideProgress();
return;
}
var percent = Math.round(charsSent*100/charsTotal);
showProgress(undefined, percent);
}
function showPrompt(title, text, buttons) {
if (!buttons) buttons={yes:1,no:1};
return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active">
<!--<a href="#close" class="modal-overlay" aria-label="Close"></a>-->
<div class="modal-container">
<div class="modal-header">
<a href="#close" class="btn btn-clear float-right" aria-label="Close"></a>
<div class="modal-title h5">${escapeHtml(title)}</div>
</div>
<div class="modal-body">
<div class="content">
${escapeHtml(text).replace(/\n/g,'<br/>')}
</div>
</div>
<div class="modal-footer">
<div class="modal-footer">
${buttons.yes?'<button class="btn btn-primary" isyes="1">Yes</button>':''}
${buttons.no?'<button class="btn" isyes="0">No</button>':''}
${buttons.ok?'<button class="btn" isyes="1">Ok</button>':''}
</div>
</div>
</div>
</div>`);
document.body.append(modal);
modal.querySelector("a[href='#close']").addEventListener("click",event => {
event.preventDefault();
reject("User cancelled");
modal.remove();
});
htmlToArray(modal.getElementsByTagName("button")).forEach(button => {
button.addEventListener("click",event => {
event.preventDefault();
var isYes = event.target.getAttribute("isyes")=="1";
if (isYes) resolve();
else reject("User cancelled");
modal.remove();
});
});
});
}
function showChangeLog(appid) { function showChangeLog(appid) {
var app = appNameToApp(appid); var app = appNameToApp(appid);
function show(contents) { function show(contents) {
@ -170,12 +58,11 @@ function handleCustomApp(appTemplate) {
Object.keys(appFiles).forEach(k => app[k] = appFiles[k]); Object.keys(appFiles).forEach(k => app[k] = appFiles[k]);
console.log("Received custom app", app); console.log("Received custom app", app);
modal.remove(); modal.remove();
showProgress(`Uploading ${app.name}`,undefined,"sticky");
Comms.uploadApp(app).then(()=>{ Comms.uploadApp(app).then(()=>{
hideProgress("sticky"); Progress.hide({sticky:true});
resolve(); resolve();
}).catch(e => { }).catch(e => {
hideProgress("sticky"); Progress.hide({sticky:true});
reject(e); reject(e);
}); });
}, false); }, false);
@ -334,9 +221,8 @@ function refreshLibrary() {
// upload // upload
icon.classList.remove("icon-upload"); icon.classList.remove("icon-upload");
icon.classList.add("loading"); icon.classList.add("loading");
showProgress(`Uploading ${app.name}`,undefined,"sticky");
Comms.uploadApp(app).then((appJSON) => { Comms.uploadApp(app).then((appJSON) => {
hideProgress("sticky"); Progress.hide({sticky:true});
if (appJSON) appsInstalled.push(appJSON); if (appJSON) appsInstalled.push(appJSON);
showToast(app.name+" Uploaded!", "success"); showToast(app.name+" Uploaded!", "success");
icon.classList.remove("loading"); icon.classList.remove("loading");
@ -344,7 +230,7 @@ function refreshLibrary() {
refreshMyApps(); refreshMyApps();
refreshLibrary(); refreshLibrary();
}).catch(err => { }).catch(err => {
hideProgress("sticky"); Progress.hide({sticky:true});
showToast("Upload failed, "+err, "error"); showToast("Upload failed, "+err, "error");
icon.classList.remove("loading"); icon.classList.remove("loading");
icon.classList.add("icon-upload"); icon.classList.add("icon-upload");
@ -403,19 +289,16 @@ function customApp(app) {
function updateApp(app) { function updateApp(app) {
if (app.custom) return customApp(app); if (app.custom) return customApp(app);
showProgress(`Upgrading ${app.name}`,undefined,"sticky");
return Comms.removeApp(app).then(()=>{ return Comms.removeApp(app).then(()=>{
showToast(app.name+" removed successfully. Updating...",); showToast(app.name+" removed successfully. Updating...",);
appsInstalled = appsInstalled.filter(a=>a.id!=app.id); appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
return Comms.uploadApp(app); return Comms.uploadApp(app);
}).then((appJSON) => { }).then((appJSON) => {
hideProgress("sticky");
if (appJSON) appsInstalled.push(appJSON); if (appJSON) appsInstalled.push(appJSON);
showToast(app.name+" Updated!", "success"); showToast(app.name+" Updated!", "success");
refreshMyApps(); refreshMyApps();
refreshLibrary(); refreshLibrary();
}, err=>{ }, err=>{
hideProgress("sticky");
showToast(app.name+" update failed, "+err,"error"); showToast(app.name+" update failed, "+err,"error");
refreshMyApps(); refreshMyApps();
refreshLibrary(); refreshLibrary();
@ -488,18 +371,15 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
function getInstalledApps() { function getInstalledApps() {
showLoadingIndicator("myappscontainer"); showLoadingIndicator("myappscontainer");
showProgress(`Getting app list...`,undefined,"sticky");
// Get apps and files // Get apps and files
return Comms.getInstalledApps() return Comms.getInstalledApps()
.then(appJSON => { .then(appJSON => {
hideProgress("sticky");
appsInstalled = appJSON; appsInstalled = appJSON;
refreshMyApps(); refreshMyApps();
refreshLibrary(); refreshLibrary();
}) })
.then(() => handleConnectionChange(true)) .then(() => handleConnectionChange(true))
.catch(err=>{ .catch(err=>{
hideProgress("sticky");
return Promise.reject(); return Promise.reject();
}); });
} }
@ -555,15 +435,14 @@ document.getElementById("settime").addEventListener("click",event=>{
}); });
document.getElementById("removeall").addEventListener("click",event=>{ document.getElementById("removeall").addEventListener("click",event=>{
showPrompt("Remove All","Really remove all apps?").then(() => { showPrompt("Remove All","Really remove all apps?").then(() => {
showProgress("Removing all apps","animate", "sticky");
return Comms.removeAllApps(); return Comms.removeAllApps();
}).then(()=>{ }).then(()=>{
hideProgress("sticky"); Progress.hide({sticky:true});
appsInstalled = []; appsInstalled = [];
showToast("All apps removed","success"); showToast("All apps removed","success");
return getInstalledApps(); return getInstalledApps();
}).catch(err=>{ }).catch(err=>{
hideProgress("sticky"); Progress.hide({sticky:true});
showToast("App removal failed, "+err,"error"); showToast("App removal failed, "+err,"error");
}); });
}); });
@ -578,24 +457,23 @@ document.getElementById("installdefault").addEventListener("click",event=>{
appCount = defaultApps.length; appCount = defaultApps.length;
return showPrompt("Install Defaults","Remove everything and install default apps?"); return showPrompt("Install Defaults","Remove everything and install default apps?");
}).then(() => { }).then(() => {
showProgress("Removing all apps","animate", "sticky");
return Comms.removeAllApps(); return Comms.removeAllApps();
}).then(()=>{ }).then(()=>{
hideProgress("sticky"); Progress.hide({sticky:true});
appsInstalled = []; appsInstalled = [];
showToast(`Existing apps removed. Installing ${appCount} apps...`); showToast(`Existing apps removed. Installing ${appCount} apps...`);
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
function upload() { function upload() {
var app = defaultApps.shift(); var app = defaultApps.shift();
if (app===undefined) return resolve(); if (app===undefined) return resolve();
showProgress(`${app.name} (${appCount-defaultApps.length}/${appCount})`,undefined,"sticky"); Progress.show({title:`${app.name} (${appCount-defaultApps.length}/${appCount})`,sticky:true});
Comms.uploadApp(app,"skip_reset").then((appJSON) => { Comms.uploadApp(app,"skip_reset").then((appJSON) => {
hideProgress("sticky"); Progress.hide({sticky:true});
if (appJSON) appsInstalled.push(appJSON); if (appJSON) appsInstalled.push(appJSON);
showToast(`(${appCount-defaultApps.length}/${appCount}) ${app.name} Uploaded`); showToast(`(${appCount-defaultApps.length}/${appCount}) ${app.name} Uploaded`);
upload(); upload();
}).catch(function() { }).catch(function() {
hideProgress("sticky"); Progress.hide({sticky:true});
reject() reject()
}); });
} }
@ -607,7 +485,7 @@ document.getElementById("installdefault").addEventListener("click",event=>{
showToast("Default apps successfully installed!","success"); showToast("Default apps successfully installed!","success");
return getInstalledApps(); return getInstalledApps();
}).catch(err=>{ }).catch(err=>{
hideProgress("sticky"); Progress.hide({sticky:true});
showToast("App Install failed, "+err,"error"); showToast("App Install failed, "+err,"error");
}); });
}); });

140
js/ui.js Normal file
View File

@ -0,0 +1,140 @@
// General UI tools (progress bar, toast, prompt)
/// Handle progress bars
var Progress = {
domElement : null, // the DOM element
sticky : false, // Progress.show({..., sticky:true}) don't remove until Progress.hide({sticky:true})
interval : undefined, // the interval used if Progress.show({progress:"animate"})
percent : undefined, // the current progress percentage
min : 0, // scaling for percentage
max : 1, // scaling for percentage
/* Show a Progress message
Progress.show({
sticky : bool // keep showing text even when Progress.hide is called (unless Progress.hide({sticky:true}))
percent : number | "animate"
min : // minimum scale for percentage (default 0)
max : // maximum scale for percentage (default 1)
}) */
show : function(options) {
options = options||{};
var text = options.title;
if (options.sticky) Progress.sticky = true;
if (options.min!==undefined) Progress.min = options.min;
if (options.max!==undefined) Progress.max = options.max;
var percent = options.percent;
if (percent!==undefined)
percent = Progress.min*100 + (Progress.max-Progress.min)*percent;
if (!Progress.domElement) {
if (Progress.interval) {
clearInterval(Progress.interval);
Progress.interval = undefined;
}
if (percent == "animate") {
Progress.interval = setInterval(function() {
Progress.percent += 2;
if (Progress.percent>100) Progress.percent=0;
Progress.show({percent:Progress.percent});
}, 100);
percent = 0;
}
var toastcontainer = document.getElementById("toastcontainer");
Progress.domElement = htmlElement(`<div class="toast">
${text ? `<div>${text}</div>`:``}
<div class="bar bar-sm">
<div class="bar-item" id="Progress.domElement" role="progressbar" style="width:${percent}%;" aria-valuenow="${percent}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>`);
toastcontainer.append(Progress.domElement);
} else {
var pt=document.getElementById("Progress.domElement");
pt.setAttribute("aria-valuenow",percent);
pt.style.width = percent+"%";
}
},
// Progress.hide({sticky:true}) undoes Progress.show({title:"title", sticky:true})
hide : function(options) {
options = options||{};
if (Progress.sticky && !options.sticky)
return;
Progress.sticky = false;
Progress.min = 0;
Progress.max = 1;
if (Progress.interval) {
clearInterval(Progress.interval);
Progress.interval = undefined;
}
if (Progress.domElement) Progress.domElement.remove();
Progress.domElement = undefined;
}
};
/// Add progress handler so we get nice uploads
Puck.writeProgress = function(charsSent, charsTotal) {
if (charsSent===undefined) {
Progress.hide();
return;
}
var percent = Math.round(charsSent*100/charsTotal);
Progress.show({percent: percent});
}
/// Show a 'toast' message for status
function showToast(message, type) {
// toast-primary, toast-success, toast-warning or toast-error
var style = "toast-primary";
if (type=="success") style = "toast-success";
else if (type=="error") style = "toast-error";
else if (type!==undefined) console.log("showToast: unknown toast "+type);
var toastcontainer = document.getElementById("toastcontainer");
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`);
msgDiv.innerHTML = message;
toastcontainer.append(msgDiv);
setTimeout(function() {
msgDiv.remove();
}, 5000);
}
/// Show a yes/no prompt
function showPrompt(title, text, buttons) {
if (!buttons) buttons={yes:1,no:1};
return new Promise((resolve,reject) => {
var modal = htmlElement(`<div class="modal active">
<!--<a href="#close" class="modal-overlay" aria-label="Close"></a>-->
<div class="modal-container">
<div class="modal-header">
<a href="#close" class="btn btn-clear float-right" aria-label="Close"></a>
<div class="modal-title h5">${escapeHtml(title)}</div>
</div>
<div class="modal-body">
<div class="content">
${escapeHtml(text).replace(/\n/g,'<br/>')}
</div>
</div>
<div class="modal-footer">
<div class="modal-footer">
${buttons.yes?'<button class="btn btn-primary" isyes="1">Yes</button>':''}
${buttons.no?'<button class="btn" isyes="0">No</button>':''}
${buttons.ok?'<button class="btn" isyes="1">Ok</button>':''}
</div>
</div>
</div>
</div>`);
document.body.append(modal);
modal.querySelector("a[href='#close']").addEventListener("click",event => {
event.preventDefault();
reject("User cancelled");
modal.remove();
});
htmlToArray(modal.getElementsByTagName("button")).forEach(button => {
button.addEventListener("click",event => {
event.preventDefault();
var isYes = event.target.getAttribute("isyes")=="1";
if (isYes) resolve();
else reject("User cancelled");
modal.remove();
});
});
});
}

View File

@ -39,7 +39,10 @@ var Util = {
window.postMessage({type:"readstoragefile",data:filename,id:__id}); window.postMessage({type:"readstoragefile",data:filename,id:__id});
}, },
eraseStorageFile : function(filename,callback) { eraseStorageFile : function(filename,callback) {
Puck.write(`\x10require("Storage").open(${JSON.stringify(filename)}","r").erase()\n`,callback); Puck.write(`\x10require("Storage").open(${JSON.stringify(filename)},"r").erase()\n`,callback);
},
eraseStorage : function(filename,callback) {
Puck.write(`\x10require("Storage").erase(${JSON.stringify(filename)})\n`,callback);
}, },
showModal : function(title) { showModal : function(title) {
if (!Util.domModal) { if (!Util.domModal) {
@ -66,6 +69,19 @@ var Util = {
hideModal : function() { hideModal : function() {
if (!Util.domModal) return; if (!Util.domModal) return;
Util.domModal.classList.remove("active"); Util.domModal.classList.remove("active");
},
saveCSV : function(filename, csvData) {
var a = document.createElement("a"),
file = new Blob([csvData], {type: "Comma-separated value file"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = filename+".csv";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} }
}; };
window.addEventListener("message", function(event) { window.addEventListener("message", function(event) {