From c99907bdcd0520b8dbaadcea00393cfeef4c1f27 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sun, 14 May 2023 18:56:08 +0200 Subject: [PATCH 001/116] lightswitch: handle and intercept swipe event --- apps/lightswitch/ChangeLog | 1 + apps/lightswitch/metadata.json | 2 +- apps/lightswitch/widget.js | 16 +++++++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/lightswitch/ChangeLog b/apps/lightswitch/ChangeLog index c4aeb2c1e..4f62ab799 100644 --- a/apps/lightswitch/ChangeLog +++ b/apps/lightswitch/ChangeLog @@ -4,3 +4,4 @@ 0.04: Add masking widget input to other apps (using espruino/Espruino#2151), add a oversize option to increase the touch area. 0.05: Prevent drawing into app area. 0.06: Fix issue where .draw was being called by reference (not allowing widgets to be hidden) +0.07: Handle the swipe event that is generated when draging to change light intensity, so it doesn't trigger some other swipe handler. diff --git a/apps/lightswitch/metadata.json b/apps/lightswitch/metadata.json index d1a8d6e2a..f1f0160b8 100644 --- a/apps/lightswitch/metadata.json +++ b/apps/lightswitch/metadata.json @@ -2,7 +2,7 @@ "id": "lightswitch", "name": "Light Switch Widget", "shortName": "Light Switch", - "version": "0.06", + "version": "0.07", "description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.", "icon": "images/app.png", "screenshots": [ diff --git a/apps/lightswitch/widget.js b/apps/lightswitch/widget.js index 922875216..6b573355b 100644 --- a/apps/lightswitch/widget.js +++ b/apps/lightswitch/widget.js @@ -165,13 +165,12 @@ w.changeValue(value, event.b); // masks this drag event by messing up the event handler - // see https://github.com/espruino/Espruino/issues/2151 - Bangle.removeListener("drag", w.dragListener); - Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]); + E.stopEventPropagation&&E.stopEventPropagation(); // on touch release remove drag listener and reset drag status to indicate stopped drag action if (!event.b) { Bangle.removeListener("drag", w.dragListener); + Bangle.removeListener("swipe", w.swipeListener); w.dragStatus = "off"; } @@ -181,6 +180,11 @@ value = undefined; }, + swipeListener: function(_,__) { + // masks this swipe event by messing up the event handler + E.stopEventPropagation&&E.stopEventPropagation(); + }, + // listener function // // touch listener for light control touchListener: function(button, cursor) { @@ -197,12 +201,14 @@ Bangle.buzz(25); // check if drag is disabled if (w.dragDelay) { - // add drag listener at first position + // add drag and swipe listeners at respective first position Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]); + Bangle["#onswipe"] = [w.swipeListener].concat(Bangle["#onswipe"]); // set drag timeout w.dragStatus = setTimeout((w) => { - // remove drag listener + // remove drag and swipe listeners Bangle.removeListener("drag", w.dragListener); + Bangle.removeListener("swipe", w.swipeListener); // clear drag timeout if (typeof w.dragStatus === "number") clearTimeout(w.dragStatus); // reset drag status to indicate stopped drag action From c5b21bf64d593083f132203f39e1c1b16a65fca7 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Thu, 25 May 2023 05:53:01 -0500 Subject: [PATCH 002/116] Update app.js - increased legibility - reworked GUI --- apps/Uke/app.js | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/Uke/app.js b/apps/Uke/app.js index c60c49a6b..3c28381c0 100644 --- a/apps/Uke/app.js +++ b/apps/Uke/app.js @@ -72,12 +72,28 @@ var ee = [ var index = 0; var chords = []; +var menu = { + "" : { + "title" : "Uke Chords" + }, + "C" : function() { draw(cc); }, + "D" : function() { draw(dd); }, + "E" : function() { draw(ee); }, + "Em" : function() { draw(em); }, + "A" : function() { draw(aa); }, + "Am" : function() { draw(am); }, + "F" : function() { draw(ff); }, + "G" : function() { draw(gg); }, + "About" : function() { + E.showMessage( + "Created By:\nNovaDawn999", { + title:"About" + } + ); + } +}; + -function init() { - g.setFontAlign(0,0); // center font - g.setFont("6x8",2); // bitmap font, 8x magnified - chords.push(cc, dd, gg, am, em, aa, ff, ee); -} function drawBase() { for (let i = 0; i < 4; i++) { @@ -87,18 +103,18 @@ function drawBase() { } function drawChord(chord) { - g.drawString(chord[0], g.getWidth() * 0.5 + 2, 18); + g.drawString(chord[0], g.getWidth() * 0.5 - 3, 18); for (let i = 0; i < chord.length; i++) { if (i === 0 || chord[i][0] === "x") { continue; } if (chord[i][0] === "0") { - g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y + fretHeight * chord[i][0], true); - g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 8); + g.drawString(chord[i][1], x + (i - 1) * stringInterval - 5, y + fretHeight * chord[i][0] + 2, true); + g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 10); } else { - g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y -fingerOffset + fretHeight * chord[i][0], true); - g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 8); + g.drawString(chord[i][1], x + (i - 1) * stringInterval -5, y -fingerOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 10); } } } @@ -107,22 +123,19 @@ function buttonPress() { setWatch(() => { buttonPress(); }, BTN); - index++; - if (index >= chords.length) { index = 0; } - draw(); + E.showMenu(menu); } -function draw() { +function draw(chord) { g.clear(); drawBase(); - drawChord(chords[index]); + drawChord(chord); } function main() { - init(); - draw(); + E.showMenu(menu); setWatch(() => { buttonPress(); }, BTN); From dc27727157b28ebfc861c0e2adf413ffce7e2fd4 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Thu, 25 May 2023 05:53:41 -0500 Subject: [PATCH 003/116] Update metadata.json 0.02 --- apps/Uke/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/Uke/metadata.json b/apps/Uke/metadata.json index 10c3b3e79..8d92718c3 100644 --- a/apps/Uke/metadata.json +++ b/apps/Uke/metadata.json @@ -1,7 +1,7 @@ { "id": "Uke", "name": "Uke Chords", "shortName":"Uke", - "version":"0.01", + "version":"0.02", "description": "Wrist mounted ukulele chords", "icon": "app.png", "tags": "uke, chords", From e4e45b5d2d2d666b789027907c1e67a8e2bbb493 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Thu, 25 May 2023 05:54:18 -0500 Subject: [PATCH 004/116] Update ChangeLog --- apps/Uke/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/Uke/ChangeLog b/apps/Uke/ChangeLog index 5560f00bc..366158b0e 100644 --- a/apps/Uke/ChangeLog +++ b/apps/Uke/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Increased Legibility, GUI rework From 6fb3417f82549145d36e6b67a8491f9475fed2b3 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Thu, 25 May 2023 05:57:10 -0500 Subject: [PATCH 005/116] Update README.md --- apps/Uke/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/Uke/README.md b/apps/Uke/README.md index 49ceea1ed..b6236e307 100644 --- a/apps/Uke/README.md +++ b/apps/Uke/README.md @@ -4,7 +4,8 @@ An app that simply describes finger placements on a Ukulele to form common chord ## Usage -Use the button to scroll through the available chords. +Select a chord to view. +Use the button to return to the chord selection menu. ## Creator From 464ef744504d823bb1bd3b0c3147c08f4915a622 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:01:20 +0100 Subject: [PATCH 006/116] bikespeedo: trim dead code --- apps/bikespeedo/app.js | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index 6f462a820..d68c089fd 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -12,7 +12,7 @@ const fontFactorB2 = 2/3; const colfg=g.theme.fg, colbg=g.theme.bg; const col1=colfg, colUncertain="#88f"; // if (lf.fix) g.setColor(col1); else g.setColor(colUncertain); -var altiGPS=0, altiBaro=0; +var altiBaro=0; var hdngGPS=0, hdngCompass=0, calibrateCompass=false; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ @@ -183,7 +183,6 @@ var KalmanFilter = (function () { var lf = {fix:0,satellites:0}; var showMax = 0; // 1 = display the max values. 0 = display the cur fix -var canDraw = 1; var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. var sec; // actual seconds for testing purposes @@ -194,30 +193,9 @@ max.n = 0; // counter. Only start comparing for max after a certain number of var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTEN2")?1:0; // 1 = running in emulator. Supplies test values; -var wp = {}; // Waypoint to use for distance from cur position. var SATinView = 0; -function radians(a) { - return a*Math.PI/180; -} - -function distance(a,b){ - var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); - var y = radians(b.lat-a.lat); - - // Distance in selected units - var d = Math.sqrt(x*x + y*y) * 6371000; - d = (d/parseFloat(cfg.dist)).toFixed(2); - if ( d >= 100 ) d = parseFloat(d).toFixed(1); - if ( d >= 1000 ) d = parseFloat(d).toFixed(0); - - return d; -} - function drawFix(dat) { - - if (!canDraw) return; - g.clearRect(0,screenYstart,screenW,screenH); var v = ''; @@ -260,14 +238,6 @@ function drawFix(dat) { } -function drawClock() { - if (!canDraw) return; - g.clearRect(0,screenYstart,screenW,screenH); - drawTime(); - g.reset(); -} - - function drawPrimary(n,u) { //if(emulator)console.log("\n1: " + n +" "+ u); var s=40; // Font size @@ -367,7 +337,6 @@ function onGPS(fix) { var sp = '---'; var al = '---'; - var di = '---'; var age = '---'; if (fix.fix) lf = fix; @@ -412,10 +381,6 @@ function onGPS(fix) { al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al); - // Distance to waypoint - di = distance(lf,wp); - if (isNaN(di)) di = 0; - // Age of last fix (secs) age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); } else { @@ -456,7 +421,6 @@ onGPS(lf); function updateClock() { - if (!canDraw) return; drawTime(); g.reset(); From 598940e90e54a098c25aea4c9811bb7cb93d155d Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:01:29 +0100 Subject: [PATCH 007/116] recorder: expose isRecording() --- apps/recorder/widget.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index e57f293c7..a17d09a53 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -231,6 +231,8 @@ },getRecorders:getRecorders,reload:function() { reload(); Bangle.drawWidgets(); // relayout all widgets + },isRecording:function() { + return !!writeInterval; },setRecording:function(isOn, options) { /* options = { force : [optional] "append"/"new"/"overwrite" - don't ask, just do what's requested From 12e6e4b2b05c226ea0614d0583f24f9c8911a39b Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:01:42 +0100 Subject: [PATCH 008/116] bikespeedo: toggle recording on btn press --- apps/bikespeedo/app.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index d68c089fd..29120772b 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -535,6 +535,20 @@ if (cfg.record && WIDGETS["recorder"]) { if (cfg.recordStopOnExit) E.on('kill', () => WIDGETS["recorder"].setRecording(false)); + + Bangle.setUI( + "custom", + () => { + const wid = WIDGETS["recorder"]; + const active = wid.isRecording(); + + if(active) + wid.setRecording(false); + else + wid.setRecording(true, { force: "append" }); + }, + ); + } else { start(); } From cca9906f85ba479bc6de073f7c8e08e921b8ae00 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:04:29 +0100 Subject: [PATCH 009/116] bikespeedo: check recorder widget each press --- apps/bikespeedo/app.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index 29120772b..0a056f938 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -536,19 +536,20 @@ if (cfg.record && WIDGETS["recorder"]) { if (cfg.recordStopOnExit) E.on('kill', () => WIDGETS["recorder"].setRecording(false)); - Bangle.setUI( - "custom", - () => { - const wid = WIDGETS["recorder"]; - const active = wid.isRecording(); - - if(active) - wid.setRecording(false); - else - wid.setRecording(true, { force: "append" }); - }, - ); - } else { start(); } + +Bangle.setUI({ + mode: "custom", + btn: () => { + const rec = WIDGETS["recorder"]; + if(!rec) return; + + const active = rec.isRecording(); + if(active) + rec.setRecording(false); + else + rec.setRecording(true, { force: "append" }); + }, +}); From 24271110bb511c7537c9ad21dadb27f84566a340 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:35:41 +0100 Subject: [PATCH 010/116] bikespeedo: remove second onGPS() call (already done in start()) --- apps/bikespeedo/app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index 0a056f938..9dec29d7d 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -415,8 +415,6 @@ function onGPS(fix) { function setButtons(){ setWatch(_=>load(), BTN1); - -onGPS(lf); } From 308220680328c3fe36895eaa7dcdc17f0a7110bb Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:37:03 +0100 Subject: [PATCH 011/116] bikespeedo: show max values if showMax is true --- apps/bikespeedo/app.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index 9dec29d7d..b1f558647 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -205,7 +205,7 @@ function drawFix(dat) { v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString(); // Primary Units - u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units; + u = (showMax ? 'max ' : '') + (cfg.primSpd?cfg.spd_unit:dat.alt_units); drawPrimary(v,u); @@ -307,16 +307,6 @@ function drawSats(sats) { g.setFont("6x8", 2); g.setFontAlign(1,1); //right, bottom g.drawString(sats,screenW,screenH); - - g.setFontVector(18); - g.setColor(col1); - - if ( cfg.modeA == 1 ) { - if ( showMax ) { - g.setFontAlign(0,1); //centre, bottom - g.drawString('MAX',120,164); - } - } } function onGPS(fix) { From 86c6040a72c6234830b408aed3211a8018a4b767 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:38:02 +0100 Subject: [PATCH 012/116] bikespeedo: setUI once loaded, btn to toggle recording --- apps/bikespeedo/app.js | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index b1f558647..d5f9a3cf6 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -403,11 +403,6 @@ function onGPS(fix) { } } -function setButtons(){ - setWatch(_=>load(), BTN1); -} - - function updateClock() { drawTime(); g.reset(); @@ -508,10 +503,24 @@ function start() { Bangle.setCompassPower(1); if (!calibrateCompass) setInterval(Compass_reading,200); - setButtons(); if (emulator) setInterval(updateClock, 2000); else setInterval(updateClock, 10000); + Bangle.setUI({ + mode: "custom", + btn: () => { + const rec = WIDGETS["recorder"]; + if(!rec) return; + + const active = rec.isRecording(); + if(active) + rec.setRecording(false); + else + rec.setRecording(true, { force: "append" }); + }, + }); + + // can't delay loadWidgets til here - need to have already done so for recorder Bangle.drawWidgets(); } @@ -527,17 +536,3 @@ if (cfg.record && WIDGETS["recorder"]) { } else { start(); } - -Bangle.setUI({ - mode: "custom", - btn: () => { - const rec = WIDGETS["recorder"]; - if(!rec) return; - - const active = rec.isRecording(); - if(active) - rec.setRecording(false); - else - rec.setRecording(true, { force: "append" }); - }, -}); From 96d4ae07d38c4d265cb5ded32db850e0187dcf0f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:38:29 +0100 Subject: [PATCH 013/116] bikespeedo: tap / btn (when not recording) to toggle showMax --- apps/bikespeedo/app.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index d5f9a3cf6..6e22cc7a1 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -492,6 +492,10 @@ function Compass_reading() { hdngCompass = Compass_heading.toFixed(0); } +function nextMode() { + showMax = 1 - showMax; +} + function start() { Bangle.setBarometerPower(1); // needs some time... g.clearRect(0,screenYstart,screenW,screenH); @@ -508,15 +512,18 @@ function start() { Bangle.setUI({ mode: "custom", + touch: nextMode, btn: () => { const rec = WIDGETS["recorder"]; - if(!rec) return; - - const active = rec.isRecording(); - if(active) - rec.setRecording(false); - else - rec.setRecording(true, { force: "append" }); + if(rec){ + const active = rec.isRecording(); + if(active) + rec.setRecording(false); + else + rec.setRecording(true, { force: "append" }); + }else{ + nextMode(); + } }, }); From 4585df251924c7cbbf15f879a29cd8f66b0abf18 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:46:28 +0100 Subject: [PATCH 014/116] bikespeedo: automatically create new recording if initial time --- apps/bikespeedo/app.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index 6e22cc7a1..327f1c754 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -510,6 +510,7 @@ function start() { if (emulator) setInterval(updateClock, 2000); else setInterval(updateClock, 10000); + let createdRecording = false; Bangle.setUI({ mode: "custom", touch: nextMode, @@ -517,10 +518,12 @@ function start() { const rec = WIDGETS["recorder"]; if(rec){ const active = rec.isRecording(); - if(active) + if(active){ + createdRecording = true; rec.setRecording(false); - else - rec.setRecording(true, { force: "append" }); + }else{ + rec.setRecording(true, { force: createdRecording ? "append" : "new" }); + } }else{ nextMode(); } From 74d91c60a1a5e3e13e428d69d9385ea782703650 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 25 May 2023 21:58:33 +0100 Subject: [PATCH 015/116] settings: permit temporarily allowing a BLE connection --- apps/setting/settings.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index ffea3ddbb..a53cf4777 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -363,17 +363,6 @@ function showWhitelistMenu() { }; } - if (settings.whitelist) settings.whitelist.forEach(function(d){ - menu[d.substr(0,17)] = function() { - E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => { - if (v) { - settings.whitelist.splice(settings.whitelist.indexOf(d),1); - updateSettings(); - } - setTimeout(showWhitelistMenu, 50); - }); - } - }); menu[/*LANG*/'Add Device']=function() { E.showAlert(/*LANG*/"Connect device\nto add to\nwhitelist",/*LANG*/"Whitelist").then(function() { NRF.removeAllListeners('connect'); @@ -389,6 +378,30 @@ function showWhitelistMenu() { showWhitelistMenu(); }); }; + + menu[/*LANG*/'Temporarily Add Device']=function() { + E.showAlert(/*LANG*/"Whitelist disabled\nConnect device",/*LANG*/"Whitelist").then(function() { + NRF.removeAllListeners('connect'); + showWhitelistMenu(); + }); + NRF.removeAllListeners('connect'); // this is sufficient to allow any device to connect + NRF.on('connect', function(addr) { + showWhitelistMenu(); + }); + }; + + if (settings.whitelist) settings.whitelist.forEach(function(d){ + menu[d.substr(0,17)] = function() { + E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => { + if (v) { + settings.whitelist.splice(settings.whitelist.indexOf(d),1); + updateSettings(); + } + setTimeout(showWhitelistMenu, 50); + }); + } + }); + E.showMenu(menu); } From 974ab7f5252666758bce7954e8101cafff536bf8 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:15:42 -0500 Subject: [PATCH 016/116] Create app.js --- apps/guitar/app.js | 159 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 apps/guitar/app.js diff --git a/apps/guitar/app.js b/apps/guitar/app.js new file mode 100644 index 000000000..d48d9009a --- /dev/null +++ b/apps/guitar/app.js @@ -0,0 +1,159 @@ +const stringInterval = 24; +const stringLength = 138; +const fretHeight = 35; +const fingerOffset = 17; +const x = 30; +const y = 32; + +//chords +const cc = [ + "C", + "0X", + "33", + "22", + "x", + "11", + "x" +]; + +const dd = [ + "D", + "0X", + "0X", + "x", + "21", + "33", + "22" +]; + +const gg = [ + "G", + "32", + "21", + "x", + "x", + "x", + "33" +]; + +const am = [ + "Am", + "0x", + "x", + "22", + "23", + "11" +]; + +const em = [ + "Em", + "x", + "22", + "23", + "x", + "x", + "x" +]; + +const aa = [ + "A", + "0X", + "x", + "21", + "22", + "23", + "x" +]; + +const ff = [ + "F", + "0X", + "33", + "34", + "22", + "11", + "11" +]; + +var ee = [ + "E", + "x", + "22", + "23", + "11", + "x", + "x" +]; + +var index = 0; +var chords = []; +var menu = { + "" : { + "title" : "Guitar Chords" + }, + "C" : function() { draw(cc); }, + "D" : function() { draw(dd); }, + "E" : function() { draw(ee); }, + "Em" : function() { draw(em); }, + "A" : function() { draw(aa); }, + "Am" : function() { draw(am); }, + "F" : function() { draw(ff); }, + "G" : function() { draw(gg); }, + "About" : function() { + E.showMessage( + "Created By:\nNovaDawn999", { + title:"About" + } + ); + } +}; + + + +function drawBase() { + for (let i = 0; i < 6; i++) { + g.drawLine(x + i * stringInterval, y, x + i * stringInterval, y + stringLength); + g.fillRect(x- 1, y + i * fretHeight - 1, x + stringInterval * 5 + 1, y + i * fretHeight + 1); + } +} + +function drawChord(chord) { + g.drawString(chord[0], g.getWidth() * 0.5 - 3, 18); + for (let i = 0; i < chord.length; i++) { + if (i === 0 || chord[i][0] === "x") { + continue; + } + if (chord[i][0] === "0") { + g.drawString(chord[i][1], x + (i - 1) * stringInterval - 5, y + fretHeight * chord[i][0] + 2, true); + g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 10); + } + else { + g.drawString(chord[i][1], x + (i - 1) * stringInterval -5, y -fingerOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 10); + } + } +} + +function buttonPress() { + setWatch(() => { + buttonPress(); + }, BTN); + E.showMenu(menu); +} + +function draw(chord) { + g.clear(); + drawBase(); + drawChord(chord); +} + + + +function main() { + E.showMenu(menu); + setWatch(() => { + buttonPress(); + }, BTN); +} + +main(); From def056b496b89324b6fbcad58514035f9d985603 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:16:32 -0500 Subject: [PATCH 017/116] Create ChangeLog --- apps/guitar/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/guitar/ChangeLog diff --git a/apps/guitar/ChangeLog b/apps/guitar/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/guitar/ChangeLog @@ -0,0 +1 @@ +0.01: New App! From 6dc94b96cae6b41289b451b27e518ed7496108a3 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:17:16 -0500 Subject: [PATCH 018/116] Create README.md --- apps/guitar/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 apps/guitar/README.md diff --git a/apps/guitar/README.md b/apps/guitar/README.md new file mode 100644 index 000000000..ad4ecca4a --- /dev/null +++ b/apps/guitar/README.md @@ -0,0 +1,12 @@ +# Guitar Chords + +An app that simply describes finger placements on a Guitar to form common chords. + +## Usage + +Select a chord to view. +Use the button to return to the chord selection menu. + +## Creator + +NovaDawn999 From 724937205ec00a4fc76532c078674c357fbf2c05 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:19:19 -0500 Subject: [PATCH 019/116] Create app-icon.js --- apps/guitar/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/guitar/app-icon.js diff --git a/apps/guitar/app-icon.js b/apps/guitar/app-icon.js new file mode 100644 index 000000000..7a5487de6 --- /dev/null +++ b/apps/guitar/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("AJhMMJApEAPwBjJKEF1aJRBcrRKaMFKJQhgoXQYoMQSMpRRiCF1COCjVRcsplaJFFIoLgnWsLkO73RcpJO7TQwDFKoLkgAD1KpEQsfLQuGRQYkF0+pFBcgRg+o0ki6I0we70i4iXQqIFzQqJ1aiFghmCLUKquwWAC2wxcZ3u7BIjUQQGLjvb3apRaAF0RcD3aDRIICAjuUJQ/IWgepEK6Xwl0euQe77hcaFxYiiw16j3uiMAiKYvh6ivDguP1AuERjCQKvwS8eFxQAIFziQKXjxgUFzxgOFzxgMXjhgQFkAwKLkQAFJAsqAPwAyA")) From 52d45afc1cfc61eeea4cfc9a0042eca08737523e Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:20:08 -0500 Subject: [PATCH 020/116] Add files via upload --- apps/guitar/app.png | Bin 0 -> 1798 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/guitar/app.png diff --git a/apps/guitar/app.png b/apps/guitar/app.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff5d79ca34164a3a66ddd3b8a0f8eae81286cd2 GIT binary patch literal 1798 zcmV+h2l@DkP)S(*QIL}#iWQ94#XV3&4bxd^y%O~VVPdERfK z%$jBzz-5Lr^Ue;i!*|a6ob$ZT`vLvvM?bt0%VtiRSgaZMpVy3AXu*BxOZX;v`29yC z$F`i;jFeTZ84sVcJTm}&0f{e6p7mMun5trv@%HA|Ce)mY8S}FeVlozbeI=I82$^$s zQQ(DQld&WBji9fOMvaa*6K8DwJZ7v-uSDQxcq`6Dj~sk5dbF$9WaR0X(dLX<6TUcW zGB%3{sf5Q$`{1p(5EnR8NFI+GSIO}r$P5IadGO0-Es;H`+79y)&YTVYM>BB$|qY|ypNN{F2#6A3e>%!Id-PhSwm zw0U7%J3ovk;)2{F;*01p)rl`nnuESV-ZDRoY41#H5<3;g8()!Nqpy*VnjShH-VU!N zZ;!_lHVp^uacHo`p}8~`r%I#nbe|yaNJOwB30Iv-pe#os_)7w4UxE&M9P)pfi$PvX zzG4;pTr1$HUIogklC=VKrGOR5x|rga2fkh7jpPNj={SDH0$y3`Sr)K19jsZS3;NDH zgc6PT`GLg`*0lc~qyqfJ<8^MP4VBI?KV#*L^ymKe{bZ;T=&cWO6Ex6gZ6>Q1_ zo3`nn^7F4-z#miZCZDw+MA@lC!@zq&x@D&-N z=lTF%w)iu1e#l+q#ZJ!mK)VS$dyTZig26SZc%wcO$J{w+ZOqe&w-tcD?E%|%8)(ZI z$=X~$wptBr-EH8R)uU*+gM6s6Q}qX6#rt}9DviNYJxaVX8UB?i*jk;6rut0Kouk`C zc3g;G`w;wnA6T9_l8t)@(DJ4aLr3}X-RBq^Ga1#@tHHVqJ)E-hio6xP{xR783D{)yWkbFn*S+P#36DR@MK6zK z!kqDZclsz6Xpivhnm}H%Jft!q!Ith+wGzrkHWS=ELcS~(!45N8q?gMB<-xz^>b8-c z7xFhg0dE}!YqkaO#$f}eKIx~(Cr1P`>GfcmXOCm_^COgeu4une@vzP__^&WCReO)~12=S$a!UyKmJ!fAgSDzT+6!y5E%zs`4PsxID*rj)HBg&9YEW<5o|3$R|UjE zRjW|`iy1-PPTnKnHyOsnr}8j)sOmgopO19=q(+QoA95vC?X#<*MD!#YyxQN;C{~CZbY8+;s*>7uq2sgXi?!?-6kb zyqNQ#%7g@4x>I{6d8;m3_5Tfc5xohA8R3;FxGl$i*9al=&)-G}v6B#1t5E-){7t?i zfOBDR zEyrQGRY{oT(xODN8&_E+WNp5`L*(bt+!Ny+VnGfv%M}C00@**6X_} z1pn<4BY#)7u?%&kY%EKJ5AG!#77VUhiLay*VkfyK3cf1ZvX%Q literal 0 HcmV?d00001 From cfa6321f71030e0a11216fe59be5198899109924 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:21:13 -0500 Subject: [PATCH 021/116] Create metadata.json --- apps/guitar/metadata.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/guitar/metadata.json diff --git a/apps/guitar/metadata.json b/apps/guitar/metadata.json new file mode 100644 index 000000000..50c55bece --- /dev/null +++ b/apps/guitar/metadata.json @@ -0,0 +1,14 @@ +{ "id": "Guitar", + "name": "Guitar Chords", + "shortName":"Guitar", + "version":"0.01", + "description": "Wrist mounted guitar chords", + "icon": "app.png", + "tags": "guitar, chords", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"guitar.app.js","url":"app.js"}, + {"name":"guitar.img","url":"app-icon.js","evaluate":true} + ] +} From a25c98d3ad4f351474b9775c45bec57a10300d9c Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:25:09 -0500 Subject: [PATCH 022/116] Update metadata.json --- apps/guitar/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/guitar/metadata.json b/apps/guitar/metadata.json index 50c55bece..8f98ce44e 100644 --- a/apps/guitar/metadata.json +++ b/apps/guitar/metadata.json @@ -1,4 +1,4 @@ -{ "id": "Guitar", +{ "id": "guitar", "name": "Guitar Chords", "shortName":"Guitar", "version":"0.01", From 6f63528a612c7814b16f59c896ea8f560727b166 Mon Sep 17 00:00:00 2001 From: NovaDawn999 <106365882+NovaDawn999@users.noreply.github.com> Date: Fri, 26 May 2023 00:26:21 -0500 Subject: [PATCH 023/116] Update app-icon.js --- apps/guitar/app-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/guitar/app-icon.js b/apps/guitar/app-icon.js index 7a5487de6..490541b44 100644 --- a/apps/guitar/app-icon.js +++ b/apps/guitar/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("AJhMMJApEAPwBjJKEF1aJRBcrRKaMFKJQhgoXQYoMQSMpRRiCF1COCjVRcsplaJFFIoLgnWsLkO73RcpJO7TQwDFKoLkgAD1KpEQsfLQuGRQYkF0+pFBcgRg+o0ki6I0we70i4iXQqIFzQqJ1aiFghmCLUKquwWAC2wxcZ3u7BIjUQQGLjvb3apRaAF0RcD3aDRIICAjuUJQ/IWgepEK6Xwl0euQe77hcaFxYiiw16j3uiMAiKYvh6ivDguP1AuERjCQKvwS8eFxQAIFziQKXjxgUFzxgOFzxgMXjhgQFkAwKLkQAFJAsqAPwAyA")) +require("heatshrink").decompress(atob("mEwwkCkQA/AGMkoQXVptEFytEogwUCoIYBLqlUGIIXTopHVknUoXULylNpouUIoKmUUi0hoMUailEiMSCR/d7pdECx8tC4IYBolULqAWC7qLSFwfdiKMRC4dEoK6RFwYWBppdW7vSLiPd6gXPConVgIWCYYYtM9vdosUqgXOFwndilBqoGDLh/eqtEioXR9xHCoIXDO5SKEpvU6kVppeQ73kqgwB7wuNOwosEXqSlB9xFNR49RpwXV6pICIxhIF73ePAIXTAAgXOJApePGBQXPGA4XPGAxeOGBAWQDAouRDAgWUAH4AZ")) From 201cbdd84382e498e5942bd4e65109725016d352 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 26 May 2023 17:45:07 +0100 Subject: [PATCH 024/116] bikespeedo: version bump --- apps/bikespeedo/ChangeLog | 1 + apps/bikespeedo/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/bikespeedo/ChangeLog b/apps/bikespeedo/ChangeLog index b47f8cdc3..52459d312 100644 --- a/apps/bikespeedo/ChangeLog +++ b/apps/bikespeedo/ChangeLog @@ -2,3 +2,4 @@ 0.02: Barometer altitude adjustment setting 0.03: Use default Bangle formatter for booleans 0.04: Add options for units in locale and recording GPS +0.05: Allow toggling of "max" values (screen tap) and recording (button press) diff --git a/apps/bikespeedo/metadata.json b/apps/bikespeedo/metadata.json index ea74db9a1..87c0ed542 100644 --- a/apps/bikespeedo/metadata.json +++ b/apps/bikespeedo/metadata.json @@ -2,7 +2,7 @@ "id": "bikespeedo", "name": "Bike Speedometer (beta)", "shortName": "Bike Speedometer", - "version": "0.04", + "version": "0.05", "description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources", "icon": "app.png", "screenshots": [{"url":"Screenshot.png"}], From b69f2224b858b917bc4c6e9f364e687f20b7013e Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 26 May 2023 21:31:59 +0100 Subject: [PATCH 025/116] recorder: bump version --- apps/recorder/ChangeLog | 3 ++- apps/recorder/metadata.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 874e4699c..9a696a8c2 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -29,4 +29,5 @@ 0.23: Add graphing for HRM, fix some other graphs Altitude graphing now uses barometer altitude if it exists plotTrack in widget allows track to be drawn in the background (doesn't block execution) -0.24: Can now specify `setRecording(true, {force:...` to not show a menu \ No newline at end of file +0.24: Can now specify `setRecording(true, {force:...` to not show a menu +0.25: Widget now has `isRecording()` for retrieving recording status. diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 99f1539c6..00c1c965e 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.24", + "version": "0.25", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", From 90276239cbbe8c8fa5bbc5dd13c761c8ca31210d Mon Sep 17 00:00:00 2001 From: pancake Date: Sun, 28 May 2023 20:37:15 +0200 Subject: [PATCH 026/116] Fix autogenerated hiragana bitmaps and speedup next/prev kanas --- apps/kanawatch/ChangeLog | 1 + apps/kanawatch/README.md | 2 +- apps/kanawatch/app.js | 78 ++++++++++++++++++++---------------- apps/kanawatch/metadata.json | 2 +- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/apps/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog index b2d2bab86..9144364d9 100644 --- a/apps/kanawatch/ChangeLog +++ b/apps/kanawatch/ChangeLog @@ -5,3 +5,4 @@ 0.05: Tell clock widgets to hide 0.06: Fix exception when showing missing hiragana 'WO' 0.07: Fix regression in bitmap selection on some code paths +0.08: Speedup next/prev and fix autogenerated hiragana bitmaps diff --git a/apps/kanawatch/README.md b/apps/kanawatch/README.md index e213949dc..87a9750b1 100644 --- a/apps/kanawatch/README.md +++ b/apps/kanawatch/README.md @@ -12,7 +12,7 @@ cards for learning. ## Author -Written by pancake in 2022, powered by insomnia +Written by pancake in 2022, maintained during 2023 and powered by insomnia ## Screenshots diff --git a/apps/kanawatch/app.js b/apps/kanawatch/app.js index 264058230..793104def 100644 --- a/apps/kanawatch/app.js +++ b/apps/kanawatch/app.js @@ -58,62 +58,76 @@ katakana['RE'] = image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4EC katakana['RO'] = image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="); katakana['YU'] = image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"); katakana['YO'] = image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"); + +// hiragana + hiragana['A'] = image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"); hiragana['I'] = image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"); hiragana['U'] = image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"); hiragana['E'] = image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"); hiragana['O'] = image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="); + hiragana['KA'] = image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="); hiragana['KI'] = image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="); hiragana['KU'] = image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"); hiragana['KE'] = image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="); hiragana['KO'] = image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="); + hiragana['SA'] = image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="); hiragana['SI'] = image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="); hiragana['SU'] = image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"); hiragana['SE'] = image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"); hiragana['SO'] = image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="); + hiragana['TA'] = image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="); hiragana['TI'] = image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="); hiragana['TU'] = image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="); hiragana['TE'] = image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"); hiragana['TO'] = image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="); -hiragana['NA'] = image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"); -hiragana['NI'] = image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="); -hiragana['NU'] = image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="); -hiragana['NE'] = image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="); -hiragana['NO'] = image(54, 50, "h4GFn+AAocB/0IAwcH/F//4AB+Ef8IFC//A/+PAwcD/0fAoX8h/wDQk/4ITDAgMDAwcH/hGC/EAj/wIwXggF/4AGB/+AJIIFBGQJJCDQoWBDQf/wZlBDQIWBh41Dx5kE/0/Mgn4IgIGD8f8MgYaBL4IaEPQJrD/6RCGoRkCKAR/BKAgaBKAoaFNYoWCKIIaC8BKCDQWAIYQaCgJCCDQRyDDQRXDEoOBK4ahBW4K+CAgKcBDgLcBMwIwC/1/4JHBCYP5CoQwC4aND/atBRofDAgPgdQaSBHgX4hxXBHQXAhAOBAwKXCAAJlBbIIAH"); -hiragana['HA'] = image(50, 50, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAA4A=="); -hiragana['HI'] = image(59, 50, "gP/AAOAA4U/AwPwAwUHAwP+CwYVC4AGCj4GB/AGCgYOCCod/AwPgGokH/g8GHQY8CHQYVCHQg8CwEfCAYEBgYQDAgV/JYYEBh5LDj/4GoJKEGoJLCAwP4JYZ9C/BLCNwSGDQgSGDOoaGDAwg6BEYQHDh//EomDAIP+ToaQBEIIvCKoJyCJgPH/yDCEIIVB4BNBMwIgB+CZCn/n4f+h5jBAQMw/+BOgKyCCoN/PIICBS4I0BCoQJBJQJqCBIP5NQfgD4KACn5tDGQSDEwADBTIJaBGQKZEDISvCToR8BeAQDBAQLbCb4RSCAAcHcQYACvwGFg45BAAj/DAAw="); -hiragana['HU'] = image(55, 50, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQAGA="); -hiragana['HE'] = image(55, 50, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIFEAAg="); -hiragana['HO'] = image(51, 50, "AAN+AokP+AFDgf+Bgl/4ASE/ASVv//AAX8h4FD/+BAonwn4FD/0HBgnAAogoBgP/HAk/8AFDg5LEgASM/gSFwADBFQIAC8E4Iof+/5FE5/wAof5/0fAwc/8YFD8f8PAYEB54MDJ4SRDJ4KRDj/gNYaoCLAYWBLAYWCLAQWCDYJvDgYSCCwV/NYQWBGQc/+AyDg4yBj4MBgYSBAQP4OwPwbIglBQAgpBBgZiBBgYYBBgY1CU4S0DFoIRCAAo="); -hiragana['MA'] = image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"); -hiragana['MI'] = image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="); -hiragana['MU'] = image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"); -hiragana['ME'] = image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"); -hiragana['MO'] = image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"); -hiragana['YA'] = image(54, 50, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECAA4"); -hiragana['YU'] = image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"); -hiragana['YO'] = image(55, 50, "AAMHAwsP+AGEn/gAwl/4AFDgP/BgkD/whF/AGEj4oFEIsA/+AEIgoFg/8EIooFJQ3/JRcHJSgoGJQxEEg//FIkfAws/Cgv/AwUGJQX/HwMP8AoB74GBj/gh/+IoU/4BzBBQJBCJQIKBNQRzBv+AWoIIDJAP4SoMBIgIkBOYMDHoKTBAIIRBXgQBBB4IfBEIQYBFALgCCwMP/iVCJAXwJ4QfDcAX/4JRBSoRvBEIZ2DcAQGCFQIhBPoIYBcAQGBDAJqBCgQ6Bg7rIAAY="); -hiragana['RA'] = image(48, 50, "gEP4AFDj//wAFE/gFE/4TCn4FBBgQFCBgQRC//gBgN/BYUP/EBAog3BGIIFCgH/BAIFCh4FEgQFEBoXwAqsfAoIuBAoROBEwIFBIwP+AoPnLIWALwZfBNQf/+AFE/AFBEIM/AoR6Bh/8OoIzBg4FBRgQFCL4UD/wlBAoikCAoM/W4QFBj5dCAoMGAohpDg4FEHYJ1EAog5DDgJWCb4Y/Cg7RDaARFCAoZFBAobiEeoruCAoQtCAoI+DAAgA="); -hiragana['RI'] = image(40, 49, "ngEDn/AAg9/4Ef/AEBwF//4EBwP//4HBw4EB4F/x4EB8F/z4EB+H/n4EDAQIjBCwUPAgUAAgX+gEH/n//gEDHIMDAg3wAgP+AgvgAhBeBAhmAAiJ3BAhf8AgRUBAhBXBAAJtBAgSgCVgRcBAAJXCEwIEDj5SCBoJDCBAKSBBASSBXwKICAgQmCAgIcCv4SCAgI0DeAY="); -hiragana['RU'] = image(51, 50, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoYAEA="); -hiragana['RE'] = image(56, 50, "gEf8AGF+AGigP/wAGDg//GYQGBh//C4M/AYICB/AGDv///gGC+P/AwQKB+YGB/wNC+//w4GDBYMDAwn4AwQ3BFQIGF8AGF4AGFgAGEAYMDHwIGBAYIGDn5XBAwhlBAwd/Axh6CAwSPBAwMHAxEDAwqdBAwidDAw5IBOoQGDU4QGDUAIGE//fAwufCgrmCh4iCAwk4nwGE/EcAwbSBjAGFegReCUgIGJOYIUEQIYGCIYOAAwPgAwIAIA="); -hiragana['RO'] = image(50, 50, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAgAD"); -hiragana['WA'] = image(51, 50, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/ABggAEA="); -hiragana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); // XXX there's no WO in hiragana, so we fill it with a copy of the katakana char -hiragana['N'] = image(54, 50, "AAVgAYUP8EHwAGCv/Av4RD/8D/wFCgf8g/8DQf4j/4AwU/8E/+AaDwF//4VBgIfB/4GCD4MPAwcf+YFB/4jBn4FC/4jBAof/4AYC//n/+DBYeD/wZC/f/FgIrCGIQsCKYU/444CKYP/z4xCvxOBv+/8EBQQP4B4KFCCoJeCNIYPBQgQKBj53CAYSbBCYQDBHgJbCTYUDOQZHBM4QTBTYX/GQQxBP4Y8BDQRGBTYY4Eh5MDHgZTDAojdEbAYGEHgIGEv7/DHgIhFfAh1EEIg8GEIg8GTYYhDHhYAF"); + + +hiragana['NA'] = image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"); +hiragana['NI'] = image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="); +hiragana['NU'] = image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"); +hiragana['NE'] = image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"); +hiragana['NO'] = image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"); + +hiragana['HA'] = image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"); +hiragana['HI'] = image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="); +hiragana['HU'] = image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="); +hiragana['HE'] = image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="); +hiragana['HO'] = image(53, 49, "h4GFv4FEg/4kAGDn/D/4ACwP+j4FC/kf+IMD8H/w4GDEAM/AoQEB4IMD4f+g4FCEoPwGIXggH/wEAgP/IIP8KQX4B4PAKQXAgP+AoMDAYMPEAQkC/+DEIIkBEAJVD/8/8IFD/P/h4GD5/wv5IDv+DBgfz/gTEz/gCYf4KIIABGgRRBLIZVDNIJVDNIRVDNIRlBNIZlCKwIDC+EDGYJpCwClCNIQMCCYIwBBgX8GAIBBJwRIBPofwJAIeBLwKCBBwIiCx/4H4IVCv/BFYIFB/f+KYIMCx6RD94YBwLfDwYTBGYV8LgJICgI5CBgUCgaGBLYQACAwLVBgA"); + +hiragana['MA'] = image(50, 49, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAAIA=="); +hiragana['MI'] = image(58, 49, "gP/AAOAA4V/AwPgAwUfAwP4AwUHAwP+DjAABgYcDDwYcDDwQcDDwQcFg/8gAXDDgMAn4XDv/Ah4XDj/wGgkPDgpQBDghPB+AcDMoXjDgQGCNwZsCNwYGEDgM/AwYcBPQQAC/kP/4IEw//MgIYC+f/wZHBCAP8//AGwMDEgKGBRAQVBz/4NYI2C44sBNYMP/PxFQI9BAQMY/+BFQKvCOoIsBEYKSCFQU/SQP8WYQCCGYIqCEwI0BFQQmBMgIDBJwOAfgXAAYItBRAJVCKIIVBAYN/FQIYBAYN/FoIrBTQSzCdgRfCAAg0BAAkfbwQACgY4BAAgGDA"); +hiragana['MU'] = image(55, 49, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQ"); +hiragana['ME'] = image(55, 49, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIDC"); +hiragana['MO'] = image(50, 49, "AAN+Aokf8AFDh/4AocD/wSE/+AAod/4AeE+AFDg/8CAf/AAX8j4FD/8HAonBAonwDBY3OKwkBKxc/N5M/GwcHh42D3/DAofn/AFD/P+DAf+v/PBgeP+YFD8f+NAuAG4axBU4ZaCKAUBOAJQDOYIYE+AYEVYKFCDAaICDASICDAsPDAQxBgYYBj4rBAoOAYQPwPQPgE4JYDRQo6BAoglBPoQ0CAogMCAoYvBIwQA="); + +hiragana['YA'] = image(53, 49, "AAVgAYUf4EPAoUB/8B/gGCg/4j/wAwU/4F/4ATDgf/BgUP/EPDQYRBn///wTBAQP//4OBCYMfAwP4CYPPAoP/8AnBAAeAh4FD/gMD/n/+ALD8H/z4EB/v/wf+CIUH/kP+4+CLoN/CYJhBCYmAgfwCYP7CYMeIwOcOoYiBBAOAPYXggZuCIwIrCTgQrCCYIMBFYP8gYZBC4Mf8B3CTQIPBQgYwBg4MDGAKYBGITABBgZnCL4QTCj5EFAAbUBAwgTBAoYTGYAITFcwQTPfQYTCTAITYMAQTDVgUAA="); +hiragana['YU'] = image(51, 49, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/AAoY"); +hiragana['YO'] = image(49, 49, "AAMP/AFDg/8AocD/wFDgP/DAn/wAFDv/AAoc/8AFDj/wGCH/AAIwDAAImCAoQmCv4FBEwU/AoImCj4FBEwUPAoJXCMO4wEM4IWDI4IwCKYQwCL4oFCDAQFDCQIXCNgQFDEoMP/iSC+EHEIJ5CAoSSCwYaBEwXhFoMf8Y4BEAJnBCYN/+Ef/AuBz41CLoPPUQd/4YFDj/AAocD4AuBPIXgDQJ/En6REA="); + +hiragana['RA'] = image(47, 49, "gEP4AFDn//Aod///wAoX///+AgMDAoP/DIMHAoX4AowjC//gh/4gIXCj4mBj4wBn/gEoP8GYI/CvAzBwAFBkAaBIgYTCAAUHGARcCJ4YrBFAJcD4AZDFAI/CFAMPJYQOBK4XwLgZdBJwIFDMIQFCQod/+AIBOIXzO4nnRIQRB55dDDYJdDHgQEBIgM/OgUD/0+Nof8jBtDOYk/OYgyDYgQhCPwLOCFoQ4DMwIcCPYSBCAATkECwKBDCwIVCFoQFCIgSNCHASNBGIQA=="); +hiragana['RI'] = image(39, 49, "ngEDv+AAgX/AYUD/wGB4EH/EH//wh/wn4EBj/h/4EBn/HAgV/z4EB+P+v4EB8YCB4F/8//E4N/54VBFgIWB4AEB346BgP/v/8AgP+//4IQP9//ggBABC4UPAgJRBj4qCgBKBC4IwBF4QrBDgQrB/5vBgYcDEwIcCEwI5BEwP3EIU/94hCv/fEIImBn4+BRYKWCg/8EwSLBTQU/CwScCUYSoDj4zCBoIzCHoIuDKARjBJYJUCQAR7DQAQbDEASABbgU/BATqE"); +hiragana['RU'] = image(51, 49, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoQ"); +hiragana['RE'] = image(55, 49, "gEf8AGEn4GFv/AAwn/wAFDgP/BgkD/wGEg/8DoIkCh/4gf/+A2C+EPAwV///gAQIGB///4ICB+AuB/+PAQPgg4DBn4GE/wSB//AEoIABwABBj4FB/hODA4PwJwYgB4BOCHwROCNoQDBJwJtCLoM/PwJdBPYN/AQMPEoQvDDQMBBIV/DwMDF4QhCg4QBEIIlBh4QBLIIlBWoRRBWol/F4eAIYIlBMwR7BEoQQBUIYvCNgIlBF4SBBEoLsBHgI2DSwP9GwaWB+ZmEj/HGwIvCj+PFgKWBjk+RgSWB/E4Lgn4sBcCIII+CGwTjDWoZFBSYYRBYYgDBYYa5CLgIGBAAI"); +hiragana['RO'] = image(50, 49, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAQA="); + +hiragana['WA'] = image(54, 49, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECgA="); +hiragana['WO'] = image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"); // XXX there's no WO in hiragana, so we fill it with a copy of the katakana char +hiragana['N'] = image(54, 49, "AAMHAwsf8AGE/+AAocD/wTF+AGEv/ACZUP/ATKgP/CYv8Awk/IQgTBIQkHCYxCFCYxWTIQxWGFAhCBAwkPAwJCE/5KDCYQiBhhCBAwJlBn+Aj/+/49BDoP/8IDBgf8IQIDBKgUf/EPLAJUBv/gn/AFgKZCAIMHCIP4DQSXBAIIaC/+BCIIaBYwKZCLwIuBCYLRCFwIKBEYX/CYUfEYP4TIRACCYQ+BwZUBDwIYBOgITCRAQVCEIP//0BYISjB+CtDUYRNBAwQ5Bg7gDBQIA="); /// ///////////////////////////////////////// let kana = katakana.KA; let scroll = 0; - +const keys = Object.keys(katakana).sort(); let hiramode = false; let curkana = 'KA'; + function next () { let found = false; - for (const k of Object.keys(katakana).sort()) { + for (const k of keys) { if (found) { kana = hiramode ? hiragana[k] : katakana[k]; curkana = k; @@ -129,7 +143,6 @@ function next () { function randKana() { try { - const keys = Object.keys(katakana); const total = keys.length; let index = 0 | (Math.random() * total); curkana = keys[index]; @@ -141,7 +154,7 @@ function randKana() { function prev () { let oldk = ''; let count = 0; - for (const k of Object.keys(katakana).sort()) { + for (const k of keys) { if (curkana === k) { if (count > 0) { curkana = oldk; @@ -155,11 +168,6 @@ function prev () { updateWatch(ohhmm); } -const kanacolors = { - A: [] -}; - - function updateWatch (hhmm) { g.setFontAlign(-1, -1, 0); g.setBgColor(0, 0, 0); diff --git a/apps/kanawatch/metadata.json b/apps/kanawatch/metadata.json index d7a6f8c23..d1fb70b53 100644 --- a/apps/kanawatch/metadata.json +++ b/apps/kanawatch/metadata.json @@ -2,7 +2,7 @@ "id": "kanawatch", "name": "Kanawatch", "shortName": "Kanawatch", - "version": "0.07", + "version": "0.08", "type": "clock", "description": "Learn Hiragana and Katakana", "icon": "app.png", From 77fd3a0c7d8c16b71f53999d4f94054ed153df80 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 10:12:46 +0100 Subject: [PATCH 027/116] Revert "settings: permit temporarily allowing a BLE connection" This reverts commit 74d91c60a1a5e3e13e428d69d9385ea782703650. --- apps/setting/settings.js | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a53cf4777..ffea3ddbb 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -363,6 +363,17 @@ function showWhitelistMenu() { }; } + if (settings.whitelist) settings.whitelist.forEach(function(d){ + menu[d.substr(0,17)] = function() { + E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => { + if (v) { + settings.whitelist.splice(settings.whitelist.indexOf(d),1); + updateSettings(); + } + setTimeout(showWhitelistMenu, 50); + }); + } + }); menu[/*LANG*/'Add Device']=function() { E.showAlert(/*LANG*/"Connect device\nto add to\nwhitelist",/*LANG*/"Whitelist").then(function() { NRF.removeAllListeners('connect'); @@ -378,30 +389,6 @@ function showWhitelistMenu() { showWhitelistMenu(); }); }; - - menu[/*LANG*/'Temporarily Add Device']=function() { - E.showAlert(/*LANG*/"Whitelist disabled\nConnect device",/*LANG*/"Whitelist").then(function() { - NRF.removeAllListeners('connect'); - showWhitelistMenu(); - }); - NRF.removeAllListeners('connect'); // this is sufficient to allow any device to connect - NRF.on('connect', function(addr) { - showWhitelistMenu(); - }); - }; - - if (settings.whitelist) settings.whitelist.forEach(function(d){ - menu[d.substr(0,17)] = function() { - E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => { - if (v) { - settings.whitelist.splice(settings.whitelist.indexOf(d),1); - updateSettings(); - } - setTimeout(showWhitelistMenu, 50); - }); - } - }); - E.showMenu(menu); } From 37c0571b972c3621af796b1c576c8dba839cc7bd Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 10:13:41 +0100 Subject: [PATCH 028/116] settings: Make Connectable temporarily bypasses the whitelist --- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 2 +- apps/boot/metadata.json | 2 +- apps/setting/ChangeLog | 3 ++- apps/setting/metadata.json | 2 +- apps/setting/settings.js | 2 ++ 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 82e55fa91..d7405e763 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -66,3 +66,4 @@ If settings.bootDebug is set, output timing for each section of .boot0 0.56: Settings.log = 0,1,2,3 for off,display, log, both 0.57: Handle the whitelist being disabled +0.58: "Make Connectable" temporarily bypasses the whitelist diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 84745b792..a12d41e1b 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -79,7 +79,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`; if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`; if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`; -if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; +if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist && !(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation // ================================================== FIXING OLDER FIRMWARES if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted. diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index c652f6136..0a4e7e9d1 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.57", + "version": "0.58", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 42bac0ea7..d090add58 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -66,4 +66,5 @@ of 'Select Clock' 0.58: On/Off settings items now use checkboxes 0.59: Preserve BLE whitelist even when disabled 0.60: Moved LCD calibration to top of menu, and use 12 taps (not 8) - LCD calibration will now error if the calibration is obviously wrong \ No newline at end of file + LCD calibration will now error if the calibration is obviously wrong +0.61: Permit temporary bypass of the BLE whitelist diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index 20213e81f..b2b19dd6b 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.60", + "version": "0.61", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index ffea3ddbb..d22f28412 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -658,6 +658,7 @@ function showUtilMenu() { function makeConnectable() { try { NRF.wake(); } catch (e) { } Bluetooth.setConsole(1); + NRF.ignoreWhitelist = 1; var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", ""); E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => { if (settings.ble != r) { @@ -665,6 +666,7 @@ function makeConnectable() { updateSettings(); } if (!r) try { NRF.sleep(); } catch (e) { } + delete NRF.ignoreWhitelist; showMainMenu(); }); } From 78995305cbca71e1dab05260261b5b92189e8faa Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 11:16:59 +0100 Subject: [PATCH 029/116] kbmulti: add ability to lowercase after first capital --- apps/kbmulti/ChangeLog | 1 + apps/kbmulti/lib.js | 11 +++++++++++ apps/kbmulti/metadata.json | 2 +- apps/kbmulti/settings.js | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/kbmulti/ChangeLog b/apps/kbmulti/ChangeLog index 4ef8f7bda..defae902b 100644 --- a/apps/kbmulti/ChangeLog +++ b/apps/kbmulti/ChangeLog @@ -3,3 +3,4 @@ 0.03: Use default Bangle formatter for booleans 0.04: Allow moving the cursor 0.05: Switch swipe directions for Caps Lock and moving cursor. +0.06: Add ability to auto-lowercase after a capital letter insertion. diff --git a/apps/kbmulti/lib.js b/apps/kbmulti/lib.js index 9b642a132..01be03feb 100644 --- a/apps/kbmulti/lib.js +++ b/apps/kbmulti/lib.js @@ -96,12 +96,14 @@ exports.input = function(options) { } function onKeyPad(key) { + var retire = 0; deactivateTimeout(charTimeout); // work out which char was pressed if (key==charCurrent) { charIndex = (charIndex+1) % letters[charCurrent].length; text = text.slice(0, -1); } else { + retire = charCurrent !== undefined; newCharacter(key); } var newLetter = letters[charCurrent][charIndex]; @@ -109,15 +111,24 @@ exports.input = function(options) { let post = text.slice(textIndex, text.length); text = pre + (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()) + post; + + if(retire) + retireCurrent(); // set a timeout charTimeout = setTimeout(function() { charTimeout = undefined; newCharacter(); + retireCurrent(); }, settings.charTimeout); displayText(charTimeout); } + function retireCurrent(why) { + if (caps && settings.autoLowercase) + setCaps(); + } + var moveMode = false; function onSwipe(dirLeftRight, dirUpDown) { diff --git a/apps/kbmulti/metadata.json b/apps/kbmulti/metadata.json index 510454f79..0b44b0306 100644 --- a/apps/kbmulti/metadata.json +++ b/apps/kbmulti/metadata.json @@ -1,6 +1,6 @@ { "id": "kbmulti", "name": "Multitap keyboard", - "version":"0.05", + "version":"0.06", "description": "A library for text input via multitap/T9 style keypad", "icon": "app.png", "type":"textinput", diff --git a/apps/kbmulti/settings.js b/apps/kbmulti/settings.js index 96e72b290..0a70bab23 100644 --- a/apps/kbmulti/settings.js +++ b/apps/kbmulti/settings.js @@ -21,6 +21,10 @@ format: v => v, onchange: v => updateSetting("charTimeout", v), }, + /*LANG*/'Lowercase after first uppercase': { + value: !!settings().autoLowercase, + onchange: v => updateSetting("autoLowercase", v) + }, /*LANG*/'Show help button?': { value: !!settings().showHelpBtn, onchange: v => updateSetting("showHelpBtn", v) From 192adef634e4c29f92e5dc2fcfff28b96efa9785 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 11:39:55 +0100 Subject: [PATCH 030/116] kbmulti: remove displayText() false - use undefined --- apps/kbmulti/lib.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/kbmulti/lib.js b/apps/kbmulti/lib.js index 01be03feb..519860b51 100644 --- a/apps/kbmulti/lib.js +++ b/apps/kbmulti/lib.js @@ -89,7 +89,7 @@ exports.input = function(options) { } function newCharacter(ch) { - displayText(); + displayText(false); if (ch && textIndex < text.length) textIndex ++; charCurrent = ch; charIndex = 0; @@ -121,7 +121,7 @@ exports.input = function(options) { newCharacter(); retireCurrent(); }, settings.charTimeout); - displayText(charTimeout); + displayText(true); } function retireCurrent(why) { From f3f223eff52c68cda2198561c3081c7ac2057d27 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 16:20:13 +0100 Subject: [PATCH 031/116] notify: permit 0x0 colour and fallback depending on theme --- apps/notify/ChangeLog | 1 + apps/notify/metadata.json | 2 +- apps/notify/notify_bjs1.js | 2 +- apps/notify/notify_bjs2.js | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index d7b754ff9..f40490488 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -9,3 +9,4 @@ 0.10: Improvements to help notifications work with themes 0.11: Fix regression that caused no notifications and corrupted background 0.12: Add Bangle.js 2 support with Bangle.setLCDOverlay +0.13: Add a default title background for the dark theme diff --git a/apps/notify/metadata.json b/apps/notify/metadata.json index 1cc8f52c1..bab68127c 100644 --- a/apps/notify/metadata.json +++ b/apps/notify/metadata.json @@ -2,7 +2,7 @@ "id": "notify", "name": "Notifications (default)", "shortName": "Notifications", - "version": "0.12", + "version": "0.13", "description": "Provides the default `notify` module used by applications to display notifications on the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", "icon": "notify.png", "type": "notify", diff --git a/apps/notify/notify_bjs1.js b/apps/notify/notify_bjs1.js index fb56e4bbc..4ff8f88e9 100644 --- a/apps/notify/notify_bjs1.js +++ b/apps/notify/notify_bjs1.js @@ -103,7 +103,7 @@ exports.show = function(options) { b -= 2;h -= 2; // title bar if (options.title || options.src) { - g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); + g.setColor("titleBgColor" in options ? options.titleBgColor : g.theme.dark ? 0x1 : 0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.drawString(title.trim().substring(0, 13), x+25,y+3); diff --git a/apps/notify/notify_bjs2.js b/apps/notify/notify_bjs2.js index c202e8c55..456c4e929 100644 --- a/apps/notify/notify_bjs2.js +++ b/apps/notify/notify_bjs2.js @@ -100,7 +100,7 @@ exports.show = function(options) { gg.clearRect(x,y, r,b); // title bar if (options.title || options.src) { - gg.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); + gg.setColor("titleBgColor" in options ? options.titleBgColor : g.theme.dark ? 0x1 : 0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; gg.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); gg.drawString(title.trim().substring(0, 13), x+25,y+3); From 6e48f7074aad8f88a30ac30d08da0ffdbf5dd69f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 21:12:04 +0100 Subject: [PATCH 032/116] kbmulti: default autoLowercase to true --- apps/kbmulti/lib.js | 1 + apps/kbmulti/settings.js | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/kbmulti/lib.js b/apps/kbmulti/lib.js index 519860b51..505132040 100644 --- a/apps/kbmulti/lib.js +++ b/apps/kbmulti/lib.js @@ -9,6 +9,7 @@ exports.input = function(options) { if (settings.firstLaunch===undefined) { settings.firstLaunch = true; } if (settings.charTimeout===undefined) { settings.charTimeout = 500; } if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; } + if (settings.autoLowercase===undefined) { settings.autoLowercase = true; } var fontSize = "6x15"; var Layout = require("Layout"); diff --git a/apps/kbmulti/settings.js b/apps/kbmulti/settings.js index 0a70bab23..08dbb1925 100644 --- a/apps/kbmulti/settings.js +++ b/apps/kbmulti/settings.js @@ -3,6 +3,7 @@ var settings = require('Storage').readJSON("kbmulti.settings.json", true) || {}; if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; } if (settings.charTimeout===undefined) { settings.charTimeout = 500; } + if (settings.autoLowercase===undefined) { settings.autoLowercase = true; } return settings; } From eab0e8c620f7175f83e7be05a9d52a2a40e50938 Mon Sep 17 00:00:00 2001 From: storm64 Date: Mon, 29 May 2023 23:45:27 +0200 Subject: [PATCH 033/116] [sleeplogalarm] Correct hide + replace all `var` --- apps/sleeplogalarm/ChangeLog | 3 ++- apps/sleeplogalarm/README.md | 4 ++-- apps/sleeplogalarm/lib.js | 20 ++++++++++---------- apps/sleeplogalarm/metadata.json | 2 +- apps/sleeplogalarm/settings.js | 20 ++++++++++---------- apps/sleeplogalarm/widget.js | 11 +++++++---- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/apps/sleeplogalarm/ChangeLog b/apps/sleeplogalarm/ChangeLog index 80f8bd7e4..e45667697 100644 --- a/apps/sleeplogalarm/ChangeLog +++ b/apps/sleeplogalarm/ChangeLog @@ -1,4 +1,5 @@ 0.01: New App! 0.02: Add "from Consec."-setting 0.03: Correct how to ignore last triggered alarm -0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings \ No newline at end of file +0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings +0.041: Correct hide function + replace all `var` with `let`. diff --git a/apps/sleeplogalarm/README.md b/apps/sleeplogalarm/README.md index 005377fb1..8da369eb3 100644 --- a/apps/sleeplogalarm/README.md +++ b/apps/sleeplogalarm/README.md @@ -1,6 +1,6 @@ # Sleep Log Alarm -This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. By default the time of the targeting alarm is displayed inside the widget which can be adjusted, too. +This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. The widget is only displayed if an active alarm is detected. The time of the targeting alarm is displayed inside the widget, too. The time or the complete widget can be hidden in the options. _This widget does not detect sleep on its own and can not create alarms. It requires the [sleeplog](/apps/?id=sleeplog) app and any alarm app that uses [sched](/apps/?id=sched) to be installed._ @@ -30,7 +30,7 @@ _This widget does not detect sleep on its own and can not create alarms. It requ - __msg includes__ | include only alarms including this string in msg __""__ / ... - __Widget__ submenu - - __hide__ | completely hide the widget + - __hide always__ | completely hide the widget _on_ / __off__ - __show time__ | show the time of the targeting alarm __on__ / _off_ diff --git a/apps/sleeplogalarm/lib.js b/apps/sleeplogalarm/lib.js index 343e811af..48fecdb5f 100644 --- a/apps/sleeplogalarm/lib.js +++ b/apps/sleeplogalarm/lib.js @@ -1,5 +1,5 @@ // load library -var sched = require("sched"); +let sched = require("sched"); // find next active alarm in range function getNextAlarm(allAlarms, fo, withId) { @@ -10,7 +10,7 @@ function getNextAlarm(allAlarms, fo, withId) { // return next active alarms in range, filter for // active && not timer && not own alarm && // after from && before to && includes msg - var ret = allAlarms.filter( + let ret = allAlarms.filter( a => a.on && !a.timer && a.id !== "sleeplog" && a.t >= fo.from && a.t < fo.to && (!fo.msg || a.msg.includes(fo.msg)) ).map(a => { // add time to alarm @@ -21,7 +21,7 @@ function getNextAlarm(allAlarms, fo, withId) { ).sort((a, b) => a.tTo - b.tTo); // prevent triggering for an already triggered alarm again if available if (fo.lastDate) { - var toLast = fo.lastDate - new Date().valueOf() + 1000; + let toLast = fo.lastDate - new Date().valueOf() + 1000; if (toLast > 0) ret = ret.filter(a => a.tTo > toLast); } // return first entry @@ -59,7 +59,7 @@ exports = { if (typeof (global.sleeplog || {}).trigger !== "object") return; // read settings to calculate alarm range - var settings = exports.getSettings(); + let settings = exports.getSettings(); // set the alarm time this.time = getNextAlarm(sched.getAlarms(), settings.filter).t; @@ -68,7 +68,7 @@ exports = { if (!this.time) return; // set widget width if not hidden - if (!this.hidden) this.width = 8; + if (!settings.hidden) this.width = 8; // insert sleeplogalarm conditions and function sleeplog.trigger.sleeplogalarm = { @@ -87,22 +87,22 @@ exports = { // trigger function trigger: function() { // read settings - var settings = exports.getSettings(); + let settings = exports.getSettings(); // read all alarms - var allAlarms = sched.getAlarms(); + let allAlarms = sched.getAlarms(); // find first active alarm - var alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm); + let alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm); // return if no alarm is found if (!alarm) return; // get now - var now = new Date(); + let now = new Date(); // get date of the alarm - var aDate = new Date(now + alarm.tTo); + let aDate = new Date(now + alarm.tTo); // disable earlier triggered alarm if set if (settings.disableOnAlarm) { diff --git a/apps/sleeplogalarm/metadata.json b/apps/sleeplogalarm/metadata.json index fd85507e6..989579d58 100644 --- a/apps/sleeplogalarm/metadata.json +++ b/apps/sleeplogalarm/metadata.json @@ -2,7 +2,7 @@ "id":"sleeplogalarm", "name":"Sleep Log Alarm", "shortName": "SleepLogAlarm", - "version": "0.04", + "version": "0.041", "description": "Enhance your morning and let your alarms wake you up when you are in light sleep.", "icon": "app.png", "type": "widget", diff --git a/apps/sleeplogalarm/settings.js b/apps/sleeplogalarm/settings.js index 1f3a13272..d797ae6bc 100644 --- a/apps/sleeplogalarm/settings.js +++ b/apps/sleeplogalarm/settings.js @@ -1,6 +1,6 @@ (function(back) { // read settings - var settings = require("sleeplogalarm").getSettings(); + let settings = require("sleeplogalarm").getSettings(); // write change to storage function writeSetting() { @@ -23,7 +23,7 @@ // show widget menu function showFilterMenu() { // set menu - var filterMenu = { + let filterMenu = { "": { title: "Filter Alarm" }, @@ -64,22 +64,22 @@ }) } }; - var menu = E.showMenu(filterMenu); + let menu = E.showMenu(filterMenu); } // show widget menu function showWidMenu() { // define color values and names - var colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"]; - var colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535]; + let colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"]; + let colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535]; // set menu - var widgetMenu = { + let widgetMenu = { "": { title: "Widget Settings" }, /*LANG*/"< Back": () => showMain(9), - /*LANG*/"hide": { + /*LANG*/"hide always": { value: settings.wid.hide, onchange: v => { settings.wid.hide = v; @@ -105,13 +105,13 @@ } } }; - var menu = E.showMenu(widgetMenu); + let menu = E.showMenu(widgetMenu); } // show main menu function showMain(selected) { // set menu - var mainMenu = { + let mainMenu = { "": { title: "Sleep Log Alarm", selected: selected @@ -184,7 +184,7 @@ } } }; - var menu = E.showMenu(mainMenu); + let menu = E.showMenu(mainMenu); } // draw main menu diff --git a/apps/sleeplogalarm/widget.js b/apps/sleeplogalarm/widget.js index e3171751f..9bed913c1 100644 --- a/apps/sleeplogalarm/widget.js +++ b/apps/sleeplogalarm/widget.js @@ -10,10 +10,13 @@ if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enable time: 0, earlier: settings.earlier, draw: function () { - // draw zzz - g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y); - // call function to draw the time of alarm if a alarm is found - if (this.time) this.drawTime(this.time + 1); + // draw if width is set + if (this.width) { + // draw zzz + g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y); + // call function to draw the time of alarm if a alarm is found + if (this.time) this.drawTime(this.time + 1); + } }, drawTime: () => {}, reload: require("sleeplogalarm").widReload From 68a7047a8ce7f5fe2cb4af84441bd6f6e3ef8853 Mon Sep 17 00:00:00 2001 From: storm64 Date: Mon, 29 May 2023 23:52:46 +0200 Subject: [PATCH 034/116] [sleeplogalarm] Second try --- apps/sleeplogalarm/ChangeLog | 2 +- apps/sleeplogalarm/lib.js | 2 +- apps/sleeplogalarm/metadata.json | 2 +- apps/sleeplogalarm/widget.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sleeplogalarm/ChangeLog b/apps/sleeplogalarm/ChangeLog index e45667697..cf79179c4 100644 --- a/apps/sleeplogalarm/ChangeLog +++ b/apps/sleeplogalarm/ChangeLog @@ -2,4 +2,4 @@ 0.02: Add "from Consec."-setting 0.03: Correct how to ignore last triggered alarm 0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings -0.041: Correct hide function + replace all `var` with `let`. +0.042: Correct hide function + replace all `var` with `let`. diff --git a/apps/sleeplogalarm/lib.js b/apps/sleeplogalarm/lib.js index 48fecdb5f..609a45fde 100644 --- a/apps/sleeplogalarm/lib.js +++ b/apps/sleeplogalarm/lib.js @@ -68,7 +68,7 @@ exports = { if (!this.time) return; // set widget width if not hidden - if (!settings.hidden) this.width = 8; + if (!settings.wid.hide) this.width = 8; // insert sleeplogalarm conditions and function sleeplog.trigger.sleeplogalarm = { diff --git a/apps/sleeplogalarm/metadata.json b/apps/sleeplogalarm/metadata.json index 989579d58..3079435b0 100644 --- a/apps/sleeplogalarm/metadata.json +++ b/apps/sleeplogalarm/metadata.json @@ -2,7 +2,7 @@ "id":"sleeplogalarm", "name":"Sleep Log Alarm", "shortName": "SleepLogAlarm", - "version": "0.041", + "version": "0.042", "description": "Enhance your morning and let your alarms wake you up when you are in light sleep.", "icon": "app.png", "type": "widget", diff --git a/apps/sleeplogalarm/widget.js b/apps/sleeplogalarm/widget.js index 9bed913c1..a62782604 100644 --- a/apps/sleeplogalarm/widget.js +++ b/apps/sleeplogalarm/widget.js @@ -1,7 +1,7 @@ // check if enabled in settings if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enabled: true}).enabled) { // read settings - settings = require("sleeplogalarm").getSettings(); // is undefined if used with var + let settings = require("sleeplogalarm").getSettings(); // insert neccessary settings into widget WIDGETS.sleeplogalarm = { From 6529533aee8cf9130b31f8d3bc761d2a33253f59 Mon Sep 17 00:00:00 2001 From: storm64 Date: Tue, 30 May 2023 00:01:08 +0200 Subject: [PATCH 035/116] [sleeplogalarm] Correct version after testing --- apps/sleeplogalarm/ChangeLog | 2 +- apps/sleeplogalarm/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sleeplogalarm/ChangeLog b/apps/sleeplogalarm/ChangeLog index cf79179c4..286221777 100644 --- a/apps/sleeplogalarm/ChangeLog +++ b/apps/sleeplogalarm/ChangeLog @@ -2,4 +2,4 @@ 0.02: Add "from Consec."-setting 0.03: Correct how to ignore last triggered alarm 0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings -0.042: Correct hide function + replace all `var` with `let`. +0.05: Correct hide function + replace all `var` with `let`. diff --git a/apps/sleeplogalarm/metadata.json b/apps/sleeplogalarm/metadata.json index 3079435b0..30d3dcda7 100644 --- a/apps/sleeplogalarm/metadata.json +++ b/apps/sleeplogalarm/metadata.json @@ -2,7 +2,7 @@ "id":"sleeplogalarm", "name":"Sleep Log Alarm", "shortName": "SleepLogAlarm", - "version": "0.042", + "version": "0.05", "description": "Enhance your morning and let your alarms wake you up when you are in light sleep.", "icon": "app.png", "type": "widget", From 2b66f370451b4bd0ddf6e462d71891fc4e910ee5 Mon Sep 17 00:00:00 2001 From: pancake Date: Tue, 30 May 2023 03:08:02 +0200 Subject: [PATCH 036/116] kanawatch: Optimize loading+rendering and add transition animations --- apps/kanawatch/ChangeLog | 2 + apps/kanawatch/app.js | 450 +++++++++++++++++++----------- apps/kanawatch/metadata.json | 5 +- apps/kanawatch/screenshot.old.png | Bin 0 -> 2992 bytes apps/kanawatch/screenshot.png | Bin 2992 -> 2653 bytes apps/kanawatch/screenshot2.png | Bin 0 -> 2689 bytes 6 files changed, 297 insertions(+), 160 deletions(-) create mode 100644 apps/kanawatch/screenshot.old.png create mode 100644 apps/kanawatch/screenshot2.png diff --git a/apps/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog index 9144364d9..1521bed73 100644 --- a/apps/kanawatch/ChangeLog +++ b/apps/kanawatch/ChangeLog @@ -6,3 +6,5 @@ 0.06: Fix exception when showing missing hiragana 'WO' 0.07: Fix regression in bitmap selection on some code paths 0.08: Speedup next/prev and fix autogenerated hiragana bitmaps +0.09: Optimize loading and rendering times, introduce transition animations +0.10: Swipe up/down for Hiragana/Katakana, right/left for next/prev letter diff --git a/apps/kanawatch/app.js b/apps/kanawatch/app.js index 793104def..2aa5c2a3c 100644 --- a/apps/kanawatch/app.js +++ b/apps/kanawatch/app.js @@ -3,121 +3,134 @@ const stripe_pos = 40; const stripe2_pos = 110; const h = g.getHeight(); const w = g.getWidth(); +const decompress = require("heatshrink").decompress; -/// ///////////////////////////////////////// -const katakana = {}; -const hiragana = {}; + +function benchStart() { + return { + now : +Date.now(), + diff: function() { + return (0+Date.now()) - this.now; + } + }; +} +const startupTime = benchStart(); function image(x,y,b) { return { bpp:1, width:x,height:y, - buffer:require('heatshrink').decompress(atob(b)) + buffer: decompress(atob(b)), }; } -katakana['A'] = image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA="); -katakana['I'] = image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA="); -katakana['U'] = image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw"); -katakana['E'] = image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA="); -katakana['O'] = image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY="); -katakana['KA'] = image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA"); -katakana['KI'] = image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY="); -katakana['KU'] = image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA="); -katakana['KE'] = image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA"); -katakana['KO'] = image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA="); -katakana['SA'] = image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F"); -katakana['SI'] = image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA"); -katakana['SU'] = image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI"); -katakana['SE'] = image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA="); -katakana['SO'] = image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG"); -katakana['TA'] = image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ"); -katakana['TI'] = image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG"); -katakana['TU'] = image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA=="); -katakana['TE'] = image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo="); -katakana['TO'] = image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA="); -katakana['MA'] = image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA"); -katakana['MI'] = image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA=="); -katakana['MU'] = image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ="); -katakana['ME'] = image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4="); -katakana['MO'] = image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA="); -katakana['NA'] = image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw"); -katakana['NI'] = image(56, 43, "h//AAf4A25+/AH4AuWggA5A="); -katakana['NU'] = image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF"); -katakana['NE'] = image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo="); -katakana['NO'] = image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA="); -katakana['HA'] = image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA=="); -katakana['HI'] = image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA="); -katakana['HU'] = image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA="); -katakana['HE'] = image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA=="); -katakana['HO'] = image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA="); -katakana['N'] = image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA=="); -katakana['WA'] = image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA="); -katakana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); -katakana['RA'] = image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA"); -katakana['RI'] = image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA="); -katakana['RU'] = image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA"); -katakana['RE'] = image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA"); -katakana['RO'] = image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="); -katakana['YU'] = image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"); -katakana['YO'] = image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"); + +const katakana = { +A: image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA="), +I: image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA="), +U: image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw"), +E: image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA="), + O: image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY="), +KA: image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA"), +KI: image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY="), +KU: image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA="), + KE: image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA"), +KO: image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA="), +SA: image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F"), +SI: image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA"), +SU: image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI"), +SE: image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA="), +SO: image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG"), +TA: image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ"), +TI: image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG"), +TU: image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA=="), +TE: image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo="), +TO: image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA="), +MA: image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA"), +MI: image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA=="), +MU: image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ="), +ME: image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4="), +MO: image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA="), +NA: image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw"), +NI: image(56, 43, "h//AAf4A25+/AH4AuWggA5A="), +NU: image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF"), +NE: image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo="), +NO: image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA="), +HA: image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA=="), +HI: image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA="), +HU: image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA="), +HE: image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA=="), +HO: image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA="), +N: image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA=="), +WA: image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA="), +WO: image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"), +RA: image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA"), +RI: image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA="), +RU: image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA"), +RE: image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA"), +RO: image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="), +YU: image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"), +YO: image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"), + +}; +const hiragana = { // hiragana -hiragana['A'] = image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"); -hiragana['I'] = image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"); -hiragana['U'] = image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"); -hiragana['E'] = image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"); -hiragana['O'] = image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="); +A: image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"), +I:image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"), +U: image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"), +E:image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"), +O: image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="), -hiragana['KA'] = image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="); -hiragana['KI'] = image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="); -hiragana['KU'] = image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"); -hiragana['KE'] = image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="); -hiragana['KO'] = image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="); +KA: image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="), +KI: image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="), +KU: image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"), +KE: image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="), +KO: image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="), -hiragana['SA'] = image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="); -hiragana['SI'] = image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="); -hiragana['SU'] = image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"); -hiragana['SE'] = image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"); -hiragana['SO'] = image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="); +SA:image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="), +SI: image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="), +SU: image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"), +SE: image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"), +SO: image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="), -hiragana['TA'] = image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="); -hiragana['TI'] = image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="); -hiragana['TU'] = image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="); -hiragana['TE'] = image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"); -hiragana['TO'] = image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="); +TA: image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="), +TI: image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="), +TU: image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="), +TE: image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"), +TO: image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="), + +NA: image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"), +NI: image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="), +NU: image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"), +NE: image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"), +NO: image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"), + +HA: image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"), +HI: image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="), +HU: image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="), +HE: image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="), +HO: image(53, 49, "h4GFv4FEg/4kAGDn/D/4ACwP+j4FC/kf+IMD8H/w4GDEAM/AoQEB4IMD4f+g4FCEoPwGIXggH/wEAgP/IIP8KQX4B4PAKQXAgP+AoMDAYMPEAQkC/+DEIIkBEAJVD/8/8IFD/P/h4GD5/wv5IDv+DBgfz/gTEz/gCYf4KIIABGgRRBLIZVDNIJVDNIRVDNIRlBNIZlCKwIDC+EDGYJpCwClCNIQMCCYIwBBgX8GAIBBJwRIBPofwJAIeBLwKCBBwIiCx/4H4IVCv/BFYIFB/f+KYIMCx6RD94YBwLfDwYTBGYV8LgJICgI5CBgUCgaGBLYQACAwLVBgA"), + +MA: image(50, 49, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAAIA=="), +MI: image(58, 49, "gP/AAOAA4V/AwPgAwUfAwP4AwUHAwP+DjAABgYcDDwYcDDwQcDDwQcFg/8gAXDDgMAn4XDv/Ah4XDj/wGgkPDgpQBDghPB+AcDMoXjDgQGCNwZsCNwYGEDgM/AwYcBPQQAC/kP/4IEw//MgIYC+f/wZHBCAP8//AGwMDEgKGBRAQVBz/4NYI2C44sBNYMP/PxFQI9BAQMY/+BFQKvCOoIsBEYKSCFQU/SQP8WYQCCGYIqCEwI0BFQQmBMgIDBJwOAfgXAAYItBRAJVCKIIVBAYN/FQIYBAYN/FoIrBTQSzCdgRfCAAg0BAAkfbwQACgY4BAAgGDA"), +MU: image(55, 49, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQ"), +ME: image(55, 49, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIDC"), +MO: image(50, 49, "AAN+Aokf8AFDh/4AocD/wSE/+AAod/4AeE+AFDg/8CAf/AAX8j4FD/8HAonBAonwDBY3OKwkBKxc/N5M/GwcHh42D3/DAofn/AFD/P+DAf+v/PBgeP+YFD8f+NAuAG4axBU4ZaCKAUBOAJQDOYIYE+AYEVYKFCDAaICDASICDAsPDAQxBgYYBj4rBAoOAYQPwPQPgE4JYDRQo6BAoglBPoQ0CAogMCAoYvBIwQA="), -hiragana['NA'] = image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"); -hiragana['NI'] = image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="); -hiragana['NU'] = image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"); -hiragana['NE'] = image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"); -hiragana['NO'] = image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"); +YA: image(53, 49, "AAVgAYUf4EPAoUB/8B/gGCg/4j/wAwU/4F/4ATDgf/BgUP/EPDQYRBn///wTBAQP//4OBCYMfAwP4CYPPAoP/8AnBAAeAh4FD/gMD/n/+ALD8H/z4EB/v/wf+CIUH/kP+4+CLoN/CYJhBCYmAgfwCYP7CYMeIwOcOoYiBBAOAPYXggZuCIwIrCTgQrCCYIMBFYP8gYZBC4Mf8B3CTQIPBQgYwBg4MDGAKYBGITABBgZnCL4QTCj5EFAAbUBAwgTBAoYTGYAITFcwQTPfQYTCTAITYMAQTDVgUAA="), +YU: image(51, 49, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/AAoY"), +YO: image(49, 49, "AAMP/AFDg/8AocD/wFDgP/DAn/wAFDv/AAoc/8AFDj/wGCH/AAIwDAAImCAoQmCv4FBEwU/AoImCj4FBEwUPAoJXCMO4wEM4IWDI4IwCKYQwCL4oFCDAQFDCQIXCNgQFDEoMP/iSC+EHEIJ5CAoSSCwYaBEwXhFoMf8Y4BEAJnBCYN/+Ef/AuBz41CLoPPUQd/4YFDj/AAocD4AuBPIXgDQJ/En6REA="), -hiragana['HA'] = image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"); -hiragana['HI'] = image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="); -hiragana['HU'] = image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="); -hiragana['HE'] = image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="); -hiragana['HO'] = image(53, 49, "h4GFv4FEg/4kAGDn/D/4ACwP+j4FC/kf+IMD8H/w4GDEAM/AoQEB4IMD4f+g4FCEoPwGIXggH/wEAgP/IIP8KQX4B4PAKQXAgP+AoMDAYMPEAQkC/+DEIIkBEAJVD/8/8IFD/P/h4GD5/wv5IDv+DBgfz/gTEz/gCYf4KIIABGgRRBLIZVDNIJVDNIRVDNIRlBNIZlCKwIDC+EDGYJpCwClCNIQMCCYIwBBgX8GAIBBJwRIBPofwJAIeBLwKCBBwIiCx/4H4IVCv/BFYIFB/f+KYIMCx6RD94YBwLfDwYTBGYV8LgJICgI5CBgUCgaGBLYQACAwLVBgA"); +RA: image(47, 49, "gEP4AFDn//Aod///wAoX///+AgMDAoP/DIMHAoX4AowjC//gh/4gIXCj4mBj4wBn/gEoP8GYI/CvAzBwAFBkAaBIgYTCAAUHGARcCJ4YrBFAJcD4AZDFAI/CFAMPJYQOBK4XwLgZdBJwIFDMIQFCQod/+AIBOIXzO4nnRIQRB55dDDYJdDHgQEBIgM/OgUD/0+Nof8jBtDOYk/OYgyDYgQhCPwLOCFoQ4DMwIcCPYSBCAATkECwKBDCwIVCFoQFCIgSNCHASNBGIQA=="), +RI: image(39, 49, "ngEDv+AAgX/AYUD/wGB4EH/EH//wh/wn4EBj/h/4EBn/HAgV/z4EB+P+v4EB8YCB4F/8//E4N/54VBFgIWB4AEB346BgP/v/8AgP+//4IQP9//ggBABC4UPAgJRBj4qCgBKBC4IwBF4QrBDgQrB/5vBgYcDEwIcCEwI5BEwP3EIU/94hCv/fEIImBn4+BRYKWCg/8EwSLBTQU/CwScCUYSoDj4zCBoIzCHoIuDKARjBJYJUCQAR7DQAQbDEASABbgU/BATqE"), +RU: image(51, 49, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoQ"), +RE: image(55, 49, "gEf8AGEn4GFv/AAwn/wAFDgP/BgkD/wGEg/8DoIkCh/4gf/+A2C+EPAwV///gAQIGB///4ICB+AuB/+PAQPgg4DBn4GE/wSB//AEoIABwABBj4FB/hODA4PwJwYgB4BOCHwROCNoQDBJwJtCLoM/PwJdBPYN/AQMPEoQvDDQMBBIV/DwMDF4QhCg4QBEIIlBh4QBLIIlBWoRRBWol/F4eAIYIlBMwR7BEoQQBUIYvCNgIlBF4SBBEoLsBHgI2DSwP9GwaWB+ZmEj/HGwIvCj+PFgKWBjk+RgSWB/E4Lgn4sBcCIII+CGwTjDWoZFBSYYRBYYgDBYYa5CLgIGBAAI"), +RO: image(50, 49, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAQA="), +WA: image(54, 49, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECgA="), +WO: image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"), +N: image(54, 49, "AAMHAwsf8AGE/+AAocD/wTF+AGEv/ACZUP/ATKgP/CYv8Awk/IQgTBIQkHCYxCFCYxWTIQxWGFAhCBAwkPAwJCE/5KDCYQiBhhCBAwJlBn+Aj/+/49BDoP/8IDBgf8IQIDBKgUf/EPLAJUBv/gn/AFgKZCAIMHCIP4DQSXBAIIaC/+BCIIaBYwKZCLwIuBCYLRCFwIKBEYX/CYUfEYP4TIRACCYQ+BwZUBDwIYBOgITCRAQVCEIP//0BYISjB+CtDUYRNBAwQ5Bg7gDBQIA="), -hiragana['MA'] = image(50, 49, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAAIA=="); -hiragana['MI'] = image(58, 49, "gP/AAOAA4V/AwPgAwUfAwP4AwUHAwP+DjAABgYcDDwYcDDwQcDDwQcFg/8gAXDDgMAn4XDv/Ah4XDj/wGgkPDgpQBDghPB+AcDMoXjDgQGCNwZsCNwYGEDgM/AwYcBPQQAC/kP/4IEw//MgIYC+f/wZHBCAP8//AGwMDEgKGBRAQVBz/4NYI2C44sBNYMP/PxFQI9BAQMY/+BFQKvCOoIsBEYKSCFQU/SQP8WYQCCGYIqCEwI0BFQQmBMgIDBJwOAfgXAAYItBRAJVCKIIVBAYN/FQIYBAYN/FoIrBTQSzCdgRfCAAg0BAAkfbwQACgY4BAAgGDA"); -hiragana['MU'] = image(55, 49, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQ"); -hiragana['ME'] = image(55, 49, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIDC"); -hiragana['MO'] = image(50, 49, "AAN+Aokf8AFDh/4AocD/wSE/+AAod/4AeE+AFDg/8CAf/AAX8j4FD/8HAonBAonwDBY3OKwkBKxc/N5M/GwcHh42D3/DAofn/AFD/P+DAf+v/PBgeP+YFD8f+NAuAG4axBU4ZaCKAUBOAJQDOYIYE+AYEVYKFCDAaICDASICDAsPDAQxBgYYBj4rBAoOAYQPwPQPgE4JYDRQo6BAoglBPoQ0CAogMCAoYvBIwQA="); - -hiragana['YA'] = image(53, 49, "AAVgAYUf4EPAoUB/8B/gGCg/4j/wAwU/4F/4ATDgf/BgUP/EPDQYRBn///wTBAQP//4OBCYMfAwP4CYPPAoP/8AnBAAeAh4FD/gMD/n/+ALD8H/z4EB/v/wf+CIUH/kP+4+CLoN/CYJhBCYmAgfwCYP7CYMeIwOcOoYiBBAOAPYXggZuCIwIrCTgQrCCYIMBFYP8gYZBC4Mf8B3CTQIPBQgYwBg4MDGAKYBGITABBgZnCL4QTCj5EFAAbUBAwgTBAoYTGYAITFcwQTPfQYTCTAITYMAQTDVgUAA="); -hiragana['YU'] = image(51, 49, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/AAoY"); -hiragana['YO'] = image(49, 49, "AAMP/AFDg/8AocD/wFDgP/DAn/wAFDv/AAoc/8AFDj/wGCH/AAIwDAAImCAoQmCv4FBEwU/AoImCj4FBEwUPAoJXCMO4wEM4IWDI4IwCKYQwCL4oFCDAQFDCQIXCNgQFDEoMP/iSC+EHEIJ5CAoSSCwYaBEwXhFoMf8Y4BEAJnBCYN/+Ef/AuBz41CLoPPUQd/4YFDj/AAocD4AuBPIXgDQJ/En6REA="); - -hiragana['RA'] = image(47, 49, "gEP4AFDn//Aod///wAoX///+AgMDAoP/DIMHAoX4AowjC//gh/4gIXCj4mBj4wBn/gEoP8GYI/CvAzBwAFBkAaBIgYTCAAUHGARcCJ4YrBFAJcD4AZDFAI/CFAMPJYQOBK4XwLgZdBJwIFDMIQFCQod/+AIBOIXzO4nnRIQRB55dDDYJdDHgQEBIgM/OgUD/0+Nof8jBtDOYk/OYgyDYgQhCPwLOCFoQ4DMwIcCPYSBCAATkECwKBDCwIVCFoQFCIgSNCHASNBGIQA=="); -hiragana['RI'] = image(39, 49, "ngEDv+AAgX/AYUD/wGB4EH/EH//wh/wn4EBj/h/4EBn/HAgV/z4EB+P+v4EB8YCB4F/8//E4N/54VBFgIWB4AEB346BgP/v/8AgP+//4IQP9//ggBABC4UPAgJRBj4qCgBKBC4IwBF4QrBDgQrB/5vBgYcDEwIcCEwI5BEwP3EIU/94hCv/fEIImBn4+BRYKWCg/8EwSLBTQU/CwScCUYSoDj4zCBoIzCHoIuDKARjBJYJUCQAR7DQAQbDEASABbgU/BATqE"); -hiragana['RU'] = image(51, 49, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoQ"); -hiragana['RE'] = image(55, 49, "gEf8AGEn4GFv/AAwn/wAFDgP/BgkD/wGEg/8DoIkCh/4gf/+A2C+EPAwV///gAQIGB///4ICB+AuB/+PAQPgg4DBn4GE/wSB//AEoIABwABBj4FB/hODA4PwJwYgB4BOCHwROCNoQDBJwJtCLoM/PwJdBPYN/AQMPEoQvDDQMBBIV/DwMDF4QhCg4QBEIIlBh4QBLIIlBWoRRBWol/F4eAIYIlBMwR7BEoQQBUIYvCNgIlBF4SBBEoLsBHgI2DSwP9GwaWB+ZmEj/HGwIvCj+PFgKWBjk+RgSWB/E4Lgn4sBcCIII+CGwTjDWoZFBSYYRBYYgDBYYa5CLgIGBAAI"); -hiragana['RO'] = image(50, 49, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAQA="); - -hiragana['WA'] = image(54, 49, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECgA="); -hiragana['WO'] = image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"); // XXX there's no WO in hiragana, so we fill it with a copy of the katakana char -hiragana['N'] = image(54, 49, "AAMHAwsf8AGE/+AAocD/wTF+AGEv/ACZUP/ATKgP/CYv8Awk/IQgTBIQkHCYxCFCYxWTIQxWGFAhCBAwkPAwJCE/5KDCYQiBhhCBAwJlBn+Aj/+/49BDoP/8IDBgf8IQIDBKgUf/EPLAJUBv/gn/AFgKZCAIMHCIP4DQSXBAIIaC/+BCIIaBYwKZCLwIuBCYLRCFwIKBEYX/CYUfEYP4TIRACCYQ+BwZUBDwIYBOgITCRAQVCEIP//0BYISjB+CtDUYRNBAwQ5Bg7gDBQIA="); -/// ///////////////////////////////////////// +}; let kana = katakana.KA; let scroll = 0; @@ -125,68 +138,69 @@ const keys = Object.keys(katakana).sort(); let hiramode = false; let curkana = 'KA'; +console.log("StartupTime: "+startupTime.diff()); + + function next () { - let found = false; - for (const k of keys) { - if (found) { - kana = hiramode ? hiragana[k] : katakana[k]; - curkana = k; - return; - } - if (curkana === k) { - found = true; - } + const off = keys.indexOf(curkana); + if (off !== -1 && off + 1 < keys.length) { + return keys[off + 1]; } - curkana = 'KA'; - updateWatch(ohhmm); + return keys[0]; } function randKana() { try { - const total = keys.length; - let index = 0 | (Math.random() * total); + let index = 0 | (Math.random() * keys.length); curkana = keys[index]; } catch (e) { randKana(); } } +// const bench = benchStart(); +// console.log("-->" + bench.diff()); function prev () { - let oldk = ''; - let count = 0; - for (const k of keys) { - if (curkana === k) { - if (count > 0) { - curkana = oldk; - return; - } - } - oldk = k; - count++; + const off = keys.indexOf(curkana); + if (off > 0) { + return keys[off - 1]; + } + return keys[keys.length - 1]; +} +let color = 0; +const colors = [ + () => g.setColor(0,1,0), + () => g.setColor(1,1,0), + () => g.setColor(0,1,1), + () => g.setColor(1,1,1), + // too dark + () => g.setColor(0,0,1), + () => g.setColor(0,0,0), + () => g.setColor(1,0,0), +]; + +function nextColor() { + if (color + 1 >= colors.length) { + color = 0; + } else { + color++; + } +} +function prevColor() { + if (color < 1) { + color = colors.length - 1; + } else { + color--; } - curkana = oldk; - updateWatch(ohhmm); } -function updateWatch (hhmm) { +function render(hhmm) { g.setFontAlign(-1, -1, 0); g.setBgColor(0, 0, 0); g.setColor(0, 0, 0); - var whitecolor = false; - if (curkana.indexOf('A') != -1) { - g.setColor(1, 0, 0); - whitecolor = true; - } else if (curkana.indexOf('I') != -1) { - g.setColor(0, 1, 0); - } else if (curkana.indexOf('U') != -1) { - g.setColor(0, 0, 1); - whitecolor = true; - } else if (curkana.indexOf('E') != -1) { - g.setColor(1, 1, 0); - } else { - g.setColor(0, 1, 1); - } - g.fillRect(0, 0, w, h); + const whitecolor = color > 3; + colors[color](); + g.fillRect(0, 30, w, h); g.setFont('Vector', 50); if (whitecolor) { @@ -204,9 +218,9 @@ function updateWatch (hhmm) { } g.drawString(hhmm, x, y - 1); - drawKana(4 + (g.getWidth() / 6), 60); + drawKana(); drawMonthDay(); - Bangle.drawWidgets(); + // Bangle.drawWidgets(); // :? always draw? } function drawMonthDay() { @@ -227,21 +241,47 @@ function getPhoneme(k) { } return k; } - + +var ohhmm = ''; +var ypos = 0; +var xpos = 0; +var zpos = 1; function drawKana (x, y) { + if (!x) { + x = 4 + (g.getWidth() / 6); + } + if (!y) { + y = 40; + } + x += xpos; + y += ypos; g.setColor(0, 0, 0); - g.fillRect(0, 0, g.getWidth(), 6 * (h / 8) + 1); + g.fillRect(0, 30, g.getWidth(), 6 * (h / 8) + 1); g.setColor(1, 1, 1); + x -= ((zpos) - 1)*50; + y -= (zpos - 1)*50; kana = hiramode ? hiragana[curkana] : katakana[curkana]; - g.drawImage(kana, x + 20, 40, { scale: 1.6 }); + if (guard) { + g.setColor(0.8,0.8,0.8); + } + g.drawImage(kana, x + 20, y, { scale: 1.6 * zpos }); g.setColor(1, 1, 1); g.setFont('Vector', 24); g.drawString(getPhoneme(curkana), 4, 32); - g.drawString(hiramode ? 'H' : 'K', w - 20, 32); + if (hiramode) { + g.setColor(0.2,0.2,0.2) + g.drawString('K', w - 20, 32); + g.setColor(1, 1, 1); + g.drawString('H', w - 20, 32+24); + } else { + g.setColor(1, 1, 1); + g.drawString('K', w - 20, 32); + g.setColor(0.2,0.2,0.2) + g.drawString('H', w - 20, 32+24); + } + // g.drawString(hiramode ? 'H' : 'K', w - 20, 32); } -var ohhmm = ''; - function tickWatch () { const now = Date(); month = now.getMonth() + 1; @@ -251,27 +291,119 @@ function tickWatch () { } const hhmm = zpad(now.getHours()) + ':' + zpad(now.getMinutes()); if (hhmm !== ohhmm) { - randKana(); - updateWatch(hhmm); ohhmm = hhmm; + randKana(); + render(hhmm); } } +let guard = false; +function hiraPush(d,dx) { + if (guard) { + return; + } + xpos = 0; + ypos = 0; + zpos = 1; + guard = true; + var count = 2; + function paint() { + count--; + if (count < 0) { + guard = false; + xpos = 0; + ypos = 0; + zpos = 1; + render(ohhmm); + return; + } + zpos -= 0.04; + render(ohhmm); + setTimeout(paint, 5); + } + setTimeout (paint, 5); +} + +function hiraSwipe(d,dx, dostuff) { + if (guard) { + return; + } + if (dx) { + ypos = 0; + } else { + ypos = (d * 4); + } + xpos = 0; + guard = true; + var count = 2; + function paint() { + count--; + if (count < 0) { + if (dx) { + curkana = d>0?prev():next(); + } else { + if (dostuff) { + hiramode = !hiramode; + } + } + guard = false; + xpos = 0; + ypos = 0; + render(ohhmm); + return; + } + if (dx) { + xpos += (4*d); + } else { + ypos -= (4*d); + } + render(ohhmm); + setTimeout(paint, 5); + } + setTimeout (paint, 5); +} Bangle.on('touch', function (tap, top) { - if (top.x < w / 4) { - prev(); - } else if (top.x > (w - (w / 4))) { - next(); + if (top.y < (h / 1.5)) { + if (top.x > w /2) { + //hiramode = !hiramode; + if (hiramode) { + hiraSwipe(1,0, hiramode); + } else { + hiraSwipe(-1,0, !hiramode); + } + } else { + hiraSwipe(1,1,1); + } + } else if (top.x < w / 2) { + nextColor(); + hiraPush(); + // curkana = prev(); } else { - hiramode = !hiramode; + prevColor(); + hiraPush(); + // curkana = next(); } - kana = hiramode ? hiragana[curkana] : katakana[curkana]; - updateWatch(ohhmm); + render(ohhmm); +}); +Bangle.on('swipe', function (x,y) { + if (x > 0) { + // nextColor(); + hiraSwipe(1, 1); + } else if (x < 0) { + // prevColor(); + hiraSwipe(-1,1); + } else if (y < 0) { + hiraSwipe(1, 0, hiramode); + } else if (y > 0) { + hiraSwipe(-1, 0, !hiramode); + } + render(ohhmm); }); g.clear(true); // show launcher when button pressed Bangle.setUI('clock'); Bangle.loadWidgets(); +Bangle.drawWidgets(); tickWatch(); setInterval(tickWatch, 1000 * 60); diff --git a/apps/kanawatch/metadata.json b/apps/kanawatch/metadata.json index d1fb70b53..0a7fd8e4a 100644 --- a/apps/kanawatch/metadata.json +++ b/apps/kanawatch/metadata.json @@ -2,7 +2,7 @@ "id": "kanawatch", "name": "Kanawatch", "shortName": "Kanawatch", - "version": "0.08", + "version": "0.10", "type": "clock", "description": "Learn Hiragana and Katakana", "icon": "app.png", @@ -26,6 +26,9 @@ "screenshots": [ { "url": "screenshot.png" + }, + { + "url": "screenshot2.png" } ] } diff --git a/apps/kanawatch/screenshot.old.png b/apps/kanawatch/screenshot.old.png new file mode 100644 index 0000000000000000000000000000000000000000..b1ed879aa1a3b8bef32bb80f4c08b1443530a177 GIT binary patch literal 2992 zcmc&$=|7Z>8s=f7LAIGt7;9!IOR|?r7?WMu!@PE3MrpCNJds@nS+fi?vW>*CX7EHv zmQW^ZrV@pWUdt%rc+c;5{(|%2e7NuHcVG8)eY&p?cY=)-Rsenk&cVSUfHO0;+sEhs z6fe(yuW6cd-v`c6JFF4Mvmwdv92|UCamME_hq?bK_#RQE&u{XCeec_*{0WR~V}n+h z(DL?LPc*$=sE4YTT2oWQQrpD>xPzzBA)^Bt%SxPZ3t>A^GF0)dmx)oAGPAr7lv$yo z%b*)FTAc8F{X6`Hz&r3gH#%f-T_X%`ap3h;=g-a$9pghOlAOrfwEM08f>u|Vd@$d;*nlML|D06 zzvZ!C@^|jKlPZD01AZ{~p`!1c4U% zJ!MIk7Cp_n-y4&+iq%eTCfL5t^DgmvhW?>(Y%mYAx-I3llDi-FEx{5ZRq*Qb?@NM5$sz42xp17GRwGy&@8FJevc){AFRq61>BUd7fI-ssr1U zr9y^xS3V-f<4C~vR!ih1Jj&>Q5cRV+-DTQf&Swm*9}s`~NDInPS&I*7(x}d6Ml2Bd z?azjr8)@vz8ukAEV9Vr)XqQDlPo4c2dl5<4*gB0NL(&m6h;u#af(;(k@wJK=YY?LZ zLyTWgd$|b$2BJ|LD-xU85}x%Do$w&VZ125(__D?7SgT(-;lE@rd|5&4T>)UA=ldV> zDF__x3ojin`|-y_dId~5*dC!%1IfRd2_<11s�Pi)X@SJ7PR%+Tce2<*}39l{e#% zPUgwTaWk5=4m^mtSnWUcc$(`dB=*7v&xPIhe*KDxk$vgNG6FyJsjEDf6LY_YE&L;$ z{WT$Sea0y5k>4daO=e#Bul08JA*?^bNtVcH^X^Hq$~f$Rzn|s!z}Yp9CMHEncNB&5 z50^k3RGcMRo#Ug*>cD~Gfm*JLPZ#pJ`&1xWoQ~luTy9(60wSyxSF??363U!5jyGui z{kJS(hOZ{j+xG7v#CU6L?)ELq>&_rwJ-zSjAw6?$))C+C>NK{qOylhQ@U0hDBZ;Gq zLTpt*;Ly^x6>WO<$J2#_f6Jx6kBg8X=+#TGJcT!+T-_6a&E-60{xsbqSX8nQ`4XL`Ew^B^}RW%e1o0%_KnU} zx6vJ!BGRS_s0dLGALp#NquN~~r4!LM@-yH=Xb`Hc(eC(Y#9Iyj$xC>d?Gap-2zLyE zp%4-L4rbUH>?R?9c%?7hF}-kUt!Ms|$c9W}4|xMOwMzk5T zxoxVatOD97sL@kDmxD*DjRKav3el^e5UjDhoFzXq;VSmG9@Y{}6L}_h&plNnXJT}S zqT%O@bN5by*)#1tU48ByIy6Ib3_mwk9g)I_Z}tlhggpkL@9EW=F0Nt0H<1YO6XZ&- z`emeU2VBpQ{PWnd_>ZXM;)8NZzNRzL_Zd%IwkP*G=BnnT6PP*Lz_$_RwSJk6fECbu z;fBNzGB|9aN{18IbLZ|l-Q>>-HdKxBTKv=1t+~KZ*3e|+hMMO;J(&C_ah$j{A}1e;eTz{n+0+wRTAF*Uk1L%W zXc;Nx7i$jJy7!@B;*qr+wQ_Lw^h)mPV)(JL+)&Er_D8d(!Ll@)A-=lAAQAQ+$*zBF z`tCnFS5Tk=QVp+UJM8c*6yCFe-RgDrg!LGP5~}E~9T_}C*Uy^3 zPM`@ckcW5nXzi6(%HaaX6?2_IgUd{vZw+FiZ!U6s1Pv;E`fX}V>wJjHcOZJ8SJmpI zw=bG+q*Zo#JpbxTf$_L==}hcamu!UA4;P$oAtY$1gs|YpeK>Ze-9XN3BxU|sS0c}= z;@oIJNmzU`%PjZ6+=N1`a>c3a>nMWsBf)E(jnuC8!91E{2Se;`awIdAz|f<1bBd*% zdi68ZftieKPXDVs;wtx|Y_0EI$QvFd3l{0{1@Pg8(RSYbI|6B#RT%8BwJ1%5Q;t=J zuvId#6MhPbA)7I^_V|~_zEKszRuBL?;_V?$)T)r!#t$vN{b-txA%D@UpzXqeO9Gc` zlrtUK$^c5QAR~4+KPxW7(ev$eJasTSv#{6S9kj>uXI?h}PefwXlzzfYDe7Sh?(TSF z&vlRf_w<$Ojtw>Wz!bNn8V9Lrq@_s3fwF?0ftE8Ow`YxHfW5!H;zUc8D0 zr(){(qUiPsQ=GW0FAJU7^-WKU*X&t7ds?W<-E&&}DXy}lEp6)zR99Dn@77<)trKWw z${~7u5`5>%9*33vkmf>c#@TOdu=O0EJZ?zzS3G(d>{1Og`FcWiP`>Uq;oO`aa!WFY zCmojP8^z&Li#zd*d=_Kp=*?HgnAGB@ED&Bb#6o+eb<&jw5_xvt0POh~+RU#n(XSDC r+JOgY3HiY|{QHiy^P>vGNswTL^R&j#U}O6J3(kQvu`+&Ugn#gF@6nHi literal 0 HcmV?d00001 diff --git a/apps/kanawatch/screenshot.png b/apps/kanawatch/screenshot.png index b1ed879aa1a3b8bef32bb80f4c08b1443530a177..4ef0ecbf286c92361988b55513459dba2dec9dcf 100644 GIT binary patch literal 2653 zcmc&$`8(A67k|%Z7L4VRY}t||yG9|)wM3I`kfp2*vhNIyeNbq@wS=;TOzDoT7<4V4 zy7nx|PR2ynE~CX#->a|wf$y*1AI^C_&+9qQd7g95>paivwiN--jXj110C1aJGP-<- zb-#igbC_$JKDr(PBK$Jm5Y!Bbtpb1*GBGl+CA)4GuSPCvaV3xLCaesZP=mzroce)} zZ|#Q<2R2y2vJdl$chvqL?{2icA%DwJ*4-g(yWIBR=lbpBaNjmoEcr z3M7o?4@^ZpRo}h*C@;~!aodhtn20f@ZnkZV_<1P+!~oPsFKzP~Y$7B&W4zS1>LG+q z-<$V?o3(tmq~DlGr4&)IbuxvH-*4Kg@RPVFY@1w$`f5d*?fEC0PeZYv2G|uT`+D*H zD^6+=PlnwnrR9%Ka1s$kvqqz8tZmKAs&P~uD2WhwIsxO4v*lhA6ohr)0f8VvG3h0T zFzmK7J5pve^5!Lp?y>auVnR%=0ewZvyoQdSOjyK)p3gHik!x@Xn+=fWUB(UbEESbm zV@h7!@}c&tPL^V!&MHQ+5(ceQM-A||&Ih<{CAB(qZs=6EaMZ-bda}(O3(&*# zC+9-s=FUA-o=Zk%{BRyLl~oHBXn3IEJUzEimj|ZojCH3SUN|Gb4zo1K3%7dx9nDU3 zbwmvKtJ6}!Ql-HEpk^Gvm#q6SE5g-HUL(|mf zfWA7>mJjGA@|3`r+^e36KcVfuBjl>(B>bs9=`m z>J-euD*m9WKU-Neg=LDv0?@h$1UZi2X`|bf*sFg;1p6L(T=7BgBp}eQ#=({!+to+S;iMb)U)TY{7m5Hcz4_ zkxh8!!r>@Wgv{iUL)mD)1LJ} z&um&AS-LrkUfRT|`fa$}G?)}UcO&Za?YLAC-=A5D))#a+Mj@e>jJ)&qJKvZ#7K{G( z%ztbm2(Rq;LuCHgcs~nmxP72Gw9RlbT}|61UGi|7+YYOt}Q4 zK{`8*WvH0fsz&9XPaT6HvQX?qZ8$p|z}M!AF=jd-@?`08+a}IypICBv1SuWqMFqO^ z^z{02U(<*m+YU4(bdpiw7Ue$UeeKBr_X%>_;)2@md9n@v(zlR58XTt~{DG$450_3MBc}c$yfIBHQni?-W*cX8 z)f9;)?oM|jVLnnAGK9amRIVkqu{BmJ{8KN88S~|UCyVFo3a)(bzqV&Tl>T~M3W*v`dh3+Y$Bo~i;08txZM}Q->}mo z((3l}0EyRD>hL$HodHfnwo(G9nzYAqpV1kIFSp>~FbvZjk%2hz8J} z?30CwUJkyWS6c9yzipH$TiDY)R+8_PaIT#fT%Y*G4%bZ1#CK_-RIN(|gTn|{vNtXy z4mhW6H*TGv|L)afSoo69IKr>^iyb39R+Re;m6Q?FmZf!&V5<}@a29<6w~M7DG_D$; z&!^bQhCRF^?Y?84fwm1?^HYYYm<6c$z z^!iD-k0hqdCI0&R035u5=aC1abImpn=3XZagLLDTI&|rdno>g#%tGIc_I1M zhuPJ`CeJ>6bFEPf>ByAMq_atX{QU0R&t97}HKWs{-Y6w4!N7sn*<}Lm&rm*Sav4D~ zmW(OD{S&5hl!Gym-kw7kMO+-Hikk&m{k{Y8K3v|2@cE=Vuh9z0??TaG`NSmy%wHoh zYe&m(>Vtz)oj2cX8{?IL^SV=^HmB)QvNf2uc%lj_cngJcDUvTmH7pBOdfQ#>_s~+q zBmRX$M_;NHoBKDDjzwcbOa6?E3UIuq=J9Ayno_8!r)7!UyYZSaT?xIe3)l(O*iH@P zgtb@4l9{WZBdMk>w2QVu1zV5!J;G}?k5Mvx9Q85r`7`rp`Q!Z!pB&Tl9kYnI^IIMI z`WvZePVlt9rI`|Z{gI8;l^P$dS0`FEoC_0c4K&(03&)wtKw6XmDdTGR>N10rwq@K| p=n$2xvqS2K1}>1+zHImt@!)~m8TlMx(xG1jCdLG#8be~ze*wopz;*xt literal 2992 zcmc&$=|7Z>8s=f7LAIGt7;9!IOR|?r7?WMu!@PE3MrpCNJds@nS+fi?vW>*CX7EHv zmQW^ZrV@pWUdt%rc+c;5{(|%2e7NuHcVG8)eY&p?cY=)-Rsenk&cVSUfHO0;+sEhs z6fe(yuW6cd-v`c6JFF4Mvmwdv92|UCamME_hq?bK_#RQE&u{XCeec_*{0WR~V}n+h z(DL?LPc*$=sE4YTT2oWQQrpD>xPzzBA)^Bt%SxPZ3t>A^GF0)dmx)oAGPAr7lv$yo z%b*)FTAc8F{X6`Hz&r3gH#%f-T_X%`ap3h;=g-a$9pghOlAOrfwEM08f>u|Vd@$d;*nlML|D06 zzvZ!C@^|jKlPZD01AZ{~p`!1c4U% zJ!MIk7Cp_n-y4&+iq%eTCfL5t^DgmvhW?>(Y%mYAx-I3llDi-FEx{5ZRq*Qb?@NM5$sz42xp17GRwGy&@8FJevc){AFRq61>BUd7fI-ssr1U zr9y^xS3V-f<4C~vR!ih1Jj&>Q5cRV+-DTQf&Swm*9}s`~NDInPS&I*7(x}d6Ml2Bd z?azjr8)@vz8ukAEV9Vr)XqQDlPo4c2dl5<4*gB0NL(&m6h;u#af(;(k@wJK=YY?LZ zLyTWgd$|b$2BJ|LD-xU85}x%Do$w&VZ125(__D?7SgT(-;lE@rd|5&4T>)UA=ldV> zDF__x3ojin`|-y_dId~5*dC!%1IfRd2_<11s�Pi)X@SJ7PR%+Tce2<*}39l{e#% zPUgwTaWk5=4m^mtSnWUcc$(`dB=*7v&xPIhe*KDxk$vgNG6FyJsjEDf6LY_YE&L;$ z{WT$Sea0y5k>4daO=e#Bul08JA*?^bNtVcH^X^Hq$~f$Rzn|s!z}Yp9CMHEncNB&5 z50^k3RGcMRo#Ug*>cD~Gfm*JLPZ#pJ`&1xWoQ~luTy9(60wSyxSF??363U!5jyGui z{kJS(hOZ{j+xG7v#CU6L?)ELq>&_rwJ-zSjAw6?$))C+C>NK{qOylhQ@U0hDBZ;Gq zLTpt*;Ly^x6>WO<$J2#_f6Jx6kBg8X=+#TGJcT!+T-_6a&E-60{xsbqSX8nQ`4XL`Ew^B^}RW%e1o0%_KnU} zx6vJ!BGRS_s0dLGALp#NquN~~r4!LM@-yH=Xb`Hc(eC(Y#9Iyj$xC>d?Gap-2zLyE zp%4-L4rbUH>?R?9c%?7hF}-kUt!Ms|$c9W}4|xMOwMzk5T zxoxVatOD97sL@kDmxD*DjRKav3el^e5UjDhoFzXq;VSmG9@Y{}6L}_h&plNnXJT}S zqT%O@bN5by*)#1tU48ByIy6Ib3_mwk9g)I_Z}tlhggpkL@9EW=F0Nt0H<1YO6XZ&- z`emeU2VBpQ{PWnd_>ZXM;)8NZzNRzL_Zd%IwkP*G=BnnT6PP*Lz_$_RwSJk6fECbu z;fBNzGB|9aN{18IbLZ|l-Q>>-HdKxBTKv=1t+~KZ*3e|+hMMO;J(&C_ah$j{A}1e;eTz{n+0+wRTAF*Uk1L%W zXc;Nx7i$jJy7!@B;*qr+wQ_Lw^h)mPV)(JL+)&Er_D8d(!Ll@)A-=lAAQAQ+$*zBF z`tCnFS5Tk=QVp+UJM8c*6yCFe-RgDrg!LGP5~}E~9T_}C*Uy^3 zPM`@ckcW5nXzi6(%HaaX6?2_IgUd{vZw+FiZ!U6s1Pv;E`fX}V>wJjHcOZJ8SJmpI zw=bG+q*Zo#JpbxTf$_L==}hcamu!UA4;P$oAtY$1gs|YpeK>Ze-9XN3BxU|sS0c}= z;@oIJNmzU`%PjZ6+=N1`a>c3a>nMWsBf)E(jnuC8!91E{2Se;`awIdAz|f<1bBd*% zdi68ZftieKPXDVs;wtx|Y_0EI$QvFd3l{0{1@Pg8(RSYbI|6B#RT%8BwJ1%5Q;t=J zuvId#6MhPbA)7I^_V|~_zEKszRuBL?;_V?$)T)r!#t$vN{b-txA%D@UpzXqeO9Gc` zlrtUK$^c5QAR~4+KPxW7(ev$eJasTSv#{6S9kj>uXI?h}PefwXlzzfYDe7Sh?(TSF z&vlRf_w<$Ojtw>Wz!bNn8V9Lrq@_s3fwF?0ftE8Ow`YxHfW5!H;zUc8D0 zr(){(qUiPsQ=GW0FAJU7^-WKU*X&t7ds?W<-E&&}DXy}lEp6)zR99Dn@77<)trKWw z${~7u5`5>%9*33vkmf>c#@TOdu=O0EJZ?zzS3G(d>{1Og`FcWiP`>Uq;oO`aa!WFY zCmojP8^z&Li#zd*d=_Kp=*?HgnAGB@ED&Bb#6o+eb<&jw5_xvt0POh~+RU#n(XSDC r+JOgY3HiY|{QHiy^P>vGNswTL^R&j#U}O6J3(kQvu`+&Ugn#gF@6nHi diff --git a/apps/kanawatch/screenshot2.png b/apps/kanawatch/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..3e85485de6cda33947b54c915308dc3e897dfded GIT binary patch literal 2689 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 Gg}(uZY{0(& literal 0 HcmV?d00001 From 9a91595e416736273884ad6d02df3ae2189da66c Mon Sep 17 00:00:00 2001 From: pancake Date: Tue, 30 May 2023 03:26:19 +0200 Subject: [PATCH 037/116] Sort kanas by AIUEO instead of AEIOU --- apps/kanawatch/ChangeLog | 1 + apps/kanawatch/README.md | 22 ++++++++++++++-------- apps/kanawatch/app.js | 28 +++++++++++++++++++++++----- apps/kanawatch/metadata.json | 2 +- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/apps/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog index 1521bed73..a50406917 100644 --- a/apps/kanawatch/ChangeLog +++ b/apps/kanawatch/ChangeLog @@ -8,3 +8,4 @@ 0.08: Speedup next/prev and fix autogenerated hiragana bitmaps 0.09: Optimize loading and rendering times, introduce transition animations 0.10: Swipe up/down for Hiragana/Katakana, right/left for next/prev letter +0.11: Sort by 'AIUEO' instead of 'AEIOU', draw Widgets every minute :? diff --git a/apps/kanawatch/README.md b/apps/kanawatch/README.md index 87a9750b1..d13550f4d 100644 --- a/apps/kanawatch/README.md +++ b/apps/kanawatch/README.md @@ -1,14 +1,19 @@ # kanawatch -A simple watchface design with hiragana and katakana -cards for learning. +A simple watchface design perfect for learning hiragana and katakana. -## Changelog +* Interact with the interface using swipes +* Swipe up/down to switch between hiragana (H) and katakana (K) +* Swipe right/left to display the next or previous letter +* Tap to change accent color (always 24h, not configurable) +* Non-intrustive transition animations +* Low battery consumption -0.01: First release -0.02: Improve battery life, sprite resolution, fix launcher issue and unaligned text bug -0.03: Reduce code size, refresh once a minute and faster refresh -0.04: Show a random kana every minute to improve learning +## TODO + +* Only render what needs to be repainted +* Dont redraw the widgets if not necessary +* Minigame to guess kata/hira phonem ## Author @@ -16,4 +21,5 @@ Written by pancake in 2022, maintained during 2023 and powered by insomnia ## Screenshots -![hiragana and katakana](screenshot.png) +![katakana](screenshot.png) +![hiragana ](screenshot2.png) diff --git a/apps/kanawatch/app.js b/apps/kanawatch/app.js index 2aa5c2a3c..a81852b1b 100644 --- a/apps/kanawatch/app.js +++ b/apps/kanawatch/app.js @@ -131,16 +131,26 @@ WO: image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBB N: image(54, 49, "AAMHAwsf8AGE/+AAocD/wTF+AGEv/ACZUP/ATKgP/CYv8Awk/IQgTBIQkHCYxCFCYxWTIQxWGFAhCBAwkPAwJCE/5KDCYQiBhhCBAwJlBn+Aj/+/49BDoP/8IDBgf8IQIDBKgUf/EPLAJUBv/gn/AFgKZCAIMHCIP4DQSXBAIIaC/+BCIIaBYwKZCLwIuBCYLRCFwIKBEYX/CYUfEYP4TIRACCYQ+BwZUBDwIYBOgITCRAQVCEIP//0BYISjB+CtDUYRNBAwQ5Bg7gDBQIA="), }; - +const keys = [ + "A","I","U","E","O", + "HA","HI","HU","HE","HO", + "KA","KI","KU","KE","KO", + "MA","MI","MU","ME","MO", + "NA","NI","NU","NE","NO", + "RA","RI","RU","RE","RO", + "SA","SI","SU","SE","SO", + "TA","TI","TU","TE","TO", + "WA","WO","YO","YU","N", + ]; let kana = katakana.KA; let scroll = 0; -const keys = Object.keys(katakana).sort(); +// const keys = Object.keys(katakana).sort(); +// console.log(keys); let hiramode = false; let curkana = 'KA'; console.log("StartupTime: "+startupTime.diff()); - function next () { const off = keys.indexOf(curkana); if (off !== -1 && off + 1 < keys.length) { @@ -318,7 +328,7 @@ function hiraPush(d,dx) { } zpos -= 0.04; render(ohhmm); - setTimeout(paint, 5); + setTimeout(paint, 100); } setTimeout (paint, 5); } @@ -352,7 +362,7 @@ function hiraSwipe(d,dx, dostuff) { return; } if (dx) { - xpos += (4*d); + xpos += (8*d); } else { ypos -= (4*d); } @@ -405,5 +415,13 @@ g.clear(true); Bangle.setUI('clock'); Bangle.loadWidgets(); Bangle.drawWidgets(); + +// redraw widgets every 10 minutes +setInterval(function() { + // maybe not always necessary + Bangle.drawWidgets(); +}, 1000 * 60 * 10); tickWatch(); setInterval(tickWatch, 1000 * 60); + + diff --git a/apps/kanawatch/metadata.json b/apps/kanawatch/metadata.json index 0a7fd8e4a..f3aaeae92 100644 --- a/apps/kanawatch/metadata.json +++ b/apps/kanawatch/metadata.json @@ -2,7 +2,7 @@ "id": "kanawatch", "name": "Kanawatch", "shortName": "Kanawatch", - "version": "0.10", + "version": "0.11", "type": "clock", "description": "Learn Hiragana and Katakana", "icon": "app.png", From 721df7cf286e798f99919524fd58b55f03a275b7 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Wed, 31 May 2023 21:47:16 +0200 Subject: [PATCH 038/116] Delete runplus (will readd soon) --- apps/runplus/ChangeLog | 22 ---- apps/runplus/README.md | 76 ------------- apps/runplus/app-icon.js | 1 - apps/runplus/app.js | 198 --------------------------------- apps/runplus/app.png | Bin 1479 -> 0 bytes apps/runplus/karvonen.js | 215 ------------------------------------ apps/runplus/metadata.json | 20 ---- apps/runplus/screenshot.png | Bin 3716 -> 0 bytes apps/runplus/settings.js | 157 -------------------------- 9 files changed, 689 deletions(-) delete mode 100644 apps/runplus/ChangeLog delete mode 100644 apps/runplus/README.md delete mode 100644 apps/runplus/app-icon.js delete mode 100644 apps/runplus/app.js delete mode 100644 apps/runplus/app.png delete mode 100644 apps/runplus/karvonen.js delete mode 100644 apps/runplus/metadata.json delete mode 100644 apps/runplus/screenshot.png delete mode 100644 apps/runplus/settings.js diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog deleted file mode 100644 index d920a3eca..000000000 --- a/apps/runplus/ChangeLog +++ /dev/null @@ -1,22 +0,0 @@ -0.01: New App! -0.02: Set pace format to mm:ss, time format to h:mm:ss, - added settings to opt out of GPS and HRM -0.03: Fixed distance calculation, tested against Garmin Etrex, Amazfit GTS 2 -0.04: Use the exstats module, and make what is displayed configurable -0.05: exstats updated so update 'distance' label is updated, option for 'speed' -0.06: Add option to record a run using the recorder app automatically -0.07: Fix crash if an odd number of active boxes are configured (fix #1473) -0.08: Added support for notifications from exstats. Support all stats from exstats -0.09: Fix broken start/stop if recording not enabled (fix #1561) -0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578) -0.11: Notifications fixes -0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11 -0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) -0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working -0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher) - Keep run state between runs (allowing you to exit and restart the app) -0.16: Don't clear zone 2b indicator segment when updating HRM reading. - Write to correct settings file, fixing settings not working. -0.17: Fix typo in variable name preventing starting a run. -0.18: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix - another typo. diff --git a/apps/runplus/README.md b/apps/runplus/README.md deleted file mode 100644 index 659cd964d..000000000 --- a/apps/runplus/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Run App - -This app allows you to display the status of your run, it -shows distance, time, steps, cadence, pace and more. - -To use it, start the app and press the middle button so that -the red `STOP` in the bottom right turns to a green `RUN`. - -## Display - -* `DIST` - the distance travelled based on the GPS (if you have a GPS lock). - * NOTE: this is based on the GPS coordinates which are not 100% accurate, especially initially. As - the GPS updates your position as it gets more satellites your position changes and the distance - shown will increase, even if you are standing still. -* `TIME` - the elapsed time for your run -* `PACE` - the number of minutes it takes you to run a given distance, configured in settings (default 1km) **based on your run so far** -* `HEART (BPM)` - Your current heart rate -* `Max BPM` - Your maximum heart rate reached during the run -* `STEPS` - Steps since you started exercising -* `CADENCE` - Steps per second based on your step rate *over the last minute* -* `GPS` - this is green if you have a GPS lock. GPS is turned on automatically -so if you have no GPS lock you just need to wait. -* The current time is displayed right at the bottom of the screen -* `RUN/STOP` - whether the distance for your run is being displayed or not - -## Recording Tracks - -When the `Recorder` app is installed, `Run` will automatically start and stop tracks -as needed, prompting you to overwrite or begin a new track if necessary. - -## Settings - -Under `Settings` -> `App` -> `Run` you can change settings for this app. - -* `Record Run` (only displayed if `Recorder` app installed) should the Run app automatically -record GPS/HRM/etc data every time you start a run? -* `Pace` is the distance that pace should be shown over - 1km, 1 mile, 1/2 Marathon or 1 Marathon -* `Boxes` leads to a submenu where you can configure what is shown in each of the 6 boxes on the display. - Available stats are "Time", "Distance", "Steps", "Heart (BPM)", "Max BPM", "Pace (avg)", "Pace (curr)", "Speed", and "Cadence". - Any box set to "-" will display no information. - * Box 1 is the top left (defaults to "Distance") - * Box 2 is the top right (defaults to "Time") - * Box 3 is the middle left (defaults to "Pace (avg)") - * Box 4 is the middle right (defaults to "Heart (BPM)") - * Box 5 is the bottom left (defaults to "Steps") - * Box 6 is the bottom right (defaults to "Cadence") -* `Notifications` leads to a submenu where you can configure if the app will notify you after -your distance, steps, or time repeatedly pass your configured thresholds - * `Ntfy Dist`: The distance that you must pass before you are notified. Follows the `Pace` options - * "Off" (default), "1km", "1 mile", "1/2 Marathon", "1 Marathon" - * `Ntfy Steps`: The number of steps that must pass before you are notified. - * "Off" (default), 100, 500, 1000, 5000, 10000 - * `Ntfy Time`: The amount of time that must pass before you are notified. - * "Off" (default), "30 sec", "1 min", "2 min", "5 min", "10 min", "30 min", "1 hour" - * `Dist Pattern`: The vibration pattern to use to notify you about meeting your distance threshold - * `Step Pattern`: The vibration pattern to use to notify you about meeting your step threshold - * `Time Pattern`: The vibration pattern to use to notify you about meeting your time threshold - -## TODO - -* Keep a log of each run's stats (distance/steps/etc) - -## Development - -This app uses the [`exstats` module](https://github.com/espruino/BangleApps/blob/master/modules/exstats.js). When uploaded via the -app loader, the module is automatically included in the app's source. However -when developing via the IDE the module won't get pulled in by default. - -There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md) -## Contributors (Run and Run+) -gfwilliams -hughbarney -GrandVizierOlaf -BartS23 -f-teacher -thyttan diff --git a/apps/runplus/app-icon.js b/apps/runplus/app-icon.js deleted file mode 100644 index a97d1b8ce..000000000 --- a/apps/runplus/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEw4UA///pH9vEFt9TIW0FqALJitUBZNVqoLqgo4BHZAUBtBTHgILB1XAEREV1WsEQ9AgWq1ALHgEO1WtBYxCBhWq0pdInWq2tABY8q1WVBZGq1XFBZS/IKQRvCDIsP9WsBZP60CTCBYs//+wLxALBTQ4AB///+AKHgYLB/gLK/4LHh//AIIwFitVr/8DIIwFLANXBAILIqogBn7DBEYrXBeQRgIBYKmHDgYLLZRBACBZYKJZIILKKRZeWgJGKAFQA==")) diff --git a/apps/runplus/app.js b/apps/runplus/app.js deleted file mode 100644 index 7cb5d4381..000000000 --- a/apps/runplus/app.js +++ /dev/null @@ -1,198 +0,0 @@ -// Use widget utils to show/hide widgets -let wu = require("widget_utils"); - -let runInterval; -let karvonenActive = false; -// Run interface wrapped in a function -let ExStats = require("exstats"); -let B2 = process.env.HWVERSION===2; -let Layout = require("Layout"); -let locale = require("locale"); -let fontHeading = "6x8:2"; -let fontValue = B2 ? "6x15:2" : "6x8:3"; -let headingCol = "#888"; -let fixCount = 0; -let isMenuDisplayed = false; - -g.reset().clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -wu.show(); - -// --------------------------- -let settings = Object.assign({ - record: true, - B1: "dist", - B2: "time", - B3: "pacea", - B4: "bpm", - B5: "step", - B6: "caden", - paceLength: 1000, - notify: { - dist: { - value: 0, - notifications: [], - }, - step: { - value: 0, - notifications: [], - }, - time: { - value: 0, - notifications: [], - }, - }, - HRM: { - min: 55, - max: 185, - }, -}, require("Storage").readJSON("runplus.json", 1) || {}); -let statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!==""); -let exs = ExStats.getStats(statIDs, settings); -// --------------------------- - -function setStatus(running) { - layout.button.label = running ? "STOP" : "START"; - layout.status.label = running ? "RUN" : "STOP"; - layout.status.bgCol = running ? "#0f0" : "#f00"; - layout.render(); -} - -// Called to start/stop running -function onStartStop() { - let running = !exs.state.active; - let prepPromises = []; - // start/stop recording - // Do this first in case recorder needs to prompt for - // an overwrite before we start tracking exstats - if (settings.record && WIDGETS["recorder"]) { - if (running) { - isMenuDisplayed = true; - prepPromises.push( - WIDGETS["recorder"].setRecording(true).then(() => { - isMenuDisplayed = false; - layout.setUI(); // grab our input handling again - layout.forgetLazyState(); - layout.render(); - }) - ); - } else { - prepPromises.push( - WIDGETS["recorder"].setRecording(false) - ); - } - } - - if (!prepPromises.length) // fix for Promise.all bug in 2v12 - prepPromises.push(Promise.resolve()); - - Promise.all(prepPromises) - .then(() => { - if (running) { - exs.start(); - } else { - exs.stop(); - } - // if stopping running, don't clear state - // so we can at least refer to what we've done - setStatus(running); - }); -} - -let lc = []; -// Load stats in pair by pair -for (let i=0;ilayout[e.id].label = e.getString()); - if (sb) sb.on('changed', e=>layout[e.id].label = e.getString()); -} -// At the bottom put time/GPS state/etc -lc.push({ type:"h", filly:1, c:[ - {type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" }, - {type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }, - {type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 } -]}); -// Now calculate the layout -let layout = new Layout( { - type:"v", c: lc -},{lazy:true, btns:[{ label:"---", cb: (()=>{if (karvonenActive) {stopKarvonenUI();run();} onStartStop();}), id:"button"}]}); -delete lc; -setStatus(exs.state.active); -layout.render(); - -function configureNotification(stat) { - stat.on('notify', (e)=>{ - settings.notify[e.id].notifications.reduce(function (promise, buzzPattern) { - return promise.then(function () { - return Bangle.buzz(buzzPattern[0], buzzPattern[1]); - }); - }, Promise.resolve()); - }); -} - -Object.keys(settings.notify).forEach((statType) => { - if (settings.notify[statType].increment > 0 && exs.stats[statType]) { - configureNotification(exs.stats[statType]); - } -}); - -// Handle GPS state change for icon -Bangle.on("GPS", function(fix) { - layout.gps.bgCol = fix.fix ? "#0f0" : "#f00"; - if (!fix.fix) return; // only process actual fixes - if (fixCount++ === 0) { - Bangle.buzz(); // first fix, does not need to respect quiet mode - } -}); - -// run() function used to switch between traditional run UI and karvonen UI -function run() { - wu.show(); - layout.lazy = false; - layout.render(); - layout.lazy = true; - // We always call ourselves once a second to update - if (!runInterval){ - runInterval = setInterval(function() { - layout.clock.label = locale.time(new Date(),1); - if (!isMenuDisplayed && !karvonenActive) layout.render(); - }, 1000); - } -} -run(); - -/////////////////////////////////////////////// -// Karvonen -/////////////////////////////////////////////// - -function stopRunUI() { - // stop updating and drawing the traditional run app UI - clearInterval(runInterval); - runInterval = undefined; - karvonenActive = true; -} - -function stopKarvonenUI() { - g.reset().clear(); - clearInterval(karvonenInterval); - karvonenInterval = undefined; - karvonenActive = false; -} - -let karvonenInterval; -// Define the function to go back and forth between the different UI's -function swipeHandler(LR,_) { - if (LR==-1 && karvonenActive && !isMenuDisplayed) {stopKarvonenUI(); run();} - if (LR==1 && !karvonenActive && !isMenuDisplayed) {stopRunUI(); karvonenInterval = eval(require("Storage").read("runplus_karvonen"))(settings.HRM, exs.stats.bpm);} -} -// Listen for swipes with the swipeHandler -Bangle.on("swipe", swipeHandler); diff --git a/apps/runplus/app.png b/apps/runplus/app.png deleted file mode 100644 index 7059b8b015e20039a96de8b65c8a6b68a5e51e18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1479 zcmV;&1vvVNP)b0~r9hI1hzN2C_Hz2bec46d zz32!1f7|Dr^Zft+eV+5aXV1bTJi@~b@gPX$zNip1Yz1>vN2mz2DMEkvlG@MN zqD@lE?a)(59(>u0=Kppec*EA5(*zQ(EG3+(+KI->CEFJ!ACP%5!I+F&=p{{UFEnH!o7WN}(7DfEF{6T=^qRTOGUUmDCINa`%K?dqCzyBw&^& zdx;n90#h)O*AZj`^*@L)UE%?ZC>&z+*w0Pj=Mr*u5X*l)p2LibbmVEnr6riETg&$lZ8)JSSrFFIvt>|1LfBuoykZR)&GJv4Nif!en;z zv+6Z;L-k`+==0YD>-(E?LK%{G3lE#^`JOB$P&RcWluLnM)#~Vk(_duC*`K_4L>V>S z)_4dF#+CBq(XN1$@$$f&f%ft_-N11%bdLd>txt@&{Fx`lWV(+H188fpdoMWEz{=c= zcS=M^psn$7;>Mk!^KP#d`utPCk6iBMM%oOs0tuJ37nQ16rohH)E z9ia`tY}y~?;W9hY+Ci=jL57GcSZ9mZX%$dBo+%nx{F~SG>u0QvN67NBtqYU0IzJ12 z!D%4xE@^d0`F6j&s+C&7&h>F)stg0CXcP_pe^xQKQS72jOY-(wh zZbX~G&?_#4KvUbwYbW!Gs64aVQ65`xr~;X3@&0)ixmMMvar5@_BseI*>bwB=Hsh4X zYQh$!?Cq~z?1DoTIA3l2sj5-q2GG>pLe-60r0n}JscF?|Pu=!AaWEs^-w{4*u-m|v z)cN}2e|5diF%6APc(zvE@zj|5xTt9M^e(*}a5ghB(al~?Yp}~eT3VX+_&;u68O^;r zFm#Zy>3N^}(KCW>&hyn7OqS~|H4F%<8>aciPOVnaXAj8ih*=;qfnl~5NeLTDd^m8~ zvnv)fz-)*f*%Y>B(}4>B-+uJwvah5;)T5WD&l4qTwmGbG5^Ni*$W&M4^}G>c@+r0 zm&L6n!K$N!Oe1_uD^fG^zOXju@RlWhrFCBC&eabb;-_mPZum831lFJkDuuov#6kU5 z4CF+`AmwiE&BPU?TrL(A+P>=nva%L?1T`4WzcBdbD0fZgQOG$4`W)s&T>c@z;lw?$ z*r#$R-lF#xBG~4P-1p~Z&LlR|e(BYTGQ71roRQk)24wckiNM_6L75_6I@Icn{4O=n ze($4X(0i|)Kwp^SEDqOJAUkp)FsXM?t`S20EwnAT>>nDL9jAl(3m`*#gmZ99% of HRR+minHR = serious risk of heart attack - let minzone2 = hrr * 0.6 + minhr; - let maxzone2 = hrr * 0.7 + minhr; - let maxzone3 = hrr * 0.8 + minhr; - let maxzone4 = hrr * 0.9 + minhr; - let maxzone5 = hrr * 0.99 + minhr; - - // HR data: large, readable, in the middle of the screen - function drawHR() { - g.setFontAlign(-1,0,0); - g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2-14,Rdiv(y,2)+25); - g.setColor(g.theme.fg); - g.setFont("Vector",50); - g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4); - } - - function drawWaitHR() { - g.setColor(g.theme.fg); - // Waiting for HRM - g.setFontAlign(0,0,0); - g.setFont("Vector",50); - g.drawString("--", Rdiv(x,2)+4, Rdiv(y,2)+4); - - // Waiting for current Zone - g.setFont("Vector",24); - g.drawString("Z-", Rdiv(x,4.3)-3, Rdiv(y,2)+2); - - // waiting for upper and lower limit of current zone - g.setFont("Vector",20); - g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2)); - g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7)); - } - - //These functions call arcs to show different HR zones. - - //To shorten the code, I'll reference some letiables and reuse them. - let centreX = R.x + 0.5 * R.w; - let centreY = R.y + 0.5 * R.h; - let minRadius = 0.38 * R.h; - let maxRadius = 0.50 * R.h; - - //draw background image (dithered green zones)(I should draw different zones in different dithered colors) - const HRzones= require("graphics_utils"); - let minRadiusz = 0.44 * R.h; - let startAngle = HRzones.degreesToRadians(-88.5); - let endAngle = HRzones.degreesToRadians(268.5); - - function drawBgArc() { - g.setColor(g.theme.dark==false?0xC618:"#002200"); - HRzones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); - } - - const zones = require("graphics_utils"); - //####### A function to simplify a bit the code ###### - function simplify (sA, eA, Z, currentZone, lastZone) { - let startAngle = zones.degreesToRadians(sA); - let endAngle = zones.degreesToRadians(eA); - if (currentZone == lastZone) zones.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle, endAngle); - else zones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); - g.setFont("Vector",24); - g.clearRect(Rdiv(x,4.3)-12, Rdiv(y,2)+2-12,Rdiv(x,4.3)+12, Rdiv(y,2)+2+12); - g.setFontAlign(0,0,0); - g.drawString(Z, Rdiv(x,4.3), Rdiv(y,2)+2); - } - - function zoning (max, min) { // draw values of upper and lower limit of current zone - g.setFont("Vector",20); - g.setColor(g.theme.fg); - g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/2)-10,Rdiv(x,2)+20*2, Rdiv(y,9/2)+10); - g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/7)-10,Rdiv(x,2)+20*2, Rdiv(y,9/7)+10); - g.setFontAlign(0,0,0); - g.drawString(max, Rdiv(x,2), Rdiv(y,9/2)); - g.drawString(min, Rdiv(x,2), Rdiv(y,9/7)); - } - - function clearCurrentZone() { // Clears the extension of the current zone by painting the extension area in background color - g.setColor(g.theme.bg); - HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle); - } - - function getZone(zone) { - drawBgArc(); - clearCurrentZone(); - if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);} - if (zone >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);} - if (zone >= 2) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-20, 1.5, "Z2", 2, zone);} - if (zone >= 3) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(3, 24, "Z2", 3, zone);} - if (zone >= 4) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(25.5, 46.5, "Z3", 4, zone);} - if (zone >= 5) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(48, 69, "Z3", 5, zone);} - if (zone >= 6) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(70.5, 91.5, "Z3", 6, zone);} - if (zone >= 7) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(93, 114.5, "Z4", 7, zone);} - if (zone >= 8) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(116, 137.5, "Z4", 8, zone);} - if (zone >= 9) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(139, 160, "Z4", 9, zone);} - if (zone >= 10) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(161.5, 182.5, "Z5", 10, zone);} - if (zone >= 11) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(184, 205, "Z5", 11, zone);} - if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} - } - - function getZoneAlert() { - const HRzonemax = require("graphics_utils"); - let centreX1,centreY1,maxRadius1 = 1; - let minRadius = 0.40 * R.h; - let startAngle1 = HRzonemax.degreesToRadians(-90); - let endAngle1 = HRzonemax.degreesToRadians(270); - g.setFont("Vector",38);g.setColor("#ff0000"); - HRzonemax.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle1, endAngle1); - g.drawString("ALERT", 26,66); - } - - //Subdivided zones for better readability of zones when calling the images. //Changing HR zones will trigger the function with the image and previous&next HR zones. - let subZoneLast; - function drawZones() { - if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawArrows(); drawHR();} // Reset UI when coming down from zone alert. - if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} // Z1 - else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} // Z2a - else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} // Z2b - else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} // Z2c - else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} // Z3a - else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} // Z3b - else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} // Z3c - else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} // Z4a - else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} // Z4b - else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} // Z4c - else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} // Z5a - else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} // Z5b - else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} // Z5c - else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert - } - - function initDraw() { - drawArrows(); - if (hr!=0) updateUI(true); else {drawWaitHR(); drawBgArc();} - //drawZones(); - } - - let hrLast; - //h = 0; // Used to force hr update via web ui console field to trigger draws, together with `if (h!=0) hr = h;` below. - function updateUI(resetHrLast) { // Update UI, only draw if warranted by change in HR. - hrLast = resetHrLast?0:hr; // Handles correct updating on init depending on if we've got HRM readings yet or not. - hr = exsHrmStats.getValue(); - //if (h!=0) hr = h; - if (hr!=hrLast) { - drawHR(); - drawZones(); - } //g.setColor(g.theme.fg).drawLine(175/2,0,175/2,175).drawLine(0,175/2,175,175/2); // Used to align UI elements. - } - - initDraw(); - - // check for updates every second. - karvonenInterval = setInterval(function() { - if (!isMenuDisplayed && karvonenActive) updateUI(); - }, 1000); - - return karvonenInterval; -}) diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json deleted file mode 100644 index 60860dc07..000000000 --- a/apps/runplus/metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "runplus", - "name": "Run+", - "version": "0.18", - "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.", - "icon": "app.png", - "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", - "supports": ["BANGLEJS2"], - "screenshots": [{"url": "screenshot.png"}], - "readme": "README.md", - "storage": [ - {"name": "runplus.app.js", "url": "app.js"}, - {"name": "runplus.img", "url": "app-icon.js", "evaluate": true}, - {"name": "runplus.settings.js", "url": "settings.js"}, - {"name": "runplus_karvonen", "url": "karvonen.js"} - ], - "data": [ - {"name": "runplus.json"} - ] -} diff --git a/apps/runplus/screenshot.png b/apps/runplus/screenshot.png deleted file mode 100644 index 1a813f19dfe304e3d8bf711692fd2abf1013c391..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3716 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.id); - - // ...and overwrite them with any saved values - // This way saved values are preserved if a new version adds more settings - const storage = require('Storage') - let settings = Object.assign({ - record: true, - B1: "dist", - B2: "time", - B3: "pacea", - B4: "bpm", - B5: "step", - B6: "caden", - paceLength: 1000, // TODO: Default to either 1km or 1mi based on locale - notify: { - dist: { - increment: 0, - notifications: [], - }, - step: { - increment: 0, - notifications: [], - }, - time: { - increment: 0, - notifications: [], - }, - }, - HRM: { - min: 55, - max: 185, - }, - }, storage.readJSON(SETTINGS_FILE, 1) || {}); - function saveSettings() { - storage.write(SETTINGS_FILE, settings) - } - - function getBoxChooser(boxID) { - return { - min: 0, max: statsIDs.length-1, - value: Math.max(statsIDs.indexOf(settings[boxID]),0), - format: v => statsList[v].name, - onchange: v => { - settings[boxID] = statsIDs[v]; - saveSettings(); - }, - } - } - - function sampleBuzz(buzzPatterns) { - return buzzPatterns.reduce(function (promise, buzzPattern) { - return promise.then(function () { - return Bangle.buzz(buzzPattern[0], buzzPattern[1]); - }); - }, Promise.resolve()); - } - - var menu = { - '': { 'title': 'Run' }, - '< Back': back, - }; - if (global.WIDGETS&&WIDGETS["recorder"]) - menu[/*LANG*/"Record Run"] = { - value : !!settings.record, - onchange : v => { - settings.record = v; - saveSettings(); - } - }; - var notificationsMenu = { - '< Back': function() { E.showMenu(menu) }, - } - menu[/*LANG*/"Notifications"] = function() { E.showMenu(notificationsMenu)}; - ExStats.appendMenuItems(menu, settings, saveSettings); - ExStats.appendNotifyMenuItems(notificationsMenu, settings, saveSettings); - var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"]; - var vibTimes = [ - [], - [[100, 1]], - [[300, 1]], - [[300, 1], [300, 0], [300, 1]], - [[300, 1],[300, 0], [100, 1], [300, 0], [300, 1]], - [[300, 1],[300, 0],[300, 1],[300, 0],[300, 1]], - ]; - notificationsMenu[/*LANG*/"Dist Pattern"] = { - value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))), - min: 0, max: vibTimes.length - 1, - format: v => vibPatterns[v]||/*LANG*/"Off", - onchange: v => { - settings.notify.dist.notifications = vibTimes[v]; - sampleBuzz(vibTimes[v]); - saveSettings(); - } - } - notificationsMenu[/*LANG*/"Step Pattern"] = { - value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))), - min: 0, max: vibTimes.length - 1, - format: v => vibPatterns[v]||/*LANG*/"Off", - onchange: v => { - settings.notify.step.notifications = vibTimes[v]; - sampleBuzz(vibTimes[v]); - saveSettings(); - } - } - notificationsMenu[/*LANG*/"Time Pattern"] = { - value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))), - min: 0, max: vibTimes.length - 1, - format: v => vibPatterns[v]||/*LANG*/"Off", - onchange: v => { - settings.notify.time.notifications = vibTimes[v]; - sampleBuzz(vibTimes[v]); - saveSettings(); - } - } - var boxMenu = { - '< Back': function() { E.showMenu(menu) }, - } - Object.assign(boxMenu,{ - 'Box 1': getBoxChooser("B1"), - 'Box 2': getBoxChooser("B2"), - 'Box 3': getBoxChooser("B3"), - 'Box 4': getBoxChooser("B4"), - 'Box 5': getBoxChooser("B5"), - 'Box 6': getBoxChooser("B6"), - }); - menu[/*LANG*/"Boxes"] = function() { E.showMenu(boxMenu)}; - - var hrmMenu = { - '< Back': function() { E.showMenu(menu) }, - } - - menu[/*LANG*/"HRM min/max"] = function() { E.showMenu(hrmMenu)}; - hrmMenu[/*LANG*/"min"] = { - min: 1, max: 100, - value: settings.HRM.min, - format: w => w, - onchange: w => { - settings.HRM.min = w; - saveSettings(); - }, - } - hrmMenu[/*LANG*/"max"] = { - min: 101, max: 220, - value: settings.HRM.max, - format: v => v, - onchange: v => { - settings.HRM.max = v; - saveSettings(); - }, - } - E.showMenu(menu); -}) From 6822d8ed709b484c37118d28f31bd3da004f3df7 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Wed, 31 May 2023 21:58:19 +0200 Subject: [PATCH 039/116] Forking run to runplus (2nd time) --- apps/runplus/ChangeLog | 17 ++++ apps/runplus/README.md | 69 ++++++++++++++++ apps/runplus/app-icon.js | 1 + apps/runplus/app.js | 160 ++++++++++++++++++++++++++++++++++++ apps/runplus/app.png | Bin 0 -> 1479 bytes apps/runplus/metadata.json | 16 ++++ apps/runplus/screenshot.png | Bin 0 -> 3716 bytes apps/runplus/settings.js | 129 +++++++++++++++++++++++++++++ 8 files changed, 392 insertions(+) create mode 100644 apps/runplus/ChangeLog create mode 100644 apps/runplus/README.md create mode 100644 apps/runplus/app-icon.js create mode 100644 apps/runplus/app.js create mode 100644 apps/runplus/app.png create mode 100644 apps/runplus/metadata.json create mode 100644 apps/runplus/screenshot.png create mode 100644 apps/runplus/settings.js diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog new file mode 100644 index 000000000..e79696c78 --- /dev/null +++ b/apps/runplus/ChangeLog @@ -0,0 +1,17 @@ +0.01: New App! +0.02: Set pace format to mm:ss, time format to h:mm:ss, + added settings to opt out of GPS and HRM +0.03: Fixed distance calculation, tested against Garmin Etrex, Amazfit GTS 2 +0.04: Use the exstats module, and make what is displayed configurable +0.05: exstats updated so update 'distance' label is updated, option for 'speed' +0.06: Add option to record a run using the recorder app automatically +0.07: Fix crash if an odd number of active boxes are configured (fix #1473) +0.08: Added support for notifications from exstats. Support all stats from exstats +0.09: Fix broken start/stop if recording not enabled (fix #1561) +0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578) +0.11: Notifications fixes +0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11 +0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) +0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working +0.15: Keep run state between runs (allowing you to exit and restart the app) +0.16: Added ability to resume a run that was stopped previously (fix #1907) \ No newline at end of file diff --git a/apps/runplus/README.md b/apps/runplus/README.md new file mode 100644 index 000000000..7f645b518 --- /dev/null +++ b/apps/runplus/README.md @@ -0,0 +1,69 @@ +# Run App + +This app allows you to display the status of your run, it +shows distance, time, steps, cadence, pace and more. + +To use it, start the app and press the middle button so that +the red `STOP` in the bottom right turns to a green `RUN`. + +## Display + +* `DIST` - the distance travelled based on the GPS (if you have a GPS lock). + * NOTE: this is based on the GPS coordinates which are not 100% accurate, especially initially. As + the GPS updates your position as it gets more satellites your position changes and the distance + shown will increase, even if you are standing still. +* `TIME` - the elapsed time for your run +* `PACE` - the number of minutes it takes you to run a given distance, configured in settings (default 1km) **based on your run so far** +* `HEART (BPM)` - Your current heart rate +* `Max BPM` - Your maximum heart rate reached during the run +* `STEPS` - Steps since you started exercising +* `CADENCE` - Steps per second based on your step rate *over the last minute* +* `GPS` - this is green if you have a GPS lock. GPS is turned on automatically +so if you have no GPS lock you just need to wait. +* The current time is displayed right at the bottom of the screen +* `RUN/STOP` - whether the distance for your run is being displayed or not + +## Recording Tracks + +When the `Recorder` app is installed, `Run` will automatically start and stop tracks +as needed, prompting you to overwrite or begin a new track if necessary. + +## Settings + +Under `Settings` -> `App` -> `Run` you can change settings for this app. + +* `Record Run` (only displayed if `Recorder` app installed) should the Run app automatically +record GPS/HRM/etc data every time you start a run? +* `Pace` is the distance that pace should be shown over - 1km, 1 mile, 1/2 Marathon or 1 Marathon +* `Boxes` leads to a submenu where you can configure what is shown in each of the 6 boxes on the display. + Available stats are "Time", "Distance", "Steps", "Heart (BPM)", "Max BPM", "Pace (avg)", "Pace (curr)", "Speed", and "Cadence". + Any box set to "-" will display no information. + * Box 1 is the top left (defaults to "Distance") + * Box 2 is the top right (defaults to "Time") + * Box 3 is the middle left (defaults to "Pace (avg)") + * Box 4 is the middle right (defaults to "Heart (BPM)") + * Box 5 is the bottom left (defaults to "Steps") + * Box 6 is the bottom right (defaults to "Cadence") +* `Notifications` leads to a submenu where you can configure if the app will notify you after +your distance, steps, or time repeatedly pass your configured thresholds + * `Ntfy Dist`: The distance that you must pass before you are notified. Follows the `Pace` options + * "Off" (default), "1km", "1 mile", "1/2 Marathon", "1 Marathon" + * `Ntfy Steps`: The number of steps that must pass before you are notified. + * "Off" (default), 100, 500, 1000, 5000, 10000 + * `Ntfy Time`: The amount of time that must pass before you are notified. + * "Off" (default), "30 sec", "1 min", "2 min", "5 min", "10 min", "30 min", "1 hour" + * `Dist Pattern`: The vibration pattern to use to notify you about meeting your distance threshold + * `Step Pattern`: The vibration pattern to use to notify you about meeting your step threshold + * `Time Pattern`: The vibration pattern to use to notify you about meeting your time threshold + +## TODO + +* Keep a log of each run's stats (distance/steps/etc) + +## Development + +This app uses the [`exstats` module](https://github.com/espruino/BangleApps/blob/master/modules/exstats.js). When uploaded via the +app loader, the module is automatically included in the app's source. However +when developing via the IDE the module won't get pulled in by default. + +There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md) diff --git a/apps/runplus/app-icon.js b/apps/runplus/app-icon.js new file mode 100644 index 000000000..a97d1b8ce --- /dev/null +++ b/apps/runplus/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///pH9vEFt9TIW0FqALJitUBZNVqoLqgo4BHZAUBtBTHgILB1XAEREV1WsEQ9AgWq1ALHgEO1WtBYxCBhWq0pdInWq2tABY8q1WVBZGq1XFBZS/IKQRvCDIsP9WsBZP60CTCBYs//+wLxALBTQ4AB///+AKHgYLB/gLK/4LHh//AIIwFitVr/8DIIwFLANXBAILIqogBn7DBEYrXBeQRgIBYKmHDgYLLZRBACBZYKJZIILKKRZeWgJGKAFQA==")) diff --git a/apps/runplus/app.js b/apps/runplus/app.js new file mode 100644 index 000000000..507e8581a --- /dev/null +++ b/apps/runplus/app.js @@ -0,0 +1,160 @@ +var ExStats = require("exstats"); +var B2 = process.env.HWVERSION===2; +var Layout = require("Layout"); +var locale = require("locale"); +var fontHeading = "6x8:2"; +var fontValue = B2 ? "6x15:2" : "6x8:3"; +var headingCol = "#888"; +var fixCount = 0; +var isMenuDisplayed = false; + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// --------------------------- +let settings = Object.assign({ + record: true, + B1: "dist", + B2: "time", + B3: "pacea", + B4: "bpm", + B5: "step", + B6: "caden", + paceLength: 1000, + notify: { + dist: { + value: 0, + notifications: [], + }, + step: { + value: 0, + notifications: [], + }, + time: { + value: 0, + notifications: [], + }, + }, +}, require("Storage").readJSON("run.json", 1) || {}); +var statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!==""); +var exs = ExStats.getStats(statIDs, settings); +// --------------------------- + +function setStatus(running) { + layout.button.label = running ? "STOP" : "START"; + layout.status.label = running ? "RUN" : "STOP"; + layout.status.bgCol = running ? "#0f0" : "#f00"; + layout.render(); +} + +// Called to start/stop running +function onStartStop() { + var running = !exs.state.active; + var shouldResume = false; + var promise = Promise.resolve(); + + if (running && exs.state.duration > 10000) { // if more than 10 seconds of duration, ask if we should resume? + promise = promise. + then(() => { + isMenuDisplayed = true; + return E.showPrompt("Resume run?",{title:"Run"}); + }).then(r => { + isMenuDisplayed=false;shouldResume=r; + }); + } + + // start/stop recording + // Do this first in case recorder needs to prompt for + // an overwrite before we start tracking exstats + if (settings.record && WIDGETS["recorder"]) { + if (running) { + isMenuDisplayed = true; + promise = promise. + then(() => WIDGETS["recorder"].setRecording(true, { force : shouldResume?"append":undefined })). + then(() => { + isMenuDisplayed = false; + layout.setUI(); // grab our input handling again + layout.forgetLazyState(); + layout.render(); + }); + } else { + promise = promise.then( + () => WIDGETS["recorder"].setRecording(false) + ); + } + } + + promise = promise.then(() => { + if (running) { + if (shouldResume) + exs.resume() + else + exs.start(); + } else { + exs.stop(); + } + // if stopping running, don't clear state + // so we can at least refer to what we've done + setStatus(running); + }); +} + +var lc = []; +// Load stats in pair by pair +for (var i=0;ilayout[e.id].label = e.getString()); + if (sb) sb.on('changed', e=>layout[e.id].label = e.getString()); +} +// At the bottom put time/GPS state/etc +lc.push({ type:"h", filly:1, c:[ + {type:"txt", font:fontHeading, label:"GPS", id:"gps", fillx:1, bgCol:"#f00" }, + {type:"txt", font:fontHeading, label:"00:00", id:"clock", fillx:1, bgCol:g.theme.fg, col:g.theme.bg }, + {type:"txt", font:fontHeading, label:"---", id:"status", fillx:1 } +]}); +// Now calculate the layout +var layout = new Layout( { + type:"v", c: lc +},{lazy:true, btns:[{ label:"---", cb: onStartStop, id:"button"}]}); +delete lc; +setStatus(exs.state.active); +layout.render(); + +function configureNotification(stat) { + stat.on('notify', (e)=>{ + settings.notify[e.id].notifications.reduce(function (promise, buzzPattern) { + return promise.then(function () { + return Bangle.buzz(buzzPattern[0], buzzPattern[1]); + }); + }, Promise.resolve()); + }); +} + +Object.keys(settings.notify).forEach((statType) => { + if (settings.notify[statType].increment > 0 && exs.stats[statType]) { + configureNotification(exs.stats[statType]); + } +}); + +// Handle GPS state change for icon +Bangle.on("GPS", function(fix) { + layout.gps.bgCol = fix.fix ? "#0f0" : "#f00"; + if (!fix.fix) return; // only process actual fixes + if (fixCount++ === 0) { + Bangle.buzz(); // first fix, does not need to respect quiet mode + } +}); +// We always call ourselves once a second to update +setInterval(function() { + layout.clock.label = locale.time(new Date(),1); + if (!isMenuDisplayed) layout.render(); +}, 1000); diff --git a/apps/runplus/app.png b/apps/runplus/app.png new file mode 100644 index 0000000000000000000000000000000000000000..7059b8b015e20039a96de8b65c8a6b68a5e51e18 GIT binary patch literal 1479 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.id); + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + let settings = Object.assign({ + record: true, + B1: "dist", + B2: "time", + B3: "pacea", + B4: "bpm", + B5: "step", + B6: "caden", + paceLength: 1000, // TODO: Default to either 1km or 1mi based on locale + notify: { + dist: { + increment: 0, + notifications: [], + }, + step: { + increment: 0, + notifications: [], + }, + time: { + increment: 0, + notifications: [], + }, + }, + }, storage.readJSON(SETTINGS_FILE, 1) || {}); + function saveSettings() { + storage.write(SETTINGS_FILE, settings) + } + + function getBoxChooser(boxID) { + return { + min: 0, max: statsIDs.length-1, + value: Math.max(statsIDs.indexOf(settings[boxID]),0), + format: v => statsList[v].name, + onchange: v => { + settings[boxID] = statsIDs[v]; + saveSettings(); + }, + } + } + + function sampleBuzz(buzzPatterns) { + return buzzPatterns.reduce(function (promise, buzzPattern) { + return promise.then(function () { + return Bangle.buzz(buzzPattern[0], buzzPattern[1]); + }); + }, Promise.resolve()); + } + + var menu = { + '': { 'title': 'Run' }, + '< Back': back, + }; + if (global.WIDGETS&&WIDGETS["recorder"]) + menu[/*LANG*/"Record Run"] = { + value : !!settings.record, + onchange : v => { + settings.record = v; + saveSettings(); + } + }; + var notificationsMenu = { + '< Back': function() { E.showMenu(menu) }, + } + menu[/*LANG*/"Notifications"] = function() { E.showMenu(notificationsMenu)}; + ExStats.appendMenuItems(menu, settings, saveSettings); + ExStats.appendNotifyMenuItems(notificationsMenu, settings, saveSettings); + var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"]; + var vibTimes = [ + [], + [[100, 1]], + [[300, 1]], + [[300, 1], [300, 0], [300, 1]], + [[300, 1],[300, 0], [100, 1], [300, 0], [300, 1]], + [[300, 1],[300, 0],[300, 1],[300, 0],[300, 1]], + ]; + notificationsMenu[/*LANG*/"Dist Pattern"] = { + value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))), + min: 0, max: vibTimes.length - 1, + format: v => vibPatterns[v]||/*LANG*/"Off", + onchange: v => { + settings.notify.dist.notifications = vibTimes[v]; + sampleBuzz(vibTimes[v]); + saveSettings(); + } + } + notificationsMenu[/*LANG*/"Step Pattern"] = { + value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))), + min: 0, max: vibTimes.length - 1, + format: v => vibPatterns[v]||/*LANG*/"Off", + onchange: v => { + settings.notify.step.notifications = vibTimes[v]; + sampleBuzz(vibTimes[v]); + saveSettings(); + } + } + notificationsMenu[/*LANG*/"Time Pattern"] = { + value: Math.max(0,vibTimes.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))), + min: 0, max: vibTimes.length - 1, + format: v => vibPatterns[v]||/*LANG*/"Off", + onchange: v => { + settings.notify.time.notifications = vibTimes[v]; + sampleBuzz(vibTimes[v]); + saveSettings(); + } + } + var boxMenu = { + '< Back': function() { E.showMenu(menu) }, + } + Object.assign(boxMenu,{ + 'Box 1': getBoxChooser("B1"), + 'Box 2': getBoxChooser("B2"), + 'Box 3': getBoxChooser("B3"), + 'Box 4': getBoxChooser("B4"), + 'Box 5': getBoxChooser("B5"), + 'Box 6': getBoxChooser("B6"), + }); + menu[/*LANG*/"Boxes"] = function() { E.showMenu(boxMenu)}; + E.showMenu(menu); +}) From e1a51d7c68aeb4a260a11541e1d1f6996e021d04 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Thu, 1 Jun 2023 20:02:43 +0200 Subject: [PATCH 040/116] kbswipe: Redone patterns a,e,m,w,z. --- apps/kbswipe/ChangeLog | 1 + apps/kbswipe/lib.js | 17 +++++++++-------- apps/kbswipe/metadata.json | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index a7b2d44c2..38d71986e 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -5,3 +5,4 @@ 0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat. 0.06: Support input of numbers and uppercase characters. 0.07: Support input of symbols. +0.08: Redone patterns a,e,m,w,z. diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index ea6d78255..4c652e9a9 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -10,11 +10,11 @@ on the left of the IDE, then do a stroke and copy out the Uint8Array line */ exports.getStrokes = function(mode, cb) { if (mode === exports.INPUT_MODE_ALPHA) { - cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152])); + cb("a", new Uint8Array([31, 157, 33, 149, 37, 131, 42, 112, 46, 97, 49, 83, 52, 72, 56, 64, 59, 59, 63, 53, 68, 48, 74, 47, 80, 47, 88, 50, 98, 63, 109, 94, 114, 115, 116, 130, 117, 141])); cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157])); cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158])); cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153])); - cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148])); + cb("e", new Uint8Array([107, 50, 101, 46, 94, 42, 85, 40, 75, 40, 65, 40, 58, 40, 51, 40, 47, 40, 44, 43, 45, 54, 52, 68, 63, 79, 70, 84, 70, 85, 59, 89, 52, 96, 45, 108, 39, 119, 37, 126, 37, 132, 37, 137, 41, 143, 48, 147, 60, 148, 69, 148, 78, 148, 84, 148, 89, 148])); cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130])); cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106])); cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159])); @@ -22,7 +22,7 @@ exports.getStrokes = function(mode, cb) { cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167])); cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153])); cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159])); - cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149])); + cb("m", new Uint8Array([36, 139, 36, 120, 36, 99, 36, 79, 36, 61, 41, 45, 56, 43, 71, 46, 77, 66, 77, 93, 77, 97, 84, 69, 93, 51, 107, 47, 118, 53, 123, 79, 124, 115, 124, 140])); cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49])); cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62])); cb("p", new Uint8Array([29, 47, 29, 55, 29, 75, 29, 110, 29, 145, 29, 165, 29, 172, 29, 164, 30, 149, 37, 120, 50, 91, 61, 74, 72, 65, 85, 61, 103, 61, 118, 63, 126, 69, 129, 76, 130, 87, 126, 98, 112, 108, 97, 114, 87, 116])); @@ -32,10 +32,10 @@ exports.getStrokes = function(mode, cb) { cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146])); cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56])); cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61])); - cb("w", new Uint8Array([25, 46, 25, 82, 25, 119, 33, 143, 43, 153, 60, 147, 73, 118, 75, 91, 76, 88, 85, 109, 96, 134, 107, 143, 118, 137, 129, 112, 134, 81, 134, 64, 134, 55])); + cb("w", new Uint8Array([35, 37, 35, 44, 35, 58, 35, 81, 35, 110, 35, 129, 39, 136, 45, 140, 51, 141, 60, 137, 70, 121, 76, 99, 78, 79, 78, 70, 78, 69, 83, 89, 89, 112, 93, 127, 97, 135, 102, 136, 108, 131, 115, 116, 119, 93, 122, 72, 123, 55, 123, 43])); cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145])); cb("y", new Uint8Array([30, 41, 30, 46, 30, 52, 30, 63, 30, 79, 33, 92, 38, 100, 47, 104, 54, 107, 66, 105, 79, 94, 88, 82, 92, 74, 94, 77, 96, 98, 96, 131, 94, 151, 91, 164, 85, 171, 75, 171, 71, 162, 74, 146, 84, 130, 95, 119, 106, 113])); - cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158])); + cb("z", new Uint8Array([39, 38, 45, 38, 53, 38, 62, 38, 72, 38, 82, 38, 89, 38, 96, 38, 99, 39, 95, 48, 82, 68, 70, 87, 60, 100, 50, 117, 42, 132, 42, 140, 45, 143, 53, 143, 67, 143, 81, 143])); cb("SHIFT", new Uint8Array([100, 160, 100, 50])); } else if (mode === exports.INPUT_MODE_NUM) { cb("0", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58])); @@ -210,7 +210,7 @@ exports.input = function(options) { if (o.stroke!==undefined && o.xy.length >= 6 && isStrokeInside(R, o.xy)) { var ch = o.stroke; if (ch=="\b") text = text.slice(0,-1); - else if (ch==="SHIFT") { shift=!shift; Bangle.drawWidgets(); } + else if (ch==="SHIFT") { shift=!shift; WIDGETS.kbswipe.draw(); } else text += shift ? ch.toUpperCase() : ch; } lastDrag = undefined; @@ -226,7 +226,7 @@ exports.input = function(options) { shift = false; setupStrokes(); show(); - Bangle.drawWidgets(); + WIDGETS.kbswipe.draw(); } Bangle.on('stroke',strokeHandler); @@ -239,7 +239,7 @@ exports.input = function(options) { area:"tl", width: 36, // 3 chars, 6*2 px/char draw: function() { - g.reset(); + g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 24); g.setFont("6x8:2x3"); g.setColor("#f00"); if (input_mode === exports.INPUT_MODE_ALPHA) { @@ -251,6 +251,7 @@ exports.input = function(options) { } } }; + Bangle.drawWidgets(); return new Promise((resolve,reject) => { Bangle.setUI({mode:"custom", drag:e=>{ diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index 6b597a371..3f3fbffa3 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,6 +1,6 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.07", + "version":"0.08", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", From dc069b0ec9c528b24e29e411521ea34358330f96 Mon Sep 17 00:00:00 2001 From: Erik Andresen Date: Fri, 2 Jun 2023 07:33:44 +0200 Subject: [PATCH 041/116] kbswipe: Fix off-by-one error --- apps/kbswipe/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index 4c652e9a9..7d05d7a8e 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -239,7 +239,7 @@ exports.input = function(options) { area:"tl", width: 36, // 3 chars, 6*2 px/char draw: function() { - g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 24); + g.reset().clearRect(this.x, this.y, this.x + this.width-1, this.y + 23); g.setFont("6x8:2x3"); g.setColor("#f00"); if (input_mode === exports.INPUT_MODE_ALPHA) { From d5f114762f331237befbdedc39f4b079e8bd2dc3 Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Wed, 22 Feb 2023 22:25:56 +0100 Subject: [PATCH 042/116] add changes to runplus changes to differentiate runplus from run app fix default settings fallback for HRM fixes to draw correctly and in a timely manner when swiping to the karvonnen ui depending on if we have hrm data or not Update ChangeLog small ui tweak run: Keep run state between runs (allowing you to exit and restart the app) tweak clearRect width to not clear the indicator segment bump version write to correct settings file add tag karvonen change spelling karvonnen -> karvonen in app.js, karvonen.js spelling karvonnen -> karvonen in metadata runplus - Fix typo in variable name preventing starting a run tweak and align HRM min/max defaults, change allowed min/max interval in settings bump version, fix typo follow the preferred metadata.json style Readd contributors to README.md Tweak ChangeLog --- apps/run/ChangeLog | 2 +- apps/runplus/ChangeLog | 9 +- apps/runplus/README.md | 7 ++ apps/runplus/app.js | 105 +++++++++++++----- apps/runplus/karvonen.js | 215 +++++++++++++++++++++++++++++++++++++ apps/runplus/metadata.json | 26 +++-- apps/runplus/settings.js | 30 +++++- 7 files changed, 352 insertions(+), 42 deletions(-) create mode 100644 apps/runplus/karvonen.js diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog index e79696c78..ab2803ec6 100644 --- a/apps/run/ChangeLog +++ b/apps/run/ChangeLog @@ -14,4 +14,4 @@ 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) 0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working 0.15: Keep run state between runs (allowing you to exit and restart the app) -0.16: Added ability to resume a run that was stopped previously (fix #1907) \ No newline at end of file +0.16: Added ability to resume a run that was stopped previously (fix #1907) diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog index e79696c78..05d24b96d 100644 --- a/apps/runplus/ChangeLog +++ b/apps/runplus/ChangeLog @@ -14,4 +14,11 @@ 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) 0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working 0.15: Keep run state between runs (allowing you to exit and restart the app) -0.16: Added ability to resume a run that was stopped previously (fix #1907) \ No newline at end of file +0.16: Added ability to resume a run that was stopped previously (fix #1907) +0.17: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher) +0.18: Don't clear zone 2b indicator segment when updating HRM reading. +Write to correct settings file, fixing settings not working. +0.19: Fix typo in variable name preventing starting a run +0.20: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix + another typo. +0.21: Rebase on "Run" app ver. 0.16. diff --git a/apps/runplus/README.md b/apps/runplus/README.md index 7f645b518..659cd964d 100644 --- a/apps/runplus/README.md +++ b/apps/runplus/README.md @@ -67,3 +67,10 @@ app loader, the module is automatically included in the app's source. However when developing via the IDE the module won't get pulled in by default. There are some options to fix this easily - please check out the [modules README.md file](https://github.com/espruino/BangleApps/blob/master/modules/README.md) +## Contributors (Run and Run+) +gfwilliams +hughbarney +GrandVizierOlaf +BartS23 +f-teacher +thyttan diff --git a/apps/runplus/app.js b/apps/runplus/app.js index 507e8581a..41fab7ae2 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -1,16 +1,23 @@ -var ExStats = require("exstats"); -var B2 = process.env.HWVERSION===2; -var Layout = require("Layout"); -var locale = require("locale"); -var fontHeading = "6x8:2"; -var fontValue = B2 ? "6x15:2" : "6x8:3"; -var headingCol = "#888"; -var fixCount = 0; -var isMenuDisplayed = false; +// Use widget utils to show/hide widgets +let wu = require("widget_utils"); -g.clear(); +let runInterval; +let karvonenActive = false; +// Run interface wrapped in a function +let ExStats = require("exstats"); +let B2 = process.env.HWVERSION===2; +let Layout = require("Layout"); +let locale = require("locale"); +let fontHeading = "6x8:2"; +let fontValue = B2 ? "6x15:2" : "6x8:3"; +let headingCol = "#888"; +let fixCount = 0; +let isMenuDisplayed = false; + +g.reset().clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); +wu.show(); // --------------------------- let settings = Object.assign({ @@ -36,9 +43,13 @@ let settings = Object.assign({ notifications: [], }, }, -}, require("Storage").readJSON("run.json", 1) || {}); -var statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!==""); -var exs = ExStats.getStats(statIDs, settings); + HRM: { + min: 55, + max: 185, + }, +}, require("Storage").readJSON("runplus.json", 1) || {}); +let statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!==""); +let exs = ExStats.getStats(statIDs, settings); // --------------------------- function setStatus(running) { @@ -100,11 +111,11 @@ function onStartStop() { }); } -var lc = []; +let lc = []; // Load stats in pair by pair -for (var i=0;i{if (karvonenActive) {stopKarvonenUI();run();} onStartStop();}), id:"button"}]}); delete lc; setStatus(exs.state.active); layout.render(); @@ -132,16 +143,16 @@ layout.render(); function configureNotification(stat) { stat.on('notify', (e)=>{ settings.notify[e.id].notifications.reduce(function (promise, buzzPattern) { - return promise.then(function () { - return Bangle.buzz(buzzPattern[0], buzzPattern[1]); - }); + return promise.then(function () { + return Bangle.buzz(buzzPattern[0], buzzPattern[1]); + }); }, Promise.resolve()); }); } Object.keys(settings.notify).forEach((statType) => { if (settings.notify[statType].increment > 0 && exs.stats[statType]) { - configureNotification(exs.stats[statType]); + configureNotification(exs.stats[statType]); } }); @@ -153,8 +164,46 @@ Bangle.on("GPS", function(fix) { Bangle.buzz(); // first fix, does not need to respect quiet mode } }); -// We always call ourselves once a second to update -setInterval(function() { - layout.clock.label = locale.time(new Date(),1); - if (!isMenuDisplayed) layout.render(); -}, 1000); + +// run() function used to switch between traditional run UI and karvonen UI +function run() { + wu.show(); + layout.lazy = false; + layout.render(); + layout.lazy = true; + // We always call ourselves once a second to update + if (!runInterval){ + runInterval = setInterval(function() { + layout.clock.label = locale.time(new Date(),1); + if (!isMenuDisplayed && !karvonenActive) layout.render(); + }, 1000); + } +} +run(); + +/////////////////////////////////////////////// +// Karvonen +/////////////////////////////////////////////// + +function stopRunUI() { + // stop updating and drawing the traditional run app UI + clearInterval(runInterval); + runInterval = undefined; + karvonenActive = true; +} + +function stopKarvonenUI() { + g.reset().clear(); + clearInterval(karvonenInterval); + karvonenInterval = undefined; + karvonenActive = false; +} + +let karvonenInterval; +// Define the function to go back and forth between the different UI's +function swipeHandler(LR,_) { + if (LR==-1 && karvonenActive && !isMenuDisplayed) {stopKarvonenUI(); run();} + if (LR==1 && !karvonenActive && !isMenuDisplayed) {stopRunUI(); karvonenInterval = eval(require("Storage").read("runplus_karvonen"))(settings.HRM, exs.stats.bpm);} +} +// Listen for swipes with the swipeHandler +Bangle.on("swipe", swipeHandler); diff --git a/apps/runplus/karvonen.js b/apps/runplus/karvonen.js new file mode 100644 index 000000000..de81494bb --- /dev/null +++ b/apps/runplus/karvonen.js @@ -0,0 +1,215 @@ +(function karvonen(hrmSettings, exsHrmStats) { + //This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+ + //The calculation of the Heart Rate Zones is based on the Karvonen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab. + //Other methods are even more approximative. + let wu = require("widget_utils"); + wu.hide(); + let R = Bangle.appRect; + + + g.reset().clearRect(R).setFontAlign(0,0,0); + + const x = "x"; const y = "y"; + function Rdiv(axis, divisor) { // Used when placing things on the screen + return axis=="x" ? (R.x + (R.w-1)/divisor):(R.y + (R.h-1)/divisor); + } + let linePoints = { //Not lists of points, but used to update points in the drawArrows function. + x: [ + 175/40, + 2, + 175/135, + ], + y: [ + 175/64, + 175/52, + 175/110, + 175/122, + ], + + }; + + function drawArrows() { + g.setColor(g.theme.fg); + // Upper + g.drawLine(Rdiv(x,linePoints.x[0]), Rdiv(y,linePoints.y[0]), Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[1])); + g.drawLine(Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[1]), Rdiv(x,linePoints.x[2]), Rdiv(y,linePoints.y[0])); + // Lower + g.drawLine(Rdiv(x,linePoints.x[0]), Rdiv(y,linePoints.y[2]), Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[3])); + g.drawLine(Rdiv(x,linePoints.x[1]), Rdiv(y,linePoints.y[3]), Rdiv(x,linePoints.x[2]), Rdiv(y,linePoints.y[2])); + } + + //To calculate Heart rate zones, we need to know the heart rate reserve (HRR) + // HRR = maximum HR - Minimum HR. minhr is minimum hr, maxhr is maximum hr. + //get the hrr (heart rate reserve). + // I put random data here, but this has to come as a menu in the settings section so that users can change it. + let minhr = hrmSettings.min; + let maxhr = hrmSettings.max; + + function calculatehrr(minhr, maxhr) { + return maxhr - minhr;} + + //test input for hrr (it works). + let hrr = calculatehrr(minhr, maxhr); + console.log(hrr); + + //Test input to verify the zones work. The following value for "hr" has to be deleted and replaced with the Heart Rate Monitor input. + let hr = exsHrmStats.getValue(); + let hr1 = hr; + // These letiables display next and previous HR zone. + //get the hrzones right. The calculation of the Heart rate zones here is based on the Karvonen method + //60-70% of HRR+minHR = zone2. //70-80% of HRR+minHR = zone3. //80-90% of HRR+minHR = zone4. //90-99% of HRR+minHR = zone5. //=>99% of HRR+minHR = serious risk of heart attack + let minzone2 = hrr * 0.6 + minhr; + let maxzone2 = hrr * 0.7 + minhr; + let maxzone3 = hrr * 0.8 + minhr; + let maxzone4 = hrr * 0.9 + minhr; + let maxzone5 = hrr * 0.99 + minhr; + + // HR data: large, readable, in the middle of the screen + function drawHR() { + g.setFontAlign(-1,0,0); + g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2-14,Rdiv(y,2)+25); + g.setColor(g.theme.fg); + g.setFont("Vector",50); + g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4); + } + + function drawWaitHR() { + g.setColor(g.theme.fg); + // Waiting for HRM + g.setFontAlign(0,0,0); + g.setFont("Vector",50); + g.drawString("--", Rdiv(x,2)+4, Rdiv(y,2)+4); + + // Waiting for current Zone + g.setFont("Vector",24); + g.drawString("Z-", Rdiv(x,4.3)-3, Rdiv(y,2)+2); + + // waiting for upper and lower limit of current zone + g.setFont("Vector",20); + g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/2)); + g.drawString("--", Rdiv(x,2)+2, Rdiv(y,9/7)); + } + + //These functions call arcs to show different HR zones. + + //To shorten the code, I'll reference some letiables and reuse them. + let centreX = R.x + 0.5 * R.w; + let centreY = R.y + 0.5 * R.h; + let minRadius = 0.38 * R.h; + let maxRadius = 0.50 * R.h; + + //draw background image (dithered green zones)(I should draw different zones in different dithered colors) + const HRzones= require("graphics_utils"); + let minRadiusz = 0.44 * R.h; + let startAngle = HRzones.degreesToRadians(-88.5); + let endAngle = HRzones.degreesToRadians(268.5); + + function drawBgArc() { + g.setColor(g.theme.dark==false?0xC618:"#002200"); + HRzones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); + } + + const zones = require("graphics_utils"); + //####### A function to simplify a bit the code ###### + function simplify (sA, eA, Z, currentZone, lastZone) { + let startAngle = zones.degreesToRadians(sA); + let endAngle = zones.degreesToRadians(eA); + if (currentZone == lastZone) zones.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle, endAngle); + else zones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); + g.setFont("Vector",24); + g.clearRect(Rdiv(x,4.3)-12, Rdiv(y,2)+2-12,Rdiv(x,4.3)+12, Rdiv(y,2)+2+12); + g.setFontAlign(0,0,0); + g.drawString(Z, Rdiv(x,4.3), Rdiv(y,2)+2); + } + + function zoning (max, min) { // draw values of upper and lower limit of current zone + g.setFont("Vector",20); + g.setColor(g.theme.fg); + g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/2)-10,Rdiv(x,2)+20*2, Rdiv(y,9/2)+10); + g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/7)-10,Rdiv(x,2)+20*2, Rdiv(y,9/7)+10); + g.setFontAlign(0,0,0); + g.drawString(max, Rdiv(x,2), Rdiv(y,9/2)); + g.drawString(min, Rdiv(x,2), Rdiv(y,9/7)); + } + + function clearCurrentZone() { // Clears the extension of the current zone by painting the extension area in background color + g.setColor(g.theme.bg); + HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle); + } + + function getZone(zone) { + drawBgArc(); + clearCurrentZone(); + if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);} + if (zone >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);} + if (zone >= 2) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-20, 1.5, "Z2", 2, zone);} + if (zone >= 3) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(3, 24, "Z2", 3, zone);} + if (zone >= 4) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(25.5, 46.5, "Z3", 4, zone);} + if (zone >= 5) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(48, 69, "Z3", 5, zone);} + if (zone >= 6) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(70.5, 91.5, "Z3", 6, zone);} + if (zone >= 7) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(93, 114.5, "Z4", 7, zone);} + if (zone >= 8) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(116, 137.5, "Z4", 8, zone);} + if (zone >= 9) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(139, 160, "Z4", 9, zone);} + if (zone >= 10) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(161.5, 182.5, "Z5", 10, zone);} + if (zone >= 11) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(184, 205, "Z5", 11, zone);} + if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} + } + + function getZoneAlert() { + const HRzonemax = require("graphics_utils"); + let centreX1,centreY1,maxRadius1 = 1; + let minRadius = 0.40 * R.h; + let startAngle1 = HRzonemax.degreesToRadians(-90); + let endAngle1 = HRzonemax.degreesToRadians(270); + g.setFont("Vector",38);g.setColor("#ff0000"); + HRzonemax.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle1, endAngle1); + g.drawString("ALERT", 26,66); + } + + //Subdivided zones for better readability of zones when calling the images. //Changing HR zones will trigger the function with the image and previous&next HR zones. + let subZoneLast; + function drawZones() { + if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawArrows(); drawHR();} // Reset UI when coming down from zone alert. + if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} // Z1 + else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} // Z2a + else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} // Z2b + else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} // Z2c + else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} // Z3a + else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} // Z3b + else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} // Z3c + else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} // Z4a + else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} // Z4b + else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} // Z4c + else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} // Z5a + else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} // Z5b + else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} // Z5c + else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert + } + + function initDraw() { + drawArrows(); + if (hr!=0) updateUI(true); else {drawWaitHR(); drawBgArc();} + //drawZones(); + } + + let hrLast; + //h = 0; // Used to force hr update via web ui console field to trigger draws, together with `if (h!=0) hr = h;` below. + function updateUI(resetHrLast) { // Update UI, only draw if warranted by change in HR. + hrLast = resetHrLast?0:hr; // Handles correct updating on init depending on if we've got HRM readings yet or not. + hr = exsHrmStats.getValue(); + //if (h!=0) hr = h; + if (hr!=hrLast) { + drawHR(); + drawZones(); + } //g.setColor(g.theme.fg).drawLine(175/2,0,175/2,175).drawLine(0,175/2,175,175/2); // Used to align UI elements. + } + + initDraw(); + + // check for updates every second. + karvonenInterval = setInterval(function() { + if (!isMenuDisplayed && karvonenActive) updateUI(); + }, 1000); + + return karvonenInterval; +}) diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json index ed253a319..40256e595 100644 --- a/apps/runplus/metadata.json +++ b/apps/runplus/metadata.json @@ -1,16 +1,20 @@ -{ "id": "run", - "name": "Run", - "version":"0.16", - "description": "Displays distance, time, steps, cadence, pace and more for runners.", +{ + "id": "runplus", + "name": "Run+", + "version": "0.21", + "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.", "icon": "app.png", - "tags": "run,running,fitness,outdoors,gps", - "supports" : ["BANGLEJS","BANGLEJS2"], - "screenshots": [{"url":"screenshot.png"}], + "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", + "supports": ["BANGLEJS2"], + "screenshots": [{"url": "screenshot.png"}], "readme": "README.md", "storage": [ - {"name":"run.app.js","url":"app.js"}, - {"name":"run.img","url":"app-icon.js","evaluate":true}, - {"name":"run.settings.js","url":"settings.js"} + {"name": "runplus.app.js", "url": "app.js"}, + {"name": "runplus.img", "url": "app-icon.js", "evaluate": true}, + {"name": "runplus.settings.js", "url": "settings.js"}, + {"name": "runplus_karvonen", "url": "karvonen.js"} ], - "data": [{"name":"run.json"}] + "data": [ + {"name": "runplus.json"} + ] } diff --git a/apps/runplus/settings.js b/apps/runplus/settings.js index 0312200a3..539391a27 100644 --- a/apps/runplus/settings.js +++ b/apps/runplus/settings.js @@ -1,5 +1,5 @@ (function(back) { - const SETTINGS_FILE = "run.json"; + const SETTINGS_FILE = "runplus.json"; var ExStats = require("exstats"); var statsList = ExStats.getList(); statsList.unshift({name:"-",id:""}); // add blank menu item @@ -31,6 +31,10 @@ notifications: [], }, }, + HRM: { + min: 55, + max: 185, + }, }, storage.readJSON(SETTINGS_FILE, 1) || {}); function saveSettings() { storage.write(SETTINGS_FILE, settings) @@ -125,5 +129,29 @@ 'Box 6': getBoxChooser("B6"), }); menu[/*LANG*/"Boxes"] = function() { E.showMenu(boxMenu)}; + + var hrmMenu = { + '< Back': function() { E.showMenu(menu) }, + } + + menu[/*LANG*/"HRM min/max"] = function() { E.showMenu(hrmMenu)}; + hrmMenu[/*LANG*/"min"] = { + min: 1, max: 100, + value: settings.HRM.min, + format: w => w, + onchange: w => { + settings.HRM.min = w; + saveSettings(); + }, + } + hrmMenu[/*LANG*/"max"] = { + min: 101, max: 220, + value: settings.HRM.max, + format: v => v, + onchange: v => { + settings.HRM.max = v; + saveSettings(); + }, + } E.showMenu(menu); }) From 7c647add8b666fdca52f9f5128482268e93e37e9 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 2 Jun 2023 21:45:51 +0100 Subject: [PATCH 043/116] kbmulti: update readme --- apps/kbmulti/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kbmulti/README.md b/apps/kbmulti/README.md index b6754711d..88b91fa80 100644 --- a/apps/kbmulti/README.md +++ b/apps/kbmulti/README.md @@ -12,6 +12,6 @@ Uses the multitap keypad logic originally from here: http://www.espruino.com/Mor ![](screenshot_2.png) ![](screenshot_3.png) -Written by: [Sir Indy](https://github.com/sir-indy) and [Thyttan](https://github.com/thyttan) +Written by: [Sir Indy](https://github.com/sir-indy), [Thyttan](https://github.com/thyttan) and [bobrippling](https://github.com/bobrippling). For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) From 1a82f314cefd3fef8c8152f54aedce98a878d226 Mon Sep 17 00:00:00 2001 From: novadawn999 Date: Sun, 4 Jun 2023 15:38:28 -0500 Subject: [PATCH 044/116] added 13 new chords support for fret offsets more formatting refinements --- apps/guitar/ChangeLog | 1 + apps/guitar/app.js | 241 +++++++++++++++++++++++++++++++++----- apps/guitar/metadata.json | 2 +- 3 files changed, 213 insertions(+), 31 deletions(-) diff --git a/apps/guitar/ChangeLog b/apps/guitar/ChangeLog index 5560f00bc..22c67383d 100644 --- a/apps/guitar/ChangeLog +++ b/apps/guitar/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: More Chords, formatting, fret offset support. \ No newline at end of file diff --git a/apps/guitar/app.js b/apps/guitar/app.js index d48d9009a..6c172f920 100644 --- a/apps/guitar/app.js +++ b/apps/guitar/app.js @@ -2,10 +2,9 @@ const stringInterval = 24; const stringLength = 138; const fretHeight = 35; const fingerOffset = 17; -const x = 30; -const y = 32; +const xOffset = 26; +const yOffset = 34; -//chords const cc = [ "C", "0X", @@ -13,7 +12,8 @@ const cc = [ "22", "x", "11", - "x" + "x", + "0" ]; const dd = [ @@ -23,7 +23,8 @@ const dd = [ "x", "21", "33", - "22" + "22", + "0" ]; const gg = [ @@ -33,16 +34,19 @@ const gg = [ "x", "x", "x", - "33" + "33", + "0" ]; const am = [ "Am", "0x", "x", - "22", "23", - "11" + "22", + "11", + "x", + "0" ]; const em = [ @@ -52,7 +56,8 @@ const em = [ "23", "x", "x", - "x" + "x", + "0" ]; const aa = [ @@ -62,17 +67,8 @@ const aa = [ "21", "22", "23", - "x" -]; - -const ff = [ - "F", - "0X", - "33", - "34", - "22", - "11", - "11" + "x", + "0" ]; var ee = [ @@ -82,9 +78,177 @@ var ee = [ "23", "11", "x", - "x" + "x", + "0" ]; +var dm = [ + "Dm", + "0x", + "0x", + "x", + "22", + "33", + "11", + "0" +]; + +var ff = [ + "F", + "0x", + "0x", + "33", + "22", + "11", + "11", + "0" +]; + +var b7 = [ + "B7", + "0x", + "22", + "11", + "23", + "x", + "24", + "0" +]; + +var cadd9 = [ + "Cadd9", + "0x", + "32", + "21", + "x", + "33", + "34", + "0" +]; + +var dadd11 = [ + "Dadd11", + "0x", + "33", + "22", + "x", + "11", + "x", + "3" +]; + +var csus2 = [ + "Csus2", + "0x", + "33", + "x", + "x", + "11", + "0x", + "0" +]; + +var gadd9 = [ + "Gadd9", + "32", + "0x", + "x", + "21", + "x", + "33", + "0" +]; + +var aadd9 = [ + "Aadd9", + "11", + "33", + "34", + "22", + "x", + "x", + "5" +]; + +var fsharp7add11 = [ + "F#7add11", + "21", + "43", + "44", + "32", + "x", + "x", + "0" +]; + +var d9 = [ + "D9", + "0x", + "22", + "11", + "23", + "23", + "0x", + "4" +]; + +var g7 = [ + "G7", + "33", + "22", + "x", + "x", + "34", + "11", + "0" +]; + +var bflatd = [ + "Bb/D", + "0x", + "33", + "11", + "11", + "11", + "0x", + "3" +]; + +var e7sharp9 = [ + "E7#9", + "0x", + "22", + "11", + "23", + "34", + "0x", + "6" +]; + +var a11 = [ + "A11 3rd fret", + "33", + "0x", + "34", + "22", + "11", + "0x", + "0" +]; + +var a9 = [ + "A9", + "32", + "0x", + "33", + "21", + "34", + "0x", + "3" +]; + + + var index = 0; var chords = []; var menu = { @@ -99,6 +263,20 @@ var menu = { "Am" : function() { draw(am); }, "F" : function() { draw(ff); }, "G" : function() { draw(gg); }, + "Dm" : function() { draw(dm); }, + "B7" : function () { draw(b7); }, + "Cadd9" : function () { draw(cadd9); }, + "Dadd11" : function () { draw(dadd11); }, + "Csus2" : function () { draw(csus2); }, + "Gadd9" : function () { draw(gadd9); }, + "Aadd9" : function () { draw(aadd9); }, + "F#7add11" : function () { draw(fsharp7add11); }, + "D9" : function () { draw(d9); }, + "G7" : function () { draw(g7); }, + "Bb/D" : function () { draw(bflatd); }, + "E7#9" : function () { draw(e7sharp9); }, + "A11" : function () { draw(a11); }, + "A9" : function () { draw(a9); }, "About" : function() { E.showMessage( "Created By:\nNovaDawn999", { @@ -112,26 +290,29 @@ var menu = { function drawBase() { for (let i = 0; i < 6; i++) { - g.drawLine(x + i * stringInterval, y, x + i * stringInterval, y + stringLength); - g.fillRect(x- 1, y + i * fretHeight - 1, x + stringInterval * 5 + 1, y + i * fretHeight + 1); + g.drawLine(xOffset + i * stringInterval, yOffset, xOffset + i * stringInterval, yOffset + stringLength); + g.fillRect(xOffset- 1, yOffset + i * fretHeight - 1, xOffset + stringInterval * 5 + 1, yOffset + i * fretHeight + 1); } } function drawChord(chord) { - g.drawString(chord[0], g.getWidth() * 0.5 - 3, 18); - for (let i = 0; i < chord.length; i++) { + g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16); + for (let i = 0; i < chord.length - 1; i++) { if (i === 0 || chord[i][0] === "x") { continue; } if (chord[i][0] === "0") { - g.drawString(chord[i][1], x + (i - 1) * stringInterval - 5, y + fretHeight * chord[i][0] + 2, true); - g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 10); + g.drawString(chord[i][1], xOffset + (i - 1) * stringInterval - 5, yOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(xOffset + (i - 1) * stringInterval -1, yOffset + fretHeight * chord[i][0], 10); } else { - g.drawString(chord[i][1], x + (i - 1) * stringInterval -5, y -fingerOffset + fretHeight * chord[i][0] + 2, true); - g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 10); + g.drawString(chord[i][1], xOffset + (i - 1) * stringInterval -5, yOffset -fingerOffset + fretHeight * chord[i][0] + 2, true); + g.drawCircle(xOffset + (i - 1) * stringInterval -1, yOffset -fingerOffset + fretHeight * chord[i][0], 10); } } + if (chord[7] !== "0") { + g.drawString(chord[7], 9, 50); + } } function buttonPress() { @@ -156,4 +337,4 @@ function main() { }, BTN); } -main(); +main(); \ No newline at end of file diff --git a/apps/guitar/metadata.json b/apps/guitar/metadata.json index 8f98ce44e..6ab3ffc51 100644 --- a/apps/guitar/metadata.json +++ b/apps/guitar/metadata.json @@ -1,7 +1,7 @@ { "id": "guitar", "name": "Guitar Chords", "shortName":"Guitar", - "version":"0.01", + "version":"0.02", "description": "Wrist mounted guitar chords", "icon": "app.png", "tags": "guitar, chords", From e21fc018b43367e9d5dbfaa872cb6ce656e1b5cd Mon Sep 17 00:00:00 2001 From: novadawn999 Date: Sun, 4 Jun 2023 16:26:35 -0500 Subject: [PATCH 045/116] added 13 more chords --- apps/Uke/ChangeLog | 1 + apps/Uke/app.js | 235 ++++++++++++++++++++++++++++++----------- apps/Uke/metadata.json | 2 +- 3 files changed, 178 insertions(+), 60 deletions(-) diff --git a/apps/Uke/ChangeLog b/apps/Uke/ChangeLog index 366158b0e..ef5ffa3fe 100644 --- a/apps/Uke/ChangeLog +++ b/apps/Uke/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Increased Legibility, GUI rework +0.03: 13 new chords diff --git a/apps/Uke/app.js b/apps/Uke/app.js index 3c28381c0..411a39b70 100644 --- a/apps/Uke/app.js +++ b/apps/Uke/app.js @@ -16,52 +16,12 @@ const cc = [ const dd = [ "D", + "22", "23", - "22", "24", "x" ]; -const gg = [ - "G", - "x", - "21", - "33", - "22", -]; - -const am = [ - "Am", - "22", - "x", - "x", - "x" -]; - -const em = [ - "Em", - "x", - "43", - "32", - "21" -]; - -const aa = [ - "A", - "22", - "11", - "x", - "x" -]; - -const ff = [ - "F", - "22", - "x", - "11", - "x" -]; - var ee = [ "E", "33", @@ -70,27 +30,184 @@ var ee = [ "11" ]; +const ff = [ + "F", + "22", + "x", + "11", + "x" +]; + +const gg = [ + "G", + "x", + "21", + "33", + "22", +]; + +const aa = [ + "A", + "22", + "11", + "x", + "x" +]; + +const bb = [ + "B", + "42", + "43", + "44", + "21" +]; + +const cm = [ + "Cm", + "11", + "x", + "12", + "34" +]; + +const dm = [ + "Dm", + "x", + "22", + "33", + "11" +]; + +const em = [ + "Em", + "x", + "43", + "32", + "21" +]; + +const fm = [ + "Fm", + "33", + "11", + "11", + "11" +]; + +const gm = [ + "Gm", + "x", + "22", + "33", + "11" +]; + +const am = [ + "Am", + "22", + "23", + "11", + "x" +]; + +const bm = [ + "Bm", + "x", + "43", + "32", + "21" +]; + +const c7 = [ + "C7", + "22", + "33", + "11", + "x" +]; + +const d7 = [ + "D7", + "x", + "22", + "11", + "23" +]; + +const e7 = [ + "E7", + "x", + "11", + "x", + "x" +]; + +const f7 = [ + "F7", + "11", + "22", + "11", + "11" +]; + +const g7 = [ + "G7", + "x", + "x", + "x", + "11" +]; + +const a7 = [ + "A7", + "21", + "21", + "21", + "32" +]; + +const b7 = [ + "B7", + "11", + "22", + "x", + "23" +]; + + + var index = 0; var chords = []; var menu = { - "" : { - "title" : "Uke Chords" - }, - "C" : function() { draw(cc); }, - "D" : function() { draw(dd); }, - "E" : function() { draw(ee); }, - "Em" : function() { draw(em); }, - "A" : function() { draw(aa); }, - "Am" : function() { draw(am); }, - "F" : function() { draw(ff); }, - "G" : function() { draw(gg); }, - "About" : function() { - E.showMessage( - "Created By:\nNovaDawn999", { - title:"About" - } - ); - } + "" : { "title" : "Uke Chords" }, + "C" : function() { draw(cc); }, + "D" : function() { draw(dd); }, + "E" : function() { draw(ee); }, + "F" : function() { draw(ff); }, + "G" : function() { draw(gg); }, + "A" : function() { draw(aa); }, + "B" : function() { draw(bb); }, + "C7" : function() { draw(c7); }, + "D7" : function() { draw(d7); }, + "E7" : function() { draw(e7); }, + "F7" : function() { draw(f7); }, + "G7" : function() { draw(g7); }, + "A7" : function() { draw(a7); }, + "B7" : function() { draw(b7); }, + "Cm" : function() { draw(cm); }, + "Dm" : function() { draw(dm); }, + "Em" : function() { draw(em); }, + "Fm" : function() { draw(fm); }, + "Gm" : function() { draw(gm); }, + "Am" : function() { draw(am); }, + "Bm" : function() { draw(bm); }, + "About" : function() { + E.showMessage( + "Created By:\nNovaDawn999", { + title:"About" + } + ); + } }; diff --git a/apps/Uke/metadata.json b/apps/Uke/metadata.json index 8d92718c3..ef31e3663 100644 --- a/apps/Uke/metadata.json +++ b/apps/Uke/metadata.json @@ -1,7 +1,7 @@ { "id": "Uke", "name": "Uke Chords", "shortName":"Uke", - "version":"0.02", + "version":"0.03", "description": "Wrist mounted ukulele chords", "icon": "app.png", "tags": "uke, chords", From 14a4dfcd968aab7bd30b5934a380257cf93329fb Mon Sep 17 00:00:00 2001 From: novadawn999 Date: Sun, 4 Jun 2023 16:45:23 -0500 Subject: [PATCH 046/116] bugfix --- apps/Uke/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/Uke/app.js b/apps/Uke/app.js index 411a39b70..095477f3f 100644 --- a/apps/Uke/app.js +++ b/apps/Uke/app.js @@ -220,7 +220,7 @@ function drawBase() { } function drawChord(chord) { - g.drawString(chord[0], g.getWidth() * 0.5 - 3, 18); + g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16); for (let i = 0; i < chord.length; i++) { if (i === 0 || chord[i][0] === "x") { continue; From 2f0f5e8dd7dea229c211487075aa54fbd796ed4a Mon Sep 17 00:00:00 2001 From: stweedo Date: Mon, 5 Jun 2023 00:31:08 -0500 Subject: [PATCH 047/116] [rescalc] - bugfix with decimal values to colors --- apps/rescalc/app.js | 49 ++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/apps/rescalc/app.js b/apps/rescalc/app.js index 1986ac2db..6805589b2 100644 --- a/apps/rescalc/app.js +++ b/apps/rescalc/app.js @@ -36,25 +36,35 @@ function colorBandsToResistance(colorBands) { } function resistanceToColorBands(resistance, tolerance) { - let resistanceStr = resistance.toString(); let firstDigit, secondDigit, multiplier; - if (resistanceStr.length === 1) { // Check if resistance is a single digit - firstDigit = 0; - secondDigit = Number(resistanceStr.charAt(0)); - multiplier = 0; - } else if (resistance >= 100) { - // Extract the first two digits from the resistance value - firstDigit = Number(resistanceStr.charAt(0)); - secondDigit = Number(resistanceStr.charAt(1)); - // Calculate the multiplier - multiplier = resistanceStr.length - 2; + if (resistance < 1) { + // The resistance is less than 1, so we need to handle this case specially + let count = 0; + while (resistance < 1) { + resistance *= 10; + count++; + } + // Now, resistance is a whole number and count is how many times we had to multiply by 10 + let resistanceStr = resistance.toString(); + firstDigit = 0; // Set the first band color to be black + secondDigit = Number(resistanceStr.charAt(0)); // Set the second band color to be the significant digit + // Use count to determine the multiplier + multiplier = count === 1 ? 0.1 : 0.01; } else { - // For values between 10-99, shift the color to the first band - firstDigit = Number(resistanceStr.charAt(0)); - secondDigit = Number(resistanceStr.charAt(1)); - multiplier = 0; + // Convert the resistance to a string so we can manipulate it easily + let resistanceStr = resistance.toString(); + if (resistanceStr.length === 1) { // Check if resistance is a single digit + firstDigit = 0; + secondDigit = Number(resistanceStr.charAt(0)); + multiplier = 1; // Set multiplier to 1 for single digit resistance values + } else { + // Extract the first two digits from the resistance value + firstDigit = Number(resistanceStr.charAt(0)); + secondDigit = Number(resistanceStr.charAt(1)); + // Calculate the multiplier by matching it directly with the length of digits + multiplier = resistanceStr.length - 2 >= 0 ? Math.pow(10, resistanceStr.length - 2) : Math.pow(10, resistanceStr.length - 1); + } } - let firstBandEntry = Object.entries(colorData).find(function(entry) { return entry[1].value === firstDigit; }); @@ -64,7 +74,7 @@ function resistanceToColorBands(resistance, tolerance) { }); let secondBand = secondBandEntry ? secondBandEntry[1].hex : undefined; let multiplierBandEntry = Object.entries(colorData).find(function(entry) { - return entry[1].multiplier === Math.pow(10, multiplier); + return entry[1].multiplier === multiplier; }); let multiplierBand = multiplierBandEntry ? multiplierBandEntry[1].hex : undefined; let toleranceBandEntry = Object.entries(colorData).find(function(entry) { @@ -352,7 +362,7 @@ function drawResistance(resistance, tolerance) { E.showMenu(toleranceMenu); } - function drawResistorAndResistance(resistance, tolerance) { + function drawResistorAndResistance(resistance, tolerance) { if (inputColorBands) { colorBands = inputColorBands.map(color => { if (colorData.hasOwnProperty(color)) { @@ -362,8 +372,11 @@ function drawResistance(resistance, tolerance) { } }); } else { + console.log("Using colorBands = resistanceToColorBands(resistance, tolerance)" + "\nResistance: " + resistance + "\nTolerance: " + tolerance); colorBands = resistanceToColorBands(resistance, tolerance); } + console.log(inputColorBands); + console.log(colorBands); drawResistor(colorBands, tolerance); drawResistance(resistance, tolerance); resetSettings(); From b62d8baf75e13f77f9892db11cd533cad7ad2ac6 Mon Sep 17 00:00:00 2001 From: stweedo Date: Mon, 5 Jun 2023 00:34:44 -0500 Subject: [PATCH 048/116] [rescalc] - Remove console logs and formatting --- apps/rescalc/app.js | 57 +++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/apps/rescalc/app.js b/apps/rescalc/app.js index 6805589b2..566809837 100644 --- a/apps/rescalc/app.js +++ b/apps/rescalc/app.js @@ -65,19 +65,19 @@ function resistanceToColorBands(resistance, tolerance) { multiplier = resistanceStr.length - 2 >= 0 ? Math.pow(10, resistanceStr.length - 2) : Math.pow(10, resistanceStr.length - 1); } } - let firstBandEntry = Object.entries(colorData).find(function(entry) { + let firstBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].value === firstDigit; }); let firstBand = firstBandEntry ? firstBandEntry[1].hex : undefined; - let secondBandEntry = Object.entries(colorData).find(function(entry) { + let secondBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].value === secondDigit; }); let secondBand = secondBandEntry ? secondBandEntry[1].hex : undefined; - let multiplierBandEntry = Object.entries(colorData).find(function(entry) { + let multiplierBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].multiplier === multiplier; }); let multiplierBand = multiplierBandEntry ? multiplierBandEntry[1].hex : undefined; - let toleranceBandEntry = Object.entries(colorData).find(function(entry) { + let toleranceBandEntry = Object.entries(colorData).find(function (entry) { return entry[1].tolerance === tolerance; }); let toleranceBand = toleranceBandEntry ? toleranceBandEntry[1].hex : undefined; @@ -175,7 +175,7 @@ function drawResistance(resistance, tolerance) { g.drawString(toleranceStr.padEnd(4), 176 - toleranceX, y); } -(function() { +(function () { let colorBands; let inputColorBands; let settings = { @@ -200,7 +200,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': `Band ${bandNumber}` }, - '< Back': function() { + '< Back': function () { E.showMenu(colorEntryMenu); }, }; @@ -209,24 +209,24 @@ function drawResistance(resistance, tolerance) { for (let color in colorData) { if (bandNumber === 1 || bandNumber === 2) { if (color !== 'none' && color !== 'gold' && color !== 'silver') { - (function(color) { - colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function() { + (function (color) { + colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function () { setBandColor(bandNumber, color); }; })(color); } } else if (bandNumber === 3) { if (color !== 'none') { - (function(color) { - colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function() { + (function (color) { + colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function () { setBandColor(bandNumber, color); }; })(color); } } else if (bandNumber === 4) { if (colorData[color].hasOwnProperty('tolerance')) { - (function(color) { - colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function() { + (function (color) { + colorBandMenu[color.charAt(0).toUpperCase() + color.slice(1)] = function () { setBandColor(bandNumber, color); }; })(color); @@ -248,7 +248,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Band Color' }, - '< Back': function() { + '< Back': function () { clearScreen(); E.showMenu(mainMenu); }, @@ -284,7 +284,7 @@ function drawResistance(resistance, tolerance) { setTimeout(() => showColorBandMenu(4), 5); } }, - 'Draw Resistor': function() { + 'Draw Resistor': function () { inputColorBands = settings.colorBands; let values = colorBandsToResistance(inputColorBands); settings.resistance = values[0]; @@ -301,7 +301,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Multiplier' }, - '< Back': function() { + '< Back': function () { showResistanceEntryMenu(); } }; @@ -314,7 +314,7 @@ function drawResistance(resistance, tolerance) { multiplierMenu[`${formattedMultiplier}`] = () => { settings.multiplier = multiplierValue; // Update the value of 'Multiplier' in resistanceEntryMenu - resistanceEntryMenu["Multiplier"] = function() { + resistanceEntryMenu["Multiplier"] = function () { showMultiplierMenu(); }; showResistanceEntryMenu(); @@ -340,7 +340,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Tolerance' }, - '< Back': function() { + '< Back': function () { showResistanceEntryMenu(); } }; @@ -352,7 +352,7 @@ function drawResistance(resistance, tolerance) { toleranceMenu[`${tolerance}%`] = () => { settings.tolerance = tolerance; // Update the value of 'Tolerance (%)' in resistanceEntryMenu - resistanceEntryMenu["Tolerance (%)"] = function() { + resistanceEntryMenu["Tolerance (%)"] = function () { showToleranceMenu(); }; showResistanceEntryMenu(); @@ -362,7 +362,7 @@ function drawResistance(resistance, tolerance) { E.showMenu(toleranceMenu); } - function drawResistorAndResistance(resistance, tolerance) { + function drawResistorAndResistance(resistance, tolerance) { if (inputColorBands) { colorBands = inputColorBands.map(color => { if (colorData.hasOwnProperty(color)) { @@ -372,11 +372,8 @@ function drawResistance(resistance, tolerance) { } }); } else { - console.log("Using colorBands = resistanceToColorBands(resistance, tolerance)" + "\nResistance: " + resistance + "\nTolerance: " + tolerance); colorBands = resistanceToColorBands(resistance, tolerance); } - console.log(inputColorBands); - console.log(colorBands); drawResistor(colorBands, tolerance); drawResistance(resistance, tolerance); resetSettings(); @@ -386,7 +383,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': 'Resistance' }, - '< Back': function() { + '< Back': function () { clearScreen(); E.showMenu(mainMenu); }, @@ -396,15 +393,15 @@ function drawResistance(resistance, tolerance) { max: 99, wrap: true, format: v => '', - onchange: v => {} + onchange: v => { } }, - 'Multiplier': function() { + 'Multiplier': function () { showMultiplierMenu(); }, - 'Tolerance (%)': function() { + 'Tolerance (%)': function () { showToleranceMenu(); }, - 'Draw Resistor': function() { + 'Draw Resistor': function () { showDrawingMenu(); } }; @@ -433,7 +430,7 @@ function drawResistance(resistance, tolerance) { '': { 'title': '' }, - '< Back': function() { + '< Back': function () { clearScreen(); E.showMenu(mainMenu); }, @@ -449,11 +446,11 @@ function drawResistance(resistance, tolerance) { 'title': 'Resistor Calc' }, '< Back': () => Bangle.showClock(), // return to the clock app - 'Resistance': function() { + 'Resistance': function () { resetSettings(); showResistanceEntryMenu(); }, - 'Colors': function() { + 'Colors': function () { resetSettings(); showColorEntryMenu(); }, From a81c3678276d7df93e08913e50d1fa3610393209 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 5 Jun 2023 09:30:42 +0100 Subject: [PATCH 049/116] using filenames based on date --- apps/recorder/ChangeLog | 3 ++- apps/recorder/app.js | 24 ++++++------------------ apps/recorder/metadata.json | 2 +- apps/recorder/widget.js | 14 ++++++++------ 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 874e4699c..eedf353b7 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -29,4 +29,5 @@ 0.23: Add graphing for HRM, fix some other graphs Altitude graphing now uses barometer altitude if it exists plotTrack in widget allows track to be drawn in the background (doesn't block execution) -0.24: Can now specify `setRecording(true, {force:...` to not show a menu \ No newline at end of file +0.24: Can now specify `setRecording(true, {force:...` to not show a menu +0.25: Now record filename based on date \ No newline at end of file diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 9e9b58f78..ca3eec525 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -33,10 +33,8 @@ function updateSettings() { function getTrackNumber(filename) { var trackNum = 0; var matches = filename.match(/^recorder\.log(.*)\.csv$/); - if (matches) { - trackNum = parseInt(matches[1]||0); - } - return trackNum; + if (matches) return matches[1]; + return 0; } function showMainMenu() { @@ -62,23 +60,13 @@ function showMainMenu() { WIDGETS["recorder"].setRecording(v).then(function() { //print("Record start Complete"); loadSettings(); - print("Recording: "+settings.recording); + //print("Recording: "+settings.recording); showMainMenu(); }); }, 1); } }, - /*LANG*/'File #': { - value: getTrackNumber(settings.file), - min: 0, - max: 99, - step: 1, - onchange: v => { - settings.recording = false; // stop recording if we change anything - settings.file = "recorder.log"+v+".csv"; - updateSettings(); - } - }, + /*LANG*/'File' : {value:getTrackNumber(settings.file)}, /*LANG*/'View Tracks': ()=>{viewTracks();}, /*LANG*/'Time Period': { value: settings.period||10, @@ -110,7 +98,7 @@ function viewTracks() { var found = false; require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{ found = true; - menu[/*LANG*/"Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); + menu[/*LANG*/getTrackNumber(filename)] = ()=>viewTrack(filename,false); }); if (!found) menu[/*LANG*/"No Tracks found"] = function(){}; @@ -353,7 +341,7 @@ function viewTrack(filename, info) { infc[i]++; } } else if (style=="Altitude") { - title = /*LANG*/"Altitude (m)"; + title = /*LANG*/"Altitude (m)"; var altIdx = info.fields.indexOf("Barometer Altitude"); if (altIdx<0) altIdx = info.fields.indexOf("Altitude"); while(l!==undefined) { diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 99f1539c6..00c1c965e 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.24", + "version": "0.25", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index e57f293c7..609bc1d36 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -244,7 +244,7 @@ if (!options.force) { // if not forced, ask the question g.reset(); // work around bug in 2v17 and earlier where bg color wasn't reset return E.showPrompt( - /*LANG*/"Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?", + /*LANG*/"Overwrite\nLog " + settings.file.match(/^recorder\.log(.*)\.csv$/)[1] + "?", { title:/*LANG*/"Recorder", buttons:{/*LANG*/"Yes":"overwrite",/*LANG*/"No":"cancel",/*LANG*/"New":"new",/*LANG*/"Append":"append"} }).then(selection=>{ @@ -260,11 +260,13 @@ // wipe the file require("Storage").open(settings.file,"r").erase(); } else if (options.force=="new") { - // new file - find the max log file number and add one - var maxNumber=0; - require("Storage").list(/recorder.log.*/).forEach( fn => maxNumber = Math.max(maxNumber, fn.match(/\d+/)[0]) ); - var newFileName = "recorder.log" + (maxNumber + 1) + ".csv"; - // FIXME: use date? + // new file - use the current date + var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10; + var newFileName; + do { // while a file exists, add one to the letter after the date + newFileName = "recorder.log" + date + trackNo.toString(36) + ".csv"; + trackNo++; + } while (require("Storage").list(newFileName).length); settings.file = newFileName; } else throw new Error("Unknown options.force, "+options.force); } From 2f6a1b8bfafc1b42c82b1dde575d3b4bf999f20e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 5 Jun 2023 09:31:12 +0100 Subject: [PATCH 050/116] remind users about 2v18 now! --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 9b1b3ee84..77a186af7 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v17"; +var RECOMMENDED_VERSION = "2v18"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles From ed9d4d47c50878e0aea5497d0c934bd750ed1e5b Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 13:32:25 +0100 Subject: [PATCH 051/116] sched: interface.html, show timers & non-date alarms too --- apps/sched/interface.html | 74 +++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index f1ace7d0c..0b3c0d986 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -93,24 +93,53 @@ function upload() { } function renderAlarm(alarm, exists) { - const localDate = dateFromAlarm(alarm); + const localDate = alarm.date ? dateFromAlarm(alarm) : null; const tr = document.createElement('tr'); tr.classList.add('event-row'); tr.dataset.uid = alarm.id; - const tdTime = document.createElement('td'); - tr.appendChild(tdTime); + const tdType = document.createElement('td'); + tdType.type = "text"; + tdType.classList.add('event-summary'); + tr.appendChild(tdType); const inputTime = document.createElement('input'); - inputTime.type = "datetime-local"; + if (localDate) { + tdType.textContent = "Alarm"; + inputTime.type = "datetime-local"; + inputTime.value = localDate.toISOString().slice(0,16); + inputTime.onchange = (e => { + const date = new Date(inputTime.value); + alarm.t = dateToMsSinceMidnight(date); + alarm.date = formatDate(date); + }); + } else { + const [hours, mins, secs] = msToHMS(alarm.timer || alarm.t); + + inputTime.type = "time"; + inputTime.step = 1; // display seconds + inputTime.value = `${hours}:${mins}:${secs}`; + + if (alarm.timer) { + tdType.textContent = "Timer"; + inputTime.onchange = e => { + alarm.timer = hmsToMs(inputTime.value); + const now = new Date(); + const currentTime = (now.getHours()*3600000)+(now.getMinutes()*60000)+(now.getSeconds()*1000); + alarm.t = currentTime + alarm.timer; + }; + } else { + tdType.textContent = "Alarm"; + inputTime.onchange = e => { + alarm.t = hmsToMs(inputTime.value); + }; + } + } + if (!exists) tdType.textContent = "* " + tdType.textContent; inputTime.classList.add('event-date'); inputTime.classList.add('form-input'); inputTime.dataset.uid = alarm.id; - inputTime.value = localDate.toISOString().slice(0,16); - inputTime.onchange = (e => { - const date = new Date(inputTime.value); - alarm.t = dateToMsSinceMidnight(date); - alarm.date = formatDate(date); - }); + const tdTime = document.createElement('td'); + tr.appendChild(tdTime); tdTime.appendChild(inputTime); const tdSummary = document.createElement('td'); @@ -150,6 +179,24 @@ function renderAlarm(alarm, exists) { document.getElementById('upload').disabled = false; } +function msToHMS(ms) { + let secs = Math.floor(ms / 1000) % 60; + let mins = Math.floor(ms / 1000 / 60) % 60; + let hours = Math.floor(ms / 1000 / 60 / 60); + if (secs < 10) secs = "0" + secs; + if (mins < 10) mins = "0" + mins; + if (hours < 10) hours = "0" + hours; + return [hours, mins, secs]; +} + +function hmsToMs(hms) { + let [hours, mins, secs] = hms.split(":"); + hours = Number(hours); + mins = Number(mins); + secs = Number(secs); + return ((hours * 60 + mins) * 60 + secs) * 1000; +} + function addAlarm() { const alarm = getAlarmDefaults(); renderAlarm(alarm); @@ -165,9 +212,7 @@ function getData() { schedSettings = JSON.parse(data || "{}") || {}; Util.hideModal(); alarms.forEach(alarm => { - if (alarm.date) { - renderAlarm(alarm, true); - } + renderAlarm(alarm, true); }); }); }); @@ -191,7 +236,8 @@ function onInit() { - + + From 138a607a1b0f76428fa3120b8d0425eaf278beda Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 13:32:39 +0100 Subject: [PATCH 052/116] sched: interface.html, sort by type, then time --- apps/sched/interface.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 0b3c0d986..0896fbd4e 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -211,6 +211,17 @@ function getData() { Util.readStorage('sched.settings.json',data=>{ schedSettings = JSON.parse(data || "{}") || {}; Util.hideModal(); + alarms.sort((a, b) => { + let x; + + x = !!b.date - !!a.date; + if(x) return x; + + x = !!a.timer - !!b.timer; + if(x) return x; + + return a.t - b.t; + }); alarms.forEach(alarm => { renderAlarm(alarm, true); }); From bd416eb0df6b59a85b2602f7a381e54db78308b6 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 13:49:33 +0100 Subject: [PATCH 053/116] sched: interface.html can create timers & daily alarms --- apps/sched/interface.html | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 0896fbd4e..ee5303bf0 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -203,6 +203,29 @@ function addAlarm() { alarms.push(alarm); } +function addDailyAlarm() { + const alarm = getAlarmDefaults(); + delete alarm.date; + renderAlarm(alarm); + alarms.push(alarm); +} + +function addTimer() { + const alarmDefaults = getAlarmDefaults(); + const timer = { + timer: hmsToMs("00:00:30"), + on: false, + t: 0, + dow: alarmDefaults.dow, + last: alarmDefaults.last, + rp: alarmDefaults.rp, + vibrate: alarmDefaults.vibrate, + as: alarmDefaults.as, + };; + renderAlarm(timer); + alarms.push(timer); +} + function getData() { Util.showModal("Loading..."); Util.readStorage('sched.json',data=>{ @@ -241,6 +264,15 @@ function onInit() {
+ +
From 64a8f50b76348cc27be80a1c784a95dad44b44c4 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 13:58:06 +0100 Subject: [PATCH 054/116] sched: interface.html reloads watch to apply alarms --- apps/sched/interface.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index ee5303bf0..69d1a8bc9 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -88,7 +88,9 @@ function eventToAlarm(event, offsetMs) { function upload() { Util.showModal("Saving..."); Util.writeStorage("sched.json", JSON.stringify(alarms), () => { - location.reload(); // reload so we see current data + Puck.write(`\x10load()\n`, () => { // reload watch to load alarms + location.reload(); // reload so we see current data + }); }); } From 4d01587c107c1af6acd6d86154a4c2c35f0f2e04 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 14:09:23 +0100 Subject: [PATCH 055/116] sched: interface.html can turn alarms on/off --- apps/sched/interface.html | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 69d1a8bc9..0e3dad8d3 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -164,10 +164,24 @@ function renderAlarm(alarm, exists) { const tdInfo = document.createElement('td'); tr.appendChild(tdInfo); + const onOffCheck = document.createElement('input'); + onOffCheck.type = 'checkbox'; + onOffCheck.checked = alarm.on; + onOffCheck.onchange = e => { + alarm.on = !alarm.on; + }; + const onOffIcon = document.createElement('i'); + onOffIcon.classList.add('form-icon'); + const onOff = document.createElement('label'); + onOff.classList.add('form-switch'); + onOff.appendChild(onOffCheck); + onOff.appendChild(onOffIcon); + tdInfo.appendChild(onOff); + const buttonDelete = document.createElement('button'); buttonDelete.classList.add('btn'); buttonDelete.classList.add('btn-action'); - tdInfo.prepend(buttonDelete); + tdInfo.appendChild(buttonDelete); const iconDelete = document.createElement('i'); iconDelete.classList.add('icon'); iconDelete.classList.add('icon-delete'); From 81e810bcd7d84199635c49030d819831d8cb7f8c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 14:18:01 +0100 Subject: [PATCH 056/116] sched: separate on/off button from delete --- apps/sched/interface.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 0e3dad8d3..c5db6806a 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -161,8 +161,8 @@ function renderAlarm(alarm, exists) { tdSummary.appendChild(inputSummary); inputSummary.onchange(); - const tdInfo = document.createElement('td'); - tr.appendChild(tdInfo); + const tdOptions = document.createElement('td'); + tr.appendChild(tdOptions); const onOffCheck = document.createElement('input'); onOffCheck.type = 'checkbox'; @@ -176,7 +176,10 @@ function renderAlarm(alarm, exists) { onOff.classList.add('form-switch'); onOff.appendChild(onOffCheck); onOff.appendChild(onOffIcon); - tdInfo.appendChild(onOff); + tdOptions.appendChild(onOff); + + const tdInfo = document.createElement('td'); + tr.appendChild(tdInfo); const buttonDelete = document.createElement('button'); buttonDelete.classList.add('btn'); @@ -299,6 +302,7 @@ function onInit() {
+ From f8d1de16ce904b974c2f1831ad670bd3c74167f6 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 15:53:07 +0100 Subject: [PATCH 057/116] sched: wait before reloading to avoid `load()` output --- apps/sched/interface.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index c5db6806a..8bfeeed13 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -89,7 +89,9 @@ function upload() { Util.showModal("Saving..."); Util.writeStorage("sched.json", JSON.stringify(alarms), () => { Puck.write(`\x10load()\n`, () => { // reload watch to load alarms - location.reload(); // reload so we see current data + setTimeout(() => { + location.reload(); // reload so we see current data + }, 500); }); }); } From 4fdef7bd8c66c4dc1091251348b3b700c8cd61da Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 20:50:38 +0100 Subject: [PATCH 058/116] sched: add th for active --- apps/sched/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 8bfeeed13..edfb8e0b3 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -303,7 +303,7 @@ function onInit() { - + From b6829b65cc04e6b050b6e9710d5e992ed3267746 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 20:51:01 +0100 Subject: [PATCH 059/116] sched: default timers to on --- apps/sched/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index edfb8e0b3..9ee1fb649 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -235,8 +235,8 @@ function addTimer() { const alarmDefaults = getAlarmDefaults(); const timer = { timer: hmsToMs("00:00:30"), - on: false, t: 0, + on: alarmDefaults.on, dow: alarmDefaults.dow, last: alarmDefaults.last, rp: alarmDefaults.rp, From bbd93f530c15cbf713e3fadf5ab191dc7ee8d5cf Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 20:51:23 +0100 Subject: [PATCH 060/116] sched: rename dated-alarms to events --- apps/sched/interface.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 9ee1fb649..0560a524b 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -108,7 +108,7 @@ function renderAlarm(alarm, exists) { tr.appendChild(tdType); const inputTime = document.createElement('input'); if (localDate) { - tdType.textContent = "Alarm"; + tdType.textContent = "Event"; inputTime.type = "datetime-local"; inputTime.value = localDate.toISOString().slice(0,16); inputTime.onchange = (e => { @@ -218,13 +218,13 @@ function hmsToMs(hms) { return ((hours * 60 + mins) * 60 + secs) * 1000; } -function addAlarm() { - const alarm = getAlarmDefaults(); - renderAlarm(alarm); - alarms.push(alarm); +function addEvent() { + const event = getAlarmDefaults(); + renderAlarm(event); + alarms.push(event); } -function addDailyAlarm() { +function addAlarm() { const alarm = getAlarmDefaults(); delete alarm.date; renderAlarm(alarm); @@ -283,11 +283,11 @@ function onInit() {

Manage dated events

- - From 1749b4c2f1668a5a11f9d3b21ed30aabc20d8d4c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 29 May 2023 21:01:17 +0100 Subject: [PATCH 061/116] sched: superscript asterisk (for space) --- apps/sched/interface.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 0560a524b..7fe04b7c1 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -138,7 +138,11 @@ function renderAlarm(alarm, exists) { }; } } - if (!exists) tdType.textContent = "* " + tdType.textContent; + if (!exists) { + const asterisk = document.createElement('sup'); + asterisk.textContent = '*'; + tdType.appendChild(asterisk); + } inputTime.classList.add('event-date'); inputTime.classList.add('form-input'); inputTime.dataset.uid = alarm.id; From 7ec932c77a4e9b71afd6e2dae962a81d29f85250 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 5 Jun 2023 16:42:37 +0100 Subject: [PATCH 062/116] ensure clockinfo widget requires the module --- apps/widclkinfo/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/widclkinfo/metadata.json b/apps/widclkinfo/metadata.json index 282e80b76..aca046d90 100644 --- a/apps/widclkinfo/metadata.json +++ b/apps/widclkinfo/metadata.json @@ -6,6 +6,7 @@ "screenshots" : [ { "url":"screenshot.png" }], "type": "widget", "tags": "widget,clkinfo", + "dependencies" : { "clock_info":"module" }, "supports" : ["BANGLEJS2"], "storage": [ {"name":"widclkinfo.wid.js","url":"widget.js"} From 64cb8f04d9b26d8341bc7519936265557c17ba0b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 5 Jun 2023 16:52:57 +0100 Subject: [PATCH 063/116] Fix search for non-app apps --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index dfba8b301..127c90aaa 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit dfba8b30128b100f900763241ec1790033f76dbb +Subproject commit 127c90aaa9e3d23f8853807e1ad17451a37dc3c1 From e7ad9ca2f13c615fabc1e115e7c20666d6d13efa Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 5 Jun 2023 16:54:12 +0100 Subject: [PATCH 064/116] remove sortorder tag - this has no app, so all it did was push the entry to the top of the app loader --- apps/popconlaunch/metadata.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/popconlaunch/metadata.json b/apps/popconlaunch/metadata.json index 9e1f096d4..f3e8ef4fb 100644 --- a/apps/popconlaunch/metadata.json +++ b/apps/popconlaunch/metadata.json @@ -16,6 +16,5 @@ ], "data": [ {"name":"popcon.cache.json"} - ], - "sortorder": -10 + ] } From 4616d533b4bd0ce9b9ac97fdeac1f399f5e15b5c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 5 Jun 2023 11:02:25 +0100 Subject: [PATCH 065/116] sched: don't force reload on alarm save --- apps/sched/interface.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 7fe04b7c1..a16cecc38 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -88,10 +88,8 @@ function eventToAlarm(event, offsetMs) { function upload() { Util.showModal("Saving..."); Util.writeStorage("sched.json", JSON.stringify(alarms), () => { - Puck.write(`\x10load()\n`, () => { // reload watch to load alarms - setTimeout(() => { - location.reload(); // reload so we see current data - }, 500); + Puck.write(`\x10E.showMessage("Hold button to\\nreload alarms")\n`, () => { + location.reload(); // reload so we see current data }); }); } From a299ac1c58cba7590dba28ee4d9e89d8cf9dfd63 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 6 Jun 2023 19:48:11 +0100 Subject: [PATCH 066/116] More navigation icons (for roundabouts) --- apps/messagegui/ChangeLog | 3 ++- apps/messagegui/app.js | 18 ++++++++++++------ apps/messagegui/metadata.json | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 3a61b87fe..4231a9f26 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -90,4 +90,5 @@ 0.65: Make sure messages are saved if not in the clock app (fix #2460) 0.66: Updated Navigation handling to work with new Gadgetbridge release 0.67: Support for 'Ignore' for messages from Gadgetbridge - Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons \ No newline at end of file + Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons +0.68: More navigation icons (for roundabouts) \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index ccc6acec6..d88fba6df 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -91,12 +91,18 @@ function showMapMessage(msg) { }else target = msg.instr; } - if (msg.action=="continue") img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA"; - else if (msg.action=="left") img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc"; - else if (msg.action=="right") img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA"; - else if (msg.action=="left_slight") img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH"; - else if (msg.action=="right_slight") img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA"; - else if (msg.action=="finish") img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A"; + switch (msg.action) { + case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break; + case "left": img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";break; + case "right": img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA";break; + case "left_slight": img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";break; + case "right_slight": img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";break; + case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break; + case "roundabout_left": img = "HhcCAAAAAAAAAAAADwAAAEAAAAP8AAGqkAAA/8ABqqqAAD/wAGqqqgAP/AAKpAakA/8AAakAGoD/////QACpP/////gABpP/////gABpD/////wACpA/8AAv4AGoAP/AAf/QakAD/wAL//qgAA/8AC//qAAAP8AAf/kAAADwAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAA=";break; + case "roundabout_right": img = "HhcCAAAAAAAAAAAAZAAADwAAAf/9AAP8AAB///gAP/AAH///4AD/wAP/Rv9AA/8Af8AH/AAP/AvwAD/////w/wAC/////8/wAC/////8vwAB/////wf8AGpAAP/AP/QaoAA/8AH//qkAD/wAB//6QAP/AAAf/0AAP8AAAA/QAADwAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAA=";break; + case "roundabout_straight": img = "EhwCAABQAAAAH0AAAAf9AAAB//QAAH//0AAf//9AB////QH/v+/0P+P8v8L4P8L4CQP8BgAC/+QAAP/+kAA//+pAC/4GqQD/AAqgH+AAagL8AAKkL8AAKkH+AAagD/AAqgC/4GqQA//+pAAP/+kAAC/+QAAAP8AAAAP8AAAAP8AA";break; + } + //FIXME: what about countries where we drive on the right? How will we know to flip the icons? layout = new Layout({ type:"v", c: [ {type:"txt", font:street?fontMedium:fontLarge, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:3 }, diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index 5743dad50..f18f39096 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.67", + "version": "0.68", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", From aa8ecc7e2c4e3fc345ae3b1ba29ca8fabfd03bf0 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 7 Jun 2023 10:06:39 +0100 Subject: [PATCH 067/116] sched: fix enabling an alarm that's sounded today --- apps/sched/interface.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index a16cecc38..3a8d99849 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -173,6 +173,7 @@ function renderAlarm(alarm, exists) { onOffCheck.checked = alarm.on; onOffCheck.onchange = e => { alarm.on = !alarm.on; + if (alarm.on) delete alarm.last; }; const onOffIcon = document.createElement('i'); onOffIcon.classList.add('form-icon'); From 4a48511fe933b2cb1e5c70f539eb89401ed6ab87 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 7 Jun 2023 10:10:10 +0100 Subject: [PATCH 068/116] sched: reload after uploading sched.json --- apps/sched/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 3a8d99849..5730b5741 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -88,7 +88,7 @@ function eventToAlarm(event, offsetMs) { function upload() { Util.showModal("Saving..."); Util.writeStorage("sched.json", JSON.stringify(alarms), () => { - Puck.write(`\x10E.showMessage("Hold button to\\nreload alarms")\n`, () => { + Puck.write(`\x10require("sched").reload();\n`, () => { location.reload(); // reload so we see current data }); }); From 6edb52ec56e474710fe744288bf33d760088558a Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 7 Jun 2023 17:26:32 +0100 Subject: [PATCH 069/116] chimer: fix typo --- apps/chimer/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/chimer/metadata.json b/apps/chimer/metadata.json index d5bc04950..e54560c93 100644 --- a/apps/chimer/metadata.json +++ b/apps/chimer/metadata.json @@ -2,7 +2,7 @@ "id": "chimer", "name": "Chimer", "version": "0.02", - "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime", + "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Repeat Chime up to 3 times \n - Set hours to disable chime", "icon": "widget.png", "type": "widget", "tags": "widget", From 0834cc358ff9bf30516da329f61dc80963860b6a Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 7 Jun 2023 17:27:48 +0100 Subject: [PATCH 070/116] chimer: avoid busy-loop when queuing next chime --- apps/chimer/widget.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/chimer/widget.js b/apps/chimer/widget.js index 18358df9e..9a29c80f6 100644 --- a/apps/chimer/widget.js +++ b/apps/chimer/widget.js @@ -16,16 +16,10 @@ var settings = readSettings(); - function sleep(milliseconds) { - const date = Date.now(); - let currentDate = null; - do { - currentDate = Date.now(); - } while (currentDate - date < milliseconds); - } - function chime() { - for (var i = 0; i < settings.repeat; i++) { + let count = settings.repeat; + + const chime1 = () => { if (settings.type === 1) { Bangle.buzz(100); } else if (settings.type === 2) { @@ -33,8 +27,11 @@ } else { return; } - sleep(150); - } + if (--count > 0) + setTimeout(chime1, 150); + }; + + chime1(); } let lastHour = new Date().getHours(); From a82163fc88b80fc92d5947db0ac4a7250361ad5c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 7 Jun 2023 17:29:12 +0100 Subject: [PATCH 071/116] chimer: simplify logic for next chime --- apps/chimer/widget.js | 124 +++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 79 deletions(-) diff --git a/apps/chimer/widget.js b/apps/chimer/widget.js index 9a29c80f6..a587b61de 100644 --- a/apps/chimer/widget.js +++ b/apps/chimer/widget.js @@ -34,6 +34,19 @@ chime1(); } + function queueNextCheckMins(mins) { + const now = new Date(), + m = now.getMinutes(), + s = now.getSeconds(), + ms = now.getMilliseconds(); + + const mLeft = mins - (m + mins * 2) % mins, + sLeft = mLeft * 60 - s, + msLeft = sLeft * 1000 - ms; + + setTimeout(check, msLeft); + } + let lastHour = new Date().getHours(); let lastMinute = new Date().getMinutes(); // don't chime when (re)loaded at a whole hour function check() { @@ -42,88 +55,41 @@ m = now.getMinutes(), s = now.getSeconds(), ms = now.getMilliseconds(); - if ( - (settings.sleep && h > settings.end) || - (settings.sleep && h >= settings.end && m !== 0) || - (settings.sleep && h < settings.start) + if (settings.sleep && ( + h > settings.end || + (h >= settings.end && m !== 0) || + h < settings.start) ) { - var mLeft = 60 - m, - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - setTimeout(check, msLeft); + queueNextCheckMins(60); return; } - if (settings.freq === 1) { - if ((m !== lastMinute && m === 0) || (m !== lastMinute && m === 30)) - chime(); - lastHour = h; - lastMinute = m; - // check again in 30 minutes - switch (true) { - case m / 30 >= 1: - var mLeft = 30 - (m - 30), - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - break; - case m / 30 < 1: - var mLeft = 30 - m, - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - break; - } - setTimeout(check, msLeft); - } else if (settings.freq === 2) { - if ( - (m !== lastMinute && m === 0) || - (m !== lastMinute && m === 15) || - (m !== lastMinute && m === 30) || - (m !== lastMinute && m === 45) - ) - chime(); - lastHour = h; - lastMinute = m; - // check again in 15 minutes - switch (true) { - case m / 15 >= 3: - var mLeft = 15 - (m - 45), - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - break; - case m / 15 >= 2: - var mLeft = 15 - (m - 30), - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - break; - case m / 15 >= 1: - var mLeft = 15 - (m - 15), - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - break; - case m / 15 < 1: - var mLeft = 15 - m, - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - break; - } - setTimeout(check, msLeft); - } else if (settings.freq === 3) { - if (m !== lastMinute) chime(); - lastHour = h; - lastMinute = m; - // check again in 1 minute - - var mLeft = 1, - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - setTimeout(check, msLeft); - } else { - if (h !== lastHour && m === 0) chime(); - lastHour = h; - // check again in 60 minutes - var mLeft = 60 - m, - sLeft = mLeft * 60 - s, - msLeft = sLeft * 1000 - ms; - setTimeout(check, msLeft); + switch (settings.freq) { + case 1: + if (m !== lastMinute && m % 30 === 0) + chime(); + lastHour = h; + lastMinute = m; + queueNextCheckMins(30); + break; + case 2: + if (m !== lastMinute && m % 15 === 0) + chime(); + lastHour = h; + lastMinute = m; + queueNextCheckMins(15); + break; + case 3: + // unreachable - not available in settings + if (m !== lastMinute) chime(); + lastHour = h; + lastMinute = m; + queueNextCheckMins(1); + break; + default: + if (h !== lastHour && m === 0) chime(); + lastHour = h; + queueNextCheckMins(60); + break; } } From 11e751fee7f217dca097002024fb05f6399e78fa Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 8 Jun 2023 09:07:49 +0100 Subject: [PATCH 072/116] chimer: version bump --- apps/chimer/ChangeLog | 3 ++- apps/chimer/metadata.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/chimer/ChangeLog b/apps/chimer/ChangeLog index 01bd00a0a..51842b5cd 100644 --- a/apps/chimer/ChangeLog +++ b/apps/chimer/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial Creation -0.02: Fixed some sleep bugs. Added a sleep mode toggle \ No newline at end of file +0.02: Fixed some sleep bugs. Added a sleep mode toggle +0.03: Reduce busy-loop and code diff --git a/apps/chimer/metadata.json b/apps/chimer/metadata.json index e54560c93..dfbabf405 100644 --- a/apps/chimer/metadata.json +++ b/apps/chimer/metadata.json @@ -1,7 +1,7 @@ { "id": "chimer", "name": "Chimer", - "version": "0.02", + "version": "0.03", "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Repeat Chime up to 3 times \n - Set hours to disable chime", "icon": "widget.png", "type": "widget", From 2b6f7765cc69db5e8dd34fa9ad86051914e26601 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 8 Jun 2023 19:26:12 +0100 Subject: [PATCH 073/116] Fix buzz.js - whenever called this used to keep firing an interval every 100ms, and they would build up! --- apps/messages/ChangeLog | 1 + apps/messages/metadata.json | 2 +- modules/buzz.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index f08357673..416363c45 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -3,3 +3,4 @@ 0.57: Optimize saving empty message list 0.58: show/hide "messages" widget directly, instead of through library stub 0.59: fixes message timeout by using setinterval, as it was intended. So the buzz is triggered every x seconds until the timeout occours. +0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 00e95e44e..e8aacd976 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,7 +1,7 @@ { "id": "messages", "name": "Messages", - "version": "0.59", + "version": "0.60", "description": "Library to handle, load and store message events received from Android/iOS", "icon": "app.png", "type": "module", diff --git a/modules/buzz.js b/modules/buzz.js index aed0e2e7b..99364fc1a 100644 --- a/modules/buzz.js +++ b/modules/buzz.js @@ -16,7 +16,7 @@ */ exports.pattern = pattern => new Promise(resolve => { function doBuzz() { - if (pattern == "") resolve(); + if (pattern == "") return resolve(); var c = pattern[0]; pattern = pattern.substr(1); const BUZZ_WEAK = 0.25, BUZZ_STRONG = 1; From b7cbdf269d2b5fd88d1e3527b91ed402ad350cdf Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 9 Jun 2023 02:19:35 -0500 Subject: [PATCH 074/116] [widbt] - fixes widget not showing on blue background --- apps/widbt/widget.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index c7ef8c0ad..31b8e12d8 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,9 +1,14 @@ WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { g.reset(); - if (NRF.getSecurityStatus().connected) - g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - else + if (NRF.getSecurityStatus().connected) { + if (g.getBgColor() === 31) { // If background color is blue use cyan instead + g.setColor("#0ff"); + } else { + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + } + } else { g.setColor(g.theme.dark ? "#666" : "#999"); + } g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); },changed:function() { WIDGETS["bluetooth"].draw(); From 465097b54b8c7e23db5dc5b1e1f2718b57bc1398 Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 9 Jun 2023 02:27:49 -0500 Subject: [PATCH 075/116] Update ChangeLog --- apps/widbt/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog index 4c2132122..292d1347e 100644 --- a/apps/widbt/ChangeLog +++ b/apps/widbt/ChangeLog @@ -5,3 +5,4 @@ 0.06: Tweaking colors for dark/light themes and low bpp screens 0.07: Memory usage improvements 0.08: Disable LCD on, on bluetooth status change +0.09: Fixes widget not showing on blue background From bed497a3bfa724027a885f98da0c002d7ac0f5bf Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 9 Jun 2023 02:28:13 -0500 Subject: [PATCH 076/116] Update ChangeLog --- apps/widbt/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog index 292d1347e..74d31ada6 100644 --- a/apps/widbt/ChangeLog +++ b/apps/widbt/ChangeLog @@ -5,4 +5,4 @@ 0.06: Tweaking colors for dark/light themes and low bpp screens 0.07: Memory usage improvements 0.08: Disable LCD on, on bluetooth status change -0.09: Fixes widget not showing on blue background +0.09: Fix widget not showing on blue background From 030279dc317a7e63b2b30f7c90e22f930863e62e Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 9 Jun 2023 02:29:16 -0500 Subject: [PATCH 077/116] Update metadata.json --- apps/widbt/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widbt/metadata.json b/apps/widbt/metadata.json index 1623db7a1..ec03fb353 100644 --- a/apps/widbt/metadata.json +++ b/apps/widbt/metadata.json @@ -1,7 +1,7 @@ { "id": "widbt", "name": "Bluetooth Widget", - "version": "0.08", + "version": "0.09", "description": "Show the current Bluetooth connection status in the top right of the clock", "icon": "widget.png", "type": "widget", From f1ae6c92157f71ebd24495495339aa6e180f11cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=BCsgen?= Date: Thu, 8 Jun 2023 20:50:56 +0200 Subject: [PATCH 078/116] feat: :sparkles: add gatthrm bootloader code --- apps/bootgatthrm/ChangeLog | 1 + apps/bootgatthrm/README.md | 16 ++++++++++++ apps/bootgatthrm/bluetooth.png | Bin 0 -> 1119 bytes apps/bootgatthrm/boot.js | 43 +++++++++++++++++++++++++++++++++ apps/bootgatthrm/metadata.json | 15 ++++++++++++ 5 files changed, 75 insertions(+) create mode 100644 apps/bootgatthrm/ChangeLog create mode 100644 apps/bootgatthrm/README.md create mode 100644 apps/bootgatthrm/bluetooth.png create mode 100644 apps/bootgatthrm/boot.js create mode 100644 apps/bootgatthrm/metadata.json diff --git a/apps/bootgatthrm/ChangeLog b/apps/bootgatthrm/ChangeLog new file mode 100644 index 000000000..2a37193a3 --- /dev/null +++ b/apps/bootgatthrm/ChangeLog @@ -0,0 +1 @@ +0.01: Initial release. diff --git a/apps/bootgatthrm/README.md b/apps/bootgatthrm/README.md new file mode 100644 index 000000000..3e559c0a5 --- /dev/null +++ b/apps/bootgatthrm/README.md @@ -0,0 +1,16 @@ +# BLE GATT Battery Service + +Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth. + +## Usage + +This boot code runs in the background and has no user interface. + +## Requests + +If you have any suggestions or ideas please post in [this forum thread](http://forum.espruino.com/conversations/351959/), +or [@jjok](https://github.com/jjok) in your Github issue. + +## Creator + +[Jonathan Jefferies](https://github.com/jjok) diff --git a/apps/bootgatthrm/bluetooth.png b/apps/bootgatthrm/bluetooth.png new file mode 100644 index 0000000000000000000000000000000000000000..1a884a62c1029ae43243f8f58ef9dc3a3327e437 GIT binary patch literal 1119 zcmV-l1fctgP)cE5(396`@Dz$wLqIPzYppJ*B(brZX|I zHChYW;Hm1lL~9feUi^283bm!!Ka0DG`v(zZvk8h?W%2@c?XT zES*ZPJU3a{7h{cB0RRrPTh=dGr*a^!0&xQX?DMczGEQwQ4)Y`c0EQJR_Oa?r)W%5x z0HhI_x1HK2pc0j7k^sKW*iQXQ=2YX6D9n-q_%qO+(!1;R%(3!ggBm9SkZ!j|fYm^E zR%O?(qZ5_=gLo$b@WZ#`wXJ`4=)@t~Y>Yv)Y!7y;OB zeO8rs?l*_RK!7NCKLhRU0}||esEhzCyx)O;I=Y5XtC(@B$NTljy45^tT?SHJ10ruX zOS$(<@@!=?P@`0+S)vnkL!=bB)DOg{Q*}L+GO)XAK;&$g@DSo22n#XlR9!)?07D(! zDxz;SOSunBbNCAVm!5U2c~9jVx@WU3=u3)pJ!ur3cu;sm-)NQ!pM}i;0{{TnZpA^Z ztASvff%b#?JdoF$<=hv8)Q159pyx{LBoBD4SyIzhb(HlW;>}!J+1=AqCDO_A6&XN}=e)0!pcibn z_HtD9d_@BAknp}zDCb9=>MK#y^fmCZ_8GoYkv>KTT7e$nHy?0mXP*UpX*>1PgVgRc z3#Fdn#d~2}5mATky^{qxZ#%U!Ve5AonQN!;&C-!_@cJGbKmk6^xYakqWbkDSU>e?6 zF9=onXw<2mHO=A62q0{DUp*iYB(+@Ja7a-n2aQU$C-1do)^M)j_l7Z`m-DHf;N2WNgeIsEr6@kFmJ z8_YbwGn3bL?QPY+LOBr_TDRcEMmfJ|;s=HR0IQ!ry9xAti1(G5Y&@#1^&${_&Hfi+ z9c`2jUpMuHhg{we{KebpwXs3NLqaRiAr)b6sg!$n>kZxDN)lj0k<-mm?qZatNdeqJ z)Lky+8&Ml40dUjvB)_tlzY&Ld+&A&{bh~wEWib~^c!+ZagoS&X-t=l^d_A@r#J2*U lKABp3ezkHW*6{xc{R@3@kib`1LqGrk002ovPDHLkV1l684mkh- literal 0 HcmV?d00001 diff --git a/apps/bootgatthrm/boot.js b/apps/bootgatthrm/boot.js new file mode 100644 index 000000000..9f1ec1584 --- /dev/null +++ b/apps/bootgatthrm/boot.js @@ -0,0 +1,43 @@ +(() => { + function setupHRMAdvertising() { + /* + * This function preparse BLE heart rate Advertisement. + */ + NRF.setServices({ + 0x180D: { // heart_rate + 0x2A37: { // heart_rate_measurement + notify: true, + value: [0x06, 0], + } + } + }, { advertise: ['180D'] }); + + } + + function updateBLEHeartRate(hrm) { + /* + * Send updated heart rate measurement via BLE + */ + if (hrm === undefined) return; + try { + NRF.updateServices({ + '180d': { + '2a37': { + value: [ + 0x06, // + hrm + ], + notify: true + } + } + }); + } catch (error) { + // After setupHRMAdvertising() BLE needs to restart. + // We force a disconnect if the Bangle was connected while setting HRM + NRF.disconnect(); + } + } + + setupHRMAdvertising(); + Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm.bpm); }); +})(); diff --git a/apps/bootgatthrm/metadata.json b/apps/bootgatthrm/metadata.json new file mode 100644 index 000000000..e7bfb0762 --- /dev/null +++ b/apps/bootgatthrm/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "bootgatthrm", + "name": "BLE GATT HRM Service", + "shortName": "BLE HRM Service", + "version": "0.01", + "description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n", + "icon": "bluetooth.png", + "type": "bootloader", + "tags": "hrm,health,ble,bluetooth,gatt", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"gatthrm.boot.js","url":"boot.js"} + ] +} From dbeb391e6965d1d9990b035054c47bcde5847df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=BCsgen?= Date: Fri, 9 Jun 2023 10:43:50 +0200 Subject: [PATCH 079/116] feat: :sparkles: Added new bootloader "bootgatthrm" which exposes HRM data via BLE --- apps/bootgatthrm/boot.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/bootgatthrm/boot.js b/apps/bootgatthrm/boot.js index 9f1ec1584..391f245b9 100644 --- a/apps/bootgatthrm/boot.js +++ b/apps/bootgatthrm/boot.js @@ -1,8 +1,9 @@ (() => { function setupHRMAdvertising() { /* - * This function preparse BLE heart rate Advertisement. + * This function prepares BLE heart rate Advertisement. */ + NRF.setServices({ 0x180D: { // heart_rate 0x2A37: { // heart_rate_measurement @@ -10,34 +11,44 @@ value: [0x06, 0], } } - }, { advertise: ['180D'] }); + }, { advertise: [0x180d] }); } - function updateBLEHeartRate(hrm) { /* * Send updated heart rate measurement via BLE */ - if (hrm === undefined) return; + if (hrm === undefined || hrm.confidence < 50) return; try { NRF.updateServices({ - '180d': { - '2a37': { + 0x180D: { + 0x2A37: { value: [ 0x06, // - hrm + hrm.bpm ], notify: true } } }); } catch (error) { - // After setupHRMAdvertising() BLE needs to restart. - // We force a disconnect if the Bangle was connected while setting HRM - NRF.disconnect(); + if (error.message.includes("BLE restart")) { + /* + * BLE has to restart after service setup. + */ + NRF.disconnect(); + } + else if (error.message.includes("UUID 0x2a37")) { + /* + * Setup service if it wasn't setup correctly for some reason + */ + setupHRMAdvertising(); + } else { + console.log("[bootgatthrm]: Unexpected error occured while updating HRM over BLE! Error: " + error.message); + } } } setupHRMAdvertising(); - Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm.bpm); }); + Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm); }); })(); From b424e0cb27fc33770a2ab5079e61879590eb888c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=BCsgen?= Date: Fri, 9 Jun 2023 11:03:44 +0200 Subject: [PATCH 080/116] docs: :memo: Update Readme --- apps/bootgatthrm/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/bootgatthrm/README.md b/apps/bootgatthrm/README.md index 3e559c0a5..15bb2b670 100644 --- a/apps/bootgatthrm/README.md +++ b/apps/bootgatthrm/README.md @@ -1,16 +1,16 @@ -# BLE GATT Battery Service +# BLE GATT HRM Service -Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth. +Adds the GATT HRM Service to advertise the current HRM over Bluetooth. ## Usage This boot code runs in the background and has no user interface. -## Requests - -If you have any suggestions or ideas please post in [this forum thread](http://forum.espruino.com/conversations/351959/), -or [@jjok](https://github.com/jjok) in your Github issue. - ## Creator -[Jonathan Jefferies](https://github.com/jjok) +[Another Stranger](https://github.com/anotherstranger) + +## Aknowledgements + +Special thanks to [Jonathan Jefferies](https://github.com/jjok) for creating the +bootgattbat app, which was the inspiration for this App! From 7be8b4638d839e7eec323305e5fd939c63591e2f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 10:39:47 +0100 Subject: [PATCH 081/116] Improved nav icons for roundabouts (not bothering with a version bump for this) --- apps/messagegui/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index d88fba6df..1f61743ef 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -98,9 +98,9 @@ function showMapMessage(msg) { case "left_slight": img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";break; case "right_slight": img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";break; case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break; - case "roundabout_left": img = "HhcCAAAAAAAAAAAADwAAAEAAAAP8AAGqkAAA/8ABqqqAAD/wAGqqqgAP/AAKpAakA/8AAakAGoD/////QACpP/////gABpP/////gABpD/////wACpA/8AAv4AGoAP/AAf/QakAD/wAL//qgAA/8AC//qAAAP8AAf/kAAADwAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAA=";break; - case "roundabout_right": img = "HhcCAAAAAAAAAAAAZAAADwAAAf/9AAP8AAB///gAP/AAH///4AD/wAP/Rv9AA/8Af8AH/AAP/AvwAD/////w/wAC/////8/wAC/////8vwAB/////wf8AGpAAP/AP/QaoAA/8AH//qkAD/wAB//6QAP/AAAf/0AAP8AAAA/QAADwAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAA=";break; - case "roundabout_straight": img = "EhwCAABQAAAAH0AAAAf9AAAB//QAAH//0AAf//9AB////QH/v+/0P+P8v8L4P8L4CQP8BgAC/+QAAP/+kAA//+pAC/4GqQD/AAqgH+AAagL8AAKkL8AAKkH+AAagD/AAqgC/4GqQA//+pAAP/+kAAC/+QAAAP8AAAAP8AAAAP8AA";break; + case "roundabout_left": img = "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=";break; + case "roundabout_right": img = "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=";break; + case "roundabout_straight": img = "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==";break; } //FIXME: what about countries where we drive on the right? How will we know to flip the icons? @@ -208,7 +208,7 @@ function showMessageScroller(msg) { var bodyFont = fontBig; g.setFont(bodyFont); var lines = []; - if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10) + if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10); var titleCnt = lines.length; if (titleCnt) lines.push(""); // add blank line after title lines = lines.concat(g.wrapString(msg.body, g.getWidth()-10),["",/*LANG*/"< Back"]); From 6bdb7dc0ad5312c0214f0ea7f058b46a007cc2c2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 11:04:06 +0100 Subject: [PATCH 082/116] Fix widgets that clear too low, and make widget swipeOn have a 2px border at the bottom --- apps/widbatv/ChangeLog | 1 + apps/widbatv/metadata.json | 2 +- apps/widbatv/widget.js | 2 +- apps/widclkinfo/ChangeLog | 3 ++- apps/widclkinfo/metadata.json | 2 +- apps/widclkinfo/widget.js | 2 +- modules/widget_utils.js | 6 +++--- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog index f7ced965b..1282b846f 100644 --- a/apps/widbatv/ChangeLog +++ b/apps/widbatv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New widget 0.02: Make color depend on level +0.03: Stop battery widget clearing too far down \ No newline at end of file diff --git a/apps/widbatv/metadata.json b/apps/widbatv/metadata.json index 74e374601..d4cbf46ac 100644 --- a/apps/widbatv/metadata.json +++ b/apps/widbatv/metadata.json @@ -1,7 +1,7 @@ { "id": "widbatv", "name": "Battery Level Widget (Vertical)", - "version": "0.02", + "version": "0.03", "description": "Slim, vertical battery widget that only takes up 14px", "icon": "widget.png", "type": "widget", diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js index efc42fdad..f26733648 100644 --- a/apps/widbatv/widget.js +++ b/apps/widbatv/widget.js @@ -12,7 +12,7 @@ WIDGETS["batv"]={area:"tr",width:14,draw:function() { if (Bangle.isCharging()) { g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); } else { - g.clearRect(x,y,x+14,y+24); + g.clearRect(x,y,x+14,y+23); g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2); var battery = E.getBattery(); if (battery < 20) {g.setColor("#f00");} diff --git a/apps/widclkinfo/ChangeLog b/apps/widclkinfo/ChangeLog index 3ca502120..6c3b85b00 100644 --- a/apps/widclkinfo/ChangeLog +++ b/apps/widclkinfo/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Widget! -0.02: Now use an app ID (to avoid conflicts with clocks that also use ClockInfo) \ No newline at end of file +0.02: Now use an app ID (to avoid conflicts with clocks that also use ClockInfo) +0.03: Fix widget clearing too far down \ No newline at end of file diff --git a/apps/widclkinfo/metadata.json b/apps/widclkinfo/metadata.json index aca046d90..4ba9b2444 100644 --- a/apps/widclkinfo/metadata.json +++ b/apps/widclkinfo/metadata.json @@ -1,6 +1,6 @@ { "id": "widclkinfo", "name": "Clock Info Widget", - "version":"0.02", + "version":"0.03", "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" }], diff --git a/apps/widclkinfo/widget.js b/apps/widclkinfo/widget.js index 95bc9a111..a802ba872 100644 --- a/apps/widclkinfo/widget.js +++ b/apps/widclkinfo/widget.js @@ -32,7 +32,7 @@ if (!require("clock_info").loadCount) { // don't load if a clock_info was alread // 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); + g.clearRect(o.x, o.y, o.x+o.w-1, o.y+o.h-1); if (clockInfoInfo) { var x = o.x; if (clockInfoInfo.img) { diff --git a/modules/widget_utils.js b/modules/widget_utils.js index e83555729..7124ac8c8 100644 --- a/modules/widget_utils.js +++ b/modules/widget_utils.js @@ -70,13 +70,13 @@ exports.swipeOn = function(autohide) { // force app rect to be fullscreen Bangle.appRect = { x: 0, y: 0, w: g.getWidth(), h: g.getHeight(), x2: g.getWidth()-1, y2: g.getHeight()-1 }; // setup offscreen graphics for widgets - let og = Graphics.createArrayBuffer(g.getWidth(),24,16,{msb:true}); + let og = Graphics.createArrayBuffer(g.getWidth(),26,16,{msb:true}); og.theme = g.theme; og._reset = og.reset; og.reset = function() { return this._reset().setColor(g.theme.fg).setBgColor(g.theme.bg); }; - og.reset().clearRect(0,0,og.getWidth(),og.getHeight()); + og.reset().clearRect(0,0,og.getWidth(),23).fillRect(0,24,og.getWidth(),25); let _g = g; let offset = -24; // where on the screen are we? -24=hidden, 0=full visible @@ -146,4 +146,4 @@ exports.swipeOn = function(autohide) { }; Bangle.on("swipe", exports.swipeHandler); Bangle.drawWidgets(); -}; +}; \ No newline at end of file From 38e3fe40e51519d3c0b9966b3079a555c153a2f1 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 14:10:02 +0100 Subject: [PATCH 083/116] More navigation icons (keep/uturn left/right)Added icon for uturn --- apps/messagegui/ChangeLog | 3 ++- apps/messagegui/app.js | 5 +++++ apps/messagegui/metadata.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 4231a9f26..ace1d3091 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -91,4 +91,5 @@ 0.66: Updated Navigation handling to work with new Gadgetbridge release 0.67: Support for 'Ignore' for messages from Gadgetbridge Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons -0.68: More navigation icons (for roundabouts) \ No newline at end of file +0.68: More navigation icons (for roundabouts) +0.69: More navigation icons (keep/uturn left/right) \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 1f61743ef..614552371 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -97,10 +97,15 @@ function showMapMessage(msg) { case "right": img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA";break; case "left_slight": img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";break; case "right_slight": img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";break; + case "keep_left": img = "ERmBAACAAOAB+AD+AP+B/+H3+PO+8c8w4wBwADgAHgAPAAfAAfAAfAAfAAeAAeAAcAA8AA4ABwADgA==";break; + case "keep_right": img = "ERmBAACAAOAA/AD+AP+A//D/fPueeceY4YBwADgAPAAeAB8AHwAfAB8ADwAPAAcAB4ADgAHAAOAAAA==";break; + case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break; + case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break; case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break; case "roundabout_left": img = "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=";break; case "roundabout_right": img = "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=";break; case "roundabout_straight": img = "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==";break; + case "roundabout_uturn": img = "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA";break; } //FIXME: what about countries where we drive on the right? How will we know to flip the icons? diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index f18f39096..dffff0c58 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.68", + "version": "0.69", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", From 0f431d53cfe0a8269c9752b68e99237e70abc4d8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 14:16:27 +0100 Subject: [PATCH 084/116] Nav messages with '/' now get split on newlines --- apps/messagegui/ChangeLog | 3 ++- apps/messagegui/app.js | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index ace1d3091..2e8f9bb3c 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -92,4 +92,5 @@ 0.67: Support for 'Ignore' for messages from Gadgetbridge Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons 0.68: More navigation icons (for roundabouts) -0.69: More navigation icons (keep/uturn left/right) \ No newline at end of file +0.69: More navigation icons (keep/uturn left/right) + Nav messages with '/' now get split on newlines \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 614552371..97396496f 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -17,6 +17,7 @@ require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title" // maps GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:966,action:"continue",eta:"08:39"}) GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:12345,action:"left_slight",eta:"08:39"}) +GB({t:"nav",src:"maps",title:"Navigation",instr:"Main St / I-29 ALT / Centerpoint Dr",distance:12345,action:"left_slight",eta:"08:39"}) // call require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true}) */ @@ -84,12 +85,13 @@ function showMapMessage(msg) { if (msg.distance!==undefined) distance = require("locale").distance(msg.distance); if (msg.instr) { - if (msg.instr.includes("towards") || msg.instr.includes("toward")) { - m = msg.instr.split(/towards|toward/); + var instr = msg.instr.replace(/\s*\/\s*/g," \/\n"); // convert slashes to newlines + if (instr.includes("towards") || instr.includes("toward")) { + m = instr.split(/towards|toward/); target = m[0].trim(); street = m[1].trim(); }else - target = msg.instr; + target = instr; } switch (msg.action) { case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break; From f0fef28fca008b4e6f6c762e66eecea0ab788104 Mon Sep 17 00:00:00 2001 From: pancake Date: Fri, 9 Jun 2023 14:33:17 +0200 Subject: [PATCH 085/116] Initial import of the sunrise watchface --- apps/sunrise/ChangeLog | 1 + apps/sunrise/README.md | 27 + apps/sunrise/app-icon.js | 1 + apps/sunrise/app.js | 321 ++++++ apps/sunrise/app.png | Bin 0 -> 1563 bytes apps/sunrise/metadata.json | 31 + apps/sunrise/screenshot.png | Bin 0 -> 2592 bytes package-lock.json | 2181 ++++++++++++++++++++++++++++++++++- 8 files changed, 2551 insertions(+), 11 deletions(-) create mode 100644 apps/sunrise/ChangeLog create mode 100644 apps/sunrise/README.md create mode 100644 apps/sunrise/app-icon.js create mode 100644 apps/sunrise/app.js create mode 100644 apps/sunrise/app.png create mode 100644 apps/sunrise/metadata.json create mode 100644 apps/sunrise/screenshot.png diff --git a/apps/sunrise/ChangeLog b/apps/sunrise/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/sunrise/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/sunrise/README.md b/apps/sunrise/README.md new file mode 100644 index 000000000..7a60b681d --- /dev/null +++ b/apps/sunrise/README.md @@ -0,0 +1,27 @@ +# sunrise watchface + +This app mimics the Apple Watch watchface that shows the sunrise and sunset time. + +This is a work-in-progress app, so you may expect missfeatures, bugs and heavy +battery draining. There's still a lot of things to optimize and improve, so take +this into account before complaining :-) + +* Lat/Lon hardcoded to Barcelona, but it's configurable if you install the "mylocation app" +* Shows sea level and make the sun/moon glow depending on the x position +* The sinus is fixed, so the sea level is curved to match the sunrise/sunset positions) + +## TODO + +* Access GPS on first start to cache lat+lon to compute sunrise/sunset times +* Cache constant values once +* Show month and day too? +* Improved gradients +* Faster rendering, by reducing sinus stepsize, only refreshing whats needed, etc + +## Author + +Written by pancake in 2023 + +## Screenshots + +![sunrise](screenshot.png) diff --git a/apps/sunrise/app-icon.js b/apps/sunrise/app-icon.js new file mode 100644 index 000000000..79e0fded7 --- /dev/null +++ b/apps/sunrise/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A/AH4A/AH4ADgMiAAMgCyoABiAXQiUjkQBCkIXQFYMzAAIEBIyMjn8z+cyMKECFwM+93uGAJIPgUzn4WBC4IwBIx0jC4njmQvOkUSkQXTicQL4JHSgSFBgUyO6QkCU4YWBF4KnLFwTXGIwMQRZQ7EC4IxBAYRHKgQjEVIIXCkcgiQwJQQxhBAAJQCGBBdEABIwIfJwmHFxwnFAYQuOFAuIcAMwC54pE1WpWgURMKUxhUKzWqDYLOKVQh1FGoOTnQaKdAR1HhWqzWKkUykK7GkKkM1WZyRsCGAikPhWZ1EzGoKHBaZ5rEGoQWRNgoXVAH4A5")) diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js new file mode 100644 index 000000000..b6dd71702 --- /dev/null +++ b/apps/sunrise/app.js @@ -0,0 +1,321 @@ +// banglejs app made by pancake +// sunrise/sunset script by Matt Kane from https://github.com/Triggertrap/sun-js + +const LOCATION_FILE = 'mylocation.json'; +let location; + +// requires the myLocation app +function loadLocation () { + try { + return require('Storage').readJSON(LOCATION_FILE, 1) || {}; + } catch (e) { + return { }; + } +} + +const latlon = loadLocation(); +const lat = latlon.lat || 41.38; +const lon = latlon.lon || 2.168; + +/** + * Sunrise/sunset script. By Matt Kane. + * + * Based loosely and indirectly on Kevin Boone's SunTimes Java implementation + * of the US Naval Observatory's algorithm. + * + * Copyright © 2012 Triggertrap Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, + * or connect to: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + */ + +Date.prototype.sunrise = function (latitude, longitude, zenith) { + return this.sunriseSet(latitude, longitude, true, zenith); +}; + +Date.prototype.sunset = function (latitude, longitude, zenith) { + return this.sunriseSet(latitude, longitude, false, zenith); +}; + +Date.prototype.sunriseSet = function (latitude, longitude, sunrise, zenith) { + if (!zenith) { + zenith = 90.8333; + } + + const hoursFromMeridian = longitude / Date.DEGREES_PER_HOUR; + const dayOfYear = this.getDayOfYear(); + let approxTimeOfEventInDays; + let sunMeanAnomaly; + let sunTrueLongitude; + let ascension; + let rightAscension; + let lQuadrant; + let raQuadrant; + let sinDec; + let cosDec; + let localHourAngle; + let localHour; + let localMeanTime; + let time; + + if (sunrise) { + approxTimeOfEventInDays = dayOfYear + ((6 - hoursFromMeridian) / 24); + } else { + approxTimeOfEventInDays = dayOfYear + ((18.0 - hoursFromMeridian) / 24); + } + + sunMeanAnomaly = (0.9856 * approxTimeOfEventInDays) - 3.289; + + sunTrueLongitude = sunMeanAnomaly + (1.916 * Math.sinDeg(sunMeanAnomaly)) + (0.020 * Math.sinDeg(2 * sunMeanAnomaly)) + 282.634; + sunTrueLongitude = Math.mod(sunTrueLongitude, 360); + + ascension = 0.91764 * Math.tanDeg(sunTrueLongitude); + rightAscension = 360 / (2 * Math.PI) * Math.atan(ascension); + rightAscension = Math.mod(rightAscension, 360); + + lQuadrant = Math.floor(sunTrueLongitude / 90) * 90; + raQuadrant = Math.floor(rightAscension / 90) * 90; + rightAscension = rightAscension + (lQuadrant - raQuadrant); + rightAscension /= Date.DEGREES_PER_HOUR; + + sinDec = 0.39782 * Math.sinDeg(sunTrueLongitude); + cosDec = Math.cosDeg(Math.asinDeg(sinDec)); + cosLocalHourAngle = ((Math.cosDeg(zenith)) - (sinDec * (Math.sinDeg(latitude)))) / (cosDec * (Math.cosDeg(latitude))); + + localHourAngle = Math.acosDeg(cosLocalHourAngle); + + if (sunrise) { + localHourAngle = 360 - localHourAngle; + } + + localHour = localHourAngle / Date.DEGREES_PER_HOUR; + + localMeanTime = localHour + rightAscension - (0.06571 * approxTimeOfEventInDays) - 6.622; + + time = localMeanTime - (longitude / Date.DEGREES_PER_HOUR); + time = Math.mod(time, 24); + + const midnight = new Date(0); + // midnight.setUTCFullYear(this.getUTCFullYear()); + // midnight.setUTCMonth(this.getUTCMonth()); + // midnight.setUTCDate(this.getUTCDate()); + + const milli = midnight.getTime() + (time * 60 * 60 * 1000); + + return new Date(milli); +}; + +Date.DEGREES_PER_HOUR = 360 / 24; + +// Utility functions + +Date.prototype.getDayOfYear = function () { + const onejan = new Date(this.getFullYear(), 0, 1); + return Math.ceil((this - onejan) / 86400000); +}; + +Math.degToRad = function (num) { + return num * Math.PI / 180; +}; + +Math.radToDeg = function (radians) { + return radians * 180.0 / Math.PI; +}; + +Math.sinDeg = function (deg) { + return Math.sin(deg * 2.0 * Math.PI / 360.0); +}; + +Math.acosDeg = function (x) { + return Math.acos(x) * 360.0 / (2 * Math.PI); +}; + +Math.asinDeg = function (x) { + return Math.asin(x) * 360.0 / (2 * Math.PI); +}; + +Math.tanDeg = function (deg) { + return Math.tan(deg * 2.0 * Math.PI / 360.0); +}; + +Math.cosDeg = function (deg) { + return Math.cos(deg * 2.0 * Math.PI / 360.0); +}; + +Math.mod = function (a, b) { + let result = a % b; + if (result < 0) { + result += b; + } + return result; +}; + +const delta = 2; +const sunrise = new Date().sunrise(lat, lon); +const sr = sunrise.getHours() + ':' + sunrise.getMinutes(); +console.log('sunrise', sunrise); +const sunset = new Date().sunset(lat, lon); +const ss = sunset.getHours() + ':' + sunset.getMinutes(); +console.log('sunset', sunset); + +const w = g.getWidth(); +const h = g.getHeight(); +const oy = h / 1.7; + +function ypos (x) { + const pc = (x * 100 / w); + return oy + (32 * Math.sin(1.7 + (pc / 16))); +} + +let sunRiseX = 0; +let sunSetX = 0; + +function drawSinuses () { + let x = 0; + + g.setColor(0, 0, 0); + // g.fillRect(0,oy,w, h); + g.setColor(1, 1, 1); + let y = oy; + for (i = 0; i < w; i++) { + x = i; + x2 = x + 6; + y2 = ypos(i); + if (x == 0) { + y = y2; + } + g.drawLine(x, y, x2, y2); + y = y2; + i += 5; // no need to draw all steps + } + + // sea level line + const sl0 = seaLevel(sunrise.getHours()); + const sl1 = seaLevel(sunset.getHours()); + sunRiseX = xfromTime(sunrise.getHours()); + sunSetX = xfromTime(sunset.getHours()); + g.setColor(0.5, 0.5, 1); + g.drawLine(0, sl0, w, sl1); + g.setColor(0, 0, 1); + g.drawLine(0, sl0 + 1, w, sl1 + 1); + g.setColor(0, 0, 0.5); + g.drawLine(0, sl0 + 2, w, sl1 + 2); +} + +function drawTimes () { + g.setColor(1, 1, 1); + g.setFont('Vector', 20); + g.drawString(sr, 10, h - 20); + g.drawString(ss, w - 60, h - 20); +} + +let pos = 0; +let realTime = true; +const r = 10; + +function drawGlow () { + const now = new Date(); + if (realTime) { + pos = xfromTime(now.getHours()); + } + const x = pos; + const y = ypos(x - (r / 2)); + const r2 = (x > sunRiseX && x < sunSetX) ? r + 20 : r + 10; + + g.setColor(0.3, 0.3, 0.3); + g.fillCircle(x, y, r2); + g.setColor(0.5, 0.5, 0.5); + g.fillCircle(x, y, r + 3); +} + +function seaLevel (hour) { + // hour goes from 0 to 24 + // to get the X we divide the screen in 24 + return ypos(xfromTime(hour)); +} + +function xfromTime (t) { + return (w / 24) * t; +} + +function drawBall () { + let x = pos; + const now = new Date(); + if (realTime) { + x = xfromTime(now.getHours()); + pos = x; + } + const y = ypos(x - (r / 2)); + + // glow + g.setColor(1, 1, 0); + if (x < sunRiseX) { + g.setColor(0.2, 0.2, 0); + } else if (x > sunSetX) { + g.setColor(0.2, 0.2, 0); + } + g.fillCircle(x, y, r); + g.setColor(1, 1, 1); + g.drawCircle(x, y, r); + let curTime = ''; + let fhours = 0.0; + let fmins = 0.0; + if (realTime) { + fhours = now.getHours(); + fmins = now.getMinutes(); + } else { + fhours = 24 * (pos / w); + if (fhours > 23) { + fhours = 0; + } + fmins = (24 - fhours) % 60; + if (fmins < 0) { + fmins = 0; + } + } + const hours = ((fhours < 10) ? '0' : '') + (0 | fhours); + const mins = ((fmins < 10) ? '0' : '') + (0 | fmins); + curTime = hours + ':' + mins; + g.setFont('Vector', 30); + g.setColor(1, 1, 1); + g.drawString(curTime, w / 1.9, 32); +} + +function renderScreen () { + g.setBgColor(0, 0, 0); + g.clear(); + if (realTime) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + } + drawGlow(); + drawSinuses(); + drawTimes(); + drawBall(); +} + +Bangle.on('drag', function (tap, top) { + pos = tap.x; + realTime = false; + renderScreen(); +}); + +Bangle.on('lock', () => { + realTime = Bangle.isLocked(); + renderScreen(); +}); +Bangle.on('tap', () => { + realTime = true; + renderScreen(); +}); +renderScreen(); + +setInterval(renderScreen, 60 * 1000); diff --git a/apps/sunrise/app.png b/apps/sunrise/app.png new file mode 100644 index 0000000000000000000000000000000000000000..a170dfa35c8ace11d424b8a6fd0c3dd74d7c5087 GIT binary patch literal 1563 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.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", + "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "dependencies": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-watch": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.11.0.tgz", + "integrity": "sha512-wAOd0moNX2kSA2FNvt8+7ORwYaJpQ1ZoWjUYdb1bBCxq4nkWuU0IiJa9VpVxrj5Ks+FGXQd62OC/Bjk0aSr+dg==", + "dev": true, + "dependencies": { + "nodemon": "^2.0.7", + "through2": "^4.0.2" + }, + "bin": { + "npm-watch": "cli.js" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, "dependencies": { "@eslint/eslintrc": { "version": "1.3.0", @@ -59,7 +2217,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.6", @@ -1391,6 +3550,15 @@ } } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -1413,15 +3581,6 @@ "es-abstract": "^1.19.5" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", From de3fbe6578f93342e7355a4efb6ab24946938f62 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 14:55:11 +0100 Subject: [PATCH 086/116] Update app.js as per https://github.com/espruino/BangleApps/commit/f0fef28fca008b4e6f6c762e66eecea0ab788104#r117262688 --- apps/sunrise/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js index b6dd71702..30b344c56 100644 --- a/apps/sunrise/app.js +++ b/apps/sunrise/app.js @@ -7,7 +7,7 @@ let location; // requires the myLocation app function loadLocation () { try { - return require('Storage').readJSON(LOCATION_FILE, 1) || {}; + return require('Storage').readJSON(LOCATION_FILE, 1); } catch (e) { return { }; } From c62ccf27e15bf0485b9fa68a5b5b40ee49a18293 Mon Sep 17 00:00:00 2001 From: pancake Date: Fri, 9 Jun 2023 16:41:11 +0200 Subject: [PATCH 087/116] Release sunrise 0.02, faster sinus and fix menu button --- apps/sunrise/ChangeLog | 1 + apps/sunrise/app.js | 6 ++++-- apps/sunrise/metadata.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/sunrise/ChangeLog b/apps/sunrise/ChangeLog index 7b83706bf..19e3bb33b 100644 --- a/apps/sunrise/ChangeLog +++ b/apps/sunrise/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Faster sinus line and fix button to open menu diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js index 30b344c56..2fa2a7502 100644 --- a/apps/sunrise/app.js +++ b/apps/sunrise/app.js @@ -177,6 +177,7 @@ function ypos (x) { let sunRiseX = 0; let sunSetX = 0; +const sinStep = 12; function drawSinuses () { let x = 0; @@ -187,14 +188,14 @@ function drawSinuses () { let y = oy; for (i = 0; i < w; i++) { x = i; - x2 = x + 6; + x2 = x + sinStep + 1; y2 = ypos(i); if (x == 0) { y = y2; } g.drawLine(x, y, x2, y2); y = y2; - i += 5; // no need to draw all steps + i += sinStep; // no need to draw all steps } // sea level line @@ -318,4 +319,5 @@ Bangle.on('tap', () => { }); renderScreen(); +Bangle.setUI("clock"); setInterval(renderScreen, 60 * 1000); diff --git a/apps/sunrise/metadata.json b/apps/sunrise/metadata.json index 61fe45520..f3012144a 100644 --- a/apps/sunrise/metadata.json +++ b/apps/sunrise/metadata.json @@ -2,7 +2,7 @@ "id": "sunrise", "name": "Sunrise", "shortName": "Sunrise", - "version": "0.01", + "version": "0.02", "type": "clock", "description": "Show sunrise and sunset times", "icon": "app.png", From 84bde38d89414868963b72d3e26b6ab227a0a62d Mon Sep 17 00:00:00 2001 From: pancake Date: Fri, 9 Jun 2023 16:58:51 +0200 Subject: [PATCH 088/116] Call setUI before loadWidgets --- apps/sunrise/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js index 2fa2a7502..57fb1c2dc 100644 --- a/apps/sunrise/app.js +++ b/apps/sunrise/app.js @@ -13,6 +13,8 @@ function loadLocation () { } } +Bangle.setUI("clock"); +Bangle.loadWidgets(); const latlon = loadLocation(); const lat = latlon.lat || 41.38; const lon = latlon.lon || 2.168; @@ -294,7 +296,6 @@ function renderScreen () { g.setBgColor(0, 0, 0); g.clear(); if (realTime) { - Bangle.loadWidgets(); Bangle.drawWidgets(); } drawGlow(); @@ -319,5 +320,4 @@ Bangle.on('tap', () => { }); renderScreen(); -Bangle.setUI("clock"); setInterval(renderScreen, 60 * 1000); From f8ccc2f59659b26eb4078deff6809ef1009c051f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 16:58:00 +0100 Subject: [PATCH 089/116] 0.27: Fix first ever recorded filename being log0 (now all are dated) --- apps/recorder/ChangeLog | 1 + apps/recorder/metadata.json | 2 +- apps/recorder/widget.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 46ea05918..94e2f28c2 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -32,3 +32,4 @@ 0.24: Can now specify `setRecording(true, {force:...` to not show a menu 0.25: Widget now has `isRecording()` for retrieving recording status. 0.26: Now record filename based on date +0.27: Fix first ever recorded filename being log0 (now all are dated) \ No newline at end of file diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 598318e29..beb5648c8 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.26", + "version": "0.27", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 061859f9c..c2e04fd09 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -240,8 +240,9 @@ var settings = loadSettings(); options = options||{}; if (isOn && !settings.recording) { + var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10; if (!settings.file) { // if no filename set - settings.file = "recorder.log0.csv"; + settings.file = "recorder.log" + date + trackNo.toString(36) + ".csv";; } else if (require("Storage").list(settings.file).length){ // if file exists if (!options.force) { // if not forced, ask the question g.reset(); // work around bug in 2v17 and earlier where bg color wasn't reset @@ -263,7 +264,6 @@ require("Storage").open(settings.file,"r").erase(); } else if (options.force=="new") { // new file - use the current date - var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10; var newFileName; do { // while a file exists, add one to the letter after the date newFileName = "recorder.log" + date + trackNo.toString(36) + ".csv"; From d620ff25be2566d88ae1b0346f57dacc455b46ab Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 9 Jun 2023 16:59:37 +0100 Subject: [PATCH 090/116] oops --- apps/recorder/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index c2e04fd09..3c9afcf70 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -242,7 +242,7 @@ if (isOn && !settings.recording) { var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10; if (!settings.file) { // if no filename set - settings.file = "recorder.log" + date + trackNo.toString(36) + ".csv";; + settings.file = "recorder.log" + date + trackNo.toString(36) + ".csv"; } else if (require("Storage").list(settings.file).length){ // if file exists if (!options.force) { // if not forced, ask the question g.reset(); // work around bug in 2v17 and earlier where bg color wasn't reset From 239c2777b1af7b8b26f1191c9079c4fd6a545d7d Mon Sep 17 00:00:00 2001 From: Luc Tielen Date: Fri, 9 Jun 2023 20:36:43 +0200 Subject: [PATCH 091/116] Update the sunrise watchface --- apps/sunrise/ChangeLog | 1 + apps/sunrise/README.md | 8 +- apps/sunrise/app.js | 147 ++++++++++++++++++++++++++----------- apps/sunrise/metadata.json | 2 +- 4 files changed, 111 insertions(+), 47 deletions(-) diff --git a/apps/sunrise/ChangeLog b/apps/sunrise/ChangeLog index 19e3bb33b..b2c5a2ae1 100644 --- a/apps/sunrise/ChangeLog +++ b/apps/sunrise/ChangeLog @@ -1,2 +1,3 @@ 0.01: First release 0.02: Faster sinus line and fix button to open menu +0.03: Show day/month, add animations, fix !mylocation and text glitch diff --git a/apps/sunrise/README.md b/apps/sunrise/README.md index 7a60b681d..bafe9f76c 100644 --- a/apps/sunrise/README.md +++ b/apps/sunrise/README.md @@ -6,17 +6,15 @@ This is a work-in-progress app, so you may expect missfeatures, bugs and heavy battery draining. There's still a lot of things to optimize and improve, so take this into account before complaining :-) -* Lat/Lon hardcoded to Barcelona, but it's configurable if you install the "mylocation app" +* Requires to configure the location in Settings -> Apps -> My Location * Shows sea level and make the sun/moon glow depending on the x position * The sinus is fixed, so the sea level is curved to match the sunrise/sunset positions) ## TODO -* Access GPS on first start to cache lat+lon to compute sunrise/sunset times -* Cache constant values once -* Show month and day too? -* Improved gradients +* Improved gradients and add support for banglejs1 * Faster rendering, by reducing sinus stepsize, only refreshing whats needed, etc +* Show red vertical lines or dots inside the sinus if there are alarms ## Author diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js index 57fb1c2dc..fd6274129 100644 --- a/apps/sunrise/app.js +++ b/apps/sunrise/app.js @@ -4,18 +4,20 @@ const LOCATION_FILE = 'mylocation.json'; let location; +Bangle.setUI('clock'); +Bangle.loadWidgets(); // requires the myLocation app function loadLocation () { try { return require('Storage').readJSON(LOCATION_FILE, 1); } catch (e) { - return { }; + return { lat: 41.38, lon: 2.168 }; } } - -Bangle.setUI("clock"); -Bangle.loadWidgets(); -const latlon = loadLocation(); +let frames = 0; // amount of pending frames to render (0 if none) +let curPos = 0; // x position of the sun +let realPos = 0; // x position of the sun depending on currentime +const latlon = loadLocation() || {}; const lat = latlon.lat || 41.38; const lon = latlon.lon || 2.168; @@ -172,11 +174,6 @@ const w = g.getWidth(); const h = g.getHeight(); const oy = h / 1.7; -function ypos (x) { - const pc = (x * 100 / w); - return oy + (32 * Math.sin(1.7 + (pc / 16))); -} - let sunRiseX = 0; let sunSetX = 0; const sinStep = 12; @@ -201,21 +198,27 @@ function drawSinuses () { } // sea level line - const sl0 = seaLevel(sunrise.getHours()); - const sl1 = seaLevel(sunset.getHours()); - sunRiseX = xfromTime(sunrise.getHours()); - sunSetX = xfromTime(sunset.getHours()); - g.setColor(0.5, 0.5, 1); + const hh0 = sunrise.getHours(); + const hh1 = sunset.getHours(); + const sl0 = seaLevel(hh0); + const sl1 = seaLevel(hh1); + sunRiseX = xfromTime(hh0) + (r / 2); + sunSetX = xfromTime(hh1) + (r / 2); + g.setColor(0, 0, 1); g.drawLine(0, sl0, w, sl1); g.setColor(0, 0, 1); g.drawLine(0, sl0 + 1, w, sl1 + 1); + /* + g.setColor(0, 0, 1); + g.drawLine(0, sl0 + 1, w, sl1 + 1); g.setColor(0, 0, 0.5); g.drawLine(0, sl0 + 2, w, sl1 + 2); + */ } function drawTimes () { g.setColor(1, 1, 1); - g.setFont('Vector', 20); + g.setFont('6x8', 2); g.drawString(sr, 10, h - 20); g.drawString(ss, w - 60, h - 20); } @@ -226,17 +229,23 @@ const r = 10; function drawGlow () { const now = new Date(); - if (realTime) { + if (frames < 1 && realTime) { pos = xfromTime(now.getHours()); } + const rh = r / 2; const x = pos; - const y = ypos(x - (r / 2)); - const r2 = (x > sunRiseX && x < sunSetX) ? r + 20 : r + 10; - - g.setColor(0.3, 0.3, 0.3); - g.fillCircle(x, y, r2); - g.setColor(0.5, 0.5, 0.5); - g.fillCircle(x, y, r + 3); + const y = ypos(x - r); + const r2 = 0; + if (x > sunRiseX && x < sunSetX) { + g.setColor(0.2, 0.2, 0); + g.fillCircle(x, y, r + 20); + g.setColor(0.5, 0.5, 0); + // wide glow + } else { + g.setColor(0.2, 0.2, 0); + } + // smol glow + g.fillCircle(x, y, r + 8); } function seaLevel (hour) { @@ -245,6 +254,11 @@ function seaLevel (hour) { return ypos(xfromTime(hour)); } +function ypos (x) { + const pc = (x * 100 / w); + return oy + (32 * Math.sin(1.7 + (pc / 16))); +} + function xfromTime (t) { return (w / 24) * t; } @@ -252,47 +266,66 @@ function xfromTime (t) { function drawBall () { let x = pos; const now = new Date(); - if (realTime) { + if (frames < 1 && realTime) { x = xfromTime(now.getHours()); - pos = x; } - const y = ypos(x - (r / 2)); + const y = ypos(x - r); // glow - g.setColor(1, 1, 0); - if (x < sunRiseX) { - g.setColor(0.2, 0.2, 0); - } else if (x > sunSetX) { - g.setColor(0.2, 0.2, 0); + if (x < sunRiseX || x > sunSetX) { + g.setColor(0.5, 0.5, 0); + } else { + g.setColor(1, 1, 1); } + const rh = r / 2; g.fillCircle(x, y, r); - g.setColor(1, 1, 1); + g.setColor(1, 1, 0); g.drawCircle(x, y, r); +} +function drawClock () { + const now = new Date(); + let curTime = ''; let fhours = 0.0; let fmins = 0.0; + let ypos = 32; if (realTime) { fhours = now.getHours(); fmins = now.getMinutes(); } else { + ypos = 32; fhours = 24 * (pos / w); if (fhours > 23) { fhours = 0; } - fmins = (24 - fhours) % 60; + const nexth = 24 * 60 * (pos / w); + fmins = 59 - ((24 * 60) - nexth) % 60; if (fmins < 0) { fmins = 0; } } + if (fmins > 59) { + fmins = 59; + } const hours = ((fhours < 10) ? '0' : '') + (0 | fhours); const mins = ((fmins < 10) ? '0' : '') + (0 | fmins); curTime = hours + ':' + mins; g.setFont('Vector', 30); g.setColor(1, 1, 1); - g.drawString(curTime, w / 1.9, 32); + g.drawString(curTime, w / 1.9, ypos); + // day-month + if (realTime) { + const mo = now.getMonth() + 1; + const da = now.getDate(); + const daymonth = '' + da + '/' + mo; + g.setFont('6x8', 2); + g.drawString(daymonth, 5, 30); + } } function renderScreen () { + realPos = xfromTime((new Date()).getHours()); + g.setFontAlign(-1, -1, 0); g.setBgColor(0, 0, 0); g.clear(); if (realTime) { @@ -301,23 +334,55 @@ function renderScreen () { drawGlow(); drawSinuses(); drawTimes(); + drawClock(); drawBall(); } Bangle.on('drag', function (tap, top) { - pos = tap.x; - realTime = false; + if (tap.y < h / 3) { + curPos = pos; + initialAnimation(); + } else { + pos = tap.x - 5; + realTime = false; + } renderScreen(); }); Bangle.on('lock', () => { + // TODO: render animation here realTime = Bangle.isLocked(); renderScreen(); }); -Bangle.on('tap', () => { - realTime = true; - renderScreen(); -}); + renderScreen(); +realPos = xfromTime((new Date()).getHours()); + +function initialAnimationFrame () { + curPos += (realPos - curPos) / 3; + pos = curPos; + renderScreen(); + if (curPos >= realPos) { + frame = 0; + } + frames--; + if (frames-- > 0) { + setTimeout(initialAnimationFrame, 50); + } else { + realTime = true; + renderScreen(); + } +} + +function initialAnimation () { + const distance = Math.abs(realPos - pos); + frames = distance / 4; + realTime = false; + initialAnimationFrame(); +} + setInterval(renderScreen, 60 * 1000); + +pos = 0; +initialAnimation(); diff --git a/apps/sunrise/metadata.json b/apps/sunrise/metadata.json index f3012144a..d9d7533b6 100644 --- a/apps/sunrise/metadata.json +++ b/apps/sunrise/metadata.json @@ -2,7 +2,7 @@ "id": "sunrise", "name": "Sunrise", "shortName": "Sunrise", - "version": "0.02", + "version": "0.03", "type": "clock", "description": "Show sunrise and sunset times", "icon": "app.png", From 0535229f53adf2c312d96285ad375d77f8ad135d Mon Sep 17 00:00:00 2001 From: Luc Tielen Date: Fri, 9 Jun 2023 20:45:16 +0200 Subject: [PATCH 092/116] sunrise 0.04 --- apps/sunrise/ChangeLog | 1 + apps/sunrise/app.js | 37 +++++++++++++++++++++++++------------ apps/sunrise/metadata.json | 2 +- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/sunrise/ChangeLog b/apps/sunrise/ChangeLog index b2c5a2ae1..490992812 100644 --- a/apps/sunrise/ChangeLog +++ b/apps/sunrise/ChangeLog @@ -1,3 +1,4 @@ 0.01: First release 0.02: Faster sinus line and fix button to open menu 0.03: Show day/month, add animations, fix !mylocation and text glitch +0.04: Always show the widgets, swifter animations and lighter sea line diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js index fd6274129..3feb4dfd4 100644 --- a/apps/sunrise/app.js +++ b/apps/sunrise/app.js @@ -204,9 +204,9 @@ function drawSinuses () { const sl1 = seaLevel(hh1); sunRiseX = xfromTime(hh0) + (r / 2); sunSetX = xfromTime(hh1) + (r / 2); - g.setColor(0, 0, 1); + g.setColor(0, 0.5, 1); g.drawLine(0, sl0, w, sl1); - g.setColor(0, 0, 1); + g.setColor(0, 0.5, 1); g.drawLine(0, sl0 + 1, w, sl1 + 1); /* g.setColor(0, 0, 1); @@ -311,7 +311,11 @@ function drawClock () { const mins = ((fmins < 10) ? '0' : '') + (0 | fmins); curTime = hours + ':' + mins; g.setFont('Vector', 30); - g.setColor(1, 1, 1); + if (realTime) { + g.setColor(1, 1, 1); + } else { + g.setColor(0, 1, 1); + } g.drawString(curTime, w / 1.9, ypos); // day-month if (realTime) { @@ -324,13 +328,13 @@ function drawClock () { } function renderScreen () { + g.setColor(0, 0, 0); + g.fillRect(0, 30, w, h); realPos = xfromTime((new Date()).getHours()); g.setFontAlign(-1, -1, 0); - g.setBgColor(0, 0, 0); - g.clear(); - if (realTime) { - Bangle.drawWidgets(); - } + + Bangle.drawWidgets(); + drawGlow(); drawSinuses(); drawTimes(); @@ -360,7 +364,11 @@ renderScreen(); realPos = xfromTime((new Date()).getHours()); function initialAnimationFrame () { - curPos += (realPos - curPos) / 3; + let distance = (realPos - curPos) / 4; + if (distance > 20) { + distance = 20; + } + curPos += distance; pos = curPos; renderScreen(); if (curPos >= realPos) { @@ -382,7 +390,12 @@ function initialAnimation () { initialAnimationFrame(); } -setInterval(renderScreen, 60 * 1000); +function main () { + g.setBgColor(0, 0, 0); + g.clear(); + setInterval(renderScreen, 60 * 1000); + pos = 0; + initialAnimation(); +} -pos = 0; -initialAnimation(); +main(); diff --git a/apps/sunrise/metadata.json b/apps/sunrise/metadata.json index d9d7533b6..051ff99bd 100644 --- a/apps/sunrise/metadata.json +++ b/apps/sunrise/metadata.json @@ -2,7 +2,7 @@ "id": "sunrise", "name": "Sunrise", "shortName": "Sunrise", - "version": "0.03", + "version": "0.04", "type": "clock", "description": "Show sunrise and sunset times", "icon": "app.png", From 8271b7472dab8dd26b20f6c6d67924d6b6a71273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=BCsgen?= Date: Fri, 9 Jun 2023 20:50:51 +0200 Subject: [PATCH 093/116] feat: :sparkles: tune service advertisement so apps like OpenTracks can find the hrm service --- apps/bootgatthrm/boot.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/bootgatthrm/boot.js b/apps/bootgatthrm/boot.js index 391f245b9..aad210f6f 100644 --- a/apps/bootgatthrm/boot.js +++ b/apps/bootgatthrm/boot.js @@ -4,14 +4,30 @@ * This function prepares BLE heart rate Advertisement. */ + NRF.setAdvertising( + { + 0x180d: undefined + }, + { + // We need custom Advertisement settings for Apps like OpenTracks + connectable: true, + discoverable: true, + scannable: true, + whenConnected: true, + } + ); + NRF.setServices({ 0x180D: { // heart_rate 0x2A37: { // heart_rate_measurement notify: true, value: [0x06, 0], + }, + 0x2A38: { // Sensor Location: Wrist + value: 0x02, } } - }, { advertise: [0x180d] }); + }); } function updateBLEHeartRate(hrm) { @@ -23,11 +39,11 @@ NRF.updateServices({ 0x180D: { 0x2A37: { - value: [ - 0x06, // - hrm.bpm - ], + value: [0x06, hrm.bpm], notify: true + }, + 0x2A38: { + value: 0x02, } } }); From ef197a5133ea37ba7a881b34587d5ec2a1dee1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=BCsgen?= Date: Sat, 10 Jun 2023 12:36:59 +0200 Subject: [PATCH 094/116] docs: :bookmark: Increment version number and add ChangeLog --- apps/bootgatthrm/ChangeLog | 1 + apps/bootgatthrm/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/bootgatthrm/ChangeLog b/apps/bootgatthrm/ChangeLog index 2a37193a3..1e772af29 100644 --- a/apps/bootgatthrm/ChangeLog +++ b/apps/bootgatthrm/ChangeLog @@ -1 +1,2 @@ 0.01: Initial release. +0.02: Added compatibility to OpenTracks and added HRM Location \ No newline at end of file diff --git a/apps/bootgatthrm/metadata.json b/apps/bootgatthrm/metadata.json index e7bfb0762..450066622 100644 --- a/apps/bootgatthrm/metadata.json +++ b/apps/bootgatthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bootgatthrm", "name": "BLE GATT HRM Service", "shortName": "BLE HRM Service", - "version": "0.01", + "version": "0.02", "description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n", "icon": "bluetooth.png", "type": "bootloader", From 238542669911d81b55706a4176bc47cd2786b0c6 Mon Sep 17 00:00:00 2001 From: Philip Andresen Date: Sat, 10 Jun 2023 11:14:04 -0400 Subject: [PATCH 095/116] Implement Matryoshka Keyboard --- apps/kbmatry/ChangeLog | 7 + apps/kbmatry/README.md | 119 +++++++++ apps/kbmatry/app-icon.js | 1 + apps/kbmatry/help.png | Bin 0 -> 5578 bytes apps/kbmatry/icon.png | Bin 0 -> 852 bytes apps/kbmatry/lib.js | 489 +++++++++++++++++++++++++++++++++++ apps/kbmatry/metadata.json | 14 + apps/kbmatry/screenshot.png | Bin 0 -> 3562 bytes apps/kbmatry/screenshot2.png | Bin 0 -> 2977 bytes apps/kbmatry/screenshot3.png | Bin 0 -> 3359 bytes apps/kbmatry/screenshot4.png | Bin 0 -> 2989 bytes apps/kbmatry/screenshot5.png | Bin 0 -> 3458 bytes apps/kbmatry/screenshot6.png | Bin 0 -> 3576 bytes 13 files changed, 630 insertions(+) create mode 100644 apps/kbmatry/ChangeLog create mode 100644 apps/kbmatry/README.md create mode 100644 apps/kbmatry/app-icon.js create mode 100644 apps/kbmatry/help.png create mode 100644 apps/kbmatry/icon.png create mode 100644 apps/kbmatry/lib.js create mode 100644 apps/kbmatry/metadata.json create mode 100644 apps/kbmatry/screenshot.png create mode 100644 apps/kbmatry/screenshot2.png create mode 100644 apps/kbmatry/screenshot3.png create mode 100644 apps/kbmatry/screenshot4.png create mode 100644 apps/kbmatry/screenshot5.png create mode 100644 apps/kbmatry/screenshot6.png diff --git a/apps/kbmatry/ChangeLog b/apps/kbmatry/ChangeLog new file mode 100644 index 000000000..98d0187ab --- /dev/null +++ b/apps/kbmatry/ChangeLog @@ -0,0 +1,7 @@ +1.00: New keyboard +1.01: Change swipe interface to taps, speed up responses (efficiency tweaks). +1.02: Generalize drawing and letter scaling. Allow custom and auto-generated character sets. Improve documentation. +1.03: Attempt to improve keyboard load time. +1.04: Make code asynchronous and improve load time. +1.05: Fix layout issue and rename library +1.06: Touch up readme, prep for IPO, add screenshots \ No newline at end of file diff --git a/apps/kbmatry/README.md b/apps/kbmatry/README.md new file mode 100644 index 000000000..73767828d --- /dev/null +++ b/apps/kbmatry/README.md @@ -0,0 +1,119 @@ +# Matryoshka Keyboard + +![icon](icon.png) + +![screenshot](screenshot.png) ![screenshot](screenshot6.png) + +![screenshot](screenshot5.png) ![screenshot](screenshot2.png) +![screenshot](screenshot3.png) ![screenshot](screenshot4.png) + +Nested key input utility. + +## How to type + +Press your finger down on the letter group that contains the character you would like to type, then tap the letter you +want to enter. Once you are touching the letter you want, release your +finger. + +![help](help.png) + +Press "shft" or "caps" to access alternative characters, including upper case letters, punctuation, and special +characters. +Pressing "shft" also reveals a cancel button if you would like to terminate input without saving. + +Press "ok" to finish typing and send your text to whatever app called this keyboard. + +Press "del" to delete the leftmost character. + +## Themes and Colors + +This keyboard will attempt to use whatever theme or colorscheme is being used by your Bangle device. + +## How to use in a program + +This was developed to match the interface implemented for kbtouch, kbswipe, etc. + +In your app's metadata, add: + +```json + "dependencies": {"textinput": "type"} +``` + +From inside your app, call: + +```js +const textInput = require("textinput"); + +textInput.input({text: ""}) + .then(result => { + console.log("The user entered: ", result); + }); +``` + +Alternatively, if you want to improve the load time of the keyboard, you can pre-generate the data the keyboard needs +to function and render like so: + +```js +const textInput = require("textinput"); + +const defaultKeyboard = textInput.generateKeyboard(textInput.defaultCharSet); +const defaultShiftKeyboard = textInput.generateKeyboard(textInput.defaultCharSetShift); +// ... +textInput.input({text: "", keyboardMain: defaultKeyboard, keyboardShift: defaultShiftKeyboard}) + .then(result => { + console.log("The user entered: ", result); + // And it was faster! + }); +``` + +This isn't required, but if you are using a large character set, and the user is interacting with the keyboard a lot, +it can really smooth the experience. + +The default keyboard has a full set of alphanumeric characters as well as special characters and buttons in a +pre-defined layout. If your application needs something different, or you want to have a custom layout, you can do so: + +```js +const textInput = require("textinput"); + +const customKeyboard = textInput.generateKeyboard([ + ["1", "2", "3", "4"], ["5", "6", "7", "8"], ["9", "0", ".", "-"], "ok", "del", "cncl" +]); +// ... +textInput.input({text: "", keyboardMain: customKeyboard}) + .then(result => { + console.log("The user entered: ", result); + // And they could only enter numbers, periods, and dashes! + }); +``` + +This will give you a keyboard with six buttons. The first three buttons will open up a 2x2 keyboard. The second three +buttons are special keys for submitting, deleting, and cancelling respectively. + +Finally if you are like, super lazy, or have a dynamic set of keys you want to be using at any given time, you can +generate keysets from strings like so: + +```js +const textInput = require("textinput"); + +const customKeyboard = textInput.generateKeyboard(createCharSet("ABCDEFGHIJKLMNOP", ["ok", "shft", "cncl"])); +const customShiftKeyboard = textInput.generateKeyboard(createCharSet("abcdefghijklmnop", ["ok", "shft", "cncl"])); +// ... +textInput.input({text: "", keyboardMain: customKeyboard, keyboardShift: customShiftKeyboard}) + .then(result => { + console.log("The user entered: ", result); + // And the keyboard was automatically generated to include "ABCDEFGHIJKLMNOP" plus an OK button, a shift button, and a cancel button! + }); +``` + +The promise resolves when the user hits "ok" on the input or if they cancel. If the user cancels, undefined is +returned, although the user can hit "OK" with an empty string as well. If you define a custom character set and +do not include the "ok" button your user will be soft-locked by the keyboard. Fair warning! + +At some point I may add swipe-for-space and swipe-for-delete as well as swipe-for-submit and swipe-for-cancel +however I want to have a good strategy for the touch screen +[affordance](https://careerfoundry.com/en/blog/ux-design/affordances-ux-design/). + +## Secret features + +If you long press a key with characters on it, that will enable "Shift" mode. + diff --git a/apps/kbmatry/app-icon.js b/apps/kbmatry/app-icon.js new file mode 100644 index 000000000..a4b0ecc16 --- /dev/null +++ b/apps/kbmatry/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcBkmSpICVz//ABARGCBIRByA/Dk+AAgUH8AECgP4kmRCwX4n+PAoXH8YEC+IRC4HguE4/+P/EfCIXwgARHn4RG+P/j4RDJwgRBGQIRIEYNxCIRECGpV/CIXAgY1P4/8v41JOgeOn4RDGo4jER5Y1FCJWQg4RDYpeSNIQAMkmTCBwRBz4IG9YRIyA8COgJHBhMgI4+QyVJAYJrC9Mkw5rHwFAkEQCImSCJvAhIRBpazFGo3HEYVJkIjGCIIUCAQu/CKGSGo4jPLIhHMNayPLYo6zBYozpH9MvdI+TfaGSv4KHCI+Qg4GDI4IABg5HGyIYENYIAB45rGyPACKIIDx/4gF/CIPx/8fCIY1F4H8CJPA8BtCa4I1DCJFxCIYXBCILXBGpXHGplwn5HPuE4NaH4n6PLyC6CgEnYpeSpICDdJYRFz4RQARQ")) \ No newline at end of file diff --git a/apps/kbmatry/help.png b/apps/kbmatry/help.png new file mode 100644 index 0000000000000000000000000000000000000000..6eef5694b06410b6cf8af39f59702b5b5bb441e6 GIT binary patch literal 5578 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 literal 0 HcmV?d00001 diff --git a/apps/kbmatry/icon.png b/apps/kbmatry/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..058df4487310ce3148fd3b3af424639896ea742b GIT binary patch literal 852 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!phSslL`iUdT1k0gQ7S`0VrE{6US4X6f{C7? zo@t7E1kf6$qn<8~AsNnBrzI9W4&Z36o@ML%{>mMjwH;5n7NtM_TD4K9e#flks&y^$xi7jCm+d>ct7T>G#G04}g_~C|?f&7rezxMj>s%w4!srKy65vw$yKkMO#^wA z3p*a!E&IP}Lh-(fJga^M_+Ah=>Uf4x^qa%t8J8@#hOcz;t9{{dLBv(~@|y&Hm;S2I z6TLNB-^1irb!G)zv~b#6+E;!4!@^k$6uX2jZ)yD&es#{dD^f{Ixh%gfRCWv&nLeY< z_Qridu(Ou(B^S3FcbA0LzJ8=Ki96@bqI-P7HD4JHE!%%CTAx2|)1T+(Gp5f!c$x9n z_G^l~#-$C+9nZekC{H>s`{DiScD~0B>b4I2H$Pj3#z*}N{2#kcxcJ63rFRUQo)%xq zSMob;|EicTLB60nT;pq_){`jqk_(CN7*6ThIMw?7@;&nU5sRnZ@f)=-vfK{&E}!CyUdwZf2Dr?`vZxu9h#$Me%jvec(R&(gN1PHovY_RlpGLskJ)Ikk)7fC zAMwhlJAc=f2IjYG+LbqSot0fx?p-=@-KzZ9IRz0YVFQdZ4MX4P4f7xIEIM@iXUu$1 zT!b`t9TR-j`|#=7L)A}$-~I%}4kRL$Y!rSaJHL80|5X;=K$#;l0g3z}$+gZ_$G>W6 z?YY{XwMfeCk$@{w5WUk zRJ8r^it;x7X< num - mandatoryExtraKeys); + let keyIndex = 0, charIndex = 0; + let keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex]; + while (keySpace < text.length) { + const numKeys = preferredNumKeys[keyIndex]; + const numChars = preferredNumChars[charIndex]; + const nextNumKeys = preferredNumKeys[keyIndex]; + const nextNumChars = preferredNumChars[charIndex]; + if (numChars <= numKeys) { + charIndex++; + } else if ((text.length / nextNumChars) < nextNumKeys) { + charIndex++; + } else { + keyIndex++; + } + keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex]; + } + const charsPerKey = preferredNumChars[charIndex]; + let charSet = []; + for (let i = 0; i < text.length; i += charsPerKey) { + charSet.push(text.slice(i, i + charsPerKey) + .split("")); + } + charSet = charSet.concat(specials); + return charSet; +} + +/** + * Given the width, height, padding (between chars) and number of characters that need to fit horizontally / + * vertically, this function attempts to select the largest font it can that will still fit within the bounds when + * drawing a grid of characters. Does not handle multi-letter entries well, assumes we are laying out a grid of + * single characters. + * @param width The total width available for letters (px) + * @param height The total height available for letters (px) + * @param padding The amount of space required between characters (px) + * @param gridWidth The number of characters wide the rendering is going to be + * @param gridHeight The number of characters high the rendering is going to be + * @returns {{w: number, h: number, font: string}} + */ +function getBestFont(width, height, padding, gridWidth, gridHeight) { + let font = "4x6"; + let w = 4; + let h = 6; + const charMaxWidth = width / gridWidth - padding * gridWidth; + const charMaxHeight = height / gridHeight - padding * gridHeight; + if (charMaxWidth >= 6 && charMaxHeight >= 8) { + w = 6; + h = 8; + font = "6x8"; + } + if (charMaxWidth >= 12 && charMaxHeight >= 16) { + w = 12; + h = 16; + font = "6x8:2"; + } + if (charMaxWidth >= 12 && charMaxHeight >= 20) { + w = 12; + h = 20; + font = "12x20"; + } + if (charMaxWidth >= 20 && charMaxHeight >= 20) { + font = "Vector" + Math.floor(Math.min(charMaxWidth, charMaxHeight)); + const dims = g.setFont(font) + .stringMetrics("W"); + w = dims.width + h = dims.height; + } + return {w, h, font}; +} + + +/** + * Generate a set of key objects given an array of arrays of characters to make available for typing. + * @param characterArrays + * @returns {Promise} + */ +function getKeys(characterArrays) { + if (Array.isArray(characterArrays)) { + return Promise.all(characterArrays.map((chars, i) => generateKeyFromChars(characterArrays, i))); + } else { + return generateKeyFromChars(characterArrays, 0); + } +} + +function generateKeyFromChars(chars, i) { + return new Promise((resolve, reject) => { + let special; + if (!Array.isArray(chars[i]) && chars[i].length > 1) { + // If it's not an array we assume it's a string. Fingers crossed I guess, lol. Be nice to my functions! + special = chars[i]; + } + const key = getKeyByIndex(chars, i, special); + if (!special) { + key.chars = chars[i]; + } + if (key.chars.length > 1) { + key.pendingSubKeys = true; + key.getSubKeys = () => getKeys(key.chars); + resolve(key) + } else { + resolve(key); + } + }) +} + + +/** + * Given a set of characters (or sets of characters) get the position and dimensions of the i'th key in that set. + * @param charSet An array where each element represents a key on the hypothetical keyboard. + * @param i The index of the key in the set you want to get dimensions for. + * @param special The special property of the key - for example "del" for a key used for deleting characters. + * @returns {{special, bord: number, pad: number, w: number, x: number, h: number, y: number, chars: *[]}} + */ +function getKeyByIndex(charSet, i, special) { + // Key dimensions + const keyboardOffsetY = 40; + const margin = 3; + const padding = 4; + const border = 2; + const gridWidth = Math.ceil(Math.sqrt(charSet.length)); + const gridHeight = Math.ceil(charSet.length / gridWidth); + const keyWidth = Math.floor((g.getWidth()) / gridWidth) - margin; + const keyHeight = Math.floor((g.getHeight() - keyboardOffsetY) / gridHeight) - margin; + const gridx = i % gridWidth; + const gridy = Math.floor(i / gridWidth) % gridWidth; + const x = gridx * (keyWidth + margin); + const y = gridy * (keyHeight + margin) + keyboardOffsetY; + const w = keyWidth; + const h = keyHeight; + // internal Character spacing + const numChars = charSet[i].length; + const subGridWidth = Math.ceil(Math.sqrt(numChars)); + const subGridHeight = Math.ceil(numChars / subGridWidth); + const bestFont = getBestFont(w - padding, h - padding, 0, subGridWidth, subGridHeight); + const letterWidth = bestFont.w; + const letterHeight = bestFont.h; + const totalWidth = (subGridWidth - 1) * (w / subGridWidth) + padding + letterWidth + 1; + const totalHeight = (subGridHeight - 1) * (h / subGridHeight) + padding + letterHeight + 1; + const extraPadH = (w - totalWidth) / 2; + const extraPadV = (h - totalHeight) / 2; + return { + x, + y, + w, + h, + pad : padding, + bord : border, + chars: [], + special, + subGridWidth, + subGridHeight, + extraPadH, + extraPadV, + font : bestFont.font + }; +} + + +/** + * This is probably the most intense part of this keyboard library. If you don't do it ahead of time, it will happen + * when you call the keyboard, and it can take up to 1.5 seconds for a full keyboard. Not a super great user experience + * SO if you have a tiny keyset, don't worry about it so much, but if you want to maximize performance, generate + * a keyboard ahead of time and pass it into the "keyboard" argument of the object in the "input" function. + * @param charSets + * @returns {Promise} + */ +function generateKeyboard(charSets) { + if (!Array.isArray(charSets)) { + // User passed a string. We will divvy it up into a real set of subdivided characters. + charSets = createCharSet(charSets, ["ok", "del", "shft"]); + } + return getKeys(charSets); +} + +const defaultCharSet = [ + ["a", "b", "c", "d", "e", "f", "g", "h", "i"], + ["j", "k", "l", "m", "n", "o", "p", "q", "r"], + ["s", "t", "u", "v", "w", "x", "y", "z", "0"], + ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + [" ", "`", "-", "=", "[", "]", "\\", ";", "'"], + [",", ".", "/"], + "ok", + "shft", + "del" +]; + +const defaultCharSetShift = [ + ["A", "B", "C", "D", "E", "F", "G", "H", "I"], + ["J", "K", "L", "M", "N", "O", "P", "Q", "R"], + ["S", "T", "U", "V", "W", "X", "Y", "Z", ")"], + ["!", "@", "#", "$", "%", "^", "&", "*", "("], + ["~", "_", "+", "{", "}", "|", ":", "\"", "<"], + [">", "?"], + "ok", + "shft", + "del" +]; + +/** + * Given initial options, allow the user to type a set of characters and return their entry in a promise. If you do not + * submit your own character set, a default alphanumeric keyboard will display. + * @param options The object containing initial options for the keyboard. + * @param {string} options.text The initial text to display / edit in the keyboard + * @param {array[]|string[]} [options.keyboardMain] The primary keyboard generated with generateKeyboard() + * @param {array[]|string[]} [options.keyboardShift] Like keyboardMain, but displayed when shift / capslock is pressed. + * @returns {Promise} + */ +function input(options) { + options = options || {}; + let typed = options.text || ""; + let resolveFunction = () => {}; + let shift = false; + let caps = false; + let activeKeySet; + + const offsetX = 0; + const offsetY = 40; + + E.showMessage("Loading..."); + let keyboardPromise; + if (options.keyboardMain) { + keyboardPromise = Promise.all([options.keyboardMain, options.keyboardShift || Promise.resolve([])]); + } else { + keyboardPromise = Promise.all([generateKeyboard(defaultCharSet), generateKeyboard(defaultCharSetShift)]) + } + + let mainKeys; + let mainKeysShift; + + /** + * Draw an individual keyboard key - handles special formatting and the rectangle pad, followed by the character + * rendering. Returns a promise so that you can do many of these in a loop without blocking the thread. + * @param key + */ + function drawKey(key) { + let bgColor = g.theme.bg; + if (key.special) { + if (key.special === "ok") bgColor = "#0F0"; + if (key.special === "cncl") bgColor = "#F00"; + if (key.special === "del") bgColor = g.theme.bg2; + if (key.special === "spc") bgColor = g.theme.bg2; + if (key.special === "shft") { + bgColor = shift ? g.theme.bgH : g.theme.bg2; + } + if (key.special === "caps") { + bgColor = caps ? g.theme.bgH : g.theme.bg2; + } + g.setColor(bgColor) + .fillRect({x: key.x, y: key.y, w: key.w, h: key.h}); + } + g.setColor(g.theme.fg) + .drawRect({x: key.x, y: key.y, w: key.w, h: key.h}); + drawChars(key); + } + + /** + * Draw the characters for a given key - this handles the layout of all characters needed for the key, whether the + * key has 12 characters, 1, or if it represents a special key. + * @param key + */ + function drawChars(key) { + const numChars = key.chars.length; + if (key.special) { + g.setColor(g.theme.fg) + .setFont("12x20") + .setFontAlign(-1, -1) + .drawString(key.special, key.x + key.w / 2 - g.stringWidth(key.special) / 2, key.y + key.h / 2 - 10, false); + } else { + g.setColor(g.theme.fg) + .setFont(key.font) + .setFontAlign(-1, -1); + for (let i = 0; i < numChars; i++) { + const gridX = i % key.subGridWidth; + const gridY = Math.floor(i / key.subGridWidth) % key.subGridWidth; + const charOffsetX = gridX * (key.w / key.subGridWidth); + const charOffsetY = gridY * (key.h / key.subGridHeight); + const posX = key.x + key.pad + charOffsetX + key.extraPadH; + const posY = key.y + key.pad + charOffsetY + key.extraPadV; + g.drawString(key.chars[i], posX, posY, false); + } + } + } + + /** + * Get the key set corresponding to the indicated shift state. Allows easy switching between capital letters and + * lower case by just switching the boolean passed here. + * @param shift + * @returns {*[]} + */ + function getMainKeySet(shift) { + return shift ? mainKeysShift : mainKeys; + } + + /** + * Draw all the given keys on the screen. + * @param keys + */ + function drawKeys(keys) { + keys.forEach(key => { + drawKey(key); + }); + } + + /** + * Draw the text that the user has typed so far, includes a cursor and automatic truncation when the string is too + * long. + * @param text + * @param cursorChar + */ + function drawTyped(text, cursorChar) { + let visibleText = text; + let ellipsis = false; + const maxWidth = 176 - 40; + while (g.setFont("12x20") + .stringWidth(visibleText) > maxWidth) { + ellipsis = true; + visibleText = visibleText.slice(1); + } + if (ellipsis) { + visibleText = "..." + visibleText; + } + g.setColor(g.theme.bg2) + .fillRect(5, 5, 171, 30); + g.setColor(g.theme.fg2) + .setFont("12x20") + .drawString(visibleText + cursorChar, 15, 10, false); + } + + /** + * Clear the space on the screen that the keyboard occupies (not the text the user has written). + */ + function clearKeySpace() { + g.setColor(g.theme.bg) + .fillRect(offsetX, offsetY, 176, 176); + } + + /** + * Based on a touch even, determine which key was pressed by the user. + * @param touchEvent + * @param keys + * @returns {*} + */ + function getTouchedKey(touchEvent, keys) { + return keys.find((key) => { + let relX = touchEvent.x - key.x; + let relY = touchEvent.y - key.y; + return relX > 0 && relX < key.w && relY > 0 && relY < key.h; + }) + } + + /** + * On a touch event, determine whether a key is touched and take appropriate action if it is. + * @param button + * @param touchEvent + */ + function keyTouch(button, touchEvent) { + const pressedKey = getTouchedKey(touchEvent, activeKeySet); + if (pressedKey == null) { + // User tapped empty space. + swapKeySet(getMainKeySet(shift !== caps)); + return; + } + if (pressedKey.pendingSubKeys) { + // We have to generate the subkeys for this key still, but we decided to wait until we needed it! + pressedKey.pendingSubKeys = false; + pressedKey.getSubKeys() + .then(subkeys => { + pressedKey.subKeys = subkeys; + keyTouch(undefined, touchEvent); + }) + return; + } + // Haptic feedback + Bangle.buzz(25, 1); + if (pressedKey.subKeys) { + // Hold press for "shift!" + if (touchEvent.type > 1) { + shift = !shift; + swapKeySet(getMainKeySet(shift !== caps)); + } else { + swapKeySet(pressedKey.subKeys); + } + } else { + if (pressedKey.special) { + evaluateSpecialFunctions(pressedKey); + } else { + typed = typed + pressedKey.chars; + shift = false; + drawTyped(typed, ""); + swapKeySet(getMainKeySet(shift !== caps)); + } + } + } + + /** + * Manage setting, generating, and rendering new keys when a key set is changed. + * @param newKeys + */ + function swapKeySet(newKeys) { + activeKeySet = newKeys; + clearKeySpace(); + drawKeys(activeKeySet); + } + + /** + * Determine if the key contains any of the special strings that have their own special behaviour when pressed. + * @param key + */ + function evaluateSpecialFunctions(key) { + switch (key.special) { + case "ok": + setTimeout(() => resolveFunction(typed), 50); + break; + case "del": + typed = typed.slice(0, -1); + drawTyped(typed, ""); + break; + case "shft": + shift = !shift; + swapKeySet(getMainKeySet(shift !== caps)); + break; + case "caps": + caps = !caps; + swapKeySet(getMainKeySet(shift !== caps)); + break; + case "cncl": + setTimeout(() => resolveFunction(), 50); + break; + case "spc": + typed = typed + " "; + break; + } + } + + let isCursorVisible = true; + + const blinkInterval = setInterval(() => { + if (!activeKeySet) return; + isCursorVisible = !isCursorVisible; + if (isCursorVisible) { + drawTyped(typed, "_"); + } else { + drawTyped(typed, ""); + } + }, 200); + + + /** + * We return a promise but the resolve function is assigned to a variable in the higher function scope. That allows + * us to return the promise and resolve it after we are done typing without having to return the entire scope of the + * application within the promise. + */ + return new Promise((resolve, reject) => { + g.clear(true); + resolveFunction = resolve; + keyboardPromise.then((result) => { + mainKeys = result[0]; + mainKeysShift = result[1]; + swapKeySet(getMainKeySet(shift !== caps)); + Bangle.setUI({ + mode: "custom", touch: keyTouch + }); + Bangle.setLocked(false); + }) + }).then((result) => { + g.clearRect(Bangle.appRect); + clearInterval(blinkInterval); + Bangle.setUI(); + return result; + }); +} + +exports.input = input; +exports.generateKeyboard = generateKeyboard; +exports.defaultCharSet = defaultCharSet; +exports.defaultCharSetShift = defaultCharSetShift; +exports.createCharSet = createCharSet; \ No newline at end of file diff --git a/apps/kbmatry/metadata.json b/apps/kbmatry/metadata.json new file mode 100644 index 000000000..793286180 --- /dev/null +++ b/apps/kbmatry/metadata.json @@ -0,0 +1,14 @@ +{ "id": "kbmatry", + "name": "Matryoshka Keyboard", + "version":"1.06", + "description": "A library for text input via onscreen keyboard. Easily enter characters with nested keyboards.", + "icon": "icon.png", + "type":"textinput", + "tags": "keyboard", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot6.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"},{"url":"screenshot5.png"},{"url": "help.png"}], + "readme": "README.md", + "storage": [ + {"name":"textinput","url":"lib.js"} + ] +} diff --git a/apps/kbmatry/screenshot.png b/apps/kbmatry/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..08bb366e48d3f7b5b1f35d063fba50e2dc97e11a GIT binary patch literal 3562 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 S%sYGD03!o4RJ9)V$$tR#q^i*X literal 0 HcmV?d00001 diff --git a/apps/kbmatry/screenshot2.png b/apps/kbmatry/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..21874244d9b17efb3469606b6423eaa34a070dce GIT binary patch literal 2977 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 literal 0 HcmV?d00001 diff --git a/apps/kbmatry/screenshot3.png b/apps/kbmatry/screenshot3.png new file mode 100644 index 0000000000000000000000000000000000000000..1f0c732657e405ed8e4b0c8264c32f76e272f086 GIT binary patch literal 3359 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 literal 0 HcmV?d00001 diff --git a/apps/kbmatry/screenshot4.png b/apps/kbmatry/screenshot4.png new file mode 100644 index 0000000000000000000000000000000000000000..de2f90bee69987f37b68a5b629cb0d4cc8f15b96 GIT binary patch literal 2989 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

=)K0Lp-la>Tmr)hS0c^U1GDq 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 VzP=*r(cV%85X{b+mKu|y{twp1VzmGO literal 0 HcmV?d00001 diff --git a/apps/kbmatry/screenshot6.png b/apps/kbmatry/screenshot6.png new file mode 100644 index 0000000000000000000000000000000000000000..20de7ddc1c44ec14693caf9dfa5316eb7ebad0f7 GIT binary patch literal 3576 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 y@?+nz!eHyM4De&$vHY_2U|N~u0000 Date: Sat, 10 Jun 2023 11:47:11 -0400 Subject: [PATCH 096/116] kbmatry: Fix jsdoc inaccuracies, add comments --- apps/kbmatry/lib.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/kbmatry/lib.js b/apps/kbmatry/lib.js index 6c32e5a81..a7a434dad 100644 --- a/apps/kbmatry/lib.js +++ b/apps/kbmatry/lib.js @@ -94,6 +94,14 @@ function getKeys(characterArrays) { } } +/** + * Given a set of characters, determine whether or not this needs to be a matryoshka key, a basic key, or a special key. + * Then generate that key. If the key is a matryoshka key, we queue up the generation of its sub-keys for later to + * improve load times. + * @param chars + * @param i + * @returns {Promise} + */ function generateKeyFromChars(chars, i) { return new Promise((resolve, reject) => { let special; @@ -170,9 +178,11 @@ function getKeyByIndex(charSet, i, special) { /** * This is probably the most intense part of this keyboard library. If you don't do it ahead of time, it will happen - * when you call the keyboard, and it can take up to 1.5 seconds for a full keyboard. Not a super great user experience - * SO if you have a tiny keyset, don't worry about it so much, but if you want to maximize performance, generate - * a keyboard ahead of time and pass it into the "keyboard" argument of the object in the "input" function. + * when you call the keyboard, and it can take up to 0.5 seconds for a full alphanumeric keyboard. Depending on what + * is an acceptable user experience for you, and how many keys you are actually generating, you may choose to do this + * ahead of time and pass the result to the "input" function of this library. NOTE: This function would need to be + * called once per key set - so if you have a keyboard with a "shift" key you'd need to run it once for your base + * keyset and once for your shift keyset. * @param charSets * @returns {Promise} */ @@ -184,6 +194,7 @@ function generateKeyboard(charSets) { return getKeys(charSets); } +// Default layout const defaultCharSet = [ ["a", "b", "c", "d", "e", "f", "g", "h", "i"], ["j", "k", "l", "m", "n", "o", "p", "q", "r"], @@ -196,6 +207,7 @@ const defaultCharSet = [ "del" ]; +// Default layout with shift pressed const defaultCharSetShift = [ ["A", "B", "C", "D", "E", "F", "G", "H", "I"], ["J", "K", "L", "M", "N", "O", "P", "Q", "R"], @@ -241,7 +253,7 @@ function input(options) { /** * Draw an individual keyboard key - handles special formatting and the rectangle pad, followed by the character - * rendering. Returns a promise so that you can do many of these in a loop without blocking the thread. + * rendering. * @param key */ function drawKey(key) { @@ -347,7 +359,7 @@ function input(options) { } /** - * Based on a touch even, determine which key was pressed by the user. + * Based on a touch event, determine which key was pressed by the user. * @param touchEvent * @param keys * @returns {*} From c1d7a4ed735c3c2ec91c1e07ced8d271149e6fc2 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sat, 10 Jun 2023 21:30:40 +0100 Subject: [PATCH 097/116] sched: kick off timers on upload --- apps/sched/interface.html | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 5730b5741..b67029fa2 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -86,6 +86,16 @@ function eventToAlarm(event, offsetMs) { } function upload() { + // kick off all the (active) timers + const now = new Date(); + const currentTime = now.getHours()*3600000 + + now.getMinutes()*60000 + + now.getSeconds()*1000; + + for (const alarm of alarms) + if (alarm.timer != undefined && alarm.on) + alarm.t = currentTime + alarm.timer; + Util.showModal("Saving..."); Util.writeStorage("sched.json", JSON.stringify(alarms), () => { Puck.write(`\x10require("sched").reload();\n`, () => { @@ -125,9 +135,7 @@ function renderAlarm(alarm, exists) { tdType.textContent = "Timer"; inputTime.onchange = e => { alarm.timer = hmsToMs(inputTime.value); - const now = new Date(); - const currentTime = (now.getHours()*3600000)+(now.getMinutes()*60000)+(now.getSeconds()*1000); - alarm.t = currentTime + alarm.timer; + // alarm.t is set on upload }; } else { tdType.textContent = "Alarm"; From a3042f206aaca3dab54eb3df3ed515f37a332b8f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 12:40:11 +0100 Subject: [PATCH 098/116] hwid-batt: whitespace --- apps/hwid_a_battery_widget/widget.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index e42c15355..11997a2bd 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -22,8 +22,8 @@ }; function draw() { - if (typeof old_x === 'undefined') old_x = this.x; - if (typeof old_y === 'undefined') old_y = this.y; + if (typeof old_x === 'undefined') old_x = this.x; + if (typeof old_y === 'undefined') old_y = this.y; var s = 29; var x = this.x; var y = this.y; @@ -38,10 +38,10 @@ g.fillRect(x,y,xl+4,y+16+3); //Clear g.setFontAlign(0,0); g.setFont('Vector',16); - //g.fillRect(old_x,old_y,old_x+4+l*(s-12)/100,old_y+16+3); // clear (lazy) + //g.fillRect(old_x,old_y,old_x+4+l*(s-12)/100,old_y+16+3); // clear (lazy) g.drawString(old_l, old_x + 14, old_y + 10); g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar - + } old_l = l; //console.log(old_x); @@ -54,11 +54,11 @@ g.setFontAlign(0,0); g.setFont('Vector',16); g.drawString(l, x + 14, y + 10); - + } old_x = this.x; - old_y = this.y; - + old_y = this.y; + if (Bangle.isCharging()) changeInterval(id, intervalHigh); else changeInterval(id, intervalLow); } From 60287b725209f1ca520c1f01187c8a25d9c10407 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 12:40:36 +0100 Subject: [PATCH 099/116] hwid-batt: factor width --- apps/hwid_a_battery_widget/widget.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 11997a2bd..cad782df2 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -24,7 +24,7 @@ function draw() { if (typeof old_x === 'undefined') old_x = this.x; if (typeof old_y === 'undefined') old_y = this.y; - var s = 29; + var s = width - 1; var x = this.x; var y = this.y; if ((typeof x === 'undefined') || (typeof y === 'undefined')) { @@ -65,6 +65,7 @@ Bangle.on('charging',function(charging) { draw(); }); var id = setInterval(()=>WIDGETS["hwid_a_battery_widget"].draw(), intervalLow); + var width = 30; - WIDGETS["hwid_a_battery_widget"]={area:"tr",width:30,draw:draw}; + WIDGETS["hwid_a_battery_widget"]={area:"tr",width,draw:draw}; })(); From 00c29eb6f67d683c8f5eb4dd13e05768606d569d Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 12:41:24 +0100 Subject: [PATCH 100/116] hwid-batt: option to show batt high mark --- apps/hwid_a_battery_widget/metadata.json | 6 ++++-- apps/hwid_a_battery_widget/settings.js | 22 ++++++++++++++++++++++ apps/hwid_a_battery_widget/widget.js | 8 +++++++- 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 apps/hwid_a_battery_widget/settings.js diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index 981b81079..32697fac7 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -11,6 +11,8 @@ "tags": "widget,battery", "provides_widgets" : ["battery"], "storage": [ - {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"} - ] + {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"}, + {"name":"hwid_a_battery_widget.setting.js","url":"settings.js"} + ], + "data": [{"name":"hwid_a_battery_widget.settings.json"}] } diff --git a/apps/hwid_a_battery_widget/settings.js b/apps/hwid_a_battery_widget/settings.js new file mode 100644 index 000000000..588ccae91 --- /dev/null +++ b/apps/hwid_a_battery_widget/settings.js @@ -0,0 +1,22 @@ +(back => { + const S = require('Storage'); + + const SETTINGS_FILE = "hwid_a_battery_widget.settings.json"; + const settings = S.readJSON(SETTINGS_FILE, 1) || { + showHighMark: true, + }; + + const save = () => S.write(SETTINGS_FILE, settings); + + E.showMenu({ + '': { 'title': 'Battery Widget (hank mod)' }, + '< Back': back, + 'Show high mark': { + value: settings.showHighMark, + onchange: v => { + settings.showHighMark = v; + save(); + }, + }, + }); +}) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index cad782df2..db2a664ba 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -1,4 +1,9 @@ (function(){ + const showHighMark = ( + require("Storage").readJSON("hwid_a_battery_widget.settings.json",1) || { + showHighMark: true, + }).showHighMark; + const intervalLow = 60000; // update time when not charging const intervalHigh = 2000; // update time when charging var old_l; @@ -48,7 +53,8 @@ g.setColor(levelColor(l)); g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar - g.fillRect((x+4+100*(s-12)/100)-1,y+14+3,x+4+100*(s-12)/100,y+16+3); // charging bar "full mark" + if (showHighMark) + g.fillRect((x+4+100*(s-12)/100)-1,y+14+3,x+4+100*(s-12)/100,y+16+3); // charging bar "full mark" // Show percentage g.setColor(COLORS.black); g.setFontAlign(0,0); From d5146903684a10d11cebe577d6d3c2dfda0d9cca Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 12:42:37 +0100 Subject: [PATCH 101/116] hwid-batt: bump version --- apps/hwid_a_battery_widget/ChangeLog | 3 ++- apps/hwid_a_battery_widget/metadata.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/hwid_a_battery_widget/ChangeLog b/apps/hwid_a_battery_widget/ChangeLog index cbdccfecf..6c57f97a8 100644 --- a/apps/hwid_a_battery_widget/ChangeLog +++ b/apps/hwid_a_battery_widget/ChangeLog @@ -5,4 +5,5 @@ 0.05: Deleting Background - making Font larger 0.06: Fixing refresh issues 0.07: Fixed position after unlocking -0.08: Handling exceptions \ No newline at end of file +0.08: Handling exceptions +0.09: Add option for showing battery high mark diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index 32697fac7..14590e49a 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -3,7 +3,7 @@ "name": "A Battery Widget (with percentage) - Hanks Mod", "shortName":"H Battery Widget", "icon": "widget.png", - "version":"0.08", + "version":"0.09", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", From 9a3f13507f9f75e083722d4ed22e03071e14fb4a Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 13:01:26 +0100 Subject: [PATCH 102/116] hwid-batt: shorten setting filename --- apps/hwid_a_battery_widget/metadata.json | 2 +- apps/hwid_a_battery_widget/settings.js | 2 +- apps/hwid_a_battery_widget/widget.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index 14590e49a..958c01c76 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -14,5 +14,5 @@ {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"}, {"name":"hwid_a_battery_widget.setting.js","url":"settings.js"} ], - "data": [{"name":"hwid_a_battery_widget.settings.json"}] + "data": [{"name":"hwid_batt.settings.json"}] } diff --git a/apps/hwid_a_battery_widget/settings.js b/apps/hwid_a_battery_widget/settings.js index 588ccae91..910fb1452 100644 --- a/apps/hwid_a_battery_widget/settings.js +++ b/apps/hwid_a_battery_widget/settings.js @@ -1,7 +1,7 @@ (back => { const S = require('Storage'); - const SETTINGS_FILE = "hwid_a_battery_widget.settings.json"; + const SETTINGS_FILE = "hwid_batt.settings.json"; const settings = S.readJSON(SETTINGS_FILE, 1) || { showHighMark: true, }; diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index db2a664ba..0054e7b1e 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -1,6 +1,6 @@ (function(){ const showHighMark = ( - require("Storage").readJSON("hwid_a_battery_widget.settings.json",1) || { + require("Storage").readJSON("hwid_batt.settings.json",1) || { showHighMark: true, }).showHighMark; From 0a894f15f0e258422f002f5e6d5f355a8e5427f5 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 13:08:02 +0100 Subject: [PATCH 103/116] hwid-batt: note high-mark in readme --- apps/hwid_a_battery_widget/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/hwid_a_battery_widget/README.md b/apps/hwid_a_battery_widget/README.md index db105635a..fd7bbec67 100644 --- a/apps/hwid_a_battery_widget/README.md +++ b/apps/hwid_a_battery_widget/README.md @@ -8,6 +8,8 @@ Show the current battery level and charging status in the top right of the clock * Blue when charging * 40 pixels wide +The high-level marker (a little bar at the 100% point) can be toggled in settings. + ![](a_battery_widget-pic.jpg) ## Creator From ff9b3448f6874bd32563024b7b81794759cc24cf Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 13:23:23 +0100 Subject: [PATCH 104/116] hwid-batt: handle filename length limit --- apps/hwid_a_battery_widget/metadata.json | 4 ++-- apps/hwid_a_battery_widget/settings.js | 2 +- apps/hwid_a_battery_widget/widget.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index 958c01c76..c06bff4cc 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -12,7 +12,7 @@ "provides_widgets" : ["battery"], "storage": [ {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"}, - {"name":"hwid_a_battery_widget.setting.js","url":"settings.js"} + {"name":"hwid_battwid.setting.js","url":"settings.js"} ], - "data": [{"name":"hwid_batt.settings.json"}] + "data": [{"name":"hwid_battwid.settings.json"}] } diff --git a/apps/hwid_a_battery_widget/settings.js b/apps/hwid_a_battery_widget/settings.js index 910fb1452..a7623dc65 100644 --- a/apps/hwid_a_battery_widget/settings.js +++ b/apps/hwid_a_battery_widget/settings.js @@ -1,7 +1,7 @@ (back => { const S = require('Storage'); - const SETTINGS_FILE = "hwid_batt.settings.json"; + const SETTINGS_FILE = "hwid_battwid.settings.json"; const settings = S.readJSON(SETTINGS_FILE, 1) || { showHighMark: true, }; diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 0054e7b1e..d23c98099 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -1,6 +1,6 @@ (function(){ const showHighMark = ( - require("Storage").readJSON("hwid_batt.settings.json",1) || { + require("Storage").readJSON("hwid_battwid.settings.json",1) || { showHighMark: true, }).showHighMark; From 5ca4db69d7636ee9578ab1366911ea886f754fb8 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 14:36:40 +0100 Subject: [PATCH 105/116] swiperclocklaunch: ensure we clear old listeners too Previously we wouldn't clear old listeners, which meant that they'd remain hanging around, potentially interfering with the currently shown app/clock. We now remember to clear them too, so setUI behaves as a proper reset. See #2809 for more details. --- apps/swiperclocklaunch/boot.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js index ea00a6735..11abb84c9 100644 --- a/apps/swiperclocklaunch/boot.js +++ b/apps/swiperclocklaunch/boot.js @@ -1,7 +1,13 @@ (function() { var sui = Bangle.setUI; + var oldSwipe; + Bangle.setUI = function(mode, cb) { + if (oldSwipe && oldSwipe !== Bangle.swipeHandler) + Bangle.removeListener("swipe", oldSwipe); sui(mode,cb); + oldSwipe = Bangle.swipeHandler; + if(!mode) return; if ("object"==typeof mode) mode = mode.mode; if (mode.startsWith("clock")) { From fc273a81c811477a4d85c51aae0ba9221fe0e891 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 14:41:14 +0100 Subject: [PATCH 106/116] swiperclocklaunch: bump version --- apps/swiperclocklaunch/ChangeLog | 1 + apps/swiperclocklaunch/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog index e7ad4555c..f62e10940 100644 --- a/apps/swiperclocklaunch/ChangeLog +++ b/apps/swiperclocklaunch/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fix issue with mode being undefined 0.03: Update setUI to work with new Bangle.js 2v13 menu style 0.04: Update to work with new 'fast switch' clock->launcher functionality +0.05: Keep track of event listeners we "overwrite", and remove them at the start of setUI diff --git a/apps/swiperclocklaunch/metadata.json b/apps/swiperclocklaunch/metadata.json index 4f27da528..d46c56693 100644 --- a/apps/swiperclocklaunch/metadata.json +++ b/apps/swiperclocklaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "swiperclocklaunch", "name": "Swiper Clock Launch", - "version": "0.04", + "version": "0.05", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", "type": "bootloader", From f2674671173b5bd905078047cfb7d8bacd292d29 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Fri, 12 May 2023 10:49:14 +0200 Subject: [PATCH 107/116] sensortools - Format README and add emulated routes as gpx --- apps/sensortools/README.md | 64 ++++++++------ apps/sensortools/square.gpx | 33 +++++++ apps/sensortools/squareFuzzy.gpx | 144 +++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 apps/sensortools/square.gpx create mode 100644 apps/sensortools/squareFuzzy.gpx diff --git a/apps/sensortools/README.md b/apps/sensortools/README.md index 8b89add7c..f44a89090 100644 --- a/apps/sensortools/README.md +++ b/apps/sensortools/README.md @@ -5,40 +5,56 @@ This allows to simulate sensor behaviour for development purposes ## Per Sensor settings: -enabled: - true or false -mode: - emulate: Completely craft events for this sensor - modify: Take existing events from real sensor and modify their data -name: - name of the emulation or modification mode -power: - emulate: Simulate Bangle._PWR changes, but do not call real power function - nop: Do nothing, ignore all power calls for this sensor but return true - passthrough: Just pass all power calls unmodified - on: Do not allow switching the sensor off, all calls are switching the real sensor on +Enabled: +* **true** +* **false** + +Mode: +* **emulate**: Completely craft events for this sensor +* **modify**: Take existing events from real sensor and modify their data + +Name: +* name of the emulation or modification mode + +Power: +* **emulate**: Simulate Bangle._PWR changes, but do not call real power function +* **nop**: Do nothing, ignore all power calls for this sensor but return true +* **passthrough**: Just pass all power calls unmodified +* **on**: Do not allow switching the sensor off, all calls are switching the real sensor on ### HRM -Modes: modify, emulate +Modes: +* **modify**: Modify the original events from this sensor +* **emulate**: Create events simulating sensor activity + Modification: - bpmtrippled: Multiply the bpm value of the original HRM values with 3 +* **bpmtrippled**: Multiply the bpm value of the original HRM values with 3 + Emulation: - sin: Calculate bpm changes by using sin +* **sin**: Calculate bpm changes by using sin ### GPS -Modes: emulate +Modes: +* **emulate** + Emulation: - staticfix: static complete fix with all values - route: A square route starting in the SW corner and moving SW->NW->NO->SW... - routeFuzzy: Roughly the same square as route, but with 100m seqments with some variaton in course - nofix: All values NaN but time,sattelites,fix and fix == 0 - changingfix: A fix with randomly changing values +* **staticfix**: static complete fix with all values +* **route**: A square route starting in the SW corner and moving SW->NW->NO->SW... [Download as gpx](square.gpx) +* **routeFuzzy**: Roughly the same square as route, but with 100m seqments with some variaton in course [Download as gpx](squareFuzzy.gpx) +* **nofix**: All values NaN but time,sattelites,fix and fix == 0 +* **changingfix**: A fix with randomly changing values ### Compass -Modes: emulate +Modes: +* **emulate** + Emulation: - static: All values but heading are 1, heading == 0 - rotate: All values but heading are 1, heading rotates 360° +* **static**: All values but heading are 1, heading == 0 +* **rotate**: All values but heading are 1, heading rotates 360° + +# Creator + +[halemmerich](https://github.com/halemmerich) \ No newline at end of file diff --git a/apps/sensortools/square.gpx b/apps/sensortools/square.gpx new file mode 100644 index 000000000..0220b4261 --- /dev/null +++ b/apps/sensortools/square.gpx @@ -0,0 +1,33 @@ + + + + 1kmsquare + Export from GpsPrune + + + 1kmsquare + 1 + + + 2273 + Lower left + + + 2166 + Top left + + + 2245 + Top right + + + 1994 + Lower right + + + 2273 + Destination + + + + diff --git a/apps/sensortools/squareFuzzy.gpx b/apps/sensortools/squareFuzzy.gpx new file mode 100644 index 000000000..8f73cc72b --- /dev/null +++ b/apps/sensortools/squareFuzzy.gpx @@ -0,0 +1,144 @@ + + + + 1kmsquare100 + Export from GpsPrune + + + 1kmsquare100 + 1 + + + 2265 + Lower left + + + 2256 + + + 2230 + + + 2219 + + + 2199 + + + 2182 + + + 2189 + + + 2180 + + + 2191 + + + 2185 + + + 2186 + + + 2166 + Top left + + + 2171 + + + 2194 + + + 2207 + + + 2212 + + + 2262 + + + 2278 + + + 2297 + + + 2303 + + + 2251 + + + 2245 + Top right + + + 2195 + + + 2163 + + + 2131 + + + 2095 + + + 2066 + + + 2037 + + + 1993 + + + 1967 + + + 1949 + + + 1972 + + + 2011 + Lower right + + + 2044 + + + 2061 + + + 2065 + + + 2071 + + + 2102 + + + 2147 + + + 2197 + + + 2228 + + + 2265 + Destination + + + + From 560e39b900b55c6c67d9a4e5b463a8a1d98a323b Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Thu, 25 May 2023 22:38:21 +0200 Subject: [PATCH 108/116] sensortools - Fix interpolation and speed for route simulation --- apps/sensortools/lib.js | 135 +++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/apps/sensortools/lib.js b/apps/sensortools/lib.js index 7dfc6307d..85ed3be60 100644 --- a/apps/sensortools/lib.js +++ b/apps/sensortools/lib.js @@ -138,63 +138,63 @@ exports.enable = () => { function interpolate(a,b,progress){ return { - lat: a.lat * progress + b.lat * (1-progress), - lon: a.lon * progress + b.lon * (1-progress), - ele: a.ele * progress + b.ele * (1-progress) + lat: b.lat * progress + a.lat * (1-progress), + lon: b.lon * progress + a.lon * (1-progress), + alt: b.alt * progress + a.alt * (1-progress) } } function getSquareRoute(){ return [ - {lat:"47.2577411",lon:"11.9927442",ele:2273}, - {lat:"47.266761",lon:"11.9926673",ele:2166}, - {lat:"47.2667605",lon:"12.0059511",ele:2245}, - {lat:"47.2577516",lon:"12.0059925",ele:1994} + {lat:47.2577411,lon:11.9927442,alt:2273}, + {lat:47.266761,lon:11.9926673,alt:2166}, + {lat:47.2667605,lon:12.0059511,alt:2245}, + {lat:47.2577516,lon:12.0059925,alt:1994} ]; } function getSquareRouteFuzzy(){ return [ - {lat:"47.2578455",lon:"11.9929891",ele:2265}, - {lat:"47.258592",lon:"11.9923341",ele:2256}, - {lat:"47.2594506",lon:"11.9927412",ele:2230}, - {lat:"47.2603323",lon:"11.9924949",ele:2219}, - {lat:"47.2612056",lon:"11.9928175",ele:2199}, - {lat:"47.2621002",lon:"11.9929817",ele:2182}, - {lat:"47.2629025",lon:"11.9923915",ele:2189}, - {lat:"47.2637828",lon:"11.9926486",ele:2180}, - {lat:"47.2646733",lon:"11.9928167",ele:2191}, - {lat:"47.2655617",lon:"11.9930357",ele:2185}, - {lat:"47.2662862",lon:"11.992252",ele:2186}, - {lat:"47.2669305",lon:"11.993173",ele:2166}, - {lat:"47.266666",lon:"11.9944419",ele:2171}, - {lat:"47.2667579",lon:"11.99576",ele:2194}, - {lat:"47.2669409",lon:"11.9970579",ele:2207}, - {lat:"47.2666562",lon:"11.9983128",ele:2212}, - {lat:"47.2666027",lon:"11.9996335",ele:2262}, - {lat:"47.2667245",lon:"12.0009395",ele:2278}, - {lat:"47.2668457",lon:"12.002256",ele:2297}, - {lat:"47.2666126",lon:"12.0035373",ele:2303}, - {lat:"47.2664554",lon:"12.004841",ele:2251}, - {lat:"47.2669461",lon:"12.005948",ele:2245}, - {lat:"47.2660877",lon:"12.006323",ele:2195}, - {lat:"47.2652729",lon:"12.0057552",ele:2163}, - {lat:"47.2643926",lon:"12.0060123",ele:2131}, - {lat:"47.2634978",lon:"12.0058302",ele:2095}, - {lat:"47.2626129",lon:"12.0060759",ele:2066}, - {lat:"47.2617325",lon:"12.0058188",ele:2037}, - {lat:"47.2608668",lon:"12.0061784",ele:1993}, - {lat:"47.2600155",lon:"12.0057392",ele:1967}, - {lat:"47.2591203",lon:"12.0058233",ele:1949}, - {lat:"47.2582307",lon:"12.0059718",ele:1972}, - {lat:"47.2578014",lon:"12.004804",ele:2011}, - {lat:"47.2577232",lon:"12.0034834",ele:2044}, - {lat:"47.257745",lon:"12.0021656",ele:2061}, - {lat:"47.2578682",lon:"12.0008597",ele:2065}, - {lat:"47.2577082",lon:"11.9995526",ele:2071}, - {lat:"47.2575917",lon:"11.9982348",ele:2102}, - {lat:"47.2577401",lon:"11.996924",ele:2147}, - {lat:"47.257715",lon:"11.9956061",ele:2197}, - {lat:"47.2578996",lon:"11.9943081",ele:2228} + {lat:47.2578455,lon:11.9929891,alt:2265}, + {lat:47.258592,lon:11.9923341,alt:2256}, + {lat:47.2594506,lon:11.9927412,alt:2230}, + {lat:47.2603323,lon:11.9924949,alt:2219}, + {lat:47.2612056,lon:11.9928175,alt:2199}, + {lat:47.2621002,lon:11.9929817,alt:2182}, + {lat:47.2629025,lon:11.9923915,alt:2189}, + {lat:47.2637828,lon:11.9926486,alt:2180}, + {lat:47.2646733,lon:11.9928167,alt:2191}, + {lat:47.2655617,lon:11.9930357,alt:2185}, + {lat:47.2662862,lon:11.992252,alt:2186}, + {lat:47.2669305,lon:11.993173,alt:2166}, + {lat:47.266666,lon:11.9944419,alt:2171}, + {lat:47.2667579,lon:11.99576,alt:2194}, + {lat:47.2669409,lon:11.9970579,alt:2207}, + {lat:47.2666562,lon:11.9983128,alt:2212}, + {lat:47.2666027,lon:11.9996335,alt:2262}, + {lat:47.2667245,lon:12.0009395,alt:2278}, + {lat:47.2668457,lon:12.002256,alt:2297}, + {lat:47.2666126,lon:12.0035373,alt:2303}, + {lat:47.2664554,lon:12.004841,alt:2251}, + {lat:47.2669461,lon:12.005948,alt:2245}, + {lat:47.2660877,lon:12.006323,alt:2195}, + {lat:47.2652729,lon:12.0057552,alt:2163}, + {lat:47.2643926,lon:12.0060123,alt:2131}, + {lat:47.2634978,lon:12.0058302,alt:2095}, + {lat:47.2626129,lon:12.0060759,alt:2066}, + {lat:47.2617325,lon:12.0058188,alt:2037}, + {lat:47.2608668,lon:12.0061784,alt:1993}, + {lat:47.2600155,lon:12.0057392,alt:1967}, + {lat:47.2591203,lon:12.0058233,alt:1949}, + {lat:47.2582307,lon:12.0059718,alt:1972}, + {lat:47.2578014,lon:12.004804,alt:2011}, + {lat:47.2577232,lon:12.0034834,alt:2044}, + {lat:47.257745,lon:12.0021656,alt:2061}, + {lat:47.2578682,lon:12.0008597,alt:2065}, + {lat:47.2577082,lon:11.9995526,alt:2071}, + {lat:47.2575917,lon:11.9982348,alt:2102}, + {lat:47.2577401,lon:11.996924,alt:2147}, + {lat:47.257715,lon:11.9956061,alt:2197}, + {lat:47.2578996,lon:11.9943081,alt:2228} ]; } @@ -215,51 +215,43 @@ exports.enable = () => { let interpSteps; if (settings.gps.name == "routeFuzzy"){ route = getSquareRouteFuzzy(); - interpSteps = 5; + interpSteps = 74; } else { route = getSquareRoute(); - interpSteps = 50; + interpSteps = 740; } let step = 0; let routeIndex = 0; modGps(() => { let newIndex = (routeIndex + 1)%route.length; - + let followingIndex = (routeIndex + 2)%route.length; + let result = { - "speed": Math.random() * 3 + 2, + "speed": Math.random()*1 + 4.5, "time": new Date(), "satellites": Math.floor(Math.random()*5)+3, "fix": 1, "hdop": Math.floor(Math.random(30)+1) }; - + let oldPos = route[routeIndex]; - if (step != 0){ - oldPos = interpolate(route[routeIndex], route[newIndex], E.clip(0,1,step/interpSteps)); - } let newPos = route[newIndex]; - if (step < interpSteps - 1){ - newPos = interpolate(route[routeIndex], route[newIndex], E.clip(0,1,(step+1)%interpSteps/interpSteps)); + let followingPos = route[followingIndex]; + let interpPos = interpolate(oldPos, newPos, E.clip(0,1,step/interpSteps)); + + if (step > 0.5* interpSteps) { + result.course = bearing(interpPos, interpolate(newPos, followingPos, E.clip(0,1,(step-0.5*interpSteps)/interpSteps))); + } else { + result.course = bearing(oldPos, newPos); } - if (step == interpSteps - 1){ - let followingIndex = (routeIndex + 2)%route.length; - newPos = interpolate(route[newIndex], route[followingIndex], E.clip(0,1,1/interpSteps)); - } - - result.lat = oldPos.lat; - result.lon = oldPos.lon; - result.alt = oldPos.ele; - - result.course = bearing(oldPos,newPos); - step++; if (step == interpSteps){ routeIndex = (routeIndex + 1) % route.length; step = 0; } - return result; + return Object.assign(result, interpPos); }); } else if (settings.gps.name == "nofix") { modGps(() => { return { @@ -281,7 +273,8 @@ exports.enable = () => { let currentDir=1000; let currentAlt=500; let currentSats=5; - modGps(() => { + + (() => { currentLat += 0.01; if (currentLat > 50) currentLat = 20; currentLon += 0.01; From d884e21a8ec6d0ed55b67ef63198a00a8d428da6 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 28 May 2023 22:13:30 +0200 Subject: [PATCH 109/116] sensortools - Add setting for log --- apps/sensortools/default.json | 1 + apps/sensortools/lib.js | 1 + apps/sensortools/settings.js | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/apps/sensortools/default.json b/apps/sensortools/default.json index a85e1ddeb..0e0d0a9af 100644 --- a/apps/sensortools/default.json +++ b/apps/sensortools/default.json @@ -1,5 +1,6 @@ { "enabled": false, + "log": false, "mag": { "enabled": false, "mode": "emulate", diff --git a/apps/sensortools/lib.js b/apps/sensortools/lib.js index 85ed3be60..f0155d289 100644 --- a/apps/sensortools/lib.js +++ b/apps/sensortools/lib.js @@ -5,6 +5,7 @@ exports.enable = () => { ); let log = function(text, param) { + if (!settings.log) return; let logline = new Date().toISOString() + " - " + "Sensortools - " + text; if (param) logline += ": " + JSON.stringify(param); print(logline); diff --git a/apps/sensortools/settings.js b/apps/sensortools/settings.js index 231ab8467..ae631e60c 100644 --- a/apps/sensortools/settings.js +++ b/apps/sensortools/settings.js @@ -88,6 +88,12 @@ writeSettings("enabled",v); }, }, + 'Log': { + value: !!settings.log, + onchange: v => { + writeSettings("log",v); + }, + }, 'GPS': ()=>{showSubMenu("GPS","gps",["nop", "staticfix", "nofix", "changingfix", "route", "routeFuzzy"],[]);}, 'Compass': ()=>{showSubMenu("Compass","mag",["nop", "static", "rotate"],[]);}, 'HRM': ()=>{showSubMenu("HRM","hrm",["nop", "static"],["bpmtrippled"],["sin"]);} From 9642b546e28fced5728ac7e330ad3798175f2316 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 11 Jun 2023 20:35:12 +0200 Subject: [PATCH 110/116] sensortools - Bump version --- apps/sensortools/ChangeLog | 2 ++ apps/sensortools/metadata.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sensortools/ChangeLog b/apps/sensortools/ChangeLog index 7d9bdd6a8..6d2f5d2b4 100644 --- a/apps/sensortools/ChangeLog +++ b/apps/sensortools/ChangeLog @@ -2,3 +2,5 @@ 0.02: Less time used during boot if disabled 0.03: Fixed some test data 0.04: Correct type of time attribute in gps to Date +0.05: Fix gps emulation interpolation + Add setting for log output diff --git a/apps/sensortools/metadata.json b/apps/sensortools/metadata.json index f5bace383..48b146617 100644 --- a/apps/sensortools/metadata.json +++ b/apps/sensortools/metadata.json @@ -2,7 +2,7 @@ "id": "sensortools", "name": "Sensor tools", "shortName": "Sensor tools", - "version": "0.04", + "version": "0.05", "description": "Tools for testing and debugging apps that use sensor input", "icon": "icon.png", "type": "bootloader", From c8070a7d1446ddead245162032b9b286d43af40c Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 11 Jun 2023 20:43:56 +0200 Subject: [PATCH 111/116] sensortools - Fix changingfix emulation --- apps/sensortools/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sensortools/lib.js b/apps/sensortools/lib.js index f0155d289..5e1c199c2 100644 --- a/apps/sensortools/lib.js +++ b/apps/sensortools/lib.js @@ -275,7 +275,7 @@ exports.enable = () => { let currentAlt=500; let currentSats=5; - (() => { + modGps(() => { currentLat += 0.01; if (currentLat > 50) currentLat = 20; currentLon += 0.01; From 786b8db814a1e19086357945125c4ae4c9d4c39c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 21:57:41 +0100 Subject: [PATCH 112/116] hwid-batt: drop high mark altogether --- apps/hwid_a_battery_widget/metadata.json | 6 ++---- apps/hwid_a_battery_widget/settings.js | 22 ---------------------- apps/hwid_a_battery_widget/widget.js | 7 ------- 3 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 apps/hwid_a_battery_widget/settings.js diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index c06bff4cc..73dfc7c92 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -11,8 +11,6 @@ "tags": "widget,battery", "provides_widgets" : ["battery"], "storage": [ - {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"}, - {"name":"hwid_battwid.setting.js","url":"settings.js"} - ], - "data": [{"name":"hwid_battwid.settings.json"}] + {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"} + ] } diff --git a/apps/hwid_a_battery_widget/settings.js b/apps/hwid_a_battery_widget/settings.js deleted file mode 100644 index a7623dc65..000000000 --- a/apps/hwid_a_battery_widget/settings.js +++ /dev/null @@ -1,22 +0,0 @@ -(back => { - const S = require('Storage'); - - const SETTINGS_FILE = "hwid_battwid.settings.json"; - const settings = S.readJSON(SETTINGS_FILE, 1) || { - showHighMark: true, - }; - - const save = () => S.write(SETTINGS_FILE, settings); - - E.showMenu({ - '': { 'title': 'Battery Widget (hank mod)' }, - '< Back': back, - 'Show high mark': { - value: settings.showHighMark, - onchange: v => { - settings.showHighMark = v; - save(); - }, - }, - }); -}) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index d23c98099..2c7bd2568 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -1,9 +1,4 @@ (function(){ - const showHighMark = ( - require("Storage").readJSON("hwid_battwid.settings.json",1) || { - showHighMark: true, - }).showHighMark; - const intervalLow = 60000; // update time when not charging const intervalHigh = 2000; // update time when charging var old_l; @@ -53,8 +48,6 @@ g.setColor(levelColor(l)); g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar - if (showHighMark) - g.fillRect((x+4+100*(s-12)/100)-1,y+14+3,x+4+100*(s-12)/100,y+16+3); // charging bar "full mark" // Show percentage g.setColor(COLORS.black); g.setFontAlign(0,0); From 37236b254e88f06742d82b16da7351d9a70c70d4 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sun, 11 Jun 2023 22:05:18 +0100 Subject: [PATCH 113/116] hwid-batt: simplify clearing old widget --- apps/hwid_a_battery_widget/widget.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 2c7bd2568..027535051 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -1,7 +1,6 @@ (function(){ const intervalLow = 60000; // update time when not charging const intervalHigh = 2000; // update time when charging - var old_l; var old_x = this.x; var old_y = this.y; @@ -22,29 +21,15 @@ }; function draw() { - if (typeof old_x === 'undefined') old_x = this.x; - if (typeof old_y === 'undefined') old_y = this.y; var s = width - 1; var x = this.x; var y = this.y; if ((typeof x === 'undefined') || (typeof y === 'undefined')) { } else { + g.clearRect(old_x, old_y, old_x + width, old_y + height); + const l = E.getBattery(); // debug: Math.floor(Math.random() * 101); let xl = x+4+l*(s-12)/100; - if ((l != old_l) && (typeof old_l != 'undefined') ){ // Delete the old value from screen - let xl_old = x+4+old_l*(s-12)/100; - g.setColor(COLORS.white); - // g.fillRect(x+2,y+5,x+s-6,y+18); - g.fillRect(x,y,xl+4,y+16+3); //Clear - g.setFontAlign(0,0); - g.setFont('Vector',16); - //g.fillRect(old_x,old_y,old_x+4+l*(s-12)/100,old_y+16+3); // clear (lazy) - g.drawString(old_l, old_x + 14, old_y + 10); - g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar - - } - old_l = l; - //console.log(old_x); g.setColor(levelColor(l)); g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar @@ -65,6 +50,7 @@ Bangle.on('charging',function(charging) { draw(); }); var id = setInterval(()=>WIDGETS["hwid_a_battery_widget"].draw(), intervalLow); var width = 30; + var height = 19; WIDGETS["hwid_a_battery_widget"]={area:"tr",width,draw:draw}; })(); From e32c6ead5a3dc4c2e29369cb7606039a3733854f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 12 Jun 2023 12:32:23 +0100 Subject: [PATCH 114/116] Handle nav messages from newer Gadgetbridge builds that output distance as a String --- apps/messagegui/ChangeLog | 3 ++- apps/messagegui/app.js | 8 +++++--- apps/messagegui/metadata.json | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 2e8f9bb3c..7418f83d5 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -93,4 +93,5 @@ Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons 0.68: More navigation icons (for roundabouts) 0.69: More navigation icons (keep/uturn left/right) - Nav messages with '/' now get split on newlines \ No newline at end of file + Nav messages with '/' now get split on newlines +0.70: Handle nav messages from newer Gadgetbridge builds that output distance as a String \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 97396496f..fbb2058b7 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -15,8 +15,8 @@ // a message require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going?",positive:1,negative:1}) // maps -GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:966,action:"continue",eta:"08:39"}) -GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:12345,action:"left_slight",eta:"08:39"}) +GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:"966yd",action:"continue",eta:"08:39"}) +GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:"12km",action:"left_slight",eta:"08:39"}) GB({t:"nav",src:"maps",title:"Navigation",instr:"Main St / I-29 ALT / Centerpoint Dr",distance:12345,action:"left_slight",eta:"08:39"}) // call require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true}) @@ -82,7 +82,9 @@ E.on("kill", saveMessages); function showMapMessage(msg) { active = "map"; var m, distance, street, target, img; - if (msg.distance!==undefined) + if ("string"==typeof msg.distance) // new gadgetbridge + distance = msg.distance; + else if ("number"==typeof msg.distance) // 0.74 gadgetbridge distance = require("locale").distance(msg.distance); if (msg.instr) { var instr = msg.instr.replace(/\s*\/\s*/g," \/\n"); // convert slashes to newlines diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index dffff0c58..a7a93ffe9 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.69", + "version": "0.70", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", From 909ddf74aa654e28ea1e643c33dc2821e7fb72db Mon Sep 17 00:00:00 2001 From: stweedo Date: Mon, 12 Jun 2023 07:31:23 -0500 Subject: [PATCH 115/116] [ohmcalc] - v0.04: Fix font size not resetting on subsequent values in results screen --- apps/ohmcalc/ChangeLog | 1 + apps/ohmcalc/app.js | 6 ++---- apps/ohmcalc/metadata.json | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/ohmcalc/ChangeLog b/apps/ohmcalc/ChangeLog index af752b896..625982edd 100644 --- a/apps/ohmcalc/ChangeLog +++ b/apps/ohmcalc/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: New Results menu item to show the formula and values used. 0.03: Adds haptics to Input screen and long press "C" to go back. +0.04: Fix font size not resetting on subsequent values in results screen diff --git a/apps/ohmcalc/app.js b/apps/ohmcalc/app.js index aa470401c..5ef30aaa5 100644 --- a/apps/ohmcalc/app.js +++ b/apps/ohmcalc/app.js @@ -291,13 +291,11 @@ function calculateValue(calculatedVariable, variableValues) { function drawResultScreen(result) { let drawPage = function() { clearScreen(); - let fontSize = 30; // Initial font size - let lineSpacing = 15; // Space between lines - // Define the vertical positions of the titles let titlePositions = [10, 72, 132]; - + let lineSpacing = 15; // Space between lines for (let i = 0; i < result.result.length; i++) { + let fontSize = 30; // Initial font size let currentResult = result.result[i]; let resultTitle = currentResult[0]; let resultValue = currentResult[1]; diff --git a/apps/ohmcalc/metadata.json b/apps/ohmcalc/metadata.json index de81ec6d7..252ece421 100644 --- a/apps/ohmcalc/metadata.json +++ b/apps/ohmcalc/metadata.json @@ -2,7 +2,7 @@ "id": "ohmcalc", "name": "Ohm's Law Calculator", "shortName": "Ohm's Law Calc", - "version": "0.03", + "version": "0.04", "description": "A smart and simple calculator for Ohm's Law calculations, designed specifically for Bangle.js 2 smartwatches. Handles voltage, current, resistance, and power calculations with smart logic to prevent invalid inputs.", "icon": "app.png", "type": "app", From 671a793a6c233375be41be47fad6c7838c6edee7 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 12 Jun 2023 14:16:27 +0100 Subject: [PATCH 116/116] If we receive a 'music' message and we're in the messages app (but not showing a message) show music (#2814) --- apps/messagegui/ChangeLog | 3 ++- apps/messagegui/app.js | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 7418f83d5..f7e35ef2a 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -94,4 +94,5 @@ 0.68: More navigation icons (for roundabouts) 0.69: More navigation icons (keep/uturn left/right) Nav messages with '/' now get split on newlines -0.70: Handle nav messages from newer Gadgetbridge builds that output distance as a String \ No newline at end of file +0.70: Handle nav messages from newer Gadgetbridge builds that output distance as a String + If we receive a 'music' message and we're in the messages app (but not showing a message) show music (#2814) \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index fbb2058b7..c78f27a95 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -28,7 +28,7 @@ var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; var fontVLarge = g.getFonts().includes("6x15")?"12x20:2":"6x8:5"; -var active; // active screen +var active; // active screen (undefined/"list"/"music"/"map"/"message"/"scroller"/"settings") var openMusic = false; // go back to music screen after we handle something else? // hack for 2v10 firmware's lack of ':size' font handling try { @@ -68,7 +68,7 @@ var onMessagesModified = function(type,msg) { } if (msg && msg.id=="music") { if (msg.state && msg.state!="play") openMusic = false; // no longer playing music to go back to - if (active!="music") return; // don't open music over other screens + if ((active!=undefined) && (active!="list") && (active!="music")) return; // don't open music over other screens (but do if we're in the main menu) } showMessage(msg&&msg.id); }; @@ -405,6 +405,7 @@ function checkMessages(options) { options=options||{}; // If no messages, just show 'no messages' and return if (!MESSAGES.length) { + active=undefined; // no messages if (!options.clockIfNoMsg) return E.showPrompt(/*LANG*/"No Messages",{ title:/*LANG*/"Messages", img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")), @@ -434,7 +435,7 @@ function checkMessages(options) { // no new messages - go to clock? if (options.clockIfAllRead && newMessages.length==0) return load(); - active = "main"; + active = "list"; // Otherwise show a menu E.showScroller({ h : 48,

DateTypeDate/Time Summary
Date/Time Summary
Type Date/Time SummaryOn?