Merge branch 'master' of github.com:targor/BangleApps
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Barometer altitude adjustment setting
|
0.02: Barometer altitude adjustment setting
|
||||||
0.03: Use default Bangle formatter for booleans
|
0.03: Use default Bangle formatter for booleans
|
||||||
|
0.04: Add options for units in locale and recording GPS
|
||||||
|
|
|
@ -403,6 +403,8 @@ function onGPS(fix) {
|
||||||
|
|
||||||
if ( sp < 10 ) sp = sp.toFixed(1);
|
if ( sp < 10 ) sp = sp.toFixed(1);
|
||||||
else sp = Math.round(sp);
|
else sp = Math.round(sp);
|
||||||
|
if (isNaN(sp)) sp = '---';
|
||||||
|
|
||||||
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
|
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
|
||||||
|
|
||||||
// Altitude
|
// Altitude
|
||||||
|
@ -416,6 +418,12 @@ function onGPS(fix) {
|
||||||
|
|
||||||
// Age of last fix (secs)
|
// Age of last fix (secs)
|
||||||
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
||||||
|
} else {
|
||||||
|
// populate spd_unit
|
||||||
|
if (cfg.spd == 0) {
|
||||||
|
m = require("locale").speed(0).match(/[0-9,\.]+(.*)/);
|
||||||
|
cfg.spd_unit = m[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cfg.modeA == 1 ) {
|
if ( cfg.modeA == 1 ) {
|
||||||
|
@ -465,7 +473,7 @@ function updateClock() {
|
||||||
// Read settings.
|
// Read settings.
|
||||||
let cfg = require('Storage').readJSON('bikespeedo.json',1)||{};
|
let cfg = require('Storage').readJSON('bikespeedo.json',1)||{};
|
||||||
|
|
||||||
cfg.spd = 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
cfg.spd = !cfg.localeUnits; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
||||||
cfg.spd_unit = 'km/h'; // Displayed speed unit
|
cfg.spd_unit = 'km/h'; // Displayed speed unit
|
||||||
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
|
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
|
||||||
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')
|
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')
|
||||||
|
@ -499,14 +507,6 @@ function onPressure(dat) {
|
||||||
altiBaro = Number(dat.altitude.toFixed(0)) + Number(cfg.altDiff);
|
altiBaro = Number(dat.altitude.toFixed(0)) + Number(cfg.altDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.setBarometerPower(1); // needs some time...
|
|
||||||
g.clearRect(0,screenYstart,screenW,screenH);
|
|
||||||
onGPS(lf);
|
|
||||||
Bangle.setGPSPower(1);
|
|
||||||
Bangle.on('GPS', onGPS);
|
|
||||||
Bangle.on('pressure', onPressure);
|
|
||||||
|
|
||||||
Bangle.setCompassPower(1);
|
|
||||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||||
if (!CALIBDATA) calibrateCompass = true;
|
if (!CALIBDATA) calibrateCompass = true;
|
||||||
function Compass_tiltfixread(O,S){
|
function Compass_tiltfixread(O,S){
|
||||||
|
@ -544,11 +544,33 @@ function Compass_reading() {
|
||||||
Compass_heading = Compass_newHeading(d,Compass_heading);
|
Compass_heading = Compass_newHeading(d,Compass_heading);
|
||||||
hdngCompass = Compass_heading.toFixed(0);
|
hdngCompass = Compass_heading.toFixed(0);
|
||||||
}
|
}
|
||||||
if (!calibrateCompass) setInterval(Compass_reading,200);
|
|
||||||
|
|
||||||
setButtons();
|
function start() {
|
||||||
if (emulator) setInterval(updateClock, 2000);
|
Bangle.setBarometerPower(1); // needs some time...
|
||||||
else setInterval(updateClock, 10000);
|
g.clearRect(0,screenYstart,screenW,screenH);
|
||||||
|
onGPS(lf);
|
||||||
|
Bangle.setGPSPower(1);
|
||||||
|
Bangle.on('GPS', onGPS);
|
||||||
|
Bangle.on('pressure', onPressure);
|
||||||
|
|
||||||
|
Bangle.setCompassPower(1);
|
||||||
|
if (!calibrateCompass) setInterval(Compass_reading,200);
|
||||||
|
|
||||||
|
setButtons();
|
||||||
|
if (emulator) setInterval(updateClock, 2000);
|
||||||
|
else setInterval(updateClock, 10000);
|
||||||
|
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
}
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
if (cfg.record && WIDGETS["recorder"]) {
|
||||||
|
WIDGETS["recorder"]
|
||||||
|
.setRecording(true)
|
||||||
|
.then(start);
|
||||||
|
|
||||||
|
if (cfg.recordStopOnExit)
|
||||||
|
E.on('kill', () => WIDGETS["recorder"].setRecording(false));
|
||||||
|
} else {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bikespeedo",
|
"id": "bikespeedo",
|
||||||
"name": "Bike Speedometer (beta)",
|
"name": "Bike Speedometer (beta)",
|
||||||
"shortName": "Bike Speedometer",
|
"shortName": "Bike Speedometer",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
|
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"Screenshot.png"}],
|
"screenshots": [{"url":"Screenshot.png"}],
|
||||||
|
|
|
@ -11,9 +11,34 @@
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
'< Load Bike Speedometer': ()=>{load('bikespeedo.app.js');},
|
'< Load Bike Speedometer': ()=>{load('bikespeedo.app.js');},
|
||||||
'Barometer Altitude adjustment' : function() { E.showMenu(altdiffMenu); },
|
'Barometer Altitude adjustment' : function() { E.showMenu(altdiffMenu); },
|
||||||
'Kalman Filters' : function() { E.showMenu(kalMenu); }
|
'Kalman Filters' : function() { E.showMenu(kalMenu); },
|
||||||
|
'Speed units': {
|
||||||
|
value: !!settings.localeUnits,
|
||||||
|
format: b => b ? "Locale" : "km/h",
|
||||||
|
onchange: b => {
|
||||||
|
settings.localeUnits = b;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (global.WIDGETS && WIDGETS["recorder"]) {
|
||||||
|
appMenu[/*LANG*/"Record rides"] = {
|
||||||
|
value : !!settings.record,
|
||||||
|
onchange : v => {
|
||||||
|
settings.record = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
appMenu[/*LANG*/"Stop record on exit"] = {
|
||||||
|
value : !!settings.recordStopOnExit,
|
||||||
|
onchange : v => {
|
||||||
|
settings.recordStopOnExit = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const altdiffMenu = {
|
const altdiffMenu = {
|
||||||
'': { 'title': 'Altitude adjustment' },
|
'': { 'title': 'Altitude adjustment' },
|
||||||
'< Back': function() { E.showMenu(appMenu); },
|
'< Back': function() { E.showMenu(appMenu); },
|
||||||
|
|
|
@ -13,3 +13,4 @@
|
||||||
0.12: Mark dated events on a day
|
0.12: Mark dated events on a day
|
||||||
0.13: Switch to swipe left/right for month and up/down for year selection
|
0.13: Switch to swipe left/right for month and up/down for year selection
|
||||||
Display events for current month on touch
|
Display events for current month on touch
|
||||||
|
0.14: Add support for holidays
|
||||||
|
|
|
@ -10,6 +10,7 @@ Basic calendar
|
||||||
- Swipe down (Bangle.js 2 only) to go to the next year
|
- Swipe down (Bangle.js 2 only) to go to the next year
|
||||||
- Touch to display events for current month
|
- Touch to display events for current month
|
||||||
- Press the button (button 3 on Bangle.js 1) to exit
|
- Press the button (button 3 on Bangle.js 1) to exit
|
||||||
|
- Holidays have same color as weekends and can be edited with the 'Download'-interface, e.g. by uploading an iCalendar file.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ const white = "#ffffff";
|
||||||
const red = "#d41706";
|
const red = "#d41706";
|
||||||
const blue = "#0000ff";
|
const blue = "#0000ff";
|
||||||
const yellow = "#ffff00";
|
const yellow = "#ffff00";
|
||||||
|
const cyan = "#00ffff";
|
||||||
let bgColor = color4;
|
let bgColor = color4;
|
||||||
let bgColorMonth = color1;
|
let bgColorMonth = color1;
|
||||||
let bgColorDow = color2;
|
let bgColorDow = color2;
|
||||||
|
@ -23,6 +24,7 @@ let bgColorWeekend = color3;
|
||||||
let fgOtherMonth = gray1;
|
let fgOtherMonth = gray1;
|
||||||
let fgSameMonth = white;
|
let fgSameMonth = white;
|
||||||
let bgEvent = blue;
|
let bgEvent = blue;
|
||||||
|
let bgOtherEvent = "#ff8800";
|
||||||
const eventsPerDay=6; // how much different events per day we can display
|
const eventsPerDay=6; // how much different events per day we can display
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|
||||||
|
@ -36,9 +38,17 @@ const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a
|
||||||
date.setHours(time.h);
|
date.setHours(time.h);
|
||||||
date.setMinutes(time.m);
|
date.setMinutes(time.m);
|
||||||
date.setSeconds(time.s);
|
date.setSeconds(time.s);
|
||||||
return {date: date, msg: a.msg};
|
return {date: date, msg: a.msg, type: "e"};
|
||||||
|
});
|
||||||
|
// add holidays & other events
|
||||||
|
(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
|
||||||
|
const date = new Date(d.date);
|
||||||
|
const o = {date: date, msg: d.name, type: d.type};
|
||||||
|
if (d.repeat) {
|
||||||
|
o.repeat = d.repeat;
|
||||||
|
}
|
||||||
|
events.push(o);
|
||||||
});
|
});
|
||||||
events.sort((a,b) => a.date - b.date);
|
|
||||||
|
|
||||||
if (settings.ndColors === undefined) {
|
if (settings.ndColors === undefined) {
|
||||||
settings.ndColors = !g.theme.dark;
|
settings.ndColors = !g.theme.dark;
|
||||||
|
@ -52,68 +62,16 @@ if (settings.ndColors === true) {
|
||||||
fgOtherMonth = blue;
|
fgOtherMonth = blue;
|
||||||
fgSameMonth = black;
|
fgSameMonth = black;
|
||||||
bgEvent = color2;
|
bgEvent = color2;
|
||||||
|
bgOtherEvent = cyan;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDowLbls(locale) {
|
function getDowLbls(locale) {
|
||||||
let dowLbls;
|
let days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
|
||||||
//TODO: Find some clever way to generate this programmatically from locale lib
|
const d = new Date();
|
||||||
switch (locale) {
|
return days.map(i => {
|
||||||
case "de_AT":
|
d.setDate(d.getDate() + (i + 7 - d.getDay()) % 7);
|
||||||
case "de_CH":
|
return require("locale").dow(d, 1);
|
||||||
case "de_DE":
|
});
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "nl_NL":
|
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "fr_BE":
|
|
||||||
case "fr_CH":
|
|
||||||
case "fr_FR":
|
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "sv_SE":
|
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "it_CH":
|
|
||||||
case "it_IT":
|
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "oc_FR":
|
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (startOnSun) {
|
|
||||||
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
||||||
} else {
|
|
||||||
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return dowLbls;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameDay(d1, d2) {
|
function sameDay(d1, d2) {
|
||||||
|
@ -206,8 +164,13 @@ function drawCalendar(date) {
|
||||||
weekBeforeMonth.setDate(weekBeforeMonth.getDate() - 7);
|
weekBeforeMonth.setDate(weekBeforeMonth.getDate() - 7);
|
||||||
const week2AfterMonth = new Date(date.getFullYear(), date.getMonth()+1, 0);
|
const week2AfterMonth = new Date(date.getFullYear(), date.getMonth()+1, 0);
|
||||||
week2AfterMonth.setDate(week2AfterMonth.getDate() + 14);
|
week2AfterMonth.setDate(week2AfterMonth.getDate() + 14);
|
||||||
|
events.forEach(ev => {
|
||||||
|
if (ev.repeat === "y") {
|
||||||
|
ev.date.setFullYear(ev.date.getMonth() < 6 ? week2AfterMonth.getFullYear() : weekBeforeMonth.getFullYear());
|
||||||
|
}
|
||||||
|
});
|
||||||
const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
|
const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
|
||||||
|
eventsThisMonth.sort((a,b) => a.date - b.date);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (y = 0; y < rowN - 1; y++) {
|
for (y = 0; y < rowN - 1; y++) {
|
||||||
for (x = 0; x < colN; x++) {
|
for (x = 0; x < colN; x++) {
|
||||||
|
@ -220,6 +183,33 @@ function drawCalendar(date) {
|
||||||
const y1 = y * rowH + headerH + rowH;
|
const y1 = y * rowH + headerH + rowH;
|
||||||
const x2 = x * colW + colW;
|
const x2 = x * colW + colW;
|
||||||
const y2 = y * rowH + headerH + rowH + rowH;
|
const y2 = y * rowH + headerH + rowH + rowH;
|
||||||
|
|
||||||
|
if (eventsThisMonth.length > 0) {
|
||||||
|
// Display events for this day
|
||||||
|
eventsThisMonth.forEach((ev, idx) => {
|
||||||
|
if (sameDay(ev.date, curDay)) {
|
||||||
|
switch(ev.type) {
|
||||||
|
case "e": // alarm/event
|
||||||
|
const hour = ev.date.getHours() + ev.date.getMinutes()/60.0;
|
||||||
|
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
|
||||||
|
const height = (y2-2) - (y1+2); // height of a cell
|
||||||
|
const sliceHeight = height/eventsPerDay;
|
||||||
|
const ystart = (y1+2) + slice*sliceHeight;
|
||||||
|
g.setColor(bgEvent).fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
|
||||||
|
break;
|
||||||
|
case "h": // holiday
|
||||||
|
g.setColor(bgColorWeekend).fillRect(x1+1, y1+1, x2-1, y2-1);
|
||||||
|
break;
|
||||||
|
case "o": // other
|
||||||
|
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsThisMonth.splice(idx, 1); // this event is no longer needed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (isToday) {
|
if (isToday) {
|
||||||
g.setColor(red);
|
g.setColor(red);
|
||||||
g.drawRect(x1, y1, x2, y2);
|
g.drawRect(x1, y1, x2, y2);
|
||||||
|
@ -231,23 +221,6 @@ function drawCalendar(date) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventsThisMonth.length > 0) {
|
|
||||||
// Display events for this day
|
|
||||||
g.setColor(bgEvent);
|
|
||||||
eventsThisMonth.forEach((ev, idx) => {
|
|
||||||
if (sameDay(ev.date, curDay)) {
|
|
||||||
const hour = ev.date.getHours() + ev.date.getMinutes()/60.0;
|
|
||||||
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
|
|
||||||
const height = (y2-2) - (y1+2); // height of a cell
|
|
||||||
const sliceHeight = height/eventsPerDay;
|
|
||||||
const ystart = (y1+2) + slice*sliceHeight;
|
|
||||||
g.fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
|
|
||||||
|
|
||||||
eventsThisMonth.splice(idx, 1); // this event is no longer needed
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
require("Font8x12").add(Graphics);
|
require("Font8x12").add(Graphics);
|
||||||
g.setFont("8x12", fontSize);
|
g.setFont("8x12", fontSize);
|
||||||
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
|
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
|
||||||
|
@ -286,10 +259,11 @@ function setUI() {
|
||||||
},
|
},
|
||||||
btn: (n) => n === (process.env.HWVERSION === 2 ? 1 : 3) && load(),
|
btn: (n) => n === (process.env.HWVERSION === 2 ? 1 : 3) && load(),
|
||||||
touch: (n,e) => {
|
touch: (n,e) => {
|
||||||
|
events.sort((a,b) => a.date - b.date);
|
||||||
const menu = events.filter(ev => ev.date.getFullYear() === date.getFullYear() && ev.date.getMonth() === date.getMonth()).map(e => {
|
const menu = events.filter(ev => ev.date.getFullYear() === date.getFullYear() && ev.date.getMonth() === date.getMonth()).map(e => {
|
||||||
const dateStr = require("locale").date(e.date, 1);
|
const dateStr = require("locale").date(e.date, 1);
|
||||||
const timeStr = require("locale").time(e.date, 1);
|
const timeStr = require("locale").time(e.date, 1);
|
||||||
return { title: `${dateStr} ${timeStr}` + (e.msg ? " " + e.msg : "") };
|
return { title: `${dateStr} ${e.type === "e" ? timeStr : ""}` + (e.msg ? " " + e.msg : "") };
|
||||||
});
|
});
|
||||||
if (menu.length === 0) {
|
if (menu.length === 0) {
|
||||||
menu.push({title: /*LANG*/"No events"});
|
menu.push({title: /*LANG*/"No events"});
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
|
||||||
|
<script>
|
||||||
|
let dataElement = document.getElementById("data");
|
||||||
|
let holidays;
|
||||||
|
|
||||||
|
function sameDay(d1, d2) {
|
||||||
|
return d1.getFullYear() === d2.getFullYear() &&
|
||||||
|
d1.getMonth() === d2.getMonth() &&
|
||||||
|
d1.getDate() === d2.getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
holidays.sort((a,b) => new Date(a.date) - new Date(b.date));
|
||||||
|
document.getElementById('events').innerHTML = "";
|
||||||
|
holidays.forEach(holiday => {
|
||||||
|
renderHoliday(holiday);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFile(input) {
|
||||||
|
document.getElementById('upload').disabled = true;
|
||||||
|
|
||||||
|
for(let i=0; i<input.files.length; i++) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => {
|
||||||
|
const jCalData = ICAL.parse(reader.result);
|
||||||
|
const comp = new ICAL.Component(jCalData);
|
||||||
|
// Fetch the VEVENT part
|
||||||
|
comp.getAllSubcomponents('vevent').forEach(vevent => {
|
||||||
|
event = new ICAL.Event(vevent);
|
||||||
|
holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists
|
||||||
|
|
||||||
|
const holiday = eventToHoliday(event);
|
||||||
|
|
||||||
|
holidays.push(holiday);
|
||||||
|
});
|
||||||
|
|
||||||
|
render();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
reader.readAsText(input.files[i], "UTF-8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventToHoliday(event) {
|
||||||
|
const date = event.startDate.toJSDate();
|
||||||
|
|
||||||
|
const holiday = {
|
||||||
|
date: formatDate(date),
|
||||||
|
name: event.summary,
|
||||||
|
type: 'h',
|
||||||
|
};
|
||||||
|
return holiday;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(d) {
|
||||||
|
return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload() {
|
||||||
|
Util.showModal("Saving...");
|
||||||
|
Util.writeStorage("calendar.days.json", JSON.stringify(holidays), () => {
|
||||||
|
location.reload(); // reload so we see current data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHoliday(holiday) {
|
||||||
|
const localDate = new Date(holiday.date);
|
||||||
|
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.classList.add('event-row');
|
||||||
|
const tdTime = document.createElement('td');
|
||||||
|
tr.appendChild(tdTime);
|
||||||
|
const inputTime = document.createElement('input');
|
||||||
|
inputTime.type = "date";
|
||||||
|
inputTime.classList.add('event-date');
|
||||||
|
inputTime.classList.add('form-input');
|
||||||
|
inputTime.value = formatDate(localDate)
|
||||||
|
inputTime.onchange = (e => {
|
||||||
|
const date = new Date(inputTime.value);
|
||||||
|
holiday.date = formatDate(date);
|
||||||
|
});
|
||||||
|
tdTime.appendChild(inputTime);
|
||||||
|
|
||||||
|
const tdSummary = document.createElement('td');
|
||||||
|
tr.appendChild(tdSummary);
|
||||||
|
const inputSummary = document.createElement('input');
|
||||||
|
inputSummary.type = "text";
|
||||||
|
inputSummary.classList.add('event-summary');
|
||||||
|
inputSummary.classList.add('form-input');
|
||||||
|
inputSummary.maxLength=40;
|
||||||
|
const summary = (holiday.name?.substring(0, inputSummary.maxLength) || "");
|
||||||
|
inputSummary.value = summary;
|
||||||
|
inputSummary.onchange = (e => {
|
||||||
|
holiday.name = inputSummary.value;
|
||||||
|
});
|
||||||
|
tdSummary.appendChild(inputSummary);
|
||||||
|
inputSummary.onchange();
|
||||||
|
|
||||||
|
const tdType = document.createElement('td');
|
||||||
|
tr.appendChild(tdType);
|
||||||
|
const selectType = document.createElement("select");
|
||||||
|
selectType.classList.add('form-select');
|
||||||
|
tdType.prepend(selectType);
|
||||||
|
const optionHoliday = document.createElement("option");
|
||||||
|
optionHoliday.text = "Holiday";
|
||||||
|
optionHoliday.value = "h";
|
||||||
|
optionHoliday.selected = holiday.type === "h";
|
||||||
|
selectType.add(optionHoliday);
|
||||||
|
const optionOther = document.createElement("option");
|
||||||
|
optionOther.text = "Other";
|
||||||
|
optionOther.value = "o";
|
||||||
|
optionOther.selected = holiday.type === "o";
|
||||||
|
selectType.add(optionOther);
|
||||||
|
selectType.onchange = (e => {
|
||||||
|
holiday.type = e.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tdRepeat = document.createElement('td');
|
||||||
|
tr.appendChild(tdRepeat);
|
||||||
|
const selectRepeat = document.createElement("select");
|
||||||
|
selectRepeat.classList.add('form-select');
|
||||||
|
tdRepeat.prepend(selectRepeat);
|
||||||
|
const optionNever = document.createElement("option");
|
||||||
|
optionNever.text = "Never";
|
||||||
|
optionNever.selected = !holiday.repeat;
|
||||||
|
selectRepeat.add(optionNever);
|
||||||
|
const optionYearly = document.createElement("option");
|
||||||
|
optionYearly.text = "Yearly";
|
||||||
|
optionYearly.value = "y";
|
||||||
|
optionYearly.selected = holiday.repeat === "y";
|
||||||
|
selectRepeat.add(optionYearly);
|
||||||
|
selectRepeat.onchange = (e => {
|
||||||
|
holiday.repeat = e.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tdAction = document.createElement('td');
|
||||||
|
tr.appendChild(tdAction);
|
||||||
|
|
||||||
|
const buttonDelete = document.createElement('button');
|
||||||
|
buttonDelete.classList.add('btn');
|
||||||
|
buttonDelete.classList.add('btn-action');
|
||||||
|
tdAction.prepend(buttonDelete);
|
||||||
|
const iconDelete = document.createElement('i');
|
||||||
|
iconDelete.classList.add('icon');
|
||||||
|
iconDelete.classList.add('icon-delete');
|
||||||
|
buttonDelete.appendChild(iconDelete);
|
||||||
|
buttonDelete.onclick = (e => {
|
||||||
|
holidays = holidays.filter(a => a !== holiday);
|
||||||
|
document.getElementById('events').removeChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('events').appendChild(tr);
|
||||||
|
document.getElementById('upload').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHoliday() {
|
||||||
|
const holiday = {date: formatDate(new Date()), type: 'h'};
|
||||||
|
renderHoliday(holiday);
|
||||||
|
holidays.push(holiday);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData() {
|
||||||
|
Util.showModal("Loading...");
|
||||||
|
Puck.write(`\x10(function() {
|
||||||
|
Bluetooth.print(JSON.stringify(require("Storage").list("calendar.days.json").sort()));
|
||||||
|
})()\n`, contents => {
|
||||||
|
const fileNames = JSON.parse(contents);
|
||||||
|
if (fileNames.length > 0) {
|
||||||
|
Util.readStorage('calendar.days.json',data=>{
|
||||||
|
holidays = JSON.parse(data || "[]") || [];
|
||||||
|
|
||||||
|
Util.hideModal();
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
holidays = [];
|
||||||
|
Util.hideModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when app starts
|
||||||
|
function onInit() {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h4 class="float-left">Holidays</h4>
|
||||||
|
|
||||||
|
<div class="float-right">
|
||||||
|
<button class="btn" onclick="addHoliday();">
|
||||||
|
<i class="icon icon-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-scroll" style="clear:both;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Holiday</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Repeat</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="events">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-5 col-xs-12">
|
||||||
|
<label class="form-label" for="fileinput">Add from iCalendar file</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-7 col-xs-12">
|
||||||
|
<input id="fileinput" class="form-input" type="file" onchange="readFile(this)" accept=".ics,.ifb,.ical,.ifbf" multiple/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<button id="upload" class="btn btn-primary" onClick="upload()" disabled>Upload</button>
|
||||||
|
<button id="reload" class="btn" onClick="location.reload()">Reload</button>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "calendar",
|
"id": "calendar",
|
||||||
"name": "Calendar",
|
"name": "Calendar",
|
||||||
"version": "0.13",
|
"version": "0.14",
|
||||||
"description": "Simple calendar",
|
"description": "Simple calendar",
|
||||||
"icon": "calendar.png",
|
"icon": "calendar.png",
|
||||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||||
|
@ -9,10 +9,11 @@
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
|
"interface": "interface.html",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"calendar.app.js","url":"calendar.js"},
|
{"name":"calendar.app.js","url":"calendar.js"},
|
||||||
{"name":"calendar.settings.js","url":"settings.js"},
|
{"name":"calendar.settings.js","url":"settings.js"},
|
||||||
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
|
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"data": [{"name":"calendar.json"}]
|
"data": [{"name":"calendar.json"}, {"name":"calendar.days.json"}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: First version
|
|
@ -0,0 +1,11 @@
|
||||||
|
# RAM Clock Info
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A clock info that displays the % memory used
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
After Width: | Height: | Size: 206 B |
|
@ -0,0 +1,61 @@
|
||||||
|
(function () {
|
||||||
|
var timeout;
|
||||||
|
|
||||||
|
var debug = function(o) {
|
||||||
|
//console.log(o);
|
||||||
|
};
|
||||||
|
|
||||||
|
var clearTimer = function() {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = undefined;
|
||||||
|
debug("timer cleared");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var queueRedraw = function() {
|
||||||
|
clearTimer();
|
||||||
|
timeout = setTimeout(function() {
|
||||||
|
timeout = undefined;
|
||||||
|
queueRedraw();
|
||||||
|
}, 60000);
|
||||||
|
info.items[0].emit("redraw");
|
||||||
|
debug("queued");
|
||||||
|
};
|
||||||
|
|
||||||
|
var img = function() {
|
||||||
|
return atob("GBgBAAAAAAAAAAAAB//gD//wH//4HgB4HAA4HAA4HAA4HDw4HDw4HDw4HDw4HAA4HAA4HAA4HgB4H//4D//wB//gAAAAAAAAAAAA");
|
||||||
|
};
|
||||||
|
|
||||||
|
var text = function() {
|
||||||
|
var val = process.memory(false);
|
||||||
|
return '' + Math.round(val.usage*100 / val.total) + '%';
|
||||||
|
};
|
||||||
|
|
||||||
|
var info = {
|
||||||
|
name: "Bangle",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: "ram",
|
||||||
|
get: function () { return ({
|
||||||
|
img: img(),
|
||||||
|
text: text()
|
||||||
|
}); },
|
||||||
|
run : function() {
|
||||||
|
debug("run");
|
||||||
|
queueRedraw();
|
||||||
|
},
|
||||||
|
show: function () {
|
||||||
|
debug("show");
|
||||||
|
this.run();
|
||||||
|
},
|
||||||
|
hide: function() {
|
||||||
|
debug("hide");
|
||||||
|
clearTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return info;
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"id": "clkinfom",
|
||||||
|
"name": "RAM Clock Info",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Clockinfo that displays % used memory",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clkinfo",
|
||||||
|
"tags": "clkinfo",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"readme":"README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"ram.clkinfo.js","url":"clkinfo.js"}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -5,7 +5,7 @@
|
||||||
"version": "0.08",
|
"version": "0.08",
|
||||||
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
|
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
|
||||||
"icon": "icons8-cycling-48.png",
|
"icon": "icons8-cycling-48.png",
|
||||||
"tags": "outdoors,exercise,ble,bluetooth",
|
"tags": "outdoors,exercise,ble,bluetooth,bike,cycle,bicycle",
|
||||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
Logs health data to a file in a defined interval, and provides an app to view it
|
Logs health data to a file in a defined interval, and provides an app to view it
|
||||||
|
|
||||||
**BETA - requires firmware 2v11 or later**
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Once installed, health data is logged automatically.
|
Once installed, health data is logged automatically.
|
||||||
|
|
|
@ -217,7 +217,7 @@ exports.buzz = function(msgSrc) {
|
||||||
if (repeat===undefined) repeat = 4; // repeat may be zero
|
if (repeat===undefined) repeat = 4; // repeat may be zero
|
||||||
if (repeat)
|
if (repeat)
|
||||||
{
|
{
|
||||||
exports.buzzTimeout = setInterval(() => require("buzz").pattern(pattern), repeat*1000);
|
exports.buzzInterval = setInterval(() => require("buzz").pattern(pattern), repeat*1000);
|
||||||
let vibrateTimeout = msgSettings.vibrateTimeout;
|
let vibrateTimeout = msgSettings.vibrateTimeout;
|
||||||
if (vibrateTimeout===undefined) vibrateTimeout = 60;
|
if (vibrateTimeout===undefined) vibrateTimeout = 60;
|
||||||
if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000);
|
if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000);
|
||||||
|
@ -228,8 +228,8 @@ exports.buzz = function(msgSrc) {
|
||||||
* Stop buzzing
|
* Stop buzzing
|
||||||
*/
|
*/
|
||||||
exports.stopBuzz = function() {
|
exports.stopBuzz = function() {
|
||||||
if (exports.buzzTimeout) clearTimeout(exports.buzzTimeout);
|
if (exports.buzzInterval) clearInterval(exports.buzzInterval);
|
||||||
delete exports.buzzTimeout;
|
delete exports.buzzInterval;
|
||||||
if (exports.stopTimeout) clearTimeout(exports.stopTimeout);
|
if (exports.stopTimeout) clearTimeout(exports.stopTimeout);
|
||||||
delete exports.stopTimeout;
|
delete exports.stopTimeout;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Show the date inside the widget bar
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4UA///8H55lCsHZHmEKgEVqoLIBQNVqgLGh4KBAANQBAUQBY1VoApBB4QLFDYoL/Bf4L/BbLrBBZAKBgEBBY0KAQIABgoLCCYQLEgEVBQYLGAAoL/Bf4LPAFw"))
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,89 @@
|
||||||
|
Graphics.prototype.setFontAudiowide = function() {
|
||||||
|
// Actual height 33 (36 - 4)
|
||||||
|
return this.setFontCustom(
|
||||||
|
E.toString(require('heatshrink').decompress(atob('AB/wAgcB/AFVgFgHbkYAok4AogvEgYFEg4FEj4FEn4FE//gKQf/4AcD/4QDh/8Djf+DhN/T4YcFgYcKh4cEh68Eh4cDAoOAAocORYkMf1JxBIYcf/6PDn//MIYEB/5KBOIIABKwIFFO4V/UQMHEIMfFQMHAQP3AQJ3BDIKABh/ggf7ApHAg/5AonxAocPAokf8IFE4IFDn4FEv+BAokBAof/AofB/wFE/gFD4YFE4/4AohgBAoXPAonvMAIFD4AFCVgIFBQYX3wCGCR4T+CTYqtLX4rLC/zXIcYoAQQYIFiJoR9CArgAlToIpDRQIFDSwI7C4CiBApN/Apb1D4F+Av4Fd8H+Aof/AoaTB/gFIgaBBAoSrB+AFCgF/8AFDAESP/Av3wv0HZYYABYoYAB+AFGZYIAB8DLCAAPAZYQFBZUhHC/gFE/wFaAAN+Av4Fqv53EboYFdAFIvB4EBGofwAon4Aon8ApX+AofAAot+Av4Fev8DAojFDAo0/S4IFGAAMf//gV4mAAoUD/zYgFwP8AoRGB/4FCAgI1CgIFC4A5BAoRHBg4FCKYMH/l+n5fC+F+g5rC8F+PoYFFZf7XVw7XNAALXNTYLXCVoYAQF4IFZjAFEnAFELIZCBAojRDAoMfAol/AohrCAoJfBNYIFBNYOAAoUf/xBDv/8AoXBRAcP4aCDh/PDgSNCDgQFCHIIFDUoafFAoJ3EGYQFCDgYFBXgZuBGYQAba4pDEhzvE/4ABKoMBAogbBAAJKBg4EBw4FEX4Z9BgIFC8AFE4F+Av5HFKYhfFAoRxCO4qDFgF/AokATYgfCZwcD/zTdAAV/Z4RBCHIZNBJYI5D/gFFOJEP+DF/a7N+ZYQFG+F+g7XFRYIFFbobLBboajCAoTRCcYTiEUQYAdgYCBsACBMwJlCAqUHJYLxDAAMgHSQ'))),
|
||||||
|
46,
|
||||||
|
atob("CiAsESQjJSQkHyQkDA=="),
|
||||||
|
48|65536
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
let img = require("Storage").read("patriotclk.bg.img");
|
||||||
|
let options = require("Storage").readJSON("patriotclk.opts",1)||{};
|
||||||
|
|
||||||
|
// timeout used to update every minute
|
||||||
|
let drawTimeout, widgetTimeout;
|
||||||
|
|
||||||
|
// draw everything
|
||||||
|
let draw = function() {
|
||||||
|
var x = g.getWidth()/2;
|
||||||
|
var y;
|
||||||
|
if (options.bottomText)
|
||||||
|
y = g.getHeight() - 24;
|
||||||
|
else
|
||||||
|
y = 4+(g.getHeight())/2; // middle
|
||||||
|
g.reset();
|
||||||
|
g.drawImage(img,0,0);
|
||||||
|
// work out locale-friendly date/time
|
||||||
|
var date = new Date();
|
||||||
|
var timeStr = require("locale").time(date,1);
|
||||||
|
var dateStr = require("locale").date(date);
|
||||||
|
// draw time
|
||||||
|
g.setFontAlign(0,0).setFont("Audiowide");
|
||||||
|
// draw a shadow by shifting left/right/up/down
|
||||||
|
g.drawString(timeStr,x-6,y);
|
||||||
|
g.drawString(timeStr,x+6,y);
|
||||||
|
g.drawString(timeStr,x,y-6);
|
||||||
|
g.drawString(timeStr,x,y+6);
|
||||||
|
g.drawString(timeStr,x-4,y+4);
|
||||||
|
g.drawString(timeStr,x+4,y+4);
|
||||||
|
g.drawString(timeStr,x-4,y-4);
|
||||||
|
g.drawString(timeStr,x+4,y-4);
|
||||||
|
// finally draw in the foreground color
|
||||||
|
g.setColor(g.theme.bg).drawString(timeStr,x,y);
|
||||||
|
// draw date
|
||||||
|
//y += 35;
|
||||||
|
//g.setFontAlign(0,0).setFont("6x8").g.setColor(g.theme.fg);
|
||||||
|
//g.drawString(dateStr,x,y);
|
||||||
|
// queue draw in one minute
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI({mode:"clock", remove:function() { //f ree memory
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
if (widgetTimeout) clearTimeout(widgetTimeout);
|
||||||
|
require("widget_utils").show();
|
||||||
|
var e = WIDGETS["patriot"];
|
||||||
|
g.reset().clearRect(e.x,e.y,e.x+63,e.y+23);
|
||||||
|
delete WIDGETS["patriot"];
|
||||||
|
delete Graphics.prototype.setFontAudiowide;
|
||||||
|
}});
|
||||||
|
// Load widgets (make them swipeable)
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
WIDGETS["patriot"] = {
|
||||||
|
area:"tl",
|
||||||
|
width: 64, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||||
|
draw : function(e) {
|
||||||
|
g.reset().clearRect(e.x,e.y,e.x+63,e.y+23);
|
||||||
|
var d = new Date();
|
||||||
|
g.setFont("6x8").setFontAlign(-1,0).drawString(require("locale").dow(d,0), e.x+2, e.y+8);
|
||||||
|
g.setFont("6x8").setFontAlign(-1,0).drawString(require("locale").date(d).trim(), e.x+2, e.y+16);
|
||||||
|
widgetTimeout = setTimeout(function() { // redraw every hour (it's just easier that working out timezones)
|
||||||
|
widgetTimeout = undefined;
|
||||||
|
WIDGETS["patriot"].draw(WIDGETS["patriot"]);
|
||||||
|
}, 3600000 - (Date.now() % 3600000));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
require("widget_utils").swipeOn();
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
g.clear();
|
||||||
|
// draw immediately at first, queue update
|
||||||
|
draw();
|
||||||
|
}
|
After Width: | Height: | Size: 446 B |
|
@ -0,0 +1,143 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<style>
|
||||||
|
.flag {
|
||||||
|
width : 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#preview {
|
||||||
|
width : 176px;
|
||||||
|
height: 176px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Please choose your country's flag:</p>
|
||||||
|
<div id="flaglist"></div>
|
||||||
|
<p>Is your flag not here? Please <a href="https://github.com/espruino/BangleApps/tree/master/apps/patriotclk/img" target="_blank">add it here</a>! </p>
|
||||||
|
<div style="float:right">Preview:<br/><canvas width="176" height="176" id="preview"></canvas></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-switch">
|
||||||
|
<input type="checkbox" id="box_zoom">
|
||||||
|
<i class="form-icon"></i> Zoom
|
||||||
|
</label>
|
||||||
|
<label class="form-switch">
|
||||||
|
<input type="checkbox" id="box_textbottom">
|
||||||
|
<i class="form-icon"></i> Time at bottom
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Click <button id="upload" class="btn btn-primary disabled" style>Upload</button></p>
|
||||||
|
|
||||||
|
<img id="preview_overlay" src="app-preview.png" style="display:none">
|
||||||
|
<img id="preview_overlay_btm" src="app-preview-btm.png" style="display:none">
|
||||||
|
|
||||||
|
|
||||||
|
<script src="../../core/lib/customize.js"></script>
|
||||||
|
<script src="../../webtools/imageconverter.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var FLAGS = `
|
||||||
|
icons8-australia-480.png
|
||||||
|
icons8-austria-480.png
|
||||||
|
icons8-belgium-480.png
|
||||||
|
icons8-brazil-480.png
|
||||||
|
icons8-canada-480.png
|
||||||
|
icons8-china-480.png
|
||||||
|
icons8-denmark-480.png
|
||||||
|
icons8-england-480.png
|
||||||
|
icons8-flag-of-europe-480.png
|
||||||
|
icons8-france-480.png
|
||||||
|
icons8-germany-480.png
|
||||||
|
icons8-great-britain-480.png
|
||||||
|
icons8-greece-480.png
|
||||||
|
icons8-hungary-480.png
|
||||||
|
icons8-italy-480.png
|
||||||
|
icons8-lgbt-flag-480.png
|
||||||
|
icons8-netherlands-480.png
|
||||||
|
icons8-new-zealand-480.png
|
||||||
|
icons8-norway-480.png
|
||||||
|
icons8-scotland-480.png
|
||||||
|
icons8-spain-480.png
|
||||||
|
icons8-sweden-480.png
|
||||||
|
icons8-switzerland-480.png
|
||||||
|
icons8-ukraine-480.png
|
||||||
|
icons8-usa-480.png
|
||||||
|
icons8-wales-480.png
|
||||||
|
`.trim().split("\n").map(s=>s.trim());
|
||||||
|
// If we can't map a flag direct to 3 bit color, enable dithering
|
||||||
|
var DITHERED_FLAGS = `
|
||||||
|
icons8-lgbt-flag-480.png
|
||||||
|
icons8-ukraine-480.png
|
||||||
|
`.trim().split("\n").map(s=>s.trim());
|
||||||
|
|
||||||
|
var selectedImage;
|
||||||
|
var bgImageData;
|
||||||
|
var clockOptions = {
|
||||||
|
bottomText : false
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("flaglist").innerHTML =
|
||||||
|
FLAGS.map(f => `<img class="flag" src="img/${f}" data-file="${f}"/>`).join("\n");
|
||||||
|
var elements = document.querySelectorAll(".flag");
|
||||||
|
for (var i=0;i<elements.length;i++)
|
||||||
|
elements[i].addEventListener("click", function(e) {
|
||||||
|
selectedImage = e.target;
|
||||||
|
drawPreview();
|
||||||
|
document.getElementById("upload").classList.remove("disabled")
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function drawPreview() {
|
||||||
|
if (!selectedImage) return;
|
||||||
|
var imgFile = selectedImage.getAttribute("data-file");
|
||||||
|
var zoom = document.getElementById("box_zoom").checked;
|
||||||
|
clockOptions.bottomText = document.getElementById("box_textbottom").checked;
|
||||||
|
const canvas = document.getElementById("preview");
|
||||||
|
canvas.width = 176; // setting size clears canvas
|
||||||
|
canvas.height = 176;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
var y = 0;
|
||||||
|
if (clockOptions.bottomText)
|
||||||
|
y = -28;
|
||||||
|
if (zoom)
|
||||||
|
ctx.drawImage(selectedImage, 90, 90, 300, 300, 0, y, 176, 176);
|
||||||
|
else
|
||||||
|
ctx.drawImage(selectedImage, 20, 20, 440, 440, 0, y, 176, 176);
|
||||||
|
var options = {
|
||||||
|
mode:"3bit",
|
||||||
|
output:"raw",
|
||||||
|
compression:false,
|
||||||
|
updateCanvas:true,
|
||||||
|
transparent:false,
|
||||||
|
contrast:128
|
||||||
|
};
|
||||||
|
if (DITHERED_FLAGS.includes(imgFile))
|
||||||
|
options.diffusion = "bayer2";
|
||||||
|
bgImageData = imageconverter.canvastoString(canvas, options);
|
||||||
|
ctx.drawImage(document.getElementById(clockOptions.bottomText?"preview_overlay_btm":"preview_overlay"),0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If options changed
|
||||||
|
document.getElementById("box_zoom").addEventListener("click", function() {
|
||||||
|
drawPreview();
|
||||||
|
});
|
||||||
|
document.getElementById("box_textbottom").addEventListener("click", function() {
|
||||||
|
drawPreview();
|
||||||
|
});
|
||||||
|
// When the 'upload' button is clicked...
|
||||||
|
document.getElementById("upload").addEventListener("click", function() {
|
||||||
|
// send finished app (in addition to contents of metadata.json)
|
||||||
|
if (bgImageData) sendCustomizedApp({
|
||||||
|
storage:[
|
||||||
|
{name:"patriotclk.bg.img", content:bgImageData},
|
||||||
|
{name:"patriotclk.opts", content:JSON.stringify(clockOptions)}, // we don't save as .json or we'd have a sanitycheck warning
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
Flags
|
||||||
|
------
|
||||||
|
|
||||||
|
These flags come from https://icons8.com/icon/set/flags/color and are 480x480px
|
||||||
|
|
||||||
|
If you want to add your own flag ensure it's in the same style and then also list the image file in custom.html in the root directory.
|
||||||
|
|
||||||
|
If your flag is listed in https://icons8.com/icon/set/flags/color and you can't download it in the right size, please file an issue and we'll download it with our account.
|
||||||
|
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 49 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
{ "id": "patriotclk",
|
||||||
|
"name": "Patriotic Clock",
|
||||||
|
"shortName":"Patriot",
|
||||||
|
"version":"0.02",
|
||||||
|
"description": "Show your Patriotism with your Country's flag as a clock face (configurable). Swipe down to show widgets and date.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock,flag",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"custom": "custom.html",
|
||||||
|
"storage": [
|
||||||
|
{"name":"patriotclk.app.js","url":"app.js"},
|
||||||
|
{"name":"patriotclk.bg.img"},
|
||||||
|
{"name":"patriotclk.opts"},
|
||||||
|
{"name":"patriotclk.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Trim old entries from the popcon app cache
|
||||||
|
|
|
@ -3,6 +3,6 @@ Popcon
|
||||||
|
|
||||||
Display apps sorted by regular use. No config - install the app and all your launchers will sort apps by most popular, based off launch counts within the last month, and then sort them by the most recently launched app.
|
Display apps sorted by regular use. No config - install the app and all your launchers will sort apps by most popular, based off launch counts within the last month, and then sort them by the most recently launched app.
|
||||||
|
|
||||||
:warning: Warning: this app overrides [`Storage.readJSON`], so may slow down your watch when using apps that perform I/O.
|
⚠️ Warning: this app overrides [`Storage.readJSON`], so may slow down your watch when using apps that perform I/O.
|
||||||
|
|
||||||
[`Storage.readJSON`]: https://www.espruino.com/ReferenceBANGLEJS2#l_Storage_readJSON
|
[`Storage.readJSON`]: https://www.espruino.com/ReferenceBANGLEJS2#l_Storage_readJSON
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
var oldRead_1 = require("Storage").readJSON;
|
var oldRead_1 = require("Storage").readJSON;
|
||||||
var monthAgo_1 = Date.now() - 1000 * 86400 * 28;
|
var oneMonth_1 = 1000 * 86400 * 28;
|
||||||
|
var monthAgo_1 = Date.now() - oneMonth_1;
|
||||||
var cache_1;
|
var cache_1;
|
||||||
var ensureCache_1 = function () {
|
var ensureCache_1 = function () {
|
||||||
if (!cache_1) {
|
if (!cache_1) {
|
||||||
|
@ -10,8 +11,20 @@
|
||||||
}
|
}
|
||||||
return cache_1;
|
return cache_1;
|
||||||
};
|
};
|
||||||
var saveCache_1 = function (orderChanged) {
|
var trimCache_1 = function (cache) {
|
||||||
require("Storage").writeJSON("popcon.cache.json", cache_1);
|
var threeMonthsBack = Date.now() - oneMonth_1 * 3;
|
||||||
|
var del = [];
|
||||||
|
for (var k in cache)
|
||||||
|
if (cache[k].last < threeMonthsBack)
|
||||||
|
del.push(k);
|
||||||
|
for (var _i = 0, del_1 = del; _i < del_1.length; _i++) {
|
||||||
|
var k = del_1[_i];
|
||||||
|
delete cache[k];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var saveCache_1 = function (cache, orderChanged) {
|
||||||
|
trimCache_1(cache);
|
||||||
|
require("Storage").writeJSON("popcon.cache.json", cache);
|
||||||
if (orderChanged) {
|
if (orderChanged) {
|
||||||
var info = oldRead_1("popcon.info", true);
|
var info = oldRead_1("popcon.info", true);
|
||||||
info.cacheBuster = !info.cacheBuster;
|
info.cacheBuster = !info.cacheBuster;
|
||||||
|
@ -74,7 +87,7 @@
|
||||||
ent.pop++;
|
ent.pop++;
|
||||||
ent.last = Date.now();
|
ent.last = Date.now();
|
||||||
var orderChanged = sortCache_1();
|
var orderChanged = sortCache_1();
|
||||||
saveCache_1(orderChanged);
|
saveCache_1(cache_3, orderChanged);
|
||||||
}
|
}
|
||||||
return oldLoad_1(src);
|
return oldLoad_1(src);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
type Timestamp = number;
|
type Timestamp = number;
|
||||||
|
type Cache = {
|
||||||
const oldRead = require("Storage").readJSON;
|
|
||||||
const monthAgo = Date.now() - 1000 * 86400 * 28;
|
|
||||||
let cache: undefined | {
|
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
sortorder: number,
|
sortorder: number,
|
||||||
pop: number, // amount of launches
|
pop: number, // amount of launches
|
||||||
|
@ -11,6 +8,11 @@ let cache: undefined | {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const oldRead = require("Storage").readJSON;
|
||||||
|
const oneMonth = 1000 * 86400 * 28;
|
||||||
|
const monthAgo = Date.now() - oneMonth;
|
||||||
|
let cache: undefined | Cache;
|
||||||
|
|
||||||
const ensureCache = (): NonNull<typeof cache> => {
|
const ensureCache = (): NonNull<typeof cache> => {
|
||||||
if(!cache){
|
if(!cache){
|
||||||
cache = oldRead("popcon.cache.json", true);
|
cache = oldRead("popcon.cache.json", true);
|
||||||
|
@ -20,7 +22,19 @@ const ensureCache = (): NonNull<typeof cache> => {
|
||||||
return cache;
|
return cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveCache = (orderChanged: boolean) => {
|
const trimCache = (cache: Cache) => {
|
||||||
|
const threeMonthsBack = Date.now() - oneMonth * 3;
|
||||||
|
const del = [];
|
||||||
|
for(const k in cache)
|
||||||
|
if(cache[k]!.last < threeMonthsBack)
|
||||||
|
del.push(k);
|
||||||
|
|
||||||
|
for(const k of del)
|
||||||
|
delete cache[k];
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveCache = (cache: Cache, orderChanged: boolean) => {
|
||||||
|
trimCache(cache);
|
||||||
require("Storage").writeJSON("popcon.cache.json", cache);
|
require("Storage").writeJSON("popcon.cache.json", cache);
|
||||||
if(orderChanged){
|
if(orderChanged){
|
||||||
// ensure launchers reload their caches:
|
// ensure launchers reload their caches:
|
||||||
|
@ -94,7 +108,7 @@ global.load = (src: string) => {
|
||||||
ent.pop++;
|
ent.pop++;
|
||||||
ent.last = Date.now();
|
ent.last = Date.now();
|
||||||
const orderChanged = sortCache();
|
const orderChanged = sortCache();
|
||||||
saveCache(orderChanged);
|
saveCache(cache, orderChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
return oldLoad(src);
|
return oldLoad(src);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "popconlaunch",
|
"id": "popconlaunch",
|
||||||
"name": "Popcon Launcher",
|
"name": "Popcon Launcher",
|
||||||
"shortName": "Popcon",
|
"shortName": "Popcon",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch",
|
"description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -9,4 +9,4 @@
|
||||||
0.07: Convert Yes/No On/Off in settings to checkboxes
|
0.07: Convert Yes/No On/Off in settings to checkboxes
|
||||||
0.08: Fix the wrapping of intervals/timeouts with parameters
|
0.08: Fix the wrapping of intervals/timeouts with parameters
|
||||||
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called
|
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called
|
||||||
0.09: Cache the app-launch info
|
0.09: Accidental version bump
|
|
@ -1 +1,2 @@
|
||||||
0.01: Initial release
|
0.01: Initial release
|
||||||
|
0.02: Cache the app-launch info
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "ratchet_launch",
|
"id": "ratchet_launch",
|
||||||
"name": "Ratchet Launcher",
|
"name": "Ratchet Launcher",
|
||||||
"shortName": "Ratchet",
|
"shortName": "Ratchet",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Launcher with discrete scrolling for quicker app selection",
|
"description": "Launcher with discrete scrolling for quicker app selection",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "launch",
|
"type": "launch",
|
||||||
|
|
|
@ -15,10 +15,11 @@ You can record
|
||||||
|
|
||||||
* **Time** The current time
|
* **Time** The current time
|
||||||
* **GPS** GPS Latitude, Longitude and Altitude
|
* **GPS** GPS Latitude, Longitude and Altitude
|
||||||
* **Steps** Steps counted by the step counter
|
|
||||||
* **HR** Heart rate and confidence
|
* **HR** Heart rate and confidence
|
||||||
* **BAT** Battery percentage and voltage
|
* **BAT** Battery percentage and voltage
|
||||||
* **Core** CoreTemp body temperature
|
* **Steps** Steps counted by the step counter
|
||||||
|
* **Baro** (Bangle.js 2) Using the built-in barometer to record Temperature, Pressure and Altitude
|
||||||
|
* **Core** CoreTemp body temperature *if* you have a CoreTemp device and the https://banglejs.com/apps/?id=coretemp app installed
|
||||||
|
|
||||||
You can then start/stop recording from the Recorder app itself (and as long as widgets are
|
You can then start/stop recording from the Recorder app itself (and as long as widgets are
|
||||||
enabled in the app you're using, you can move to another app and continue recording).
|
enabled in the app you're using, you can move to another app and continue recording).
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||||
<script src="../../core/lib/interface.js"></script>
|
<script src="../../core/lib/interface.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/0.0.3/ical.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
let dataElement = document.getElementById("data");
|
let dataElement = document.getElementById("data");
|
||||||
let alarms;
|
let alarms;
|
||||||
|
@ -17,7 +17,7 @@ function readFile(input) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.addEventListener("load", () => {
|
reader.addEventListener("load", () => {
|
||||||
const jCalData = ICAL.parse(reader.result);
|
const jCalData = ICAL.parse(reader.result);
|
||||||
const comp = new ICAL.Component(jCalData[1]);
|
const comp = new ICAL.Component(jCalData);
|
||||||
// Fetch the VEVENT part
|
// Fetch the VEVENT part
|
||||||
comp.getAllSubcomponents('vevent').forEach(vevent => {
|
comp.getAllSubcomponents('vevent').forEach(vevent => {
|
||||||
event = new ICAL.Event(vevent);
|
event = new ICAL.Event(vevent);
|
||||||
|
@ -50,13 +50,17 @@ function dateFromAlarm(alarm) {
|
||||||
return new Date(date.getTime() + alarm.t);
|
return new Date(date.getTime() + alarm.t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDate(d) {
|
||||||
|
return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
function getAlarmDefaults() {
|
function getAlarmDefaults() {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return {
|
return {
|
||||||
on: true,
|
on: true,
|
||||||
t: dateToMsSinceMidnight(date),
|
t: dateToMsSinceMidnight(date),
|
||||||
dow: 127,
|
dow: 127,
|
||||||
date: date.toISOString().substring(0,10),
|
date: formatDate(date),
|
||||||
last: 0,
|
last: 0,
|
||||||
rp: "defaultRepeat" in schedSettings ? schedSettings.defaultRepeat : false,
|
rp: "defaultRepeat" in schedSettings ? schedSettings.defaultRepeat : false,
|
||||||
vibrate: "defaultAlarmPattern" in schedSettings ? schedSettings.defaultAlarmPattern : "::",
|
vibrate: "defaultAlarmPattern" in schedSettings ? schedSettings.defaultAlarmPattern : "::",
|
||||||
|
@ -72,7 +76,7 @@ function eventToAlarm(event, offsetMs) {
|
||||||
id: event.uid,
|
id: event.uid,
|
||||||
msg: event.summary,
|
msg: event.summary,
|
||||||
t: dateToMsSinceMidnight(date),
|
t: dateToMsSinceMidnight(date),
|
||||||
date: date.toISOString().substring(0,10),
|
date: formatDate(date),
|
||||||
data: {end: event.endDate.toJSDate().toISOString()}
|
data: {end: event.endDate.toJSDate().toISOString()}
|
||||||
}};
|
}};
|
||||||
if (offsetMs) { // Alarm time is not real event time, so do a backup
|
if (offsetMs) { // Alarm time is not real event time, so do a backup
|
||||||
|
@ -105,7 +109,7 @@ function renderAlarm(alarm, exists) {
|
||||||
inputTime.onchange = (e => {
|
inputTime.onchange = (e => {
|
||||||
const date = new Date(inputTime.value);
|
const date = new Date(inputTime.value);
|
||||||
alarm.t = dateToMsSinceMidnight(date);
|
alarm.t = dateToMsSinceMidnight(date);
|
||||||
alarm.date = date.toISOString().substring(0,10);
|
alarm.date = formatDate(date);
|
||||||
});
|
});
|
||||||
tdTime.appendChild(inputTime);
|
tdTime.appendChild(inputTime);
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: New 'Settings Menu' to choose your favorite color and switch between light or dark themes
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Shadow Clock
|
||||||
|
|
||||||
|
Shadow Clock uses the "Londrina" font in a user selectable color and surrounds it in the "Londrina Shadow" font to create a visually appealing way to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
* Install Shadow Clock through the Bangle.js app loader.
|
||||||
|
* Configure it through the default Bangle.js configuration mechanism
|
||||||
|
(Settings app, "Apps" menu, "Shadow Clock" submenu).
|
||||||
|
* If you like it, make it your default watch face
|
||||||
|
(Settings app, "System" menu, "Clock" submenu, select "Shadow Clock").
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Anton Clock is configured by the standard settings mechanism of Bangle.js's operating system:
|
||||||
|
Open the `Settings` app, then the `Apps` submenu and below it the `Shadow Clock` menu.
|
||||||
|
You configure Shadow Clock by selecting a `Light` or `Dark` system wide theme and then selecting the `Color` of the clock numbers.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
Shadow Clock should be fully compatible with with Bangle.js 1 and Bangle.js 2. However, it was built and tested with Bangle.js 2
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
[stweedo](https://github.com/stweedo)
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AH4A/AH4A/AH4A/AFGsAAQOUA4OBDBYfHo8jAAOBByOssgICkdkF6AWEF5IOHxAHDAAVALx4VEF5AOHA4oAD0ovNoEjo6BCF5AOHF4ccp9dAoVdFxh2CioUCa44OIwIFCirwBjgFBqyNPqxJCEAOIKwOIBxYJChwfBwIABJQ7dHnIkCEAsVBxovDdiMcVYIgHFQgOJR4WIxBdMCoccshUCkdkRYJ6DBxIvDAAlkF5wWLBxQKJOAIvTroOOBQkcmU5aoYAKhwACjiqDqwOOTQQqDGwQvMAAawCcoOsAAQOL642DBAI0EAB1AC4QfBJIU5BxQ2DAAp5FF6ZJFF4xgFAAMOOwyPKjkcCgWsAoMyBxQAB1k5BAMcZAQuPDIS6EYBAIRAHKHCAIhPLAoWswIMEL6OBDIgYGHA4qCCwIKBDIgvPDggnIAw7lBF4J4KR5gVJKgZDDIgQCDMITvXXA47EHxD2aFQhOCBAY8DRiiWLD4aEBMYzuCLzgvEEQK4BFAQCDB4TACF7QA/AH4A/AH4A/AAQA=="))
|
require("heatshrink").decompress(atob("mEwxH+64Afq17AAlWq2sBYOtB4Urq2BFzeBwONAAeyHAQvCrPX1g3BlYvb02BjwADF4QmBwdZF4MrleBgAvlgAwBgqQBlcAF72HF46PBwaPDwK/dVoLpBAAwMBweDFTYA/AH4A/AH4A/AF2lAAOsBRIAF6AMDp9Wp+lFyNWkYABitWFwoKCAAsHBgMyBAkVwIuO6EVCod6F50A6+BBI0VMRxGEF6BfBIwZqHABRGCo5EIF4cOgEHAAUA0scBYQDDCAIvMhx7CCYTfDAYIvDK4McNoaODh1PPgcO1jsNiskOYQTBh0cjlW1iDHfwJfDjmeoDKEABSlCgAlCF4QJGcgwPEfQ5eMFQIvDRYIfClesioADEgcrSAguEwJeNh1AAgQkBwOlkkkQoOBvQACIgRAB685F4wJBF5oAGp4UJ1i6CeQK/Dg4ECZYQvTComsnNdrp+B0rADlaKD1l6X52BgAACJIgvEFIcO0sOF4cAAgeISgYvLMg8HKoOBAAIvEAAsOL4bwGFxyuDg/QGoYZBT5AKBXQYvVCYUH1gvFqwjGTwWBGA0AOwIAN6EOhytBAwIFBh1PAoN6g8VskVmUAZoelBQVABQIuPMAS6DfYWBMgI9CwMrq2lDA2Bq1PFqLpCHBAWKMIJGBAgRDEABiKBlYTDAYcA1gKBqwgFlYMBLoIaBCIKZDABh+BDIQVBC4WsF4YOBGAmsBQSgCAIIvQFYIiBKwReDFQI3DEIlWFIIsBCwIABF6JhClZhBZQo8BAYIOBIoh0CBIJ5DAH4AEPYKBDWwSGCAoKIDAoIwcEwJ/CRYICCFoI8BQ4YKBMDhWBEwIjBYQcAAYQKBZIRfdAIJVCQgLkDAYJjCcX4A/AH4A/AFwA=="))
|
||||||
|
|
|
@ -1,63 +1,95 @@
|
||||||
// Clock with large colored digits using the "Londrina" font and a slightly larger "Londrina Shadow" font on top
|
// Clock with large colored digits using the "Londrina" font and a slightly larger "Londrina Shadow" font on top
|
||||||
|
|
||||||
Graphics.prototype.setFontLondrinaSolid = function() {
|
Graphics.prototype.setFontLondrinaSolid = function() {
|
||||||
// Actual height 59 (64 - 6)
|
// Actual height 59 (64 - 6)
|
||||||
return this.setFontCustom(
|
return this.setFontCustom(
|
||||||
E.toString(require('heatshrink').decompress(atob('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'))),
|
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||||
46,
|
46,
|
||||||
atob("DyEqHigoJikpJygqEQ=="),
|
atob("DyEqHigoJikpJygqEQ=="),
|
||||||
81|65536
|
81 | 65536
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Graphics.prototype.setFontLondrinaShadow = function() {
|
Graphics.prototype.setFontLondrinaShadow = function() {
|
||||||
// Actual height 63 (67 - 5)
|
// Actual height 63 (67 - 5)
|
||||||
return this.setFontCustom(
|
return this.setFontCustom(
|
||||||
E.toString(require('heatshrink').decompress(atob('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'))),
|
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||||
46,
|
46,
|
||||||
atob("DyEqHigoJikpJygqEQ=="),
|
atob("DyEqHigoJikpJygqEQ=="),
|
||||||
81|65536
|
81 | 65536
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
(function() {
|
Graphics.prototype.setFontDotGothic16 = function() {
|
||||||
let drawTimeout;
|
// Actual height 20 (19 - 0)
|
||||||
|
return this.setFontCustom(
|
||||||
|
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||||
|
32,
|
||||||
|
atob("CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoA"),
|
||||||
|
20|65536
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Actually draw the watch face
|
let storage = require('Storage');
|
||||||
function draw() {
|
|
||||||
const x = g.getWidth() / 2;
|
|
||||||
const y = g.getHeight() / 2;
|
|
||||||
g.reset().clearRect(Bangle.appRect);
|
|
||||||
const date = new Date();
|
|
||||||
var hour = String(date.getHours()).padStart(2, '0');
|
|
||||||
if (hour[0] === '0') hour = hour[1];
|
|
||||||
var minutes = String(date.getMinutes()).padStart(2, '0');
|
|
||||||
const timeStr = hour + ':' + minutes;
|
|
||||||
|
|
||||||
g.setFontAlign(0, 0).setFont("LondrinaSolid").setColor(0, 1, 1).drawString(timeStr, x - 1, y);
|
var settings = Object.assign({
|
||||||
g.reset().setFontAlign(0, 0).setFont("LondrinaShadow").drawString(timeStr, x - 1, y);
|
// default values
|
||||||
|
color: "#0ff",
|
||||||
|
theme: "light",
|
||||||
|
}, storage.readJSON("shadowclk.json", true) || {});
|
||||||
|
|
||||||
const locale = require("locale");
|
(function() {
|
||||||
const dateStr = locale.date(date, 0).toUpperCase() + "\n" +
|
let drawTimeout;
|
||||||
locale.dow(date, 0).toUpperCase();
|
|
||||||
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y + 48);
|
|
||||||
|
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
function draw() {
|
||||||
drawTimeout = setTimeout(() => {
|
var x = g.getWidth() / 2;
|
||||||
drawTimeout = undefined;
|
var y = g.getHeight() / 2;
|
||||||
draw();
|
g.reset().clearRect(Bangle.appRect);
|
||||||
}, 60000 - (Date.now() % 60000));
|
var date = new Date();
|
||||||
}
|
var hour = String(date.getHours()).padStart(2, '0');
|
||||||
|
if (hour[0] === '0') hour = hour[1];
|
||||||
|
var minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
var timeStr = hour + ':' + minutes;
|
||||||
|
var color = settings.color;
|
||||||
|
g.setFontAlign(0, 0).setFont("LondrinaSolid").setColor(color).drawString(timeStr, x - 1, y);
|
||||||
|
g.reset().setFontAlign(0, 0).setFont("LondrinaShadow").drawString(timeStr, x - 1, y);
|
||||||
|
|
||||||
|
var locale = require("locale");
|
||||||
|
var dayOfMonth = date.getDate();
|
||||||
|
var month = locale.month(date, 1).slice(0, 1).toUpperCase() + locale.month(date, 1).slice(1).toLowerCase();
|
||||||
|
var year = date.getFullYear();
|
||||||
|
var dayOfMonthStr = dayOfMonth.toString();
|
||||||
|
if (dayOfMonth === 1 || dayOfMonth === 21 || dayOfMonth === 31) {
|
||||||
|
dayOfMonthStr += "st";
|
||||||
|
} else if (dayOfMonth === 2 || dayOfMonth === 22) {
|
||||||
|
dayOfMonthStr += "nd";
|
||||||
|
} else if (dayOfMonth === 3 || dayOfMonth === 23) {
|
||||||
|
dayOfMonthStr += "rd";
|
||||||
|
} else {
|
||||||
|
dayOfMonthStr += "th";
|
||||||
|
}
|
||||||
|
var dayOfWeek = locale.dow(date, 0).slice(0, 1).toUpperCase() + locale.dow(date, 0).slice(1).toLowerCase();
|
||||||
|
var dateStr = month + " " + dayOfMonthStr + ", " + year + "\n" + dayOfWeek;
|
||||||
|
g.setFontAlign(0, 0).setFont("DotGothic16").drawString(dateStr, x, y + 48);
|
||||||
|
|
||||||
Bangle.setUI({
|
|
||||||
mode: "clock",
|
|
||||||
remove: function() {
|
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = setTimeout(() => {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.setUI({
|
||||||
draw();
|
mode: "clock",
|
||||||
setTimeout(Bangle.drawWidgets, 0);
|
remove: function() {
|
||||||
})();
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
delete Graphics.prototype.setFontLondrinaSolid;
|
||||||
|
delete Graphics.prototype.setFontLondrinaShadow;
|
||||||
|
delete Graphics.prototype.setFontDotGothic16;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
draw();
|
||||||
|
setTimeout(Bangle.drawWidgets, 0);
|
||||||
|
})();
|
||||||
|
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 446 B |
|
@ -0,0 +1,370 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Londrina+Shadow&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=DotGothic16&display=swap" rel="stylesheet">
|
||||||
|
<title>3-Bit Color Picker</title>
|
||||||
|
<style>
|
||||||
|
.main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-0 {
|
||||||
|
background: #000
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-1 {
|
||||||
|
background: #f00
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-2 {
|
||||||
|
background: #0f0
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-3 {
|
||||||
|
background: #ff0
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-4 {
|
||||||
|
background: #00f
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-5 {
|
||||||
|
background: #f0f
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-6 {
|
||||||
|
background: #0ff
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-7 {
|
||||||
|
background: #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-box {
|
||||||
|
width: 176px;
|
||||||
|
height: 176px;
|
||||||
|
border: 1px solid black;
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-canvas {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-bg {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upload {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message-container {
|
||||||
|
height: 40px;
|
||||||
|
/* adjust the height based on your desired fixed height */
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<div class="main-container">
|
||||||
|
<h1>3-Bit Color Picker</h1>
|
||||||
|
<div id="color-buttons-container"></div>
|
||||||
|
<button id="toggle-bg" class="btn btn-primary" onclick="toggleBackground()">Light/Dark</button>
|
||||||
|
<div id="preview-box">
|
||||||
|
<canvas id="preview-canvas" width="176" height="176"></canvas>
|
||||||
|
</div>
|
||||||
|
<button id="upload" class="btn btn-primary">Upload</button>
|
||||||
|
<div id="message-container">
|
||||||
|
<div id="message"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const messageDiv = document.getElementById('message');
|
||||||
|
let colors = ['#000', '#f00', '#0f0', '#ff0', '#00f', '#f0f', '#0ff', '#fff'];
|
||||||
|
let colorButtonsContainer = document.getElementById('color-buttons-container');
|
||||||
|
colors.forEach((color, i) => {
|
||||||
|
let button = document.createElement('button');
|
||||||
|
button.className = `color-button color-${i}`;
|
||||||
|
button.dataset.color = color;
|
||||||
|
colorButtonsContainer.appendChild(button);
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".color-button").forEach(button => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
selectedColor = button.dataset.color;
|
||||||
|
drawText(selectedColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatTime(date) {
|
||||||
|
let hours = date.getHours();
|
||||||
|
let minutes = date.getMinutes();
|
||||||
|
let formattedHours = hours < 10 ? `${hours}` : `${hours}`;
|
||||||
|
let formattedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
|
||||||
|
return `${formattedHours}:${formattedMinutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentDate() {
|
||||||
|
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||||
|
let suffixes = ["st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "st"];
|
||||||
|
let date = new Date();
|
||||||
|
let month = months[date.getMonth()];
|
||||||
|
let day = date.getDate();
|
||||||
|
let suffix = suffixes[day - 1];
|
||||||
|
let year = date.getFullYear();
|
||||||
|
return `${month} ${day}${suffix}, ${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentDay() {
|
||||||
|
let days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||||
|
let date = new Date();
|
||||||
|
return days[date.getDay()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBackground() {
|
||||||
|
isDarkBg = !isDarkBg; // Toggle the background state
|
||||||
|
let previewBox = document.getElementById("preview-box");
|
||||||
|
previewBox.style.backgroundColor = isDarkBg ? "black" : "white";
|
||||||
|
drawText(selectedColor); // Redraw the text with updated color
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawText(color) {
|
||||||
|
let canvas = document.getElementById("preview-canvas");
|
||||||
|
let ctx = canvas.getContext("2d");
|
||||||
|
let previewBox = document.getElementById("preview-box");
|
||||||
|
previewBox.style.backgroundColor = isDarkBg ? "black" : "white";
|
||||||
|
|
||||||
|
// Clear the canvas
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Set the base font and selected color
|
||||||
|
ctx.font = "81px Londrina Solid";
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
|
||||||
|
// Get the current local system time
|
||||||
|
let currentTime = formatTime(new Date());
|
||||||
|
|
||||||
|
// Measure the text width and calculate the horizontal position
|
||||||
|
let timeWidth = ctx.measureText(currentTime).width;
|
||||||
|
let xPos = (canvas.width - timeWidth) / 2;
|
||||||
|
|
||||||
|
// Measure the text height based on the font size and calculate the vertical position
|
||||||
|
let timeHeight = ctx.measureText('M').actualBoundingBoxAscent + ctx.measureText('M').actualBoundingBoxDescent;
|
||||||
|
let yPos = (canvas.height + timeHeight) / 2;
|
||||||
|
|
||||||
|
// Draw the time
|
||||||
|
ctx.fillText(currentTime, xPos, yPos);
|
||||||
|
|
||||||
|
// Set the outline font and color
|
||||||
|
ctx.font = "81px Londrina Shadow";
|
||||||
|
|
||||||
|
// Set the text color based on the background state
|
||||||
|
if (isDarkBg) {
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = "#000";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the time again
|
||||||
|
ctx.fillText(currentTime, xPos, yPos);
|
||||||
|
|
||||||
|
// Set the font for the date
|
||||||
|
ctx.font = "19px DotGothic16";
|
||||||
|
|
||||||
|
// Get the current date
|
||||||
|
let currentDate = getCurrentDate();
|
||||||
|
|
||||||
|
// Measure the date width and calculate the horizontal position
|
||||||
|
let dateWidth = ctx.measureText(currentDate).width;
|
||||||
|
xPos = (canvas.width - dateWidth) / 2;
|
||||||
|
|
||||||
|
// Draw the date
|
||||||
|
yPos += 20;
|
||||||
|
ctx.fillText(currentDate, xPos, yPos);
|
||||||
|
|
||||||
|
// Get the current day of the week
|
||||||
|
let currentDay = getCurrentDay();
|
||||||
|
|
||||||
|
// Measure the day width and calculate the horizontal position
|
||||||
|
let dayWidth = ctx.measureText(currentDay).width;
|
||||||
|
xPos = (canvas.width - dayWidth) / 2;
|
||||||
|
|
||||||
|
// Draw the day of the week
|
||||||
|
ctx.fillText(currentDay, xPos, yPos + 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToDec(hex) {
|
||||||
|
if (hex.length === 4) {
|
||||||
|
hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
|
||||||
|
}
|
||||||
|
let r = parseInt(hex.slice(1, 3), 16) >> 3;
|
||||||
|
let g = parseInt(hex.slice(3, 5), 16) >> 2;
|
||||||
|
let b = parseInt(hex.slice(5, 7), 16) >> 3;
|
||||||
|
return (r << 11) | (g << 5) | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors from 'Light BW' and 'Dark BW' themes
|
||||||
|
function createThemeColors(isDarkBg) {
|
||||||
|
return isDarkBg ? {
|
||||||
|
fg: hexToDec("#fff"),
|
||||||
|
bg: hexToDec("#000"),
|
||||||
|
fg2: hexToDec("#fff"),
|
||||||
|
bg2: hexToDec("#004"),
|
||||||
|
fgH: hexToDec("#fff"),
|
||||||
|
bgH: hexToDec("#00f"),
|
||||||
|
dark: true
|
||||||
|
} : {
|
||||||
|
fg: hexToDec("#000"),
|
||||||
|
bg: hexToDec("#fff"),
|
||||||
|
fg2: hexToDec("#000"),
|
||||||
|
bg2: hexToDec("#cff"),
|
||||||
|
fgH: hexToDec("#000"),
|
||||||
|
bgH: hexToDec("#0ff"),
|
||||||
|
dark: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveThemeToSettings(theme) {
|
||||||
|
Puck.eval('require("Storage").readJSON("setting.json", true)', (data) => {
|
||||||
|
if (data) {
|
||||||
|
// Ensure that data.theme exists
|
||||||
|
if (!data.theme) {
|
||||||
|
data.theme = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save all theme values
|
||||||
|
for (let key in theme) {
|
||||||
|
data.theme[key] = theme[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
data.clock = "shadowclk.app.js"; // Set Shadow Clock as default
|
||||||
|
|
||||||
|
Puck.write(`require("Storage").write("setting.json", ${JSON.stringify(data)});\n`, (result) => {
|
||||||
|
console.log('Theme saved:', result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Shadow Clock color and theme setting
|
||||||
|
let selectedColor = "#0ff";
|
||||||
|
let isDarkBg = false;
|
||||||
|
|
||||||
|
function loadSettings(callback) {
|
||||||
|
// Set a timeout for loading the settings
|
||||||
|
const timeout = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error("Timeout occurred")), 500)
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadSettingsFromStorage = new Promise((resolve) => {
|
||||||
|
Puck.eval('require("Storage").readJSON("shadowclk.json", true)', (data) => {
|
||||||
|
if (data) {
|
||||||
|
// Apply color and theme from JSON
|
||||||
|
const { color, theme } = data;
|
||||||
|
selectedColor = color;
|
||||||
|
isDarkBg = theme === "dark";
|
||||||
|
displayMessage("Previous settings loaded.", 3000);
|
||||||
|
} else {
|
||||||
|
// Use default values if there is no data for color and theme
|
||||||
|
selectedColor = "#0ff";
|
||||||
|
isDarkBg = false;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.race([loadSettingsFromStorage, timeout])
|
||||||
|
.then(() => callback())
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
// Use default values in case of a timeout or error
|
||||||
|
selectedColor = "#0ff";
|
||||||
|
isDarkBg = false;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTime() {
|
||||||
|
setInterval(() => {
|
||||||
|
drawText(selectedColor);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayMessage(text, timeout) {
|
||||||
|
// Remove any existing message
|
||||||
|
while (messageDiv.firstChild) {
|
||||||
|
messageDiv.removeChild(messageDiv.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new message element
|
||||||
|
const message = document.createElement('p');
|
||||||
|
message.innerHTML = text; // Use innerHTML instead of textContent
|
||||||
|
message.style.fontSize = '24px';
|
||||||
|
messageDiv.appendChild(message);
|
||||||
|
|
||||||
|
// Remove the message element after the timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
messageDiv.removeChild(message);
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("upload").addEventListener("click", function () {
|
||||||
|
// Save theme settings to Bangle.js
|
||||||
|
let themeColors = createThemeColors(isDarkBg);
|
||||||
|
saveThemeToSettings(themeColors);
|
||||||
|
|
||||||
|
// Save Shadow Clock color setting
|
||||||
|
let data = {
|
||||||
|
color: selectedColor,
|
||||||
|
theme: isDarkBg ? "dark" : "light"
|
||||||
|
};
|
||||||
|
Puck.write(`require("Storage").write("shadowclk.json", ${JSON.stringify(data)});\n`, (result) => {
|
||||||
|
console.log('Color saved:', result);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display the message using displayMessage function
|
||||||
|
displayMessage('Configuration sent...<br>Hold button on Bangle.js', 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadFonts() {
|
||||||
|
return Promise.all([
|
||||||
|
document.fonts.load('81px Londrina Solid'),
|
||||||
|
document.fonts.load('81px Londrina Shadow'),
|
||||||
|
document.fonts.load('19px DotGothic16')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
await loadFonts(); // Load fonts before drawing for the first time
|
||||||
|
loadSettings(updateTime); // Pass updateTime as the callback function to loadSettings
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -1,16 +1,35 @@
|
||||||
{
|
{
|
||||||
"id": "shadowclk",
|
"id": "shadowclk",
|
||||||
"name": "Shadow Clock",
|
"name": "Shadow Clock",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "A simple clock using the Londrina font with color and a shadowed outline. Based on the Anton Clock.",
|
"description": "A simple clock using the Londrina font in color with a shadowed outline. Based on the Anton Clock.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}],
|
"screenshots": [{
|
||||||
|
"url": "screenshot.png"
|
||||||
|
}, {
|
||||||
|
"url": "screenshot-1.png"
|
||||||
|
}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"interface": "interface.html",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [{
|
||||||
{"name":"shadowclk.app.js","url":"app.js"},
|
"name": "shadowclk.app.js",
|
||||||
{"name":"shadowclk.img","url":"app-icon.js","evaluate":true}
|
"url": "app.js"
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
"name": "shadowclk.settings.js",
|
||||||
|
"url": "settings.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shadowclk.img",
|
||||||
|
"url": "app-icon.js",
|
||||||
|
"evaluate": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data": [{
|
||||||
|
"name": "shadowclk.json"
|
||||||
|
}]
|
||||||
}
|
}
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,104 @@
|
||||||
|
(function(back) {
|
||||||
|
let teletextColors = ["#000", "#f00", "#0f0", "#ff0", "#00f", "#f0f", "#0ff", "#fff"];
|
||||||
|
let teletextColorNames = ["Black", "Red", "Green", "Yellow", "Blue", "Magenta", "Cyan", "White"];
|
||||||
|
|
||||||
|
// Load and set default settings
|
||||||
|
let appSettings = Object.assign({
|
||||||
|
color: teletextColors[6],
|
||||||
|
theme: 'light',
|
||||||
|
}, require('Storage').readJSON("shadowclk.json", true) || {});
|
||||||
|
|
||||||
|
// Save settings to storage
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON("shadowclk.json", appSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors from 'Light BW' and 'Dark BW' themes
|
||||||
|
function createThemeColors(mode) {
|
||||||
|
let cl = x => g.setColor(x).getColor();
|
||||||
|
return mode ? {
|
||||||
|
fg: cl("#fff"),
|
||||||
|
bg: cl("#000"),
|
||||||
|
fg2: cl("#fff"),
|
||||||
|
bg2: cl("#004"),
|
||||||
|
fgH: cl("#fff"),
|
||||||
|
bgH: cl("#00f"),
|
||||||
|
dark: true
|
||||||
|
} : {
|
||||||
|
fg: cl("#000"),
|
||||||
|
bg: cl("#fff"),
|
||||||
|
fg2: cl("#000"),
|
||||||
|
bg2: cl("#cff"),
|
||||||
|
fgH: cl("#000"),
|
||||||
|
bgH: cl("#0ff"),
|
||||||
|
dark: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch theme and save to storage
|
||||||
|
function switchTheme(mode) {
|
||||||
|
if (mode === g.theme.dark) return;
|
||||||
|
let s = require('Storage').readJSON("setting.json", 1) || {};
|
||||||
|
s.theme = createThemeColors(mode);
|
||||||
|
require('Storage').writeJSON("setting.json", s);
|
||||||
|
updateTheme(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current menu with the new theme
|
||||||
|
function updateTheme(mode) {
|
||||||
|
let newTheme = createThemeColors(mode);
|
||||||
|
g.theme = newTheme;
|
||||||
|
appSettings.theme = mode ? 'dark' : 'light';
|
||||||
|
writeSettings();
|
||||||
|
delete g.reset;
|
||||||
|
g._reset = g.reset;
|
||||||
|
g.reset = function(n) {
|
||||||
|
return g._reset().setColor(newTheme.fg).setBgColor(newTheme.bg);
|
||||||
|
};
|
||||||
|
g.clear = function(n) {
|
||||||
|
if (n) g.reset();
|
||||||
|
return g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||||
|
};
|
||||||
|
g.clear(1);
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
showMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the current system theme
|
||||||
|
function getCurrentTheme() {
|
||||||
|
let s = require('Storage').readJSON("setting.json", 1) || {};
|
||||||
|
if (!s.theme) {
|
||||||
|
return appSettings.theme; // fallback to appSettings.theme (light or dark)
|
||||||
|
}
|
||||||
|
return s.theme.dark ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMenu() {
|
||||||
|
appSettings.theme = getCurrentTheme();
|
||||||
|
E.showMenu({
|
||||||
|
"": {
|
||||||
|
"title": "Shadow Clock"
|
||||||
|
},
|
||||||
|
"< Back": () => back(),
|
||||||
|
'Theme:': {
|
||||||
|
value: (appSettings.theme === 'dark'),
|
||||||
|
format: v => v ? "Dark" : "Light",
|
||||||
|
onchange: v => {
|
||||||
|
switchTheme(v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Color:': {
|
||||||
|
value: teletextColors.indexOf(appSettings.color),
|
||||||
|
min: 0,
|
||||||
|
max: 7,
|
||||||
|
onchange: v => {
|
||||||
|
appSettings.color = teletextColors[v];
|
||||||
|
writeSettings();
|
||||||
|
},
|
||||||
|
format: v => teletextColorNames[v]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Initially show the menu
|
||||||
|
showMenu();
|
||||||
|
});
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New app!
|
0.01: New app!
|
||||||
0.02: Bug fixes
|
0.02: Bug fixes
|
||||||
0.03: Submitted to the app loader
|
0.03: Submitted to the app loader
|
||||||
|
0.04: Reduce battery consumption when the user's not looking
|
|
@ -8,7 +8,7 @@ const BUTTON_ICONS = {
|
||||||
reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI="))
|
reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI="))
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = storage.readJSON(STATE_PATH);
|
let state = storage.readJSON(STATE_PATH, 1);
|
||||||
const STATE_DEFAULT = {
|
const STATE_DEFAULT = {
|
||||||
wasRunning: false, //If the stopwatch was ever running since being reset
|
wasRunning: false, //If the stopwatch was ever running since being reset
|
||||||
sessionStart: 0, //When the stopwatch was first started
|
sessionStart: 0, //When the stopwatch was first started
|
||||||
|
@ -157,7 +157,7 @@ function firstTimeStart(now, time) {
|
||||||
elapsedTime: 0,
|
elapsedTime: 0,
|
||||||
};
|
};
|
||||||
lapFile = 'stlap-' + state.sessionStart + '.json';
|
lapFile = 'stlap-' + state.sessionStart + '.json';
|
||||||
setupTimerInterval();
|
setupTimerIntervalFast();
|
||||||
Bangle.buzz(200);
|
Bangle.buzz(200);
|
||||||
drawButtons();
|
drawButtons();
|
||||||
}
|
}
|
||||||
|
@ -201,13 +201,15 @@ function start(now, time) {
|
||||||
state.elapsedTime += (state.pausedTime - state.startTime);
|
state.elapsedTime += (state.pausedTime - state.startTime);
|
||||||
state.startTime = now;
|
state.startTime = now;
|
||||||
state.running = true;
|
state.running = true;
|
||||||
setupTimerInterval();
|
setupTimerIntervalFast();
|
||||||
Bangle.buzz(200);
|
Bangle.buzz(200);
|
||||||
drawTime();
|
drawTime();
|
||||||
drawButtons();
|
drawButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bangle.on("touch", (button, xy) => {
|
Bangle.on("touch", (button, xy) => {
|
||||||
|
setupTimerIntervalFast();
|
||||||
|
|
||||||
//In gesture mode, just turn on the light and then return
|
//In gesture mode, just turn on the light and then return
|
||||||
if (gestureMode) {
|
if (gestureMode) {
|
||||||
Bangle.setLCDPower(true);
|
Bangle.setLCDPower(true);
|
||||||
|
@ -242,6 +244,8 @@ Bangle.on("touch", (button, xy) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.on('swipe', direction => {
|
Bangle.on('swipe', direction => {
|
||||||
|
setupTimerIntervalFast();
|
||||||
|
|
||||||
let now = (new Date()).getTime();
|
let now = (new Date()).getTime();
|
||||||
let time = getTime();
|
let time = getTime();
|
||||||
|
|
||||||
|
@ -272,12 +276,23 @@ setWatch(() => {
|
||||||
}, BTN1, { repeat: true });
|
}, BTN1, { repeat: true });
|
||||||
|
|
||||||
let timerInterval;
|
let timerInterval;
|
||||||
|
let userWatching = false;
|
||||||
|
|
||||||
|
function setupTimerIntervalFast() {
|
||||||
|
userWatching = true;
|
||||||
|
setupTimerInterval();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
userWatching = false;
|
||||||
|
setupTimerInterval();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
function setupTimerInterval() {
|
function setupTimerInterval() {
|
||||||
if (timerInterval !== undefined) {
|
if (timerInterval !== undefined) {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
}
|
}
|
||||||
timerInterval = setInterval(drawTime, 10);
|
timerInterval = setInterval(drawTime, userWatching ? 10 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopTimerInterval() {
|
function stopTimerInterval() {
|
||||||
|
@ -289,7 +304,7 @@ function stopTimerInterval() {
|
||||||
|
|
||||||
drawTime();
|
drawTime();
|
||||||
if (state.running) {
|
if (state.running) {
|
||||||
setupTimerInterval();
|
setupTimerIntervalFast();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Save our state when the app is closed
|
//Save our state when the app is closed
|
||||||
|
@ -300,5 +315,8 @@ E.on('kill', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// change interval depending of whether the user's looking
|
||||||
|
Bangle.on("twist", setupTimerIntervalFast);
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "stlap",
|
"id": "stlap",
|
||||||
"name": "Stopwatch",
|
"name": "Stopwatch",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "A stopwatch that remembers its state, with a lap timer and a gesture mode (enable by swiping)",
|
"description": "A stopwatch that remembers its state, with a lap timer and a gesture mode (enable by swiping)",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
@ -20,5 +20,9 @@
|
||||||
"url": "icon.js",
|
"url": "icon.js",
|
||||||
"evaluate": true
|
"evaluate": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"stlap.state.json"},
|
||||||
|
{"wildcard":"stlap-*.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: 1st version: saves values to csv
|
0.01: 1st version: saves values to csv
|
||||||
0.02: added HTML interface
|
0.02: added HTML interface
|
||||||
0.03: Added Stop/start recording, change BG color, filesize info
|
0.03: Added Stop/start recording, change BG color, filesize info
|
||||||
|
0.04: Support for negative degree, Min/Max, random for emulator, clean of code
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Temperature Monitor (with logging)
|
# Temperature Monitor (with logging)
|
||||||
Temperature monitor that shows temperature on real time but also allows to store in a file for a later process.
|
Temperature / Thermometer monitor that not only shows degrees on real time but also allows to store this info in a file for a later process.
|
||||||
|
|
||||||
Compatible with BangleJS1,BangleJS2,and EMSCRIPTENx emulators
|
Compatible with BangleJS1,BangleJS2,and EMSCRIPTENx emulators
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"id": "tempmonitor",
|
"id": "tempmonitor",
|
||||||
"name": "Temperature monitor",
|
"name": "Temperature monitor",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Displays the current temperature and stores in a CSV file",
|
"description": "Another thermometer, besides displaying current temperature, stores it in a CSV file",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
"interface": "interface.html",
|
"interface": "interface.html",
|
||||||
|
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -23,6 +23,8 @@ var v_model=process.env.BOARD;
|
||||||
var v_color_erase=g.getBgColor(); //original BG color overwritten on SetVariables
|
var v_color_erase=g.getBgColor(); //original BG color overwritten on SetVariables
|
||||||
var v_color=g.getColor();//original FG color
|
var v_color=g.getColor();//original FG color
|
||||||
var id_rec_intv; //var for the recording interval
|
var id_rec_intv; //var for the recording interval
|
||||||
|
var v_t_max=-50; //preset with an opposite and impossible record measure
|
||||||
|
var v_t_min=70;
|
||||||
|
|
||||||
if (readFreq>saveFreq) console.log("Read refresh freq should be higher than saving");
|
if (readFreq>saveFreq) console.log("Read refresh freq should be higher than saving");
|
||||||
if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_color);
|
if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_color);
|
||||||
|
@ -32,281 +34,303 @@ if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_co
|
||||||
function SetVariables(){
|
function SetVariables(){
|
||||||
//EMSCRIPTEN,EMSCRIPTEN2
|
//EMSCRIPTEN,EMSCRIPTEN2
|
||||||
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') {
|
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') {
|
||||||
v_font_size1=16;
|
v_font_size1=16;
|
||||||
v_font_size2=50;
|
v_font_size2=50;
|
||||||
}else{
|
}else{
|
||||||
//Banglejs2 or others
|
//Banglejs2 or others
|
||||||
v_font_size1=11; //too small?
|
v_font_size1=11; //too small?
|
||||||
v_font_size2=40;
|
v_font_size2=40;
|
||||||
}
|
}
|
||||||
//overwriting default BG, is better detect?
|
//overwriting default BG, is better detect?
|
||||||
if (g.theme.dark==1) v_color_erase=0x0000; //dynamic; //bg black
|
if (g.theme.dark==1) v_color_erase=0x0000; //dynamic; //bg black
|
||||||
else if (g.theme.dark==0) v_color_erase=0xFFFF; //dynamic; //bg white
|
else if (g.theme.dark==0) v_color_erase=0xFFFF; //dynamic; //bg white
|
||||||
}
|
}
|
||||||
|
|
||||||
//print result
|
//print result
|
||||||
function printTemperature(v_temp) {
|
function printTemperature(v_temp) {
|
||||||
if (v_mode_debug>1) console.log("v_temp in "+v_temp+" entries "+v_saved_entries);
|
if (v_mode_debug>1) console.log("v_temp in "+v_temp+" entries "+v_saved_entries);
|
||||||
ClearBox();
|
|
||||||
//g.setFont("6x8",2).setFontAlign(0,0);
|
|
||||||
g.setFontVector(v_font_size1).setFontAlign(0,0);
|
|
||||||
var x = (rect.x+(rect.x2-60))/2;//-60 space for graph and layout buttons
|
|
||||||
var y = (rect.y+rect.y2)/2 + 20;
|
|
||||||
|
|
||||||
if (v_saveToFile==true) {
|
// Avg of temperature readings
|
||||||
// if (v_mode_debug>0) console.log("prev color="+v_color);
|
while (history.length>4) history.shift();
|
||||||
printInfo("Recording : "+v_saved_entries, '#CC3333',x,rect.y+30);
|
history.push(v_temp);
|
||||||
//g.setColor('#CC3333'); //red
|
var avrTemp = E.sum(history) / history.length;
|
||||||
// g.drawString("Recording : "+v_saved_entries, x, rect.y+35);
|
//var t = require('locale').temp(avrTemp);
|
||||||
//g.setColor(v_color);//restore default color
|
//.replace("'","°");
|
||||||
}
|
lastMeasure=avrTemp.toString();
|
||||||
else printInfo("Rec paused : "+v_saved_entries, v_color,x,rect.y+30);
|
if (lastMeasure.length>4) lastMeasure=lastMeasure.substr(0,4);
|
||||||
//else g.drawString("Rec paused : "+v_saved_entries, x, rect.y+35);
|
|
||||||
//space for printing info
|
|
||||||
g.drawString("Temperature:", x, rect.y+45+(v_font_size1*2));
|
ClearBox();
|
||||||
//dynamic font (g.getWidth() > 200 ? 60 : 40)
|
//g.setFont("6x8",2).setFontAlign(0,0);
|
||||||
g.setFontVector(v_font_size2).setFontAlign(0,0);
|
g.setFontVector(v_font_size1).setFontAlign(0,0);
|
||||||
// Avg of temperature readings
|
var x = (rect.x+(rect.x2-60))/2;//-60 space for graph and layout buttons
|
||||||
while (history.length>4) history.shift();
|
var y = (rect.y+rect.y2)/2 + 20;
|
||||||
history.push(v_temp);
|
|
||||||
var avrTemp = E.sum(history) / history.length;
|
if (v_saveToFile==true) {
|
||||||
//var t = require('locale').temp(avrTemp);
|
// if (v_mode_debug>0) console.log("prev color="+v_color);
|
||||||
//.replace("'","°");
|
printInfo("Recording : "+v_saved_entries, '#CC3333',x,rect.y+30);
|
||||||
lastMeasure=avrTemp.toString();
|
//g.setColor('#CC3333'); //red
|
||||||
if (lastMeasure.length>4) lastMeasure=lastMeasure.substr(0,4);
|
// g.drawString("Recording : "+v_saved_entries, x, rect.y+35);
|
||||||
//DRAW temperature in the center
|
//g.setColor(v_color);//restore default color
|
||||||
//remove g.drawString(" ", x-20, y);
|
}
|
||||||
g.drawString(v_temp+v_t_symbol, x, y);
|
else printInfo("Rec paused : "+v_saved_entries, v_color,x,rect.y+30);
|
||||||
g.flip();
|
//else g.drawString("Rec paused : "+v_saved_entries, x, rect.y+35);
|
||||||
|
//space for printing info
|
||||||
|
g.drawString("Temperature:", x, rect.y+45+(v_font_size1*2));
|
||||||
|
|
||||||
|
if (v_temp>v_t_max) {
|
||||||
|
g.setColor(v_color_erase);
|
||||||
|
g.drawString(v_t_max.toString().substr(0,5), rect.x2-40,(rect.y2/2)-30);
|
||||||
|
v_t_max=v_temp;
|
||||||
|
g.setColor(v_color);
|
||||||
|
}
|
||||||
|
g.drawString(v_t_max.toString().substr(0,5), rect.x2-40,(rect.y2/2)-30);
|
||||||
|
if (v_temp<v_t_min) {
|
||||||
|
g.setColor(v_color_erase);
|
||||||
|
g.drawString(v_t_min.toString().substr(0,5), rect.x2-40,(rect.y2/2)+50);
|
||||||
|
v_t_min=v_temp;
|
||||||
|
g.setColor(v_color);
|
||||||
|
}
|
||||||
|
g.drawString(v_t_min.toString().substr(0,5), rect.x2-40,(rect.y2/2)+50);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//dynamic font (g.getWidth() > 200 ? 60 : 40)
|
||||||
|
g.setFontVector(v_font_size2).setFontAlign(0,0);
|
||||||
|
|
||||||
|
//DRAW temperature in the center
|
||||||
|
//5 char required for negative degrees
|
||||||
|
g.drawString((v_temp.toString().substr(0,5))+v_t_symbol, x, y);
|
||||||
|
g.flip();
|
||||||
}
|
}
|
||||||
// from: BJS2 pressure sensor, BJS1 inbuilt thermistor
|
// from: BJS2 pressure sensor, BJS1 inbuilt thermistor
|
||||||
function getTemperature() {
|
function getTemperature() {
|
||||||
if(v_model.substr(0,10)!='EMSCRIPTEN'){
|
if(v_model.substr(0,10)!='EMSCRIPTEN'){
|
||||||
if (Bangle.getPressure) {
|
if (Bangle.getPressure) {
|
||||||
Bangle.getPressure().then(p =>{if (p) printTemperature(p);});
|
Bangle.getPressure().then(p =>{if (p) printTemperature(p);});
|
||||||
} else printTemperature(E.getTemperature());
|
} else printTemperature(E.getTemperature());
|
||||||
}
|
}
|
||||||
else printTemperature(11.25);//fake temperature medition for emulators
|
else printTemperature(-11.2+Math.random());//fake temperature medition for emulators
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note that it changes BG and also FG to an opposite*/
|
/* Note that it changes BG and also FG to an opposite*/
|
||||||
function changeBGcolor(){
|
function changeBGcolor(){
|
||||||
//pend to refactor
|
//pend to refactor
|
||||||
if (v_mode_debug>1) console.log("before BG/FG "+v_color_erase+" /"+v_color);
|
if (v_mode_debug>1) console.log("before BG/FG "+v_color_erase+" /"+v_color);
|
||||||
v_color_erase=0xFFFF-v_color_erase;
|
v_color_erase=0xFFFF-v_color_erase;
|
||||||
v_color=0xFFFF-v_color;
|
v_color=0xFFFF-v_color;
|
||||||
if (v_mode_debug>1) console.log("after result BG/FG "+v_color_erase+" /"+v_color);
|
if (v_mode_debug>1) console.log("after result BG/FG "+v_color_erase+" /"+v_color);
|
||||||
//g.setColor(color_result);
|
//g.setColor(color_result);
|
||||||
g.setBgColor(v_color_erase);// 0 white, 1 black
|
g.setBgColor(v_color_erase);// 0 white, 1 black
|
||||||
g.setColor(v_color);
|
g.setColor(v_color);
|
||||||
//move to event?
|
//move to event?
|
||||||
ClearScreen();
|
ClearScreen();
|
||||||
ClearBox();
|
ClearBox();//?
|
||||||
drawGraph();
|
getTemperature();
|
||||||
getTemperature();
|
drawGraph();
|
||||||
//setDrawLayout(); //uncomment if layout can work with setUI
|
//setDrawLayout(); //uncomment if layout can work with setUI
|
||||||
//g.clear();//impact on widgets
|
//g.clear();//impact on widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveToFile(){
|
function saveToFile(){
|
||||||
//input global vars: lastMeasure
|
//input global vars: lastMeasure
|
||||||
var a=new Date();
|
var a=new Date();
|
||||||
var strlastSaveTime=new String();
|
var strlastSaveTime=new String();
|
||||||
strlastSaveTime=a.toISOString();
|
strlastSaveTime=a.toISOString();
|
||||||
//strlastSaveTime=strlastSaveTime.concat(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes());;
|
//strlastSaveTime=strlastSaveTime.concat(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes());;
|
||||||
if (v_mode_debug>1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure);
|
if (v_mode_debug>1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure);
|
||||||
if (v_saveToFile==true){
|
if (v_saveToFile==true){
|
||||||
//write(strlastSaveTime+";"+
|
//write(strlastSaveTime+";"+
|
||||||
//var f = require("Storage").open(v_filename,"r");
|
//var f = require("Storage").open(v_filename,"r");
|
||||||
// f=require("Storage").read(v_filename+"\1");//suffix required load completely!!
|
// f=require("Storage").read(v_filename+"\1");//suffix required load completely!!
|
||||||
//note that .read uses Storage Class .open uses StorageFile Class , difference in file chunks
|
//note that .read uses Storage Class .open uses StorageFile Class , difference in file chunks
|
||||||
// if (v_mode_debug>0) console.log("f "+f);
|
// if (v_mode_debug>0) console.log("f "+f);
|
||||||
var f = require("Storage").open(v_filename,"r");
|
var f = require("Storage").open(v_filename,"r");
|
||||||
if ((v_mode_debug>0) && (v_saved_entries==0)) console.log("file info:"+f);
|
if ((v_mode_debug>0) && (v_saved_entries==0)) console.log("file info:"+f);
|
||||||
if (f.len>0) {
|
if (f.len>0) {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
require("Storage").open(v_filename,"w").write("Month;Day;Time;Temp"+"\n");
|
require("Storage").open(v_filename,"w").write("Month;Day;Time;Temp"+"\n");
|
||||||
if (v_mode_debug>0) console.log("not exist but created "+f);
|
if (v_mode_debug>0) console.log("not exist but created "+f);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
require("Storage").open(v_filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n");
|
require("Storage").open(v_filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n");
|
||||||
//(getTime()+",");
|
//(getTime()+",");
|
||||||
v_saved_entries=v_saved_entries+1;
|
v_saved_entries=v_saved_entries+1;
|
||||||
if (v_mode_debug>1) console.log("append to already exist "+f.name+" , "+v_saved_entries);
|
if (v_mode_debug>1) console.log("append to already exist "+f.name+" , "+v_saved_entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (v_mode_debug>0) console.log("recording mode stopped");
|
else if (v_mode_debug>0) console.log("recording mode stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawGraph(){
|
function drawGraph(){
|
||||||
var img_obj_thermo = {
|
if (v_mode_debug>1) console.log("drawGraph");
|
||||||
width : 36, height : 36, bpp : 3,
|
var img_obj_thermo = {
|
||||||
transparent : 0,
|
width : 36, height : 36, bpp : 3,
|
||||||
buffer : require("heatshrink").decompress(atob("AEFt2AMKm3bsAMJjdt23ABhEB+/7tgaJ///DRUP//7tuADRP923YDRXbDRfymwaJhu/koaK7eyiwaK3cLDRlWDRY1NKBY1Ztu5kjmJg3cyVI7YMHgdu5Mkyu2fxHkyVJjdgDRFJkmRDRPsDQNbDQ5QBGoONKBJrBoxQIQwO2eRcbtu24AMIFIQLJAH4AMA=="))
|
transparent : 0,
|
||||||
};
|
buffer : require("heatshrink").decompress(atob("AEFt2AMKm3bsAMJjdt23ABhEB+/7tgaJ///DRUP//7tuADRP923YDRXbDRfymwaJhu/koaK7eyiwaK3cLDRlWDRY1NKBY1Ztu5kjmJg3cyVI7YMHgdu5Mkyu2fxHkyVJjdgDRFJkmRDRPsDQNbDQ5QBGoONKBJrBoxQIQwO2eRcbtu24AMIFIQLJAH4AMA=="))
|
||||||
g.drawImage(img_obj_thermo,rect.x2-60,rect.y2/2);
|
};
|
||||||
g.flip();
|
g.drawImage(img_obj_thermo,rect.x2-60,rect.y2/2);
|
||||||
|
g.flip();
|
||||||
}
|
}
|
||||||
function ClearScreen(){
|
function ClearScreen(){
|
||||||
//avoid widget areas
|
//avoid widget areas
|
||||||
g.setBgColor(v_color_erase);
|
g.setBgColor(v_color_erase);
|
||||||
g.clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24);
|
g.clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24);
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
}
|
||||||
function ClearBox(){
|
function ClearBox(){
|
||||||
//custom boxarea , left space for static graph at right
|
//custom boxarea , left space for static graph at right
|
||||||
g.setBgColor(v_color_erase);
|
g.setBgColor(v_color_erase);
|
||||||
g.clearRect(rect.x, rect.y+24, rect.x2-60, rect.y2-24);
|
g.clearRect(rect.x, rect.y+24, rect.x2-60, rect.y2-24);
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
}
|
||||||
function introPage(){
|
function introPage(){
|
||||||
//g.setFont("6x8",2).setFontAlign(0,0);
|
//g.setFont("6x8",2).setFontAlign(0,0);
|
||||||
g.setFontVector(v_font_size1).setFontAlign(-1,0);
|
g.setFontVector(v_font_size1).setFontAlign(-1,0);
|
||||||
//x alignment. -1=left (default), 0=center, 1=right
|
//x alignment. -1=left (default), 0=center, 1=right
|
||||||
var x=3;
|
var x=3;
|
||||||
//dynamic positions as height for BJS1 is double than BJS2
|
//dynamic positions as height for BJS1 is double than BJS2
|
||||||
var y = (rect.y+rect.y2)/2 + 10;
|
var y = (rect.y+rect.y2)/2 + 10;
|
||||||
g.drawString(" Default values ", x, y - ((v_font_size1*3)+2));
|
g.drawString(" Default values ", x, y - ((v_font_size1*3)+2));
|
||||||
g.drawString("--------------------", x, y - ((v_font_size1*2)+2));
|
g.drawString("--------------------", x, y - ((v_font_size1*2)+2));
|
||||||
g.drawString("Mode debug: "+v_mode_debug, x, y - ((v_font_size1*1)+2));
|
g.drawString("Mode debug: "+v_mode_debug, x, y - ((v_font_size1*1)+2));
|
||||||
g.drawString("Read freq(ms): "+readFreq, x, y );
|
g.drawString("Read frq(ms): "+readFreq, x, y );
|
||||||
g.drawString("Save to file: "+v_saveToFile, x, y+ ((v_font_size1*1)+2) );
|
g.drawString("Save file: "+v_saveToFile, x, y+ ((v_font_size1*1)+2) );
|
||||||
g.drawString("Save freq(ms):"+saveFreq, x, y+((v_font_size1*2)+2) );
|
g.drawString("Save frq(ms):"+saveFreq, x, y+((v_font_size1*2)+2) );
|
||||||
fr=require("Storage").read(v_filename+"\1");//suffix required
|
fr=require("Storage").read(v_filename+"\1");//suffix required
|
||||||
if (fr) g.drawString("Filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) );
|
if (fr) g.drawString("Filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) );
|
||||||
else g.drawString("File not exist", x, y+((v_font_size1*3)+2));
|
else g.drawString("File not exist", x, y+((v_font_size1*3)+2));
|
||||||
}
|
}
|
||||||
function printInfo(pmsg, pcolor,px,py){
|
function printInfo(pmsg, pcolor,px,py){
|
||||||
g.setColor(pcolor);
|
g.setColor(pcolor);
|
||||||
g.setFontVector(v_font_size1).setFontAlign(0,0);
|
g.setFontVector(v_font_size1).setFontAlign(0,0);
|
||||||
g.drawString(pmsg, px,py+v_font_size1);
|
g.drawString(pmsg, px,py+v_font_size1);
|
||||||
g.setColor(v_color);//restore default color
|
g.setColor(v_color);//restore default color
|
||||||
}
|
}
|
||||||
function toggleRecMode(duration, exectime){
|
function toggleRecMode(duration, exectime){
|
||||||
//bydefault float, standard epoch requires *1000
|
//bydefault float, standard epoch requires *1000
|
||||||
if (v_mode_debug>0) console.log("duration"+duration);
|
if (v_mode_debug>0) console.log("duration"+duration);
|
||||||
if (duration>2) { //delete file
|
if (duration>2) { //delete file
|
||||||
var x = (rect.x+(rect.x2-60))/2;
|
var x = (rect.x+(rect.x2-60))/2;
|
||||||
printInfo("Deleting file",'#CC3333',x, rect.y+32+v_font_size1);
|
printInfo("Deleting file",'#CC3333',x, rect.y+32+v_font_size1);
|
||||||
// g.setColor('#CC3333'); //red
|
// g.setColor('#CC3333'); //red
|
||||||
|
|
||||||
//too long "Deleting file: "+v_filename,
|
//too long "Deleting file: "+v_filename,
|
||||||
// for StorageFiles created with require("Storage").open(filename, ...)
|
// for StorageFiles created with require("Storage").open(filename, ...)
|
||||||
//require("Storage").erase(v_filename);
|
//require("Storage").erase(v_filename);
|
||||||
//TODO refactor in a new function
|
//TODO refactor in a new function
|
||||||
//var mifile = require("Storage").open(v_filename,"w");
|
//var mifile = require("Storage").open(v_filename,"w");
|
||||||
var mifile = require("Storage").open("temphistory.csv","w");
|
var mifile = require("Storage").open("temphistory.csv","w");
|
||||||
var v_output=mifile.erase();
|
var v_output=mifile.erase();
|
||||||
//mifile.StorageFile.erase();
|
//mifile.StorageFile.erase();
|
||||||
if (v_mode_debug>0) console.log("output"+v_output);
|
if (v_mode_debug>0) console.log("output"+v_output);
|
||||||
setTimeout(function() { if (v_mode_debug>0) console.log("pause for 1 sec");},1000);
|
setTimeout(function() { if (v_mode_debug>0) console.log("pause for 1 sec");},1000);
|
||||||
return; //leave this function
|
return; //leave this function
|
||||||
}
|
}
|
||||||
if (v_saveToFile) v_saveToFile=false;
|
if (v_saveToFile) v_saveToFile=false;
|
||||||
else v_saveToFile=true;
|
else v_saveToFile=true;
|
||||||
if (v_mode_debug>0) console.log("recording? "+v_saveToFile);
|
if (v_mode_debug>0) console.log("recording? "+v_saveToFile);
|
||||||
setRecordingFreq();
|
setRecordingFreq();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRecordingFreq(){
|
function setRecordingFreq(){
|
||||||
if (v_saveToFile==true) { //TODO now start on false btn will no enable
|
if (v_saveToFile==true) { //TODO now start on false btn will no enable
|
||||||
id_rec_intv=setInterval(function() {
|
id_rec_intv=setInterval(function() {
|
||||||
saveToFile();
|
saveToFile();
|
||||||
}, saveFreq); //ms
|
}, saveFreq); //ms
|
||||||
if (v_mode_debug>0) console.log("interval id / frq"+id_rec_intv+" / "+saveFreq);
|
if (v_mode_debug>0) console.log("interval id / frq"+id_rec_intv+" / "+saveFreq);
|
||||||
}
|
}
|
||||||
else if (id_rec_intv){
|
else if (id_rec_intv){
|
||||||
clearInterval(id_rec_intv);
|
clearInterval(id_rec_intv);
|
||||||
if (v_mode_debug>0) console.log("rec interval removed, id "+id_rec_intv);
|
if (v_mode_debug>0) console.log("rec interval removed, id "+id_rec_intv);
|
||||||
id_rec_intv=0; // to reset var
|
id_rec_intv=0; // to reset var
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserInput(){
|
function UserInput(){
|
||||||
//theoretically incompatible with Layout
|
//theoretically incompatible with Layout
|
||||||
Bangle.setUI({
|
Bangle.setUI({
|
||||||
mode : "custom",
|
mode : "custom",
|
||||||
//adds a back icon on top widget area
|
//adds a back icon on top widget area
|
||||||
back : function() {load();},
|
back : function() {load();},
|
||||||
//touch : function(n,e) {}, // optional - handler for 'touch' events
|
//touch : function(n,e) {}, // optional - handler for 'touch' events
|
||||||
// righ/Left 1/-1 , updown
|
// righ/Left 1/-1 , updown
|
||||||
swipe : function(dir_rl,dir_ud) {
|
swipe : function(dir_rl,dir_ud) {
|
||||||
if(dir_rl == 1) {
|
if(dir_rl == 1) {
|
||||||
if (v_mode_debug>0) console.log("swipe right: ");
|
if (v_mode_debug>0) console.log("swipe right: ");
|
||||||
getFileInfo(v_filename);
|
getFileInfo(v_filename);
|
||||||
}
|
}
|
||||||
else if (dir_rl == -1){
|
else if (dir_rl == -1){
|
||||||
if (v_mode_debug>0) console.log("swipe left: ");
|
if (v_mode_debug>0) console.log("swipe left: ");
|
||||||
changeBGcolor();
|
changeBGcolor();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
touch : function(tzone,tobj){
|
touch : function(tzone,tobj){
|
||||||
if ((process.env.HWVERSION == 2)&&(v_mode_debug>0)){
|
if ((process.env.HWVERSION == 2)&&(v_mode_debug>0)){
|
||||||
console.log("tobj x,y,type : "+tobj.x+" "+tobj.y+" "+tobj.type);
|
console.log("tobj x,y,type : "+tobj.x+" "+tobj.y+" "+tobj.type);
|
||||||
}
|
}
|
||||||
switch(tzone){
|
switch(tzone){
|
||||||
//case 1: //left , back managed by setUI
|
//case 1: //left , back managed by setUI
|
||||||
case 2: // right disable/enable recording
|
case 2: // right disable/enable recording
|
||||||
toggleRecMode(0); //toggleRecMode(duration, exectime)
|
toggleRecMode(0); //toggleRecMode(duration, exectime)
|
||||||
break;
|
break;
|
||||||
// case 3: console.log("Touch 3 aka 1+2 not for BJS1 emul");//center 1+2
|
// case 3: console.log("Touch 3 aka 1+2 not for BJS1 emul");//center 1+2
|
||||||
// break;
|
// break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//inferior to
|
//inferior to
|
||||||
btn : function(btn) {
|
btn : function(btn) {
|
||||||
if(btn == 1) {
|
if(btn == 1) {
|
||||||
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') toggleRecMode(1); //console.log("btn1 BJS1");
|
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') toggleRecMode(1); //console.log("btn1 BJS1");
|
||||||
else mainBtnShortcut(); //console.log("btn1 BJS2");
|
else mainBtnShortcut(); //console.log("btn1 BJS2");
|
||||||
}
|
}
|
||||||
else if (btn == 2) mainBtnShortcut(); //console.log("btn2 BJS1");
|
else if (btn == 2) mainBtnShortcut(); //console.log("btn2 BJS1");
|
||||||
else if (btn == 3) changeBGcolor(); //console.log("btn3 BJS1");
|
else if (btn == 3) changeBGcolor(); //console.log("btn3 BJS1");
|
||||||
}
|
}
|
||||||
}); //endof setUI
|
}); //endof setUI
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mainBtnShortcut() {
|
function mainBtnShortcut() {
|
||||||
//if messages app installed shortcut otherwise default access to launcher
|
//if messages app installed shortcut otherwise default access to launcher
|
||||||
if (require("Storage").read("messagegui.app.js")===undefined)
|
if (require("Storage").read("messagegui.app.js")===undefined)
|
||||||
{
|
{
|
||||||
if (require("Storage").read("messagelist.app.js")===undefined) Bangle.showLauncher(); // implies btn2(js1) btn(js2)- launcher
|
if (require("Storage").read("messagelist.app.js")===undefined) Bangle.showLauncher(); // implies btn2(js1) btn(js2)- launcher
|
||||||
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagelist.app.js");
|
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagelist.app.js");
|
||||||
else load("messagelist.app.js");
|
else load("messagelist.app.js");
|
||||||
}
|
}
|
||||||
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagegui.app.js");
|
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagegui.app.js");
|
||||||
else load("messagegui.app.js");
|
else load("messagegui.app.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Show file size
|
// Show file size
|
||||||
function getFileInfo(v_filename) {
|
function getFileInfo(v_filename) {
|
||||||
var f = require("Storage").open(v_filename,"r");
|
var f = require("Storage").open(v_filename,"r");
|
||||||
//todo refactor and reuse common code
|
//todo refactor and reuse common code
|
||||||
g.setFontVector(v_font_size1).setFontAlign(0,0);
|
g.setFontVector(v_font_size1).setFontAlign(0,0);
|
||||||
var x = (rect.x+(rect.x2-60))/2;
|
var x = (rect.x+(rect.x2-60))/2;
|
||||||
printInfo("file size:"+f.len,v_color,x, rect.y+32+v_font_size1);
|
printInfo("file size:"+f.len,v_color,x, rect.y+32+v_font_size1);
|
||||||
// g.drawString("file size:"+f.len, x, rect.y+37+v_font_size1);
|
// g.drawString("file size:"+f.len, x, rect.y+37+v_font_size1);
|
||||||
if (v_mode_debug>0) console.log("file "+v_filename+" size: "+f.len);
|
if (v_mode_debug>0) console.log("file "+v_filename+" size: "+f.len);
|
||||||
}// not used
|
}// not used
|
||||||
|
|
||||||
|
|
||||||
//MAIN
|
//MAIN
|
||||||
SetVariables();
|
SetVariables();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
ClearScreen();
|
ClearScreen();
|
||||||
introPage();
|
introPage();
|
||||||
|
|
||||||
//setDrawLayout(); //uncomment if layout can work with setUI
|
//setDrawLayout(); //uncomment if layout can work with setUI
|
||||||
|
|
||||||
UserInput(); //inc SetUI and back icon
|
UserInput(); //inc SetUI and back icon
|
||||||
|
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
getTemperature();
|
getTemperature();
|
||||||
}, readFreq); //ms
|
}, readFreq); //ms
|
||||||
|
//??need
|
||||||
|
drawGraph();
|
||||||
setRecordingFreq();
|
setRecordingFreq();
|
||||||
|
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.03","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"}
|
{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.04","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"}
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New Widget!
|
|
@ -0,0 +1,13 @@
|
||||||
|
{ "id": "widclkinfo",
|
||||||
|
"name": "Clock Info Widget",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Use 'Clock Info' in the Widget bar. Tap on the widget to select, then drag up/down/left/right to choose what information is displayed.",
|
||||||
|
"icon": "widget.png",
|
||||||
|
"screenshots" : [ { "url":"screenshot.png" }],
|
||||||
|
"type": "widget",
|
||||||
|
"tags": "widget,clkinfo",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"widclkinfo.wid.js","url":"widget.js"}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,49 @@
|
||||||
|
if (!require("clock_info").loadCount) { // don't load if a clock_info was already loaded
|
||||||
|
// Load the clock infos
|
||||||
|
let clockInfoItems = require("clock_info").load();
|
||||||
|
// Add the
|
||||||
|
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, {
|
||||||
|
// Add the dimensions we're rendering to here - these are used to detect taps on the clock info area
|
||||||
|
x : 0, y: 0, w: 72, h:24,
|
||||||
|
// You can add other information here you want to be passed into 'options' in 'draw'
|
||||||
|
// This function draws the info
|
||||||
|
draw : (itm, info, options) => {
|
||||||
|
// itm: the item containing name/hasRange/etc
|
||||||
|
// info: data returned from itm.get() containing text/img/etc
|
||||||
|
// options: options passed into addInteractive
|
||||||
|
clockInfoInfo = info;
|
||||||
|
if (WIDGETS["clkinfo"])
|
||||||
|
WIDGETS["clkinfo"].draw(WIDGETS["clkinfo"]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let clockInfoInfo; // when clockInfoMenu.draw is called we set this up
|
||||||
|
|
||||||
|
// The actual widget we're displaying
|
||||||
|
WIDGETS["clkinfo"] = {
|
||||||
|
area:"tl",
|
||||||
|
width: clockInfoMenu.w,
|
||||||
|
draw:function(e) {
|
||||||
|
clockInfoMenu.x = e.x;
|
||||||
|
clockInfoMenu.y = e.y;
|
||||||
|
var o = clockInfoMenu;
|
||||||
|
// Clear the background
|
||||||
|
g.reset();
|
||||||
|
// indicate focus - make background reddish
|
||||||
|
//if (clockInfoMenu.focus) g.setBgColor(g.blendColor(g.theme.bg, "#f00", 0.25));
|
||||||
|
if (clockInfoMenu.focus) g.setColor("#f00");
|
||||||
|
g.clearRect(o.x, o.y, o.x+o.w-1, o.y+o.h);
|
||||||
|
if (clockInfoInfo) {
|
||||||
|
var x = o.x;
|
||||||
|
if (clockInfoInfo.img) {
|
||||||
|
g.drawImage(clockInfoInfo.img, x,o.y); // draw the image
|
||||||
|
x+=24;
|
||||||
|
}
|
||||||
|
var availableWidth = o.x+clockInfoMenu.w - (x+2);
|
||||||
|
g.setFont("6x8:2").setFontAlign(-1,0);
|
||||||
|
if (g.stringWidth(clockInfoInfo.text) > availableWidth)
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.drawString(clockInfoInfo.text, x+2,o.y+12); // draw the text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
After Width: | Height: | Size: 6.9 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
# Description
|
||||||
|
|
||||||
|
A music control widget based on [Swipe Bluetooth Music Controls] (based on [Bluetooth Music Controls]).
|
||||||
|
By operating as a widget, you can control music without leaving your current app (e.g. on a run, bike ride or just watching the clock).
|
||||||
|
|
||||||
|
|
||||||
|
[Swipe Bluetooth Music Controls]: https://github.com/espruino/BangleApps/tree/master/apps/hidmsicswipe
|
||||||
|
[Bluetooth Music Controls]: https://github.com/espruino/BangleApps/tree/master/apps/hidmsic
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Swipe down to enable - note the icon changes from blue to orange, indicating it's listening for your instruction. Then drag up/down for volume, left/right for previous and next and tap for play/pause.
|
||||||
|
|
||||||
|
All other watch interaction is disabled for 3 seconds, to prevent clashing taps/drags - this period is extended as you continue to alter the volume, play/pause and jump between tracks.
|
||||||
|
|
||||||
|
|
||||||
|
# Setup / Technical details
|
||||||
|
|
||||||
|
Note that HID must be enabled in settings. Then provided you're paired with your phone/computer, the widget icon will appear and you can control music from your clock face!
|
||||||
|
|
||||||
|
The app disables all other drag and tap handlers while this widget is "active" (in a similar manner to [`backswipe`](https://github.com/espruino/BangleApps/pull/2524#issuecomment-1406230564) and [`lightswitch`](https://github.com/espruino/Espruino/issues/2151#issuecomment-1042423211)).
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkBiIA/AEhtDNSSHGCyIbFGJ0QFAowIA48QE4oGBBAomBHAxXHA4IJED5IXJCAcQIAxGGC4YKEI44HCBAxAGO4wXBB4JYGNRBfHC/6HFB4wXHUA6YIC4oOCGA6YGU4quHJ5LXGdJIXNF65fIC5AQFQorHJXxwXJK5xGJC65GsgJG/Iw4uUfgIuUC4QWTIwIusLq4WBFy50tC1YXBCyoA/ADw="))
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"id": "widhid",
|
||||||
|
"name": "Bluetooth Music Swipe Control Widget",
|
||||||
|
"shortName": "BLE Swipe Widget",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Based on Swipe Bluetooth Music Controls (based on Bluetooth Music Controls). Swipe down to enable, then swipe up/down for volume, left/right for previous and next and tap for play/pause. Enable HID in settings, pair with your phone/computer, then use this widget to control music from your watch!",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"readme": "README.md",
|
||||||
|
"type": "widget",
|
||||||
|
"tags": "widget,bluetooth,music",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"widhid.wid.js","url":"wid.js"},
|
||||||
|
{"name":"widhid.img","url":"icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
(function () {
|
||||||
|
var settings = require("Storage").readJSON("setting.json", true) || { HID: false };
|
||||||
|
if (settings.HID !== "kbmedia") {
|
||||||
|
console.log("widhid: can't enable, HID setting isn't \"kbmedia\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete settings;
|
||||||
|
var anchor = { x: 0, y: 0 };
|
||||||
|
var start = { x: 0, y: 0 };
|
||||||
|
var dragging = false;
|
||||||
|
var activeTimeout;
|
||||||
|
var waitForRelease = true;
|
||||||
|
var onSwipe = (function (_lr, ud) {
|
||||||
|
if (Bangle.CLKINFO_FOCUS)
|
||||||
|
return;
|
||||||
|
if (!activeTimeout && ud > 0) {
|
||||||
|
listen();
|
||||||
|
Bangle.buzz(20);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var onDrag = (function (e) {
|
||||||
|
if (Bangle.CLKINFO_FOCUS)
|
||||||
|
return;
|
||||||
|
if (e.b === 0) {
|
||||||
|
var wasDragging = dragging;
|
||||||
|
dragging = false;
|
||||||
|
if (waitForRelease) {
|
||||||
|
waitForRelease = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!wasDragging
|
||||||
|
|| (Math.abs(e.x - anchor.x) < 2 && Math.abs(e.y - anchor.y) < 2)) {
|
||||||
|
toggle();
|
||||||
|
onEvent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (waitForRelease)
|
||||||
|
return;
|
||||||
|
if (e.b && !dragging) {
|
||||||
|
dragging = true;
|
||||||
|
setStart(e);
|
||||||
|
Object.assign(anchor, start);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dx = e.x - start.x;
|
||||||
|
var dy = e.y - start.y;
|
||||||
|
if (Math.abs(dy) > 25 && Math.abs(dx) > 25) {
|
||||||
|
setStart(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dx > 40) {
|
||||||
|
next();
|
||||||
|
onEvent();
|
||||||
|
waitForRelease = true;
|
||||||
|
}
|
||||||
|
else if (dx < -40) {
|
||||||
|
prev();
|
||||||
|
onEvent();
|
||||||
|
waitForRelease = true;
|
||||||
|
}
|
||||||
|
else if (dy > 30) {
|
||||||
|
down();
|
||||||
|
onEvent();
|
||||||
|
setStart(e);
|
||||||
|
}
|
||||||
|
else if (dy < -30) {
|
||||||
|
up();
|
||||||
|
onEvent();
|
||||||
|
setStart(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var setStart = function (_a) {
|
||||||
|
var x = _a.x, y = _a.y;
|
||||||
|
start.x = x;
|
||||||
|
start.y = y;
|
||||||
|
};
|
||||||
|
var onEvent = function () {
|
||||||
|
Bangle.buzz(20);
|
||||||
|
listen();
|
||||||
|
};
|
||||||
|
var listen = function () {
|
||||||
|
var wasActive = !!activeTimeout;
|
||||||
|
if (!wasActive) {
|
||||||
|
suspendOthers();
|
||||||
|
waitForRelease = true;
|
||||||
|
Bangle.on("drag", onDrag);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
if (activeTimeout)
|
||||||
|
clearTimeout(activeTimeout);
|
||||||
|
activeTimeout = setTimeout(function () {
|
||||||
|
activeTimeout = undefined;
|
||||||
|
Bangle.removeListener("drag", onDrag);
|
||||||
|
resumeOthers();
|
||||||
|
redraw();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
var redraw = function () { return setTimeout(Bangle.drawWidgets, 50); };
|
||||||
|
var connected = NRF.getSecurityStatus().connected;
|
||||||
|
WIDGETS["hid"] = {
|
||||||
|
area: "tr",
|
||||||
|
sortorder: -20,
|
||||||
|
draw: function () {
|
||||||
|
if (this.width === 0)
|
||||||
|
return;
|
||||||
|
g.drawImage(activeTimeout
|
||||||
|
? require("heatshrink").decompress(atob("jEYxH+AEfH44XXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA=="))
|
||||||
|
: require("heatshrink").decompress(atob("jEYxH+AEcdjoXXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA==")), this.x + 2, this.y + 2);
|
||||||
|
},
|
||||||
|
width: connected ? 24 : 0,
|
||||||
|
};
|
||||||
|
if (connected)
|
||||||
|
Bangle.on("swipe", onSwipe);
|
||||||
|
delete connected;
|
||||||
|
NRF.on("connect", function () {
|
||||||
|
WIDGETS["hid"].width = 24;
|
||||||
|
Bangle.on("swipe", onSwipe);
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
NRF.on("disconnect", function () {
|
||||||
|
WIDGETS["hid"].width = 0;
|
||||||
|
Bangle.removeListener("swipe", onSwipe);
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
var sendHid = function (code) {
|
||||||
|
NRF.sendHIDReport([1, code], function () { return NRF.sendHIDReport([1, 0]); });
|
||||||
|
};
|
||||||
|
var next = function () { return sendHid(0x01); };
|
||||||
|
var prev = function () { return sendHid(0x02); };
|
||||||
|
var toggle = function () { return sendHid(0x10); };
|
||||||
|
var up = function () { return sendHid(0x40); };
|
||||||
|
var down = function () { return sendHid(0x80); };
|
||||||
|
var touchEvents = {
|
||||||
|
tap: null,
|
||||||
|
gesture: null,
|
||||||
|
aiGesture: null,
|
||||||
|
swipe: null,
|
||||||
|
touch: null,
|
||||||
|
drag: null,
|
||||||
|
stroke: null,
|
||||||
|
};
|
||||||
|
var suspendOthers = function () {
|
||||||
|
for (var event in touchEvents) {
|
||||||
|
var handlers = Bangle["#on".concat(event)];
|
||||||
|
if (!handlers)
|
||||||
|
continue;
|
||||||
|
var newEvents = void 0;
|
||||||
|
if (handlers instanceof Array)
|
||||||
|
newEvents = handlers.slice();
|
||||||
|
else
|
||||||
|
newEvents = [handlers];
|
||||||
|
for (var _i = 0, newEvents_1 = newEvents; _i < newEvents_1.length; _i++) {
|
||||||
|
var handler = newEvents_1[_i];
|
||||||
|
Bangle.removeListener(event, handler);
|
||||||
|
}
|
||||||
|
touchEvents[event] = newEvents;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var resumeOthers = function () {
|
||||||
|
for (var event in touchEvents) {
|
||||||
|
var handlers = touchEvents[event];
|
||||||
|
touchEvents[event] = null;
|
||||||
|
if (handlers)
|
||||||
|
for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
|
||||||
|
var handler = handlers_1[_i];
|
||||||
|
try {
|
||||||
|
Bangle.on(event, handler);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("couldn't restore \"".concat(event, "\" handler:"), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
|
@ -0,0 +1,199 @@
|
||||||
|
(() => {
|
||||||
|
const settings: Settings = require("Storage").readJSON("setting.json", true) || { HID: false } as Settings;
|
||||||
|
if (settings.HID !== "kbmedia") {
|
||||||
|
console.log("widhid: can't enable, HID setting isn't \"kbmedia\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
delete settings;
|
||||||
|
|
||||||
|
let anchor = {x:0,y:0};
|
||||||
|
let start = {x:0,y:0};
|
||||||
|
let dragging = false;
|
||||||
|
let activeTimeout: number | undefined;
|
||||||
|
let waitForRelease = true;
|
||||||
|
|
||||||
|
const onSwipe = ((_lr, ud) => {
|
||||||
|
if((Bangle as BangleExt).CLKINFO_FOCUS) return;
|
||||||
|
|
||||||
|
if(!activeTimeout && ud! > 0){
|
||||||
|
listen();
|
||||||
|
Bangle.buzz(20);
|
||||||
|
}
|
||||||
|
}) satisfies SwipeCallback;
|
||||||
|
|
||||||
|
const onDrag = (e => {
|
||||||
|
if((Bangle as BangleExt).CLKINFO_FOCUS) return;
|
||||||
|
|
||||||
|
if(e.b === 0){
|
||||||
|
// released
|
||||||
|
const wasDragging = dragging;
|
||||||
|
dragging = false;
|
||||||
|
|
||||||
|
if(waitForRelease){
|
||||||
|
waitForRelease = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!wasDragging // i.e. tap
|
||||||
|
|| (Math.abs(e.x - anchor.x) < 2 && Math.abs(e.y - anchor.y) < 2))
|
||||||
|
{
|
||||||
|
toggle();
|
||||||
|
onEvent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(waitForRelease) return;
|
||||||
|
|
||||||
|
if(e.b && !dragging){
|
||||||
|
dragging = true;
|
||||||
|
setStart(e);
|
||||||
|
Object.assign(anchor, start);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dx = e.x - start.x;
|
||||||
|
const dy = e.y - start.y;
|
||||||
|
|
||||||
|
if(Math.abs(dy) > 25 && Math.abs(dx) > 25){
|
||||||
|
// diagonal, ignore
|
||||||
|
setStart(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// had a drag in a single axis
|
||||||
|
if(dx > 40){ next(); onEvent(); waitForRelease = true; }
|
||||||
|
else if(dx < -40){ prev(); onEvent(); waitForRelease = true; }
|
||||||
|
else if(dy > 30){ down(); onEvent(); setStart(e); }
|
||||||
|
else if(dy < -30){ up(); onEvent(); setStart(e); }
|
||||||
|
}) satisfies DragCallback;
|
||||||
|
|
||||||
|
const setStart = ({ x, y }: { x: number, y: number }) => {
|
||||||
|
start.x = x;
|
||||||
|
start.y = y;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEvent = () => {
|
||||||
|
Bangle.buzz(20); // feedback event sent
|
||||||
|
listen(); // had an event, keep listening for more
|
||||||
|
};
|
||||||
|
|
||||||
|
const listen = () => {
|
||||||
|
const wasActive = !!activeTimeout;
|
||||||
|
if(!wasActive){
|
||||||
|
suspendOthers();
|
||||||
|
waitForRelease = true; // wait for first touch up before accepting gestures
|
||||||
|
Bangle.on("drag", onDrag);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activeTimeout) clearTimeout(activeTimeout);
|
||||||
|
activeTimeout = setTimeout(() => {
|
||||||
|
activeTimeout = undefined;
|
||||||
|
|
||||||
|
Bangle.removeListener("drag", onDrag);
|
||||||
|
resumeOthers();
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const redraw = () => setTimeout(Bangle.drawWidgets, 50);
|
||||||
|
|
||||||
|
const connected = NRF.getSecurityStatus().connected;
|
||||||
|
WIDGETS["hid"] = {
|
||||||
|
area: "tr",
|
||||||
|
sortorder: -20,
|
||||||
|
draw: function() {
|
||||||
|
if(this.width === 0) return;
|
||||||
|
g.drawImage(
|
||||||
|
activeTimeout
|
||||||
|
? require("heatshrink").decompress(atob("jEYxH+AEfH44XXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA=="))
|
||||||
|
: require("heatshrink").decompress(atob("jEYxH+AEcdjoXXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA==")),
|
||||||
|
this.x! + 2,
|
||||||
|
this.y! + 2
|
||||||
|
);
|
||||||
|
},
|
||||||
|
width: connected ? 24 : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(connected)
|
||||||
|
Bangle.on("swipe", onSwipe);
|
||||||
|
// @ts-ignore
|
||||||
|
delete connected;
|
||||||
|
|
||||||
|
NRF.on("connect", () => {
|
||||||
|
WIDGETS["hid"]!.width = 24;
|
||||||
|
Bangle.on("swipe", onSwipe);
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
NRF.on("disconnect", () => {
|
||||||
|
WIDGETS["hid"]!.width = 0;
|
||||||
|
Bangle.removeListener("swipe", onSwipe);
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
|
||||||
|
//const DEBUG = true;
|
||||||
|
const sendHid = (code: number) => {
|
||||||
|
//if(DEBUG) return;
|
||||||
|
NRF.sendHIDReport(
|
||||||
|
[1, code],
|
||||||
|
() => NRF.sendHIDReport([1, 0]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const next = () => /*DEBUG ? console.log("next") : */ sendHid(0x01);
|
||||||
|
const prev = () => /*DEBUG ? console.log("prev") : */ sendHid(0x02);
|
||||||
|
const toggle = () => /*DEBUG ? console.log("toggle") : */ sendHid(0x10);
|
||||||
|
const up = () => /*DEBUG ? console.log("up") : */ sendHid(0x40);
|
||||||
|
const down = () => /*DEBUG ? console.log("down") : */ sendHid(0x80);
|
||||||
|
|
||||||
|
// similarly to the lightswitch app, we tangle with the listener arrays to
|
||||||
|
// disable event handlers
|
||||||
|
type Handler = () => void;
|
||||||
|
const touchEvents: {
|
||||||
|
[key: string]: null | Handler[]
|
||||||
|
} = {
|
||||||
|
tap: null,
|
||||||
|
gesture: null,
|
||||||
|
aiGesture: null,
|
||||||
|
swipe: null,
|
||||||
|
touch: null,
|
||||||
|
drag: null,
|
||||||
|
stroke: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const suspendOthers = () => {
|
||||||
|
for(const event in touchEvents){
|
||||||
|
const handlers: Handler[] | Handler | undefined
|
||||||
|
= (Bangle as any)[`#on${event}`];
|
||||||
|
|
||||||
|
if(!handlers) continue;
|
||||||
|
|
||||||
|
let newEvents;
|
||||||
|
if(handlers instanceof Array)
|
||||||
|
newEvents = handlers.slice();
|
||||||
|
else
|
||||||
|
newEvents = [handlers /* single fn */];
|
||||||
|
|
||||||
|
for(const handler of newEvents)
|
||||||
|
Bangle.removeListener(event, handler);
|
||||||
|
|
||||||
|
touchEvents[event] = newEvents;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const resumeOthers = () => {
|
||||||
|
for(const event in touchEvents){
|
||||||
|
const handlers = touchEvents[event];
|
||||||
|
touchEvents[event] = null;
|
||||||
|
|
||||||
|
if(handlers)
|
||||||
|
for(const handler of handlers)
|
||||||
|
try{
|
||||||
|
Bangle.on(event as any, handler);
|
||||||
|
}catch(e){
|
||||||
|
console.log(`couldn't restore "${event}" handler:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})()
|
After Width: | Height: | Size: 739 B |