Merge branch 'master' into cli-clock

pull/172/head
Gordon Williams 2020-04-01 08:30:38 +01:00 committed by GitHub
commit a98973b3c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1495 additions and 274 deletions

View File

@ -30,8 +30,9 @@ easily distinguish between file types, we use the following:
* `stuff.info` is JSON that describes an app - this is auto-generated by the App Loader
* `stuff.img` is an image
* `stuff.app.js` is JS code
* `stuff.app.js` is JS code for applications
* `stuff.wid.js` is JS code for widgets
* `stuff.boot.js` is JS code that automatically gets run at boot time
* `stuff.json` is used for JSON settings for an app
## Developing your own app

118
apps.json
View File

@ -2,7 +2,7 @@
{ "id": "boot",
"name": "Bootloader",
"icon": "bootloader.png",
"version":"0.11",
"version":"0.13",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"tags": "tool,system",
"type":"bootloader",
@ -12,6 +12,31 @@
],
"sortorder" : -10
},
{ "id": "moonphase",
"name": "Moonphase",
"icon": "app.png",
"version":"0.01",
"description": "Shows current moon phase. Currently only with fixed coordinates (northern hemisphere).",
"tags": "",
"allow_emulator":true,
"storage": [
{"name":"moonphase.app.js","url":"app.js"},
{"name":"moonphase.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "daysl",
"name": "Days left",
"icon": "app.png",
"version":"0.01",
"description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.",
"tags": "",
"allow_emulator":false,
"storage": [
{"name":"daysl.app.js","url":"app.js"},
{"name":"daysl.img","url":"app-icon.js","evaluate":true},
{"name":"daysl.wid.js","url":"widget.js"}
]
},
{ "id": "launch",
"name": "Default Launcher",
"shortName":"Launcher",
@ -106,11 +131,12 @@
"name": "Default Alarm",
"shortName":"Alarms",
"icon": "app.png",
"version":"0.04",
"version":"0.05",
"description": "Set and respond to alarms",
"tags": "tool,alarm,widget",
"storage": [
{"name":"alarm.app.js","url":"app.js"},
{"name":"alarm.boot.js","url":"boot.js"},
{"name":"alarm.js","url":"alarm.js"},
{"name":"alarm.json","content":"[]"},
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
@ -133,7 +159,7 @@
{ "id": "aclock",
"name": "Analog Clock",
"icon": "clock-analog.png",
"version":"0.02",
"version":"0.10",
"description": "An Analog Clock",
"tags": "clock",
"type":"clock",
@ -355,8 +381,8 @@
{ "id": "swatch",
"name": "Stopwatch",
"icon": "stopwatch.png",
"version":"0.01",
"description": "Simple stopwatch with Lap Time recording",
"version":"0.03",
"description": "Simple stopwatch with Lap Time logging to a JSON file",
"tags": "health",
"allow_emulator":true,
"storage": [
@ -508,6 +534,19 @@
{"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true}
]
},
{ "id": "dclock",
"name": "Dev Clock",
"icon": "clock-dev.png",
"version":"0.09",
"description": "A Digital Clock including timestamp (tst), beats(@), days in current month (dm) and days since new moon (l)",
"tags": "clock",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"dclock.app.js","url":"clock-dev.js"},
{"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true}
]
},
{ "id": "gesture",
"name": "Gesture Test",
"icon": "gesture.png",
@ -790,7 +829,7 @@
"id": "pipboy",
"name": "Pipboy",
"icon": "app.png",
"version": "0.01",
"version": "0.02",
"description": "Pipboy themed clock",
"tags": "clock",
"type":"clock",
@ -824,10 +863,25 @@
{"name":"widid.wid.js","url":"widget.js"}
]
},
{
"id": "grocery",
"name": "Grocery",
"icon": "grocery.png",
"version":"0.01",
"description": "Simple grocery list - Display a list of product and track if you already put them in your cart.",
"tags": "tool,outdoors",
"type": "app",
"custom":"grocery.html",
"storage": [
{"name":"grocery"},
{"name":"grocery.app.js"},
{"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
]
},
{ "id": "marioclock",
"name": "Mario Clock",
"icon": "marioclock.png",
"version":"0.01",
"version":"0.03",
"description": "Animated Mario clock, jumps to change the time!",
"tags": "clock,mario,retro",
"type": "clock",
@ -838,17 +892,41 @@
]
},
{ "id": "cliock",
"name": "Commandline-Clock",
"shortName":"CLI-Clock",
"icon": "app.png",
"version":"0.07",
"description": "Simple CLI-Styled Clock",
"tags": "clock,cli,command,bash,shell",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"cliock.app.js","url":"app.js"},
{"name":"cliock.img","url":"app-icon.js","evaluate":true}
]
}
"name": "Commandline-Clock",
"shortName":"CLI-Clock",
"icon": "app.png",
"version":"0.07",
"description": "Simple CLI-Styled Clock",
"tags": "clock,cli,command,bash,shell",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"cliock.app.js","url":"app.js"},
{"name":"cliock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "widver",
"name": "Firmware Version Widget",
"icon": "widget.png",
"version":"0.01",
"description": "Display the version of the installed firmware in the top widget section.",
"tags": "widget,tool,system",
"type":"widget",
"storage": [
{"name":"widver.wid.js","url":"widget.js"}
]
},
{ "id": "barclock",
"name": "Bar Clock",
"icon": "clock-bar.png",
"version":"0.02",
"description": "A simple digital clock showing seconds as a bar",
"tags": "clock",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"barclock.app.js","url":"clock-bar.js"},
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
]
}
]

View File

@ -1 +1,7 @@
0.02: Modified for use with new bootloader and firmware
0.03: add hour ticks, remove timers
0.04: add day-date display
0.07: make date and face bigger
0.08: make dots bigger and date more readable
0.09: center date, remove box around it, internal refactor to remove redundant code.
0.10: remove debug, refactor seconds to show elapsed secs each time app is displayed

View File

@ -1,94 +1,146 @@
const p = Math.PI/2;
const PRad = Math.PI/180;
let g;
let Bangle;
let intervalRefMin = null;
let intervalRefSec = null;
// http://forum.espruino.com/conversations/345155/#comment15172813
const locale = require('locale');
const p = Math.PI / 2;
const pRad = Math.PI / 180;
const faceWidth = 100; // watch face radius
let timer = null;
let currentDate = new Date();
const centerPx = g.getWidth() / 2;
let minuteDate = new Date();
let secondDate = new Date();
const seconds = (angle) => {
const a = angle * pRad;
const x = centerPx + Math.sin(a) * faceWidth;
const y = centerPx - Math.cos(a) * faceWidth;
function seconds(angle, r) {
const a = angle*PRad;
const x = 120+Math.sin(a)*r;
const y = 120-Math.cos(a)*r;
g.fillRect(x-1,y-1,x+1,y+1);
}
function hand(angle, r1,r2) {
const a = angle*PRad;
// if 15 degrees, make hour marker larger
const radius = (angle % 15) ? 2 : 4;
g.fillCircle(x, y, radius);
};
const hand = (angle, r1, r2) => {
const a = angle * pRad;
const r3 = 3;
g.fillPoly([
120+Math.sin(a)*r1,
120-Math.cos(a)*r1,
120+Math.sin(a+p)*r3,
120-Math.cos(a+p)*r3,
120+Math.sin(a)*r2,
120-Math.cos(a)*r2,
120+Math.sin(a-p)*r3,
120-Math.cos(a-p)*r3]);
}
function drawAll() {
g.fillPoly([
Math.round(centerPx + Math.sin(a) * r1),
Math.round(centerPx - Math.cos(a) * r1),
Math.round(centerPx + Math.sin(a + p) * r3),
Math.round(centerPx - Math.cos(a + p) * r3),
Math.round(centerPx + Math.sin(a) * r2),
Math.round(centerPx - Math.cos(a) * r2),
Math.round(centerPx + Math.sin(a - p) * r3),
Math.round(centerPx - Math.cos(a - p) * r3)
]);
};
const drawAll = () => {
g.clear();
secondDate = minuteDate = new Date();
currentDate = new Date();
// draw hands first
onMinute();
// draw seconds
g.setColor(0,0,0.6);
for (let i=0;i<60;i++)
seconds(360*i/60, 90);
const currentSec = currentDate.getSeconds();
// draw all secs
for (let i = 0; i < 60; i++) {
if (i > currentSec) {
g.setColor(0, 0, 0.6);
} else {
g.setColor(0.3, 0.3, 1);
}
seconds((360 * i) / 60);
}
onSecond();
}
};
function onSecond() {
g.setColor(0,0,0.6);
seconds(360*secondDate.getSeconds()/60, 90);
g.setColor(1,0,0);
secondDate = new Date();
seconds(360*secondDate.getSeconds()/60, 90);
g.setColor(1,1,1);
const resetSeconds = () => {
g.setColor(0, 0, 0.6);
for (let i = 0; i < 60; i++) {
seconds((360 * i) / 60);
}
};
}
const onSecond = () => {
g.setColor(0.3, 0.3, 1);
seconds((360 * currentDate.getSeconds()) / 60);
if (currentDate.getSeconds() === 59) {
resetSeconds();
onMinute();
}
g.setColor(1, 0.7, 0.2);
currentDate = new Date();
seconds((360 * currentDate.getSeconds()) / 60);
g.setColor(1, 1, 1);
};
function onMinute() {
g.setColor(0,0,0);
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -10, 50);
hand(360*minuteDate.getMinutes()/60, -10, 82);
minuteDate = new Date();
g.setColor(1,1,1);
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -10, 50);
hand(360*minuteDate.getMinutes()/60, -10, 82);
if(minuteDate.getHours() >= 0 && minuteDate.getMinutes() === 0) {
const drawDate = () => {
g.reset();
g.setColor(1, 0, 0);
g.setFont('6x8', 2);
const dayString = locale.dow(currentDate, true);
// pad left date
const dateString = (currentDate.getDate() < 10) ? '0' : '' + currentDate.getDate().toString();
const dateDisplay = `${dayString}-${dateString}`;
// console.log(`${dayString}|${dateString}`);
// center date
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
const t = centerPx + 37;
g.drawString(dateDisplay, l, t);
// console.log(l, t);
};
const onMinute = () => {
if (currentDate.getHours() === 0 && currentDate.getMinutes() === 0) {
g.clear();
resetSeconds();
}
// clear existing hands
g.setColor(0, 0, 0);
// Hour
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
// Minute
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
// get new date, then draw new hands
currentDate = new Date();
g.setColor(1, 0.9, 0.9);
// Hour
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
g.setColor(1, 1, 0.9);
// Minute
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
Bangle.buzz();
}
}
drawDate();
};
function clearTimers() {
if(intervalRefMin) {clearInterval(intervalRefMin);}
if(intervalRefSec) {clearInterval(intervalRefSec);}
}
const startTimers = () => {
timer = setInterval(onSecond, 1000);
};
function startTimers() {
minuteDate = new Date();
secondDate = new Date();
intervalRefSec = setInterval(onSecond,1000);
intervalRefMin = setInterval(onMinute,60*1000);
drawAll();
}
Bangle.on('lcdPower',function(on) {
Bangle.on('lcdPower', (on) => {
if (on) {
g.clear();
Bangle.drawWidgets();
// g.clear();
drawAll();
startTimers();
}else {
clearTimers();
Bangle.drawWidgets();
} else {
if (timer) {
clearInterval(timer);
}
}
});
g.clear();
resetSeconds();
startTimers();
drawAll();
Bangle.loadWidgets();
Bangle.drawWidgets();
drawAll();
startTimers();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

View File

@ -2,3 +2,4 @@
0.02: Fix issues with alarm scheduling
0.03: More alarm scheduling issues
0.04: Tweaks for variable size widget system
0.05: Add alarm.boot.js and move code from the bootloader

24
apps/alarm/boot.js Normal file
View File

@ -0,0 +1,24 @@
// check for alarms
(function() {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));
if (active.length) {
active = active.sort((a,b)=>a.hr-b.hr);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]")
} else {
var t = 3600000*(active[0].hr-hr);
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
})()

2
apps/barclock/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Created Bar Clock
0.02: Apply locale, 12-hour setting

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/AD8Mgfwh/AhgFFngHBOIM8AovMDIXA5gFFDoUAmYjDAocMSoMz/4FF//P/g1CAopTLDAIABwAFGAH4AfA"))

127
apps/barclock/clock-bar.js Normal file
View File

@ -0,0 +1,127 @@
/* jshint esversion: 6 */
/**
* A simple digital clock showing seconds as a bar
**/
{
// Check settings for what type our clock should be
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
const locale = require('locale')
{ // add some more info to locale
let date = new Date()
date.setFullYear(1111)
date.setMonth(1, 3) // februari: months are zero-indexed
const localized = locale.date(date, true)
locale.dayFirst = /3.*2/.test(localized)
locale.hasMeridian = (locale.meridian(date) !== '')
}
const timeFont = '6x8'
const timeFontSize = (is12Hour && locale.hasMeridian) ? 6 : 8
const ampmFontSize = 2
const dateFont = 'Vector'
const dateFontSize = 20
const screenSize = g.getWidth()
const screenCenter = screenSize / 2
const timeY = screenCenter
const barY = 155 // just below time
const barThickness = 6 // matches time digit size
const dateY = screenSize - dateFontSize // at bottom of screen
const SECONDS_PER_MINUTE = 60
function timeText(date) {
if (!is12Hour) {
return {time: locale.time(date, true), ampm: ''}
}
const meridian = locale.meridian(date)
const hours = date.getHours()
if (hours === 0) {
date.setHours(12)
} else if (hours > 12) {
date.setHours(hours - 12)
}
return {time: locale.time(date, true), ampm: meridian}
}
function dateText(date) {
const dayName = locale.dow(date, true),
month = locale.month(date, true),
day = date.getDate()
return `${dayName} ` + (locale.dayFirst ? `${day} ${month}` : `${month} ${day}`)
}
function drawDateTime(date) {
const timeTexts = timeText(date)
g.setFontAlign(0, 0) // centered
g.setFont(timeFont, timeFontSize)
g.drawString(timeTexts.time, screenCenter, timeY, true)
if (timeTexts.ampm !== '') {
g.setFontAlign(1, -1)
g.setFont(timeFont, ampmFontSize)
g.drawString(timeTexts.ampm,
// at right edge of screen , aligned with time bottom
(screenSize - ampmFontSize * 2), (timeY + timeFontSize - ampmFontSize),
true)
}
g.setFontAlign(0, 0) // centered
g.setFont(dateFont, dateFontSize)
g.drawString(dateText(date), screenCenter, dateY, true)
}
function drawBar(date) {
const seconds = date.getSeconds()
if (seconds === 0) return; // zero-size rect stills draws one line of pixels
const fraction = seconds / SECONDS_PER_MINUTE
g.fillRect(0, barY, fraction * screenSize, barY + barThickness)
}
function eraseBar() {
const color = g.getColor()
g.setColor(g.getBgColor())
g.fillRect(0, barY, screenSize, barY + barThickness)
g.setColor(color)
}
let lastSeconds
function tick() {
g.reset()
const date = new Date()
const seconds = date.getSeconds()
if (lastSeconds > seconds) {
// new minute
eraseBar()
drawDateTime(date)
}
drawBar(date)
lastSeconds = seconds
}
let iTick
function start() {
lastSeconds = 99 // force redraw
tick()
iTick = setInterval(tick, 1000)
}
function stop() {
if (iTick) {
clearInterval(iTick)
iTick = undefined
}
}
// clean app screen
g.clear()
Bangle.loadWidgets()
Bangle.drawWidgets()
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'})
Bangle.on('lcdPower', function (on) {
on ? start() : stop()
})
start()
}

BIN
apps/barclock/clock-bar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

View File

@ -9,3 +9,6 @@
0.10: Stop users calling save() (fix #125)
If Debug info is set to 'show' don't move to Terminal if connected!
0.11: Added vibrate as beep workaround
0.12: Add an event on BTN2 to open launcher when no clock detected (fix #147)
0.13: Now automatically load *.boot.js at startup
Move alarm code into alarm.boot.js

View File

@ -39,25 +39,7 @@ E.setTimeZone(s.timezone);
delete s;
// stop users doing bad things!
global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }
// check for alarms
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));
if (active.length) {
active = active.sort((a,b)=>a.hr-b.hr);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]")
} else {
var t = 3600000*(active[0].hr-hr);
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
// Load *.boot.js files
var clockApps = require('Storage').list(/\.boot\.js/).map(bootFile=>{
eval(require('Storage').read(bootFile));
});

View File

@ -12,7 +12,11 @@ if (!settings.welcomed && require("Storage").read("welcome.js")!==undefined) {
clockApp = require("Storage").read(clockApps[0].src);
delete clockApps;
}
if (!clockApp) clockApp='E.showMessage("No Clock Found")';
if (!clockApp) clockApp=`E.showMessage("No Clock Found");
setWatch(() => {
Bangle.showLauncher();
}, BTN2, {repeat:false,edge:"falling"});)
`;
delete settings;
// check to see if our clock is wrong - if it is use GPS time
if ((new Date()).getFullYear()==1970) {

1
apps/daysl/ChangeLog Normal file
View File

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

1
apps/daysl/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwMB//D/4CgwHDFYPDgICBAoQCB/4CKADmAAgcBIARCCAqQAXF/4v24CtDgYFR"))

67
apps/daysl/app.js Normal file
View File

@ -0,0 +1,67 @@
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
const storage = require('Storage');
let settings;
function updateSettings() {
storage.write('daysleft.json', settings);
}
function resetSettings() {
settings = {
day : 17,
month : 6,
year: 1981
};
updateSettings();
}
settings = storage.readJSON('daysleft.json',1);
if (!settings) resetSettings();
function showMenu() {
const datemenu = {
'': {
'title': 'Set Date',
'predraw': function() {
datemenu.Date.value = settings.day;
datemenu.Month.value = settings.month;
datemenu.Year.value = settings.year;
}
},
'Day': {
value: settings.day,
min: 1,
max: 31,
step: 1,
onchange: v => {
settings.day = v;
updateSettings();
}
},
'Month': {
value: settings.month,
min: 1,
max: 12,
step: 1,
onchange: v => {
settings.month = v;
updateSettings();
}
},
'Year': {
value: settings.year,
step: 1,
onchange: v => {
settings.year = v;
updateSettings();
}
}
};
datemenu['-Exit-'] = ()=>{load();};
return E.showMenu(datemenu);
}
showMenu();

BIN
apps/daysl/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

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

@ -0,0 +1,33 @@
const storage = require('Storage');
let settings;
function updateSettings() {
storage.write('daysleft.json', settings);
}
function resetSettings() {
settings = {
day : 17,
month : 6,
year: 1981
};
updateSettings();
}
settings = storage.readJSON('daysleft.json',1);
if (!settings) resetSettings();
var dd = settings.day+1,
mm = settings.month-1,
yy = settings.year;
const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
const targetDate = new Date(yy, mm, dd);
const today = new Date();
const diffDays = Math.round(Math.abs((targetDate - today) / oneDay));
WIDGETS["daysl"]={area:"tl",width:40,draw:function(){
g.setFont("6x8", 1);
g.drawString(diffDays,this.x+12,this.y+12);
}};

9
apps/dclock/ChangeLog Normal file
View File

@ -0,0 +1,9 @@
0.01: branched from simple clock and added seconds
0.02: add timestamp (tst)
0.03: fix timestamp round to whole number
0.04: add iso datetime and move day of the week (d) / month names (m)
0.05: add beats (@)
0.06: tidy up
0.07: add days in current month (md) and days since new moon (l)
0.08: update icon
0.09: Use localised month and day of the week from locale

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEIf4A5/8wgf/AwUB/8gh/zA4QMCl/xA4cAichgIaBiEDgMgmECDQMAkMA+EgiYvDkQJBkcQgMQDwMggUiiECG4MikEBmQWCgURiEREQIXBCIMxkIIBAoMSiQ4BGoIABKgPykRSBI4JfC+c/iARBl8zmBfEAAUvIgIAUkbAtgalB+ADDBIKSBHgUgmYJCAAa6BmCoBAYMiBIMRC4UQmEAAoQvFmUDAYUSmcxWIKMBEQKrBOw0yh8wmcyj4nBIYQDB+cwBAQA/ABUxgUDkBqBgchkMiiUikMRgSOBkR3BkEhC4MgiQHBiADBC4UQAYMRiUxkECAAITBC4MSiUQF4MTiQTBBAIDBkcCiMxkUTAYIvCAH4A/AH4AKiIPPgMxiESgUQgECgMBdAMiiUgC48ikUBiEBiIXDGQURiIbBF48RkAvCEwIvCkERgQMBRHpDBOoRhBNoJOBJIkiKYMjgcTOoMhLQMQmMDDIMjQQInEC4MhiUSkQHCC4MAkAXCiUjiZ5UiR5jLwLaBAQJ1BAgIAMCgMxMwMgkciAoMjC5pqBRwPxCoMiiUyGBsgiBBBiESVAKzBf+YACA=="))

112
apps/dclock/clock-dev.js Normal file
View File

@ -0,0 +1,112 @@
var locale = require("locale");
/* jshint esversion: 6 */
const timeFontSize = 4;
const dateFontSize = 3;
const smallFontSize = 2;
const font = "6x8";
const xyCenter = g.getWidth() / 2;
const yposTime = 50;
const yposDate = 85;
const yposTst = 115;
const yposDml = 170;
const yposDayMonth = 195;
const yposGMT = 220;
// Check settings for what type our clock should be
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
function getUTCTime(d) {
return d.toUTCString().split(' ')[4].split(':').map(function(d){return Number(d)});
}
function drawSimpleClock() {
// get date
var d = new Date();
var da = d.toString().split(" ");
var dutc = getUTCTime(d);
g.reset(); // default draw styles
// drawSting centered
g.setFontAlign(0, 0);
// draw time
var time = da[4].split(":");
var hours = time[0],
minutes = time[1],
seconds = time[2];
var meridian = "";
if (is12Hour) {
hours = parseInt(hours,10);
meridian = "AM";
if (hours == 0) {
hours = 12;
meridian = "AM";
} else if (hours >= 12) {
meridian = "PM";
if (hours>12) hours -= 12;
}
hours = (" "+hours).substr(-2);
}
// Time
g.setFont(font, timeFontSize);
g.drawString(`${hours}:${minutes}:${seconds}`, xyCenter, yposTime, true);
g.setFont(font, smallFontSize);
g.drawString(meridian, xyCenter + 102, yposTime + 10, true);
// Date String
g.setFont(font, dateFontSize);
g.drawString(`${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}`, xyCenter, yposDate, true);
// Timestamp
var tst = Math.round(d.getTime());
g.setFont(font, smallFontSize);
g.drawString(`tst:${tst}`, xyCenter, yposTst, true);
//Days in month
var dom = new Date(d.getFullYear(), d.getMonth()+1, 0).getDate();
//Days since full moon
var knownnew = new Date(2020,02,24,09,28,0);
// Get millisecond difference and divide down to cycles
var cycles = (d.getTime()-knownnew.getTime())/1000/60/60/24/29.53;
// Multiply decimal component back into days since new moon
var sincenew = (cycles % 1)*29.53;
// Draw days in month and sime since new moon
g.setFont(font, smallFontSize);
g.drawString(`md:${dom} l:${sincenew.toFixed(2)}`, xyCenter, yposDml, true);
// draw Month name, Day of the week and beats
var beats = Math.floor((((dutc[0] + 1) % 24) + dutc[1] / 60 + dutc[2] / 3600) * 1000 / 24);
g.setFont(font, smallFontSize);
g.drawString(`m:${locale.month(d,true)} d:${locale.dow(d,true)} @${beats}`, xyCenter, yposDayMonth, true);
// draw gmt
var gmt = da[5];
g.setFont(font, smallFontSize);
g.drawString(gmt, xyCenter, yposGMT, true);
}
// handle switch display on by pressing BTN1
Bangle.on('lcdPower', function(on) {
if (on) drawSimpleClock();
});
// clean app screen
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
// refesh every 100 milliseconds
setInterval(drawSimpleClock, 100);
// draw now
drawSimpleClock();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});

BIN
apps/dclock/clock-dev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

178
apps/gbridge/PROTOCOL.md Normal file
View File

@ -0,0 +1,178 @@
# Watch -> Phone
## show toast
```
{ "t": "info", "msg": "message" }
```
t can be one of "info", "warn", "error"
## report battery level
```
{ "t": "status", "bat": 30, "volt": 30 }
```
* bat is in range 0 to 100
* volt is optional and should be greater than 0
## find phone
```
{ "t": "findPhone", "n": true }
```
n is an boolean and toggles the find phone function
## control music player
```
{ "t": "music", "n": "play" }
```
n can be one of "play", "pause", "playpause", "next", "previous", "volumeup", "volumedown", "forward", "rewind"
## control phone call
```
{ "t": "call", "n": "accept"}
```
n can be one of "accept", "end", "incoming", "outcoming", "reject", "start", "ignore"
## react to notifications
Send a response to a notification from phone
```
{
"t": "notify",
"n": "dismiss",
"id": 2,
"tel": "+491234",
"msg": "message",
}
```
* n can be one of "dismiss", "dismiss all", "open", "mute", "reply"
* id, tel and message are optional
# Phone -> Watch
## show notification
```
{
"t": "notify",
"id": 2,
"src": "app",
"title": "titel",
"subject": "subject",
"body": "message body",
"sender": "sender",
"tel": "+491234"
}
```
## notification deleted
This event is send when the user skipped a notification
```
{ "t": "notify-", "id": 2 }
```
## set alarm
```
{
"t": "alarm",
"d": [
{ "h": 13, "m": 37 },
{ "h": 8, "m": 0 }
]
}
```
## call state changed
```
{
"t": "call",
"cmd": "accept",
"name": "name",
"number": "+491234"
}
```
cmd can be one of "", "undefined", "accept", "incoming", "outgoing", "reject", "start", "end"
## music state changed
```
{
"t": "musicstate",
"state": "play",
"position": 40,
"shuffle": 0,
"repeat": 1
}
```
## set music info
```
{
"t": "musicinfo",
"artist": "artist",
"album": "album",
"track": "track",
"dur": 1,
"c": 2,
"n" 3
}
```
* dur is the duration of the track
* c is the track count
* n is the track number
## find device
```
{
"t": "find",
"n": true
}
```
n toggles find device functionality
## set constant vibration
```
{
"t": "vibrate",
"n": 2
}
```
n is the intensity
## send weather
```
{
"t": "weather",
"temp": 10,
"hum": 71,
"txt": "condition",
"wind": 13,
"loc": "location"
}
```
* hum is the humidity
* txt is the weather condition
* loc is the location

View File

@ -5,32 +5,9 @@
<body>
<div id="tracks"></div>
<div class="modal active" id="status-modal">
<div class="modal-overlay"></div>
<div class="modal-container">
<div class="modal-header">
<div class="modal-title h5">Please wait</div>
</div>
<div class="modal-body">
<div class="content">
Loading...
</div>
</div>
</div>
</div>
<script src="../../lib/interface.js"></script>
<script>
var domTracks = document.getElementById("tracks");
var domModal = document.getElementById("status-modal");
function showModal(title) {
domModal.querySelector(".content").innerHTML = title;
domModal.classList.add("active");
}
function hideModal(title) {
domModal.classList.remove("active");
}
function saveKML(track,title) {
var kml = `<?xml version="1.0" encoding="UTF-8"?>
@ -107,19 +84,15 @@ function trackLineToObject(l, hasTrackNumber) {
}
function downloadTrack(trackid, callback) {
showModal("Downloading Track...");
Puck.write(`\x10(function() {
var f = require("Storage").open(".gpsrc${trackid.toString(36)}","r");
var l = f.readLine();
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
})()\n`,tracklist=>{
hideModal();
var track = tracklist.trim().split("\n").map(l=>trackLineToObject(l,false));
Util.showModal("Downloading Track...");
Util.readStorageFile(`.gpsrc${trackid.toString(36)}`,data=>{
Util.hideModal();
var track = data.trim().split("\n").map(l=>trackLineToObject(l,false));
callback(track);
});
}
function getTrackList() {
showModal("Loading Tracks...");
Util.showModal("Loading Tracks...");
domTracks.innerHTML = "";
Puck.write(`\x10(function() {
for (var n=0;n<36;n++) {
@ -171,7 +144,7 @@ function getTrackList() {
</div>
</div>`;
domTracks.innerHTML = html;
hideModal();
Util.hideModal();
var buttons = domTracks.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
@ -179,9 +152,9 @@ function getTrackList() {
var trackid = parseInt(button.getAttribute("trackid"));
var task = button.getAttribute("task");
if (task=="delete") {
showModal("Deleting Track...");
Puck.write(`\x10require("Storage").open(".gpsrc${trackid.toString(36)}","r").erase()\n`,()=>{
hideModal();
Util.showModal("Deleting Track...");
Util.eraseStorageFile(`.gpsrc${trackid.toString(36)}`,()=>{
Util.hideModal();
getTrackList();
});
}

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AFEEolAC6lN7vdDCcECwPd6guVGCYuDC4cCBQMikQXQJAMjkECmcyIx4XDmUjmYvLC4XUDARHBIoIWLgATCGQdA7tEonQC5ouDDYg0BOxgSEAggwKRwgUCC6ZIDSwoXNogWDDgNCAgIWIkUEoUk6kiCgMkokipsiBIQXIki2CAgNCAoYADC5Eic4Mic4ICCAIIJCC5MzAAcykYGEAAIXOABAXTmUzGoIXVAIIXLB4SICDIovjO76PZbYR3PDI4XiI6530MIh3SC6R33C/oAOC48CCxsgC44A/ADY="))

163
apps/grocery/grocery.html Normal file
View File

@ -0,0 +1,163 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<h4>List of products</h4>
<table class="table">
<thead>
<tr>
<th>name</th>
<th>quantity</th>
<th>actions</th>
</tr>
</thead>
<tbody id="products">
</tbody>
</table>
<br><br>
<h4>Add a new product</h4>
<form id="add_product_form">
<div class="columns">
<div class="column col-4 col-xs-12">
<input class="form-input input-sm" type="text" id="add_product_name" placeholder="Name">
</div>
<div class="column col-4 col-xs-12">
<input class="form-input input-sm" value="1" type="number" id="add_product_quantity" placeholder="Quantity">
</div>
<div class="column col-4 col-xs-12">
<button id="add_product_button" class="btn btn-primary btn-sm">Add</button>
</div>
</div>
</form>
<br><br>
<button id="reset" class="btn btn-error">Reset</button> <button id="upload" class="btn btn-primary">Upload</button>
<script src="../../lib/customize.js"></script>
<script>
var products = []
try{
var stored = localStorage.getItem('grocery-product-list')
if(stored) products = JSON.parse(stored);
}catch(e){}
var $name = document.getElementById('add_product_name')
var $form = document.getElementById('add_product_form')
var $button = document.getElementById('add_product_button')
var $quantity = document.getElementById('add_product_quantity')
var $list = document.getElementById('products')
var $reset = document.getElementById('reset')
renderProducts()
$reset.addEventListener('click', reset)
$form.addEventListener('submit', event => {
event.preventDefault()
var name = $name.value.trim()
if(!name) return;
var quantity = parseInt($quantity.value)
products.push({
name, quantity,
ok: false
})
renderProducts()
$name.value = ''
$quantity.value = 1
save()
})
function save(){
localStorage.setItem('grocery-product-list',JSON.stringify(products));
}
function reset(){
products = []
save()
renderProducts()
}
function removeProduct(index){
products = products.filter((p,i) => i!==index)
save()
renderProducts()
}
function renderProducts(){
$list.innerHTML = ''
products.forEach((product,index) => {
var $product = document.createElement('tr')
$product.innerHTML = `<td>${product.name}</td><td>${product.quantity}</td><td><button class="btn btn-error" onclick="removeProduct(${index})">remove</button></td>`
$list.appendChild($product)
})
$name.focus()
}
document.getElementById("upload").addEventListener("click", function() {
var app = `
var newTime = ${Date.now()}
var products = ${JSON.stringify(products)}
var newTime = newTime;
var filename = 'grocery';
var settings = require("Storage").readJSON(filename,1)|| null;
function getSettings(){
return {
products : products,
date: newTime
};
}
if(!settings || !settings.date || settings.date < newTime){
settings = getSettings();
Bangle.buzz(500);
}
function updateSettings() {
require("Storage").writeJSON(filename, settings);
Bangle.buzz();
}
function twoChat(n){
if(n<10) return '0'+n;
return ''+n;
}
const mainMenu = settings.products.reduce(function(m, p, i){
const name = twoChat(p.quantity)+' '+p.name;
m[name] = {
value: p.ok,
format: v => v?'[x]':'[ ]',
onchange: v => {
settings.products[i].ok = v;
updateSettings();
}
};
return m;
}, {
'': { 'title': 'Grocery list' }
});
mainMenu['< Back'] = ()=>{load();};
E.showMenu(mainMenu);
`;
var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQ0QACF1nGAAIxpFoYwqFwwwnRggwGB4eFAggACLzwHCMAeF1WGAgOGw2x2IGCLzYGEF4YpBwotCFwJfWFwo1GSAYtBAIIABRq4vFMhAwBzoAFdzIuKAAOc4IAGGC4qEMZOiF44wXFxovleBYvIGCwmB0WjE4V/AgfG1IvCzujFQOjwoECF6WFwovBDYOFEwN/AgIwCAgOFBwYrBBAQEBzodCF6AAHww1CBpIODAAYvRDAWG2IEBAYYJFBxICCF6Ox1WxAAQfBAYQlCAAIOJAQIvUADQvn1WGR4RfbP4gAFBwgFCF7a5EdwQADF46/cL9wAQF94AGF85bB1TvmF47vdJ4bvFF8qPRFgLv/L7jPCaQq/fYYrvgJgoAGd/7v/F/4v/F5oAdF54weFyAA/AH4A3A="))`;
sendCustomizedApp({
storage:[
{name:"grocery.app.js", content:app},
{name:"grocery.img", content:icon, evaluate:true},
{name:"grocery"}
]
});
});
</script>
</body>
</html>

BIN
apps/grocery/grocery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -5,32 +5,9 @@
<body>
<div id="records"></div>
<div class="modal active" id="status-modal">
<div class="modal-overlay"></div>
<div class="modal-container">
<div class="modal-header">
<div class="modal-name h5">Please wait</div>
</div>
<div class="modal-body">
<div class="content">
Loading...
</div>
</div>
</div>
</div>
<script src="../../lib/interface.js"></script>
<script>
var domRecords = document.getElementById("records");
var domModal = document.getElementById("status-modal");
function showModal(name) {
domModal.querySelector(".content").innerHTML = name;
domModal.classList.add("active");
}
function hideModal(name) {
domModal.classList.remove("active");
}
function saveRecord(record,name) {
var csv = `${record.map(rec=>[rec.time, rec.bpm, rec.confidence].join(",")).join("\n")}`;
@ -62,20 +39,16 @@ function recordLineToObject(l, hasRecordNbr) {
}
function downloadRecord(recordNbr, callback) {
showModal("Downloading heart rate record...");
Puck.write(`\x10(function() {
var f = require("Storage").open(".heart${recordNbr.toString(36)}","r");
var l = f.readLine();
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
})()\n`,recordList=>{
hideModal();
var record = recordList.trim().split("\n").map(l=>recordLineToObject(l,false));
Util.showModal("Downloading heart rate record...");
Util.readStorageFile(`.heart${recordNbr.toString(36)}`,data=>{
Util.hideModal();
var record = data.trim().split("\n").map(l=>recordLineToObject(l,false));
callback(record);
});
}
function getRecordList() {
showModal("Loading heart rate records...");
Util.showModal("Loading heart rate records...");
domRecords.innerHTML = "";
Puck.write(`\x10(function() {
for (var n=0;n<36;n++) {
@ -118,7 +91,7 @@ function getRecordList() {
</div>
</div>`;
domRecords.innerHTML = html;
hideModal();
Util.hideModal();
var buttons = domRecords.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
@ -126,9 +99,9 @@ function getRecordList() {
var recordNbr = parseInt(button.getAttribute("recordNbr"));
var task = button.getAttribute("task");
if (task=="delete") {
showModal("Deleting record...");
Puck.write(`\x10require("Storage").open(".heart${recordNbr.toString(36)}","r").erase()\n`,()=>{
hideModal();
Util.showModal("Deleting record...");
Util.eraseStorageFile(`.heart${recordNbr.toString(36)}`,()=>{
Util.hideModal();
getRecordList();
});
}

View File

@ -348,7 +348,7 @@ var locales = {
temperature: '°C',
ampm: {0:"de",1:"du"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%Y %d %b", 1: "%Y.%m.%d" }, // 2020 Feb 28" // "2020.03.01."(short)
datePattern: { 0: "%Y %b %d, %A", 1: "%Y.%m.%d" }, // 2020 Feb 28, Péntek" // "2020.03.01."(short)
abmonth: "Jan,Feb,Már,Ápr,Máj,Jún,Júl,Aug,Szep,Okt,Nov,Dec",
month: "Január,Február,Március,Április,Május,Június,Július,Augusztus,Szeptember,Október,November,December",
abday: "Vas,Hét,Ke,Szer,Csüt,Pén,Szom",

View File

@ -1,2 +1,3 @@
0.01: Create mario app
0.02: Fix day of the week and add padding
0.03: use short date format from locale, take timeout from settings

View File

@ -6,6 +6,9 @@
**********************************/
var locale = require("locale");
const storage = require('Storage');
const settings = (storage.readJSON('setting.json',1)||{});
const timeout = settings.timeout||10;
// Screen dimensions
let W, H;
@ -280,14 +283,10 @@ function drawTime() {
}
function drawDate() {
const date = new Date();
const day = locale.dow(date).substr(0, 3);
const dayNum = ("0" + date.getDate()).substr(-2);
const month = locale.month(date).substr(0, 3);
g.setFont("6x8");
g.setColor(LIGHTEST);
g.drawString(`${day} ${dayNum} ${month}`, 10, 0, true);
const dateStr = locale.date(new Date(), true);
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 0, true);
}
function redraw() {
@ -322,7 +321,7 @@ function resetDisplayTimeout() {
displayTimeoutRef = setInterval(() => {
if (Bangle.isLCDOn()) Bangle.setLCDPower(false);
clearTimers();
}, ONE_SECOND * 10);
}, ONE_SECOND * timeout);
}
function startTimers(){

1
apps/moonphase/ChangeLog Normal file
View File

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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIifh/wAod//wECgP//+AAoMHAoPgCwQFBDAUfw///AFBjkD//8AoMYgPnEgUQgPB/4qCgeB/YFDwHxGAeA+AFEvHAAocdAoQCBh4CBgJFBh4CBAoNwg4FBhHA+AIBgkcgJSBAoMIg5SBAoIpB/E58EGAoP8n4FD/8f8EDAoQvBgfANYOfAYPwAoP/4AtBAoWAgP4SARfBAoZYB/0/Aod/AgKJCBQSVCj4FBUIStFXIrFFaIrdGADYA=="))

296
apps/moonphase/app.js Normal file
View File

@ -0,0 +1,296 @@
//Icons from https://icons8.com
//Sun and Moon calculations from https://github.com/mourner/suncalc and https://gist.github.com/endel/dfe6bb2fbe679781948c
//pictures
function getImg(i) {
var data = {
"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==",
"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==",
"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==",
"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==",
"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==",
"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==",
"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=="
};
return {
width : 26, height : 26, bpp : 1,
transparent : 0,
buffer : E.toArrayBuffer(atob(data[i]))
};
}
//coordinates (will get from GPS later on real device)
var lat = 52.96236,
lon = 7.62571;
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 new Date((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 = {};
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) {
times.push([angle, riseName, setName]);
};
// 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.
SunCalc.getMoonIllumination = function (date) {
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var Moon = {
phases: ['new', 'waxing-crescent', 'first-quarter', 'waxing-gibbous', 'full', 'waning-gibbous', 'last-quarter', 'waning-crescent'],
phase: function (year, month, day) {
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
//print ({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 = {};
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();
date.setDate(date.getDate() + offset);
var dd = String(date.getDate());
if(dd<10){dd='0'+dd;}
var mm = String(date.getMonth() + 1);
if(mm<10){mm='0'+mm;}
var yyyy = date.getFullYear();
var phase = SunCalc.getMoonIllumination(date);
return dd + "." + mm + "." + yyyy + ": "+ phase.name;
}
function getMPhaseSim (offset) {
var date = new Date();
date.setDate(date.getDate() + offset);
var dd = String(date.getDate());
if(dd<10){dd='0'+dd;}
var mm = String(date.getMonth() + 1);
if(mm<10){mm='0'+mm;}
var yyyy = date.getFullYear();
var phase = SunCalc.getMoonIllumination(date);
return phase.name;
}
function drawMoonPhase(offset, x, y){
if (lat >= 0 && lat <= 90){ //Northern hemisphere
if (getMPhaseSim(offset) == "new") {g.drawImage(getImg("NewMoon"), 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) == "waxing-gibbous") {g.drawImage(getImg("WaxingGibbousNorth"), 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) == "last-quarter") {g.drawImage(getImg("LastQuarterNorth"), x, y);}
if (getMPhaseSim(offset) == "waning-crescent") {g.drawImage(getImg("WaningCrescentNorth"), x, y);}
}
else { //Southern hemisphere
if (getMPhaseSim(offset) == "new") {g.drawImage(getImg("NewMoon"), 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) == "waxing-gibbous") {g.drawImage(getImg("WaxingGibbousSouth"), 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) == "last-quarter") {g.drawImage(getImg("LastQuarterSouth"), x, y);}
if (getMPhaseSim(offset) == "waning-crescent") {g.drawImage(getImg("WaningCrescentSouth"), x, y);}
}
}
function drawMoon(offset, x, y) {
g.setFont("6x8");
g.clear();
g.drawString("Key1: increase day, Key3:decrease day",10,10);
g.drawString(getMPhaseComp(offset),x,y-10);
drawMoonPhase(offset, x, y);
g.drawString(getMPhaseComp(offset+2),x,y+40);
drawMoonPhase(offset+2, x, y+50);
g.drawString(getMPhaseComp(offset+4),x,y+90);
drawMoonPhase(offset+4, x, y+100);
g.drawString(getMPhaseComp(offset+6),x,y+140);
drawMoonPhase(offset+6, x, y+150);
}
function start() {
var x = 10;
var y = 40;
var offsetMoon = 0;
drawMoon(offsetMoon, x, y); //offset, x, y
//define button functions
setWatch(function() {
offsetMoon++; //jump to next day
drawMoon(offsetMoon, x, y); //offset, x, y
}, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function() {
offsetMoon--; //jump to next day
drawMoon(offsetMoon, x, y); //offset, x, y
}, BTN3, {edge:"rising", debounce:50, repeat:true});
}
start();

BIN
apps/moonphase/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

2
apps/pipboy/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New Watch!
0.02: Changed colors for better readability and added current date

View File

@ -29,17 +29,23 @@ function topLine() {
function bottomLine() {
g.setColor(darkGreen);
g.fillRect(5, 175, 60, 185);
g.fillRect(67, 175, 140, 185);
//first line
g.setColor(darkerGreen);
g.fillRect(5, 175, 100, 185); //DATE
g.fillRect(105, 175, 160, 185);//STIM
g.fillRect(166, 175, 239, 185); // RADAWAY
g.setColor(green);
g.setFont("6x8", tinyFont);
g.drawString("DATE", 20, 177);
g.drawString("STIM (3)", 135, 177);
g.drawString("RADAWAY (8)", 205, 177);
//second line
g.setColor(darkerGreen);
g.fillRect(5, 190, 70, 200);
g.fillRect(75, 190, 239, 200);
g.setFont("6x8", tinyFont);
g.drawString("STIM (0)", 32, 177);
g.drawString("RADAWAY (0)", 105, 177);
g.setColor(green);
g.drawString("HP 115/115", 38, 192);
g.drawString("LEVEL 6", 100, 192);
@ -55,7 +61,15 @@ function drawClock() {
var t = new Date();
var h = t.getHours();
var m = t.getMinutes();
var dd = t.getDate();
var mm = t.getMonth()+1; //month is zero-based
var yy = t.getFullYear();
var time = ("0" + h).substr(-2) + ":" + ("0" + m).substr(-2);
//create date string
if (dd.toString().length < 2) dd = '0' + dd;
if (mm.toString().length < 2) mm = '0' + mm;
var date = dd + "." + mm + "." + yy;
g.setFont("6x8",bigFont);
g.setColor(green);
@ -63,6 +77,10 @@ function drawClock() {
g.clearRect(0, 110, 150, 140);
g.drawString(time, 70, 110);
//draw date
g.setFont("6x8", tinyFont);
g.drawString(date, 67, 177);
}
function drawAll() {
@ -83,4 +101,3 @@ setInterval(drawClock, 1E4);
drawAll();
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});

5
apps/swatch/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: Original App
0.02: Lap log now counts up from 1
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
Fixed bug from 0.01 where BN1 (reset) could clear the lap log when timer is running

View File

@ -4,6 +4,7 @@ var started = false;
var timeY = 60;
var hsXPos = 0;
var lapTimes = [];
var saveTimes = [];
var displayInterval;
function timeToText(t) {
@ -17,12 +18,15 @@ function updateLabels() {
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
g.drawString(started?"STOP":"GO",230,120);
if (!started) g.drawString("RESET",230,50);
g.drawString("LAP",230,190);
if (!started) g.drawString("RESET",230,190);
g.drawString(started?"LAP":"SAVE",230,50);
g.setFont("6x8",1);
g.setFontAlign(-1,-1);
for (var i in lapTimes) {
g.drawString(i+": "+timeToText(lapTimes[i]),10,timeY + 30 + i*8);
if (i<18)
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),35,timeY + 30 + i*8);}
else
{g.drawString(lapTimes.length-i+": "+timeToText(lapTimes[i]),125,timeY + 30 + (i-18)*8);}
}
drawsecs();
}
@ -47,6 +51,11 @@ function drawms() {
g.clearRect(hsXPos,timeY,220,timeY+20);
g.drawString("."+("0"+hs).substr(-2),hsXPos,timeY+10);
}
function saveconvert() {
for (var v in lapTimes){
saveTimes[v]=v+1+"-"+timeToText(lapTimes[(lapTimes.length-1)-v]);
}
}
setWatch(function() { // Start/stop
started = !started;
@ -69,19 +78,25 @@ setWatch(function() { // Start/stop
drawms();
}, 20);
}, BTN2, {repeat:true});
setWatch(function() { // Reset
Bangle.beep();
if (!started) {
tStart = tCurrent = Date.now();
}
lapTimes = [];
updateLabels();
}, BTN1, {repeat:true});
setWatch(function() { // Lap
Bangle.beep();
if (started) tCurrent = Date.now();
lapTimes.unshift(tCurrent-tStart);
tStart = tCurrent;
if (!started)
{
var timenow= Date();
saveconvert();
require("Storage").writeJSON("StpWch-"+timenow.toString(), saveTimes);
}
updateLabels();
}, BTN1, {repeat:true});
setWatch(function() { // Reset
if (!started) {
Bangle.beep();
tStart = tCurrent = Date.now();
lapTimes = [];
}
updateLabels();
}, BTN3, {repeat:true});

1
apps/widver/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New Widget

11
apps/widver/widget.js Normal file
View File

@ -0,0 +1,11 @@
/* jshint esversion: 6 */
(() => {
var width = 28,
ver = process.env.VERSION.split('.');
function draw() {
g.reset().setColor(0, 0.5, 1).setFont("6x8", 1);
g.drawString(ver[0], this.x + 2, this.y + 4, true);
g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + width / 2, this.y + 14, true);
}
WIDGETS["version"] = { area: "tr", width: width, draw: draw };
})();

BIN
apps/widver/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

View File

@ -51,7 +51,9 @@ apps.forEach((app,addIdx) => {
if (app.version != "0.01")
WARN(`App ${app.id} has no ChangeLog`);
} else {
var versions = fs.readFileSync(appDir+"ChangeLog").toString().match(/\d+\.\d+:/g);
var changeLog = fs.readFileSync(appDir+"ChangeLog").toString();
var versions = changeLog.match(/\d+\.\d+:/g);
if (!versions) ERROR(`No versions found in ${app.id} ChangeLog (${appDir}ChangeLog)`);
var lastChangeLog = versions.pop().slice(0,-1);
if (lastChangeLog != app.version)
WARN(`App ${app.id} app version (${app.version}) and ChangeLog (${lastChangeLog}) don't agree`);

File diff suppressed because one or more lines are too long

View File

@ -128,10 +128,9 @@
</div>
<script src="https://www.puck-js.com/puck.js"></script>
<script src="utils.js"></script>
<script src="comms.js"></script>
<script src="appinfo.js"></script>
<script src="index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js" type="application/javascript"></script>
<script src="js/utils.js"></script>
<script src="js/comms.js"></script>
<script src="js/appinfo.js"></script>
<script src="js/index.js"></script>
</body>
</html>

View File

@ -27,14 +27,19 @@ var AppInfo = {
// then map each file to a command to load into storage
fileContents.forEach(storageFile => {
// format ready for Espruino
var js;
if (storageFile.evaluate) {
js = storageFile.content.trim();
let js = storageFile.content.trim();
if (js.endsWith(";"))
js = js.slice(0,-1);
} else
js = toJS(storageFile.content);
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${js});`;
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${js});`;
} else {
let code = storageFile.content;
// write code in chunks, in case it is too big to fit in RAM (fix #157)
var CHUNKSIZE = 4096;
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`;
for (var i=CHUNKSIZE;i<code.length;i+=CHUNKSIZE)
storageFile.cmd += `\n\x10require('Storage').write(${toJS(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`;
}
});
resolve(fileContents);
}).catch(err => reject(err));

View File

@ -147,5 +147,50 @@ readFile : (file) => {
});
});
});
},
readStorageFile : (filename) => { // StorageFiles are different to normal storage entries
return new Promise((resolve,reject) => {
// Use "\xFF" to signal end of file (can't occur in files anyway)
var fileContent = "";
var fileSize = undefined;
var connection = Puck.getConnection();
connection.received = "";
connection.cb = function(d) {
var finished = false;
var eofIndex = d.indexOf("\xFF");
if (eofIndex>=0) {
finished = true;
d = d.substr(0,eofIndex);
}
fileContent += d;
if (fileSize === undefined) {
var newLineIdx = fileContent.indexOf("\n");
if (newLineIdx>=0) {
fileSize = parseInt(fileContent.substr(0,newLineIdx));
console.log("File size is "+fileSize);
fileContent = fileContent.substr(newLineIdx+1);
}
} else {
showProgress(undefined,100*fileContent.length / (fileSize||1000000));
}
if (finished) {
hideProgress();
connection.received = "";
connection.cb = undefined;
resolve(fileContent);
}
};
console.log(`Reading StorageFile ${JSON.stringify(filename)}`);
connection.write(`\x03\x10(function() {
var f = require("Storage").open(${JSON.stringify(filename)},"r");
Bluetooth.println(f.getLength());
var l = f.readLine();
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
Bluetooth.print("\xFF");
})()\n`,() => {
showProgress(`Reading ${JSON.stringify(filename)}`,0);
console.log(`StorageFile read started...`);
});
});
}
};

View File

@ -229,6 +229,14 @@ function handleAppInterface(app) {
id : msg.id
});
});
} else if (msg.type=="readstoragefile") {
Comms.readStorageFile(msg.data/*filename*/).then(function(result) {
iwin.postMessage({
type : "readstoragefilersp",
data : result,
id : msg.id
});
});
}
}, false);
iwin.postMessage({type:"init"});
@ -343,22 +351,9 @@ function refreshLibrary() {
});
} else if (icon.classList.contains("icon-menu")) {
// custom HTML update
if (app.custom) {
icon.classList.remove("icon-menu");
icon.classList.add("loading");
handleCustomApp(app).then((appJSON) => {
if (appJSON) appsInstalled.push(appJSON);
showToast(app.name+" Uploaded!", "success");
icon.classList.remove("loading");
icon.classList.add("icon-delete");
refreshMyApps();
refreshLibrary();
}).catch(err => {
showToast("Customise failed, "+err, "error");
icon.classList.remove("loading");
icon.classList.add("icon-menu");
});
}
icon.classList.remove("icon-menu");
icon.classList.add("loading");
customApp(app);
} else if (icon.classList.contains("icon-delete")) {
// Remove app
icon.classList.remove("icon-delete");
@ -393,9 +388,23 @@ function removeApp(app) {
});
}
function customApp(app) {
return handleCustomApp(app).then((appJSON) => {
if (appJSON) appsInstalled.push(appJSON);
showToast(app.name+" Uploaded!", "success");
refreshMyApps();
refreshLibrary();
}).catch(err => {
showToast("Customise failed, "+err, "error");
refreshMyApps();
refreshLibrary();
});
}
function updateApp(app) {
if (app.custom) return customApp(app);
showProgress(`Upgrading ${app.name}`,undefined,"sticky");
Comms.removeApp(app).then(()=>{
return Comms.removeApp(app).then(()=>{
showToast(app.name+" removed successfully. Updating...",);
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
return Comms.uploadApp(app);
@ -408,10 +417,13 @@ function updateApp(app) {
}, err=>{
hideProgress("sticky");
showToast(app.name+" update failed, "+err,"error");
refreshMyApps();
refreshLibrary();
});
}
function appNameToApp(appName) {
var app = appJSON.find(app=>app.id==appName);
if (app) return app;

View File

@ -3,10 +3,21 @@ be used from within BangleApps
See: README.md / `apps.json`: `interface` element
This exposes a 'Puck' object like the puck.js library,
and calls `onInit` when it's ready. `Puck` can be used
for sending/receiving data to the correctly connected
This exposes a 'Puck' object (a simple version of
https://github.com/espruino/EspruinoWebTools/blob/master/puck.js)
and calls `onInit` when it's ready. `Puck` can be used for
sending/receiving data to the correctly connected
device with Puck.eval/write.
Puck.write(data,callback)
Puck.eval(data,callback)
There is also:
Util.readStorageFile(filename,callback)
Util.eraseStorageFile(filename,callback)
Util.showModal(title)
Util.hideModal()
*/
var __id = 0, __idlookup = [];
var Puck = {
@ -20,11 +31,48 @@ var Puck = {
window.postMessage({type:"write",data:data,id:__id});
}
};
var Util = {
readStorageFile : function(filename,callback) {
__id++;
__idlookup[__id] = callback;
window.postMessage({type:"readstoragefile",data:filename,id:__id});
},
eraseStorageFile : function(filename,callback) {
Puck.write(`\x10require("Storage").open(${JSON.stringify(filename)}","r").erase()\n`,callback);
},
showModal : function(title) {
if (!Util.domModal) {
Util.domModal = document.createElement('div');
Util.domModal.id = "status-modal";
Util.domModal.classList.add("modal");
Util.domModal.classList.add("active");
Util.domModal.innerHTML = `<div class="modal-overlay"></div>
<div class="modal-container">
<div class="modal-header">
<div class="modal-title h5">Please wait</div>
</div>
<div class="modal-body">
<div class="content">
Loading...
</div>
</div>
</div>`;
document.body.appendChild(Util.domModal);
}
Util.domModal.querySelector(".content").innerHTML = title;
Util.domModal.classList.add("active");
},
hideModal : function() {
if (!Util.domModal) return;
Util.domModal.classList.remove("active");
}
};
window.addEventListener("message", function(event) {
var msg = event.data;
if (msg.type=="init") {
onInit();
} else if (msg.type=="evalrsp" || msg.type=="writersp") {
} else if (msg.type=="evalrsp" || msg.type=="writersp"|| msg.type=="readstoragefilersp") {
var cb = __idlookup[msg.id];
delete __idlookup[msg.id];
cb(msg.data);