diff --git a/.eslintignore b/.eslintignore index fcbea07f9..4af79d129 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,5 @@ apps/banglerun/rollup.config.js apps/schoolCalendar/fullcalendar/main.js apps/authentiwatch/qr_packed.js apps/qrcode/qr-scanner.umd.min.js +apps/gipy/pkg/gpconv.js *.test.js diff --git a/.gitmodules b/.gitmodules index fd6663d2a..c2c1104c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "EspruinoAppLoaderCore"] path = core url = https://github.com/espruino/EspruinoAppLoaderCore.git +[submodule "webtools"] + path = webtools + url = https://github.com/espruino/EspruinoWebTools.git diff --git a/README.md b/README.md index d2f7022e9..1058787bb 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,11 @@ and which gives information about the app for the Launcher. // 'app' - an application // 'clock' - a clock - required for clocks to automatically start // 'widget' - a widget + // 'module' - this provides a module that can be used with 'require'. + // 'provides_modules' should be used if type:module is specified // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js' // 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js' + // 'clkinfo' - Provides a 'myapp.clkinfo.js' file that can be used to display info in clocks - see modules/clock_info.js // 'RAM' - code that runs and doesn't upload anything to storage // 'launch' - replacement 'Launcher' // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle @@ -266,10 +269,21 @@ and which gives information about the app for the Launcher. // 'locale' - provides 'locale' library for language-specific date/distance/etc // (a version of 'locale' is included in the firmware) "tags": "", // comma separated tag list for searching + // common types are: + // 'clock' - it's a clock + // 'widget' - it is (or provides) a widget + // 'outdoors' - useful for outdoor activities + // 'tool' - a useful utility (timer, calculator, etc) + // 'game' - a game + // 'bluetooth' - uses Bluetooth LE + // 'system' - used by the system + // 'clkinfo' - provides or uses clock_info module for data on your clock face (see modules/clock_info.js) "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above) "dependencies" : { "messages":"app" } // optional, depend on a specific app ID // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' + "dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' + "provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require' "readme": "README.md", // if supplied, a link to a markdown-style text file // that contains more information about this app (usage, etc) // A 'Read more...' link will be added under the app @@ -454,7 +468,10 @@ It should also add `myappid.json` to `data`, to make sure it is cleaned up when ## Modules You can include any of [Espruino's modules](https://www.espruino.com/Modules) as -normal with `require("modulename")`. If you want to develop your own module for your +normal with `require("modulename")`. To include [Bangle's modules](modules) for use in the Web +IDE, [upload the modules to internal storage](modules#upload-the-module-to-the-bangles-internal-storage) +or [change the IDE's search path](modules#change-the-web-ide-search-path-to-include-banglejs-modules). +If you want to develop your own module for your app(s) then you can do that too. Just add the module into the `modules` folder then you can use it from your app as normal. diff --git a/android.html b/android.html index 93999008f..8a70a46e9 100644 --- a/android.html +++ b/android.html @@ -170,10 +170,10 @@ - + + - diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index ffe9de081..e236e4b34 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -11,3 +11,4 @@ 0.11: Bangle.js2: New pixels, btn1 to exit 0.12: Actual pixels as of 29th Nov 2021 0.13: Bangle.js 2: Use setUI to add software back button +0.14: Add automatic translation of more strings diff --git a/apps/about/app-bangle1.js b/apps/about/app-bangle1.js index 28a292376..dd94c1e84 100644 --- a/apps/about/app-bangle1.js +++ b/apps/about/app-bangle1.js @@ -11,8 +11,8 @@ g.drawString("BANGLEJS.COM",120,y-4); } else { y=-(4+h); // small screen, start right at top } -g.drawString("Powered by Espruino",0,y+=4+h); -g.drawString("Version "+ENV.VERSION,0,y+=h); +g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h); +g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h); g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); function getVersion(name,file) { var j = s.readJSON(file,1); @@ -24,9 +24,9 @@ getVersion("Launcher","launch.info"); getVersion("Settings","setting.info"); y+=h; -g.drawString(MEM.total+" JS Variables available",0,y+=h); -g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); -if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); +g.drawString(MEM.total+/*LANG*/" JS Variables available",0,y+=h); +g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h); +if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h); if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); g.setFontAlign(0,-1); g.flip(); diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js index 471b0670f..ccffd183f 100644 --- a/apps/about/app-bangle2.js +++ b/apps/about/app-bangle2.js @@ -35,17 +35,17 @@ function drawInfo() { g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56); var h=8, y = 24-h; g.setFont("6x8").setFontAlign(-1,-1); - g.drawString("Powered by Espruino",0,y+=4+h); - g.drawString("Version "+ENV.VERSION,0,y+=h); + g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h); + g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h); g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); getVersion("Bootloader","boot.info"); getVersion("Launcher","launch.info"); getVersion("Settings","setting.info"); - g.drawString(MEM.total+" JS Vars",0,y+=h); - g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); - if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); + g.drawString(MEM.total+/*LANG*/" JS Vars",0,y+=h); + g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h); + if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h); if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); imageTop = y+h; imgScroll = imgHeight-imageTop; diff --git a/apps/about/metadata.json b/apps/about/metadata.json index 648576576..52cd37b7d 100644 --- a/apps/about/metadata.json +++ b/apps/about/metadata.json @@ -1,7 +1,7 @@ { "id": "about", "name": "About", - "version": "0.13", + "version": "0.14", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "icon": "app.png", "tags": "tool,system", diff --git a/apps/activepedom/README.md b/apps/activepedom/README.md index ac32a1dd6..06ad280ee 100644 --- a/apps/activepedom/README.md +++ b/apps/activepedom/README.md @@ -1,6 +1,11 @@ # Active Pedometer + Pedometer that filters out arm movement and displays a step goal progress. +**Note:** Since creation of this app, Bangle.js's step counting algorithm has +improved significantly - and as a result the algorithm in this app (which + runs *on top* of Bangle.js's algorithm) may no longer be accurate. + I changed the step counting algorithm completely. Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long. To get in 'active' mode, you have to reach the step threshold before the active timer runs out. @@ -9,6 +14,7 @@ When you reach the step threshold, the steps needed to reach the threshold are c Steps are saved to a datafile every 5 minutes. You can watch a graph using the app. ## Screenshots + * 600 steps ![](600.png) @@ -70,4 +76,4 @@ Steps are saved to a datafile every 5 minutes. You can watch a graph using the a ## Requests -If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/ \ No newline at end of file +If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/ diff --git a/apps/activepedom/metadata.json b/apps/activepedom/metadata.json index 4deb7006d..81bafb573 100644 --- a/apps/activepedom/metadata.json +++ b/apps/activepedom/metadata.json @@ -3,7 +3,7 @@ "name": "Active Pedometer", "shortName": "Active Pedometer", "version": "0.09", - "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", + "description": "(NOT RECOMMENDED) Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph. The `Health` app now provides step logging and graphs.", "icon": "app.png", "tags": "outdoors,widget", "supports": ["BANGLEJS"], diff --git a/apps/advcasio/ChangeLog b/apps/advcasio/ChangeLog index a1b528cf6..fd37c324e 100644 --- a/apps/advcasio/ChangeLog +++ b/apps/advcasio/ChangeLog @@ -1,3 +1,4 @@ 0.01: AdvCasio first version 0.02: Remove un-needed fonts to improve memory usage 0.03: Tell clock widgets to hide. +0.04: Swipe down to see widgets, step counter now just uses getHealthStatus diff --git a/apps/advcasio/app.js b/apps/advcasio/app.js index 8cb904f90..9d246b7ef 100644 --- a/apps/advcasio/app.js +++ b/apps/advcasio/app.js @@ -1,304 +1,160 @@ const storage = require('Storage'); +require("Font6x12").add(Graphics); require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg").add(Graphics); function bigThenSmall(big, small, x, y) { - g.setFont("7x11Numeric7Seg", 2); - g.drawString(big, x, y); - x += g.stringWidth(big); - g.setFont("8x12"); - g.drawString(small, x, y); + g.setFont("7x11Numeric7Seg", 2); + g.drawString(big, x, y); + x += g.stringWidth(big); + g.setFont("8x12"); + g.drawString(small, x, y); } -function getClockBg() { - return require("heatshrink").decompress(atob("icVgf/ABv8v4DBx4CB+PH8F+nAGB48fwEHBwXjxwqBuPH//+nAGBBwIjCAwI2D/wGBgIyDI4QGDwAGBHYX/4AGBn4UFEYQpCEYYpCAAMfMhP4FIgABwJ8OEBIA==")); -} - - -// sun, cloud, rain, thunder -var iconsWeather = [ - require("heatshrink").decompress(atob("i8Ugf/ACcfA434BA/AAwsAv0/8F/BAcDwEHHIpECFI3wn4GC/gOC+PAGoXggEH/+ODQgXBGQv/wAbBBAnguEACIn4gfxI4JXFwJmG/kPBA3jSynw")), require("heatshrink").decompress(atob("i0Ugf/AEXggIGE/0A/kPBAmBCIN/A4Y8CgAICwEHBYoUE/ACCj4sDn4CBC4YyDwBrDCgYA3A")), require("heatshrink").decompress(atob("h8Rgf/AAuBAgf8h4FDCwM/AgPA/gFC/0HgEBBQPwnEfDoWAg4jC/gOCAoQmBAQXjFIV//8f//4IQP4j/+gAIB4EcHII4CAoI+DLQJXF/AA==")), require("heatshrink").decompress(atob("h0Pgf/AA8fAYX+g4EC8EBAgXADAeAgAECgAOC/wrCDQIOBBYfwgAaC/kAn4EB/EAv4aDHAeBIg38")) -]; - - function getBackgroundImage() { - return require("heatshrink").decompress(atob("2GwghC/AH4A/AH4AMl////wAwURiQECgUzmcxBQQCBiYUBBARW+LAcCAgcPBYgFBkAIFG7kQiAKIiIKBgISOAAJBD//zKQfxK4vyAoMQCgn/ERBhBBYR5BAwR1DB4Y2DgYPCGIQRCCQcP+EfGJI0FEgRSCGAQCCX4JXCkAhDn4lI+HyK4YWBFIPzJYJXHAIMSK4cwJ4I3CAYMzA4cfcRMBdwytBK4i6FK4IUCMgYAEGIITBK4cCaAPwgJXB+fzK4sAgYtCK5EfA4pXR+AmBaIZYCK6KcCAwSjDEYXx/8vK5QRCK4kPK6cDkJREBIMBfgIrDK5svUAIQBAwIaCK4w+DK4YGBK7IaBboIuCK4gFCJwYBBiBCCCgQhHHYgGDgArBK5IGDAYMgJ4Xwn53BGgLVDmBXKAAinDLpJXCAAYhHR4YODn/wJIPyTYZXDE4RXD+ECNILIDAIPwj4xIAAYNCR4fyVIYLFA4KEBBAglKAGUCmcykEAiMQBIURBYM/BgIUEgcz+bTKAH4A/AH4A/AHP/AGY1d+BWCh5X/LCpW1K74fgG/5X/AH5X/K9Bg/K63wK/5XWgBX/K6pWBK/5XU+BWBh5J/K6auCK/5XTVwRfFAH5XOKwRX/K6auDh5I/K6SuDWP5XSVwYADWX6vXK/5XQWQpW/K6auDJP5XWV35XT+Cu/K7Ku/K65H/K6hW/K7EPI35XWIv5XWAH5X/K/4A/K/5X/K/4A/K9cAAH4A/AFzz/AHRX/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/40VAH4A/AFzLb+EPDm4AdK/5X/K+PwgEAHy5X9HgMAK/5XXH6xX/H65X/K/5X/K98AK7sAgBX3DjBWFO644DSTHwGzJXED4RXaDoLqcK7weWDIQcXK8I6YK77KXK4o8DPbY6ZK7qvDDy6vdR7JXDh60EDyw5BAIRXYSwjMbAgIhUDwJZCHwJX0GwjRWNwIAEHSwBCDSpXFH4pXzDS5XIEARXVSYbQEDaYzCK+6vcKaxXNDypX9HwQkbHS40COSpXKK2A6CHgRXcPIhX0SwpXYVuQ6EgBX/K644YODBXkSDJX/K/5X/DtRX6gA3YOkRWbLDZX4KwYA/AG8F5vdABncKH4AGhpRJAYXNAgPAKP4AF5vMJwoDBAQIKE6BR/AAvc5vO9wAB7oCB9veAoPcAoPcK+kwh8AgcA98An//gH/+sD//wCISgBJ4IABAYpaC9vdK4UP/9AAQNQr/zgHwEYNQFYQAh+EP+FegH+A4QBCMQIKBAAPNK4yxBA4RXCV4YZBE4IjChwCDmApCK8VdmHggHgFYf0SQJXE5nMK4anCAoYHC5pXCaQJXBop+BqAGEK7f/AAQeEKwQrBqCtDAILjBCQfNK4JTCAYZXF7qvD//gV4S2DgEFFIYAECgIACMC8PKoIBB8n1K4ivF5vc5xOCWYZbBAYavHU4RXCr4pEAEMDfoNQGoMEgEwYQPwAoIBBAAPM5ipC7oDCVIIAE7hXCD4SdBiEP+gGBgihCFYIAz5pXBAAnN7oIB7nc5gOBK4QA/K4pNCWgSpCBInNK/4AGhncKIStC7gCBA4QAC4BR/AAysCABZW/AHwA=")); + return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA==")); } +function getRocketSequences() { + return { + 1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")), + 2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")), + 3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")), + 4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")), + 5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")), + 6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")), + 7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")), + 8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")), + }; +} +let rocketSequence = 1; +let settings = storage.readJSON("cassioWatch.settings.json", true) || {}; +let rocketSpeed = settings.rocketSpeed || 700; +delete settings; // schedule a draw for the next minute let rocketInterval; var drawTimeout; function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); } function clearIntervals() { - if (rocketInterval) clearInterval(rocketInterval); - rocketInterval = undefined; - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; + if (rocketInterval) clearInterval(rocketInterval); + rocketInterval = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } -//////////////////////////////////////////// -// TIMER FUNC -// -var timer_time = 0; -var alreadyListenTouch = false; -function initTouchTimer () { - if (alreadyListenTouch) return; - alreadyListenTouch = true; - - Bangle.on('swipe', function(dirX,dirY) { - if (canTouch === false) return; - var njson = getDataJson(); - if (!njson) return; - - if (dirX === -1) { - timer_time = 0; - delete njson.timer; - setDataJson(njson); - } - else if (dirX === 1) { - var now = new Date().getTime(); - njson.timer = now + (timer_time * 1000 * 60); - Bangle.setLocked(true); - setDataJson(njson); - Bangle.buzz(200, 0); - timer_time = 0; - } - else if (dirY === -1) { - if (canTouch === false || njson.timer) return; - timer_time = timer_time + 5; - } - else if (dirY === 1) { - if (canTouch === false || njson.timer) return; - timer_time = timer_time - 5; - } - draw(); - }); -} -setTimeout(() => { - initTouchTimer (); -}); - -function getTimerTime() { - // if timer_time !== -1, take it - if (timer_time !== 0) { - return timer_time + "m"; - } else { - // else, show diff between njsontime and now - var njson = getDataJson(); - if (!njson) return false; - var now = new Date().getTime(); - var diff = Math.round((njson.timer - now) / (1000 * 60)); - //console.log(123, njson, diff, now, njson.timer - now); - if (diff > 0) return diff + "m"; - else if (njson.timer) { - Bangle.buzz(1000, 1); - console.log("END OF TIMER"); - delete njson.timer; - setDataJson(njson); - return false; - } else { - return false; - } - // if diff is <0, delete timer from json - } -} -function drawTimer() { - //g.drawString(getTimerTime(), 100, 100); - g.setFont("8x12", 2); - var t = 97; - var l = 105; - var time = getTimerTime(); - if (time || timer_time !== 0) g.drawString(time, l+5, t+0); - if (time && timer_time === 0) g.drawImage(getClockBg(), l-20, t+2, { scale: 1 }); -} - - -//////////////////////////////////////////// -// DATA READING -// -function getDataJson(){ - var res = {"tasks":"", "weather":[]}; - try { - res = storage.readJSON('advcasio.data.json'); - } catch(ex) { - return res; - } - return res; -} -function setDataJson(resJson){ - try { - res = storage.writeJSON('advcasio.data.json', resJson); - } catch(ex) { - return res; - } - return res; -} -var dataJson = getDataJson(); - -//////////////////////////////////////////// -// WEATHER! -// -function drawWeather(arr) { - g.setFont("6x8", 1); - var p = {l: 8, tText: 40, tIcon:20, decal:25}; - var today = new Date().getTime(); - var yesterday = today - (1000 * 60 * 60 * 24); - var testday = today + (1000 * 60 * 60 * 24 * 2); - //12h auj > 12h hier qui est sup a 0h auj - //23h59 hier est sup a 0h auj - var j = 0; - for(var i = 0; i yesterday && j < 4) { - g.drawString(arr[i][0], p.l + p.decal*j + 4, p.tText); - g.drawImage(iconsWeather[arr[i][1]], p.l + p.decal*j, p.tIcon, { scale: 1 }); - j++ - } - } -} - - -//////////////////////////////////////////// -// DRAWING FUNCS -// -function drawTasks(str) { - g.setFont("6x8", 1); - var t = 57; - var l = 0; - g.drawString(str, l+5, t+0); -} - -function drawSteps() { - g.setFont("8x12", 2); - var t = 132; - var l = 150; - g.drawString(getSteps(), l+5, t+0); -} - - function drawClock() { - g.setFont("7x11Numeric7Seg", 3); - g.clearRect(80, 57, 170, 96); - g.setColor(255, 255, 255); - var l = 77; - var t = 57; - var w = 170; - var h = 116; - g.drawRect(l, t, w, h); - g.fillRect(l, t, w, h); - g.setColor(0, 0, 0); - g.drawString(require("locale").time(new Date(), 1), 76, 60); - - // day - //g.setFont("8x12", 1); - //g.setFont("9x18", 1); - //g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136); - g.setFont("8x12", 2); - g.drawString(require("locale").dow(new Date(), 2), 18, 130); - - // month - g.setFont("8x12"); - g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127); - - // day nb - g.setFont("8x12", 2); - const time = new Date().getDate(); - g.drawString(time < 10 ? "0" + time : time, 78, 137); + g.setFont("7x11Numeric7Seg", 3); + g.clearRect(80, 57, 170, 96); + g.setColor(0, 255, 255); + g.drawRect(80, 57, 170, 96); + g.fillRect(80, 57, 170, 96); + g.setColor(0, 0, 0); + g.drawString(require("locale").time(new Date(), 1), 70, 60); + g.setFont("8x12", 2); + g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130); + g.setFont("8x12"); + g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126); + g.setFont("8x12", 2); + const time = new Date().getDate(); + g.drawString(time < 10 ? "0" + time : time, 78, 137); } function drawBattery() { - bigThenSmall(E.getBattery(), "%", 140, 23); + bigThenSmall(E.getBattery(), "%", 135, 21); } +function drawRocket() { + let Rocket = getRocketSequences(); + g.clearRect(5, 62, 63, 115); + g.setColor(0, 255, 255); + g.drawRect(5, 62, 63, 115); + g.fillRect(5, 62, 63, 115); + g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 }); + g.setColor(0, 0, 0); + rocketSequence = rocketSequence + 1; + if(rocketSequence > 8) rocketSequence = 1; +} + +function getTemperature(){ + try { + var weatherJson = storage.readJSON('weather.json'); + var weather = weatherJson.weather; + return Math.round(weather.temp-273.15); + + } catch(ex) { + print(ex) + return "?" + } +} function getSteps() { - var steps = 0; - try{ - if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } else if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else { - steps = Bangle.getHealthStatus("day").steps; - } - } catch(ex) { - // In case we failed, we can only show 0 steps. - return "? k"; - } - - steps = Math.round(steps/1000); - return steps + "k"; + var steps = Bangle.getHealthStatus("day").steps; + steps = Math.round(steps/1000); + return steps + "k"; } - function draw() { - - queueDraw(); + queueDraw(); - g.reset(); - g.clear(); - g.setColor(255, 255, 255); - g.fillRect(0, 0, g.getWidth(), g.getHeight()); - let background = getBackgroundImage(); - g.drawImage(background, 0, 0, { scale: 1 }); - - - g.setColor(0, 0, 0); - if(dataJson && dataJson.weather) drawWeather(dataJson.weather); - if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks); - + g.clear(1); + g.setColor(0, 255, 255); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + let background = getBackgroundImage(); + g.drawImage(background, 0, 0, { scale: 1 }); + g.setColor(0, 0, 0); + g.setFont("6x12"); + g.drawString("Launching Process", 30, 20); + g.setFont("8x12"); + g.drawString("ACTIVATE", 40, 35); - g.setFontAlign(0,-1); - g.setFont("8x12", 2); + g.setFontAlign(0,-1); + g.setFont("8x12", 2); + g.drawString(getTemperature(), 155, 132); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(getSteps(), 158, 98); - drawSteps(); - g.setFontAlign(-1,-1); - drawClock(); - drawBattery(); - drawTimer(); - // Hide widgets - for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); + + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } -// save batt power, does not seem to work although... -var canTouch = true; Bangle.on("lcdPower", (on) => { - if (on) { - draw(); - } else { - canTouch = false; - clearIntervals(); - } + if (on) { + draw(); + } else { + clearIntervals(); + } }); Bangle.on("lock", (locked) => { - clearIntervals(); - draw(); - if (!locked) { - canTouch = true; - } else { - canTouch = false; - } + clearIntervals(); + draw(); + if (!locked) { + rocketInterval = setInterval(drawRocket, rocketSpeed); + } }); - Bangle.setUI("clock"); // Load widgets, but don't show them Bangle.loadWidgets(); - -g.reset(); -g.clear(); +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe +g.clear(1); draw(); diff --git a/apps/advcasio/metadata.json b/apps/advcasio/metadata.json index 152f47132..25dc1243a 100644 --- a/apps/advcasio/metadata.json +++ b/apps/advcasio/metadata.json @@ -1,7 +1,7 @@ { "id": "advcasio", "name": "Advanced Casio Clock", "shortName":"advcasio", - "version":"0.03", + "version":"0.04", "description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.", "icon": "app.png", "tags": "clock", @@ -12,7 +12,7 @@ { "url": "screenshot-clock-3.jpg" }, { "url": "screenshot-webapp.jpg" } ], - "supports" : ["BANGLEJS", "BANGLEJS2"], + "supports" : ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "allow_emulator":true, "storage": [ diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog index 0a7916810..7f749ff25 100644 --- a/apps/agenda/ChangeLog +++ b/apps/agenda/ChangeLog @@ -4,4 +4,6 @@ 0.04: Added awareness of allDay field 0.05: Displaying calendar colour and name 0.06: Added clkinfo for clocks. -0.07: Clkinfo improvements. \ No newline at end of file +0.07: Clkinfo improvements. +0.08: Fix error in clkinfo (didn't require Storage & locale) + Fix clkinfo icon diff --git a/apps/agenda/agenda.clkinfo.js b/apps/agenda/agenda.clkinfo.js index 6c2ddb3da..baa8b9516 100644 --- a/apps/agenda/agenda.clkinfo.js +++ b/apps/agenda/agenda.clkinfo.js @@ -1,29 +1,29 @@ (function() { - var agendaItems = { - name: "Agenda", - img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="), - items: [] - }; + var agendaItems = { + name: "Agenda", + img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), + items: [] + }; + var locale = require("locale"); + var now = new Date(); + var agenda = require("Storage").readJSON("android.calendar.json") + .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) + .sort((a,b)=>a.timestamp - b.timestamp); - var now = new Date(); - var agenda = storage.readJSON("android.calendar.json") - .filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000) - .sort((a,b)=>a.timestamp - b.timestamp); + agenda.forEach((entry, i) => { - agenda.forEach((entry, i) => { + var title = entry.title.slice(0,12); + var date = new Date(entry.timestamp*1000); + var dateStr = locale.date(date).replace(/\d\d\d\d/,""); + dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; - var title = entry.title.slice(0,18); - var date = new Date(entry.timestamp*1000); - var dateStr = locale.date(date).replace(/\d\d\d\d/,""); - dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; + agendaItems.items.push({ + name: "Agenda "+i, + get: () => ({ text: title + "\n" + dateStr, img: null}), + show: function() { agendaItems.items[i].emit("redraw"); }, + hide: function () {} + }); + }); - agendaItems.items.push({ - name: null, - get: () => ({ text: title + "\n" + dateStr, img: null}), - show: function() { agendaItems.items[i].emit("redraw"); }, - hide: function () {} - }); - }); - - return agendaItems; -}) \ No newline at end of file + return agendaItems; +}) diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json index 6d91455f0..7e49e3f96 100644 --- a/apps/agenda/metadata.json +++ b/apps/agenda/metadata.json @@ -1,11 +1,11 @@ { "id": "agenda", "name": "Agenda", - "version": "0.07", + "version": "0.08", "description": "Simple agenda", "icon": "agenda.png", "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], - "tags": "agenda", + "tags": "agenda,clkinfo", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, diff --git a/apps/agpsdata/ChangeLog b/apps/agpsdata/ChangeLog index 89844a132..8ada244d7 100644 --- a/apps/agpsdata/ChangeLog +++ b/apps/agpsdata/ChangeLog @@ -2,3 +2,4 @@ 0.02: Load AGPS data on app start and automatically in background 0.03: Do not load AGPS data on boot Increase minimum interval to 6 hours +0.04: Write AGPS data chunks with delay to improve reliability diff --git a/apps/agpsdata/app.js b/apps/agpsdata/app.js index 647723bb4..4a6d2ba5c 100644 --- a/apps/agpsdata/app.js +++ b/apps/agpsdata/app.js @@ -36,7 +36,7 @@ function updateAgps() { g.clear(); if (!waiting) { waiting = true; - display("Updating A-GPS..."); + display("Updating A-GPS...", "takes ~ 10 seconds"); require("agpsdata").pull(function() { waiting = false; display("A-GPS updated.", "touch to close"); diff --git a/apps/agpsdata/lib.js b/apps/agpsdata/lib.js index 7d9758c0a..34608a5c6 100644 --- a/apps/agpsdata/lib.js +++ b/apps/agpsdata/lib.js @@ -8,41 +8,52 @@ var FILE = "agpsdata.settings.json"; var settings; readSettings(); -function setAGPS(data) { - var js = jsFromBase64(data); - try { - eval(js); - return true; - } - catch(e) { - console.log("error:", e); - } - return false; +function setAGPS(b64) { + return new Promise(function(resolve, reject) { + var initCommands = "Bangle.setGPSPower(1);\n"; // turn GPS on + const gnsstype = settings.gnsstype || 1; // default GPS + initCommands += `Serial1.println("${CASIC_CHECKSUM("$PCAS04," + gnsstype)}")\n`; // set GNSS mode + // What about: + // NAV-TIMEUTC (0x01 0x10) + // NAV-PV (0x01 0x03) + // or AGPS.zip uses AID-INI (0x0B 0x01) + + eval(initCommands); + + try { + writeChunks(atob(b64), resolve); + } catch (e) { + console.log("error:", e); + reject(); + } + }); } -function jsFromBase64(b64) { - var bin = atob(b64); - var chunkSize = 128; - var js = "Bangle.setGPSPower(1);\n"; // turn GPS on - var gnsstype = settings.gnsstype || 1; // default GPS - js += `Serial1.println("${CASIC_CHECKSUM("$PCAS04,"+gnsstype)}")\n`; // set GNSS mode - // What about: - // NAV-TIMEUTC (0x01 0x10) - // NAV-PV (0x01 0x03) - // or AGPS.zip uses AID-INI (0x0B 0x01) +var chunkI = 0; +function writeChunks(bin, resolve) { + return new Promise(function(resolve2) { + const chunkSize = 128; + setTimeout(function() { + if (chunkI < bin.length) { + var chunk = bin.substr(chunkI, chunkSize); + js = `Serial1.write(atob("${btoa(chunk)}"))\n`; + eval(js); - for (var i=0;i { - let result = setAGPS(event.resp); - if (result) { - updateLastUpdate(); - if (successCallback) successCallback(); - } else { - console.log("error applying AGPS data"); - if (failureCallback) failureCallback("Error applying AGPS data"); - } - }).catch((e)=>{ - console.log("error", e); - if (failureCallback) failureCallback(e); - }); + const uri = "https://www.espruino.com/agps/casic.base64"; + if (Bangle.http) { + Bangle.http(uri, {timeout : 10000}) + .then(event => { + setAGPS(event.resp) + .then(r => { + updateLastUpdate(); + if (successCallback) + successCallback(); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); + }) + .catch((e) => { + console.log("error", e); + if (failureCallback) + failureCallback(e); + }); } else { console.log("error: No http method found"); - if (failureCallback) failureCallback(/*LANG*/"No http method"); + if (failureCallback) + failureCallback(/*LANG*/ "No http method"); } }; diff --git a/apps/agpsdata/metadata.json b/apps/agpsdata/metadata.json index 1ce299532..203a00f72 100644 --- a/apps/agpsdata/metadata.json +++ b/apps/agpsdata/metadata.json @@ -2,7 +2,7 @@ "name": "A-GPS Data Downloader App", "shortName":"A-GPS Data", "icon": "agpsdata.png", - "version":"0.03", + "version":"0.04", "description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.", "tags": "boot,tool,assisted,gps,agps,http", "allow_emulator":true, diff --git a/apps/aiclock/ChangeLog b/apps/aiclock/ChangeLog index 68f77be10..96b389f6e 100644 --- a/apps/aiclock/ChangeLog +++ b/apps/aiclock/ChangeLog @@ -1,2 +1,4 @@ 0.01: New app! -0.02: Design improvements and fixes. \ No newline at end of file +0.02: Design improvements and fixes. +0.03: Indicate battery level through line occurrence. +0.04: Use widget_utils module. diff --git a/apps/aiclock/README.md b/apps/aiclock/README.md index a3855a166..9e23de3a6 100644 --- a/apps/aiclock/README.md +++ b/apps/aiclock/README.md @@ -8,14 +8,16 @@ The original output of stable diffusion is shown here: ![](orig.png) -And my implementation is shown here: +My implementation is shown below. Note that horizontal lines occur randomly, but the +probability is correlated with the battery level. So if your screen contains only +a few lines its time to charge your bangle again ;) ![](impl.png) # Thanks to -The great open source community: I used an open source diffusion model (https://github.com/CompVis/stable-diffusion) -to generate a watch face for the open source smartwatch BangleJs. +The great open-source community: I used an open-source diffusion model (https://github.com/CompVis/stable-diffusion) +to generate a watch face for the open-source smartwatch BangleJs. ## Creator - [David Peer](https://github.com/peerdavid). \ No newline at end of file diff --git a/apps/aiclock/aiclock.app.js b/apps/aiclock/aiclock.app.js index ecac5f2dc..5d4e98fcd 100644 --- a/apps/aiclock/aiclock.app.js +++ b/apps/aiclock/aiclock.app.js @@ -37,8 +37,15 @@ function drawBackground() { g.setFontAlign(0,0); g.setColor(g.theme.fg); - y = 0; + var bat = E.getBattery() / 100.0; + var y = 0; while(y < H){ + // Show less lines in case of small battery level. + if(Math.random() > bat){ + y += 5; + continue; + } + y += 3 + Math.floor(Math.random() * 10); g.drawLine(0, y, W, y); g.drawLine(0, y+1, W, y+1); @@ -103,7 +110,7 @@ function drawDate(){ g.setFontAlign(0,0); g.setFontGochiHand(); - var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+date.getMonth()).substr(-2); + var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2); var w = g.stringWidth(text); g.setColor(g.theme.bg); g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12); @@ -208,8 +215,7 @@ Bangle.loadWidgets(); * so we will blank out the draw() functions of each widget and change the * area to the top bar doesn't get cleared. */ -for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} - +require('widget_utils').hide(); // Clear the screen once, at startup and draw clock g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear(); draw(); diff --git a/apps/aiclock/metadata.json b/apps/aiclock/metadata.json index 5ab0c5bb3..0da23e8ca 100644 --- a/apps/aiclock/metadata.json +++ b/apps/aiclock/metadata.json @@ -3,7 +3,7 @@ "name": "AI Clock", "shortName":"AI Clock", "icon": "aiclock.png", - "version":"0.02", + "version":"0.04", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.", diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 52ee8bf9c..6ce6147ca 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -34,4 +34,6 @@ 0.32: Fix wrong hidden filter Add option for auto-delete a timer after it expires 0.33: Allow hiding timers&alarms +0.34: Add "Confirm" option to alarm/timer edit menus +0.35: Add automatic translation of more strings diff --git a/apps/alarm/app.js b/apps/alarm/app.js index ed5aa608a..1414c0b90 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -128,7 +128,12 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) { value: alarm.hidden || false, onchange: v => alarm.hidden = v }, - /*LANG*/"Cancel": () => showMainMenu() + /*LANG*/"Cancel": () => showMainMenu(), + /*LANG*/"Confirm": () => { + prepareAlarmForSave(alarm, alarmIndex, time); + saveAndReload(); + showMainMenu(); + } }; if (!isNew) { @@ -178,7 +183,7 @@ function decodeDOW(alarm) { .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_") .join("") .toLowerCase() - : "Once" + : /*LANG*/"Once" } function showEditRepeatMenu(repeat, dow, dowChangeCallback) { @@ -293,7 +298,12 @@ function showEditTimerMenu(selectedTimer, timerIndex) { onchange: v => timer.hidden = v }, /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v), - /*LANG*/"Cancel": () => showMainMenu() + /*LANG*/"Cancel": () => showMainMenu(), + /*LANG*/"Confirm": () => { + prepareTimerForSave(timer, timerIndex, time); + saveAndReload(); + showMainMenu(); + } }; if (!isNew) { diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 31dd58ece..a02985851 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.33", + "version": "0.35", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/alpinenav/ChangeLog b/apps/alpinenav/ChangeLog new file mode 100644 index 000000000..b3d1e0874 --- /dev/null +++ b/apps/alpinenav/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/alpinenav/app.js b/apps/alpinenav/app.js index 29eeab0c9..7cffc39c3 100644 --- a/apps/alpinenav/app.js +++ b/apps/alpinenav/app.js @@ -224,7 +224,7 @@ Bangle.on('mag', function (m) { if (isNaN(m.heading)) compass_heading = "---"; else - compass_heading = 360 - Math.round(m.heading); + compass_heading = Math.round(m.heading); current_colour = g.getColor(); g.reset(); g.setColor(background_colour); diff --git a/apps/alpinenav/metadata.json b/apps/alpinenav/metadata.json index dcb56e912..c5a0e0611 100644 --- a/apps/alpinenav/metadata.json +++ b/apps/alpinenav/metadata.json @@ -1,7 +1,7 @@ { "id": "alpinenav", "name": "Alpine Nav", - "version": "0.01", + "version": "0.02", "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", "icon": "app-icon.png", "tags": "outdoors,gps", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index a65326941..e4a3e9de0 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -14,3 +14,6 @@ 0.14: Fix timeout of http function not being cleaned up 0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later) 0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) +0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge +0.18: Use new message library + If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) \ No newline at end of file diff --git a/apps/android/README.md b/apps/android/README.md index f9ab73699..c76e6e528 100644 --- a/apps/android/README.md +++ b/apps/android/README.md @@ -20,6 +20,8 @@ It contains: of Gadgetbridge - making your phone make noise so you can find it. * `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js keep any messages it has received, or should it delete them? +* `Overwrite GPS` - when GPS is requested by an app, this doesn't use Bangle.js's GPS +but instead asks Gadgetbridge on the phone to use the phone's GPS * `Messages` - launches the messages app, showing a list of messages ## How it works diff --git a/apps/android/boot.js b/apps/android/boot.js index 0d1edae99..8e75241e7 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -91,10 +91,6 @@ sched.reload(); }, //TODO perhaps move those in a library (like messages), used also for viewing events? - //simple package with events all together - "calendarevents" : function() { - require("Storage").writeJSON("android.calendar.json", event.events); - }, //add and remove events based on activity on phone (pebble-like) "calendar" : function() { var cal = require("Storage").readJSON("android.calendar.json",true); @@ -109,7 +105,7 @@ "calendar-" : function() { var cal = require("Storage").readJSON("android.calendar.json",true); //if any of those happen we are out of sync! - if (!cal || !Array.isArray(cal)) return; + if (!cal || !Array.isArray(cal)) cal = []; cal = cal.filter(e=>e.id!=event.id); require("Storage").writeJSON("android.calendar.json", cal); }, @@ -130,6 +126,18 @@ request.j(event.err); //r = reJect function else request.r(event); //r = resolve function + }, + "gps": function() { + const settings = require("Storage").readJSON("android.settings.json",1)||{}; + if (!settings.overwriteGps) return; + delete event.t; + event.satellites = NaN; + event.course = NaN; + event.fix = 1; + Bangle.emit('gps', event); + }, + "is_gps_active": function() { + gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 }); } }; var h = HANDLERS[event.t]; @@ -170,7 +178,10 @@ // Battery monitor function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } - NRF.on("connect", () => setTimeout(sendBattery, 2000)); + NRF.on("connect", () => setTimeout(function() { + sendBattery(); + GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process + }, 2000)); Bangle.on("charging", sendBattery); if (!settings.keep) NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect @@ -190,6 +201,30 @@ if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id }); // error/warn here? }; + // GPS overwrite logic + if (settings.overwriteGps) { // if the overwrite option is set../ + // Save current logic + const originalSetGpsPower = Bangle.setGPSPower; + // Replace set GPS power logic to suppress activation of gps (and instead request it from the phone) + Bangle.setGPSPower = (isOn, appID) => { + // if not connected, use old logic + if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID); + // Emulate old GPS power logic + if (!Bangle._PWR) Bangle._PWR={}; + if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; + if (!appID) appID="?"; + if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID); + if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1); + let pwr = Bangle._PWR.GPS.length>0; + gbSend({ t: "gps_power", status: pwr }); + return pwr; + } + // Replace check if the GPS is on to check the _PWR variable + Bangle.isGPSOn = () => { + return Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0; + } + } + // remove settings object so it's not taking up RAM delete settings; })(); diff --git a/apps/android/metadata.json b/apps/android/metadata.json index ab340340c..45c08a75a 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,11 +2,11 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.16", + "version": "0.18", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", - "dependencies": {"messages":"app"}, + "dependencies": {"messages":"module"}, "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/android/settings.js b/apps/android/settings.js index c7c34a76f..3e04e0f9d 100644 --- a/apps/android/settings.js +++ b/apps/android/settings.js @@ -1,4 +1,7 @@ (function(back) { + + + function gb(j) { Bluetooth.println(JSON.stringify(j)); } @@ -23,7 +26,17 @@ updateSettings(); } }, - /*LANG*/"Messages" : ()=>load("messages.app.js"), + /*LANG*/"Overwrite GPS" : { + value : !!settings.overwriteGps, + onchange: newValue => { + if (newValue) { + Bangle.setGPSPower(false, 'android'); + } + settings.overwriteGps = newValue; + updateSettings(); + } + }, + /*LANG*/"Messages" : ()=>require("message").openGUI(), }; E.showMenu(mainmenu); }) diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 9e75d889a..4ef0cee75 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -12,3 +12,5 @@ 0.08: fixed calendar weeknumber not shortened to two digits 0.09: Use default Bangle formatter for booleans 0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 +0.11: Moved enhanced Anton clock to 'Anton Clock Plus' and stripped this clock back down to make it faster for new users (270ms -> 170ms) + Modified to avoid leaving functions defined when using setUI({remove:...}) diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index 07f67f696..528866588 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -4,234 +4,42 @@ Graphics.prototype.setFontAnton = function(scale) { g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78 + (scale << 8) + (1 << 16)); }; -Graphics.prototype.setFontAntonSmall = function(scale) { - // Actual height 53 (52 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAMAAAAAAAAD8AAAAAAAA/8AAAAAAAf/8AAAAAAH//8AAAAAB///8AAAAA////8AAAAP////8AAAD/////8AAB//////8AAf//////8AH///////4A///////+AA///////AAA//////wAAA/////8AAAA////+AAAAA////gAAAAA///4AAAAAA//8AAAAAAA//AAAAAAAA/wAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAA//////8AAB//////+AAH///////gAH///////gAP///////wAf///////4Af///////4A////////8A////////8A////////8A//AAAAD/8A/8AAAAA/8A/8AAAAA/8A/8AAAAA/8A/+AAAAB/8A////////8A////////8A////////8Af///////4Af///////4AP///////wAP///////wAH///////gAD///////AAA//////8AAAP/////wAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAA/4AAAAAAAA/4AAAAAAAB/wAAAAAAAB/wAAAAAAAD/wAAAAAAAD/gAAAAAAAH///////8AP///////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAP8AA//4AAA/8AB//4AAH/8AH//4AAP/8AP//4AA//8AP//4AB//8Af//4AD//8Af//4AP//8A///4Af//8A///4A///8A///4D///8A//AAH///8A/8AAP///8A/8AA//+/8A/8AD//8/8A/+Af//w/8A//////g/8A/////+A/8A/////8A/8Af////4A/8Af////wA/8AP////AA/8AP///+AA/8AH///8AA/8AD///wAA/8AA///AAA/8AAP/4AAA/8AAAAAAAAAAAAAAAAAAAAAAH4AAf/gAAA/4AAf/8AAD/4AAf//AAH/4AAf//gAP/4AAf//wAP/4AAf//wAf/4AAf//4Af/4AAf//4A//4AAf//8A//4AAf//8A//4AAP//8A//A/8AB/8A/8A/8AA/8A/8B/8AA/8A/8B/8AA/8A/+D//AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////gAH//9////gAD//4///+AAB//wf//4AAAP/AH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAB//wAAAAAAP//wAAAAAD///wAAAAA////wAAAAH////wAAAB/////wAAAf/////wAAD//////wAA///////wAA/////h/wAA////wB/wAA///8AB/wAA///AAB/wAA//gAAB/wAA////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA////4P/+AA////4P//AA////4P//gA////4P//wA////4P//wA////4P//4A////4P//4A////4P//8A////4P//8A////4P//8A/8H/AAB/8A/8H+AAA/8A/8P+AAA/8A/8P+AAA/8A/8P/gAD/8A/8P/////8A/8P/////8A/8P/////8A/8P/////4A/8H/////4A/8H/////wA/8D/////wA/8B/////gA/8A////+AA/8AP///4AAAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////wAAAf/////8AAB///////AAH///////gAP///////wAP///////wAf///////4Af///////4A////////8A////////8A////////8A/+AH/AB/8A/8AP+AA/8A/4Af+AA/8A/8Af+AA/8A/8Af/gH/8A//4f////8A//4f////8A//4f////8Af/4f////4Af/4f////4AP/4P////wAP/4P////gAH/4H////AAD/4D///+AAB/4B///4AAAP4AP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAA/8AAAAAAAA/8AAAAAB8A/8AAAAB/8A/8AAAAf/8A/8AAAH//8A/8AAA///8A/8AAH///8A/8AA////8A/8AD////8A/8Af////8A/8B/////8A/8P/////8A/8//////8A////////AA///////AAA//////gAAA/////4AAAA/////AAAAA////4AAAAA////AAAAAA///8AAAAAA///gAAAAAA//+AAAAAAA//wAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gD//gAAA//4P//8AAD//8f///AAH//+////gAH///////wAP///////4AP///////8Af///////8Af///////+Af///////+A////////+A//B//AB/+A/+A/+AA/+A/8Af+AA/+A/+Af+AA/+A//A//AB/+A////////+Af///////+Af///////+Af///////8Af///////8AP///////4AH///////4AH//+////wAD//+////AAA//4P//+AAAP/gH//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAfgAAA///8A/8AAB///+A//AAH////A//gAH////g//wAP////g//wAf////w//4Af////w//4A/////w//8A/////w//8A/////w//8A//gP/wA/8A/8AD/wA/8A/8AD/wAf8A/8AD/gA/8A/+AH/AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////wAH///////gAD//////+AAA//////4AAAP/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DhgeFB4eHh4eHh4eDw=="), 60 + (scale << 8) + (1 << 16)); -}; - { // must be inside our own scope here so that when we are unloaded everything disappears - -const SETTINGSFILE = "antonclk.json"; -const isBangle1 = (process.env.HWVERSION == 1); - -// variables defined from settings -let secondsMode; -let secondsColoured; -let secondsWithColon; -let dateOnMain; -let dateOnSecs; -let weekDay; -let calWeek; -let upperCase; -let vectorFont; - -// dynamic variables + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global let drawTimeout; -let queueMillis = 1000; -let secondsScreen = true; +// Actually draw the watch face +let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2; + g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets) + var date = new Date(); + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); + // Show date and day of week + var dateStr = require("locale").date(date, 0).toUpperCase()+"\n"+ + require("locale").dow(date, 0).toUpperCase(); + g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+48); - -//For development purposes -/* -require('Storage').writeJSON(SETTINGSFILE, { - secondsMode: "Unlocked", // "Never", "Unlocked", "Always" - secondsColoured: true, - secondsWithColon: true, - dateOnMain: "Long", // "Short", "Long", "ISO8601" - dateOnSecs: "Year", // "No", "Year", "Weekday", LEGACY: true/false - weekDay: true, - calWeek: true, - upperCase: true, - vectorFont: true, -}); -*/ - -// OR (also for development purposes) -/* -require('Storage').erase(SETTINGSFILE); -*/ - -// Load settings -function loadSettings() { - // Helper function default setting - function def (value, def) {return value !== undefined ? value : def;} - - var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; - secondsMode = def(settings.secondsMode, "Never"); - secondsColoured = def(settings.secondsColoured, true); - secondsWithColon = def(settings.secondsWithColon, true); - dateOnMain = def(settings.dateOnMain, "Long"); - dateOnSecs = def(settings.dateOnSecs, "Year"); - weekDay = def(settings.weekDay, true); - calWeek = def(settings.calWeek, false); - upperCase = def(settings.upperCase, true); - vectorFont = def(settings.vectorFont, false); - - // Legacy - if (dateOnSecs === true) - dateOnSecs = "Year"; - if (dateOnSecs === false) - dateOnSecs = "No"; -} - -// schedule a draw for the next second or minute -function queueDraw() { + // queue next draw if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function() { drawTimeout = undefined; draw(); - }, queueMillis - (Date.now() % queueMillis)); -} + }, 60000 - (Date.now() % 60000)); +}; -function updateState() { - if (Bangle.isLCDOn()) { - if ((secondsMode === "Unlocked" && !Bangle.isLocked()) || secondsMode === "Always") { - secondsScreen = true; - queueMillis = 1000; - } else { - secondsScreen = false; - queueMillis = 60000; - } - draw(); // draw immediately, queue redraw - } else { // stop draw timer - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - } -} - -function isoStr(date) { - return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2); -} - -let calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested) -function ISO8601calWeek(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 - dateNoTime = date; dateNoTime.setHours(0,0,0,0); - if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2]; - calWeekBuffer[0] = date.getTimezoneOffset(); - calWeekBuffer[1] = dateNoTime; - var tdt = new Date(date.valueOf()); - var dayn = (date.getDay() + 6) % 7; - tdt.setDate(tdt.getDate() - dayn + 3); - var firstThursday = tdt.valueOf(); - tdt.setMonth(0, 1); - if (tdt.getDay() !== 4) { - tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); - } - calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000); - return calWeekBuffer[2]; -} - -function doColor() { - return !isBangle1 && !Bangle.isLocked() && secondsColoured; -} - -// Actually draw the watch face -function draw() { - var x = g.getWidth() / 2; - var y = g.getHeight() / 2 - (secondsMode !== "Never" ? 24 : (vectorFont ? 12 : 0)); - g.reset(); - /* This is to mark the widget areas during development. - g.setColor("#888") - .fillRect(0, 0, g.getWidth(), 23) - .fillRect(0, g.getHeight() - 23, g.getWidth(), g.getHeight()).reset(); - /* */ - g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24); // clear whole background (w/o widgets) - var date = new Date(); // Actually the current date, this one is shown - var timeStr = require("locale").time(date, 1); // Hour and minute - g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); // draw time - if (secondsScreen) { - y += 65; - var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).slice(-2); - if (doColor()) - g.setColor(0, 0, 1); - g.setFont("AntonSmall"); - if (dateOnSecs !== "No") { // A bit of a complex drawing with seconds on the right and date on the left - g.setFontAlign(1, 0).drawString(secStr, g.getWidth() - (isBangle1 ? 32 : 2), y); // seconds - y -= (vectorFont ? 15 : 13); - x = g.getWidth() / 4 + (isBangle1 ? 12 : 4) + (secondsWithColon ? 0 : g.stringWidth(":") / 2); - var dateStr2 = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, 1)); - var year; - var md; - var yearfirst; - if (dateStr2.match(/\d\d\d\d$/)) { // formatted date ends with year - year = (dateOnSecs === "Year" ? dateStr2.slice(-4) : require("locale").dow(date, 1)); - md = dateStr2.slice(0, -4); - if (!md.endsWith(".")) // keep separator before the year only if it is a dot (31.12. but 31/12) - md = md.slice(0, -1); - yearfirst = false; - } else { // formatted date begins with year - if (!dateStr2.match(/^\d\d\d\d/)) // if year position cannot be detected... - dateStr2 = isoStr(date); // ...use ISO date format instead - year = (dateOnSecs === "Year" ? dateStr2.slice(0, 4) : require("locale").dow(date, 1)); - md = dateStr2.slice(5); // never keep separator directly after year - yearfirst = true; - } - if (dateOnSecs === "Weekday" && upperCase) - year = year.toUpperCase(); - g.setFontAlign(0, 0); - if (vectorFont) - g.setFont("Vector", 24); - else - g.setFont("6x8", 2); - if (doColor()) - g.setColor(1, 0, 0); - g.drawString(md, x, (yearfirst ? y + (vectorFont ? 26 : 16) : y)); - g.drawString(year, x, (yearfirst ? y : y + (vectorFont ? 26 : 16))); - } else { - g.setFontAlign(0, 0).drawString(secStr, x, y); // Just the seconds centered - } - } else { // No seconds screen: Show date and optionally day of week - y += (vectorFont ? 50 : (secondsMode !== "Never") ? 52 : 40); - var dateStr = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, (dateOnMain === "Long" ? 0 : 1))); - if (upperCase) - dateStr = dateStr.toUpperCase(); - g.setFontAlign(0, 0); - if (vectorFont) - g.setFont("Vector", 24); - else - g.setFont("6x8", 2); - g.drawString(dateStr, x, y); - if (calWeek || weekDay) { - var dowcwStr = ""; - if (calWeek) - dowcwStr = " #" + ("0" + ISO8601calWeek(date)).slice(-2); - if (weekDay) - dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort # e.g. Mon #01 - else //week #01 - dowcwStr = /*LANG*/"week" + dowcwStr; - if (upperCase) - dowcwStr = dowcwStr.toUpperCase(); - g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16)); - } - } - - // queue next draw - queueDraw(); -} - -// Init the settings of the app -loadSettings(); -// Clear the screen once, at startup -g.clear(); -// Set dynamic state and perform initial drawing -updateState(); -// Register hooks for LCD on/off event and screen lock on/off event -Bangle.on('lcdPower', updateState); -Bangle.on('lock', updateState); // Show launcher when middle button pressed -Bangle.setUI({ +Bangle.setUI({ mode : "clock", remove : function() { // Called to unload all of the clock app - Bangle.removeListener('lcdPower', updateState); - Bangle.removeListener('lock', updateState); if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; delete Graphics.prototype.setFontAnton; - delete Graphics.prototype.setFontAntonSmall; }}); // Load widgets Bangle.loadWidgets(); -Bangle.drawWidgets(); +draw(); +setTimeout(Bangle.drawWidgets,0); } diff --git a/apps/antonclk/app.png b/apps/antonclk/app.png index a38093c5f..bb764d2a1 100644 Binary files a/apps/antonclk/app.png and b/apps/antonclk/app.png differ diff --git a/apps/antonclk/metadata.json b/apps/antonclk/metadata.json index b6134d1a1..b8242f11a 100644 --- a/apps/antonclk/metadata.json +++ b/apps/antonclk/metadata.json @@ -1,9 +1,8 @@ { "id": "antonclk", "name": "Anton Clock", - "version": "0.10", - "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", - "readme":"README.md", + "version": "0.11", + "description": "A simple clock using the bold Anton font. See `Anton Clock Plus` for an enhanced version", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", @@ -12,8 +11,6 @@ "allow_emulator": true, "storage": [ {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.settings.js","url":"settings.js"}, {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"antonclk.json"}] + ] } diff --git a/apps/antonclk/screenshot.png b/apps/antonclk/screenshot.png index e949b8a24..9b38e90d5 100644 Binary files a/apps/antonclk/screenshot.png and b/apps/antonclk/screenshot.png differ diff --git a/apps/antonclkplus/ChangeLog b/apps/antonclkplus/ChangeLog new file mode 100644 index 000000000..3b0a3d8b8 --- /dev/null +++ b/apps/antonclkplus/ChangeLog @@ -0,0 +1,15 @@ +0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide +0.03: Clock now shows day of week under date. +0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too. +0.05: Clock can optionally show ISO-8601 calendar weeknumber (default: Off) + when weekday name "Off": week #: + when weekday name "On": weekday name is cut at 6th position and .# is added +0.06: fixes #1271 - wrong settings name + when weekday name and calendar weeknumber are on then display is # + week is buffered until date or timezone changes +0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users) +0.08: fixed calendar weeknumber not shortened to two digits +0.09: Use default Bangle formatter for booleans +0.10: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 + Modified to avoid leaving functions defined when using setUI({remove:...}) diff --git a/apps/antonclk/README.md b/apps/antonclkplus/README.md similarity index 87% rename from apps/antonclk/README.md rename to apps/antonclkplus/README.md index 28a38f5fd..25b478dd9 100644 --- a/apps/antonclk/README.md +++ b/apps/antonclkplus/README.md @@ -1,6 +1,6 @@ -# Anton Clock - Large font digital watch with seconds and date +# Anton Clock Plus - Large font digital watch with seconds and date -Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit. +Anton Clock Plus uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit. ## Features @@ -16,16 +16,16 @@ The basic time representation only shows hours and minutes of the current time. ## Usage -Install Anton clock through the Bangle.js app loader. -Configure it through the default Bangle.js configuration mechanism +* Install Anton Clock Plus through the Bangle.js app loader. +* Configure it through the default Bangle.js configuration mechanism (Settings app, "Apps" menu, "Anton clock" submenu). -If you like it, make it your default watch face +* If you like it, make it your default watch face (Settings app, "System" menu, "Clock" submenu, select "Anton clock"). ## Configuration -Anton clock is configured by the standard settings mechanism of Bangle.js's operating system: -Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu. +Anton Clock is configured by the standard settings mechanism of Bangle.js's operating system: +Open the `Settings` app, then the `Apps` submenu and below it the `Anton Clock+` menu. You configure Anton clock through several "on/off" switches in two menus. ### The main menu diff --git a/apps/antonclkplus/app-icon.js b/apps/antonclkplus/app-icon.js new file mode 100644 index 000000000..0c3aeb210 --- /dev/null +++ b/apps/antonclkplus/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgf/AH4At/l/Aofgh4DB+EAj4REQoM/AgP4AoeACIoLCg4FB4AFDCIwLCgAROgYIB8EBAoUH/gVBCIxQBCKYHBCJp9DI4ICBLJYRCn4RQEYMOR5ARDIgIRMYQZZBgARGZwZBDCKQrCgEDR5AdBUIQRJDoLXFCJD7J/xrICIQFCn4RH/4LDAoTaCCI4Ar/LLDCBfypMkCgMkyV/CJOSCIOf5IRGFwOfCJNP//JnmT588z/+pM/BYIRCk4RC/88+f/n4RCngRCz1JCIf5/nzGoQRIHwXPCIPJI4f8CJHJGQJKCCI59LCI5ZCCJ/+v/kBoM/+V/HIJrHBYJWB/JKB5x9JEYP8AQKdBpwRL841Dp41KZoTxBHYTXBWY77PCKKhJ/4/CcgMkXoQAiA=")) diff --git a/apps/antonclkplus/app.js b/apps/antonclkplus/app.js new file mode 100644 index 000000000..409d7d487 --- /dev/null +++ b/apps/antonclkplus/app.js @@ -0,0 +1,238 @@ +// Clock with large digits using the "Anton" bold font +Graphics.prototype.setFontAnton = function(scale) { + // Actual height 69 (68 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78 + (scale << 8) + (1 << 16)); +}; + +Graphics.prototype.setFontAntonSmall = function(scale) { + // Actual height 53 (52 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAMAAAAAAAAD8AAAAAAAA/8AAAAAAAf/8AAAAAAH//8AAAAAB///8AAAAA////8AAAAP////8AAAD/////8AAB//////8AAf//////8AH///////4A///////+AA///////AAA//////wAAA/////8AAAA////+AAAAA////gAAAAA///4AAAAAA//8AAAAAAA//AAAAAAAA/wAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAA//////8AAB//////+AAH///////gAH///////gAP///////wAf///////4Af///////4A////////8A////////8A////////8A//AAAAD/8A/8AAAAA/8A/8AAAAA/8A/8AAAAA/8A/+AAAAB/8A////////8A////////8A////////8Af///////4Af///////4AP///////wAP///////wAH///////gAD///////AAA//////8AAAP/////wAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAA/4AAAAAAAA/4AAAAAAAB/wAAAAAAAB/wAAAAAAAD/wAAAAAAAD/gAAAAAAAH///////8AP///////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAP8AA//4AAA/8AB//4AAH/8AH//4AAP/8AP//4AA//8AP//4AB//8Af//4AD//8Af//4AP//8A///4Af//8A///4A///8A///4D///8A//AAH///8A/8AAP///8A/8AA//+/8A/8AD//8/8A/+Af//w/8A//////g/8A/////+A/8A/////8A/8Af////4A/8Af////wA/8AP////AA/8AP///+AA/8AH///8AA/8AD///wAA/8AA///AAA/8AAP/4AAA/8AAAAAAAAAAAAAAAAAAAAAAH4AAf/gAAA/4AAf/8AAD/4AAf//AAH/4AAf//gAP/4AAf//wAP/4AAf//wAf/4AAf//4Af/4AAf//4A//4AAf//8A//4AAf//8A//4AAP//8A//A/8AB/8A/8A/8AA/8A/8B/8AA/8A/8B/8AA/8A/+D//AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////gAH//9////gAD//4///+AAB//wf//4AAAP/AH//gAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAAAB//wAAAAAAP//wAAAAAD///wAAAAA////wAAAAH////wAAAB/////wAAAf/////wAAD//////wAA///////wAA/////h/wAA////wB/wAA///8AB/wAA///AAB/wAA//gAAB/wAA////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8A////////8AAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA////4P/+AA////4P//AA////4P//gA////4P//wA////4P//wA////4P//4A////4P//4A////4P//8A////4P//8A////4P//8A/8H/AAB/8A/8H+AAA/8A/8P+AAA/8A/8P+AAA/8A/8P/gAD/8A/8P/////8A/8P/////8A/8P/////8A/8P/////4A/8H/////4A/8H/////wA/8D/////wA/8B/////gA/8A////+AA/8AP///4AAAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////wAAAf/////8AAB///////AAH///////gAP///////wAP///////wAf///////4Af///////4A////////8A////////8A////////8A/+AH/AB/8A/8AP+AA/8A/4Af+AA/8A/8Af+AA/8A/8Af/gH/8A//4f////8A//4f////8A//4f////8Af/4f////4Af/4f////4AP/4P////wAP/4P////gAH/4H////AAD/4D///+AAB/4B///4AAAP4AP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAA/8AAAAAAAA/8AAAAAB8A/8AAAAB/8A/8AAAAf/8A/8AAAH//8A/8AAA///8A/8AAH///8A/8AA////8A/8AD////8A/8Af////8A/8B/////8A/8P/////8A/8//////8A////////AA///////AAA//////gAAA/////4AAAA/////AAAAA////4AAAAA////AAAAAA///8AAAAAA///gAAAAAA//+AAAAAAA//wAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gD//gAAA//4P//8AAD//8f///AAH//+////gAH///////wAP///////4AP///////8Af///////8Af///////+Af///////+A////////+A//B//AB/+A/+A/+AA/+A/8Af+AA/+A/+Af+AA/+A//A//AB/+A////////+Af///////+Af///////+Af///////8Af///////8AP///////4AH///////4AH//+////wAD//+////AAA//4P//+AAAP/gH//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAfgAAA///8A/8AAB///+A//AAH////A//gAH////g//wAP////g//wAf////w//4Af////w//4A/////w//8A/////w//8A/////w//8A//gP/wA/8A/8AD/wA/8A/8AD/wAf8A/8AD/gA/8A/+AH/AB/8A////////8A////////8A////////8Af///////4Af///////4Af///////wAP///////wAH///////gAD//////+AAA//////4AAAP/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAP+AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DhgeFB4eHh4eHh4eDw=="), 60 + (scale << 8) + (1 << 16)); +}; + +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global + +const SETTINGSFILE = "antonclk.json"; +const isBangle1 = (process.env.HWVERSION == 1); + +// variables defined from settings +let secondsMode; +let secondsColoured; +let secondsWithColon; +let dateOnMain; +let dateOnSecs; +let weekDay; +let calWeek; +let upperCase; +let vectorFont; + +// dynamic variables +let drawTimeout; +let queueMillis = 1000; +let secondsScreen = true; + + + +//For development purposes +/* +require('Storage').writeJSON(SETTINGSFILE, { + secondsMode: "Unlocked", // "Never", "Unlocked", "Always" + secondsColoured: true, + secondsWithColon: true, + dateOnMain: "Long", // "Short", "Long", "ISO8601" + dateOnSecs: "Year", // "No", "Year", "Weekday", LEGACY: true/false + weekDay: true, + calWeek: true, + upperCase: true, + vectorFont: true, +}); +*/ + +// OR (also for development purposes) +/* +require('Storage').erase(SETTINGSFILE); +*/ + +// Load settings +let loadSettings = function() { + // Helper function default setting + function def (value, def) {return value !== undefined ? value : def;} + + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + secondsMode = def(settings.secondsMode, "Never"); + secondsColoured = def(settings.secondsColoured, true); + secondsWithColon = def(settings.secondsWithColon, true); + dateOnMain = def(settings.dateOnMain, "Long"); + dateOnSecs = def(settings.dateOnSecs, "Year"); + weekDay = def(settings.weekDay, true); + calWeek = def(settings.calWeek, false); + upperCase = def(settings.upperCase, true); + vectorFont = def(settings.vectorFont, false); + + // Legacy + if (dateOnSecs === true) + dateOnSecs = "Year"; + if (dateOnSecs === false) + dateOnSecs = "No"; +} + +// schedule a draw for the next second or minute +let queueDraw = function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, queueMillis - (Date.now() % queueMillis)); +} + +let updateState = function() { + if (Bangle.isLCDOn()) { + if ((secondsMode === "Unlocked" && !Bangle.isLocked()) || secondsMode === "Always") { + secondsScreen = true; + queueMillis = 1000; + } else { + secondsScreen = false; + queueMillis = 60000; + } + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +} + +let isoStr = function(date) { + return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2); +} + +let calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested) +let ISO8601calWeek = function(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 + dateNoTime = date; dateNoTime.setHours(0,0,0,0); + if (calWeekBuffer[0] === date.getTimezoneOffset() && calWeekBuffer[1] === dateNoTime) return calWeekBuffer[2]; + calWeekBuffer[0] = date.getTimezoneOffset(); + calWeekBuffer[1] = dateNoTime; + var tdt = new Date(date.valueOf()); + var dayn = (date.getDay() + 6) % 7; + tdt.setDate(tdt.getDate() - dayn + 3); + var firstThursday = tdt.valueOf(); + tdt.setMonth(0, 1); + if (tdt.getDay() !== 4) { + tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); + } + calWeekBuffer[2] = 1 + Math.ceil((firstThursday - tdt) / 604800000); + return calWeekBuffer[2]; +} + +let doColor = function() { + return !isBangle1 && !Bangle.isLocked() && secondsColoured; +} + +// Actually draw the watch face +let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2 - (secondsMode !== "Never" ? 24 : (vectorFont ? 12 : 0)); + g.reset(); + /* This is to mark the widget areas during development. + g.setColor("#888") + .fillRect(0, 0, g.getWidth(), 23) + .fillRect(0, g.getHeight() - 23, g.getWidth(), g.getHeight()).reset(); + /* */ + g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24); // clear whole background (w/o widgets) + var date = new Date(); // Actually the current date, this one is shown + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); // draw time + if (secondsScreen) { + y += 65; + var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).slice(-2); + if (doColor()) + g.setColor(0, 0, 1); + g.setFont("AntonSmall"); + if (dateOnSecs !== "No") { // A bit of a complex drawing with seconds on the right and date on the left + g.setFontAlign(1, 0).drawString(secStr, g.getWidth() - (isBangle1 ? 32 : 2), y); // seconds + y -= (vectorFont ? 15 : 13); + x = g.getWidth() / 4 + (isBangle1 ? 12 : 4) + (secondsWithColon ? 0 : g.stringWidth(":") / 2); + var dateStr2 = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, 1)); + var year; + var md; + var yearfirst; + if (dateStr2.match(/\d\d\d\d$/)) { // formatted date ends with year + year = (dateOnSecs === "Year" ? dateStr2.slice(-4) : require("locale").dow(date, 1)); + md = dateStr2.slice(0, -4); + if (!md.endsWith(".")) // keep separator before the year only if it is a dot (31.12. but 31/12) + md = md.slice(0, -1); + yearfirst = false; + } else { // formatted date begins with year + if (!dateStr2.match(/^\d\d\d\d/)) // if year position cannot be detected... + dateStr2 = isoStr(date); // ...use ISO date format instead + year = (dateOnSecs === "Year" ? dateStr2.slice(0, 4) : require("locale").dow(date, 1)); + md = dateStr2.slice(5); // never keep separator directly after year + yearfirst = true; + } + if (dateOnSecs === "Weekday" && upperCase) + year = year.toUpperCase(); + g.setFontAlign(0, 0); + if (vectorFont) + g.setFont("Vector", 24); + else + g.setFont("6x8", 2); + if (doColor()) + g.setColor(1, 0, 0); + g.drawString(md, x, (yearfirst ? y + (vectorFont ? 26 : 16) : y)); + g.drawString(year, x, (yearfirst ? y : y + (vectorFont ? 26 : 16))); + } else { + g.setFontAlign(0, 0).drawString(secStr, x, y); // Just the seconds centered + } + } else { // No seconds screen: Show date and optionally day of week + y += (vectorFont ? 50 : (secondsMode !== "Never") ? 52 : 40); + var dateStr = (dateOnMain === "ISO8601" ? isoStr(date) : require("locale").date(date, (dateOnMain === "Long" ? 0 : 1))); + if (upperCase) + dateStr = dateStr.toUpperCase(); + g.setFontAlign(0, 0); + if (vectorFont) + g.setFont("Vector", 24); + else + g.setFont("6x8", 2); + g.drawString(dateStr, x, y); + if (calWeek || weekDay) { + var dowcwStr = ""; + if (calWeek) + dowcwStr = " #" + ("0" + ISO8601calWeek(date)).slice(-2); + if (weekDay) + dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort # e.g. Mon #01 + else //week #01 + dowcwStr = /*LANG*/"week" + dowcwStr; + if (upperCase) + dowcwStr = dowcwStr.toUpperCase(); + g.drawString(dowcwStr, x, y + (vectorFont ? 26 : 16)); + } + } + + // queue next draw + queueDraw(); +} + +// Init the settings of the app +loadSettings(); +// Clear the screen once, at startup +g.clear(); +// Set dynamic state and perform initial drawing +updateState(); +// Register hooks for LCD on/off event and screen lock on/off event +Bangle.on('lcdPower', updateState); +Bangle.on('lock', updateState); +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + Bangle.removeListener('lcdPower', updateState); + Bangle.removeListener('lock', updateState); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontAnton; + delete Graphics.prototype.setFontAntonSmall; + }}); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +} diff --git a/apps/antonclkplus/app.png b/apps/antonclkplus/app.png new file mode 100644 index 000000000..a38093c5f Binary files /dev/null and b/apps/antonclkplus/app.png differ diff --git a/apps/antonclkplus/metadata.json b/apps/antonclkplus/metadata.json new file mode 100644 index 000000000..05c59a4fb --- /dev/null +++ b/apps/antonclkplus/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "antonclkplus", + "name": "Anton Clock Plus", + "shortName": "Anton Clock+", + "version": "0.10", + "description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.", + "readme":"README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"antonclkplus.app.js","url":"app.js"}, + {"name":"antonclkplus.settings.js","url":"settings.js"}, + {"name":"antonclkplus.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"antonclkplus.json"}] +} diff --git a/apps/antonclkplus/screenshot.png b/apps/antonclkplus/screenshot.png new file mode 100644 index 000000000..e949b8a24 Binary files /dev/null and b/apps/antonclkplus/screenshot.png differ diff --git a/apps/antonclk/settings.js b/apps/antonclkplus/settings.js similarity index 100% rename from apps/antonclk/settings.js rename to apps/antonclkplus/settings.js diff --git a/apps/aptsciclk/metadata.json b/apps/aptsciclk/metadata.json index c450d926e..77e40f843 100644 --- a/apps/aptsciclk/metadata.json +++ b/apps/aptsciclk/metadata.json @@ -5,6 +5,7 @@ "version": "0.08", "description": "A clock based on the portal series", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/aptsciclk/screenshot.png b/apps/aptsciclk/screenshot.png new file mode 100644 index 000000000..4803e4b13 Binary files /dev/null and b/apps/aptsciclk/screenshot.png differ diff --git a/apps/astral/ChangeLog b/apps/astral/ChangeLog index c93c2b6c2..747e5ac2e 100644 --- a/apps/astral/ChangeLog +++ b/apps/astral/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget 0.03: Update to use Bangle.setUI instead of setWatch 0.04: Tell clock widgets to hide. +0.05: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/astral/app.js b/apps/astral/app.js index c4339bc09..a435ca9e3 100644 --- a/apps/astral/app.js +++ b/apps/astral/app.js @@ -834,7 +834,7 @@ Bangle.on('mag', function (m) { if (isNaN(m.heading)) compass_heading = "---"; else - compass_heading = 360 - Math.round(m.heading); + compass_heading = Math.round(m.heading); // g.setColor("#000000"); // g.fillRect(160, 10, 160, 20); g.setColor(display_colour); diff --git a/apps/astral/metadata.json b/apps/astral/metadata.json index d7120878b..647066a13 100644 --- a/apps/astral/metadata.json +++ b/apps/astral/metadata.json @@ -1,7 +1,7 @@ { "id": "astral", "name": "Astral Clock", - "version": "0.04", + "version": "0.05", "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", "icon": "app-icon.png", "type": "clock", diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index a00ae9325..88f4eaf00 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -12,4 +12,5 @@ 0.12: Add settings to hide date,widgets 0.13: Add font setting 0.14: Use ClockFace_menu.addItems -0.15: Add Power saving option \ No newline at end of file +0.15: Add Power saving option +0.16: Support Fast Loading diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index 5a7dfc8c0..f2499189b 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -1,124 +1,128 @@ /* jshint esversion: 6 */ -/** - * A simple digital clock showing seconds as a bar - **/ +{ + /** + * A simple digital clock showing seconds as a bar + **/ // Check settings for what type our clock should be -let locale = require("locale"); -{ // add some more info to locale - let date = new Date(); - date.setFullYear(1111); - date.setMonth(1, 3); // februari: months are zero-indexed - const localized = locale.date(date, true); - locale.dayFirst = /3.*2/.test(localized); - locale.hasMeridian = (locale.meridian(date)!==""); -} - -let barW = 0, prevX = 0; -function renderBar(l) { - "ram"; - if (l) prevX = 0; // called from Layout: drawing area was cleared - else l = clock.layout.bar; - let x2 = l.x+barW; - if (clock.powerSave && Bangle.isLocked()) x2 = 0; // hide bar - if (x2===prevX) return; // nothing to do - if (x2===0) x2--; // don't leave 1px line - if (x212) { - date12.setHours(hours-12); - } - return locale.time(date12, true); -} -function ampmText(date) { - return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : ""; -} -function dateText(date) { - const dayName = locale.dow(date, true), - month = locale.month(date, true), - day = date.getDate(); - const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`; - return `${dayName} ${dayMonth}`; -} -const ClockFace = require("ClockFace"), - clock = new ClockFace({ - precision: 1, - settingsFile: "barclock.settings.json", - init: function() { - const Layout = require("Layout"); - this.layout = new Layout({ - type: "v", c: [ - { - type: "h", c: [ - {id: "time", label: "88:88", type: "txt", font: "6x8:5", col: g.theme.fg, bgCol: g.theme.bg}, // updated below - {id: "ampm", label: " ", type: "txt", font: "6x8:2", col: g.theme.fg, bgCol: g.theme.bg}, - ], - }, - {id: "bar", type: "custom", fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, - this.showDate ? {height: 40} : {}, - this.showDate ? {id: "date", type: "txt", font: "10%", valign: 1} : {}, - ], - }, {lazy: true}); - // adjustments based on screen size and whether we display am/pm - let thickness; // bar thickness, same as time font "pixel block" size - if (this.is12Hour && locale.hasMeridian) { - // Maximum font size = ( - ) / (5chars * 6px) - thickness = Math.floor((Bangle.appRect.w-24)/(5*6)); - } else { - this.layout.ampm.label = ""; - thickness = Math.floor(Bangle.appRect.w/(5*6)); - } - let bar = this.layout.bar; - bar.height = thickness+1; - if (this.font===1) { // vector - const B2 = process.env.HWVERSION>1; + let barW = 0, prevX = 0; + const renderBar = function (l) { + "ram"; + if (l) prevX = 0; // called from Layout: drawing area was cleared + else l = clock.layout.bar; + let x2 = l.x+barW; + if (clock.powerSave && Bangle.isLocked()) x2 = 0; // hide bar + if (x2===prevX) return; // nothing to do + if (x2===0) x2--; // don't leave 1px line + if (x212) { + date12.setHours(hours-12); + } + return locale.time(date12, true); + } + const ampmText = date => (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : ""; + const dateText = date => { + const dayName = locale.dow(date, true), + month = locale.month(date, true), + day = date.getDate(); + const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`; + return `${dayName} ${dayMonth}`; + }; + + const ClockFace = require("ClockFace"), + clock = new ClockFace({ + precision: 1, + settingsFile: "barclock.settings.json", + init: function() { + const Layout = require("Layout"); + this.layout = new Layout({ + type: "v", c: [ + { + type: "h", c: [ + {id: "time", label: "88:88", type: "txt", font: "6x8:5", col: g.theme.fg, bgCol: g.theme.bg}, // updated below + {id: "ampm", label: " ", type: "txt", font: "6x8:2", col: g.theme.fg, bgCol: g.theme.bg}, + ], + }, + {id: "bar", type: "custom", fillx: 1, height: 6, col: g.theme.fg2, render: renderBar}, + this.showDate ? {height: 40} : {}, + this.showDate ? {id: "date", type: "txt", font: "10%", valign: 1} : {}, + ], + }, {lazy: true}); + // adjustments based on screen size and whether we display am/pm + let thickness; // bar thickness, same as time font "pixel block" size if (this.is12Hour && locale.hasMeridian) { - this.layout.time.font = "Vector:"+(B2 ? 50 : 60); - this.layout.ampm.font = "Vector:"+(B2 ? 20 : 40); + // Maximum font size = ( - ) / (5chars * 6px) + thickness = Math.floor((Bangle.appRect.w-24)/(5*6)); } else { - this.layout.time.font = "Vector:"+(B2 ? 60 : 80); + this.layout.ampm.label = ""; + thickness = Math.floor(Bangle.appRect.w/(5*6)); } - } else { - this.layout.time.font = "6x8:"+thickness; - } - this.layout.update(); - bar.y2 = bar.y+bar.height-1; - }, - update: function(date, c) { - "ram"; - if (c.m) this.layout.time.label = timeText(date); - if (c.h) this.layout.ampm.label = ampmText(date); - if (c.d && this.showDate) this.layout.date.label = dateText(date); - if (c.m) this.layout.render(); - if (c.s) { - barW = Math.round(date.getSeconds()/60*this.layout.bar.w); - renderBar(); - } - }, - resume: function() { - prevX = 0; // force redraw of bar - this.layout.forgetLazyState(); - }, - }); + let bar = this.layout.bar; + bar.height = thickness+1; + if (this.font===1) { // vector + const B2 = process.env.HWVERSION>1; + if (this.is12Hour && locale.hasMeridian) { + this.layout.time.font = "Vector:"+(B2 ? 50 : 60); + this.layout.ampm.font = "Vector:"+(B2 ? 20 : 40); + } else { + this.layout.time.font = "Vector:"+(B2 ? 60 : 80); + } + } else { + this.layout.time.font = "6x8:"+thickness; + } + this.layout.update(); + bar.y2 = bar.y+bar.height-1; + }, + update: function(date, c) { + "ram"; + if (c.m) this.layout.time.label = timeText(date); + if (c.h) this.layout.ampm.label = ampmText(date); + if (c.d && this.showDate) this.layout.date.label = dateText(date); + if (c.m) this.layout.render(); + if (c.s) { + barW = Math.round(date.getSeconds()/60*this.layout.bar.w); + renderBar(); + } + }, + resume: function() { + prevX = 0; // force redraw of bar + this.layout.forgetLazyState(); + }, + remove: function() { + if (this.onLock) Bangle.removeListener("lock", this.onLock); + }, + }); -// power saving: only update once a minute while locked, hide bar -if (clock.powerSave) { - Bangle.on("lock", lock => { - clock.precision = lock ? 60 : 1; - clock.tick(); - renderBar(); // hide/redraw bar right away - }); -} + // power saving: only update once a minute while locked, hide bar + if (clock.powerSave) { + clock.onLock = lock => { + clock.precision = lock ? 60 : 1; + clock.tick(); + renderBar(); // hide/redraw bar right away + } + Bangle.on("lock", clock.onLock); + } -clock.start(); + clock.start(); +} \ No newline at end of file diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json index 5b783dbda..785c228b0 100644 --- a/apps/barclock/metadata.json +++ b/apps/barclock/metadata.json @@ -1,7 +1,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.15", + "version": "0.16", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], diff --git a/apps/barometer/ChangeLog b/apps/barometer/ChangeLog index de3a5cb96..b429dda17 100644 --- a/apps/barometer/ChangeLog +++ b/apps/barometer/ChangeLog @@ -1,3 +1,4 @@ 0.01: Display pressure as number and hand 0.02: Use theme color 0.03: workaround for some firmwares that return 'undefined' for first call to barometer +0.04: Update every second, go back with short button press diff --git a/apps/barometer/app.js b/apps/barometer/app.js index 77d4c974f..7e793af4f 100644 --- a/apps/barometer/app.js +++ b/apps/barometer/app.js @@ -59,6 +59,7 @@ function drawTicks(){ function drawScaleLabels(){ g.setColor(g.theme.fg); g.setFont("Vector",12); + g.setFontAlign(-1,-1); let label = MIN; for (let i=0;i <= NUMBER_OF_LABELS; i++){ @@ -103,22 +104,29 @@ function drawIcons() { } g.setBgColor(g.theme.bg); -g.clear(); - -drawTicks(); -drawScaleLabels(); -drawIcons(); try { function baroHandler(data) { - if (data===undefined) // workaround for https://github.com/espruino/BangleApps/issues/1429 - setTimeout(() => Bangle.getPressure().then(baroHandler), 500); - else + g.clear(); + + drawTicks(); + drawScaleLabels(); + drawIcons(); + if (data!==undefined) { drawHand(Math.round(data.pressure)); + } } Bangle.getPressure().then(baroHandler); + setInterval(() => Bangle.getPressure().then(baroHandler), 1000); } catch(e) { - print(e.message); - print("barometer not supporter, show a demo value"); + if (e !== undefined) { + print(e.message); + } + print("barometer not supported, show a demo value"); drawHand(MIN); } + +Bangle.setUI({ + mode : "custom", + back : function() {load();} +}); diff --git a/apps/barometer/metadata.json b/apps/barometer/metadata.json index a385f2be2..767fa630b 100644 --- a/apps/barometer/metadata.json +++ b/apps/barometer/metadata.json @@ -1,7 +1,7 @@ { "id": "barometer", "name": "Barometer", "shortName":"Barometer", - "version":"0.03", + "version":"0.04", "description": "A simple barometer that displays the current air pressure", "icon": "barometer.png", "tags": "tool,outdoors", diff --git a/apps/barwatch/ChangeLog b/apps/barwatch/ChangeLog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/barwatch/ChangeLog @@ -0,0 +1 @@ +0.01: First version diff --git a/apps/barwatch/README.md b/apps/barwatch/README.md new file mode 100644 index 000000000..c37caa6e4 --- /dev/null +++ b/apps/barwatch/README.md @@ -0,0 +1,5 @@ +# BarWatch - an experimental watch + +For too long the watches have shown the time with digits or hands. No more! +With this stylish watch the time is represented by bars. Up to 24 as the day goes by. +Practical? Not really, but a different look! \ No newline at end of file diff --git a/apps/barwatch/app-icon.js b/apps/barwatch/app-icon.js new file mode 100644 index 000000000..82416ee28 --- /dev/null +++ b/apps/barwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY=")) \ No newline at end of file diff --git a/apps/barwatch/app.js b/apps/barwatch/app.js new file mode 100644 index 000000000..e0ed15ce6 --- /dev/null +++ b/apps/barwatch/app.js @@ -0,0 +1,76 @@ +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +function draw() { + g.reset(); + + if(g.theme.dark){ + g.setColor(1,1,1); + }else{ + g.setColor(0,0,0); + } + + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + + // hour bars + var bx_offset = 10, by_offset = 35; + var b_width = 8, b_height = 60; + var b_space = 5; + + for(var i=0; i 11){ + by_offset = 105; + } + var iter = i % 12; + //console.log(iter); + g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter), + by_offset, + bx_offset+(b_width*iter)+(b_space*iter), + by_offset+b_height); + } + + // minute bar + if(h > 11){ + by_offset = 105; + } + var m_bar = h % 12; + if(m != 0){ + g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar), + by_offset+b_height-m, + bx_offset+(b_width*m_bar)+(b_space*m_bar), + by_offset+b_height); + } + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/barwatch/app.png b/apps/barwatch/app.png new file mode 100644 index 000000000..134de9424 Binary files /dev/null and b/apps/barwatch/app.png differ diff --git a/apps/barwatch/metadata.json b/apps/barwatch/metadata.json new file mode 100644 index 000000000..adcd44107 --- /dev/null +++ b/apps/barwatch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "barwatch", + "name": "BarWatch", + "shortName":"BarWatch", + "version":"0.01", + "description": "A watch that displays the time using bars. One bar for each hour.", + "readme": "README.md", + "icon": "screenshot.png", + "tags": "clock", + "type": "clock", + "allow_emulator":true, + "screenshots" : [ { "url": "screenshot.png" } ], + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"barwatch.app.js","url":"app.js"}, + {"name":"barwatch.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/barwatch/screenshot.png b/apps/barwatch/screenshot.png new file mode 100644 index 000000000..305138252 Binary files /dev/null and b/apps/barwatch/screenshot.png differ diff --git a/apps/beer/ChangeLog b/apps/beer/ChangeLog new file mode 100644 index 000000000..21ec45242 --- /dev/null +++ b/apps/beer/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Added adjustment for Bangle.js magnetometer heading fix + Bangle.js 2 compatibility diff --git a/apps/beer/app-icon.js b/apps/beer/app-icon.js index c700b3bd2..734985cb5 100644 --- a/apps/beer/app-icon.js +++ b/apps/beer/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4")) +require("heatshrink").decompress(atob("mEw4cA///wH9/++1P+u3//3/qv/gv+KHkJkmABxcBBwNJkmQCJYOByQCCCBUCCItJkARQkgQHggLBku25IRDJQ4LCtu27Mt2RKJCInbAQIRLpYROglt24OB6wSC7dwLQ4LB9u2EgfbsARJ8u2mwRO+u3CNJtHCJFpCINALJoRCpCiGBoMSdQcpegIRGyaPB+QRDkARIyQRBc4YRKyet23iCJxHB6QRBzOJCJ+dCJY1CpfMGphrCp2YNZlL54CBEZgLBAQoRBiTFFCNMvmQRPndiEcJHEyQQECJMpAYIRQyARQwAROI4IAGB4wCBNAoRmhIRHCA4A/AAo")) diff --git a/apps/beer/custom.html b/apps/beer/custom.html index a357ab378..f0895f93f 100644 --- a/apps/beer/custom.html +++ b/apps/beer/custom.html @@ -127,6 +127,8 @@ var img_nofix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF8u02gziGBoyhQ5gwDGRozRGCQydGCgybGCwyZC5gAaGPQwnGRAwpGQ4xwGFYyFDKsrlYxYDCsBmUyg4yXLyUsFwMyq1WAgUsNCRjUmVXroAEq8yMbcllkskwCEkplDmQwDq0sC54xEHQ9RqQAGqIwCFgOBAASYBSgMBltRAA0sgJsOGJeBxAAGwMrgIXIloxOJYNSvl8CwIDCqMBlYxNC4wxQDIOCwVYDIIDBGJ9YwV8rADBwRJCSqAVCAYaVMC4oxCPYYxQSo4xMSpIxPY4T5HY54XIMbIxKgwXKfKjhEllWGJNWlgXJGLNXruCGI+CrtXGKP+GJB9HMZ6VO/wxJcI8lfJclfKAxKfJEAGJIXLGKSvBWYQZCMZbfEqTHBGJYyFfIo1DGJ4tDGJQwCGJB9IMZyVNGIYyEfJQxPfJgwEMgoZJgAxMltRAA0tGJQyEksslkmAQklGINXxDTBFwIDCq8rC4YACC4gwJMowAJldWAAwwBABowIGJ4AYGJIymGBQylGBgyjGBwyhGCAzeF6YycGCwzYF7IzVF7o1PDqYA==")); var img_fix = require("heatshrink").decompress(atob("mUyxH+ACYhJDygtYGsqLVF94zaDYkq6wAOlQyYJo2A63VAAIoC2m0GI16My5/H5/V64ABGQIwBGQ+rTKwWHkhiBGIYwDGQ3VZioVIqoiBGAJhEGRFPGSYTIYwQxCGA4yFqodJGKeqSgQwJGQmkGKQSJfAYwLGQfPDxQwRgHVfAi/EAA4xLGQwRLYwb5BABoxQCBcA43G5wABAgIAMEBgxQ0QxB54xB5gAG4xgBBYOiGJ4PMGInPGIhcCGIt4EJoxPvHM5oxBGAnO6xrCGoXMqgxdpwxD5qQFL4QADlQxdgAhBGILIDMYoADEBwwPgCHBfQzHDAAb4NACTIIAA74OACLIIMo7GOACQoBZAoHBHQPNA4QwggGiZBA5B54HBY0DIKMYtUGMMqFYLIGY4jGhZAr6FAAYwiZAgxIY0TIFfQgADvAfR/zISGJTGR/wxRkj6CGJBiSGKL6DGP4xOGSKVDGAwxRGAQxU5oxcGR75DGJEkGCYxPlXM5vPGA/MlQxUGR1OGIL4I5lOGCgyOqgxBShHMqgwVGJt4GJd4GKwyMvHG5vGABAxMGBQyM1mtABWsGC4yLGBYABGDAyKGKwwQGZKVUF6b/OABowWGbAvZGaovdGp4dTA")); +var W = g.getWidth(), H = g.getHeight(); + // https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js function project(latlong) { var d = Math.PI / 180, @@ -170,32 +172,30 @@ Bangle.on('GPS', function(f) { Bangle.on('mag', function(m) { if (!Bangle.isLCDOn()) return; - var headingrad = m.heading*Math.PI/180; // in radians + var headingrad = (360-m.heading)*Math.PI/180; // in radians if (!isFinite(headingrad)) headingrad=0; if (nearest) - g.drawImage(img_fix,120,120,{ + g.drawImage(img_fix,W/2,H/2,{ rotate: (Math.PI/2)+headingrad-nearestangle, scale:3, }); else - g.drawImage(img_nofix,120,120,{ + g.drawImage(img_nofix,W/2,H/2,{ rotate: headingrad, scale:2, }); - g.clearRect(60,0,180,24); - g.setFontAlign(0,0); - g.setFont("6x8"); + g.clearRect(0,0,W,24).setFontAlign(0,0).setFont("6x8"); if (fix.fix) { - g.drawString(nearest ? nearest.name : "---",120,4); + g.drawString(nearest ? nearest.name : "---",W/2,4); g.setFont("6x8",2); - g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",120,16); + g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",W/2,16); } else { - g.drawString(fix.satellites+" satellites",120,4); + g.drawString(fix.satellites+" satellites",W/2,4); } }); Bangle.setCompassPower(1); Bangle.setGPSPower(1); -g.clear();`; +g.setColor("#fff").setBgColor("#000").clear();`; sendCustomizedApp({ storage:[ diff --git a/apps/beer/metadata.json b/apps/beer/metadata.json index cf69aee90..3a2421bd1 100644 --- a/apps/beer/metadata.json +++ b/apps/beer/metadata.json @@ -1,11 +1,11 @@ { "id": "beer", "name": "Beer Compass", - "version": "0.01", + "version": "0.02", "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one", "icon": "app.png", "tags": "", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", "storage": [ {"name":"beer.app.js"}, diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 16252251c..0eaf517d9 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -53,3 +53,11 @@ 0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed) 0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks) 0.49: Store first found clock as a setting to speed up further boots +0.50: Allow setting of screen rotation + Remove support for 2v11 and earlier firmware +0.51: Remove patches for 2v10 firmware (BEEPSET and setUI) + Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware + Ensure clock is only fast-loaded if it doesn't contain widgets +0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass +0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock +0.54: Fix for invalid version comparison in polyfill diff --git a/apps/boot/bootloader.js b/apps/boot/bootloader.js index 1e0d22645..6e6466f48 100644 --- a/apps/boot/bootloader.js +++ b/apps/boot/bootloader.js @@ -1,5 +1,10 @@ // This runs after a 'fresh' boot var s = require("Storage").readJSON("setting.json",1)||{}; +/* If were being called from JS code in order to load the clock quickly (eg from a launcher) +and the clock in question doesn't have widgets, force a normal 'load' as this will then +reset everything and remove the widgets. */ +if (global.__FILE__ && !s.clockHasWidgets) {load();throw "Clock has no widgets, can't fast load";} +// Otherwise continue to try and load the clock var _clkApp = require("Storage").read(s.clock); if (!_clkApp) { _clkApp = require("Storage").list(/\.info$/) @@ -13,7 +18,8 @@ if (!_clkApp) { .sort((a, b) => a.sortorder - b.sortorder)[0]; if (_clkApp){ s.clock = _clkApp.src; - _clkApp = require("Storage").read(_clkApp.src); + _clkApp = require("Storage").read(_clkApp.src); + s.clockHasWidgets = _clkApp.includes("Bangle.loadWidgets"); require("Storage").writeJSON("setting.json", s); } } diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 4cb3c52e4..ad58437ec 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -4,6 +4,7 @@ of the time. */ E.showMessage(/*LANG*/"Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 +var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1")); var boot = "", bootPost = ""; if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); @@ -62,23 +63,6 @@ if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep(); if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`; if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`; boot += `E.setTimeZone(${s.timezone});`; -// Set vibrate, beep, etc IF on older firmwares -if (!Bangle.F_BEEPSET) { - if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n` - if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n` - else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) { - return new Promise(function(resolve) { - if ((0|freq)<=0) freq=4000; - if ((0|time)<=0) time=200; - if (time>5000) time=5000; - analogWrite(D13,0.1,{freq:freq}); - setTimeout(function() { - digitalWrite(D13,0); - resolve(); - }, time); - }); - };\n`; -} // Draw out of memory errors onto the screen boot += `E.on('errorFlag', function(errorFlags) { g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip(); @@ -92,76 +76,20 @@ 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) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; -// Pre-2v10 firmwares without a theme/setUI -delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.theme) { - boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`; -} -try { - Bangle.setUI({}); // In 2v12.xx we added the option for mode to be an object - for 2v12 and earlier, add a fix if it fails with an object supplied -} catch(e) { - boot += `Bangle._setUI = Bangle.setUI; -Bangle.setUI=function(mode, cb) { - if (Bangle.uiRemove) { - Bangle.uiRemove(); - delete Bangle.uiRemove; - } - if ("object"==typeof mode) { - // TODO: handle mode.back? - mode = mode.mode; - } - Bangle._setUI(mode, cb); -};\n`; -} -delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill - boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);bm||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fg).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), -k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.reset().clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`; -} -delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill - boot += `Graphics.prototype.imageMetrics=function(src) { - if (src[0]) return {width:src[0],height:src[1]}; - else if ('object'==typeof src) return { - width:("width" in src) ? src.width : src.getWidth(), - height:("height" in src) ? src.height : src.getHeight()}; - var im = E.toString(src); - return {width:im.charCodeAt(0), height:im.charCodeAt(1)}; -};\n`; -} -delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill - boot += `Graphics.prototype.stringMetrics=function(txt) { - txt = txt.toString().split("\\n"); - return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; -};\n`; -} -delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill - boot += `Graphics.prototype.wrapString=function(str, maxWidth) { - var lines = []; - for (var unwrappedLine of str.split("\\n")) { - var words = unwrappedLine.split(" "); - var line = words.shift(); - for (var word of words) { - if (g.stringWidth(line + " " + word) > maxWidth) { - lines.push(line); - line = word; - } else { - line += " " + word; - } - } - lines.push(line); - } - return lines; -};\n`; -} -delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted -if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares - boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); - (lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\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. + boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;}); +Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`; +// deleting stops us getting confused by our own decl. builtins can't be deleted +// this is a polyfill without fastloading capability +delete Bangle.showClock; +if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`; +delete Bangle.load; +if (!Bangle.load) boot += `Bangle.load = load;\n`; + +// ================================================== BOOT.JS // Append *.boot.js files // These could change bleServices/bleServiceOptions if needed var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index 31e211f7c..bd39beb7f 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.49", + "version": "0.54", "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/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 57f0ecf3d..99cf0c670 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -29,3 +29,14 @@ Use default boolean formatter in custom menu and directly apply config if useful Allow recording unmodified internal HR Better connection retry handling +0.13: Less time used during boot if disabled +0.14: Allow bonding (Debug menu) + Prevent mixing of BT and internal HRM events if both are enabled + Always use a grace period (default 0 ms) to decouple some connection steps + Device not found errors now utilize increasing timeouts +0.15: Fix recording internal sensor + Handle fallback to internal sensor consistently if BT bpm is 0 + Power internal sensor down if not needed for fallback +0.16: Set powerdownRequested correctly on BTHRM power on + Additional logging on errors + Add debug option for disabling active scanning diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index aa97d83b7..3e3d35737 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -1,633 +1 @@ -(function() { - var settings = Object.assign( - require('Storage').readJSON("bthrm.default.json", true) || {}, - require('Storage').readJSON("bthrm.json", true) || {} - ); - - var log = function(text, param){ - if (global.showStatusInfo) - showStatusInfo(text); - if (settings.debuglog){ - var logline = new Date().toISOString() + " - " + text; - if (param) logline += ": " + JSON.stringify(param); - print(logline); - } - }; - - log("Settings: ", settings); - - if (settings.enabled){ - - var clearCache = function() { - return require('Storage').erase("bthrm.cache.json"); - }; - - var getCache = function() { - var cache = require('Storage').readJSON("bthrm.cache.json", true) || {}; - if (settings.btid && settings.btid === cache.id) return cache; - clearCache(); - return {}; - }; - - var addNotificationHandler = function(characteristic) { - log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/); - characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value)); - }; - - var writeCache = function(cache) { - var oldCache = getCache(); - if (oldCache !== cache) { - log("Writing cache"); - require('Storage').writeJSON("bthrm.cache.json", cache); - } else { - log("No changes, don't write cache"); - } - }; - - var characteristicsToCache = function(characteristics) { - log("Cache characteristics"); - var cache = getCache(); - if (!cache.characteristics) cache.characteristics = {}; - for (var c of characteristics){ - //"handle_value":16,"handle_decl":15 - log("Saving handle " + c.handle_value + " for characteristic: ", c); - cache.characteristics[c.uuid] = { - "handle": c.handle_value, - "uuid": c.uuid, - "notify": c.properties.notify, - "read": c.properties.read - }; - } - writeCache(cache); - }; - - var characteristicsFromCache = function(device) { - var service = { device : device }; // fake a BluetoothRemoteGATTService - log("Read cached characteristics"); - var cache = getCache(); - if (!cache.characteristics) return []; - var restored = []; - for (var c in cache.characteristics){ - var cached = cache.characteristics[c]; - var r = new BluetoothRemoteGATTCharacteristic(); - log("Restoring characteristic ", cached); - r.handle_value = cached.handle; - r.uuid = cached.uuid; - r.properties = {}; - r.properties.notify = cached.notify; - r.properties.read = cached.read; - r.service = service; - addNotificationHandler(r); - log("Restored characteristic: ", r); - restored.push(r); - } - return restored; - }; - - log("Start"); - - var lastReceivedData={ - }; - - var supportedServices = [ - "0x180d", // Heart Rate - "0x180f", // Battery - ]; - - var bpmTimeout; - - var supportedCharacteristics = { - "0x2a37": { - //Heart rate measurement - active: false, - handler: function (dv){ - var flags = dv.getUint8(0); - - var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit - supportedCharacteristics["0x2a37"].active = bpm > 0; - log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active); - if (supportedCharacteristics["0x2a37"].active) stopFallback(); - if (bpmTimeout) clearTimeout(bpmTimeout); - bpmTimeout = setTimeout(()=>{ - supportedCharacteristics["0x2a37"].active = false; - startFallback(); - }, 3000); - - var sensorContact; - - if (flags & 2){ - sensorContact = !!(flags & 4); - } - - var idx = 2 + (flags&1); - - var energyExpended; - if (flags & 8){ - energyExpended = dv.getUint16(idx,1); - idx += 2; - } - var interval; - if (flags & 16) { - interval = []; - var maxIntervalBytes = (dv.byteLength - idx); - log("Found " + (maxIntervalBytes / 2) + " rr data fields"); - for(var i = 0 ; i < maxIntervalBytes / 2; i++){ - interval[i] = dv.getUint16(idx,1); // in milliseconds - idx += 2; - } - } - - var location; - if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){ - location = lastReceivedData["0x180d"]["0x2a38"]; - } - - var battery; - if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){ - battery = lastReceivedData["0x180f"]["0x2a19"]; - } - - if (settings.replace){ - var repEvent = { - bpm: bpm, - confidence: (sensorContact || sensorContact === undefined)? 100 : 0, - src: "bthrm" - }; - - log("Emitting HRM", repEvent); - Bangle.emit("HRM_int", repEvent); - } - - var newEvent = { - bpm: bpm - }; - - if (location) newEvent.location = location; - if (interval) newEvent.rr = interval; - if (energyExpended) newEvent.energy = energyExpended; - if (battery) newEvent.battery = battery; - if (sensorContact) newEvent.contact = sensorContact; - - log("Emitting BTHRM", newEvent); - Bangle.emit("BTHRM", newEvent); - } - }, - "0x2a38": { - //Body sensor location - handler: function(dv){ - if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {}; - lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10); - } - }, - "0x2a19": { - //Battery - handler: function (dv){ - if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {}; - lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0); - } - } - }; - - var device; - var gatt; - var characteristics = []; - var blockInit = false; - var currentRetryTimeout; - var initialRetryTime = 40; - var maxRetryTime = 60000; - var retryTime = initialRetryTime; - - var connectSettings = { - minInterval: 7.5, - maxInterval: 1500 - }; - - var waitingPromise = function(timeout) { - return new Promise(function(resolve){ - log("Start waiting for " + timeout); - setTimeout(()=>{ - log("Done waiting for " + timeout); - resolve(); - }, timeout); - }); - }; - - if (settings.enabled){ - Bangle.isBTHRMActive = function (){ - return supportedCharacteristics["0x2a37"].active; - }; - - Bangle.isBTHRMOn = function(){ - return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0); - }; - - Bangle.isBTHRMConnected = function(){ - return gatt && gatt.connected; - }; - } - - if (settings.replace){ - Bangle.origIsHRMOn = Bangle.isHRMOn; - - Bangle.isHRMOn = function() { - if (settings.enabled && !settings.replace){ - return Bangle.origIsHRMOn(); - } else if (settings.enabled && settings.replace){ - return Bangle.isBTHRMOn(); - } - return Bangle.origIsHRMOn() || Bangle.isBTHRMOn(); - }; - } - - var clearRetryTimeout = function(resetTime) { - if (currentRetryTimeout){ - log("Clearing timeout " + currentRetryTimeout); - clearTimeout(currentRetryTimeout); - currentRetryTimeout = undefined; - } - if (resetTime) { - log("Resetting retry time"); - retryTime = initialRetryTime; - } - }; - - var retry = function() { - log("Retry"); - - if (!currentRetryTimeout){ - - var clampedTime = retryTime < 100 ? 100 : retryTime; - - log("Set timeout for retry as " + clampedTime); - clearRetryTimeout(); - currentRetryTimeout = setTimeout(() => { - log("Retrying"); - currentRetryTimeout = undefined; - initBt(); - }, clampedTime); - - retryTime = Math.pow(clampedTime, 1.1); - if (retryTime > maxRetryTime){ - retryTime = maxRetryTime; - } - } else { - log("Already in retry..."); - } - }; - - var buzzing = false; - var onDisconnect = function(reason) { - log("Disconnect: " + reason); - log("GATT", gatt); - log("Characteristics", characteristics); - clearRetryTimeout(reason != "Connection Timeout"); - supportedCharacteristics["0x2a37"].active = false; - startFallback(); - blockInit = false; - if (settings.warnDisconnect && !buzzing){ - buzzing = true; - Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;}); - } - if (Bangle.isBTHRMOn()){ - retry(); - } - }; - - var createCharacteristicPromise = function(newCharacteristic) { - log("Create characteristic promise", newCharacteristic); - var result = Promise.resolve(); - // For values that can be read, go ahead and read them, even if we might be notified in the future - // Allows for getting initial state of infrequently updating characteristics, like battery - if (newCharacteristic.readValue){ - result = result.then(()=>{ - log("Reading data", newCharacteristic); - return newCharacteristic.readValue().then((data)=>{ - if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) { - supportedCharacteristics[newCharacteristic.uuid].handler(data); - } - }); - }); - } - if (newCharacteristic.properties.notify){ - result = result.then(()=>{ - log("Starting notifications", newCharacteristic); - var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic)); - if (settings.gracePeriodNotification > 0){ - log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); - startPromise = startPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodNotification); - }); - } - return startPromise; - }); - } - return result.then(()=>log("Handled characteristic", newCharacteristic)); - }; - - var attachCharacteristicPromise = function(promise, characteristic) { - return promise.then(()=>{ - log("Handling characteristic:", characteristic); - return createCharacteristicPromise(characteristic); - }); - }; - - var createCharacteristicsPromise = function(newCharacteristics) { - log("Create characteristics promis ", newCharacteristics); - var result = Promise.resolve(); - for (var c of newCharacteristics){ - if (!supportedCharacteristics[c.uuid]) continue; - log("Supporting characteristic", c); - characteristics.push(c); - if (c.properties.notify){ - addNotificationHandler(c); - } - - result = attachCharacteristicPromise(result, c); - } - return result.then(()=>log("Handled characteristics")); - }; - - var createServicePromise = function(service) { - log("Create service promise", service); - var result = Promise.resolve(); - result = result.then(()=>{ - log("Handling service" + service.uuid); - return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c)); - }); - return result.then(()=>log("Handled service" + service.uuid)); - }; - - var attachServicePromise = function(promise, service) { - return promise.then(()=>createServicePromise(service)); - }; - - var initBt = function () { - log("initBt with blockInit: " + blockInit); - if (blockInit){ - retry(); - return; - } - - blockInit = true; - - var promise; - var filters; - - if (!device){ - if (settings.btid){ - log("Configured device id", settings.btid); - filters = [{ id: settings.btid }]; - } else { - return; - } - log("Requesting device with filters", filters); - promise = NRF.requestDevice({ filters: filters, active: true }); - - if (settings.gracePeriodRequest){ - log("Add " + settings.gracePeriodRequest + "ms grace period after request"); - } - - promise = promise.then((d)=>{ - log("Got device", d); - d.on('gattserverdisconnected', onDisconnect); - device = d; - }); - - promise = promise.then(()=>{ - log("Wait after request"); - return waitingPromise(settings.gracePeriodRequest); - }); - } else { - promise = Promise.resolve(); - log("Reuse device", device); - } - - promise = promise.then(()=>{ - if (gatt){ - log("Reuse GATT", gatt); - } else { - log("GATT is new", gatt); - characteristics = []; - var cachedId = getCache().id; - if (device.id !== cachedId){ - log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache"); - clearCache(); - } - var newCache = getCache(); - newCache.id = device.id; - writeCache(newCache); - gatt = device.gatt; - } - - return Promise.resolve(gatt); - }); - - promise = promise.then((gatt)=>{ - if (!gatt.connected){ - log("Connecting..."); - var connectPromise = gatt.connect(connectSettings).then(function() { - log("Connected."); - }); - if (settings.gracePeriodConnect > 0){ - log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); - connectPromise = connectPromise.then(()=>{ - log("Wait after connect"); - return waitingPromise(settings.gracePeriodConnect); - }); - } - return connectPromise; - } else { - return Promise.resolve(); - } - }); - -/* promise = promise.then(() => { - log(JSON.stringify(gatt.getSecurityStatus())); - if (gatt.getSecurityStatus()['bonded']) { - log("Already bonded"); - return Promise.resolve(); - } else { - log("Start bonding"); - return gatt.startBonding() - .then(() => console.log(gatt.getSecurityStatus())); - } - });*/ - - promise = promise.then(()=>{ - if (!characteristics || characteristics.length === 0){ - characteristics = characteristicsFromCache(device); - } - }); - - promise = promise.then(()=>{ - var characteristicsPromise = Promise.resolve(); - if (characteristics.length === 0){ - characteristicsPromise = characteristicsPromise.then(()=>{ - log("Getting services"); - return gatt.getPrimaryServices(); - }); - - characteristicsPromise = characteristicsPromise.then((services)=>{ - log("Got services", services); - var result = Promise.resolve(); - for (var service of services){ - if (!(supportedServices.includes(service.uuid))) continue; - log("Supporting service", service.uuid); - result = attachServicePromise(result, service); - } - if (settings.gracePeriodService > 0) { - log("Add " + settings.gracePeriodService + "ms grace period after services"); - result = result.then(()=>{ - log("Wait after services"); - return waitingPromise(settings.gracePeriodService); - }); - } - return result; - }); - } else { - for (var characteristic of characteristics){ - characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true); - } - } - - return characteristicsPromise; - }); - - return promise.then(()=>{ - log("Connection established, waiting for notifications"); - characteristicsToCache(characteristics); - clearRetryTimeout(true); - }).catch((e) => { - characteristics = []; - log("Error:", e); - onDisconnect(e); - }); - }; - - Bangle.setBTHRMPower = function(isOn, app) { - // Do app power handling - if (!app) app="?"; - if (Bangle._PWR===undefined) Bangle._PWR={}; - if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; - if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); - if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app); - isOn = Bangle._PWR.BTHRM.length; - // so now we know if we're really on - if (isOn) { - switchFallback(); - if (!Bangle.isBTHRMConnected()) initBt(); - } else { // not on - log("Power off for " + app); - clearRetryTimeout(true); - if (gatt) { - if (gatt.connected){ - log("Disconnect with gatt", gatt); - try{ - gatt.disconnect().then(()=>{ - log("Successful disconnect"); - }).catch((e)=>{ - log("Error during disconnect promise", e); - }); - } catch (e){ - log("Error during disconnect attempt", e); - } - } - } - } - }; - - if (settings.replace){ - Bangle.on("HRM", (e) => { - e.modified = true; - Bangle.emit("HRM_int", e); - }); - - Bangle.origOn = Bangle.on; - Bangle.on = function(name, callback) { - if (name == "HRM") { - Bangle.origOn("HRM_int", callback); - } else { - Bangle.origOn(name, callback); - } - }; - - Bangle.origRemoveListener = Bangle.removeListener; - Bangle.removeListener = function(name, callback) { - if (name == "HRM") { - Bangle.origRemoveListener("HRM_int", callback); - } else { - Bangle.origRemoveListener(name, callback); - } - }; - - } - - Bangle.origSetHRMPower = Bangle.setHRMPower; - - if (settings.startWithHrm){ - - Bangle.setHRMPower = function(isOn, app) { - log("setHRMPower for " + app + ": " + (isOn?"on":"off")); - if (settings.enabled){ - Bangle.setBTHRMPower(isOn, app); - } - if ((settings.enabled && !settings.replace) || !settings.enabled){ - Bangle.origSetHRMPower(isOn, app); - } - }; - } - - var fallbackActive = false; - var inSwitch = false; - - var stopFallback = function(){ - if (fallbackActive){ - Bangle.origSetHRMPower(0, "bthrm_fallback"); - fallbackActive = false; - log("Fallback to HRM disabled"); - } - }; - - var startFallback = function(){ - if (!fallbackActive && settings.allowFallback) { - fallbackActive = true; - Bangle.origSetHRMPower(1, "bthrm_fallback"); - log("Fallback to HRM enabled"); - } - }; - - var switchFallback = function() { - log("Check falling back to HRM"); - if (!inSwitch){ - inSwitch = true; - if (Bangle.isBTHRMActive()){ - stopFallback(); - } else { - startFallback(); - } - } - inSwitch = false; - }; - - if (settings.replace){ - log("Replace HRM event"); - if (Bangle._PWR && Bangle._PWR.HRM){ - for (var i = 0; i < Bangle._PWR.HRM.length; i++){ - var app = Bangle._PWR.HRM[i]; - log("Moving app " + app); - Bangle.origSetHRMPower(0, app); - Bangle.setBTHRMPower(1, app); - if (Bangle._PWR.HRM===undefined) break; - } - } - } - - E.on("kill", ()=>{ - if (gatt && gatt.connected){ - log("Got killed, trying to disconnect"); - gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e)); - } - }); - } -})(); +if ((require('Storage').readJSON("bthrm.json", true) || {}).enabled != false) require("bthrm").enable(); diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json index fb284bcd2..79605b412 100644 --- a/apps/bthrm/default.json +++ b/apps/bthrm/default.json @@ -16,5 +16,7 @@ "gracePeriodNotification": 0, "gracePeriodConnect": 0, "gracePeriodService": 0, - "gracePeriodRequest": 0 + "gracePeriodRequest": 0, + "bonding": false, + "active": true } diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js new file mode 100644 index 000000000..f5e0e1e5b --- /dev/null +++ b/apps/bthrm/lib.js @@ -0,0 +1,656 @@ +exports.enable = () => { + var settings = Object.assign( + require('Storage').readJSON("bthrm.default.json", true) || {}, + require('Storage').readJSON("bthrm.json", true) || {} + ); + + var log = function(text, param){ + if (global.showStatusInfo) + showStatusInfo(text); + if (settings.debuglog){ + var logline = new Date().toISOString() + " - " + text; + if (param) logline += ": " + JSON.stringify(param); + print(logline); + } + }; + + log("Settings: ", settings); + + if (settings.enabled){ + + var clearCache = function() { + return require('Storage').erase("bthrm.cache.json"); + }; + + var getCache = function() { + var cache = require('Storage').readJSON("bthrm.cache.json", true) || {}; + if (settings.btid && settings.btid === cache.id) return cache; + clearCache(); + return {}; + }; + + var addNotificationHandler = function(characteristic) { + log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/); + characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value)); + }; + + var writeCache = function(cache) { + var oldCache = getCache(); + if (oldCache !== cache) { + log("Writing cache"); + require('Storage').writeJSON("bthrm.cache.json", cache); + } else { + log("No changes, don't write cache"); + } + }; + + var characteristicsToCache = function(characteristics) { + log("Cache characteristics"); + var cache = getCache(); + if (!cache.characteristics) cache.characteristics = {}; + for (var c of characteristics){ + //"handle_value":16,"handle_decl":15 + log("Saving handle " + c.handle_value + " for characteristic: ", c); + cache.characteristics[c.uuid] = { + "handle": c.handle_value, + "uuid": c.uuid, + "notify": c.properties.notify, + "read": c.properties.read + }; + } + writeCache(cache); + }; + + var characteristicsFromCache = function(device) { + var service = { device : device }; // fake a BluetoothRemoteGATTService + log("Read cached characteristics"); + var cache = getCache(); + if (!cache.characteristics) return []; + var restored = []; + for (var c in cache.characteristics){ + var cached = cache.characteristics[c]; + var r = new BluetoothRemoteGATTCharacteristic(); + log("Restoring characteristic ", cached); + r.handle_value = cached.handle; + r.uuid = cached.uuid; + r.properties = {}; + r.properties.notify = cached.notify; + r.properties.read = cached.read; + r.service = service; + addNotificationHandler(r); + log("Restored characteristic: ", r); + restored.push(r); + } + return restored; + }; + + log("Start"); + + var lastReceivedData={ + }; + + var supportedServices = [ + "0x180d", // Heart Rate + "0x180f", // Battery + ]; + + var bpmTimeout; + + var supportedCharacteristics = { + "0x2a37": { + //Heart rate measurement + active: false, + handler: function (dv){ + var flags = dv.getUint8(0); + + var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit + supportedCharacteristics["0x2a37"].active = bpm > 0; + log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active); + switchFallback(); + if (bpmTimeout) clearTimeout(bpmTimeout); + bpmTimeout = setTimeout(()=>{ + bpmTimeout = undefined; + supportedCharacteristics["0x2a37"].active = false; + startFallback(); + }, 3000); + + var sensorContact; + + if (flags & 2){ + sensorContact = !!(flags & 4); + } + + var idx = 2 + (flags&1); + + var energyExpended; + if (flags & 8){ + energyExpended = dv.getUint16(idx,1); + idx += 2; + } + var interval; + if (flags & 16) { + interval = []; + var maxIntervalBytes = (dv.byteLength - idx); + log("Found " + (maxIntervalBytes / 2) + " rr data fields"); + for(var i = 0 ; i < maxIntervalBytes / 2; i++){ + interval[i] = dv.getUint16(idx,1); // in milliseconds + idx += 2; + } + } + + var location; + if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){ + location = lastReceivedData["0x180d"]["0x2a38"]; + } + + var battery; + if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){ + battery = lastReceivedData["0x180f"]["0x2a19"]; + } + + if (settings.replace && bpm > 0){ + var repEvent = { + bpm: bpm, + confidence: (sensorContact || sensorContact === undefined)? 100 : 0, + src: "bthrm" + }; + + log("Emitting HRM_R(bt)", repEvent); + Bangle.emit("HRM_R", repEvent); + } + + var newEvent = { + bpm: bpm + }; + + if (location) newEvent.location = location; + if (interval) newEvent.rr = interval; + if (energyExpended) newEvent.energy = energyExpended; + if (battery) newEvent.battery = battery; + if (sensorContact) newEvent.contact = sensorContact; + + log("Emitting BTHRM", newEvent); + Bangle.emit("BTHRM", newEvent); + } + }, + "0x2a38": { + //Body sensor location + handler: function(dv){ + if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {}; + lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10); + } + }, + "0x2a19": { + //Battery + handler: function (dv){ + if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {}; + lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0); + } + } + }; + + var device; + var gatt; + var characteristics = []; + var blockInit = false; + var currentRetryTimeout; + var initialRetryTime = 40; + var maxRetryTime = 60000; + var retryTime = initialRetryTime; + + var connectSettings = { + minInterval: 7.5, + maxInterval: 1500 + }; + + var waitingPromise = function(timeout) { + return new Promise(function(resolve){ + log("Start waiting for " + timeout); + setTimeout(()=>{ + log("Done waiting for " + timeout); + resolve(); + }, timeout); + }); + }; + + if (settings.enabled){ + Bangle.isBTHRMActive = function (){ + return supportedCharacteristics["0x2a37"].active; + }; + + Bangle.isBTHRMOn = function(){ + return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0); + }; + + Bangle.isBTHRMConnected = function(){ + return gatt && gatt.connected; + }; + } + + if (settings.replace){ + Bangle.origIsHRMOn = Bangle.isHRMOn; + + Bangle.isHRMOn = function() { + if (settings.enabled && !settings.replace){ + return Bangle.origIsHRMOn(); + } else if (settings.enabled && settings.replace){ + return Bangle.isBTHRMOn(); + } + return Bangle.origIsHRMOn() || Bangle.isBTHRMOn(); + }; + } + + var clearRetryTimeout = function(resetTime) { + if (currentRetryTimeout){ + log("Clearing timeout " + currentRetryTimeout); + clearTimeout(currentRetryTimeout); + currentRetryTimeout = undefined; + } + if (resetTime) { + log("Resetting retry time"); + retryTime = initialRetryTime; + } + }; + + var retry = function() { + log("Retry"); + + if (!currentRetryTimeout && !powerdownRequested){ + + var clampedTime = retryTime < 100 ? 100 : retryTime; + + log("Set timeout for retry as " + clampedTime); + clearRetryTimeout(); + currentRetryTimeout = setTimeout(() => { + log("Retrying"); + currentRetryTimeout = undefined; + initBt(); + }, clampedTime); + + retryTime = Math.pow(clampedTime, 1.1); + if (retryTime > maxRetryTime){ + retryTime = maxRetryTime; + } + } else { + log("Already in retry..."); + } + }; + + var buzzing = false; + var onDisconnect = function(reason) { + log("Disconnect: " + reason); + log("GATT", gatt); + log("Characteristics", characteristics); + + var retryTimeResetNeeded = true; + retryTimeResetNeeded &= reason != "Connection Timeout"; + retryTimeResetNeeded &= reason != "No device found matching filters"; + clearRetryTimeout(retryTimeResetNeeded); + supportedCharacteristics["0x2a37"].active = false; + if (!powerdownRequested) startFallback(); + blockInit = false; + if (settings.warnDisconnect && !buzzing){ + buzzing = true; + Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;}); + } + if (Bangle.isBTHRMOn()){ + retry(); + } + }; + + var createCharacteristicPromise = function(newCharacteristic) { + log("Create characteristic promise", newCharacteristic); + var result = Promise.resolve(); + // For values that can be read, go ahead and read them, even if we might be notified in the future + // Allows for getting initial state of infrequently updating characteristics, like battery + if (newCharacteristic.readValue){ + result = result.then(()=>{ + log("Reading data", newCharacteristic); + return newCharacteristic.readValue().then((data)=>{ + if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) { + supportedCharacteristics[newCharacteristic.uuid].handler(data); + } + }); + }); + } + if (newCharacteristic.properties.notify){ + result = result.then(()=>{ + log("Starting notifications", newCharacteristic); + var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic)); + + log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications"); + startPromise = startPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodNotification); + }); + + return startPromise; + }); + } + return result.then(()=>log("Handled characteristic", newCharacteristic)); + }; + + var attachCharacteristicPromise = function(promise, characteristic) { + return promise.then(()=>{ + log("Handling characteristic:", characteristic); + return createCharacteristicPromise(characteristic); + }); + }; + + var createCharacteristicsPromise = function(newCharacteristics) { + log("Create characteristics promis ", newCharacteristics); + var result = Promise.resolve(); + for (var c of newCharacteristics){ + if (!supportedCharacteristics[c.uuid]) continue; + log("Supporting characteristic", c); + characteristics.push(c); + if (c.properties.notify){ + addNotificationHandler(c); + } + + result = attachCharacteristicPromise(result, c); + } + return result.then(()=>log("Handled characteristics")); + }; + + var createServicePromise = function(service) { + log("Create service promise", service); + var result = Promise.resolve(); + result = result.then(()=>{ + log("Handling service" + service.uuid); + return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c)); + }); + return result.then(()=>log("Handled service" + service.uuid)); + }; + + var attachServicePromise = function(promise, service) { + return promise.then(()=>createServicePromise(service)); + }; + + var initBt = function () { + log("initBt with blockInit: " + blockInit); + if (blockInit && !powerdownRequested){ + retry(); + return; + } + + blockInit = true; + + var promise; + var filters; + + if (!device){ + if (settings.btid){ + log("Configured device id", settings.btid); + filters = [{ id: settings.btid }]; + } else { + return; + } + log("Requesting device with filters", filters); + try { + promise = NRF.requestDevice({ filters: filters, active: settings.active }); + } catch (e){ + log("Error during initial request:", e); + onDisconnect(e); + return; + } + + if (settings.gracePeriodRequest){ + log("Add " + settings.gracePeriodRequest + "ms grace period after request"); + } + + promise = promise.then((d)=>{ + log("Got device", d); + d.on('gattserverdisconnected', onDisconnect); + device = d; + }); + + promise = promise.then(()=>{ + log("Wait after request"); + return waitingPromise(settings.gracePeriodRequest); + }); + } else { + promise = Promise.resolve(); + log("Reuse device", device); + } + + promise = promise.then(()=>{ + if (gatt){ + log("Reuse GATT", gatt); + } else { + log("GATT is new", gatt); + characteristics = []; + var cachedId = getCache().id; + if (device.id !== cachedId){ + log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache"); + clearCache(); + } + var newCache = getCache(); + newCache.id = device.id; + writeCache(newCache); + gatt = device.gatt; + } + + return Promise.resolve(gatt); + }); + + promise = promise.then((gatt)=>{ + if (!gatt.connected){ + log("Connecting..."); + var connectPromise = gatt.connect(connectSettings).then(function() { + log("Connected."); + }); + log("Add " + settings.gracePeriodConnect + "ms grace period after connecting"); + connectPromise = connectPromise.then(()=>{ + log("Wait after connect"); + return waitingPromise(settings.gracePeriodConnect); + }); + return connectPromise; + } else { + return Promise.resolve(); + } + }); + + if (settings.bonding){ + promise = promise.then(() => { + log(JSON.stringify(gatt.getSecurityStatus())); + if (gatt.getSecurityStatus()['bonded']) { + log("Already bonded"); + return Promise.resolve(); + } else { + log("Start bonding"); + return gatt.startBonding() + .then(() => log("Security status" + gatt.getSecurityStatus())); + } + }); + } + + promise = promise.then(()=>{ + if (!characteristics || characteristics.length === 0){ + characteristics = characteristicsFromCache(device); + } + }); + + promise = promise.then(()=>{ + var characteristicsPromise = Promise.resolve(); + if (characteristics.length === 0){ + characteristicsPromise = characteristicsPromise.then(()=>{ + log("Getting services"); + return gatt.getPrimaryServices(); + }); + + characteristicsPromise = characteristicsPromise.then((services)=>{ + log("Got services", services); + var result = Promise.resolve(); + for (var service of services){ + if (!(supportedServices.includes(service.uuid))) continue; + log("Supporting service", service.uuid); + result = attachServicePromise(result, service); + } + log("Add " + settings.gracePeriodService + "ms grace period after services"); + result = result.then(()=>{ + log("Wait after services"); + return waitingPromise(settings.gracePeriodService); + }); + return result; + }); + } else { + for (var characteristic of characteristics){ + characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true); + } + } + + return characteristicsPromise; + }); + + return promise.then(()=>{ + log("Connection established, waiting for notifications"); + characteristicsToCache(characteristics); + clearRetryTimeout(true); + }).catch((e) => { + characteristics = []; + log("Error:", e); + onDisconnect(e); + }); + }; + + var powerdownRequested = false; + + Bangle.setBTHRMPower = function(isOn, app) { + // Do app power handling + if (!app) app="?"; + if (Bangle._PWR===undefined) Bangle._PWR={}; + if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; + if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); + if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app); + isOn = Bangle._PWR.BTHRM.length; + // so now we know if we're really on + if (isOn) { + powerdownRequested = false; + switchFallback(); + if (!Bangle.isBTHRMConnected()) initBt(); + } else { // not on + log("Power off for " + app); + powerdownRequested = true; + clearRetryTimeout(true); + stopFallback(); + if (gatt) { + if (gatt.connected){ + log("Disconnect with gatt", gatt); + try{ + gatt.disconnect().then(()=>{ + log("Successful disconnect"); + }).catch((e)=>{ + log("Error during disconnect promise", e); + }); + } catch (e){ + log("Error during disconnect attempt", e); + } + } + } + } + }; + + if (settings.replace){ + // register a listener for original HRM events and emit as HRM_int + Bangle.on("HRM", (e) => { + e.modified = true; + log("Emitting HRM_int", e); + Bangle.emit("HRM_int", e); + if (fallbackActive){ + // if fallback to internal HRM is active, emit as HRM_R to which everyone listens + log("Emitting HRM_R(int)", e); + Bangle.emit("HRM_R", e); + } + }); + + // force all apps wanting to listen to HRM to actually get events for HRM_R + Bangle.on = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.on); + + Bangle.removeListener = ( o => (name, cb) => { + o = o.bind(Bangle); + if (name == "HRM") o("HRM_R", cb); + else o(name, cb); + })(Bangle.removeListener); + } + + Bangle.origSetHRMPower = Bangle.setHRMPower; + + if (settings.startWithHrm){ + Bangle.setHRMPower = function(isOn, app) { + log("setHRMPower for " + app + ": " + (isOn?"on":"off")); + if (settings.enabled){ + Bangle.setBTHRMPower(isOn, app); + if (Bangle._PWR && Bangle._PWR.HRM && Object.keys(Bangle._PWR.HRM).length == 0) { + Bangle._PWR.BTHRM = []; + Bangle.setBTHRMPower(0); + if (!isOn) stopFallback(); + } + } + if ((settings.enabled && !settings.replace) || !settings.enabled){ + Bangle.origSetHRMPower(isOn, app); + } + }; + } + + var fallbackActive = false; + var inSwitch = false; + + var stopFallback = function(){ + if (fallbackActive){ + Bangle.origSetHRMPower(0, "bthrm_fallback"); + fallbackActive = false; + log("Fallback to HRM disabled"); + } + }; + + var startFallback = function(){ + if (!fallbackActive && settings.allowFallback) { + fallbackActive = true; + Bangle.origSetHRMPower(1, "bthrm_fallback"); + log("Fallback to HRM enabled"); + } + }; + + var switchFallback = function() { + log("Check falling back to HRM"); + if (!inSwitch){ + inSwitch = true; + if (Bangle.isBTHRMActive()){ + stopFallback(); + } else { + startFallback(); + } + } + inSwitch = false; + }; + + if (settings.replace){ + log("Replace HRM event"); + if (Bangle._PWR && Bangle._PWR.HRM){ + for (var i = 0; i < Bangle._PWR.HRM.length; i++){ + var app = Bangle._PWR.HRM[i]; + log("Moving app " + app); + Bangle.origSetHRMPower(0, app); + Bangle.setBTHRMPower(1, app); + if (Bangle._PWR.HRM===undefined) break; + } + } + } + + E.on("kill", ()=>{ + if (gatt && gatt.connected){ + log("Got killed, trying to disconnect"); + try { + gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect promise on kill", e)); + } catch (e) { + log("Error during disconnnect on kill", e) + } + } + }); + } +}; diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 4d2cb811b..18c34ea33 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.12", + "version": "0.16", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", "type": "app", @@ -15,6 +15,7 @@ {"name":"bthrm.0.boot.js","url":"boot.js"}, {"name":"bthrm.img","url":"app-icon.js","evaluate":true}, {"name":"bthrm.settings.js","url":"settings.js"}, + {"name":"bthrm","url":"lib.js"}, {"name":"bthrm.default.json","url":"default.json"} ] } diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js index ed36b5aef..fcfed47c3 100644 --- a/apps/bthrm/recorder.js +++ b/apps/bthrm/recorder.js @@ -38,35 +38,32 @@ recorders.hrmint = function() { var active = false; var bpmTimeout; - var bpm = "", bpmConfidence = "", src=""; + var bpm = "", bpmConfidence = ""; function onHRM(h) { bpmConfidence = h.confidence; bpm = h.bpm; - srv = h.src; if (h.bpm > 0){ active = true; - print("active" + h.bpm); if (bpmTimeout) clearTimeout(bpmTimeout); bpmTimeout = setTimeout(()=>{ - print("inactive"); active = false; },3000); } } return { name : "HR int", - fields : ["Heartrate", "Confidence"], + fields : ["Int Heartrate", "Int Confidence"], getValues : () => { - var r = [bpm,bpmConfidence,src]; - bpm = ""; bpmConfidence = ""; src=""; + var r = [bpm,bpmConfidence]; + bpm = ""; bpmConfidence = ""; return r; }, start : () => { - Bangle.origOn('HRM', onHRM); + Bangle.on('HRM_int', onHRM); if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder"); }, stop : () => { - Bangle.removeListener('HRM', onHRM); + Bangle.removeListener('HRM_int', onHRM); if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder"); }, draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js index 2b19ea46a..459ed29fc 100644 --- a/apps/bthrm/settings.js +++ b/apps/bthrm/settings.js @@ -96,6 +96,18 @@ writeSettings("debuglog",v); } }, + 'Use bonding': { + value: !!settings.bonding, + onchange: v => { + writeSettings("bonding",v); + } + }, + 'Use active scanning': { + value: !!settings.active, + onchange: v => { + writeSettings("active",v); + } + }, 'Grace periods': function() { E.showMenu(submenu_grace); } }; diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index fbae0e1e7..fa0f7b01f 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -7,7 +7,7 @@ "icon": "app.png", "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}], "type": "clock", - "tags": "clock", + "tags": "clock,clkinfo", "supports": ["BANGLEJS2"], "allow_emulator": true, "storage": [ diff --git a/apps/calclock/ChangeLog b/apps/calclock/ChangeLog index f4a0c96f5..5c1b7c4bc 100644 --- a/apps/calclock/ChangeLog +++ b/apps/calclock/ChangeLog @@ -2,3 +2,4 @@ 0.02: More compact rendering & app icon 0.03: Tell clock widgets to hide. 0.04: Improve current time readability in light theme. +0.05: Show calendar colors & improved all day events. diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js index a55dc05f9..5a13a202f 100644 --- a/apps/calclock/calclock.js +++ b/apps/calclock/calclock.js @@ -20,41 +20,55 @@ function zp(str) { } function drawEventHeader(event, y) { - g.setFont("Vector", 24); - + var x = 0; var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000); - var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes()); - g.drawString(timeStr, 5, y); - y += 24; + + //Don't need to know what time the event is at if its all day + if (isActive(event) || !event.allDay) { + g.setFont("Vector", 24); + var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes()); + g.drawString(timeStr, 0, y); + y += 3; + x = 13*timeStr.length+5; + } g.setFont("12x20", 1); + if (isActive(event)) { - g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),15*timeStr.length,y-21); + g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),x,y); } else { var offset = 0-time.getTimezoneOffset()/1440; var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset); - if(days > 0) { + if(days > 0 || event.allDay) { var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days"; - g.drawString(daysStr,15*timeStr.length,y-21); + g.drawString(daysStr,x,y); } } + y += 21; return y; } function drawEventBody(event, y) { g.setFont("12x20", 1); - var lines = g.wrapString(event.title, g.getWidth()-10); + var lines = g.wrapString(event.title, g.getWidth()-15); + var yStart = y; if (lines.length > 2) { lines = lines.slice(0,2); lines[1] = lines[1].slice(0,-3)+"..."; } - g.drawString(lines.join('\n'), 5, y); + g.drawString(lines.join('\n'),10,y); y+=20 * lines.length; if(event.location) { - g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),5,y); - g.drawString(event.location, 20, y); + g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y); + g.drawString(event.location,25,y); y+=20; } + if (event.color) { + var oldColor = g.getColor(); + g.setColor("#"+(0x1000000+Number(event.color)).toString(16).padStart(6,"0")); + g.fillRect(0,yStart,5,y-3); + g.setColor(oldColor); + } y+=5; return y; } @@ -68,19 +82,19 @@ function drawEvent(event, y) { var curEventHeight = 0; function drawCurrentEvents(y) { - g.setColor(g.theme.dark ? "#0ff" : "#0000ff"); - g.clearRect(5, y, g.getWidth() - 5, y + curEventHeight); + g.setColor(g.theme.dark ? "#0ff" : "#00f"); + g.clearRect(0,y,g.getWidth()-5,y+curEventHeight); curEventHeight = y; if(current.length === 0) { y = drawEvent({timestamp: getTime(), durationInSeconds: 100}, y); } else { - y = drawEventHeader(current[0], y); + y = drawEventHeader(current[0],y); for (var e of current) { - y = drawEventBody(e, y); + y = drawEventBody(e,y); } } - curEventHeight = y - curEventHeight; + curEventHeight = y-curEventHeight; return y; } @@ -94,7 +108,7 @@ function drawFutureEvents(y) { } function fullRedraw() { - g.clearRect(5,24,g.getWidth()-5,g.getHeight()); + g.clearRect(0,24,g.getWidth()-5,g.getHeight()); updateCalendar(); var y = 30; y = drawCurrentEvents(y); @@ -117,3 +131,4 @@ var minuteInterval = setInterval(redraw, 60 * 1000); Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); + diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json index 3aab55186..be0a1bdd8 100644 --- a/apps/calclock/metadata.json +++ b/apps/calclock/metadata.json @@ -2,7 +2,7 @@ "id": "calclock", "name": "Calendar Clock", "shortName": "CalClock", - "version": "0.04", + "version": "0.05", "description": "Show the current and upcoming events synchronized from Gadgetbridge", "icon": "calclock.png", "type": "clock", diff --git a/apps/calclock/screenshot.patch b/apps/calclock/screenshot.patch new file mode 100644 index 000000000..3fdbf79d1 --- /dev/null +++ b/apps/calclock/screenshot.patch @@ -0,0 +1,32 @@ +diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js +index cb8c6100e..2092c1a4e 100644 +--- a/apps/calclock/calclock.js ++++ b/apps/calclock/calclock.js +@@ -3,9 +3,24 @@ var current = []; + var next = []; + + function updateCalendar() { +- calendar = require("Storage").readJSON("android.calendar.json",true)||[]; +- calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp); +- calendar.sort((a,b) => a.timestamp - b.timestamp); ++ calendar = [ ++ { ++ t: "calendar", ++ id: 2, type: 0, timestamp: getTime(), durationInSeconds: 200, ++ title: "Capture Screenshot", ++ description: "Capture Screenshot", ++ location: "", ++ calName: "", ++ color: -7151168, allDay: true }, ++ { ++ t: "calendar", ++ id: 7186, type: 0, timestamp: getTime() + 2000, durationInSeconds: 100, ++ title: "Upload to BangleApps", ++ description: "", ++ location: "", ++ calName: "", ++ color: -509406, allDay: false } ++ ]; + + current = calendar.filter(isActive); + next = calendar.filter(e=>!isActive(e)); diff --git a/apps/calclock/screenshot.png b/apps/calclock/screenshot.png index 4ab503f2b..8b2e39784 100644 Binary files a/apps/calclock/screenshot.png and b/apps/calclock/screenshot.png differ diff --git a/apps/calculator/ChangeLog b/apps/calculator/ChangeLog index a08a0f5a7..2e1ace7bf 100644 --- a/apps/calculator/ChangeLog +++ b/apps/calculator/ChangeLog @@ -3,3 +3,5 @@ 0.03: Support for different screen sizes and touchscreen 0.04: Display current operation on LHS 0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2) +0.06: Bangle.js 2: Exit with a short press of the physical button +0.07: Bangle.js 2: Exit by pressing upper left corner of the screen diff --git a/apps/calculator/README.md b/apps/calculator/README.md index b25d355bf..62f6cef24 100644 --- a/apps/calculator/README.md +++ b/apps/calculator/README.md @@ -12,12 +12,20 @@ Basic calculator reminiscent of MacOs's one. Handy for small calculus. ## Controls +Bangle.js 1 - UP: BTN1 - DOWN: BTN3 - LEFT: BTN4 - RIGHT: BTN5 - SELECT: BTN2 +Bangle.js 2 +- Swipes to change visible buttons +- Click physical button to exit +- Press upper left corner of screen to exit (where the red back button would be) ## Creator + +## Contributors +[thyttan](https://github.com/thyttan) diff --git a/apps/calculator/app.js b/apps/calculator/app.js index 40953254e..d9a89a989 100644 --- a/apps/calculator/app.js +++ b/apps/calculator/app.js @@ -3,6 +3,8 @@ * * Original Author: Frederic Rousseau https://github.com/fredericrous * Created: April 2020 + * + * Contributors: thyttan https://github.com/thyttan */ g.clear(); @@ -402,43 +404,42 @@ if (process.env.HWVERSION==1) { swipeEnabled = false; drawGlobal(); } else { // touchscreen? - selected = "NONE"; + selected = "NONE"; swipeEnabled = true; prepareScreen(numbers, numbersGrid, COLORS.DEFAULT); prepareScreen(operators, operatorsGrid, COLORS.OPERATOR); prepareScreen(specials, specialsGrid, COLORS.SPECIAL); drawNumbers(); - Bangle.on('touch',(n,e)=>{ - for (var key in screen) { - if (typeof screen[key] == "undefined") break; - var r = screen[key].xy; - if (e.x>=r[0] && e.y>=r[1] && - e.x{ + for (var key in screen) { + if (typeof screen[key] == "undefined") break; + var r = screen[key].xy; + if (e.x>=r[0] && e.y>=r[1] && e.x { - if (!e.b) { - if (lastX > 50) { // right + }, + swipe : (LR, UD) => { + if (LR == 1) { // right drawSpecials(); - } else if (lastX < -50) { // left + } + if (LR == -1) { // left drawOperators(); - } else if (lastY > 50) { // down - drawNumbers(); - } else if (lastY < -50) { // up + } + if (UD == 1) { // down + drawNumbers(); + } + if (UD == -1) { // up drawNumbers(); } - lastX = 0; - lastY = 0; - } else { - lastX = lastX + e.dx; - lastY = lastY + e.dy; } }); + } - displayOutput(0); diff --git a/apps/calculator/metadata.json b/apps/calculator/metadata.json index e78e4d54f..1674b7843 100644 --- a/apps/calculator/metadata.json +++ b/apps/calculator/metadata.json @@ -2,7 +2,7 @@ "id": "calculator", "name": "Calculator", "shortName": "Calculator", - "version": "0.05", + "version": "0.07", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "icon": "calculator.png", "screenshots": [{"url":"screenshot_calculator.png"}], diff --git a/apps/calibration/metadata.json b/apps/calibration/metadata.json index b60650300..f428bd538 100644 --- a/apps/calibration/metadata.json +++ b/apps/calibration/metadata.json @@ -3,7 +3,7 @@ "shortName":"Calibration", "icon": "calibration.png", "version":"0.03", - "description": "A simple calibration app for the touchscreen", + "description": "(NOT RECOMMENDED) A simple calibration app for the touchscreen. Please use the Touchscreen Calibration in the Settings app instead.", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "tags": "tool", diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog index 883bd2585..1180554ff 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -10,3 +10,4 @@ 0.9: Remove ESLint spaces 0.10: Show daily steps, heartrate and the temperature if weather information is available. 0.11: Tell clock widgets to hide. +0.12: Swipe down to see widgets, step counter now just uses getHealthStatus diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md index aaeb3f122..6c13cdcac 100644 --- a/apps/cassioWatch/README.md +++ b/apps/cassioWatch/README.md @@ -8,4 +8,5 @@ It displays current temperature,day,steps,battery.heartbeat and weather. **To-do**: -Align and change size of some elements. + +* Align and change size of some elements diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js index 91f6737ad..19dd883d2 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -91,7 +91,6 @@ function getTemperature(){ var weatherJson = storage.readJSON('weather.json'); var weather = weatherJson.weather; return Math.round(weather.temp-273.15); - } catch(ex) { print(ex) return "?" @@ -99,20 +98,7 @@ function getTemperature(){ } function getSteps() { - var steps = 0; - try{ - if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } else if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else { - steps = Bangle.getHealthStatus("day").steps; - } - } catch(ex) { - // In case we failed, we can only show 0 steps. - return "? k"; - } - + var steps = Bangle.getHealthStatus("day").steps; steps = Math.round(steps/1000); return steps + "k"; } @@ -121,8 +107,7 @@ function getSteps() { function draw() { queueDraw(); - g.reset(); - g.clear(); + g.clear(1); g.setColor(0, 255, 255); g.fillRect(0, 0, g.getWidth(), g.getHeight()); let background = getBackgroundImage(); @@ -143,9 +128,6 @@ function draw() { drawClock(); drawRocket(); drawBattery(); - - // Hide widgets - for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } Bangle.on("lcdPower", (on) => { @@ -169,7 +151,6 @@ Bangle.setUI("clock"); // Load widgets, but don't show them Bangle.loadWidgets(); - -g.reset(); -g.clear(); +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe +g.clear(1); draw(); diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index abfee9b93..5ac4502fd 100644 --- a/apps/cassioWatch/metadata.json +++ b/apps/cassioWatch/metadata.json @@ -4,7 +4,7 @@ "description": "Animated Clock with Space Cassio Watch Style", "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], "icon": "app.png", - "version": "0.11", + "version": "0.12", "type": "clock", "tags": "clock, weather, cassio, retro", "supports": ["BANGLEJS2"], diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index c398a89b6..8ec578fb6 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -26,3 +26,9 @@ 0.12: Allow configuration of update interval 0.13: Load step goal from Bangle health app as fallback Memory optimizations +0.14: Support to show big weather info +0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16 +0.16: Fix const error + Use widget_utils if available +0.17: Load circles from clkinfo +0.18: Improved clkinfo handling and using it for the weather circle diff --git a/apps/circlesclock/README.md b/apps/circlesclock/README.md index aa429d5ec..8c8fbe4ae 100644 --- a/apps/circlesclock/README.md +++ b/apps/circlesclock/README.md @@ -9,10 +9,11 @@ It can show the following information (this can be configured): * Steps distance * Heart rate (automatically updates when screen is on and unlocked) * Battery (including charging status and battery low warning) - * Weather (requires [weather app](https://banglejs.com/apps/#weather)) + * Weather (requires [OWM weather provider](https://banglejs.com/apps/?id=owmweather)) * Humidity or wind speed as circle progress * Temperature inside circle * Condition as icon below circle + * Big weather icon next to clock * Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation)) * Temperature, air pressure or altitude from internal pressure sensor @@ -27,6 +28,8 @@ The color of each circle can be configured. The following colors are available: ![Screenshot light theme](screenshot-light.png) ![Screenshot dark theme with four circles](screenshot-dark-4.png) ![Screenshot light theme with four circles](screenshot-light-4.png) +![Screenshot light theme with big weather enabled](screenshot-light-with-big-weather.png) + ## Ideas * Show compass heading @@ -35,4 +38,5 @@ The color of each circle can be configured. The following colors are available: Marco ([myxor](https://github.com/myxor)) ## Icons -Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 +Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from +[icons8](https://icons8.com/icon/set/weather/small--static--black) diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index fc501a5d0..ee6741398 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,5 +1,6 @@ -const locale = require("locale"); -const storage = require("Storage"); +let clock_info = require("clock_info"); +let locale = require("locale"); +let storage = require("Storage"); Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { // Actual height 39 (40 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16)); @@ -12,23 +13,27 @@ Graphics.prototype.setFontRobotoRegular21 = function(scale) { return this; }; -const SETTINGS_FILE = "circlesclock.json"; +let SETTINGS_FILE = "circlesclock.json"; let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} ); + //TODO deprecate this (and perhaps use in the clkinfo module) // Load step goal from health app and pedometer widget as fallback if (settings.stepGoal == undefined) { let d = storage.readJSON("health.json", true) || {}; settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined; - - if (settings.stepGoal == undefined) { + + if (settings.stepGoal == undefined) { d = storage.readJSON("wpedom.json", true) || {}; settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; } } +let timerHrm; //TODO deprecate this +let drawTimeout; + /* * Read location from myLocation app */ @@ -37,29 +42,29 @@ function getLocation() { } let location = getLocation(); -const showWidgets = settings.showWidgets || false; -const circleCount = settings.circleCount || 3; +let showWidgets = settings.showWidgets || false; +let circleCount = settings.circleCount || 3; +let showBigWeather = settings.showBigWeather || false; -let hrtValue; +let hrtValue; //TODO deprecate this let now = Math.round(new Date().getTime() / 1000); - // layout values: -const colorFg = g.theme.dark ? '#fff' : '#000'; -const colorBg = g.theme.dark ? '#000' : '#fff'; -const colorGrey = '#808080'; -const colorRed = '#ff0000'; -const colorGreen = '#008000'; -const colorBlue = '#0000ff'; -const colorYellow = '#ffff00'; -const widgetOffset = showWidgets ? 24 : 0; -const dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date -const h = g.getHeight() - widgetOffset; -const w = g.getWidth(); -const hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; -const h1 = Math.round(1 * h / 5 - hOffset); -const h2 = Math.round(3 * h / 5 - hOffset); -const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position +let colorFg = g.theme.dark ? '#fff' : '#000'; +let colorBg = g.theme.dark ? '#000' : '#fff'; +let colorGrey = '#808080'; +let colorRed = '#ff0000'; +let colorGreen = '#008000'; +let colorBlue = '#0000ff'; +let colorYellow = '#ffff00'; +let widgetOffset = showWidgets ? 24 : 0; +let dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date +let h = g.getHeight() - widgetOffset; +let w = g.getWidth(); +let hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; +let h1 = Math.round(1 * h / 5 - hOffset); +let h2 = Math.round(3 * h / 5 - hOffset); +let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position /* * circle x positions @@ -73,21 +78,38 @@ const h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position * | (1) (2) (3) (4) | * => circles start at 1,3,5,7 / 8 */ -const parts = circleCount * 2; -const circlePosX = [ +let parts = circleCount * 2; +let circlePosX = [ Math.round(1 * w / parts), // circle1 Math.round(3 * w / parts), // circle2 Math.round(5 * w / parts), // circle3 Math.round(7 * w / parts), // circle4 ]; -const radiusOuter = circleCount == 3 ? 25 : 20; -const radiusInner = circleCount == 3 ? 20 : 15; -const circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10"; -const circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; -const circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; -const iconOffset = circleCount == 3 ? 6 : 8; -const defaultCircleTypes = ["steps", "hr", "battery", "weather"]; +let radiusOuter = circleCount == 3 ? 25 : 20; +let radiusInner = circleCount == 3 ? 20 : 15; +let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10"; +let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; +let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; +let iconOffset = circleCount == 3 ? 6 : 8; +let defaultCircleTypes = ["Bangle/Steps", "Bangle/HRM", "Bangle/Battery", "weather"]; + +let circleInfoNum = [ + 0, // circle1 + 0, // circle2 + 0, // circle3 + 0, // circle4 +]; +let circleItemNum = [ + 0, // circle1 + 1, // circle2 + 2, // circle3 + 3, // circle4 +]; +let weatherCircleNum = 0; +let weatherCircleDataNum = 0; +let weatherCircleCondNum = 0; +let weatherCircleTempNum = 0; function hideWidgets() { /* @@ -105,9 +127,16 @@ function hideWidgets() { function draw() { g.clear(true); + let widgetUtils; + + try { + widgetUtils = require("widget_utils"); + } catch (e) { + } if (!showWidgets) { - hideWidgets(); + if (widgetUtils) widgetUtils.hide(); else hideWidgets(); } else { + if (widgetUtils) widgetUtils.show(); Bangle.drawWidgets(); } @@ -116,29 +145,64 @@ function draw() { // time g.setFontRobotoRegular50NumericOnly(); - g.setFontAlign(0, -1); g.setColor(colorFg); - g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6); + if (!showBigWeather) { + g.setFontAlign(0, -1); + g.drawString(locale.time(new Date(), 1), w / 2, h1 + 6); + } + else { + g.setFontAlign(-1, -1); + g.drawString(locale.time(new Date(), 1), 2, h1 + 6); + } now = Math.round(new Date().getTime() / 1000); // date & dow g.setFontRobotoRegular21(); - g.setFontAlign(0, 0); - g.drawString(locale.date(new Date()), w / 2, h2); - g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); - + if (!showBigWeather) { + g.setFontAlign(0, 0); + g.drawString(locale.date(new Date()), w / 2, h2); + g.drawString(locale.dow(new Date()), w / 2, h2 + dowOffset); + } else { + g.setFontAlign(-1, 0); + g.drawString(locale.date(new Date()), 2, h2); + g.drawString(locale.dow(new Date()), 2, h2 + dowOffset, 1); + } + + // weather + if (showBigWeather) { + let weather = getWeather(); + let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; + g.setFontAlign(1, 0); + if (tempString) g.drawString(tempString, w, h2); + + let code = weather ? weather.code : -1; + let icon = getWeatherIconByCode(code, true); + if (icon) g.drawImage(icon, w - 48, h1, {scale:0.75}); + } + drawCircle(1); drawCircle(2); drawCircle(3); if (circleCount >= 4) drawCircle(4); + + queueDraw(); } function drawCircle(index) { let type = settings['circle' + index]; if (!type) type = defaultCircleTypes[index - 1]; - const w = getCircleXPosition(type); + let w = getCircleXPosition(type); switch (type) { + case "weather": + drawWeather(w); + break; + case "sunprogress": + case "sunProgress": + drawSunProgress(w); + break; + //TODO those are going to be deprecated, keep for backwards compatibility for now + //ideally all data should come from some clkinfo case "steps": drawSteps(w); break; @@ -151,13 +215,6 @@ function drawCircle(index) { case "battery": drawBattery(w); break; - case "weather": - drawWeather(w); - break; - case "sunprogress": - case "sunProgress": - drawSunProgress(w); - break; case "temperature": drawTemperature(w); break; @@ -167,9 +224,12 @@ function drawCircle(index) { case "altitude": drawAltitude(w); break; + //end deprecated case "empty": // we draw nothing here return; + default: + drawClkInfo(index, w); } } @@ -188,7 +248,7 @@ function getCirclePosition(type) { return circlePositionsCache[type]; } for (let i = 1; i <= circleCount; i++) { - const setting = settings['circle' + i]; + let setting = settings['circle' + i]; if (setting == type) { circlePositionsCache[type] = i - 1; return i - 1; @@ -204,7 +264,7 @@ function getCirclePosition(type) { } function getCircleXPosition(type) { - const circlePos = getCirclePosition(type); + let circlePos = getCirclePosition(type); if (circlePos != undefined) { return circlePosX[circlePos]; } @@ -216,14 +276,14 @@ function isCircleEnabled(type) { } function getCircleColor(type) { - const pos = getCirclePosition(type); - const color = settings["circle" + (pos + 1) + "color"]; + let pos = getCirclePosition(type); + let color = settings["circle" + (pos + 1) + "color"]; if (color && color != "") return color; } function getCircleIconColor(type, color, percent) { - const pos = getCirclePosition(type); - const colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true; + let pos = getCirclePosition(type); + let colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true; if (colorizeIcon) { return getGradientColor(color, percent); } else { @@ -234,18 +294,18 @@ function getCircleIconColor(type, color, percent) { function getGradientColor(color, percent) { if (isNaN(percent)) percent = 0; if (percent > 1) percent = 1; - const colorList = [ + let colorList = [ '#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000' ]; if (color == "fg") { color = colorFg; } if (color == "green-red") { - const colorIndex = Math.round(colorList.length * percent); + let colorIndex = Math.round(colorList.length * percent); return colorList[Math.min(colorIndex, colorList.length) - 1] || "#00ff00"; } if (color == "red-green") { - const colorIndex = colorList.length - Math.round(colorList.length * percent); + let colorIndex = colorList.length - Math.round(colorList.length * percent); return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000"; } return color; @@ -266,16 +326,161 @@ function getImage(graphic, color) { } } -function drawSteps(w) { - if (!w) w = getCircleXPosition("steps"); - const steps = getSteps(); +function drawWeather(w) { + if (!w) w = getCircleXPosition("weather"); + let weatherInfo = menu[weatherCircleNum]; + let weatherCond = weatherCircleCondNum >= 0? weatherInfo.items[weatherCircleCondNum]: undefined; + let weatherData = weatherCircleDataNum >= 0? weatherInfo.items[weatherCircleDataNum]: undefined; + let weatherTemp = weatherCircleTempNum >= 0? weatherInfo.items[weatherCircleTempNum]: undefined; + let color = getCircleColor("weather"); + let percent = 0; + let data = settings.weatherCircleData; + let tempString = "?", icon = undefined; + let scale = 16/24; //our icons are 16x16 while clkinfo's are 24x24 + + if(weatherCond) { + weatherCond.show() + weatherCond.hide() + let data = weatherCond.get() + if(settings.legacyWeatherIcons) { //may disappear in future + icon = getWeatherIconByCode(data.v); + scale = 1; + } else + icon = data.img; + } + if(weatherTemp) { + weatherTemp.show() + weatherTemp.hide() + tempString = weatherTemp.get().text; + } drawCircleBackground(w); - const color = getCircleColor("steps"); + if(weatherData) { + weatherData.show(); + weatherData.hide(); + let data = weatherData.get(); + if(weatherData.hasRange) percent = (data.v-data.min) / (data.max-data.min); + drawGauge(w, h3, percent, color); + } + + drawInnerCircleAndTriangle(w); + + writeCircleText(w, tempString); + + if(icon) { + g.setColor(getCircleIconColor("weather", color, percent)) + .drawImage(icon, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: scale}); + } else { + g.drawString("?", w, h3 + radiusOuter); + } +} +function drawWeatherOld(w) { + if (!w) w = getCircleXPosition("weather"); + let weather = getWeather(); + let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; + let code = weather ? weather.code : -1; + + drawCircleBackground(w); + + let color = getCircleColor("weather"); + let percent; + let data = settings.weatherCircleData; + switch (data) { + case "humidity": + let humidity = weather ? weather.hum : undefined; + if (humidity >= 0) { + percent = humidity / 100; + drawGauge(w, h3, percent, color); + } + break; + case "wind": + if (weather) { + let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); + if (wind[1] >= 0) { + if (wind[2] == "kmh") { + wind[1] = windAsBeaufort(wind[1]); + } + // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) + percent = wind[1] / 12; + drawGauge(w, h3, percent, color); + } + } + break; + case "empty": + break; + } + + drawInnerCircleAndTriangle(w); + + writeCircleText(w, tempString ? tempString : "?"); + + if (code > 0) { + let icon = getWeatherIconByCode(code); + if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); + } else { + g.drawString("?", w, h3 + radiusOuter); + } +} + +function drawSunProgress(w) { + if (!w) w = getCircleXPosition("sunprogress"); + let percent = getSunProgress(); + + // sunset icons: + let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); + let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); + + drawCircleBackground(w); + + let color = getCircleColor("sunprogress"); + + drawGauge(w, h3, percent, color); + + drawInnerCircleAndTriangle(w); + + let icon = sunSetDown; + let text = "?"; + let times = getSunData(); + if (times != undefined) { + let sunRise = Math.round(times.sunrise.getTime() / 1000); + let sunSet = Math.round(times.sunset.getTime() / 1000); + if (!isDay()) { + // night + if (now > sunRise) { + // after sunRise + let upcomingSunRise = sunRise + 60 * 60 * 24; + text = formatSeconds(upcomingSunRise - now); + } else { + text = formatSeconds(sunRise - now); + } + icon = sunSetUp; + } else { + // day, approx sunrise tomorrow: + text = formatSeconds(sunSet - now); + icon = sunSetDown; + } + } + + writeCircleText(w, text); + + g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); +} + +/* + * Deprecated but nice as references for clkinfo + */ + +function drawSteps(w) { + if (!w) w = getCircleXPosition("steps"); + let steps = getSteps(); + + drawCircleBackground(w); + + let color = getCircleColor("steps"); let percent; - const stepGoal = settings.stepGoal; + let stepGoal = settings.stepGoal; if (stepGoal > 0) { percent = steps / stepGoal; if (stepGoal < steps) percent = 1; @@ -291,16 +496,16 @@ function drawSteps(w) { function drawStepsDistance(w) { if (!w) w = getCircleXPosition("stepsDistance"); - const steps = getSteps(); - const stepDistance = settings.stepLength; - const stepsDistance = Math.round(steps * stepDistance); + let steps = getSteps(); + let stepDistance = settings.stepLength; + let stepsDistance = Math.round(steps * stepDistance); drawCircleBackground(w); - const color = getCircleColor("stepsDistance"); + let color = getCircleColor("stepsDistance"); let percent; - const stepDistanceGoal = settings.stepDistanceGoal; + let stepDistanceGoal = settings.stepDistanceGoal; if (stepDistanceGoal > 0) { percent = stepsDistance / stepDistanceGoal; if (stepDistanceGoal < stepsDistance) percent = 1; @@ -317,16 +522,16 @@ function drawStepsDistance(w) { function drawHeartRate(w) { if (!w) w = getCircleXPosition("hr"); - const heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); + let heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); drawCircleBackground(w); - const color = getCircleColor("hr"); + let color = getCircleColor("hr"); let percent; if (hrtValue != undefined) { - const minHR = settings.minHR; - const maxHR = settings.maxHR; + let minHR = settings.minHR; + let maxHR = settings.maxHR; percent = (hrtValue - minHR) / (maxHR - minHR); if (isNaN(percent)) percent = 0; drawGauge(w, h3, percent, color); @@ -341,9 +546,9 @@ function drawHeartRate(w) { function drawBattery(w) { if (!w) w = getCircleXPosition("battery"); - const battery = E.getBattery(); + let battery = E.getBattery(); - const powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); + let powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); drawCircleBackground(w); @@ -368,112 +573,18 @@ function drawBattery(w) { g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); } - -function drawWeather(w) { - if (!w) w = getCircleXPosition("weather"); - const weather = getWeather(); - const tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; - const code = weather ? weather.code : -1; - - drawCircleBackground(w); - - const color = getCircleColor("weather"); - let percent; - const data = settings.weatherCircleData; - switch (data) { - case "humidity": - const humidity = weather ? weather.hum : undefined; - if (humidity >= 0) { - percent = humidity / 100; - drawGauge(w, h3, percent, color); - } - break; - case "wind": - if (weather) { - const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); - if (wind[1] >= 0) { - if (wind[2] == "kmh") { - wind[1] = windAsBeaufort(wind[1]); - } - // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) - percent = wind[1] / 12; - drawGauge(w, h3, percent, color); - } - } - break; - case "empty": - break; - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, tempString ? tempString : "?"); - - if (code > 0) { - const icon = getWeatherIconByCode(code); - if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - } else { - g.drawString("?", w, h3 + radiusOuter); - } -} - - -function drawSunProgress(w) { - if (!w) w = getCircleXPosition("sunprogress"); - const percent = getSunProgress(); - - // sunset icons: - const sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); - const sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); - - drawCircleBackground(w); - - const color = getCircleColor("sunprogress"); - - drawGauge(w, h3, percent, color); - - drawInnerCircleAndTriangle(w); - - let icon = sunSetDown; - let text = "?"; - const times = getSunData(); - if (times != undefined) { - const sunRise = Math.round(times.sunrise.getTime() / 1000); - const sunSet = Math.round(times.sunset.getTime() / 1000); - if (!isDay()) { - // night - if (now > sunRise) { - // after sunRise - const upcomingSunRise = sunRise + 60 * 60 * 24; - text = formatSeconds(upcomingSunRise - now); - } else { - text = formatSeconds(sunRise - now); - } - icon = sunSetUp; - } else { - // day, approx sunrise tomorrow: - text = formatSeconds(sunSet - now); - icon = sunSetDown; - } - } - - writeCircleText(w, text); - - g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - function drawTemperature(w) { if (!w) w = getCircleXPosition("temperature"); getPressureValue("temperature").then((temperature) => { drawCircleBackground(w); - const color = getCircleColor("temperature"); + let color = getCircleColor("temperature"); let percent; if (temperature) { - const min = -40; - const max = 85; + let min = -40; + let max = 85; percent = (temperature - min) / (max - min); drawGauge(w, h3, percent, color); } @@ -482,7 +593,7 @@ function drawTemperature(w) { if (temperature) writeCircleText(w, locale.temp(temperature)); - + g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); }); @@ -494,12 +605,12 @@ function drawPressure(w) { getPressureValue("pressure").then((pressure) => { drawCircleBackground(w); - const color = getCircleColor("pressure"); + let color = getCircleColor("pressure"); let percent; if (pressure && pressure > 0) { - const minPressure = 950; - const maxPressure = 1050; + let minPressure = 950; + let maxPressure = 1050; percent = (pressure - minPressure) / (maxPressure - minPressure); drawGauge(w, h3, percent, color); } @@ -520,12 +631,12 @@ function drawAltitude(w) { getPressureValue("altitude").then((altitude) => { drawCircleBackground(w); - const color = getCircleColor("altitude"); + let color = getCircleColor("altitude"); let percent; if (altitude) { - const min = 0; - const max = 10000; + let min = 0; + let max = 10000; percent = (altitude - min) / (max - min); drawGauge(w, h3, percent, color); } @@ -540,11 +651,123 @@ function drawAltitude(w) { }); } +function shortValue(v) { + if (isNaN(v)) return '-'; + if (v <= 999) return v; + if (v >= 1000 && v < 10000) { + v = Math.floor(v / 100) * 100; + return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; + } + if (v >= 10000) { + v = Math.floor(v / 1000) * 1000; + return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; + } +} + +function getSteps() { + if (Bangle.getHealthStatus) { + return Bangle.getHealthStatus("day").steps; + } + if (WIDGETS && WIDGETS.wpedom !== undefined) { + return WIDGETS.wpedom.getSteps(); + } + return 0; +} + +function getPressureValue(type) { + return new Promise((resolve) => { + if (Bangle.getPressure) { + if (!pressureLocked) { + pressureLocked = true; + if (pressureCache && pressureCache[type]) { + resolve(pressureCache[type]); + } + Bangle.getPressure().then(function(d) { + pressureLocked = false; + if (d) { + pressureCache = d; + if (d[type]) { + resolve(d[type]); + } + } + }).catch(() => {}); + } else { + if (pressureCache && pressureCache[type]) { + resolve(pressureCache[type]); + } + } + } + }); +} + +/* + * end deprecated + */ + +var menu = null; +function reloadMenu() { + menu = clock_info.load(); + for(var i=1; i<5; i++) + if(settings['circle'+i].includes("/")) { + let parts = settings['circle'+i].split("/"); + let infoName = parts[0], itemName = parts[1]; + let infoNum = menu.findIndex(e=>e.name==infoName); + let itemNum = 0; //get first if dynamic + if(!menu[infoNum].dynamic) + itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName); + circleInfoNum[i-1] = infoNum; + circleItemNum[i-1] = itemNum; + } else if(settings['circle'+i] == "weather") { + weatherCircleNum = menu.findIndex(e=>e.name.toLowerCase() == "weather"); + weatherCircleDataNum = menu[weatherCircleNum].items.findIndex(it=>it.name==settings.weatherCircleData); + weatherCircleCondNum = menu[weatherCircleNum].items.findIndex(it=>it.name=="condition"); + weatherCircleTempNum = menu[weatherCircleNum].items.findIndex(it=>it.name=="temperature"); + } +} +//reload periodically for changes? +reloadMenu(); + +function drawEmpty(img, w, color) { + drawGauge(w, h3, 0, color); + drawInnerCircleAndTriangle(w); + writeCircleText(w, "?"); + if(img) + g.setColor(getGradientColor(color, 0)) + .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); +} + +function drawClkInfo(index, w) { + var info = menu[circleInfoNum[index-1]]; + var type = settings['circle'+index]; + if (!w) w = getCircleXPosition(type); + drawCircleBackground(w); + const color = getCircleColor(type); + var item = info.items[circleItemNum[index-1]]; + if(!info || !item) { + drawEmpty(info? info.img : null, w, color); + return; + } + item.show(); + item.hide(); + var data=item.get(); + var img = data.img; + var percent = 1; //fill up if no range + var txt = data.text; + if(!img) img = info.img; + if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min); + if(data.short) txt = data.short; + drawGauge(w, h3, percent, color); + drawInnerCircleAndTriangle(w); + writeCircleText(w, txt); + g.setColor(getCircleIconColor(type, color, percent)) + .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); +} + /* * wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) */ function windAsBeaufort(windInKmh) { - const beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118]; + let beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118]; let l = 0; while (l < beaufort.length && beaufort[l] < windInKmh) { l++; @@ -557,20 +780,22 @@ function windAsBeaufort(windInKmh) { * Choose weather icon to display based on weather conditition code * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 */ -function getWeatherIconByCode(code) { - const codeGroup = Math.round(code / 100); +function getWeatherIconByCode(code, big) { + let codeGroup = Math.round(code / 100); + if (big == undefined) big = false; // weather icons: - const weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); - const weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); - const weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); - const weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); - const weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); - const weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); - const weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); - const weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); - const weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); - + let weatherCloudy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); + let weatherSunny = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAA//8AAA//8AD//wAAD//wAP//AAAP//AA//8AAA//8AAADwAADwAAAAAHgAAeAAAAAAeAAB4AAAAAB8AAPgAAAAADwAA8AAAAAAPgAHwAAAAAAfgB+AAAAAAD/gf8AAAAAAf///4AAAAAD7//3wAAAAAfD/8PgAAAAD4B+AfAAAAAfADwA+AAAAD4APAB8AAAAfAA8AD4AAAB4ADwAHgAAADAAPAAMAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); + let weatherMoon = big ? atob("QEDBAP//wxgAAAYAAAAPAAAAD4AAAA8AAAAPwAAADwAAAA/gAAAPAAAAB/APAP/wAAAH+A8A//AAAAf4DwD/8AAAB/wPAP/wAAAH/gAADwAAAAe+AAAPAAAAB54AAA8AAAAHngAADwAAAAePAAAAAAAAD48OAAAAAAAPDw+AAAAAAB8PD8AAAAAAHg8P4AAAAAA+DwPwAAAAAHwfAfgAAAAB+D4A/AAA8AfwfgB/8AD//+D+AD/8AP//wfgAH/4Af/8B8AAf/wB//APgAAgfgD+AA8AAAAfAH8AHwAAAA+AP8B+AAAAB4Af//4AAAAHgA///gAAAAPAA//8AAAAA8AAf/wAAAADwAAAAAAAAAPAAAAAAAAAA8AcAAAAAAADwD+AAAAAAAfAfgAAAAAAB+D4AAAAAAAB8fAAAAAAAAD54AAAAAAAAHngAAAAAAAAe8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAPeAAAAAAAAB54AAAAAAAAHnwAAAAAAAA+PgAAAAAAAHwfgAAAAAAB+A/////////wB////////+AD////////wAB///////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); + let weatherPartlyCloudy = big ? atob("QEDBAP//wxgAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAABwAPAA4AAAAHgA8AHgAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+BAAAAAP+B/wOAAAAAfgB+B8AAAAD4AD8H4AAAAPAA/wPwAAAB8AH+Af/AAAHgA/AA//AAAeAH4AB/+AADwAfAAH/8A//AD4AAIH4D/8AfAAAAHwP/wB4AAAAPg//AHgAAAAeAA8B+AAAAB4AB4fwAAAADwAHn/AAAAAPAAff8AAAAA8AA/8AAAAADwAD/AAAAAAPAEH4AAAAAA8A4PgAAAAAHwHgcAAAAAAfg+AwAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); + let weatherRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4APAA8AAfg+AA8ADwAAfHwADwAPAAA+eAAPAA8AAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AADw8PDwAP8AAPDw8PAA/wAA8PDw8AD3gADw8PDwAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP/w8PDw8P8Af/Dw8PDw/gA/8PDw8PD8AAfw8PDw8OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); + let weatherPartlyRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAA8AAfg+AAAADwAAfHwAAAAPAAA+eAAAAA8AAB54AAAADwAAHvAAAAAPAAAP8AAAAA8AAA/wAAAADwAAD/AAAA8PAAAP8AAADw8AAA/wAAAPDwAAD3gAAA8PAAAeeAAADw8AAB58AAAPDwAAPj4AAA8PAAB8H4AADw8AAfgP//8PDw//8Af//w8PD//gA///Dw8P/8AAf/8PDw/+AAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); + let weatherSnowy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAADwAfg+AAAAAPAAfHwAAAAA8AA+eAAAAADwAB54AA8AD/8AHvAADwAP/wAP8AAPAA//AA/wAA8AD/8AD/AA//AA8AAP8AD/8ADwAA/wAP/wAPAAD3gA//AA8AAeeAAPAAAAAB58AA8AAAAAPj4ADwAAAAB8H4APAAAAAfgP/wAA8A//8Af/AADwD//gA/8AAPAP/8AAfwAA8A/+AAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); + let weatherFoggy = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AD///AADwAAAP//8AAeAAAA///wAB4AAAD///AAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAB+AAAAAAAAAf8AAAAD///D/4AAAAP//8P3wAAAA///w8PgAAAD///CAfAAAAAAAAAA+AAAAAAAAAB8AAAAAAAAAD4AAAAAAAAAHgAAP//8PAAMAAA///w8AAAAAD///DwAAAAAP//8PAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); + let weatherStormy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAD/AAHvAAAAAf4AAP8AAAAB/gAA/wAAAAP8AAD/AAAAA/gAAP8AAAAH+AAA/wAAAAfwAAD3gAAAD/AAAeeAAAAP4AAB58AAAB/AAAPj4AAAH8AAB8H4AAA/gAAfgP//+D//D/8Af//4f/4P/gA///B//B/8AAf/8P/8P+AAAAAAAPgAAAAAAAAB8AAAAAAAAAHwAAAAAAAAA+AAAAAAAAADwAAAAAAAAAfAAAAAAAAAB4AAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); + let unknown = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAH//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAA/AAD4AAAAAD4H4HwAAAAAfB/4PgAAAAB8P/weAAAAAHg//h4AAAAA+Hw+HwAAAAD4eB8PAAAAAP/wDw8AAAAA//APDwAAAAD/8A8PAAAAAH/gDw8AAAAAAAAfDwAAAAAAAH4fAAAAAAAB/B4AAAAAAAf4HgAAAAAAD/A+AAAAAAAfwHwAAAAAAD8A+AAAAAAAPgH4AAAAAAB8B/AAAAAAAHgf4AAAAAAA+H+AAAAAAADwfwAAAAAAAPD8AAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAADw8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : undefined; + switch (codeGroup) { case 2: return weatherStormy; @@ -607,16 +832,16 @@ function getWeatherIconByCode(code) { return weatherCloudy; } default: - return undefined; + return unknown; } } function isDay() { - const times = getSunData(); + let times = getSunData(); if (times == undefined) return true; - const sunRise = Math.round(times.sunrise.getTime() / 1000); - const sunSet = Math.round(times.sunset.getTime() / 1000); + let sunRise = Math.round(times.sunrise.getTime() / 1000); + let sunSet = Math.round(times.sunset.getTime() / 1000); return (now > sunRise && now < sunSet); } @@ -633,7 +858,7 @@ function formatSeconds(s) { function getSunData() { if (location != undefined && location.lat != undefined) { - const SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); + let SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); // get today's sunlight times for lat/lon return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined; } @@ -646,14 +871,14 @@ function getSunData() { * Taken from rebble app and modified */ function getSunProgress() { - const times = getSunData(); + let times = getSunData(); if (times == undefined) return 0; - const sunRise = Math.round(times.sunrise.getTime() / 1000); - const sunSet = Math.round(times.sunset.getTime() / 1000); + let sunRise = Math.round(times.sunrise.getTime() / 1000); + let sunSet = Math.round(times.sunset.getTime() / 1000); if (isDay()) { // during day - const dayLength = sunSet - sunRise; + let dayLength = sunSet - sunRise; if (now > sunRise) { return (now - sunRise) / dayLength; } else { @@ -662,10 +887,10 @@ function getSunProgress() { } else { // during night if (now < sunRise) { - const prevSunSet = sunSet - 60 * 60 * 24; + let prevSunSet = sunSet - 60 * 60 * 24; return 1 - (sunRise - now) / (sunRise - prevSunSet); } else { - const upcomingSunRise = sunRise + 60 * 60 * 24; + let upcomingSunRise = sunRise + 60 * 60 * 24; return (upcomingSunRise - now) / (upcomingSunRise - sunSet); } } @@ -700,16 +925,16 @@ function radians(a) { * This draws the actual gauge consisting out of lots of little filled circles */ function drawGauge(cx, cy, percent, color) { - const offset = 15; - const end = 360 - offset; - const radius = radiusInner + (circleCount == 3 ? 3 : 2); - const size = radiusOuter - radiusInner - 2; + let offset = 15; + let end = 360 - offset; + let radius = radiusInner + (circleCount == 3 ? 3 : 2); + let size = radiusOuter - radiusInner - 2; if (percent <= 0) return; // no gauge needed if (percent > 1) percent = 1; - const startRotation = -offset; - const endRotation = startRotation - ((end - offset) * percent); + let startRotation = -offset; + let endRotation = startRotation - ((end - offset) * percent); color = getGradientColor(color, percent); g.setColor(color); @@ -723,7 +948,7 @@ function drawGauge(cx, cy, percent, color) { function writeCircleText(w, content) { if (content == undefined) return; - const font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig; + let font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig; g.setFont(font); g.setFontAlign(0, 0); @@ -731,118 +956,32 @@ function writeCircleText(w, content) { g.drawString(content, w, h3); } -function shortValue(v) { - if (isNaN(v)) return '-'; - if (v <= 999) return v; - if (v >= 1000 && v < 10000) { - v = Math.floor(v / 100) * 100; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } - if (v >= 10000) { - v = Math.floor(v / 1000) * 1000; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } -} - -function getSteps() { - if (Bangle.getHealthStatus) { - return Bangle.getHealthStatus("day").steps; - } - if (WIDGETS && WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return 0; -} - function getWeather() { - const jsonWeather = storage.readJSON('weather.json'); + let jsonWeather = storage.readJSON('weather.json'); return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined; } -function enableHRMSensor() { - Bangle.setHRMPower(1, "circleclock"); - if (hrtValue == undefined) { - hrtValue = '...'; - drawHeartRate(); - } -} +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; -let pressureLocked = false; -let pressureCache; + delete Graphics.prototype.setFontRobotoRegular50NumericOnly; + delete Graphics.prototype.setFontRobotoRegular21; + }}); -function getPressureValue(type) { - return new Promise((resolve) => { - if (Bangle.getPressure) { - if (!pressureLocked) { - pressureLocked = true; - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - Bangle.getPressure().then(function(d) { - pressureLocked = false; - if (d) { - pressureCache = d; - if (d[type]) { - resolve(d[type]); - } - } - }).catch(() => {}); - } else { - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - } - } - }); -} - -Bangle.on('lock', function(isLocked) { - if (!isLocked) { - draw(); - if (isCircleEnabled("hr")) { - enableHRMSensor(); - } - } else { - Bangle.setHRMPower(0, "circleclock"); - } -}); - - -let timerHrm; -Bangle.on('HRM', function(hrm) { - if (isCircleEnabled("hr")) { - if (hrm.confidence >= (settings.confidence)) { - hrtValue = hrm.bpm; - if (Bangle.isLCDOn()) { - drawHeartRate(); - } - } - // Let us wait before we overwrite "good" HRM values: - if (Bangle.isLCDOn()) { - if (timerHrm) clearTimeout(timerHrm); - timerHrm = setTimeout(() => { - hrtValue = '...'; - drawHeartRate(); - }, settings.hrmValidity * 1000); - } - } -}); - -Bangle.on('charging', function(charging) { - if (isCircleEnabled("battery")) drawBattery(); -}); - -if (isCircleEnabled("hr")) { - enableHRMSensor(); -} - -Bangle.setUI("clock"); Bangle.loadWidgets(); -// schedule a draw for the next minute -setTimeout(function() { - // draw in interval - setInterval(draw, settings.updateInterval * 1000); -}, 60000 - (Date.now() % 60000)); +// schedule a draw for the next second or minute +function queueDraw() { + let queueMillis = settings.updateInterval * 1000; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, queueMillis - (Date.now() % queueMillis)); +} draw(); diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json index ea00dc347..0a41bf9e0 100644 --- a/apps/circlesclock/default.json +++ b/apps/circlesclock/default.json @@ -1,17 +1,11 @@ { - "minHR": 40, - "maxHR": 200, - "confidence": 0, - "stepGoal": 10000, - "stepDistanceGoal": 8000, - "stepLength": 0.8, "batteryWarn": 30, "showWidgets": false, "weatherCircleData": "humidity", "circleCount": 3, - "circle1": "hr", - "circle2": "steps", - "circle3": "battery", + "circle1": "Bangle/HRM", + "circle2": "Bangle/Steps", + "circle3": "Bangle/Battery", "circle4": "weather", "circle1color": "green-red", "circle2color": "#0000ff", @@ -21,6 +15,15 @@ "circle2colorizeIcon": true, "circle3colorizeIcon": true, "circle4colorizeIcon": false, - "hrmValidity": 60, - "updateInterval": 60 + "updateInterval": 60, + "showBigWeather": false, + + "minHR": 40, + "maxHR": 200, + "confidence": 0, + "stepGoal": 10000, + "stepDistanceGoal": 8000, + "stepLength": 0.8, + "hrmValidity": 60 + } diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json index 837fcaa88..fb743ae4c 100644 --- a/apps/circlesclock/metadata.json +++ b/apps/circlesclock/metadata.json @@ -1,7 +1,7 @@ { "id": "circlesclock", "name": "Circles clock", "shortName":"Circles clock", - "version":"0.13", + "version":"0.18", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "icon": "app.png", "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], diff --git a/apps/circlesclock/screenshot-light-with-big-weather.png b/apps/circlesclock/screenshot-light-with-big-weather.png new file mode 100644 index 000000000..d1d569247 Binary files /dev/null and b/apps/circlesclock/screenshot-light-with-big-weather.png differ diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js index fb23f8d5e..2ab655f01 100644 --- a/apps/circlesclock/settings.js +++ b/apps/circlesclock/settings.js @@ -1,6 +1,7 @@ (function(back) { const SETTINGS_FILE = "circlesclock.json"; const storage = require('Storage'); + const clock_info = require("clock_info"); let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} @@ -11,8 +12,22 @@ storage.write(SETTINGS_FILE, settings); } - const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"]; - const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"]; + //const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"]; + //const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude", "timer"]; + var valuesCircleTypes = ["empty","weather", "sunprogress"]; + var namesCircleTypes = ["empty","weather", "sun"]; + clock_info.load().forEach(e=>{ + if(e.dynamic) { + valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]); + namesCircleTypes = namesCircleTypes.concat([e.name]); + } else { + let values = e.items.map(i=>e.name+"/"+i.name); + let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values; + valuesCircleTypes = valuesCircleTypes.concat(values); + namesCircleTypes = namesCircleTypes.concat(names); + } + }) + const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; @@ -36,8 +51,6 @@ /*LANG*/'circle 2': ()=>showCircleMenu(2), /*LANG*/'circle 3': ()=>showCircleMenu(3), /*LANG*/'circle 4': ()=>showCircleMenu(4), - /*LANG*/'heartrate': ()=>showHRMenu(), - /*LANG*/'steps': ()=>showStepMenu(), /*LANG*/'battery warn': { value: settings.batteryWarn, min: 10, @@ -68,96 +81,22 @@ return x + 's'; }, onchange: x => save('updateInterval', x), + }, + //TODO deprecated local icons, may disappear in future + /*LANG*/'legacy weather icons': { + value: !!settings.legacyWeatherIcons, + format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'), + onchange: x => save('legacyWeatherIcons', x), + }, + /*LANG*/'show big weather': { + value: !!settings.showBigWeather, + format: () => (settings.showBigWeather ? 'Yes' : 'No'), + onchange: x => save('showBigWeather', x), } }; E.showMenu(menu); } - function showHRMenu() { - let menu = { - '': { 'title': /*LANG*/'Heartrate' }, - /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'minimum': { - value: settings.minHR, - min: 0, - max : 250, - step: 5, - format: x => { - return x + " bpm"; - }, - onchange: x => save('minHR', x), - }, - /*LANG*/'maximum': { - value: settings.maxHR, - min: 20, - max : 250, - step: 5, - format: x => { - return x + " bpm"; - }, - onchange: x => save('maxHR', x), - }, - /*LANG*/'min. confidence': { - value: settings.confidence, - min: 0, - max : 100, - step: 10, - format: x => { - return x + "%"; - }, - onchange: x => save('confidence', x), - }, - /*LANG*/'valid period': { - value: settings.hrmValidity, - min: 10, - max : 1800, - step: 10, - format: x => { - return x + "s"; - }, - onchange: x => save('hrmValidity', x), - }, - }; - E.showMenu(menu); - } - - function showStepMenu() { - let menu = { - '': { 'title': /*LANG*/'Steps' }, - /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'goal': { - value: settings.stepGoal, - min: 1000, - max : 50000, - step: 500, - format: x => { - return x; - }, - onchange: x => save('stepGoal', x), - }, - /*LANG*/'distance goal': { - value: settings.stepDistanceGoal, - min: 1000, - max : 50000, - step: 500, - format: x => { - return x; - }, - onchange: x => save('stepDistanceGoal', x), - }, - /*LANG*/'step length': { - value: settings.stepLength, - min: 0.1, - max : 1.5, - step: 0.01, - format: x => { - return x; - }, - onchange: x => save('stepLength', x), - } - }; - E.showMenu(menu); - } function showCircleMenu(circleId) { const circleName = "circle" + circleId; const colorKey = circleName + "color"; @@ -187,6 +126,5 @@ E.showMenu(menu); } - showMainMenu(); }); diff --git a/apps/ncfrun/ChangeLog b/apps/clkinfosunrise/ChangeLog similarity index 100% rename from apps/ncfrun/ChangeLog rename to apps/clkinfosunrise/ChangeLog diff --git a/apps/clkinfosunrise/app.png b/apps/clkinfosunrise/app.png new file mode 100644 index 000000000..a1d53946d Binary files /dev/null and b/apps/clkinfosunrise/app.png differ diff --git a/apps/clkinfosunrise/clkinfo.js b/apps/clkinfosunrise/clkinfo.js new file mode 100644 index 000000000..1454a83f3 --- /dev/null +++ b/apps/clkinfosunrise/clkinfo.js @@ -0,0 +1,33 @@ +(function() { + // get today's sunlight times for lat/lon + var sunrise, sunset; + + function calculate() { + var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); + const locale = require("locale"); + var location = require("Storage").readJSON("mylocation.json",1)||{}; + location.lat = location.lat||51.5072; + location.lon = location.lon||0.1276; + location.location = location.location||"London"; + var times = SunCalc.getTimes(new Date(), location.lat, location.lon); + sunrise = locale.time(times.sunrise,1); + sunset = locale.time(times.sunset,1); + /* do we want to re-calculate this every day? Or we just assume + that 'show' will get called once a day? */ + } + + return { + name: "Bangle", + items: [ + { name : "Sunrise", + get : () => ({ text : sunrise, + img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }), + show : calculate, hide : () => {} + }, { name : "Sunset", + get : () => ({ text : sunset, + img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }), + show : calculate, hide : () => {} + } + ] + }; +}) diff --git a/apps/clkinfosunrise/metadata.json b/apps/clkinfosunrise/metadata.json new file mode 100644 index 000000000..f8b68e11f --- /dev/null +++ b/apps/clkinfosunrise/metadata.json @@ -0,0 +1,12 @@ +{ "id": "clkinfosunrise", + "name": "Sunrise Clockinfo", + "version":"0.01", + "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app", + "icon": "app.png", + "type": "clkinfo", + "tags": "clkinfo,sunrise", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"sunrise.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog index deb1072f5..cb1c6d463 100644 --- a/apps/compass/ChangeLog +++ b/apps/compass/ChangeLog @@ -5,3 +5,4 @@ 0.05: Fix bearing not clearing correctly (visible in single or double digit bearings) 0.06: Add button for force compass calibration 0.07: Use 360-heading to output the correct heading value (fix #1866) +0.08: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/compass/compass.js b/apps/compass/compass.js index dd398ffa6..9a7aec2fc 100644 --- a/apps/compass/compass.js +++ b/apps/compass/compass.js @@ -20,7 +20,7 @@ ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1); ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11); function arrow(r,c) { - r=r*Math.PI/180; + r=(360-r)*Math.PI/180; var p = Math.PI/2; ag.setColor(c).fillPoly([ AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r), @@ -34,7 +34,7 @@ var oldHeading = 0; Bangle.on('mag', function(m) { if (!Bangle.isLCDOn()) return; g.reset(); - if (isNaN(m.heading)) { + if (isNaN(m.heading)) { if (!wasUncalibrated) { g.clearRect(0,24,W,48); g.setFontAlign(0,-1).setFont("6x8"); @@ -49,7 +49,7 @@ Bangle.on('mag', function(m) { g.setFontAlign(0,0).setFont("6x8",3); var y = 36; g.clearRect(M-40,24,M+40,48); - g.drawString(Math.round(360-m.heading),M,y,true); + g.drawString(Math.round(m.heading),M,y,true); } diff --git a/apps/compass/metadata.json b/apps/compass/metadata.json index a3995a123..1a614e1f8 100644 --- a/apps/compass/metadata.json +++ b/apps/compass/metadata.json @@ -1,7 +1,7 @@ { "id": "compass", "name": "Compass", - "version": "0.07", + "version": "0.08", "description": "Simple compass that points North", "icon": "compass.png", "screenshots": [{"url":"screenshot_compass.png"}], diff --git a/apps/demoapp/metadata.json b/apps/demoapp/metadata.json index df6554ef5..2fb30f718 100644 --- a/apps/demoapp/metadata.json +++ b/apps/demoapp/metadata.json @@ -12,6 +12,5 @@ "storage": [ {"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.img","url":"app-icon.js","evaluate":true} - ], - "sortorder": -9 + ] } diff --git a/apps/dotmatrixclock/ChangeLog b/apps/dotmatrixclock/ChangeLog index 7ab9e14a9..12edf33a3 100644 --- a/apps/dotmatrixclock/ChangeLog +++ b/apps/dotmatrixclock/ChangeLog @@ -1 +1,2 @@ 0.01: Create dotmatrix clock app +0.02: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/dotmatrixclock/app.js b/apps/dotmatrixclock/app.js index ba34d4885..493a3c43f 100644 --- a/apps/dotmatrixclock/app.js +++ b/apps/dotmatrixclock/app.js @@ -186,7 +186,7 @@ function drawCompass(lastHeading) { 'NW' ]; const cps = Bangle.getCompass(); - let angle = cps.heading; + let angle = 360-cps.heading; let heading = angle? directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]: "-- "; @@ -351,4 +351,4 @@ Bangle.on('faceUp', (up) => { setSensors(1); resetDisplayTimeout(); } -}); \ No newline at end of file +}); diff --git a/apps/dotmatrixclock/metadata.json b/apps/dotmatrixclock/metadata.json index 3425dc1b2..fdfb5271f 100644 --- a/apps/dotmatrixclock/metadata.json +++ b/apps/dotmatrixclock/metadata.json @@ -1,7 +1,7 @@ { "id": "dotmatrixclock", "name": "Dotmatrix Clock", - "version": "0.01", + "version": "0.02", "description": "A clear white-on-blue dotmatrix simulated clock", "icon": "dotmatrixclock.png", "type": "clock", diff --git a/apps/dragboard/ChangeLog b/apps/dragboard/ChangeLog index 48a1ffb03..265094e87 100644 --- a/apps/dragboard/ChangeLog +++ b/apps/dragboard/ChangeLog @@ -3,3 +3,4 @@ 0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights. 0.04: Now displays the opened text string at launch. 0.05: Now scrolls text when string gets longer than screen width. +0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present. diff --git a/apps/dragboard/lib.js b/apps/dragboard/lib.js index b9b19f982..220f075d7 100644 --- a/apps/dragboard/lib.js +++ b/apps/dragboard/lib.js @@ -1,12 +1,9 @@ -//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes -//Bangle.setLCDTimeout(30); -//Bangle.setLCDPower(1); - exports.input = function(options) { options = options||{}; var text = options.text; if ("string"!=typeof text) text=""; - + + var R = Bangle.appRect; var BGCOLOR = g.theme.bg; var HLCOLOR = g.theme.fg; var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; @@ -17,35 +14,38 @@ exports.input = function(options) { var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1))); var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase(); - var ABCPADDING = (g.getWidth()-6*ABC.length)/2; + var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2; var NUM = ' 1234567890!?,.- '; var NUMHIDDEN = ' 1234567890!?,.- '; - var NUMPADDING = (g.getWidth()-6*NUM.length)/2; + var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2; var rectHeight = 40; - var delSpaceLast; function drawAbcRow() { g.clear(); + try { // Draw widgets if they are present in the current app. + if (WIDGETS) Bangle.drawWidgets(); + } catch (_) {} g.setFont(SMALLFONT); g.setColor(ABCCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); - g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight()); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); + g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h)); } function drawNumRow() { g.setFont(SMALLFONT); g.setColor(NUMCOLOR); - g.drawString(NUM, NUMPADDING, g.getHeight()/4); + g.setFontAlign(-1, -1, 0); + g.drawString(NUM, NUMPADDING, (R.y+R.h)/4); - g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3); + g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3); } function updateTopString() { - "ram" g.setColor(BGCOLOR); g.fillRect(0,4+20,176,13+20); g.setColor(0.2,0,0); @@ -54,13 +54,10 @@ exports.input = function(options) { g.setColor(0.7,0,0); g.fillRect(rectLen+5,4+20,rectLen+10,13+20); g.setColor(1,1,1); + g.setFontAlign(-1, -1, 0); g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20); } - drawAbcRow(); - drawNumRow(); - updateTopString(); - var abcHL; var abcHLPrev = -10; var numHL; @@ -68,194 +65,182 @@ exports.input = function(options) { var type = ''; var typePrev = ''; var largeCharOffset = 6; - + function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) { - "ram" + "ram"; // Small character in list g.setColor(rowColor); g.setFont(SMALLFONT); - g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor); + g.setFontAlign(-1, -1, 0); + g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor); // Large character g.setColor(BGCOLOR); - g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24); - //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. + g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24); + //g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle. // mark in the list } function showChars(char, HL, typePadding, heightDivisor) { - "ram" + "ram"; // mark in the list g.setColor(HLCOLOR); g.setFont(SMALLFONT); - if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor); + g.setFontAlign(-1, -1, 0); + if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor); // show new large character g.setFont(BIGFONT); - g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3); + g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3); g.setFont(SMALLFONT); } - + + function initDraw() { + //var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed. + drawAbcRow(); + drawNumRow(); + updateTopString(); + } + initDraw(); + //setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise. + function changeCase(abcHL) { g.setColor(BGCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.setFontAlign(-1, -1, 0); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase(); else ABC = ABC.toUpperCase(); g.setColor(ABCCOLOR); - g.drawString(ABC, ABCPADDING, g.getHeight()/2); + g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); } return new Promise((resolve,reject) => { - // Interpret touch input + // Interpret touch input Bangle.setUI({ - mode: 'custom', - back: ()=>{ - Bangle.setUI(); - g.clearRect(Bangle.appRect); - resolve(text); - }, - drag: function(event) { + mode: 'custom', + back: ()=>{ + Bangle.setUI(); + g.clearRect(Bangle.appRect); + resolve(text); + }, + drag: function(event) { + "ram"; + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // Choose character by draging along red rectangle at bottom of screen + if (event.y >= ( (R.y+R.h) - 12 )) { + // Translate x-position to character + if (event.x < ABCPADDING) { abcHL = 0; } + else if (event.x >= 176-ABCPADDING) { abcHL = 25; } + else { abcHL = Math.floor((event.x-ABCPADDING)/6); } - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // Choose character by draging along red rectangle at bottom of screen - if (event.y >= ( g.getHeight() - 12 )) { - // Translate x-position to character - if (event.x < ABCPADDING) { abcHL = 0; } - else if (event.x >= 176-ABCPADDING) { abcHL = 25; } - else { abcHL = Math.floor((event.x-ABCPADDING)/6); } + // Datastream for development purposes + //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); - // Datastream for development purposes - //print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev)); + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - - if (abcHL != abcHLPrev) { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); + if (abcHL != abcHLPrev) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2); } - // Print string at top of screen - if (event.b == 0) { - text = text + ABC.charAt(abcHL); - updateTopString(); - - // Autoswitching letter case - if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); - } - // Update previous character to current one - abcHLPrev = abcHL; - typePrev = 'abc'; - } - - - - - - - - - - // 12345678901234567890 - // Choose number or puctuation by draging on green rectangle - else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) { - // Translate x-position to character - if (event.x < NUMPADDING) { numHL = 0; } - else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } - else { numHL = Math.floor((event.x-NUMPADDING)/6); } - - // Datastream for development purposes - //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); - - // Unmark previous character and mark the current one... - // Handling switching between letters and numbers/punctuation - if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - - if (numHL != numHLPrev) { - resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); - } - // Print string at top of screen - if (event.b == 0) { - g.setColor(HLCOLOR); - // Backspace if releasing before list of numbers/punctuation - if (event.x < NUMPADDING) { - // show delete sign - showChars('del', 0, g.getWidth()/2 +6 -27 , 4); - delSpaceLast = 1; - text = text.slice(0, -1); - updateTopString(); - //print(text); - } - // Append space if releasing after list of numbers/punctuation - else if (event.x > g.getWidth()-NUMPADDING) { - //show space sign - showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); - delSpaceLast = 1; - text = text + ' '; - updateTopString(); - //print(text); - } - // Append selected number/punctuation - else { - text = text + NUMHIDDEN.charAt(numHL); + // Print string at top of screen + if (event.b == 0) { + text = text + ABC.charAt(abcHL); updateTopString(); // Autoswitching letter case - if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL); } + // Update previous character to current one + abcHLPrev = abcHL; + typePrev = 'abc'; } - // Update previous character to current one - numHLPrev = numHL; - typePrev = 'num'; - } - - - - - - - - - // Make a space or backspace by swiping right or left on screen above green rectangle - else if (event.y > 20+4) { - if (event.b == 0) { - g.setColor(HLCOLOR); - if (event.x < g.getWidth()/2) { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + // 12345678901234567890 + // Choose number or puctuation by draging on green rectangle + else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) { + // Translate x-position to character + if (event.x < NUMPADDING) { numHL = 0; } + else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; } + else { numHL = Math.floor((event.x-NUMPADDING)/6); } + + // Datastream for development purposes + //print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev)); + + // Unmark previous character and mark the current one... + // Handling switching between letters and numbers/punctuation + if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + + if (numHL != numHLPrev) { resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); - - // show delete sign - showChars('del', 0, g.getWidth()/2 +6 -27 , 4); - delSpaceLast = 1; - - // Backspace and draw string upper right corner - text = text.slice(0, -1); - updateTopString(); - if (text.length==0) changeCase(abcHL); - //print(text, 'undid'); + showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4); } - else { - resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); - resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + // Print string at top of screen + if (event.b == 0) { + g.setColor(HLCOLOR); + // Backspace if releasing before list of numbers/punctuation + if (event.x < NUMPADDING) { + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + text = text.slice(0, -1); + updateTopString(); + //print(text); + } + // Append space if releasing after list of numbers/punctuation + else if (event.x > (R.x+R.w)-NUMPADDING) { + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + text = text + ' '; + updateTopString(); + //print(text); + } + // Append selected number/punctuation + else { + text = text + NUMHIDDEN.charAt(numHL); + updateTopString(); - //show space sign - showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4); - delSpaceLast = 1; + // Autoswitching letter case + if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase(); + } + } + // Update previous character to current one + numHLPrev = numHL; + typePrev = 'num'; + } - // Append space and draw string upper right corner - text = text + NUMHIDDEN.charAt(0); - updateTopString(); - //print(text, 'made space'); + // Make a space or backspace by swiping right or left on screen above green rectangle + else if (event.y > 20+4) { + if (event.b == 0) { + g.setColor(HLCOLOR); + if (event.x < (R.x+R.w)/2) { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + // show delete sign + showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4); + delSpaceLast = 1; + + // Backspace and draw string upper right corner + text = text.slice(0, -1); + updateTopString(); + if (text.length==0) changeCase(abcHL); + //print(text, 'undid'); + } + else { + resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR); + resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR); + + //show space sign + showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4); + delSpaceLast = 1; + + // Append space and draw string upper right corner + text = text + NUMHIDDEN.charAt(0); + updateTopString(); + //print(text, 'made space'); + } } } } - } - }); -}); -/* return new Promise((resolve,reject) => { - Bangle.setUI({mode:"custom", back:()=>{ - Bangle.setUI(); - g.clearRect(Bangle.appRect); - Bangle.setUI(); - resolve(text); - }}); - }); */ - + }); + }); }; diff --git a/apps/dragboard/metadata.json b/apps/dragboard/metadata.json index f9c73ddde..64b6dbe18 100644 --- a/apps/dragboard/metadata.json +++ b/apps/dragboard/metadata.json @@ -1,6 +1,6 @@ { "id": "dragboard", "name": "Dragboard", - "version":"0.05", + "version":"0.06", "description": "A library for text input via swiping keyboard", "icon": "app.png", "type":"textinput", diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 16c550334..044b8c35f 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -14,3 +14,13 @@ 0.14: Don't move pages when doing exit swipe - Bangle 2. 0.15: 'Swipe to exit'-code is slightly altered to be more reliable - Bangle 2. 0.16: Use default Bangle formatter for booleans +0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to +clock face by timeout. +0.18: Bangle 2: Move interactions inside setUI. Replace "one click exit" with +back-functionality through setUI, adding the red back button as well. Hardware +button to exit is no longer an option. +0.19: Bangle 2: Utilize new Bangle.load(), Bangle.showClock() functions to +facilitate 'fast switching' of apps where available. +0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since +widgets would still be loaded when they weren't supposed to. + diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 8cd5790bb..a7a318c18 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -1,61 +1,59 @@ -/* Desktop launcher -* -*/ +{ // must be inside our own scope here so that when we are unloaded everything disappears -var settings = Object.assign({ - showClocks: true, - showLaunchers: true, - direct: false, - oneClickExit:false, - swipeExit: false -}, require('Storage').readJSON("dtlaunch.json", true) || {}); + /* Desktop launcher + * + */ -if( settings.oneClickExit) - setWatch(_=> load(), BTN1); - -var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{ - var a=s.readJSON(app,1); - return a && { - name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src - };}).filter( - app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + let settings = Object.assign({ + showClocks: true, + showLaunchers: true, + direct: false, + swipeExit: false, + timeOut: "Off" + }, require('Storage').readJSON("dtlaunch.json", true) || {}); -apps.sort((a,b)=>{ - var n=(0|a.sortorder)-(0|b.sortorder); - if (n) return n; // do sortorder first - if (a.nameb.name) return 1; - return 0; -}); -apps.forEach(app=>{ + let s = require("Storage"); + var apps = s.list(/\.info$/).map(app=>{ + let a=s.readJSON(app,1); + return a && { + name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src + };}).filter( + app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type)); + + apps.sort((a,b)=>{ + let n=(0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; + }); + apps.forEach(app=>{ if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area }); -var Napps = apps.length; -var Npages = Math.ceil(Napps/4); -var maxPage = Npages-1; -var selected = -1; -var oldselected = -1; -var page = 0; -const XOFF = 24; -const YOFF = 30; + let Napps = apps.length; + let Npages = Math.ceil(Napps/4); + let maxPage = Npages-1; + let selected = -1; + let oldselected = -1; + let page = 0; + const XOFF = 24; + const YOFF = 30; -function draw_icon(p,n,selected) { - var x = (n%2)*72+XOFF; - var y = n>1?72+YOFF:YOFF; + let drawIcon= function(p,n,selected) { + let x = (n%2)*72+XOFF; + let y = n>1?72+YOFF:YOFF; (selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52); g.clearRect(x+12,y+4,x+59,y+51); g.setColor(g.theme.fg); try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){} g.setFontAlign(0,-1,0).setFont("6x8",1); - var txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); - var lineY = 0; - var line = ""; - while (txt.length > 0){ - var c = txt.shift(); - + let txt = apps[p*4+n].name.replace(/([a-z])([A-Z])/g, "$1 $2").split(" "); + let lineY = 0; + let line = ""; + while (txt.length > 0){ + let c = txt.shift(); if (c.length + 1 + line.length > 13){ if (line.length > 0){ g.drawString(line.trim(),x+36,y+54+lineY*8); @@ -67,70 +65,91 @@ function draw_icon(p,n,selected) { } } g.drawString(line.trim(),x+36,y+54+lineY*8); -} + }; -function drawPage(p){ + let drawPage = function(p){ g.reset(); g.clearRect(0,24,175,175); - var O = 88+YOFF/2-12*(Npages/2); - for (var j=0;j{ + Bangle.loadWidgets(); + drawPage(0); + + let swipeListenerDt = function(dirLeftRight, dirUpDown){ + updateTimeoutToClock(); selected = 0; oldselected=-1; - if(settings.swipeExit && dirLeftRight==1) load(); + if(settings.swipeExit && dirLeftRight==1) Bangle.showClock(); if (dirUpDown==-1||dirLeftRight==-1){ - ++page; if (page>maxPage) page=0; - drawPage(page); + ++page; if (page>maxPage) page=0; + drawPage(page); } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){ - --page; if (page<0) page=maxPage; - drawPage(page); + --page; if (page<0) page=maxPage; + drawPage(page); } -}); + }; -function isTouched(p,n){ + let isTouched = function(p,n){ if (n<0 || n>3) return false; - var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; - var x2 = x1+71; var y2 = y1+81; + let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF; + let x2 = x1+71; let y2 = y1+81; return (p.x>x1 && p.y>y1 && p.x{ - var i; + let touchListenerDt = function(_,p){ + updateTimeoutToClock(); + let i; for (i=0;i<4;i++){ - if((page*4+i)=0 || settings.direct) { - if (selected!=i && !settings.direct){ - draw_icon(page,selected,false); - } else { - load(apps[page*4+i].src); - } - } - selected=i; - break; + if((page*4+i)=0 || settings.direct) { + if (selected!=i && !settings.direct){ + drawIcon(page,selected,false); + } else { + load(apps[page*4+i].src); } + } + selected=i; + break; } + } } if ((i==4 || (page*4+i)>Napps) && selected>=0) { - draw_icon(page,selected,false); - selected=-1; + drawIcon(page,selected,false); + selected=-1; } -}); + }; -Bangle.loadWidgets(); -g.clear(); -Bangle.drawWidgets(); -drawPage(0); + Bangle.setUI({ + mode : 'custom', + back : Bangle.showClock, + swipe : swipeListenerDt, + touch : touchListenerDt, + remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);} + }); + + // taken from Icon Launcher with minor alterations + let timeoutToClock; + const updateTimeoutToClock = function(){ + if (settings.timeOut!="Off"){ + let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt + if (timeoutToClock) clearTimeout(timeoutToClock); + timeoutToClock = setTimeout(Bangle.showClock,time*1000); + } + }; + updateTimeoutToClock(); + +} // end of app scope diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 36728f342..b69a1a5e6 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.16", + "version": "0.20", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/dtlaunch/settings-b2.js b/apps/dtlaunch/settings-b2.js index fac9c0fff..24959df8c 100644 --- a/apps/dtlaunch/settings-b2.js +++ b/apps/dtlaunch/settings-b2.js @@ -5,51 +5,56 @@ showClocks: true, showLaunchers: true, direct: false, - oneClickExit:false, - swipeExit: false + swipeExit: false, + timeOut: "Off" }, require('Storage').readJSON(FILE, true) || {}); function writeSettings() { require('Storage').writeJSON(FILE, settings); } + const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"]; + E.showMenu({ "" : { "title" : "Desktop launcher" }, - "< Back" : () => back(), - 'Show clocks': { + /*LANG*/"< Back" : () => back(), + /*LANG*/'Show clocks': { value: settings.showClocks, onchange: v => { settings.showClocks = v; writeSettings(); } }, - 'Show launchers': { + /*LANG*/'Show launchers': { value: settings.showLaunchers, onchange: v => { settings.showLaunchers = v; writeSettings(); } }, - 'Direct launch': { + /*LANG*/'Direct launch': { value: settings.direct, onchange: v => { settings.direct = v; writeSettings(); } }, - 'Swipe Exit': { + /*LANG*/'Swipe Exit': { value: settings.swipeExit, onchange: v => { settings.swipeExit = v; writeSettings(); } }, - 'One click exit': { - value: settings.oneClickExit, + /*LANG*/'Time Out': { // Adapted from Icon Launcher + value: timeOutChoices.indexOf(settings.timeOut), + min: 0, + max: timeOutChoices.length-1, + format: v => timeOutChoices[v], onchange: v => { - settings.oneClickExit = v; + settings.timeOut = timeOutChoices[v]; writeSettings(); } } }); -}) +}); diff --git a/apps/entonclk/ChangeLog b/apps/entonclk/ChangeLog new file mode 100644 index 000000000..62e2d0c20 --- /dev/null +++ b/apps/entonclk/ChangeLog @@ -0,0 +1 @@ +0.1: New App! \ No newline at end of file diff --git a/apps/entonclk/README.md b/apps/entonclk/README.md new file mode 100644 index 000000000..8c788c7a5 --- /dev/null +++ b/apps/entonclk/README.md @@ -0,0 +1,9 @@ +Enton - Enhanced Anton Clock + +This clock face is based on the 'Anton Clock'. + +Things I changed: + +- The main font for the time is now Audiowide +- Removed the written out day name and replaced it with steps and bpm +- Changed the date string to a (for me) more readable string \ No newline at end of file diff --git a/apps/entonclk/app-icon.js b/apps/entonclk/app-icon.js new file mode 100644 index 000000000..9993b0871 --- /dev/null +++ b/apps/entonclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkE/4A/AH4A/AH4A/AH4Aw+cikf/mQDCAAIFBAwQDBBYgXCgEDAQIABn4JBkAFBgIKDgQwFmMD+UCmcgl/zEIMzmcQmYKBmYiCAAfxC4QrBl8wBwcgkYsGC4sAiMAF4UxiIGBn8QAgMSC48wgMRiEDBAISCiYcFC48v//yC4PzgJAGiAXIiczPgPzC4JyBmf/AYQXI+KcCj8wmYFCgEjAYQ3G+cjbQIABJIMzAoUin7XIADpSEK4rWGI4MhmRJBn8j+U/d4MimUTkUzIw5dBl4UBMgIXBAgMyLYKOBmQXHiSbCDgMyl8z+UjmJ1BHgJbHCgM/IYQABAgQJBYYYA/AH4AtaQU/mTvBBozWBd44KBkUSkLnBEo8jkcvBI0/CgMiDAIXHHYIXImUzJQJHH+Y+Bn6Z/ABQA==")) \ No newline at end of file diff --git a/apps/entonclk/app.js b/apps/entonclk/app.js new file mode 100644 index 000000000..69fdea479 --- /dev/null +++ b/apps/entonclk/app.js @@ -0,0 +1,67 @@ +Graphics.prototype.setFontAudiowide = function() { + // Actual height 33 (36 - 4) + var widths = atob("CiAsESQjJSQkHyQkDA=="); + var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA"); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16)); +}; + +function getSteps() { + var steps = 0; + try{ + if (WIDGETS.wpedom !== undefined) { + steps = WIDGETS.wpedom.getSteps(); + } else if (WIDGETS.activepedom !== undefined) { + steps = WIDGETS.activepedom.getSteps(); + } else { + steps = Bangle.getHealthStatus("day").steps; + } + } catch(ex) { + // In case we failed, we can only show 0 steps. + return "?"; + } + + return Math.round(steps); +} + +{ // must be inside our own scope here so that when we are unloaded everything disappears + // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global +let drawTimeout; + + +// Actually draw the watch face +let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2; + g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets) + var date = new Date(); + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y); + var dateStr = require("locale").date(date, 1).toUpperCase(); + g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28); + g.setFontAlign(0, 0).setFont("6x8", 2); + g.drawString(getSteps(), 50, y+70); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70); + + // queue next draw + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +}; + +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontAnton; + }}); +// Load widgets +Bangle.loadWidgets(); +draw(); +setTimeout(Bangle.drawWidgets,0); +} diff --git a/apps/entonclk/app.png b/apps/entonclk/app.png new file mode 100644 index 000000000..5b634de5a Binary files /dev/null and b/apps/entonclk/app.png differ diff --git a/apps/entonclk/metadata.json b/apps/entonclk/metadata.json new file mode 100644 index 000000000..7e4947406 --- /dev/null +++ b/apps/entonclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "entonclk", + "name": "Enton Clock", + "version": "0.1", + "description": "A simple clock using the Audiowide font. ", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme":"README.md", + "storage": [ + {"name":"entonclk.app.js","url":"app.js"}, + {"name":"entonclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/entonclk/screenshot.png b/apps/entonclk/screenshot.png new file mode 100644 index 000000000..0905c6fc8 Binary files /dev/null and b/apps/entonclk/screenshot.png differ diff --git a/apps/f9lander/ChangeLog b/apps/f9lander/ChangeLog index 5560f00bc..a13f2a313 100644 --- a/apps/f9lander/ChangeLog +++ b/apps/f9lander/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Add lightning diff --git a/apps/f9lander/app.js b/apps/f9lander/app.js index 7e52104c0..2f17a5bd5 100644 --- a/apps/f9lander/app.js +++ b/apps/f9lander/app.js @@ -46,6 +46,9 @@ var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2, var exploded = false; var nExplosions = 0; var landed = false; +var lightning = 0; + +var settings = require("Storage").readJSON('f9settings.json', 1) || {}; const gravity = 4; const dt = 0.1; @@ -61,18 +64,40 @@ function flameImageGen (throttle) { function drawFalcon(x, y, throttle, angle) { g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle}); - if (throttle>0) { + if (throttle>0 || lightning>0) { var flameImg = flameImageGen(throttle); var r = falcon9.height/2 + flameImg.height/2-1; var xoffs = -Math.sin(angle)*r; var yoffs = Math.cos(angle)*r; if (Math.random()>0.7) g.setColor(1, 0.5, 0); else g.setColor(1, 1, 0); - g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle}); + if (throttle>0) g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle}); + if (lightning>1 && lightning<30) { + for (var i=0; i<6; ++i) { + var r = Math.random()*6; + var x = Math.random()*5 - xoffs; + var y = Math.random()*5 - yoffs; + g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r); + } + } } } +function drawLightning() { + var c = {x:cloudOffs+50, y:30}; + var dx = c.x-booster.x; + var dy = c.y-booster.y; + var m1 = {x:booster.x+0.6*dx+Math.random()*20, y:booster.y+0.6*dy+Math.random()*10}; + var m2 = {x:booster.x+0.4*dx+Math.random()*20, y:booster.y+0.4*dy+Math.random()*10}; + g.setColor(1, 1, 1).drawLine(c.x, c.y, m1.x, m1.y).drawLine(m1.x, m1.y, m2.x, m2.y).drawLine(m2.x, m2.y, booster.x, booster.y); +} + function drawBG() { + if (lightning==1) { + g.setBgColor(1, 1, 1).clear(); + Bangle.buzz(200); + return; + } g.setBgColor(0.2, 0.2, 1).clear(); g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1); g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10); @@ -88,6 +113,7 @@ function renderScreen(input) { drawBG(); showFuel(); drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle); + if (lightning>1 && lightning<6) drawLightning(); } function getInputs() { @@ -97,6 +123,7 @@ function getInputs() { if (t > 1) t = 1; if (t < 0) t = 0; if (booster.fuel<=0) t = 0; + if (lightning>0 && lightning<20) t = 0; return {throttle: t, angle: a}; } @@ -121,7 +148,6 @@ function gameStep() { else { var input = getInputs(); if (booster.y >= targetY) { -// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle); if (Math.abs(booster.x-droneX-droneShip.width/2)40) && Math.random()>0.98) lightning = 1; booster.x += booster.vx*dt; booster.y += booster.vy*dt; booster.vy += gravity*dt; diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json index 75c6a0164..1db777099 100644 --- a/apps/f9lander/metadata.json +++ b/apps/f9lander/metadata.json @@ -1,7 +1,7 @@ { "id": "f9lander", "name": "Falcon9 Lander", "shortName":"F9lander", - "version":"0.01", + "version":"0.02", "description": "Land a rocket booster", "icon": "f9lander.png", "screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }], @@ -10,6 +10,7 @@ "supports" : ["BANGLEJS", "BANGLEJS2"], "storage": [ {"name":"f9lander.app.js","url":"app.js"}, - {"name":"f9lander.img","url":"app-icon.js","evaluate":true} + {"name":"f9lander.img","url":"app-icon.js","evaluate":true}, + {"name":"f9lander.settings.js", "url":"settings.js"} ] } diff --git a/apps/f9lander/settings.js b/apps/f9lander/settings.js new file mode 100644 index 000000000..0f9fba302 --- /dev/null +++ b/apps/f9lander/settings.js @@ -0,0 +1,36 @@ +// This file should contain exactly one function, which shows the app's settings +/** + * @param {function} back Use back() to return to settings menu + */ +const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off"; +(function(back) { + const SETTINGS_FILE = 'f9settings.json' + // initialize with default settings... + let settings = { + 'lightning': false, + } + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const saved = storage.readJSON(SETTINGS_FILE, 1) || {} + for (const key in saved) { + settings[key] = saved[key]; + } + // creates a function to safe a specific setting, e.g. save('color')(1) + function save(key) { + return function (value) { + settings[key] = value; + storage.write(SETTINGS_FILE, settings); + } + } + const menu = { + '': { 'title': 'OpenWind' }, + '< Back': back, + 'Lightning': { + value: settings.lightning, + format: boolFormat, + onchange: save('lightning'), + } + } + E.showMenu(menu); +}) diff --git a/apps/fastload/ChangeLog b/apps/fastload/ChangeLog new file mode 100644 index 000000000..53e3c2591 --- /dev/null +++ b/apps/fastload/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Allow redirection of loads to the launcher +0.03: Allow hiding the fastloading info screen diff --git a/apps/fastload/README.md b/apps/fastload/README.md new file mode 100644 index 000000000..a1feedcf8 --- /dev/null +++ b/apps/fastload/README.md @@ -0,0 +1,21 @@ +# Fastload Utils + +*EXPERIMENTAL* Use this with caution. When you find something misbehaving please check if the problem actually persists when removing this app. + +This allows fast loading of all apps with two conditions: +* Loaded app contains `Bangle.loadWidgets`. This is needed to prevent problems with apps not expecting widgets to be already loaded. +* Current app can be removed completely from RAM. + +## Settings + +* Allows to redirect all loads usually loading the clock to the launcher instead +* The "Fastloading..." screen can be switched off + +## Technical infos + +This is still experimental but it uses the same mechanism as `.bootcde` does. +It checks the app to be loaded for widget use and stores the result of that and a hash of the js in a cache. + +# Creator + +[halemmerich](https://github.com/halemmerich) diff --git a/apps/fastload/boot.js b/apps/fastload/boot.js new file mode 100644 index 000000000..c9271abbf --- /dev/null +++ b/apps/fastload/boot.js @@ -0,0 +1,66 @@ +{ +const SETTINGS = require("Storage").readJSON("fastload.json") || {}; + +let loadingScreen = function(){ + g.reset(); + + let x = g.getWidth()/2; + let y = g.getHeight()/2; + g.setColor(g.theme.bg); + g.fillRect(x-49, y-19, x+49, y+19); + g.setColor(g.theme.fg); + g.drawRect(x-50, y-20, x+50, y+20); + g.setFont("6x8"); + g.setFontAlign(0,0); + g.drawString("Fastloading...", x, y); + g.flip(true); +}; + +let cache = require("Storage").readJSON("fastload.cache") || {}; + +let checkApp = function(n){ + // no widgets, no problem + if (!global.WIDGETS) return true; + let app = require("Storage").read(n); + if (cache[n] && E.CRC32(app) == cache[n].crc) + return cache[n].fast + cache[n] = {}; + cache[n].fast = app.includes("Bangle.loadWidgets"); + cache[n].crc = E.CRC32(app); + require("Storage").writeJSON("fastload.cache", cache); + return cache[n].fast; +} + +global._load = load; + +let slowload = function(n){ + global._load(n); +} + +let fastload = function(n){ + if (!n || checkApp(n)){ + // Bangle.load can call load, to prevent recursion this must be the system load + global.load = slowload; + Bangle.load(n); + // if fastloading worked, we need to set load back to this method + global.load = fastload; + } + else + slowload(n); +}; +global.load = fastload; + +Bangle.load = (o => (name) => { + if (Bangle.uiRemove && !SETTINGS.hideLoading) loadingScreen(); + if (SETTINGS.autoloadLauncher && !name){ + let orig = Bangle.load; + Bangle.load = (n)=>{ + Bangle.load = orig; + fastload(n); + } + Bangle.showLauncher(); + Bangle.load = orig; + } else + o(name); +})(Bangle.load); +} diff --git a/apps/fastload/icon.png b/apps/fastload/icon.png new file mode 100644 index 000000000..7fe9afe6e Binary files /dev/null and b/apps/fastload/icon.png differ diff --git a/apps/fastload/metadata.json b/apps/fastload/metadata.json new file mode 100644 index 000000000..15adcb7e3 --- /dev/null +++ b/apps/fastload/metadata.json @@ -0,0 +1,16 @@ +{ "id": "fastload", + "name": "Fastload Utils", + "shortName" : "Fastload Utils", + "version": "0.03", + "icon": "icon.png", + "description": "Enable experimental fastloading for more apps", + "type":"bootloader", + "tags": "system", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"fastload.5.boot.js","url":"boot.js"}, + {"name":"fastload.settings.js","url":"settings.js"} + ], + "data": [{"name":"fastload.json"}] +} diff --git a/apps/fastload/settings.js b/apps/fastload/settings.js new file mode 100644 index 000000000..4904e057e --- /dev/null +++ b/apps/fastload/settings.js @@ -0,0 +1,38 @@ +(function(back) { + var FILE="fastload.json"; + var settings; + + function writeSettings(key, value) { + var s = require('Storage').readJSON(FILE, true) || {}; + s[key] = value; + require('Storage').writeJSON(FILE, s); + readSettings(); + } + + function readSettings(){ + settings = require('Storage').readJSON(FILE, true) || {}; + } + + readSettings(); + + function buildMainMenu(){ + var mainmenu = { + '': { 'title': 'Fastload', back: back }, + 'Force load to launcher': { + value: !!settings.autoloadLauncher, + onchange: v => { + writeSettings("autoloadLauncher",v); + } + }, + 'Hide "Fastloading..."': { + value: !!settings.hideLoading, + onchange: v => { + writeSettings("hideLoading",v); + } + } + }; + return mainmenu; + } + + E.showMenu(buildMainMenu()); +}) diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog index 06f84a11a..ea0b48eb9 100644 --- a/apps/fwupdate/ChangeLog +++ b/apps/fwupdate/ChangeLog @@ -5,3 +5,4 @@ 0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later Add CRC checks for common bootloaders that we know don't work 0.04: Include a precompiled bootloader for easy bootloader updates +0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 8ef117933..31eb4a256 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -3,7 +3,7 @@ -

This tool allows you to update the bootloader on Bangle.js 2 devices +

This tool allows you to update the firmware on Bangle.js 2 devices from within the App Loader.

@@ -12,27 +12,41 @@ see the Bangle.js 1 instructions

    -

    Your current firmware version is unknown and bootloader is unknown

    +

    Your current firmware version is unknown and DFU is unknown

- + + + + diff --git a/apps/medicalinfo/lib.js b/apps/medicalinfo/lib.js new file mode 100644 index 000000000..683005359 --- /dev/null +++ b/apps/medicalinfo/lib.js @@ -0,0 +1,21 @@ +const storage = require('Storage'); + +exports.load = function () { + const medicalinfo = storage.readJSON('medicalinfo.json') || { + bloodType: "", + height: "", + weight: "", + medicalAlert: [""] + }; + + // Don't return anything unexpected + const expectedMedicalinfo = [ + "bloodType", + "height", + "weight", + "medicalAlert" + ].filter(key => key in medicalinfo) + .reduce((obj, key) => (obj[key] = medicalinfo[key], obj), {}); + + return expectedMedicalinfo; +}; diff --git a/apps/medicalinfo/medicalinfo.json b/apps/medicalinfo/medicalinfo.json new file mode 100644 index 000000000..8b49725cb --- /dev/null +++ b/apps/medicalinfo/medicalinfo.json @@ -0,0 +1,6 @@ +{ + "bloodType": "", + "height": "", + "weight": "", + "medicalAlert": [ "" ] +} diff --git a/apps/medicalinfo/metadata.json b/apps/medicalinfo/metadata.json new file mode 100644 index 000000000..f1a0c145f --- /dev/null +++ b/apps/medicalinfo/metadata.json @@ -0,0 +1,20 @@ +{ "id": "medicalinfo", + "name": "Medical Information", + "version":"0.01", + "description": "Provides 'medicalinfo.json' used by various health apps, as well as a way to edit it from the App Loader", + "icon": "app.png", + "tags": "health,medical", + "type": "app", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot_light.png"}], + "interface": "interface.html", + "storage": [ + {"name":"medicalinfo.app.js","url":"app.js"}, + {"name":"medicalinfo.img","url":"app-icon.js","evaluate":true}, + {"name":"medicalinfo","url":"lib.js"} + ], + "data": [ + {"name":"medicalinfo.json","url":"medicalinfo.json"} + ] +} diff --git a/apps/medicalinfo/screenshot_light.png b/apps/medicalinfo/screenshot_light.png new file mode 100644 index 000000000..42970f9fc Binary files /dev/null and b/apps/medicalinfo/screenshot_light.png differ diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog new file mode 100644 index 000000000..854979d18 --- /dev/null +++ b/apps/messagegui/ChangeLog @@ -0,0 +1,81 @@ +0.01: New App! +0.02: Add 'messages' library +0.03: Fixes for Bangle.js 1 +0.04: Add require("messages").clearAll() +0.05: Handling of message actions (ok/clear) +0.06: New messages now go at the start (fix #898) + Answering true/false now exits the messages app if no new messages + Back now marks a message as read + Clicking top-left opens a menu which allows you to delete a message or mark unread +0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909) +0.08: Fix rendering of long messages (fix #969) + buzz on new message (fix #999) +0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) + Fix phone icon (#1014) +0.10: Respect the 'new' attribute if it was set from iOS integrations +0.11: Open app when touching the widget (Bangle.js 2 only) +0.12: Extra app-specific notification icons + New animated notification icon (instead of large blinking 'MESSAGES') + Added screenshots +0.13: Add /*LANG*/ comments for internationalisation + Add 'Delete All' option to message options + Now update correctly when 'require("messages").clearAll()' is called +0.14: Hide widget when all unread notifications are dismissed from phone +0.15: Don't buzz when Quiet Mode is active +0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) +0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font +0.18: Use app-specific icon colors + Spread message action buttons out + Back button now goes back to list of messages + If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) +0.19: Use a larger font for message text if it'll fit +0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) +0.21: Improve list readability on dark theme +0.22: Add Home Assistant icon + Allow repeat to be switched Off, so there is no buzzing repetition. + Also gave the widget a pixel more room to the right +0.23: Change message colors to match current theme instead of using green + Now attempt to use Large/Big/Medium fonts, and allow minimum font size to be configured +0.24: Remove left-over debug statement +0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550) +0.26: Setting to auto-open music +0.27: Add 'mark all read' option to popup menu (fix #1624) +0.28: Option to auto-unlock the watch when a new message arrives +0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) +0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) +0.31: Option to disable icon flashing +0.32: Added an option to allow quiet mode to override message auto-open +0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going +0.34: Don't buzz for 'map' update messages +0.35: Reset graphics colors before rendering a message (possibly fix #1752) +0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) +0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items +0.38: Add telegram foss handling +0.39: Set default color for message icons according to theme +0.40: Use default Bangle formatter for booleans +0.41: Add notification icons in the widget +0.42: Fix messages ignoring "Vibrate: Off" setting +0.43: Add new Icons (Airbnb, warnwetter) +0.44: Separate buzz pattern for incoming calls +0.45: Added new app colors and icons +0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off + Fix message removal from widget bar (previously caused exception as .hide has been removed) +0.47: Add new Icons (Nextbike, Mattermost, etc.) +0.48: When getting new message from the clock, only buzz once the messages app is loaded +0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally +0.50: Add `getMessages` and `status` functions to library + Option to disable auto-open of messages + Option to make message icons monochrome (not colored) + messages widget buzz now returns a promise +0.51: Emit "message events" + Setting to hide widget + Add custom event handlers to prevent default app form loading + Move WIDGETS.messages.buzz() to require("messages").buzz() +0.52: Fix require("messages").buzz() regression + Fix background color in messages list after one unread message is shown +0.53: Messages now uses Bangle.load() to load messages app faster (if possible) +0.54: Move icons out to messageicons module +0.55: Rename to messagegui, move global message handling library to message module + Move widget to widmessage +0.56: Fix handling of music messages +0.57: Fix "unread Timeout" = off (previously defaulted to 60s) diff --git a/apps/messagegui/README.md b/apps/messagegui/README.md new file mode 100644 index 000000000..699588e1b --- /dev/null +++ b/apps/messagegui/README.md @@ -0,0 +1,68 @@ +# Messages app + +Default app to handle the display of messages and message notifications. It allows +them to be listed, viewed, and responded to. +It is installed automatically if you install `Android Integration` or `iOS Integration`. + +It is a replacement for the old `notify`/`gadgetbridge` apps. + + +## Settings + +You can change settings by going to the global `Settings` app, then `App Settings` +and `Messages`: + +* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received +* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received +* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds +* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and +buzz every `Repeat` seconds. This is how long we continue to do that. +* `Unread Timer` - When a new message is received when showing the clock we go into the Messages app. +If there is no user input for this amount of time then the app will exit and return +to the clock where a ringing bell will be shown in the Widget bar. +* `Min Font` - The minimum font size used when displaying messages on the screen. A bigger font +is chosen if there isn't much message text, but this specifies the smallest the font should get before +it starts getting clipped. +* `Auto-Open Music` - Should the app automatically open when the phone starts playing music? +* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app? + +## New Messages + +When a new message is received: + +* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. +* If you're in a clock, the Messages app will automatically start and show the message + +When a message is shown, you'll see a screen showing the message title and text. + +* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. +* The top-left icon shows more options, for instance deleting the message of marking unread +* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) +* If shown, the 'tick' button: + * **Android** opens the notification on the phone + * **iOS** responds positively to the notification (accept call/etc) +* If shown, the 'cross' button: + * **Android** dismisses the notification on the phone + * **iOS** responds negatively to the notification (dismiss call/etc) + +## Images +_1. Screenshot of a notification_ + +![](screenshot.png) + + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app + +## Creator + +Gordon Williams + +## Contributors + +[Jeroen Peters](https://github.com/jeroenpeters1986) + +## Attributions + +Icons used in this app are from https://icons8.com diff --git a/apps/messages/app-icon.js b/apps/messagegui/app-icon.js similarity index 100% rename from apps/messages/app-icon.js rename to apps/messagegui/app-icon.js diff --git a/apps/messages/app-newmessage.js b/apps/messagegui/app-newmessage.js similarity index 50% rename from apps/messages/app-newmessage.js rename to apps/messagegui/app-newmessage.js index 328927c70..73d9a79c1 100644 --- a/apps/messages/app-newmessage.js +++ b/apps/messagegui/app-newmessage.js @@ -1,5 +1,5 @@ /* Called when we have a new message when we're in the clock... -BUZZ_ON_NEW_MESSAGE is set so when messages.app.js loads it knows +BUZZ_ON_NEW_MESSAGE is set so when messagegui.app.js loads it knows that it should buzz */ global.BUZZ_ON_NEW_MESSAGE = true; -eval(require("Storage").read("messages.app.js")); +eval(require("Storage").read("messagegui.app.js")); diff --git a/apps/messages/app.js b/apps/messagegui/app.js similarity index 91% rename from apps/messages/app.js rename to apps/messagegui/app.js index f6226d178..f6f7779eb 100644 --- a/apps/messages/app.js +++ b/apps/messagegui/app.js @@ -19,7 +19,6 @@ require("messages").pushMessage({"t":"add","id":1,"src":"Maps","title":"0 yd - H // call require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true}) */ - var Layout = require("Layout"); var settings = require('Storage').readJSON("messages.settings.json", true) || {}; var fontSmall = "6x8"; @@ -49,8 +48,11 @@ to the clock. */ var unreadTimeout; /// List of all our messages var MESSAGES = require("messages").getMessages(); -if (!Array.isArray(MESSAGES)) MESSAGES=[]; -var onMessagesModified = function(msg) { + +var onMessagesModified = function(type,msg) { + if (msg.handled) return; + msg.handled = true; + require("messages").apply(msg, MESSAGES); // TODO: if new, show this new one if (msg && msg.id!=="music" && msg.new && active!="map" && !((require('Storage').readJSON('setting.json', 1) || {}).quiet)) { @@ -62,9 +64,15 @@ var onMessagesModified = function(msg) { } showMessage(msg&&msg.id); }; +Bangle.on("message", onMessagesModified); + function saveMessages() { - require("Storage").writeJSON("messages.json",MESSAGES) + require("messages").write(MESSAGES.map(m => { + delete m.show; + return m; + })); } +E.on("kill", saveMessages); function showMapMessage(msg) { active = "map"; @@ -104,9 +112,11 @@ function showMapMessage(msg) { Bangle.setUI({mode:"updown", back: back}, back); // any input takes us back } -var updateLabelsInterval; +let updateLabelsInterval, + music = {artist: "", album: "", title: ""}; // defaults, so e.g. msg.title.length doesn't error function showMusicMessage(msg) { active = "music"; + msg = Object.assign(music, msg); // combine+remember "musicinfo" and "musicstate" messages openMusic = msg.state=="play"; var trackScrollOffset = 0; var artistScrollOffset = 0; @@ -316,10 +326,14 @@ function showMessage(msgid) { {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, ]}, - { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg, g.theme.fg2), pad: 3, cb:()=>{ - cancelReloadTimeout(); // don't auto-reload to clock now - showMessageSettings(msg); - }}, + { type:"btn", + src:require("messageicons").getImage(msg), + col:require("messageicons").getColor(msg, {settings:settings, default:g.theme.fg2}), + pad: 3, cb:()=>{ + cancelReloadTimeout(); // don't auto-reload to clock now + showMessageSettings(msg); + } + }, ]}, {type:"txt", font:bodyFont, label:body, fillx:1, filly:1, pad:2, cb:()=>{ // allow tapping to show a larger version @@ -351,12 +365,16 @@ function checkMessages(options) { } // we have >0 messages var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music"); + var toShow = MESSAGES.find(m=>m.show); + if (toShow) { + newMessages.unshift(toShow); + } // If we have a new message, show it - if (options.showMsgIfUnread && newMessages.length) { + if ((toShow||options.showMsgIfUnread) && newMessages.length) { showMessage(newMessages[0].id); // buzz after showMessage, so being busy during layout doesn't affect the buzz pattern if (global.BUZZ_ON_NEW_MESSAGE) { - // this is set if we entered the messages app by loading `messages.new.js` + // this is set if we entered the messages app by loading `messagegui.new.js` // ... but only buzz the first time we view a new message global.BUZZ_ON_NEW_MESSAGE = false; // messages.buzz respects quiet mode - no need to check here @@ -382,14 +400,15 @@ function checkMessages(options) { g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); if (!msg) return; var x = r.x+2, title = msg.title, body = msg.body; - var img = require("messages").getMessageImage(msg); + var img = require("messageicons").getImage(msg); if (msg.id=="music") { title = msg.artist || /*LANG*/"Music"; body = msg.track; } if (img) { - var fg = g.getColor(); - g.setColor(require("messages").getMessageImageCol(msg,fg)).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering + var fg = g.getColor(), + col = require("messageicons").getColor(msg, {settings:settings, default:fg}); + g.setColor(col).drawImage(img, x+24, r.y+24, {rotate:0}) // force centering .setColor(fg); // only color the icon x += 50; } @@ -423,13 +442,13 @@ function cancelReloadTimeout() { g.clear(); Bangle.loadWidgets(); +require("messages").toggleWidget(false); Bangle.drawWidgets(); setTimeout(() => { - var unreadTimeoutMillis = (settings.unreadTimeout || 60) * 1000; - if (unreadTimeoutMillis) { - unreadTimeout = setTimeout(load, unreadTimeoutMillis); - } + if (!isFinite(settings.unreadTimeout)) settings.unreadTimeout=60; + if (settings.unreadTimeout) + unreadTimeout = setTimeout(load, settings.unreadTimeout*1000); // only openMusic on launch if music is new var newMusic = MESSAGES.some(m => m.id === "music" && m.new); checkMessages({ clockIfNoMsg: 0, clockIfAllRead: 0, showMsgIfUnread: 1, openMusic: newMusic && settings.openMusic }); diff --git a/apps/messagegui/app.png b/apps/messagegui/app.png new file mode 100644 index 000000000..c9177692e Binary files /dev/null and b/apps/messagegui/app.png differ diff --git a/apps/messagegui/boot.js b/apps/messagegui/boot.js new file mode 100644 index 000000000..4592f825d --- /dev/null +++ b/apps/messagegui/boot.js @@ -0,0 +1,3 @@ +(function() { + Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg)); +})(); \ No newline at end of file diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js new file mode 100644 index 000000000..e45e6e5a0 --- /dev/null +++ b/apps/messagegui/lib.js @@ -0,0 +1,60 @@ +/** + * Listener set up in boot.js, calls into here to keep boot.js short + */ +exports.listener = function(type, msg) { + // Default handler: Launch the GUI for all unhandled messages (except music if disabled in settings) + if (msg.handled || (global.__FILE__ && __FILE__.startsWith('messagegui.'))) return; // already handled or app open + + // if no new messages now, make sure we don't load the messages app + if (exports.messageTimeout && !msg.new && require("messages").status(msg) !== "new") { + clearTimeout(exports.messageTimeout); + delete exports.messageTimeout; + } + if (msg.t==="remove") return; + + const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {}; + let loadMessages = (Bangle.CLOCK || event.important); + if (type==="music") { + if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true; + else return; + } + require("messages").save(msg); + msg.handled = true; + if ((msg.t!=="add" || !msg.new) && (type!=="music")) { // music always has t:"modify" + return; + } + + const quiet = (require("Storage").readJSON("setting.json", 1) || {}).quiet; + const unlockWatch = appSettings.unlockWatch; + // don't auto-open messages in quiet mode if quietNoAutOpn is true + if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) + loadMessages = false; + + // after a delay load the app, to ensure we have all the messages + if (exports.messageTimeout) clearTimeout(exports.messageTimeout); + exports.messageTimeout = setTimeout(function() { + delete exports.messageTimeout; + if (type!=="music") { + if (!loadMessages) return require("messages").buzz(msg.src); // no opening the app, just buzz + if (!quiet && unlockWatch) { + Bangle.setLocked(false); + Bangle.setLCDPower(1); // turn screen on + } + } + exports.open(msg); + }, 500); +}; + +/** + * Launch GUI app with given message + * @param {object} msg + */ +exports.open = function(msg) { + if (msg && msg.id && !msg.show) { + // store which message to load + msg.show = 1; + require("messages").save(msg, {force: 1}); + } + + Bangle.load((msg && msg.new && msg.id!=="music") ? "messagegui.new.js" : "messagegui.app.js"); +}; diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json new file mode 100644 index 000000000..b3e70d5d4 --- /dev/null +++ b/apps/messagegui/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "messagegui", + "name": "Message UI", + "version": "0.57", + "description": "Default app to display notifications from iOS and Gadgetbridge/Android", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module" }, + "provides_modules": ["messagegui"], + "readme": "README.md", + "storage": [ + {"name":"messagegui","url":"lib.js"}, + {"name":"messagegui.app.js","url":"app.js"}, + {"name":"messagegui.new.js","url":"app-newmessage.js"}, + {"name":"messagegui.boot.js","url":"boot.js"}, + {"name":"messagegui.img","url":"app-icon.js","evaluate":true} + ], + "screenshots": [{"url":"screenshot.png"}], + "sortorder": -9 +} diff --git a/apps/messages/screenshot.png b/apps/messagegui/screenshot.png similarity index 100% rename from apps/messages/screenshot.png rename to apps/messagegui/screenshot.png diff --git a/apps/messageicons/ChangeLog b/apps/messageicons/ChangeLog new file mode 100644 index 000000000..52a4b35a7 --- /dev/null +++ b/apps/messageicons/ChangeLog @@ -0,0 +1 @@ +0.01: Moved message icons from messages into standalone library diff --git a/apps/messageicons/app.png b/apps/messageicons/app.png new file mode 100644 index 000000000..1e47a39c6 Binary files /dev/null and b/apps/messageicons/app.png differ diff --git a/apps/messageicons/icons/1password.png b/apps/messageicons/icons/1password.png new file mode 100644 index 000000000..7e28c0c93 Binary files /dev/null and b/apps/messageicons/icons/1password.png differ diff --git a/apps/messageicons/icons/airbnb.png b/apps/messageicons/icons/airbnb.png new file mode 100644 index 000000000..f691469bc Binary files /dev/null and b/apps/messageicons/icons/airbnb.png differ diff --git a/apps/messageicons/icons/alarm.png b/apps/messageicons/icons/alarm.png new file mode 100644 index 000000000..22a5b6cc4 Binary files /dev/null and b/apps/messageicons/icons/alarm.png differ diff --git a/apps/messageicons/icons/amazon.png b/apps/messageicons/icons/amazon.png new file mode 100644 index 000000000..9d446cb6a Binary files /dev/null and b/apps/messageicons/icons/amazon.png differ diff --git a/apps/messageicons/icons/bag.png b/apps/messageicons/icons/bag.png new file mode 100644 index 000000000..70dab4221 Binary files /dev/null and b/apps/messageicons/icons/bag.png differ diff --git a/apps/messageicons/icons/bank.png b/apps/messageicons/icons/bank.png new file mode 100644 index 000000000..fa1500a41 Binary files /dev/null and b/apps/messageicons/icons/bank.png differ diff --git a/apps/messageicons/icons/beeper.png b/apps/messageicons/icons/beeper.png new file mode 100644 index 000000000..bea9138ec Binary files /dev/null and b/apps/messageicons/icons/beeper.png differ diff --git a/apps/messageicons/icons/bitcoin.png b/apps/messageicons/icons/bitcoin.png new file mode 100644 index 000000000..85deecc36 Binary files /dev/null and b/apps/messageicons/icons/bitcoin.png differ diff --git a/apps/messageicons/icons/bolt.png b/apps/messageicons/icons/bolt.png new file mode 100644 index 000000000..215b9d052 Binary files /dev/null and b/apps/messageicons/icons/bolt.png differ diff --git a/apps/messageicons/icons/cafe.png b/apps/messageicons/icons/cafe.png new file mode 100644 index 000000000..26a3bb114 Binary files /dev/null and b/apps/messageicons/icons/cafe.png differ diff --git a/apps/messageicons/icons/calendar.png b/apps/messageicons/icons/calendar.png new file mode 100644 index 000000000..286952af5 Binary files /dev/null and b/apps/messageicons/icons/calendar.png differ diff --git a/apps/messageicons/icons/cart.png b/apps/messageicons/icons/cart.png new file mode 100644 index 000000000..dec53ef00 Binary files /dev/null and b/apps/messageicons/icons/cart.png differ diff --git a/apps/messageicons/icons/cashapp.png b/apps/messageicons/icons/cashapp.png new file mode 100644 index 000000000..23e897c82 Binary files /dev/null and b/apps/messageicons/icons/cashapp.png differ diff --git a/apps/messageicons/icons/cbc.png b/apps/messageicons/icons/cbc.png new file mode 100644 index 000000000..96e3ddd1b Binary files /dev/null and b/apps/messageicons/icons/cbc.png differ diff --git a/apps/messageicons/icons/chrome.png b/apps/messageicons/icons/chrome.png new file mode 100644 index 000000000..b477c57ff Binary files /dev/null and b/apps/messageicons/icons/chrome.png differ diff --git a/apps/messageicons/icons/coronavirus.png b/apps/messageicons/icons/coronavirus.png new file mode 100644 index 000000000..98b967954 Binary files /dev/null and b/apps/messageicons/icons/coronavirus.png differ diff --git a/apps/messageicons/icons/crave.png b/apps/messageicons/icons/crave.png new file mode 100644 index 000000000..ee6f0778a Binary files /dev/null and b/apps/messageicons/icons/crave.png differ diff --git a/apps/messageicons/icons/delivery.png b/apps/messageicons/icons/delivery.png new file mode 100644 index 000000000..78ca0e190 Binary files /dev/null and b/apps/messageicons/icons/delivery.png differ diff --git a/apps/messageicons/icons/desjardins.png b/apps/messageicons/icons/desjardins.png new file mode 100644 index 000000000..c54899aab Binary files /dev/null and b/apps/messageicons/icons/desjardins.png differ diff --git a/apps/messageicons/icons/discord.png b/apps/messageicons/icons/discord.png new file mode 100644 index 000000000..a8c4e2d39 Binary files /dev/null and b/apps/messageicons/icons/discord.png differ diff --git a/apps/messageicons/icons/dollars.png b/apps/messageicons/icons/dollars.png new file mode 100644 index 000000000..e5c1d2e68 Binary files /dev/null and b/apps/messageicons/icons/dollars.png differ diff --git a/apps/messageicons/icons/dropbox.png b/apps/messageicons/icons/dropbox.png new file mode 100644 index 000000000..ad6dd84a8 Binary files /dev/null and b/apps/messageicons/icons/dropbox.png differ diff --git a/apps/messageicons/icons/facebook messenger.png b/apps/messageicons/icons/facebook messenger.png new file mode 100644 index 000000000..286b5bc29 Binary files /dev/null and b/apps/messageicons/icons/facebook messenger.png differ diff --git a/apps/messageicons/icons/facebook.png b/apps/messageicons/icons/facebook.png new file mode 100644 index 000000000..5ba18eca3 Binary files /dev/null and b/apps/messageicons/icons/facebook.png differ diff --git a/apps/messageicons/icons/fdroid.png b/apps/messageicons/icons/fdroid.png new file mode 100644 index 000000000..4b5c6761e Binary files /dev/null and b/apps/messageicons/icons/fdroid.png differ diff --git a/apps/messageicons/icons/firefox.png b/apps/messageicons/icons/firefox.png new file mode 100644 index 000000000..2dcae9270 Binary files /dev/null and b/apps/messageicons/icons/firefox.png differ diff --git a/apps/messageicons/icons/github.png b/apps/messageicons/icons/github.png new file mode 100644 index 000000000..813cbb2c9 Binary files /dev/null and b/apps/messageicons/icons/github.png differ diff --git a/apps/messageicons/icons/gitlab.png b/apps/messageicons/icons/gitlab.png new file mode 100644 index 000000000..3e7280f59 Binary files /dev/null and b/apps/messageicons/icons/gitlab.png differ diff --git a/apps/messageicons/icons/google chat.png b/apps/messageicons/icons/google chat.png new file mode 100644 index 000000000..6d8eb7741 Binary files /dev/null and b/apps/messageicons/icons/google chat.png differ diff --git a/apps/messageicons/icons/google drive.png b/apps/messageicons/icons/google drive.png new file mode 100644 index 000000000..97da419a8 Binary files /dev/null and b/apps/messageicons/icons/google drive.png differ diff --git a/apps/messageicons/icons/google home.png b/apps/messageicons/icons/google home.png new file mode 100644 index 000000000..f6ebaa77f Binary files /dev/null and b/apps/messageicons/icons/google home.png differ diff --git a/apps/messageicons/icons/google keep.png b/apps/messageicons/icons/google keep.png new file mode 100644 index 000000000..f7d1f97c6 Binary files /dev/null and b/apps/messageicons/icons/google keep.png differ diff --git a/apps/messageicons/icons/google opinion rewards.png b/apps/messageicons/icons/google opinion rewards.png new file mode 100644 index 000000000..479ec0c5f Binary files /dev/null and b/apps/messageicons/icons/google opinion rewards.png differ diff --git a/apps/messageicons/icons/google photos.png b/apps/messageicons/icons/google photos.png new file mode 100644 index 000000000..aecf00dbe Binary files /dev/null and b/apps/messageicons/icons/google photos.png differ diff --git a/apps/messageicons/icons/google play store.png b/apps/messageicons/icons/google play store.png new file mode 100644 index 000000000..166094907 Binary files /dev/null and b/apps/messageicons/icons/google play store.png differ diff --git a/apps/messageicons/icons/google.png b/apps/messageicons/icons/google.png new file mode 100644 index 000000000..62797fefb Binary files /dev/null and b/apps/messageicons/icons/google.png differ diff --git a/apps/messageicons/icons/instagram.png b/apps/messageicons/icons/instagram.png new file mode 100644 index 000000000..9bccd20af Binary files /dev/null and b/apps/messageicons/icons/instagram.png differ diff --git a/apps/messageicons/icons/kde connect.png b/apps/messageicons/icons/kde connect.png new file mode 100644 index 000000000..f13298b1d Binary files /dev/null and b/apps/messageicons/icons/kde connect.png differ diff --git a/apps/messageicons/icons/lieferando.png b/apps/messageicons/icons/lieferando.png new file mode 100644 index 000000000..7a31bc9e1 Binary files /dev/null and b/apps/messageicons/icons/lieferando.png differ diff --git a/apps/messageicons/icons/linkedin.png b/apps/messageicons/icons/linkedin.png new file mode 100644 index 000000000..016e29ca8 Binary files /dev/null and b/apps/messageicons/icons/linkedin.png differ diff --git a/apps/messageicons/icons/mail.png b/apps/messageicons/icons/mail.png new file mode 100644 index 000000000..8c29a4895 Binary files /dev/null and b/apps/messageicons/icons/mail.png differ diff --git a/apps/messageicons/icons/map.png b/apps/messageicons/icons/map.png new file mode 100644 index 000000000..215f3f971 Binary files /dev/null and b/apps/messageicons/icons/map.png differ diff --git a/apps/messageicons/icons/mastodon.png b/apps/messageicons/icons/mastodon.png new file mode 100644 index 000000000..82fe737a3 Binary files /dev/null and b/apps/messageicons/icons/mastodon.png differ diff --git a/apps/messageicons/icons/matrix element.png b/apps/messageicons/icons/matrix element.png new file mode 100644 index 000000000..9ae89dc37 Binary files /dev/null and b/apps/messageicons/icons/matrix element.png differ diff --git a/apps/messageicons/icons/mattermost.png b/apps/messageicons/icons/mattermost.png new file mode 100644 index 000000000..2d5f168ca Binary files /dev/null and b/apps/messageicons/icons/mattermost.png differ diff --git a/apps/messageicons/icons/mcdonalds.png b/apps/messageicons/icons/mcdonalds.png new file mode 100644 index 000000000..efe4088a4 Binary files /dev/null and b/apps/messageicons/icons/mcdonalds.png differ diff --git a/apps/messageicons/icons/message.png b/apps/messageicons/icons/message.png new file mode 100644 index 000000000..a93cb3f4c Binary files /dev/null and b/apps/messageicons/icons/message.png differ diff --git a/apps/messageicons/icons/netflix.png b/apps/messageicons/icons/netflix.png new file mode 100644 index 000000000..d956a103b Binary files /dev/null and b/apps/messageicons/icons/netflix.png differ diff --git a/apps/messageicons/icons/news.png b/apps/messageicons/icons/news.png new file mode 100644 index 000000000..8e75513e9 Binary files /dev/null and b/apps/messageicons/icons/news.png differ diff --git a/apps/messageicons/icons/notification.png b/apps/messageicons/icons/notification.png new file mode 100644 index 000000000..c29a6025c Binary files /dev/null and b/apps/messageicons/icons/notification.png differ diff --git a/apps/messageicons/icons/onedrive.png b/apps/messageicons/icons/onedrive.png new file mode 100644 index 000000000..ff2b08304 Binary files /dev/null and b/apps/messageicons/icons/onedrive.png differ diff --git a/apps/messageicons/icons/outlook.png b/apps/messageicons/icons/outlook.png new file mode 100644 index 000000000..5519ccd4c Binary files /dev/null and b/apps/messageicons/icons/outlook.png differ diff --git a/apps/messageicons/icons/paypal.png b/apps/messageicons/icons/paypal.png new file mode 100644 index 000000000..cd76aae90 Binary files /dev/null and b/apps/messageicons/icons/paypal.png differ diff --git a/apps/messageicons/icons/phone.png b/apps/messageicons/icons/phone.png new file mode 100644 index 000000000..376170f7c Binary files /dev/null and b/apps/messageicons/icons/phone.png differ diff --git a/apps/messageicons/icons/playstation.png b/apps/messageicons/icons/playstation.png new file mode 100644 index 000000000..a97a38964 Binary files /dev/null and b/apps/messageicons/icons/playstation.png differ diff --git a/apps/messageicons/icons/plex.png b/apps/messageicons/icons/plex.png new file mode 100644 index 000000000..a0840b751 Binary files /dev/null and b/apps/messageicons/icons/plex.png differ diff --git a/apps/messageicons/icons/pocket.png b/apps/messageicons/icons/pocket.png new file mode 100644 index 000000000..d34f2e399 Binary files /dev/null and b/apps/messageicons/icons/pocket.png differ diff --git a/apps/messageicons/icons/podcast.png b/apps/messageicons/icons/podcast.png new file mode 100644 index 000000000..1be0f22b6 Binary files /dev/null and b/apps/messageicons/icons/podcast.png differ diff --git a/apps/messageicons/icons/pokeball.png b/apps/messageicons/icons/pokeball.png new file mode 100644 index 000000000..d023761eb Binary files /dev/null and b/apps/messageicons/icons/pokeball.png differ diff --git a/apps/messageicons/icons/protonmail.png b/apps/messageicons/icons/protonmail.png new file mode 100644 index 000000000..065607c47 Binary files /dev/null and b/apps/messageicons/icons/protonmail.png differ diff --git a/apps/messageicons/icons/protonvpn.png b/apps/messageicons/icons/protonvpn.png new file mode 100644 index 000000000..6d837a3e2 Binary files /dev/null and b/apps/messageicons/icons/protonvpn.png differ diff --git a/apps/messageicons/icons/radio.png b/apps/messageicons/icons/radio.png new file mode 100644 index 000000000..ea1cbffe1 Binary files /dev/null and b/apps/messageicons/icons/radio.png differ diff --git a/apps/messageicons/icons/reddit.png b/apps/messageicons/icons/reddit.png new file mode 100644 index 000000000..96fcce901 Binary files /dev/null and b/apps/messageicons/icons/reddit.png differ diff --git a/apps/messageicons/icons/restaurant.png b/apps/messageicons/icons/restaurant.png new file mode 100644 index 000000000..9bf1fcea9 Binary files /dev/null and b/apps/messageicons/icons/restaurant.png differ diff --git a/apps/messageicons/icons/router.png b/apps/messageicons/icons/router.png new file mode 100644 index 000000000..4446342f6 Binary files /dev/null and b/apps/messageicons/icons/router.png differ diff --git a/apps/messageicons/icons/rss.png b/apps/messageicons/icons/rss.png new file mode 100644 index 000000000..b248b70e9 Binary files /dev/null and b/apps/messageicons/icons/rss.png differ diff --git a/apps/messageicons/icons/rust.png b/apps/messageicons/icons/rust.png new file mode 100644 index 000000000..b74eb6ec4 Binary files /dev/null and b/apps/messageicons/icons/rust.png differ diff --git a/apps/messageicons/icons/security.png b/apps/messageicons/icons/security.png new file mode 100644 index 000000000..b8cc5c77e Binary files /dev/null and b/apps/messageicons/icons/security.png differ diff --git a/apps/messageicons/icons/shopping.png b/apps/messageicons/icons/shopping.png new file mode 100644 index 000000000..f966188b8 Binary files /dev/null and b/apps/messageicons/icons/shopping.png differ diff --git a/apps/messageicons/icons/signal.png b/apps/messageicons/icons/signal.png new file mode 100644 index 000000000..e8508706f Binary files /dev/null and b/apps/messageicons/icons/signal.png differ diff --git a/apps/messageicons/icons/skype.png b/apps/messageicons/icons/skype.png new file mode 100644 index 000000000..867a8feb6 Binary files /dev/null and b/apps/messageicons/icons/skype.png differ diff --git a/apps/messageicons/icons/slack.png b/apps/messageicons/icons/slack.png new file mode 100644 index 000000000..7a5a5a71c Binary files /dev/null and b/apps/messageicons/icons/slack.png differ diff --git a/apps/messageicons/icons/snapchat.png b/apps/messageicons/icons/snapchat.png new file mode 100644 index 000000000..42daddbaf Binary files /dev/null and b/apps/messageicons/icons/snapchat.png differ diff --git a/apps/messageicons/icons/steam.png b/apps/messageicons/icons/steam.png new file mode 100644 index 000000000..f6212cdfb Binary files /dev/null and b/apps/messageicons/icons/steam.png differ diff --git a/apps/messageicons/icons/syncthing.png b/apps/messageicons/icons/syncthing.png new file mode 100644 index 000000000..174384aba Binary files /dev/null and b/apps/messageicons/icons/syncthing.png differ diff --git a/apps/messageicons/icons/task.png b/apps/messageicons/icons/task.png new file mode 100644 index 000000000..c43d355c4 Binary files /dev/null and b/apps/messageicons/icons/task.png differ diff --git a/apps/messageicons/icons/taxi.png b/apps/messageicons/icons/taxi.png new file mode 100644 index 000000000..b577eef0e Binary files /dev/null and b/apps/messageicons/icons/taxi.png differ diff --git a/apps/messageicons/icons/teams.png b/apps/messageicons/icons/teams.png new file mode 100644 index 000000000..5160b007e Binary files /dev/null and b/apps/messageicons/icons/teams.png differ diff --git a/apps/messageicons/icons/telegram.png b/apps/messageicons/icons/telegram.png new file mode 100644 index 000000000..fe8051006 Binary files /dev/null and b/apps/messageicons/icons/telegram.png differ diff --git a/apps/messageicons/icons/terminal.png b/apps/messageicons/icons/terminal.png new file mode 100644 index 000000000..10a35e71f Binary files /dev/null and b/apps/messageicons/icons/terminal.png differ diff --git a/apps/messageicons/icons/thermostat.png b/apps/messageicons/icons/thermostat.png new file mode 100644 index 000000000..8e96f6241 Binary files /dev/null and b/apps/messageicons/icons/thermostat.png differ diff --git a/apps/messageicons/icons/threema.png b/apps/messageicons/icons/threema.png new file mode 100644 index 000000000..401660b5c Binary files /dev/null and b/apps/messageicons/icons/threema.png differ diff --git a/apps/messageicons/icons/tiktok.png b/apps/messageicons/icons/tiktok.png new file mode 100644 index 000000000..99afd3dd9 Binary files /dev/null and b/apps/messageicons/icons/tiktok.png differ diff --git a/apps/messageicons/icons/transit.png b/apps/messageicons/icons/transit.png new file mode 100644 index 000000000..3ec108194 Binary files /dev/null and b/apps/messageicons/icons/transit.png differ diff --git a/apps/messageicons/icons/twitch.png b/apps/messageicons/icons/twitch.png new file mode 100644 index 000000000..cd7d479c1 Binary files /dev/null and b/apps/messageicons/icons/twitch.png differ diff --git a/apps/messageicons/icons/twitter.png b/apps/messageicons/icons/twitter.png new file mode 100644 index 000000000..88df293f8 Binary files /dev/null and b/apps/messageicons/icons/twitter.png differ diff --git a/apps/messageicons/icons/videoconf.png b/apps/messageicons/icons/videoconf.png new file mode 100644 index 000000000..9b420341a Binary files /dev/null and b/apps/messageicons/icons/videoconf.png differ diff --git a/apps/messageicons/icons/vlc.png b/apps/messageicons/icons/vlc.png new file mode 100644 index 000000000..74949aded Binary files /dev/null and b/apps/messageicons/icons/vlc.png differ diff --git a/apps/messageicons/icons/voicemail.png b/apps/messageicons/icons/voicemail.png new file mode 100644 index 000000000..2c1972a56 Binary files /dev/null and b/apps/messageicons/icons/voicemail.png differ diff --git a/apps/messageicons/icons/wallet.png b/apps/messageicons/icons/wallet.png new file mode 100644 index 000000000..536cae2ba Binary files /dev/null and b/apps/messageicons/icons/wallet.png differ diff --git a/apps/messageicons/icons/warning.png b/apps/messageicons/icons/warning.png new file mode 100644 index 000000000..59080713f Binary files /dev/null and b/apps/messageicons/icons/warning.png differ diff --git a/apps/messageicons/icons/webhook.png b/apps/messageicons/icons/webhook.png new file mode 100644 index 000000000..7562fd759 Binary files /dev/null and b/apps/messageicons/icons/webhook.png differ diff --git a/apps/messageicons/icons/wechat.png b/apps/messageicons/icons/wechat.png new file mode 100644 index 000000000..55f4bd6a9 Binary files /dev/null and b/apps/messageicons/icons/wechat.png differ diff --git a/apps/messageicons/icons/whatsapp.png b/apps/messageicons/icons/whatsapp.png new file mode 100644 index 000000000..d6d89bc0c Binary files /dev/null and b/apps/messageicons/icons/whatsapp.png differ diff --git a/apps/messageicons/icons/xbox.png b/apps/messageicons/icons/xbox.png new file mode 100644 index 000000000..dce76128d Binary files /dev/null and b/apps/messageicons/icons/xbox.png differ diff --git a/apps/messageicons/icons/youtube.png b/apps/messageicons/icons/youtube.png new file mode 100644 index 000000000..93e50ccad Binary files /dev/null and b/apps/messageicons/icons/youtube.png differ diff --git a/apps/messageicons/lib.js b/apps/messageicons/lib.js new file mode 100644 index 000000000..5a24ac098 --- /dev/null +++ b/apps/messageicons/lib.js @@ -0,0 +1,138 @@ +exports.getImage = function(msg) { + /* + * icons should be 24x24px or less with 1bpp colors and 'Transparency to Color' + * http://www.espruino.com/Image+Converter + */ + if (msg.img) return atob(msg.img); + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + if (s=="airbnb") return atob("GBgBAAAAADwAAH4AAMMAAIMAAYGAAQGAAwDAAwDABjxgBn5gDMMwDMMwGMMYGMMYMGYMMGYMIDwEIBgEIDwEMH4MHee4D4HwAAAA"); // icons/airbnb.png + if (s=="alarm" || s =="alarmclockreceiver") return atob("GBgBAAAAAAAAAgBABwDgHn54Of+cE8PIBwDgDhhwDBgwHBg4GBgYGBgYGBgYGA4YHAc4DAEwDgBwBwDgA8PAAf+AAH4AAAAAAAAA"); // icons/alarm.png + if (s=="amazon shopping") return atob("GBgBAAAAAP8AAf+AA//AA+fAA8PAAIPAAD/AAP/AA//AA+PAB8PAB8fAB8fgB//gA//gA/3AAPCecAAeOAAeDwH0B//kAf+AAAAA"); // icons/amazon.png + if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); + if (s=="bitwarden" || s=="1password" || s=="lastpass" || s=="dashlane") return atob("GBgBAAAAABgAAP8AA//AD4/wHg/4GA/4GA/4GA/4GA/4GA/4GA/4H/AYH/AYH/A4D/AwD/BwB/BgB/DgA/HAAfeAAP8AADwAAAAA"); // icons/security.png + if (s=="bring") return atob("GBgBAAAAAAAAAAAAAAAAAHwAAFoAAf+AA/+AA/+AA/+AA/eAA+eAA0+AAx+AA7+AA/+AA//AA/+AAf8AAAIAAAAAAAAAAAAAAAAA"); + if (s=="calendar" || s=="etar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); + if (s=="chat") return atob("GBgBAAAAAf/8A//+A//+A//+OAB+e/8+e/++e/++e/++e/++e/++e/++ef+8fAAAf//Af//Af//Af//Af/+AcAAAYAAAQAAAAAAA"); // icons/google chat.png + if (s=="chrome") return atob("GBgBAAAAAAAAAP8AA//AB+fgDwDwHgB4HAA4Pj/8OmYcO8McMYEMMYEMOMMcOGccOD4cHAw4Hgx4DxjwB//gA//AAP8AAAAAAAAA"); // icons/chrome.png + if (s=="corona-warn") return atob("GBgBAAAAAAAAABgAABgABhhgDn5wD//wA8PAA+fAB2bgBgBgPpl8Ppl8BgBgB2bgA+fAA8PAD//wDn5wBhhgABgAABgAAAAAAAAA"); // icons/coronavirus.png + if (s=="bmo" || s=="desjardins" || s=="rbc mobile" || s=="nbc" || s=="rabobank" || s=="scotiabank" || s=="td (canada)") return atob("GBgBAAAAADgAAP4AAe8AB4PAHgDwP//4P//4AAAAAAAADjjgDjjgDjjgDjjgDjjgDjjgDjjgAAAAAAAAP//4P//4AAAAAAAAAAAA"); // icons/bank.png + if (s=="discord") return atob("GBgBAAAAAAAAAAAAAAAAA4HAD//wH//4H//4P//8P//8P//8fn5+fDw+fDw+fn5+f//+f//+ff++PgB8DgBwAAAAAAAAAAAAAAAA"); // icons/discord.png + if (s=="drive") return atob("GBgBAAAAAAAAAH8AAH8AAT+AA7/AA9/AB8/gB+/gD+fwD+fwH8P4P8P8P4H8fwAAf3/+Pn/8Pv/8HP/4Df/wC//wAAAAAAAAAAAA"); // icons/google drive.png + if (s=="element") return atob("GBgBAAAAAHwAAH4AAH8AAAeAAePAB+HAD+DgHgDgPADuOADucAAOcAAOdwAcdwA8BwB4BwfwA4fgA8eAAeAAAP4AAH4AAD4AAAAA"); // icons/matrix element.png + if (s=="facebook") return atob("GBgBAAAAAAAAAH4AAf+AB//gD//wD/DwH+D4H+P4P+f8P+f8P+f8PwD8PwD8PwD8H+f4H+f4D+fwD+fwB+fgAeeAAOcAAAAAAAAA"); // icons/facebook.png + if (s=="messenger") return atob("GBgBAAAAAAAAAP8AA//AB//gD//wH//4H//4P//8P9+8P458PwB8PgD8PnH8Pfv8H//4H//4D//wB//gB//AB/8AAwAAAAAAAAAA"); // icons/facebook messenger.png + if (s=="firefox" || s=="firefox beta" || s=="firefox nightly") return atob("GBgBAAAAAAAAAAMAAAcAAAeABA/ADY/gH4P4H4H4H8H8P/H8P+D8PwD8PwD8PwD8H4H4H8P4H//4D//wB//gA//AAP8AAAAAAAAA"); // icons/firefox.png + if (s=="f-droid" || s=="neo store" || s=="aurora droid") return atob("GBgBAAAAQAACYAAGP//8H//4H//4HH44HH44H//4AAAAH//4H8P4H734H374HsN4Hvl4Hv14Hvl4HsN4H374H734H8P4D//wAAAA"); // icons/security.png + if (s=="github") return atob("GBgBAAAAAAAAAH4AAf+AB//gD//wDv9wHgB4HgB4PAA8PAA8PAA8PAA8PAA8PgB8HwD4G8P4DcPwDgPwB4PgAcOAAAAAAAAAAAAA"); // icons/github.png + if (s=="gitlab") return atob("GBgBAAAABAAgDAAwDAAwHgB4HgB4PgB8PwD8P//8f//+f//+f//+f//+f//+f//+P//8H//4D//wA//AAf+AAP8AADwAABgAAAAA"); // icons/gitlab.png + if (s=="gmx") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEJmfmd8Zuc85v847/88Z9s8fttmHIHiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + if (s=="google") return atob("GBgBAAAAAP8AA//AB//gD//gH+fAP4CAPwAAPgAAfAAAfA/+fA/+fA/+fA/+fAA+PgA+PwB8P4D8H+f4D//4B//wA//AAP8AAAAA"); // icons/google.png + if (s=="google home") return atob("GBgBAAAAABgAADwAAH4AAf4AA/zAB/vgD/fwH+f4P4H8fwD+fgB+fAA+eAA+cAA+bAA+HAA+PAA+ff++ff++ff++ff++Pf+8AAAA"); // icons/google home.png + if (s=="google play store") return atob("GBgBAAAAAAAAAH4AAP8AAMMAAMMAP//8P//8MAAMMAAMMGAMMHgMMH4MMH8MMH4MMHgMMGAMMAAMMAAMP//8H//4AAAAAAAAAAAA"); // icons/google play store.png + if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); + if (s=="instagram") return atob("GBgBAAAAD//wH//4OAAccAAOYABmYDxmYP8GYeeGYYGGY4HGYwDGYwDGY4HGYYGGYeeGYP8GYDwGYAAGcAAOOAAcH//4D//wAAAA"); // icons/instagram.png + if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); + if (s=="keep notes") return atob("GBgBAAAAAAAAH//4P//8P8P8Pzz8P378Pv98Pv98Pv98Pv98P378Pzz8P738P4H8P738P738P4GMP8OYP/+wP//gH//AAAAAAAAA"); // icons/google keep.png + if (s=="lieferando") return atob("GBgBAAAAADwAAH4AAP/gAf/wA//wB//wD//wH//4H/98Pt58ft5+Ptx8DtxwDtxwDhxwDhhwDhhwDzhwD75wD75wD75wB77gAAAA"); // icons/lieferando.png + if (s=="linkedin") return atob("GBgBAAAAf//+f//+f//+ef/+cf/+cf/+f//+f//+ccw+ccAeccAecccOcceOcceOcceOcceOcceOcceOec+ef//+f//+f//+AAAA"); // icons/linkedin.png + if (s=="maps" || s=="organic maps" || s=="osmand") return atob("GBgBAAAAAAAAAAAAAeAYD/z4H//4GMeYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGeMYH//4Hz/wGAeAAAAAAAAAAAAA"); // icons/map.png + if (s=="mastodon" || s=="fedilab" || s=="tooot" || s=="tusky") return atob("GBgBAAAAB//gD//4H//4P//8PBg8PAA8fOMeeOeeeOeeOOeeOOecOP+cOP+cP//8P//4P//4P//gHwAAH4AAD+cAB/8AAf4AAAAA"); // icons/mastodon.png + if (s=="mattermost") return atob("GBgBAAAAAPAAA+EAB4GADgOQHAeYOA+cOB+MeB+OcD+GcD+GcD+GeD8OeB4OeAAOfAAePgA8P4B8H/f4D//wB//gA//AAP8AAAAA"); // icons/mattermost.png + if (s=="n26") return atob("GBgBAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAOIAAOIAAPIAANoAANoAAM4AAMYAAMYAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAA"); + if (s=="netflix") return atob("GBgBAAAAA8PAA+PAAePAAePAAfPAAvPAA/PAAvvAAn/AA/nAA3/AA/7AA5/AA/5AA99AA8/AA89AA8+AA8eAA8eAA8fAA8PAAAAA"); // icons/netflix.png + if (s=="news" || s=="cbc news" || s=="rc info" || s=="reuters" || s=="ap news" || s=="la presse" || s=="nbc news") return atob("GBgBAAAAAAAAAAAALaW0P//8P//8P//8P//8MAAMMAAMMAAMP//8P//8MBwcMBwcMB/8MB/8MBwcMBwcP//8P//8AAAAAAAAAAAA"); // icons/news.png + if (s=="nextbike") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAACAfgDAPwDAP4HAH4N4H8f8D82GMd8CMDsDMGMDMGGGGMHOD4D8AAAAAAAAAAAAAAAAAAAAAAAA"); + if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); + if (s=="outlook mail") return atob("GBgBAAAAAAAAAP/8AP/8AP/8AJjMf/jMf//8f//8cHjMd3jMZz/+Zz/+d3jecHj+f//mf/eGf/PGAwDmAwA+A//+Af/+AAAAAAAA"); // icons/outlook.png + if (s=="paypal") return atob("GBgBAAAAA/+AA//gA//wB//wB//wB//wB//wB//wB//gD//gD//ID/+ID/wwD4BwD5/gD74AH7gAHzAAHzAAHzAAAHAAAHAAAAAA"); // icons/paypal.png + if (s=="phone") return atob("GBgBAAAAAAAAH4AAP8AAP8AAP8AAH8AAH8AAH8AAH4AADwAADwAABwAAA4AAA8HwAeP8AP/8AH/8AD/8AA/8AAP8AAB4AAAAAAAA"); // icons/phone.png + if (s=="plex") return atob("GBgBAAAAB/gAB/gAA/wAAf4AAf4AAP8AAH+AAH+AAD/AAB/gAB/gAB/gAB/gAD/AAH+AAH+AAP8AAf4AAf4AA/wAB/gAB/gAAAAA"); // icons/plex.png + if (s=="pocket") return atob("GBgBAAAAAAAAP//8f//+f//+f//+f//+f//+fP8+eH4efDw+fhh+fwD+f4H+P8P8P+f8H//4H//4D//wB//gAf+AAH4AAAAAAAAA"); // icons/pocket.png + if (s=="post & dhl") return atob("GBgBAAAAAAAAAAAAAAAAP/+Af/+AYAGAYAGAYAHwYAH4YAGMYAGGYAH+YAH+bwH+f//+ef+eGYGYH4H4DwDwAAAAAAAAAAAAAAAA"); // icons/delivery.png + if (s=="proton mail") return atob("GBgBAAAAAAAAAAAAQAACYAAGcAAOeAAePABeXgDebwHed4Pee/fefe/efh/ef//ef//ef//ef//ef//ef//eP//cAAAAAAAAAAAA"); // icons/protonmail.png + if (s=="reddit" || s=="sync pro" || s=="sync dev" || s=="boost" || s=="infinity" || s=="slide") return atob("GBgBAAAAAAAAAAYwAAX4AAh4AAgwAAgAAAgAAH4AAf+AN//sf//+fn5+PDw8HDw4Hn54H//4H//4DzzwB4HgAf+AAH4AAAAAAAAA"); // icons/reddit.png + if (s=="signal") return atob("GBgBAAAAAL0AAYGABH4gCf+QE//IB//gL//0b//2H//4X//6X//6X//6X//6H//4b//2L//0D//gL//ID/+QYH4gVYGAcL0AAAAA"); // icons/signal.png + if (s=="skype") return atob("GBgBAAAAB8AAH/8AP//AP//gf8fwfwD4fgB4fjx8fj/8Pg/8PwH8P4B8P/h8Pnx+Pjx+Hhh+HwD+D8P+B//8A//8AP/4AAPgAAAA"); // icons/skype.png + if (s=="slack") return atob("GBgBAAAAAOcAAeeAAeeAAeeAAGeAAAeAP+ecf+eef+e+f+e+AAAAAAAAfef+fef+eef+Oef8AeAAAeYAAeeAAeeAAeeAAOcAAAAA"); // icons/slack.png + if (s=="snapchat") return atob("GBgBAAAAAAAAAAAAAH4AAf+AAYGAAwDAAwDAAwDADwDwDwDwDgBwBwDgBwDgDgBwHAA4OAAcHAA4D4HwB//gAH4AAAAAAAAAAAAA"); // icons/snapchat.png + if (s=="starbucks") return atob("GBgBAAAAAAAAAAAAD//4D//8DADMDADMDADMDAD8DAD4DADADADADADADADADgHAB/+AA/8AAAAAAAAAP//wP//wAAAAAAAAAAAA"); // icons/cafe.png + if (s=="steam") return atob("GBgBAAAAAAAAAf+AA//AD//wD//wH/g4P/OcP/RcP+RcP+ReH8OeB4A+AAH+AMP8IC/8OS/8HN/4Dj/wD//wA//AAf+AAAAAAAAA"); // icons/steam.png + if (s=="teams") return atob("GBgBAAAAAAgAAD4AADcYAGM8AGNmP/dmP/48MDAYMD/+PP/+PPBmPPBmPPBmPPBmP/BmP/BmH+B+AYD4AMDAAOOAAH8AABwAAAAA"); // icons/teams.png + if (s=="telegram" || s=="telegram foss") return atob("GBgBAAAAAAAAAAAAAAAeAAB+AAP+AA/+AD/+Af9+B/z+H/n8f+P8f8f8Dw/8AB/8AB/8AB/4AAf4AAP4AAD4AABwAAAAAAAAAAAA"); // icons/telegram.png + if (s=="threema") return atob("GBgBAAAAAP8AA//AB//gD//wH8P4H9v4H734P5n8P4H8P4H8H4H4H4H4D//wD//gD//AH/8AHDwAAAAAAAAABhhgDzzwBhhgAAAA"); // icons/threema.png + if (s=="tiktok") return atob("GBgBAAAAAAAAAAcAAAcAAAeAAAfAAAfwAAf4AAf4AMd4A8cAB8cAD8cADwcAHgcAHgcAHg8ADw8AD/4AB/4AA/wAAfAAAAAAAAAA"); // icons/tiktok.png + if (s=="to do" || s=="opentasks" || s=="tasks") return atob("GBgBAAAAAHwAAf+AA//ID4GcHwA8HAB4PADwOAHgcAPGcAeOcY8Oc94OcfwOcPgOOHAcOCAcHAA4DgB4D4HwB//gAf+AAH4AAAAA"); // icons/task.png + if (s=="transit") return atob("GBgBAAAAD//wP//8P//8f//+f/j+ffA+eOA+eOMef+cefef+eOe+fecef+e+eOf+eOcefAcefA++fx/+f//+P//8P//8D//wAAAA"); // icons/transit.png + if (s=="twitch") return atob("GBgBAAAAA//8B//8DgAMHgAMPhjMPhjMPhjMPhjMPhjMPgAMPgAMPgAYPgAwP+fgP+/AP/+AP/8AP/4AAeAAAcAAAYAAAQAAAAAA"); // icons/twitch.png + if (s=="twitter") return atob("GBgBAAAAAAAAAAAAAAPAIAf8MA/4PA/8Pg/4H//4H//4P//4P//wH//wD//wD//gD//AA//AAf+AB/8AP/wAD/AAAAAAAAAAAAAA"); // icons/twitter.png + if (s=="uber" || s=="lyft") return atob("GBgBAAAAAAAAAAAAAH4AAH4AB//gB//gDgBwDAAwDAAwH//4H//4GAAYG4HYG4HYG4HYGAAYH//4H//4HAA4HAA4AAAAAAAAAAAA"); // icons/taxi.png + if (s=="vlc") return atob("GBgBAAAAABgAABgAADwAADwAAAAAAAAAAAAAAAAAAIEAAP8AAP8AAf+AAP8AAAAADAAwDAAwHAA4HwD4H//4P//8P//8P//8AAAA"); // icons/vlc.png + if (s=="warnapp") return atob("GBgBAAAAAAAAAAAAAH4AAP8AA//AA//AD//gP//gf//4f//+/+P+/8H//8n//4n/fxh/fzg+Pj88Dn44AA4AAAwAAAwAAAgAAAAA"); + if (s=="whatsapp") return atob("GBgBAAAAAP8AA//AB4HwDgB4HAA4OAAcMYAMc8AOc8AGY8AGYcAGYeAGYPOGcH/OcD/OMA+MOAAcMAA4MgBwf8Pgf//AcP8AAAAA"); // icons/whatsapp.png + if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); + if (s=="youtube" || s=="newpipe") return atob("GBgBAAAAAAAAAAAAAAAAAAAAH//4P//8P//8f//+f8/+f8P+f8D+f8D+f8P+f8/+f//+P//8P//8H//4AAAAAAAAAAAAAAAAAAAA"); // icons/youtube.png + if (s=="zoom" || s=="meet") return atob("GBgBAAAAAAAAAAAAP/+Af//Af//AcADicADmcADucAD+cAD+cAD+cAD+cAD+cAD+cADucADmcADif//Af//AP/+AAAAAAAAAAAAA"); // icons/videoconf.png + if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) + return atob("FhKBAH//+P//yf/+c//z5/+fz/z/n+f/Pz/+ef/8D///////////////////////f//4///A"); +}; + +exports.getColor = function(msg,options) { + options = options||{}; + var st = options.settings || require('Storage').readJSON("messages.settings.json", 1) || {}; + if (options.default===undefined) options.default=g.theme.fg; + if (st.iconColorMode == 'mono') return options.default; + const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); + return { + // generic colors, using B2-safe colors + // DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used + "airbnb": "#ff385c", // https://news.airbnb.com/media-assets/category/brand/ + "mail": "#ff0", + "music": "#f0f", + "phone": "#0f0", + "sms message": "#0ff", + // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) + // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) + "bibel": "#54342c", + "bring": "#455a64", + "discord": "#5865f2", // https://discord.com/branding + "etar": "#36a18b", + "facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo + "gmail": "#ea4335", + "gmx": "#1c449b", + "google": "#4285F4", + "google home": "#fbbc05", +// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background + "instagram": "#ff0069", // https://about.instagram.com/brand/gradient + "lieferando": "#ff8000", + "linkedin": "#0a66c2", // https://brand.linkedin.com/ + "messenger": "#0078ff", + "mastodon": "#563acc", // https://www.joinmastodon.org/branding + "mattermost": "#00f", + "n26": "#36a18b", + "nextbike": "#00f", + "newpipe": "#f00", + "nina": "#e57004", + "opentasks": "#409f8f", + "outlook mail": "#0078d4", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products + "paypal": "#003087", + "pocket": "#ef4154f", // https://blog.getpocket.com/press/ + "post & dhl": "#f2c101", + "reddit": "#ff4500", // https://www.redditinc.com/brand + "signal": "#3a76f0", // https://github.com/signalapp/Signal-Desktop/blob/main/images/signal-logo.svg + "skype": "#0078d4", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products + "slack": "#e51670", + "snapchat": "#ff0", + "steam": "#171a21", + "teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products + "telegram": "#0088cc", + "telegram foss": "#0088cc", + "to do": "#3999e5", + "twitch": "#9146ff", // https://brand.twitch.tv/ + "twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit + "vlc": "#ff8800", + "whatsapp": "#4fce5d", + "wordfeud": "#e7d3c7", + "youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors + }[s]||options.default; +}; diff --git a/apps/messageicons/metadata.json b/apps/messageicons/metadata.json new file mode 100644 index 000000000..eb907f893 --- /dev/null +++ b/apps/messageicons/metadata.json @@ -0,0 +1,14 @@ +{ + "id": "messageicons", + "name": "Message Icons", + "version": "0.01", + "description": "Library containing a list of icons and colors for apps", + "icon": "app.png", + "type": "module", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "provides_modules" : ["messageicons"], + "storage": [ + {"name":"messageicons","url":"lib.js"} + ] +} diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 166ff64ae..3df056d62 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1,75 +1,2 @@ -0.01: New App! -0.02: Add 'messages' library -0.03: Fixes for Bangle.js 1 -0.04: Add require("messages").clearAll() -0.05: Handling of message actions (ok/clear) -0.06: New messages now go at the start (fix #898) - Answering true/false now exits the messages app if no new messages - Back now marks a message as read - Clicking top-left opens a menu which allows you to delete a message or mark unread -0.07: Added settings menu with option to choose vibrate pattern and frequency (fix #909) -0.08: Fix rendering of long messages (fix #969) - buzz on new message (fix #999) -0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) - Fix phone icon (#1014) -0.10: Respect the 'new' attribute if it was set from iOS integrations -0.11: Open app when touching the widget (Bangle.js 2 only) -0.12: Extra app-specific notification icons - New animated notification icon (instead of large blinking 'MESSAGES') - Added screenshots -0.13: Add /*LANG*/ comments for internationalisation - Add 'Delete All' option to message options - Now update correctly when 'require("messages").clearAll()' is called -0.14: Hide widget when all unread notifications are dismissed from phone -0.15: Don't buzz when Quiet Mode is active -0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147) -0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font -0.18: Use app-specific icon colors - Spread message action buttons out - Back button now goes back to list of messages - If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267) -0.19: Use a larger font for message text if it'll fit -0.20: Allow tapping on the body to show a scrollable view of the message and title in a bigger font (fix #1405, #1031) -0.21: Improve list readability on dark theme -0.22: Add Home Assistant icon - Allow repeat to be switched Off, so there is no buzzing repetition. - Also gave the widget a pixel more room to the right -0.23: Change message colors to match current theme instead of using green - Now attempt to use Large/Big/Medium fonts, and allow minimum font size to be configured -0.24: Remove left-over debug statement -0.25: Fix widget memory usage issues if message received and watch repeatedly calls Bangle.drawWidgets (fix #1550) -0.26: Setting to auto-open music -0.27: Add 'mark all read' option to popup menu (fix #1624) -0.28: Option to auto-unlock the watch when a new message arrives -0.29: Fix message list overwrites on Bangle.js 1 (fix #1642) -0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel) -0.31: Option to disable icon flashing -0.32: Added an option to allow quiet mode to override message auto-open -0.33: Timeout from the message list screen if the message being displayed is removed and there is a timer going -0.34: Don't buzz for 'map' update messages -0.35: Reset graphics colors before rendering a message (possibly fix #1752) -0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) -0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items -0.38: Add telegram foss handling -0.39: Set default color for message icons according to theme -0.40: Use default Bangle formatter for booleans -0.41: Add notification icons in the widget -0.42: Fix messages ignoring "Vibrate: Off" setting -0.43: Add new Icons (Airbnb, warnwetter) -0.44: Separate buzz pattern for incoming calls -0.45: Added new app colors and icons -0.46: Add 'Vibrate Timer' option to set how long to vibrate for, and fix Repeat:off - Fix message removal from widget bar (previously caused exception as .hide has been removed) -0.47: Add new Icons (Nextbike, Mattermost, etc.) -0.48: When getting new message from the clock, only buzz once the messages app is loaded -0.49: Change messages icon (to fit within 24px) and ensure widget renders icons centrally -0.50: Add `getMessages` and `status` functions to library - Option to disable auto-open of messages - Option to make message icons monochrome (not colored) - messages widget buzz now returns a promise -0.51: Emit "message events" - Setting to hide widget - Add custom event handlers to prevent default app form loading - Move WIDGETS.messages.buzz() to require("messages").buzz() -0.52: Fix require("messages").buzz() regression - Fix background color in messages list after one unread message is shown +0.55: Moved messages library into standalone library +0.56: Fix handling of music messages diff --git a/apps/messages/README.md b/apps/messages/README.md index 72a989146..83524d7c8 100644 --- a/apps/messages/README.md +++ b/apps/messages/README.md @@ -1,62 +1,26 @@ -# Messages app +# Messages library -This app handles the display of messages and message notifications. It stores -a list of currently received messages and allows them to be listed, viewed, -and responded to. +This library handles the passing of messages. It can storess a list of messages +and allows them to be retrieved by other apps. -It is a replacement for the old `notify`/`gadgetbridge` apps. +## Example -## Settings +Assuming you are using GadgetBridge and "overlay notifications": -You can change settings by going to the global `Settings` app, then `App Settings` -and `Messages`: - -* `Vibrate` - This is the pattern of buzzes that should be made when a new message is received -* `Vibrate for calls` - This is the pattern of buzzes that should be made when an incoming call is received -* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds -* `Vibrate Timer` - When a new message is received when in a non-clock app, we display the message icon and -buzz every `Repeat` seconds. This is how long we continue to do that. -* `Unread Timer` - When a new message is received when showing the clock we go into the Messages app. -If there is no user input for this amount of time then the app will exit and return -to the clock where a ringing bell will be shown in the Widget bar. -* `Min Font` - The minimum font size used when displaying messages on the screen. A bigger font -is chosen if there isn't much message text, but this specifies the smallest the font should get before -it starts getting clipped. -* `Auto-Open Music` - Should the app automatically open when the phone starts playing music? -* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app? -* `Flash Icon` - Toggle flashing of the widget icon. -* `Widget messages` - The maximum amount of message icons to show on the widget, or `Hide` the widget completely. - -## New Messages - -When a new message is received: - -* If you're in an app, the Bangle will buzz and a message icon appears in the Widget bar. You can tap this icon to view the message. -* If you're in a clock, the Messages app will automatically start and show the message - -When a message is shown, you'll see a screen showing the message title and text. - -* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read. -* The top-left icon shows more options, for instance deleting the message of marking unread -* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`) -* If shown, the 'tick' button: - * **Android** opens the notification on the phone - * **iOS** responds positively to the notification (accept call/etc) -* If shown, the 'cross' button: - * **Android** dismisses the notification on the phone - * **iOS** responds negatively to the notification (dismiss call/etc) - -## Images -_1. Screenshot of a notification_ - -![](screenshot.png) - -_2. What the notify icon looks like (it's touchable on Bangle.js2!)_ - -![](screenshot-notify.gif) +1. Gadgetbridge sends an event to your watch for an incoming message +2. The `android` app parses the message, and calls `require("messages").pushMessage({/** the message */})` +3. `require("messages")` calls `Bangle.emit("message", "text", {/** the message */})` +4. Overlay Notifications shows the message in an overlay, and marks it as `handled` +5. The default UI app (Message UI, `messagegui`) sees the event is marked as `handled`, so does nothing. +6. The default widget (`widmessages`) does nothing with `handled`, and shows a notification icon. +7. You tap the notification, in order to open the full GUI: Overlay Notifications + calls `require("messages").openGUI({/** the message */})` +8. `openGUI` calls `require("messagegui").open(/** copy of the message */)`. +9. The `messagegui` library loads the Message UI app. -## Events (for app/widget developers) + +## Events When a new message arrives, a `"message"` event is emitted, you can listen for it like this: @@ -64,8 +28,7 @@ it like this: ```js myMessageListener = Bangle.on("message", (type, message)=>{ if (message.handled) return; // another app already handled this message - // is one of "text", "call", "alarm", "map", "music", or "clearAll" - if (type === "clearAll") return; // not a message + // is one of "text", "call", "alarm", "map", or "music" // see `messages/lib.js` for possible formats // message.t could be "add", "modify" or "remove" E.showMessage(`${message.title}\n${message.body}`, `${message.t} ${type} message`); @@ -74,10 +37,23 @@ myMessageListener = Bangle.on("message", (type, message)=>{ }); ``` +Apps can launch the full GUI by calling `require("messages").openGUI()`, if you +want to write your own GUI, it should include boot code that listens for +`"messageGUI"` events: + +```js +Bangle.on("messageGUI", message=>{ + if (message.handled) return; // another app already opened it's GUI + message.handled = true; // prevent other apps form launching + Bangle.load("my_message_gui.app.js"); +}) + +``` + ## Requests -Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=[messages]%20library ## Creator diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 0188342ee..e5c81f3fd 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -1,10 +1,15 @@ -function openMusic() { - // only read settings file for first music message - if ("undefined"==typeof exports._openMusic) { - exports._openMusic = !!((require('Storage').readJSON("messages.settings.json", true) || {}).openMusic); - } - return exports._openMusic; +exports.music = {}; +/** + * Emit "message" event with appropriate type from Bangle + * @param {object} msg + */ +function emit(msg) { + let type = "text"; + if (["call", "music", "map"].includes(msg.id)) type = msg.id; + if (msg.src && msg.src.toLowerCase().startsWith("alarm")) type = "alarm"; + Bangle.emit("message", type, msg); } + /* Push a new message onto messages queue, event is: {t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool} {t:"add",id:int, id:"music", state, artist, track, etc} // add new @@ -12,125 +17,178 @@ function openMusic() { {t:"modify",id:int, title:string} // modified */ exports.pushMessage = function(event) { - var messages = exports.getMessages(); // now modify/delete as appropriate - var mIdx = messages.findIndex(m=>m.id==event.id); - if (event.t=="remove") { - if (mIdx>=0) messages.splice(mIdx, 1); // remove item - mIdx=-1; + if (event.t==="remove") { + if (event.id==="music") exports.music = {}; } else { // add/modify - if (event.t=="add"){ - if(event.new === undefined ) { // If 'new' has not been set yet, set it - event.new=true; // Assume it should be new - } + if (event.t==="add") { + if (event.new===undefined) event.new = true; // Assume it should be new + } else if (event.t==="modify") { + const old = exports.getMessages().find(m => m.id===event.id); + if (old) event = Object.assign(old, event); } - if (mIdx<0) { - mIdx=0; - messages.unshift(event); // add new messages to the beginning - } - else Object.assign(messages[mIdx], event); - if (event.id=="music" && messages[mIdx].state=="play") { - messages[mIdx].new = true; // new track, or playback (re)started - type = 'music'; + + // combine musicinfo and musicstate events + if (event.id==="music") { + if (event.state==="play") event.new = true; // new track, or playback (re)started + event = Object.assign(exports.music, event); } } - require("Storage").writeJSON("messages.json",messages); - var message = mIdx<0 ? {id:event.id, t:'remove'} : messages[mIdx]; - // if in app, process immediately - if ("undefined"!=typeof MESSAGES) return onMessagesModified(message); - // emit message event - var type = 'text'; - if (["call", "music", "map"].includes(message.id)) type = message.id; - if (message.src && message.src.toLowerCase().startsWith("alarm")) type = "alarm"; - Bangle.emit("message", type, message); - // update the widget icons shown - if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages,true); - var handleMessage = () => { - // if no new messages now, make sure we don't load the messages app - if (event.t=="remove" && exports.messageTimeout && !messages.some(m => m.new)) { - clearTimeout(exports.messageTimeout); - delete exports.messageTimeout; - } - // ok, saved now - if (event.id=="music" && Bangle.CLOCK && messages[mIdx].new && openMusic()) { - // just load the app to display music: no buzzing - load("messages.app.js"); - } else if (event.t!="add") { - // we only care if it's new - return; - } else if (event.new==false) { - return; - } - // otherwise load messages/show widget - var loadMessages = Bangle.CLOCK || event.important; - var quiet = (require('Storage').readJSON('setting.json', 1) || {}).quiet; - var appSettings = require('Storage').readJSON('messages.settings.json', 1) || {}; - var unlockWatch = appSettings.unlockWatch; - // don't auto-open messages in quiet mode if quietNoAutOpn is true - if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn) - loadMessages = false; - delete appSettings; - // after a delay load the app, to ensure we have all the messages - if (exports.messageTimeout) clearTimeout(exports.messageTimeout); - exports.messageTimeout = setTimeout(function() { - exports.messageTimeout = undefined; - // if we're in a clock or it's important, go straight to messages app - if (loadMessages) { - if (!quiet && unlockWatch) { - Bangle.setLocked(false); - Bangle.setLCDPower(1); // turn screen on - } - // we will buzz when we enter the messages app - return load("messages.new.js"); - } - if (global.WIDGETS && WIDGETS.messages) WIDGETS.messages.update(messages); - exports.buzz(message.src); - }, 500); - }; - setTimeout(()=>{ - if (!message.handled) handleMessage(); - },0); -} -/// Remove all messages + // reset state (just in case) + delete event.handled; + delete event.saved; + emit(event); +}; + +/** + * Save a single message to flash + * Also sets msg.saved=true + * + * @param {object} msg + * @param {object} [options={}] Options: + * {boolean} [force=false] Force save even if msg.saved is already set + */ +exports.save = function(msg, options) { + if (!options) options = {}; + if (msg.saved && !options.force) return; //already saved + let messages = exports.getMessages(); + exports.apply(msg, messages); + exports.write(messages); + msg.saved = true; +}; + +/** + * Apply incoming event to array of messages + * + * @param {object} event Event to apply + * @param {array} messages Array of messages, *will be modified in-place* + * @return {array} Modified messages array + */ +exports.apply = function(event, messages) { + if (!event || !event.id) return messages; + const mIdx = messages.findIndex(m => m.id===event.id); + if (event.t==="remove") { + if (mIdx<0) return messages; // already gone -> nothing to do + messages.splice(mIdx, 1); + } else if (event.t==="add") { + if (mIdx>=0) messages.splice(mIdx, 1); // duplicate ID! erase previous version + messages.unshift(event); + } else if (event.t==="modify") { + if (mIdx>=0) messages[mIdx] = Object.assign(messages[mIdx], event); + else messages.unshift(event); + } + return messages; +}; + +/** + * Accept a call (or other acceptable event) + * @param {object} msg + */ +exports.accept = function(msg) { + if (msg.positive) Bangle.messageResponse(msg, true); +}; + +/** + * Dismiss a message (if applicable), and erase it from flash + * Emits a "message" event with t="remove", only if message existed + * + * @param {object} msg + */ +exports.dismiss = function(msg) { + if (msg.negative) Bangle.messageResponse(msg, false); + let messages = exports.getMessages(); + const mIdx = messages.findIndex(m=>m.id===msg.id); + if (mIdx<0) return; + messages.splice(mIdx, 1); + exports.write(messages); + if (msg.t==="remove") return; // already removed, don't re-emit + msg.t = "remove"; + emit(msg); // emit t="remove", so e.g. widgets know to update +}; + +/** + * Emit a "type=openGUI" event, to open GUI app + * + * @param {object} [msg={}] Message the app should show + */ +exports.openGUI = function(msg) { + if (!require("Storage").read("messagegui")) return; // "messagegui" module is missing! + // Mark the event as unhandled for GUI, but leave passed arguments intact + let copy = Object.assign({}, msg); + delete copy.handled; + require("messagegui").open(copy); +}; + +/** + * Show/hide the messages widget + * + * @param {boolean} show + */ +exports.toggleWidget = function(show) { + if (!require("Storage").read("messagewidget")) return; // "messagewidget" module is missing! + if (show) require("messagewidget").show(); + else require("messagewidget").hide(); +}; + +/** + * Replace all stored messages + * @param {array} messages Messages to save + */ +exports.write = function(messages) { + require("Storage").writeJSON("messages.json", messages.map(m => { + // we never want to save saved/handled status to file; + delete m.saved; + delete m.handled; + return m; + })); +}; +/** + * Erase all messages + */ exports.clearAll = function() { - if ("undefined"!= typeof MESSAGES) { // we're in a messages app, clear that as well - MESSAGES = []; - } - // Clear all messages - require("Storage").writeJSON("messages.json", []); - // if we have a widget, update it - if (global.WIDGETS && WIDGETS.messages) - WIDGETS.messages.update([]); - // let message listeners know - Bangle.emit("message", "clearAll", {}); // guarantee listeners an object as `message` - // clearAll cannot be marked as "handled" - // update app if in app - if ("function"== typeof onMessagesModified) onMessagesModified(); + exports.write([]); + Bangle.emit("message", "clearAll", {}); } /** + * Get saved messages + * + * Optionally pass in a message to apply to the list, this is for event handlers: + * By passing the message from the event, you can make sure the list is up-to-date, + * even if the message has not been saved (yet) + * + * Example: + * Bangle.on("message", (type, msg) => { + * console.log("All messages:", require("messages").getMessages(msg)); + * }); + * + * @param {object} [withMessage] Apply this event to messages * @returns {array} All messages */ -exports.getMessages = function() { - if ("undefined"!=typeof MESSAGES) return MESSAGES; // loaded/managed by app - return require("Storage").readJSON("messages.json",1)||[]; -} +exports.getMessages = function(withMessage) { + let messages = require("Storage").readJSON("messages.json", true); + messages = Array.isArray(messages) ? messages : []; // make sure we always return an array + if (withMessage && withMessage.id) exports.apply(withMessage, messages); + return messages; +}; /** * Check if there are any messages + * + * @param {object} [withMessage] Apply this event to messages, see getMessages * @returns {string} "new"/"old"/"none" */ - exports.status = function() { +exports.status = function(withMessage) { try { - let status= "none"; - for(const m of exports.getMessages()) { + let status = "none"; + for(const m of exports.getMessages(withMessage)) { if (["music", "map"].includes(m.id)) continue; if (m.new) return "new"; status = "old"; } return status; } catch(e) { - return "none"; // don't bother e.g. the widget with errors + return "none"; // don't bother callers with errors } }; @@ -141,24 +199,24 @@ exports.getMessages = function() { */ exports.buzz = function(msgSrc) { exports.stopBuzz(); // cancel any previous buzz timeouts - if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return Promise.resolve(); // never buzz during Quiet Mode - - var pattern; - if (msgSrc && msgSrc.toLowerCase() === "phone") { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet) return Promise.resolve(); // never buzz during Quiet Mode + const msgSettings = require("Storage").readJSON("messages.settings.json", true) || {}; + let pattern; + if (msgSrc && msgSrc.toLowerCase()==="phone") { // special vibration pattern for incoming calls - pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateCalls; + pattern = msgSettings.vibrateCalls; } else { - pattern = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate; + pattern = msgSettings.vibrate; } - if (pattern === undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here + if (pattern===undefined) { pattern = ":"; } // pattern may be "", so we can't use || ":" here if (!pattern) return Promise.resolve(); - var repeat = (require('Storage').readJSON("messages.settings.json", true) || {}).repeat; - if (repeat===undefined) repeat=4; // repeat may be zero + let repeat = msgSettings.repeat; + if (repeat===undefined) repeat = 4; // repeat may be zero if (repeat) { - exports.buzzTimeout = setTimeout(()=>require("buzz").pattern(pattern), repeat*1000); - var vibrateTimeout = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrateTimeout; - if (vibrateTimeout===undefined) vibrateTimeout=60; + exports.buzzTimeout = setTimeout(() => require("buzz").pattern(pattern), repeat*1000); + let vibrateTimeout = msgSettings.vibrateTimeout; + if (vibrateTimeout===undefined) vibrateTimeout = 60; if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000); } return require("buzz").pattern(pattern); @@ -172,107 +230,3 @@ exports.stopBuzz = function() { if (exports.stopTimeout) clearTimeout(exports.stopTimeout); delete exports.stopTimeout; }; - -exports.getMessageImage = function(msg) { - /* - * icons should be 24x24px or less with 1bpp colors and 'Transparency to Color' - * http://www.espruino.com/Image+Converter - */ - if (msg.img) return atob(msg.img); - const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); - if (s=="airbnb") return atob("GBgBAAAAAAAAAAAAADwAAH4AAGYAAMMAAIEAAYGAAYGAAzzAA2bABmZgBmZgDGYwDDwwCDwQCBgQDDwwB+fgA8PAAAAAAAAAAAAA"); - if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); - if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA"); - if (s=="bring") return atob("GBgBAAAAAAAAAAAAAAAAAHwAAFoAAf+AA/+AA/+AA/+AA/eAA+eAA0+AAx+AA7+AA/+AA//AA/+AAf8AAAIAAAAAAAAAAAAAAAAA"); - if (s=="calendar" || s=="etar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA=="); - if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA"); - if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA"); - if (s=="facebook" || s=="messenger") return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); - if (s=="gmx") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEJmfmd8Zuc85v847/88Z9s8fttmHIHiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); - if (s=="google") return atob("GBiBAAAAAAD/AAP/wAf/4A/D4B8AwDwAADwAAHgAAHgAAHAAAHAH/nAH/nAH/ngH/ngAHjwAPDwAfB8A+A/D8Af/4AP/wAD/AAAAAA=="); - if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); // 2 bit unpaletted - if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA="); - if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA=="); - if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA"); - if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44"); - if (s=="mattermost") return atob("GBgBAAAAAPAAA+EAB4MADgcYHAcYOA8MOB8OeD8GcD8GcH8GcD8HcD8HeBwHeAAOfAAOfgAePwA8P8D8H//4D//wB//gAf/AAH4A"); - if (s=="n26") return atob("GBgBAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAOIAAOIAAPIAANoAANoAAM4AAMYAAMYAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAA"); - if (s=="nextbike") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAACAfgDAPwDAP4HAH4N4H8f8D82GMd8CMDsDMGMDMGGGGMHOD4D8AAAAAAAAAAAAAAAAAAAAAAAA"); - if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA"); - if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA="); - if (s=="paypal") return atob("GBgBAAAAAAAAAAAAAf+AAf/AAf/gA//gA//gA//wA//wA//wA//wB//wB//wB//gB/+AB/gAB/gAB/gAAPgAAPgAAAAAAAAAAAAA"); - if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); - if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw"); - if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA"); - if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); - if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA=="); - if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA"); - if (s=="steam") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAfgAAwwAAvQABvQABvQADvQgDww4H/g+f8A/zwAf9gAH9AAB8AAACAAAcAAAAAAAAAAAAAAAAAA"); - if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA"); - if (s=="telegram" || s=="telegram foss") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); - if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); - if (s=="to do" || s=="opentasks") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA"); - if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA"); - if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); - if (s=="warnapp") return atob("GBgBAAAAAAAAAAAAAH4AAP8AA//AA//AD//gP//gf//4f//+/+P+/8H//8n//4n/fxh/fzg+Pj88Dn44AA4AAAwAAAwAAAgAAAAA"); - if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); - if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql"); - if (s=="youtube" || s=="newpipe") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA"); - if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); - // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) - return atob("FhKBAH//+P//yf/+c//z5/+fz/z/n+f/Pz/+ef/8D///////////////////////f//4///A"); -}; - -exports.getMessageImageCol = function(msg,def) { - let iconColorMode = (require('Storage').readJSON("messages.settings.json", 1) || {}).iconColorMode; - if (iconColorMode == 'mono') - return g.theme.fg; - const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase(); - return { - // generic colors, using B2-safe colors - // DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used - "airbnb": "#f00", - "mail": "#ff0", - "music": "#f0f", - "phone": "#0f0", - "sms message": "#0ff", - // brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos) - // all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?) - "bibel": "#54342c", - "bring": "#455a64", - "discord": "#738adb", - "etar": "#36a18b", - "facebook": "#4267b2", - "gmail": "#ea4335", - "gmx": "#1c449b", - "google": "#4285F4", - "google home": "#fbbc05", -// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background - "instagram": "#dd2a7b", - "lieferando": "#ee5c00", - "messenger": "#0078ff", - "mattermost": "#00f", - "n26": "#36a18b", - "nextbike": "#00f", - "newpipe": "#f00", - "nina": "#e57004", - "opentasks": "#409f8f", - "outlook mail": "#0072c6", - "paypal": "#003087", - "post & dhl": "#f2c101", - "signal": "#00f", - "skype": "#00aff0", - "slack": "#e51670", - "snapchat": "#ff0", - "steam": "#171a21", - "teams": "#464eb8", - "telegram": "#0088cc", - "telegram foss": "#0088cc", - "to do": "#3999e5", - "twitch": "#6441A4", - "twitter": "#1da1f2", - "whatsapp": "#4fce5d", - "wordfeud": "#e7d3c7", - "youtube": "#f00", - }[s]||(def !== undefined?def:g.theme.fg); -}; diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index a31c21e03..8bcf3da25 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,22 +1,18 @@ { "id": "messages", "name": "Messages", - "version": "0.52", - "description": "App to display notifications from iOS and Gadgetbridge/Android", + "version": "0.56", + "description": "Library to handle, load and store message events received from Android/iOS", "icon": "app.png", - "type": "app", + "type": "module", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], + "provides_modules" : ["messages"], + "dependencies" : { "messagegui":"module","messagewidget":"module" }, "readme": "README.md", "storage": [ - {"name":"messages.app.js","url":"app.js"}, - {"name":"messages.new.js","url":"app-newmessage.js"}, - {"name":"messages.settings.js","url":"settings.js"}, - {"name":"messages.img","url":"app-icon.js","evaluate":true}, - {"name":"messages.wid.js","url":"widget.js"}, - {"name":"messages","url":"lib.js"} + {"name":"messages","url":"lib.js"}, + {"name":"messages.settings.js","url":"settings.js"} ], - "data": [{"name":"messages.json"},{"name":"messages.settings.json"}], - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}], - "sortorder": -9 + "data": [{"name":"messages.json"},{"name":"messages.settings.json"}] } diff --git a/apps/messages/widget.js b/apps/messages/widget.js deleted file mode 100644 index c8d132f82..000000000 --- a/apps/messages/widget.js +++ /dev/null @@ -1,55 +0,0 @@ -(() => { -if ((require('Storage').readJSON("messages.settings.json", true) || {}).maxMessages===0) return; - -function filterMessages(msgs) { - return msgs.filter(msg => msg.new && msg.id != "music") - .map(m => m.src) // we only need this for icon/color - .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); -} - -WIDGETS["messages"]={area:"tl", width:0, draw:function(recall) { - // If we had a setTimeout queued from the last time we were called, remove it - if (WIDGETS["messages"].i) { - clearTimeout(WIDGETS["messages"].i); - delete WIDGETS["messages"].i; - } - Bangle.removeListener('touch', this.touch); - if (!this.width) return; - let settings = Object.assign({flash:true, maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); - if (recall !== true || settings.flash) { - var msgsShown = E.clip(this.msgs.length, 0, settings.maxMessages); - g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); - for(let i = 0;i < msgsShown;i++) { - const msg = this.msgs[i]; - const colors = [g.theme.bg, g.setColor(require("messages").getMessageImageCol(msg)).getColor()]; - if (settings.flash && ((Date.now()/1000)&1)) { - if (colors[1] == g.theme.fg) { - colors.reverse(); - } else { - colors[1] = g.theme.fg; - } - } - g.setColor(colors[1]).setBgColor(colors[0]); - // draw the icon, or '...' if too many messages - g.drawImage(i == (settings.maxMessages - 1) && this.msgs.length > settings.maxMessages ? atob("EASBAGGG88/zz2GG") : require("messages").getMessageImage(msg), - this.x + 12 + i * 24, this.y + 12, {rotate:0/*force centering*/}); - } - } - WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000); - if (process.env.HWVERSION>1) Bangle.on('touch', this.touch); -},update:function(rawMsgs) { - const settings = Object.assign({maxMessages:3},require('Storage').readJSON("messages.settings.json", true) || {}); - this.msgs = filterMessages(rawMsgs); - this.width = 24 * E.clip(this.msgs.length, 0, settings.maxMessages); - Bangle.drawWidgets(); -},touch:function(b,c) { - var w=WIDGETS["messages"]; - if (!w||!w.width||c.xw.x+w.width||c.yw.y+24) return; - load("messages.app.js"); -}}; - -/* We might have returned here if we were in the Messages app for a -message but then the watch was never viewed. */ -if (global.MESSAGES===undefined) - WIDGETS["messages"].update(require("messages").getMessages()); -})(); diff --git a/apps/messages_light/ChangeLog b/apps/messages_light/ChangeLog new file mode 100644 index 000000000..328e2a120 --- /dev/null +++ b/apps/messages_light/ChangeLog @@ -0,0 +1,7 @@ +1.0: New App! +1.1: fix app opening when a remove notification arrives +1.2: message_light overrides require() by sending requests to "message" to a proxy library which overrides pushMessage + settings now points to message settings + implemented use of the "messageicons" library + removed lib no longer used +1.3: icon changed \ No newline at end of file diff --git a/apps/messages_light/README.md b/apps/messages_light/README.md new file mode 100644 index 000000000..00fe39bd0 --- /dev/null +++ b/apps/messages_light/README.md @@ -0,0 +1,11 @@ +# Messages app + +This app handles the display of messages and message notifications. + +It is a GUI replacement for the `messages` apps. + + +## Creator + +Rarder44 + diff --git a/apps/messages_light/app-icon.js b/apps/messages_light/app-icon.js new file mode 100644 index 000000000..7d1da35c9 --- /dev/null +++ b/apps/messages_light/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA/4ACBIMQwhL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBYVe1QAB1YLGrSlC/YLGrYHCr4Lrr9drpLC1oLEAAN5rxKB/ILHEYV5EY4LIHYoLorRaBqoPCBYlfUoXrBYwGBrdeDIILIvXVBZFa1I+CBY/5BZIHBBwOq1ILGrXVvf//oLGq+trLLFBYVVvQxCBY9XJIQLCgILDHoVVoALHAAQLCgALHBQUAioKFqgLDEgwiDAH4AGA")) \ No newline at end of file diff --git a/apps/messages_light/app-icon.png b/apps/messages_light/app-icon.png new file mode 100644 index 000000000..c9b4b62ac Binary files /dev/null and b/apps/messages_light/app-icon.png differ diff --git a/apps/messages_light/app.png b/apps/messages_light/app.png new file mode 100644 index 000000000..1f738504d Binary files /dev/null and b/apps/messages_light/app.png differ diff --git a/apps/messages_light/full-size-app.png b/apps/messages_light/full-size-app.png new file mode 100644 index 000000000..2df7915ed Binary files /dev/null and b/apps/messages_light/full-size-app.png differ diff --git a/apps/messages_light/messages_light.app.js b/apps/messages_light/messages_light.app.js new file mode 100644 index 000000000..5d5363d38 --- /dev/null +++ b/apps/messages_light/messages_light.app.js @@ -0,0 +1,496 @@ +/* MESSAGES is a list of: + {id:int, + src, + title, + subject, + body, + sender, + tel:string, + new:true // not read yet + } +*/ + +let LOG=function(){ + //print.apply(null, arguments); +} + + + + +let settings= (()=>{ + let tmp={}; + tmp.NewEventFileName="messages_light.NewEvent.json"; + + tmp.fontSmall = "6x8"; + tmp.fontMedium = g.getFonts().includes("Vector")?"Vector:16":"6x8:2"; + tmp.fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; + tmp.fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + + + tmp.colHeadBg = g.theme.dark ? "#141":"#4f4"; + tmp.colBg = g.theme.dark ? "#000":"#fff"; + tmp.colLock = g.theme.dark ? "#ff0000":"#ff0000"; + + tmp.quiet=((require('Storage').readJSON('setting.json', 1) || {}).quiet) + + return tmp; +})(); +let EventQueue=[]; //in posizione 0, c'è quello attualmente visualizzato +let callInProgress=false; + + + + +//TODO: RICORDARSI DI FARE IL DELETE +var manageEvent = function(event) { + + event.new=true; + + + LOG("manageEvent"); + if( event.id=="call") + { + showCall(event); + return; + } + switch(event.t) + { + case "add": + EventQueue.unshift(event); + + if(!callInProgress) + showMessage(event); + break; + + case "modify": + //cerco l'evento nella lista, se lo trovo, lo modifico, altrimenti lo pusho + let find=false; + EventQueue.forEach(element => { + if(element.id == event.id) + { + find=true; + Object.assign(element,event); + } + }); + if(!find) //se non l'ho trovato, lo aggiungo in fondo + EventQueue.unshift(event); + + if(!callInProgress) + showMessage(event); + break; + + case "remove": + + //se non c'è niente nella queue e non c'è una chiamata in corso + if( EventQueue.length==0 && !callInProgress) + next(); + + //se l'id è uguale a quello attualmente visualizzato ( e non siamo in chiamata ) + if(!callInProgress && EventQueue[0] !== undefined && EventQueue[0].id == event.id) + next(); //passo al messaggio successivo ( per la rimozione ci penserà la next ) + + else{ + //altrimenti rimuovo tutti gli elementi con quell'id( creando un nuovo array ) + let newEventQueue=[]; + EventQueue.forEach(element => { + if(element.id != event.id) + newEventQueue.push(element); + }); + EventQueue=newEventQueue; + } + + + + + break; + case "musicstate": + case "musicinfo": + + break; + } +}; + + + + + + +let showMessage = function(msg){ + LOG("showMessage"); + LOG(msg); + g.setBgColor(settings.colBg); + + + if(typeof msg.CanScrollDown==="undefined") + msg.CanScrollDown=false; + if(typeof msg.CanScrollUp==="undefined") + msg.CanScrollUp=false; + + + + + + // Normal text message display + let title=msg.title, titleFont = settings.fontLarge, lines; + if (title) { + let w = g.getWidth()-48; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = settings.fontMedium; + if (g.setFont(titleFont).stringWidth(title) > w) { + lines = g.wrapString(title, w); + title = (lines.length>2) ? lines.slice(0,2).join("\n")+"..." : lines.join("\n"); + } + } + + + + let Layout = require("Layout"); + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:settings.colHeadBg, c: [ + { type:"btn", src:require("messageicons").getImage(msg), col:require("messageicons").getColor(msg), pad: 3}, + { type:"v", fillx:1, c: [ + {type:"txt", font:settings.fontSmall, label:msg.src||/*LANG*/"Message", bgCol:settings.colHeadBg, fillx:1, pad:2, halign:1 }, + title?{type:"txt", font:titleFont, label:title, bgCol:settings.colHeadBg, fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"v",fillx:1,filly:1,pad:2 ,halign:-1,c:[]}, + + + + + ]}); + + + if (!settings.quiet && msg.new) + { + msg.new=false; + Bangle.buzz(); + } + + + g.clearRect(Bangle.appRect); + layout.render(); + + PrintMessageStrings(msg); + Bangle.setLCDPower(1); + + DrawLock(); + +}; +let DrawLock=function() +{ + let w=8,h=8; + let x = g.getWidth()-w; + let y = 0; + if(Bangle.isLocked()) + g.setBgColor(settings.colLock); + else + g.setBgColor(settings.colHeadBg); + g.clearRect(x,y,x+w,y+h); +}; + + + + + + +let showCall = function(msg) +{ + LOG("showCall"); + LOG(msg); + // se anche prima era una call PrevMessage==msg.id + //non so perchè prima era cosi + if( msg.t=="remove") + { + LOG("hide call screen"); + next(); //dont shift + return; + } + + callInProgress=true; + + + + //se è una chiamata ( o una nuova chiamata, diversa dalla precedente ) + //la visualizzo + + let title=msg.title, titleFont = settings.fontLarge, lines; + if (title) { + let w = g.getWidth()-48; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = settings.fontMedium; + if (g.setFont(titleFont).stringWidth(title) > w) { + lines = g.wrapString(title, w); + title = (lines.length>2) ? lines.slice(0,2).join("\n")+"..." : lines.join("\n"); + } + } + let Layout = require("Layout"); + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:settings.colHeadBg, c: [ + { type:"btn", src:require("messageicons").getImage(msg), col:require("messageicons").getColor(msg), pad: 3}, + { type:"v", fillx:1, c: [ + {type:"txt", font:settings.fontSmall, label:msg.src||/*LANG*/"Message", bgCol:settings.colHeadBg, fillx:1, pad:2, halign:1 }, + title?{type:"txt", font:titleFont, label:title, bgCol:settings.colHeadBg, fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"txt", font:settings.fontMedium, label:msg.body, fillx:1,filly:1,pad:2 ,halign:0} + ]}); + + + StopBuzzCall(); + if ( !settings.quiet ) { + if(msg.new) + { + msg.new=false; + CallBuzzTimer = setInterval(function() { + Bangle.buzz(500); + }, 1000); + + Bangle.buzz(500); + } + } + g.clearRect(Bangle.appRect); + layout.render(); + PrintMessageStrings(msg); + Bangle.setLCDPower(1); + DrawLock(); +}; + + + + + + + + + +let next=function(){ + LOG("next"); + StopBuzzCall(); + + + //se c'è una chiamata, non shifto + if(!callInProgress) + EventQueue.shift(); //passa al messaggio successivo, se presente - tolgo il primo + + callInProgress=false; + if( EventQueue.length == 0) + { + LOG("no element in queue - closing") + setTimeout(_ => load()); + return; + } + + + showMessage(EventQueue[0]); + +}; + + + + + + + + + + + +let showMapMessage=function(msg) { + + g.clearRect(Bangle.appRect); + PrintMessageStrings({body:"Not implemented!"}); + +} + + + + + +let CallBuzzTimer=null; +let StopBuzzCall=function() +{ + if (CallBuzzTimer){ + clearInterval(CallBuzzTimer); + CallBuzzTimer=null; + } +} +let DrawTriangleUp=function() +{ + g.fillPoly([169,46,164,56,174,56]); +} +let DrawTriangleDown=function() +{ + g.fillPoly([169,170,164,160,174,160]); +} + + + + +let ScrollUp=function() +{ + msg= EventQueue[0]; + + if(typeof msg.FirstLine==="undefined") + msg.FirstLine=0; + if(typeof msg.CanScrollUp==="undefined") + msg.CanScrollUp=false; + + if(!msg.CanScrollUp) return; + + msg.FirstLine = msg.FirstLine>0?msg.FirstLine-1:0; + + PrintMessageStrings(msg); +} +let ScrollDown=function() +{ + msg= EventQueue[0]; + if(typeof msg.FirstLine==="undefined") + msg.FirstLine=0; + if(typeof msg.CanScrollDown==="undefined") + msg.CanScrollDown=false; + + if(!msg.CanScrollDown) return; + + msg.FirstLine = msg.FirstLine+1; + PrintMessageStrings(msg); +} + + + + + + +let PrintMessageStrings=function(msg) +{ + let MyWrapString = function (str,maxWidth) + { + str=str.replace("\r\n","\n").replace("\r","\n"); + return g.wrapString(str,maxWidth); + } + + + if(typeof msg.FirstLine==="undefined") msg.FirstLine=0; + + let bodyFont = typeof msg.bodyFont==="undefined"? settings.fontMedium : msg.bodyFont; + let Padding=2; + if(typeof msg.lines==="undefined") + { + g.setFont(bodyFont); + msg.lines = MyWrapString(msg.body,g.getWidth()-(Padding*2)) + if ( msg.lines.length<=2) + { + bodyFont= g.getFonts().includes("Vector")?"Vector:20":"6x8:3"; + g.setFont(bodyFont); + msg.lines = MyWrapString(msg.body,g.getWidth()-(Padding*2)) + msg.bodyFont = bodyFont; + } + } + + + + //prendo le linee da stampare + let NumLines=8; + let linesToPrint = (msg.lines.length>NumLines) ? msg.lines.slice(msg.FirstLine,msg.FirstLine+NumLines):msg.lines; + + + let yText=45; + + //invalido l'area e disegno il testo + g.setBgColor(settings.colBg); + g.clearRect(0,yText,176,176); + let xText=Padding; + yText+=Padding; + g.setFont(bodyFont); + let HText=g.getFontHeight(); + + yText=((176-yText)/2)-(linesToPrint.length * HText / 2) + yText; + + if( linesToPrint.length<=2) + { + g.setFontAlign(0,-1); + xText = g.getWidth()/2; + } + else + g.setFontAlign(-1,-1); + + + linesToPrint.forEach((line, i)=>{ + g.drawString(line,xText,yText+HText*i); + }); + + //disegno le freccie + if(msg.FirstLine!=0) + { + msg.CanScrollUp=true; + DrawTriangleUp(); + } + else + msg.CanScrollUp=false; + + if(msg.FirstLine+linesToPrint.length < msg.lines.length) + { + msg.CanScrollDown=true; + DrawTriangleDown(); + } + else + msg.CanScrollDown=false; + + +} + + + + +let doubleTapUnlock=function(data) { + if( data.double) //solo se in double + { + Bangle.setLocked(false); + Bangle.setLCDPower(1); + } +} +let toushScroll=function(button, xy) { + let height=176; //g.getHeight(); -> 176 B2 + height/=2; + + if(xy.y next(), BTN1,{repeat: true}); + + //il tap è il tocco con l'accellerometro! + Bangle.on('tap', doubleTapUnlock); + Bangle.on('touch', toushScroll); + + //quando apro quest'app, do per scontato che c'è un messaggio da leggere posto in un file particolare ( NewMessage.json ) + let eventToShow = require('Storage').readJSON(settings.NewEventFileName, true); + require("Storage").erase(settings.NewEventFileName) + if( eventToShow!==undefined) + manageEvent(eventToShow); + else + { + LOG("file not found!"); + setTimeout(_ => load(), 0); + } +}; + + + + +main(); \ No newline at end of file diff --git a/apps/messages_light/messages_light.boot.js b/apps/messages_light/messages_light.boot.js new file mode 100644 index 000000000..741d08b96 --- /dev/null +++ b/apps/messages_light/messages_light.boot.js @@ -0,0 +1,33 @@ +/* +//OLD CODE -> backup purpose + +let messageBootManager=function(type,event){ + //se l'app non è aperta + if ("undefined"==typeof manageEvent) + { + if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app + + //la apro + require("Storage").writeJSON("messages_light.NewEvent.json",{"event":event,"type":type}); + load("messages_light.app.js"); + } + else + { + //altrimenti gli dico di gestire il messaggio + manageEvent(type,event); + } +} +Bangle.on("message", messageBootManager); +Bangle.on("call", messageBootManager);*/ + + + +//override require to filter require("message") +global.require_real=global.require; +global.require = (_require => file => { + if (file==="messages") file = "messagesProxy"; + //else if (file==="messages_REAL") file = "messages"; //backdoor to real message + + return _require(file); +})(require); + diff --git a/apps/messages_light/messages_light.messagesProxy.js b/apps/messages_light/messages_light.messagesProxy.js new file mode 100644 index 000000000..723397057 --- /dev/null +++ b/apps/messages_light/messages_light.messagesProxy.js @@ -0,0 +1,30 @@ + +//gestisco il messaggio a modo mio +exports.pushMessage = function(event) { + + //TODO: now i can't handle the music, so i call the real message app + if( event.id=="music") return require_real("messages").pushMessage(event); + + //se l'app non è aperta + if ("undefined"==typeof manageEvent) + { + if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app + + //la apro + require_real("Storage").writeJSON("messages_light.NewEvent.json",event); + load("messages_light.app.js"); + } + else + { + //altrimenti gli dico di gestire il messaggio + manageEvent(event); + } +} + + +//Call original message library +exports.clearAll = function() { return require_real("messages").clearAll()} +exports.getMessages = function() { return require_real("messages").getMessages()} +exports.status = function() { return require_real("messages").status()} +exports.buzz = function() { return require_real("messages").buzz(msgSrc)} +exports.stopBuzz = function() { return require_real("messages").stopBuzz()} \ No newline at end of file diff --git a/apps/messages_light/messages_light.settings.js b/apps/messages_light/messages_light.settings.js new file mode 100644 index 000000000..b7197c70a --- /dev/null +++ b/apps/messages_light/messages_light.settings.js @@ -0,0 +1 @@ +eval(require("Storage").read("messages.settings.js")); diff --git a/apps/messages_light/metadata.json b/apps/messages_light/metadata.json new file mode 100644 index 000000000..3515a75c2 --- /dev/null +++ b/apps/messages_light/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "messages_light", + "name": "Messages Light", + "version": "1.3", + "description": "A light implementation of messages App (display notifications from iOS and Gadgetbridge/Android)", + "icon": "app.png", + "type": "app", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "messageicons":"module","messages":"app" }, + "readme": "README.md", + "storage": [ + {"name":"messages_light.app.js","url":"messages_light.app.js"}, + {"name":"messages_light.settings.js","url":"messages_light.settings.js"}, + {"name":"messages_light.img","url":"app-icon.js","evaluate":true}, + {"name":"messagesProxy","url":"messages_light.messagesProxy.js"}, + {"name":"messages_light.boot.js","url":"messages_light.boot.js"} + ], + "data": [{"name":"messages_light.settings.json"},{"name":"messages_light.NewMessage.json"}], + "screenshots": [{"url":"screenshot-notify.png"} ,{"url":"screenshot-long-text1.png"},{"url":"screenshot-long-text2.png"}, {"url":"screenshot-call.png"} ] +} diff --git a/apps/messages_light/screenshot-call.png b/apps/messages_light/screenshot-call.png new file mode 100644 index 000000000..703faad6f Binary files /dev/null and b/apps/messages_light/screenshot-call.png differ diff --git a/apps/messages_light/screenshot-long-text1.png b/apps/messages_light/screenshot-long-text1.png new file mode 100644 index 000000000..147b0cd5c Binary files /dev/null and b/apps/messages_light/screenshot-long-text1.png differ diff --git a/apps/messages_light/screenshot-long-text2.png b/apps/messages_light/screenshot-long-text2.png new file mode 100644 index 000000000..5408f2059 Binary files /dev/null and b/apps/messages_light/screenshot-long-text2.png differ diff --git a/apps/messages_light/screenshot-notify.png b/apps/messages_light/screenshot-notify.png new file mode 100644 index 000000000..8896b803a Binary files /dev/null and b/apps/messages_light/screenshot-notify.png differ diff --git a/apps/messagesmusic/ChangeLog b/apps/messagesmusic/ChangeLog index 9f4cafb0e..8cc3079a3 100644 --- a/apps/messagesmusic/ChangeLog +++ b/apps/messagesmusic/ChangeLog @@ -1,2 +1,5 @@ 0.01: New App! 0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app. +0.03: Use the new messages library +0.04: Fix dependency on messages library + Fix loading message UI \ No newline at end of file diff --git a/apps/messagesmusic/README.md b/apps/messagesmusic/README.md index 85608118d..9a50de93e 100644 --- a/apps/messagesmusic/README.md +++ b/apps/messagesmusic/README.md @@ -1,15 +1,9 @@ Hacky app that uses Messages app and it's library to push a message that triggers the music controls. It's nearly not an app, and yet it moves. -This app require Messages setting 'Auto-open Music' to be 'Yes'. If it isn't, the app will change it to 'Yes' and let it stay that way. - Making the music controls accessible this way lets one start a music stream on the phone in some situations even though the message app didn't receive a music message from gadgetbridge to begin with. (I think.) It is suggested to use Messages Music along side the app Quick Launch. -Messages Music v0.02 has been verified to work with Messages v0.41 on Bangle.js 2 fw2v14. - -Messages Music should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much. - Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986). The icon used for this app is from [https://icons8.com](https://icons8.com). diff --git a/apps/messagesmusic/app.js b/apps/messagesmusic/app.js index 27f3f6e4d..26fedecc1 100644 --- a/apps/messagesmusic/app.js +++ b/apps/messagesmusic/app.js @@ -1,14 +1 @@ -let showMusic = () => { - Bangle.CLOCK = 1; // To pass condition in messages library - require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true}); -}; - -var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict -if (!settings.openMusic) { - settings.openMusic = true; // This app/hack works as intended only if this setting is true - require('Storage').writeJSON('messages.settings.json', settings); - E.showMessage("First run:\n\nMessages setting\n\n 'Auto-Open Music'\n\n set to 'Yes'"); - setTimeout(()=>{showMusic();}, 5000); -} else { - showMusic(); -} +setTimeout(()=>require('messages').openGUI({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true})); diff --git a/apps/messagesmusic/metadata.json b/apps/messagesmusic/metadata.json index c29ffbc34..22e0eff52 100644 --- a/apps/messagesmusic/metadata.json +++ b/apps/messagesmusic/metadata.json @@ -1,7 +1,7 @@ { "id": "messagesmusic", "name":"Messages Music", - "version":"0.02", + "version":"0.04", "description": "Uses Messages library to push a music message which in turn displays Messages app music controls", "icon":"app.png", "type": "app", @@ -13,6 +13,5 @@ {"name":"messagesmusic.app.js","url":"app.js"}, {"name":"messagesmusic.img","url":"app-icon.js","evaluate":true} ], - "dependencies": {"messages":"app"} - + "dependencies":{"messages":"module"} } diff --git a/apps/mylocation/ChangeLog b/apps/mylocation/ChangeLog index c14e64ba9..afe1810e9 100644 --- a/apps/mylocation/ChangeLog +++ b/apps/mylocation/ChangeLog @@ -5,3 +5,5 @@ 0.05: Fixed issue with back option 0.06: renamed source files to match standard 0.07: Move mylocation app into 'Settings -> Apps' +0.08: Allow setting location from webinterface in the AppLoader +0.09: Fix web interface so app can be installed (replaced custom with interface html) diff --git a/apps/mylocation/README.md b/apps/mylocation/README.md index a6a16ce83..11a644262 100644 --- a/apps/mylocation/README.md +++ b/apps/mylocation/README.md @@ -1,10 +1,15 @@ # My Location - *Sets and stores GPS lat and lon of your preferred city* +*Sets and stores GPS lat and lon of your preferred city* -To access, go to `Settings -> Apps -> My Location` +To access, you have two options: -* Select one of the preset Cities or setup through the GPS +**In the App Loader** once My Location is installed, click on the 'Save' icon +next to it - and you can choose your location on a map. + +**On Bangle.js** go to `Settings -> Apps -> My Location` + +* Select one of the preset Cities, setup through the GPS or use the webinterface from the AppLoader * Other Apps can read this information to do calculations based on location * When the City shows ??? it means the location has been set through the GPS diff --git a/apps/mylocation/interface.html b/apps/mylocation/interface.html new file mode 100644 index 000000000..79a122bf7 --- /dev/null +++ b/apps/mylocation/interface.html @@ -0,0 +1,114 @@ + + + + + + + + + +
+
+
+ Click the map to select a location +
+
+ + + + + + + + + + + diff --git a/apps/mylocation/metadata.json b/apps/mylocation/metadata.json index 4ab9aa37e..1c2974030 100644 --- a/apps/mylocation/metadata.json +++ b/apps/mylocation/metadata.json @@ -4,11 +4,12 @@ "icon": "app.png", "type": "settings", "screenshots": [{"url":"screenshot_1.png"}], - "version":"0.07", - "description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README", + "version":"0.09", + "description": "Sets and stores the latitude and longitude of your preferred City. It can be set from GPS or webinterface. `mylocation.json` can be used by other apps that need your main location. See README for details.", "readme": "README.md", "tags": "tool,utility", "supports": ["BANGLEJS", "BANGLEJS2"], + "interface": "interface.html", "storage": [ {"name":"mylocation.settings.js","url":"settings.js"} ], diff --git a/apps/ncfrun/metadata.json b/apps/ncfrun/metadata.json deleted file mode 100644 index 831ae3d4e..000000000 --- a/apps/ncfrun/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "ncfrun", - "name": "NCEU 5K Fun Run", - "version": "0.01", - "description": "Display a map of the NodeConf EU 2019 5K Fun Run route and your location on it", - "icon": "nceu-funrun.png", - "tags": "health", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"ncfrun.app.js","url":"nceu-funrun.js"}, - {"name":"ncfrun.img","url":"nceu-funrun-icon.js","evaluate":true} - ] -} diff --git a/apps/ncfrun/nceu-funrun-icon.js b/apps/ncfrun/nceu-funrun-icon.js deleted file mode 100644 index a13452a8b..000000000 --- a/apps/ncfrun/nceu-funrun-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwwgurglEC6tDmYYUgkzAANAFygXKKYIADBwgXDkg8LBwwXMoQXEH4hHNC4s0O6BfECAKhDHYKnOghCB3cga6dEnYYBaScC2cznewC6W7OQU7BYyIFAAhFBAAYwGC5RFBC5QAJlY0FSIQAMkUjGgrTJRYoXFPQIXGLg8iAAJFDDgIXGgYXJGAWweQJHOC4jtBC6cidgQXUUQQXBogACDYR3HmQXHAAYzKU4IACC48kJBwFBgg7EMZYwDJAReDoh5PC4QARJAoARJAYXTJChtDoSgNAAaeEAAU0C5wqCC4q5LOYYvWgjOEaJ4AGoZGQPY6OPFw0yF34uFRlYXCFykAoQuVeIQWUAB4A=")) diff --git a/apps/ncfrun/nceu-funrun.js b/apps/ncfrun/nceu-funrun.js deleted file mode 100644 index 30e587188..000000000 --- a/apps/ncfrun/nceu-funrun.js +++ /dev/null @@ -1,140 +0,0 @@ -var coordScale = 0.6068; -var coords = new Int32Array([-807016,6918514,-807057,6918544,-807135,6918582,-807238,6918630,-807289,6918646,-807308,6918663,-807376,6918755,-807413,6918852,-807454,6919002,-807482,6919080,-807509,6919158,-807523,6919221,-807538,6919256,-807578,6919336,-807628,6919447,-807634,6919485,-807640,6919505,-807671,6919531,-807703,6919558,-807760,6919613,-807752,6919623,-807772,6919643,-807802,6919665,-807807,6919670,-807811,6919685,-807919,6919656,-807919,6919645,-807890,6919584,-807858,6919533,-807897,6919503,-807951,6919463,-807929,6919430,-807916,6919412,-807907,6919382,-807901,6919347,-807893,6919322,-807878,6919292,-807858,6919274,-807890,6919232,-807909,6919217,-807938,6919206,-807988,6919180,-807940,6919127,-807921,6919100,-807908,6919072,-807903,6919039,-807899,6919006,-807911,6918947,-807907,6918936,-807898,6918905,-807881,6918911,-807874,6918843,-807870,6918821,-807854,6918775,-807811,6918684,-807768,6918593,-807767,6918593,-807729,6918516,-807726,6918505,-807726,6918498,-807739,6918481,-807718,6918465,-807697,6918443,-807616,6918355,-807518,6918263,-807459,6918191,-807492,6918162,-807494,6918147,-807499,6918142,-807500,6918142,-807622,6918041,-807558,6917962,-807520,6917901,-807475,6917933,-807402,6917995,-807381,6918024,-807361,6918068,-807323,6918028,-807262,6918061,-807263,6918061,-807159,6918116,-807148,6918056,-807028,6918063,-807030,6918063,-806979,6918068,-806892,6918090,-806760,6918115,-806628,6918140,-806556,6918162,-806545,6918175,-806531,6918173,-806477,6918169,-806424,6918180,-806425,6918180,-806367,6918195,-806339,6918197,-806309,6918191,-806282,6918182,-806248,6918160,-806225,6918136,-806204,6918107,-806190,6918076,-806169,6917968,-806167,6917953,-806157,6917925,-806140,6917896,-806087,6917839,-806071,6917824,-805969,6917904,-805867,6917983,-805765,6918063,-805659,6918096,-805677,6918131,-805676,6918131,-805717,6918212,-805757,6918294,-805798,6918397,-805827,6918459,-805877,6918557,-805930,6918608,-805965,6918619,-806037,6918646,-806149,6918676,-806196,6918685,-806324,6918703,-806480,6918735,-806528,6918738,-806644,6918712,-806792,6918667,-806846,6918659,-806914,6918654,-806945,6918661,-806971,6918676,-806993,6918689,-806992,6918692,-807065,6918753,-807086,6918786,-807094,6918788,-807102,6918795,-807104,6918793,-807107,6918799,-807102,6918802,-807112,6918812,-807106,6918815,-807115,6918826,-807120,6918823,-807132,6918841,-807141,6918850,-807151,6918841,-807170,6918832,-807193,6918813,-807222,6918775,-807246,6918718,-807250,6918694,-807264,6918637,-807238,6918630,-807148,6918587,-807057,6918544,-806948,6918463]); - -var min = {"x":-807988,"y":6917824}; -var max = {"x":-805659,"y":6919685}; -var gcoords = new Uint8Array(coords.length); -var coordDistance = new Uint16Array(coords.length/2); - -var PT_DISTANCE = 30; // distance to a point before we consider it complete - -function toScr(p) { - return { - x : 10 + (p.x-min.x)*100/(max.x-min.x), - y : 230 - (p.y-min.y)*100/(max.y-min.y) - }; -} - -var last; -var totalDistance = 0; -for (var i=0;i { - require('Storage').write('ncstart.json', {welcomed: true}) - load('ncstart.app.js') - }) - } -})() diff --git a/apps/ncstart/metadata.json b/apps/ncstart/metadata.json deleted file mode 100644 index d2b3e2196..000000000 --- a/apps/ncstart/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "ncstart", - "name": "NCEU Startup", - "version": "0.06", - "description": "NodeConfEU 2019 'First Start' Sequence", - "icon": "start.png", - "tags": "start,welcome", - "supports": ["BANGLEJS"], - "storage": [ - {"name":"ncstart.app.js","url":"start.js"}, - {"name":"ncstart.boot.js","url":"boot.js"}, - {"name":"ncstart.settings.js","url":"settings.js"}, - {"name":"ncstart.img","url":"start-icon.js","evaluate":true}, - {"name":"nc-bangle.img","url":"start-bangle.js","evaluate":true}, - {"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true}, - {"name":"nc-nfr.img","url":"start-nfr.js","evaluate":true}, - {"name":"nc-nodew.img","url":"start-nodew.js","evaluate":true}, - {"name":"nc-tf.img","url":"start-tf.js","evaluate":true} - ], - "data": [{"name":"ncstart.json"}] -} diff --git a/apps/ncstart/settings.js b/apps/ncstart/settings.js deleted file mode 100644 index 560fad8ba..000000000 --- a/apps/ncstart/settings.js +++ /dev/null @@ -1,14 +0,0 @@ -(function(back) { - let settings = require('Storage').readJSON('ncstart.json', 1) - || require('Storage').readJSON('setting.json', 1) || {} - E.showMenu({ - '': { 'title': 'NCEU Startup' }, - 'Run on Next Boot': { - value: !settings.welcomed, - format: v => v ? 'OK' : 'No', - onchange: v => require('Storage').write('ncstart.json', {welcomed: !v}), - }, - 'Run Now': () => load('ncstart.app.js'), - '< Back': back, - }) -}) diff --git a/apps/ncstart/start-bangle.js b/apps/ncstart/start-bangle.js deleted file mode 100644 index 26f38ae14..000000000 --- a/apps/ncstart/start-bangle.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("s8wxH+AH4AQ/4AJJX5mmM/5m/AH5m/M34A/M35l/M35mqM/5m/AH5m/M34A/MqQKQJm5laOh7kNM35MGbiQxLM9osWIiZnGDI5m/VTBm/MsrOGM35maB4xm/MsoZFORZm/Fq5mDAAwUKBhAHBDJYLGAY4rOPShmRF44TIIoqlJCIxmKEZLMSBxY1GE5RTIJpwYSP5hmQZxodKLBKpIDBQZHMxS4MM1IKCMzKNQHJJmtFwbbUMy4AIM35mcJR5mbLCo1GZrxLOLZ6BMH5wOHMyAYRSRLOWGRY+MAxRmODCZeNMyLNMAA4TIBgpmPFA4YMHBZnPFIp/cADa0cC9Zm2J5YkKMtgsIGjZRTCYLMsFow0dDqJluGAgzhEJwxiAGpYLMn70hAA5N/M34A/M35mzJn5m/AH5nNJf5m/AH5m/M34A/M35m/MpgA=")) diff --git a/apps/ncstart/start-icon.js b/apps/ncstart/start-icon.js deleted file mode 100644 index 0302cadbc..000000000 --- a/apps/ncstart/start-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwxH+AHMPADQv/F+YxZYtb1wFto7SEbwwQBIsen0/ADU+jxfOjwtbAAYwDWZVWF79WfBAvEq4vfq4vIGQgviR44AEFz4vEGRQvnGA4v/F79YX9IHEq4aKh//jwvRrBcHG4ovL/4ABB5gAFRAwvVGIQveoAAIF4oABq0/CZIACF8BiBrAvTGIoaKF5AABIpVXd44AFJBQvKh4vOGBIvVL54vdX5iPhqztLoFYFpYvSh8/FxgABFpYvQRRgveoEP/8eFqAvbACi/CeA4IDP6IvUGIYGEF+EMADwvJR4ovmdoovnFoowDF8QsIF4dZF79ZF5RpCj1AFztAjy7JAAgwdFwbAFFwwAmF/4vhGFrxLFkoAvA=")) diff --git a/apps/ncstart/start-nceu.js b/apps/ncstart/start-nceu.js deleted file mode 100644 index 89a9850cc..000000000 --- a/apps/ncstart/start-nceu.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("o9HxH+AEOAwAkiIkIADIv5CEI/4/IJHbNLbPA4Iv1+JHREIwkmk2EJBBE2IoUnIwJHBCx5GoBA2DIgQACBw5G3aQQADwRG+wEmagQCBvxGufoQpDFxOCI4YNIDgxNeD4gDHCY+EwgMKBQIjGJDJlHA4YlKvzRHDRZHZDJQkMBZojVECb+OHJgkOZ6w6KCJAHJCgY1dK5wPDCg4GICYjDZBY9+vxGMArItLeRgWDwOEwmBJA5Ggv2GlMMwJGTwRFBI5JGfv2HlIACwRGRwBFDAAIUGIz+FIYMMI4R0CIxzSCRwhaMIBy2FAAaMBhmHI4QjIRqwUFIxxFJOgLTDlMGRqJHFwF+CpAWDIxgwJBgN+aoSMEIyAGBweDXBg6FABIWLAgOCw+GMhRGKByI9IIxYtQIywaJC5YTTIzwRGOyQqTIzLGNCTJGgXqIRTIzILIIzQvUI5a4EBgh6TDI7dKZJo7IAwQLFIzAjKIhwQGChBvMEhojLIqIjGBaZGPEbppOEerrLBYpGVEZrVOBpJjJIzCHNcpoqPI6gaUIywfSCLJGgXBYSZIzwRFCxoSGFSJGYCA4XLCRArQIywOJYxDPLFqA3OwFPp4HCy4lKHogAIM5uulukMIxGNy1MAAWW2JENFBJIMv8B0ksAAQQDIx2AptMpoCCChZGQGROYIocslsBIyGVIQNOp5HByhaMIxj9IAAWMIYUtRwiNPaIKNCpgUGIB4FNAAMXRq/+yhDBAAOUtJGlgKOCAAOvCJRGH2OVp1OypFGI0BHB0jUBzCMCIyAABtJEHI0RICIgYRMJBBGMCg4GICYgnPCBhHPBwQSIA5IUDGpxWOJBwgLfpgkOIhwVOEBj9WIipsKA4YiKgMBERojIIqphHAgYjKy+n1VpTJYjIADZlGEpOVlwABhTJKRL4oHFxIIEIgUKlula44/hShwIG1RFB02lJQJVII2zTC0iNBhVpI24vGgOmlpIBl2WagwWIJGFp1UKhRFGImI0FGouAaIoPIJGQMWJG5E7H5BE/I4pF/JA4kiA")) diff --git a/apps/ncstart/start-nfr.js b/apps/ncstart/start-nfr.js deleted file mode 100644 index 2a0ad70ea..000000000 --- a/apps/ncstart/start-nfr.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("5EuxH+AAkPABIQFABIaKDiIA/AH6qaVpwbbAH4A/YzysMDbYA/AH7GfVhbHgChrr0MT5FoTDobOQijH/Y/6aYcqzH/Y/5EeZDLHmFxTH/Y/4TVY84uJY/7H7TibHuC4rH5XmiHRZC7HpDAjSQF5QpJCgYGJY6Y8MFR4bJBSrITY9RNJb6LFNY5ALFP6CsVY55PQTzDH6MhrGPY7opYY7IZFAgqfRY9xzIWx7GQY6QsYFTTHQZDLHlL44ONDxIfJdKS3PA54qSCRL1MWpDIRY8yLNCg5FICB7ZMHZwrKaB4bQEpTHZH5bHgRhiZNbCTZSY5qBNHiDHZZCbHsOZjHPRSTHYOZbHyZDLH/Y8pQRY+zIYY8xPLG6bHsDJjHuUiTbQdTjHfQBjHYVaLHyUqbHoKJC2KCBgRDBA7HeThbHvZETHdVxKKPTkzISfJLHpZELHeOZLGOY9g8OY+TIgY74OJLqDHqFZIMJY/7HuFxRcPYJbHeXi7HUKAqGYCSgdRAH4A/XC7IdY/4A/Y9rIZY/4A/Y/7H/AH7H2ZDDH/AH7HvZC7H/SMrH/Io7IZDCoVIBgwNFBSA7JBRoZOJ5jboY6IOBY9oWKDpYLFApZkNH6YIHJ5BMNY97IZY6yvTTJCGRBwQRIExYVKB4zH+ZDDHpBQ7HgH5Q+QY/7IYY9KDJY6QeKY6xDOY/7H7BhRiPCRQGHA4SsRCJDH4ZDJqUfpQiIBR6UNDRISQOJ52TY9DIYNSyvSIZLfOAxoaIY/7HVZC4SQQBSJUC57HTDIw9QGZzH/Y7xmINyTHTAAwfKHyzH/OBTH3CRg1LYxAUFD5Y+QY9RXLLxQaWY6yIYY6g5SH57kHY9StRcbZPQQJivRC6AKJEBpGHBxrH/DcbHUEpQKQBojSPH5gpIXx7HjVp4caJkbjRGv4AkA==")) diff --git a/apps/ncstart/start-nodew.js b/apps/ncstart/start-nodew.js deleted file mode 100644 index 287d49a1b..000000000 --- a/apps/ncstart/start-nodew.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("5E1xH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A6hEICD4A/AH7FlY6TJ/AH7G1Y6bI/AH7FyY6rJ/AH7GyY6zI/AH7H/Y/4A/Y3DHXg5g/AH4Ak5jH/AH4A/Y9VXq5l/AH4Ag47HjZAJm/AH7GgY5S9KZBjHDZH4A/Y9S5KBxrH/AH7GkY5DGOCIrHJZA4pGBiQAPKpAQLHKQmPADRSQY6LFQCZLHPQJjIeQqwKVHTLHoEpDISY4rIGJRbH/IjBYXC67GCY5LGRY7CCaY8hEPV87Ha4zHEYyoXGY6SOKY9IQJIhRDUY+YdEY64YCAgTIBY6CDPMBxRIDpAvMIaZALV5z/NFRrHH5glGYyqOFY4LIJDJoHKaZolPMZIRMUZAyLHxotLLJzHJ4zGBY8TICY6KXJO6wdQWiJCHGRp+QJaTINYoRbQY6bICZINRY8RJQDhowPY8RMYY5YABgR9US6MHgIwGJ5QMLE44GIURY4NBSoyKIZQRObhrIMg7HkgQvIJBapSBzrBPCBhdJY5w+NeBgAFzO93rIFY8AFBxmGwydKFxSFOMJR6JFZhXLIKbHVPhbHPZALJBZA7HcAgLFBY5qFYY+KsLY/DICY4rIWC4kC/2MY6CGJOZjWPRBy8KY95MHY62ZAoLICY64/G/zGIMRxcQdB7HWBZqeQEZxcIY7e9Y4ZMHY/AwJIByrOY7JzLCJAbNY8jITCozHVURqDRDrY/MGSDHWPhTHOZAbHFZCgxHY4gxGIJbrLT6AQIDiRLQOp7ITGBbHcZB4wKY5o+IOxwWLBoQdUY54zMCJTTQBQ6GSZAjHGZCJCLY5IA/AH4AWY5L7LBpzHDNH4A/ZEDHIXQoALaZLH/AH7HsZB4WHgTHCM34A/AELHjY34A/AErHhAH4A/AEzH/AH4A/Y8xe/AH7IwCsgA/AH7IiCkYA/AH7IjCcQA/AH7JkCMAA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHIA=")) diff --git a/apps/ncstart/start-tf.js b/apps/ncstart/start-tf.js deleted file mode 100644 index d09185caf..000000000 --- a/apps/ncstart/start-tf.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("4M7ghC/AH4A/AEcBiMQAgY+3iIACIAQhcAgYjdTzZgfYH5giYHo5BIIQGDAQYLHgMQBoZZFYAoYENGREDGgI6BAYYMDBYrXDA4RTDAobnFLgbNEYF5LFVAIGCAghsEBQYMFYAoMEC4TAxAAg5DLoqiCZQZ2CAQa2DYAiGFA4bAvXYj6DAIZkEVYizHLYhyDNoaGCL9zAEHw5aGOIwHJYAqBFL96zCHxJsEKwcBYAx5GYA4SCFobAuMohoDAQ4UCKgYCEYBR7FYGqsDIIwLFYAzUFYAhbEL+IyGIojAFBYbAEKYZYHCgghEYGoGFWoQGFJIYHBgRZDbQpsFYGiNuIP4AedooA/L/4A5gJf/AH4A/AH4A/AH4A/AH4A/AH4A/ACsN7oAJG+oJC6AoaL5QmbG7PQTLrA/YH7A/YH7AhFgxbrYA4JQFkTArWwzAlEQhnCA4bPDBQwLDHwwJH5oGBEAydNAwQHDEgggDcaJBDEY4JDJoYSEH5A7GBAbAPHg5aHCQoiSEYpNHBIxpIBIQHGTpwzGGQJlMESZCIFowTNCQLAVSZAXFGQnNL5AiNBJAeBBIYsDNJIJHTpwyMCQhGEQQwsFYo7kGAoQJDEQQyDS5ChDCgxRJGRJ4DUIpAFYBQiFJgwUGBIr3IIAy5EaJgyNcgoTGcZZCFWwxMLboxqLYBoUJGw5GIYBaSGFoo3GLApAOYCAUJQYycLYBCSHDIoUJYBfdYDwKDEwQFCBAbAfIwwHEFA6rKTpLAPEoQCDYEBgIFgzAMHwzAOXpDAkMIxALYD4wEAozAOaA7AKMIxAJVZjAWApLAOBxasGEYYZCIAyrOYCQlBYC4jGhpHCYBBpJAYSrSTp4FELQZmFYBoRDBQjAMGwvcHQYiEdBDANHgpTFApbALaYgWERpISGHYoJFYCo8JVBKAHFg5COAoY2JBI65IYBofHOYZmIYBxgGNIr4KSxJJHYCQfGCQhmIYBwjFPZDVKQg53IYCI8IBIgFIERgjEPZLVJEhHcAwUMYCo8IYBIfGAH4A/AHw")) diff --git a/apps/ncstart/start.js b/apps/ncstart/start.js deleted file mode 100644 index d2d713cb2..000000000 --- a/apps/ncstart/start.js +++ /dev/null @@ -1,120 +0,0 @@ -g.setFontAlign(1, 1, 0); -const d = g.getWidth() - 18; -function c(a) { - return { - width: 8, - height: a.length, - bpp: 1, - buffer: (new Uint8Array(a)).buffer - }; -} - -function welcome() { - var welcomes = [ - 'Welcome', - 'Failte', - 'Bienvenue', - 'Willkommen', - 'Bienvenido' - ]; - function next() { - var n = welcomes.shift(); - E.showMessage(n); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); - welcomes.push(n); - } - return new Promise((res) => { - next(); - var i = setInterval(next, 2000); - setWatch(() => { - clearInterval(i); - clearWatch(); - E.showMessage('Loading...'); - res(); - }, BTN2, {repeat:false}); - }); -} - -function logos() { - var logos = [ - ['nfr', 20, 90, ()=>{}], - ['nceu', 20, 90, ()=>{ - g.setFont("6x8", 2); - g.setColor(0,0,1); - g.drawString('Welcome To', 160, 110); - g.drawString('NodeConfEU', 160, 130); - g.drawString('2019', 200, 150); - }], - ['bangle', 70, 90, ()=>{}], - ['nodew', 20, 90, ()=>{}], - ['tf', 24, 90, ()=>{}], - ]; - function next() { - var n = logos.shift(); - var img = require("Storage").read("nc-"+n[0]+".img"); - g.clear(); - g.drawImage(img, n[1], n[2]); - n[3](); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); - logos.push(n); - } - return new Promise((res) => { - next(); - var i = setInterval(next, 2000); - setWatch(() => { - clearInterval(i); - clearWatch(); - res(); - }, BTN2, {repeat:false}); - }); -} - -function info() { - var slides = [ - () => E.showMessage('Visit\nnodewatch.dev\nfor info'), - () => E.showMessage('Visit\nbanglejs.com/apps\nfor apps'), - () => E.showMessage('Remember\nto charge\nyour watch!'), - () => { - g.clear(); - g.setFont('6x8',2); - g.setColor(1,1,1); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,40); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,194); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); - g.drawString('Menu Up', d - 50, 42); - g.drawString('Select', d - 40, 118); - g.drawString('Menu Down', d - 60, 196); - }, - () => { - g.clear(); - E.showMessage('Hold\nto return\nto clock'); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,194); - }, - () => { - g.clear(); - E.showMessage('Hold both\nto reboot'); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,40); - g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); - }, - () => E.showMessage('Open Settings\nto enable\nBluetooth') - ]; - function next() { - var n = slides.shift(); - n(); - slides.push(n); - } - return new Promise((res) => { - next(); - var i = setInterval(next, 2000); - setWatch(()=>{ - clearInterval(i); - clearWatch(); - res(); - }, BTN2, {repeat:false}); - }); -} - -welcome() - .then(logos) - .then(info) - .then(load); diff --git a/apps/ncstart/start.png b/apps/ncstart/start.png deleted file mode 100644 index 9df0974c8..000000000 Binary files a/apps/ncstart/start.png and /dev/null differ diff --git a/apps/nixie/nixie.info b/apps/nixie/nixie.info deleted file mode 100644 index 66f5ff2a5..000000000 --- a/apps/nixie/nixie.info +++ /dev/null @@ -1,10 +0,0 @@ -{ -"id":"jvNixie", -"name":"Nixie Clock", -"type":"clock", -"src":"nixie.app.js", -"icon": "nixie.img", -"sortorder":1, -"version":"1.1", -"files":"nixie.info,nixie.app.js,nixie.img, m_vatch.js" -} diff --git a/apps/noteify/interface.html b/apps/noteify/interface.html index 027c98860..4d7974ad9 100644 --- a/apps/noteify/interface.html +++ b/apps/noteify/interface.html @@ -18,6 +18,11 @@ var notesElement = document.getElementById("notes"); var notes = {}; +function disableFormInput() { + document.querySelectorAll(".form-input").forEach(el => el.disabled = true); + document.querySelectorAll(".btn").forEach(el => el.disabled = true); +} + function getData() { // show loading window Util.showModal("Loading..."); @@ -53,8 +58,10 @@ function getData() { buttonSave.classList.add('btn-default'); buttonSave.onclick = function() { notes[i].note = textarea.value; - Util.writeStorage("noteify.json", JSON.stringify(notes)); - location.reload(); + disableFormInput(); + Util.writeStorage("noteify.json", JSON.stringify(notes), () => { + location.reload(); // reload so we see current data + }); } divColumn2.appendChild(buttonSave); @@ -64,8 +71,10 @@ function getData() { buttonDelete.onclick = function() { notes[i].note = textarea.value; notes.splice(i, 1); - Util.writeStorage("noteify.json", JSON.stringify(notes)); - location.reload(); // reload so we see current data + disableFormInput(); + Util.writeStorage("noteify.json", JSON.stringify(notes), () => { + location.reload(); // reload so we see current data + }); } divColumn2.appendChild(buttonDelete); divColumn.appendChild(divColumn2); @@ -77,8 +86,10 @@ function getData() { document.getElementById("btnAdd").addEventListener("click", function() { const note = document.getElementById("note-new").value; notes.push({"note": note}); - Util.writeStorage("noteify.json", JSON.stringify(notes)); - location.reload(); // reload so we see current data + disableFormInput(); + Util.writeStorage("noteify.json", JSON.stringify(notes), () => { + location.reload(); // reload so we see current data + }); }); }); } diff --git a/apps/openhaystack/ChangeLog b/apps/openhaystack/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/openhaystack/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/openhaystack/README.md b/apps/openhaystack/README.md new file mode 100644 index 000000000..e2d5e2212 --- /dev/null +++ b/apps/openhaystack/README.md @@ -0,0 +1,18 @@ +# OpenHaystack (AirTag) + +Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag + +Based on https://github.com/seemoo-lab/openhaystack/issues/59 + +## Usage + +* Follow the steps on https://github.com/seemoo-lab/openhaystack#how-to-use-openhaystack to install OpenHaystack and get a unique base64 code +* Click the ≡ icon next to `OpenHaystack (AirTag)` +* Paste in the base64 code +* Click `Upload` + +## Note + +This code changes your Bangle's MAC address, so while it still advertises with +the same `Bangle.js abcd` name, devices that were previously paired with it +won't automatically reconnect it until you re-pair. diff --git a/apps/openhaystack/custom.html b/apps/openhaystack/custom.html new file mode 100644 index 000000000..f56e94a98 --- /dev/null +++ b/apps/openhaystack/custom.html @@ -0,0 +1,56 @@ + + + + + + +

Follow the steps on https://github.com/seemoo-lab/openhaystack to install OpenHaystack and get a unique base64 code

+

Then paste the key in below and click Upload

+ +

Base64 key:

+

 

+ +

Click

+ + + + + + + diff --git a/apps/openhaystack/icon.png b/apps/openhaystack/icon.png new file mode 100644 index 000000000..f5e4f7f3b Binary files /dev/null and b/apps/openhaystack/icon.png differ diff --git a/apps/openhaystack/metadata.json b/apps/openhaystack/metadata.json new file mode 100644 index 000000000..5573529f7 --- /dev/null +++ b/apps/openhaystack/metadata.json @@ -0,0 +1,14 @@ +{ "id": "openhaystack", + "name": "OpenHaystack (AirTag)", + "icon": "icon.png", + "version":"0.01", + "description": "Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag", + "tags": "openhaystack,bluetooth,ble,tracking,airtag", + "type": "bootloader", + "custom": "custom.html", + "readme": "README.md", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"openhaystack.boot.js"} + ] +} diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 2a6c15a09..7f788c139 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -12,3 +12,7 @@ Fix alignment of satellite info text 0.12: switch to using normal OpenStreetMap tiles (opentopomap was too slow) 0.13: Use a single image file with 'frames' of data (drastically reduces file count, possibility of >1 map on device) +0.14: Added ability to upload multiple sets of map tiles + Support for zooming in on map + Satellite count moved to widget bar to leave more room for the map +0.15: Make track drawing an option (default off) diff --git a/apps/openstmap/README.md b/apps/openstmap/README.md new file mode 100644 index 000000000..f19b13bd1 --- /dev/null +++ b/apps/openstmap/README.md @@ -0,0 +1,53 @@ +# OpenStreetMap + +This app allows you to upload and use OpenSteetMap map tiles onto your +Bangle. There's an uploader, the app, and also a library that +allows you to use the maps in your Bangle.js applications. + +## Uploader + +Once you've installed OpenStreepMap on your Bangle, find it +in the App Loader and click the Disk icon next to it. + +A window will pop up showing what maps you have loaded. + +To add a map: + +* Click `Add Map` +* Scroll and zoom to the area of interest or use the Search button in the top left +* Now choose the size you want to upload (Small/Medium/etc) +* On Bangle.js 1 you can choose if you want a 3 bits per pixel map (this is lower +quality but uploads faster and takes less space). On Bangle.js 2 you only have a 3bpp +display so can only use 3bpp. +* Click `Get Map`, and a preview will be displayed. If you need to adjust the area you +can change settings, move the map around, and click `Get Map` again. +* When you're ready, click `Upload` + +## Bangle.js App + +The Bangle.js app allows you to view a map - it also turns the GPS on and marks +the path that you've been travelling (if enabled). + +* Drag on the screen to move the map +* Press the button to bring up a menu, where you can zoom, go to GPS location +, put the map back in its default location, or choose whether to draw the currently +recording GPS track (from the `Recorder` app). + +**Note:** If enabled, drawing the currently recorded GPS track can take a second +or two (which happens after you've finished scrolling the screen with your finger). + + +## Library + +See the documentation in the library itself for full usage info: +https://github.com/espruino/BangleApps/blob/master/apps/openstmap/openstmap.js + +Or check the app itself: https://github.com/espruino/BangleApps/blob/master/apps/openstmap/app.js + +But in the most simple form: + +``` +var m = require("openstmap"); +// m.lat/lon are now the center of the loaded map +m.draw(); // draw centered on the middle of the loaded map +``` diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 62597ca20..89e2d2ddb 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -1,20 +1,31 @@ var m = require("openstmap"); var HASWIDGETS = true; -var y1,y2; +var R; var fix = {}; +var mapVisible = false; +var hasScrolled = false; +var settings = require("Storage").readJSON("openstmap.json",1)||{}; +// Redraw the whole page function redraw() { - g.setClipRect(0,y1,g.getWidth()-1,y2); + g.setClipRect(R.x,R.y,R.x2,R.y2); m.draw(); drawMarker(); - if (WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { - g.flip(); // force immediate draw on double-buffered screens - track will update later - g.setColor(0.75,0.2,0); - WIDGETS["gpsrec"].plotTrack(m); + // if track drawing is enabled... + if (settings.drawTrack) { + if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later + WIDGETS["gpsrec"].plotTrack(m); + } + if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) { + g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later + WIDGETS["recorder"].plotTrack(m); + } } g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); } +// Draw the marker for where we are function drawMarker() { if (!fix.fix) return; var p = m.latLonToXY(fix.lat, fix.lon); @@ -22,50 +33,70 @@ function drawMarker() { g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2); } -var fix; Bangle.on('GPS',function(f) { fix=f; - g.reset().clearRect(0,y1,g.getWidth()-1,y1+8).setFont("6x8").setFontAlign(0,0); - var txt = fix.satellites+" satellites"; - if (!fix.fix) - txt += " - NO FIX"; - g.drawString(txt,g.getWidth()/2,y1 + 4); - drawMarker(); + if (HASWIDGETS) WIDGETS["sats"].draw(WIDGETS["sats"]); + if (mapVisible) drawMarker(); }); Bangle.setGPSPower(1, "app"); if (HASWIDGETS) { Bangle.loadWidgets(); + WIDGETS["sats"] = { area:"tl", width:48, draw:w=>{ + var txt = (0|fix.satellites)+" Sats"; + if (!fix.fix) txt += "\nNO FIX"; + g.reset().setFont("6x8").setFontAlign(0,0) + .drawString(txt,w.x+24,w.y+12); + } + }; Bangle.drawWidgets(); - y1 = 24; - var hasBottomRow = Object.keys(WIDGETS).some(w=>WIDGETS[w].area[0]=="b"); - y2 = g.getHeight() - (hasBottomRow ? 24 : 1); -} else { - y1=0; - y2=g.getHeight()-1; } +R = Bangle.appRect; -redraw(); - -function recenter() { - if (!fix.fix) return; - m.lat = fix.lat; - m.lon = fix.lon; +function showMap() { + mapVisible = true; + g.reset().clearRect(R); redraw(); + Bangle.setUI({mode:"custom",drag:e=>{ + if (e.b) { + g.setClipRect(R.x,R.y,R.x2,R.y2); + g.scroll(e.dx,e.dy); + m.scroll(e.dx,e.dy); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); + hasScrolled = true; + } else if (hasScrolled) { + hasScrolled = false; + redraw(); + } + }, btn: btn=>{ + mapVisible = false; + var menu = {"":{title:"Map"}, + "< Back": ()=> showMap(), + /*LANG*/"Zoom In": () =>{ + m.scale /= 2; + showMap(); + }, + /*LANG*/"Zoom Out": () =>{ + m.scale *= 2; + showMap(); + }, + /*LANG*/"Draw Track": { + value : !!settings.drawTrack, + onchange : v => { settings.drawTrack=v; require("Storage").writeJSON("openstmap.json",settings); } + }, + /*LANG*/"Center Map": () =>{ + m.lat = m.map.lat; + m.lon = m.map.lon; + m.scale = m.map.scale; + showMap(); + }}; + if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{ + m.lat = fix.lat; + m.lon = fix.lon; + showMap(); + }; + E.showMenu(menu); + }}); } -setWatch(recenter, global.BTN2?BTN2:BTN1, {repeat:true}); - -var hasScrolled = false; -Bangle.on('drag',e=>{ - if (e.b) { - g.setClipRect(0,y1,g.getWidth()-1,y2); - g.scroll(e.dx,e.dy); - m.scroll(e.dx,e.dy); - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); - hasScrolled = true; - } else if (hasScrolled) { - hasScrolled = false; - redraw(); - } -}); +showMap(); diff --git a/apps/openstmap/custom.html b/apps/openstmap/interface.html similarity index 56% rename from apps/openstmap/custom.html rename to apps/openstmap/interface.html index 3bba997e7..0bf2268a4 100644 --- a/apps/openstmap/custom.html +++ b/apps/openstmap/interface.html @@ -9,7 +9,8 @@ padding: 0; margin: 0; } - html, body, #map { + html, body, #map, #mapsLoaded, #mapContainer { + position: relative; height: 100%; width: 100%; } @@ -27,24 +28,46 @@ width: 256px; height: 256px; } + .tile-title { + font-weight:bold; + font-size: 125%; + } + .tile-map { + width: 128px; + height: 128px; + } -
+
-
-

3 bit
-
- - +
+
+
+
+

3 bit
+
+ +
+
+ + +
- + - - + + diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 32093f70e..819dc4122 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,17 +2,21 @@ "id": "openstmap", "name": "OpenStreetMap", "shortName": "OpenStMap", - "version": "0.13", + "version": "0.15", "description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps", + "readme": "README.md", "icon": "app.png", "tags": "outdoors,gps,osm", "supports": ["BANGLEJS","BANGLEJS2"], "screenshots": [{"url":"screenshot.png"}], - "custom": "custom.html", - "customConnect": true, + "interface": "interface.html", "storage": [ {"name":"openstmap","url":"openstmap.js"}, {"name":"openstmap.app.js","url":"app.js"}, {"name":"openstmap.img","url":"app-icon.js","evaluate":true} + ], "data": [ + {"name":"openstmap.json"}, + {"wildcard":"openstmap.*.json"}, + {"wildcard":"openstmap.*.img"} ] } diff --git a/apps/openstmap/openstmap.js b/apps/openstmap/openstmap.js index 2bd7d2e2e..692344357 100644 --- a/apps/openstmap/openstmap.js +++ b/apps/openstmap/openstmap.js @@ -20,32 +20,59 @@ function center() { m.draw(); } +// you can even change the scale - eg 'm/scale *= 2' + */ -var map = require("Storage").readJSON("openstmap.0.json"); -map.center = Bangle.project({lat:map.lat,lon:map.lon}); -exports.map = map; -exports.lat = map.lat; // actual position of middle of screen -exports.lon = map.lon; // actual position of middle of screen var m = exports; +m.maps = require("Storage").list(/openstmap\.\d+\.json/).map(f=>{ + let map = require("Storage").readJSON(f); + map.center = Bangle.project({lat:map.lat,lon:map.lon}); + return map; +}); +// we base our start position on the middle of the first map +m.map = m.maps[0]; +m.scale = m.map.scale; // current scale (based on first map) +m.lat = m.map.lat; // position of middle of screen +m.lon = m.map.lon; // position of middle of screen exports.draw = function() { - var img = require("Storage").read(map.fn); var cx = g.getWidth()/2; var cy = g.getHeight()/2; var p = Bangle.project({lat:m.lat,lon:m.lon}); - var ix = (p.x-map.center.x)/map.scale + (map.imgx/2) - cx; - var iy = (map.center.y-p.y)/map.scale + (map.imgy/2) - cy; - //console.log(ix,iy); - var tx = 0|(ix/map.tilesize); - var ty = 0|(iy/map.tilesize); - var ox = (tx*map.tilesize)-ix; - var oy = (ty*map.tilesize)-iy; - for (var x=ox,ttx=tx;x=0 && ttx=0 && tty { + var d = map.scale/m.scale; + var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx; + var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy; + var o = {}; + var s = map.tilesize; + if (d!=1) { // if the two are different, add scaling + s *= d; + o.scale = d; } + //console.log(ix,iy); + var tx = 0|(ix/s); + var ty = 0|(iy/s); + var ox = (tx*s)-ix; + var oy = (ty*s)-iy; + var img = require("Storage").read(map.fn); + // fix out of range so we don't have to iterate over them + if (tx<0) { + ox+=s*-tx; + tx=0; + } + if (ty<0) { + oy+=s*-ty; + ty=0; + } + var mx = g.getWidth(); + var my = g.getHeight(); + for (var x=ox,ttx=tx; x { + if (!waiting){ + waiting = true; + require("owmweather").pull(completion); + } + }, 5000); } setInterval(() => { if (!waiting && NRF.getSecurityStatus().connected){ @@ -25,4 +28,4 @@ } }, settings.refresh * 1000 * 60); } -})(); +} diff --git a/apps/owmweather/metadata.json b/apps/owmweather/metadata.json index 013d345a5..56f9afca7 100644 --- a/apps/owmweather/metadata.json +++ b/apps/owmweather/metadata.json @@ -1,7 +1,7 @@ { "id": "owmweather", "name": "OpenWeatherMap weather provider", "shortName":"OWM Weather", - "version":"0.01", + "version":"0.02", "description": "Pulls weather from OpenWeatherMap (OWM) API", "icon": "app.png", "type": "bootloader", diff --git a/apps/palikkainen/ChangeLog b/apps/palikkainen/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/palikkainen/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/palikkainen/README.md b/apps/palikkainen/README.md new file mode 100644 index 000000000..81d857209 --- /dev/null +++ b/apps/palikkainen/README.md @@ -0,0 +1,7 @@ +# Palikkainen + +By Jukio Kallio + +A minimal watch face consisting of blocks. Minutes fills the blocks, and after 12 hours it starts to empty them. + +![](screenshot1.png) diff --git a/apps/palikkainen/app-icon.js b/apps/palikkainen/app-icon.js new file mode 100644 index 000000000..a99602121 --- /dev/null +++ b/apps/palikkainen/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkBiIA0/4AKCpMfCxYAB+ItTGJQuOGBAWPGAwuQGAwXvCyJgFC+PwgAAEh4X/C/6//A4gX/C/6//A4QX/C/6/vC6sfCyPxC+ZgSCwgwRFwowRCwwwPFw4xOCpIArA")) diff --git a/apps/palikkainen/app.js b/apps/palikkainen/app.js new file mode 100644 index 000000000..42013af69 --- /dev/null +++ b/apps/palikkainen/app.js @@ -0,0 +1,184 @@ +// Palikkainen +// +// Bangle.js 2 watch face +// by Jukio Kallio +// www.jukiokallio.com + +require("Font6x8").add(Graphics); + +// settings +const watch = { + x:0, y:0, w:0, h:0, + bgcolor:g.theme.bg, + fgcolor:g.theme.fg, + font: "6x8", fontsize: 1, + finland:true, // change if you want Finnish style date, or US style +}; + +// set some additional settings +watch.w = g.getWidth(); // size of the background +watch.h = g.getHeight(); +watch.x = watch.w * 0.5; // position of the circles +watch.y = watch.h * 0.45; + +const dateWeekday = { 0: "SUN", 1: "MON", 2: "TUE", 3: "WED", 4:"THU", 5:"FRI", 6:"SAT" }; // weekdays + +var wait = 60000; // wait time, normally a minute + + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, wait - (Date.now() % wait)); +} + + +// main function +function draw() { + // make date object + var date = new Date(); + + // work out the date string + var dateDay = date.getDate(); + var dateMonth = date.getMonth() + 1; + var dateYear = date.getFullYear(); + var dateStr = dateWeekday[date.getDay()] + " " + dateMonth + "." + dateDay + "." + dateYear; + if (watch.finland) dateStr = dateWeekday[date.getDay()] + " " + dateDay + "." + dateMonth + "." + dateYear; // the true way of showing date + + // Reset the state of the graphics library + g.reset(); + + // Clear the area where we want to draw the time + g.setColor(watch.bgcolor); + g.fillRect(0, 0, watch.w, watch.h); + + // setup watch face + const block = { + w: watch.w / 2 - 6, + h: 18, + pad: 4, + }; + + // get hours and minutes + var hour = date.getHours(); + var minute = date.getMinutes(); + + // calculate size of the block face + var facew = block.w * 2 + block.pad; + var faceh = (block.h + block.pad) * 6; + + + // loop through first 12 hours and draw blocks accordingly + g.setColor(watch.fgcolor); // set foreground color + + for (var i = 0; i < 12; i++) { + // where to draw + var x = watch.x - facew / 2; // starting position + var y = watch.y + faceh / 2 - block.h - block.pad / 2; // draw blocks from bottom up + if (i > 5) { + // second column + x += block.w + block.pad; + y -= (block.h + block.pad) * (i - 6); + } else { + // first column + x += 0; + y -= (block.h + block.pad) * i; + } + + if (i < hour) { + // draw full hour block + g.fillRect(x, y, x + block.w, y + block.h); + } else if (i == hour) { + // draw minutes + g.fillRect(x, y, x + block.w * (minute / 60), y + block.h); + + // minute reading help + for (var m = 1; m < 12; m++) { + // set color + if (m * 5 < minute) g.setColor(watch.bgcolor); else g.setColor(watch.fgcolor); + + var mlineh = 1; // minute line height + if (m == 3 || m == 6 || m == 9) mlineh = 3; // minute line height at 15, 30 and 45 minutes + + g.drawLine(x + (block.w / 12 * m), y + block.h / 2 - mlineh, x + (block.w / 12 * m), y + block.h / 2 + mlineh); + } + } + } + + + // loop through second 12 hours and draw blocks accordingly + if (hour >= 12) { + g.setColor(watch.bgcolor); // set foreground color + + for (var i2 = 0; i2 < 12; i2++) { + // where to draw + var x2 = watch.x - facew / 2; // starting position + var y2 = watch.y + faceh / 2 - block.h - block.pad / 2; // draw blocks from bottom up + if (i2 > 5) { + // second column + x2 += block.w + block.pad; + y2 -= (block.h + block.pad) * (i2 - 6); + } else { + // first column + x2 += 0; + y2 -= (block.h + block.pad) * i2; + } + + if (i2 < hour % 12) { + // draw full hour block + g.fillRect(x2, y2, x2 + block.w, y2 + block.h); + } else if (i2 == hour % 12) { + // draw minutes + g.fillRect(x2, y2, x2 + block.w * (minute / 60), y2 + block.h); + + // minute reading help + for (var m2 = 1; m2 < 12; m2++) { + // set color + if (m2 * 5 < minute) g.setColor(watch.fgcolor); else g.setColor(watch.bgcolor); + + var mlineh2 = 1; // minute line height + if (m2 == 3 || m2 == 6 || m2 == 9) mlineh2 = 3; // minute line height at 15, 30 and 45 minutes + + g.drawLine(x2 + (block.w / 12 * m2), y2 + block.h / 2 - mlineh2, x2 + (block.w / 12 * m2), y2 + block.h / 2 + mlineh2); + } + } + } + } + + + // draw date + var datey = 11; + g.setFontAlign(0,-1).setFont(watch.font, watch.fontsize).setColor(watch.fgcolor); + g.drawString(dateStr, watch.x, watch.y + faceh / 2 + datey); + + + // queue draw + queueDraw(); +} + + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + + +// Show launcher when middle button pressed +Bangle.setUI("clock"); diff --git a/apps/palikkainen/app.png b/apps/palikkainen/app.png new file mode 100644 index 000000000..142d429e9 Binary files /dev/null and b/apps/palikkainen/app.png differ diff --git a/apps/palikkainen/metadata.json b/apps/palikkainen/metadata.json new file mode 100644 index 000000000..4ed8be817 --- /dev/null +++ b/apps/palikkainen/metadata.json @@ -0,0 +1,16 @@ +{ "id": "palikkainen", + "name": "Palikkainen - A blocky watch face", + "shortName":"Palikkainen", + "version":"0.01", + "description": "A minimal watch face consisting of blocks.", + "icon": "app.png", + "screenshots": [{"url":"screenshot1.png"}], + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"palikkainen.app.js","url":"app.js"}, + {"name":"palikkainen.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/palikkainen/screenshot1.png b/apps/palikkainen/screenshot1.png new file mode 100644 index 000000000..43a630d59 Binary files /dev/null and b/apps/palikkainen/screenshot1.png differ diff --git a/apps/pebble/ChangeLog b/apps/pebble/ChangeLog index ac894fc00..9c21e09bc 100644 --- a/apps/pebble/ChangeLog +++ b/apps/pebble/ChangeLog @@ -9,3 +9,5 @@ 0.09: Add support for internationalization (LANG placeholders + "locale" module) Get steps from built-in step counter (widpedom no more needed, fix #1697) 0.10: Tell clock widgets to hide. +0.11: Swipe down to see widgets + Support for fast loading diff --git a/apps/pebble/metadata.json b/apps/pebble/metadata.json index c5faa8857..0ccb8e2af 100644 --- a/apps/pebble/metadata.json +++ b/apps/pebble/metadata.json @@ -2,7 +2,7 @@ "id": "pebble", "name": "Pebble Clock", "shortName": "Pebble", - "version": "0.10", + "version": "0.11", "description": "A pebble style clock to keep the rebellion going", "readme": "README.md", "icon": "pebble.png", diff --git a/apps/pebble/pebble.app.js b/apps/pebble/pebble.app.js index 729d0a896..48acbae87 100644 --- a/apps/pebble/pebble.app.js +++ b/apps/pebble/pebble.app.js @@ -1,22 +1,24 @@ Graphics.prototype.setFontLECO1976Regular42 = function(scale) { // Actual height 42 (41 - 0) g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAA/AAAAAAAAH/AAAAAAAA//AAAAAAAP//AAAAAAB///AAAAAAP///AAAAAB////AAAAAf////AAAAD////4AAAAf////AAAAH////4AAAA////+AAAAA////wAAAAA///+AAAAAA///gAAAAAA//8AAAAAAA//gAAAAAAA/4AAAAAAAA/AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAH/AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA//h////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////gD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4AAAH/AAA/4B/gH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAA////wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAAAAB/wAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA////x//AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/4B////AAA/wB////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA//gAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA/4AAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA////wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA/4B/wH/AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAA///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAP+AAH/AAAAH+AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ERkmHyYmJiYmJCYmEQ=="), 60+(scale<<8)+(1<<16)); -} +}; Graphics.prototype.setFontLECO1976Regular22 = function(scale) { // Actual height 22 (21 - 0) g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nA/+cD/5wP/nAAAAAAAAPwAA/gAD+AAPwAAAAAD+AAP4AA/gAAAAAAAAAAAAAcOAP//A//8D//wP//AHDgAcOAP//A//8D//wP//AHDgAAAAAAAAH/jgf+OB/44H/jj8OP/w4//Dj/8OPxw/4HD/gcP+Bw/4AAAAAAAP+AA/8AD/wQOHHA4c8D//wP/8A//gAD4AAfAAH/8A//wP//A84cDjhwIP/AA/8AB/wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8ABwAAAAAAAAD8AAP4AA/gAD8AAAAAAAAAAAEAAD+AB//A///v/D//gB/wABwAAAAAADgAA/wAf/4P8///wf/4AP8AAOAAAAAAAAAyAAHcAAPwAD/gAP/AA/8AA/AAH8AAMwAAAAAAAAAAAAADgAAOAAA4AAf8AD/wAP/AA/8AAOAAA4AADgAAAAAAAAAAD8AAfwAB/AAD8AAAAAAAADgAAOAAA4AADgAAOAAA4AADgAAAAAAAAAADgAAOAAA4AADgAAAAAAAAABwAB/AA/8A//gP/gA/wADwAAIAAAAAAD//wP//A//8D//wOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA4AcDgBwOAHA//8D//wP//A//8AABwAAHAAAcAAAAAAAA+f8D5/wPn/A+f8DhxwOHHA4ccDhxwP/HA/8cD/xwP/HAAAAAAAAOAHA4AcDhxwOHHA4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/wAP/AA/8AD/wAAHAAAcAABwAAHAA//8D//wP//A//8AAAAAAAA/98D/3wP/fA/98DhxwOHHA4ccDhxwOH/A4f8Dh/wOH/AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccDh/wOH/A4f8Dh/wAAAAAAAD4AAPgAA+AADgAAOAAA4AADgAAP//A//8D//wP//AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA//8D//wP//A//8AAAAAAAAOA4A4DgDgOAOA4AAAAAAAAOA/A4H8DgfwOA/AAAAAAAAB4AAPwAA/AAD8AAf4ABzgAPPAA8cAHh4AAAAAAAAAAAAHHAAccABxwAHHAAccABxwAHHAAccABxwAHHAAAAAAAAAOHAA4cADzwAPPAAf4AB/gAD8AAPwAAeAAB4AAAAAAAAA+AAD4AAPgAA+ecDh9wOH3A4fcDhwAP/AA/8AD/wAP/AAAAAAAAAP//4///j//+P//44ADjn/OOf845/zjnHOP8c4//zj//OP/84AAAAAAAP//A//8D//wP//A4cADhwAOHAA4cAD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA//8D//wP9/A/j8AAAAAAAA//8D//wP//A//8DgBwOAHA4AcDgBwOAHA4AcDgBwOAHAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA8A8D//wH/+AP/wAf+AAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4ccDhxwOAHA4AcAAAAAAAA//8D//wP//A//8DhwAOHAA4cADhwAOHAA4cADgAAOAAAAAAD//wP//A//8D//wOAHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA//8D//wP//A//8ABwAAHAAAcAABwAP//A//8D//wP//AAAAAAAAP//A//8D//wP//AAAAAAAAOAHA4AcDgBwOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA//8D//wP//A//8AHwAA/AAP8AB/wAPn/A8f8DB/wIH/AAAAAAAAP//A//8D//wP//AAAcAABwAAHAAAcAABwAAHAAAAAAAAP//A//8D//wP//Af8AAP+AAH/AAD8AAHwAD/AB/wAf8AP+AA//8D//wP//AAAAAAAAP//A//8D//wP//AfwAAfwAAfwAAfwAAfwP//A//8D//wAAAAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHAA4cADhwAOHAA/8AD/wAP/AA/8AAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//+P//4///j//+AAA4AADgAAAP//A//8D//wP//A4eADh+AOH8A4f4D/3wP/HA/8MD/wQAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA4AADgAAOAAA//8D//wP//A//8DgAAOAAA4AADgAAAAAA//8D//wP//A//8AABwAAHAAAcAABwP//A//8D//wP//AAAADAAAPgAA/wAD/4AB/8AA/8AAfwAB/AA/8Af+AP/AA/wAD4AAMAAA4AAD+AAP/gA//8AH/wAB/AAf8Af/wP/4A/4AD/gAP/4AH/8AB/wAB/AB/8D//wP/gA/gADgAAIABA4AcDwDwPw/Afn4Af+AA/wAD/AA//AH5+A/D8DwDwOAHAgAEAAAAP/AA/8AD/wAP/AAAf8AB/wAH/AAf8D/wAP/AA/8AD/wAAAAAAAADh/wOH/A4f8Dh/wOHHA4ccDhxwOHHA/8cD/xwP/HA/8cAAAAAAAAf//9///3///f//9wAA3AADcAAMAAAOAAA/gAD/wAH/8AB/8AA/wAAPAAAEAAAAHAADcAANwAB3///f//9///wAA"), 32, atob("BwYLDg4UDwYJCQwMBgkGCQ4MDg4ODg4NDg4GBgwMDA4PDg4ODg4NDg4GDQ4MEg8ODQ8ODgwODhQODg4ICQg="), 22+(scale<<8)+(1<<16)); -} +}; +{ const SETTINGS_FILE = "pebble.json"; let settings; let theme; +let drawTimeout; -function loadSettings() { +let loadSettings = function() { settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'theme':'System', 'showlock':false}; -} +}; -var img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA==")); +const img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA==")); const h = g.getHeight(); const w = g.getWidth(); @@ -26,7 +28,7 @@ const h3 = 7*h/8; let batteryWarning = false; -function draw() { +let draw = function() { let locale = require("locale"); let date = new Date(); let dayOfWeek = locale.dow(date, 1).toUpperCase(); @@ -81,10 +83,16 @@ function draw() { drawCalendar(((w/2) - 42)/2, 14, 42, 4, dayOfMonth); drawLock(); -} + // queue next draw + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +}; // at x,y width:wi thicknes:th -function drawCalendar(x,y,wi,th,str) { +let drawCalendar = function(x,y,wi,th,str) { g.setColor(theme.fg); g.fillRect(x, y, x + wi, y + wi); g.setColor(theme.bg); @@ -100,9 +108,9 @@ function drawCalendar(x,y,wi,th,str) { g.setFontLECO1976Regular22(); g.setFontAlign(0, 0); g.drawString(str, x + wi/2, y + wi/2 + th); -} +}; -function loadThemeColors() { +let loadThemeColors = function() { theme = {fg: g.theme.fg, bg: g.theme.bg, day: g.toColor(0,0,0)}; if (settings.theme === "Dark") { theme.fg = g.toColor(1,1,1); @@ -114,9 +122,9 @@ function loadThemeColors() { // day and steps if (settings.color == 'Blue' || settings.color == 'Red') theme.day = g.toColor(1,1,1); // white on blue or red best contrast -} +}; -function drawLock(){ +let drawLock = function(){ if (settings.showlock) { if (Bangle.isLocked()){ g.setColor(theme.day); @@ -127,27 +135,28 @@ function drawLock(){ g.fillRect(0, 0, 20, 20); } } -} +}; -Bangle.on('lock', function(on) { - drawLock(); -}); +Bangle.on('lock', drawLock); -Bangle.setUI("clock"); +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + Bangle.removeListener('lock', drawLock); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontLECO1976Regular22; + delete Graphics.prototype.setFontLECO1976Regular42; + require("widget_utils").show(); // re-show widgets + }}); g.clear(); Bangle.loadWidgets(); - -// We are not drawing the widgets as we are taking over the whole screen -// so we will blank out the draw() functions of each widget and change the -// area to the top bar doesn't get cleared. -for (let wd of WIDGETS) { - wd.draw=()=>{}; - wd.area=""; -} +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe loadSettings(); loadThemeColors(); -setInterval(draw, 15000); // refresh every 15s draw(); - +} diff --git a/apps/pebbled/ChangeLog b/apps/pebbled/ChangeLog index 8fe3a0b2c..d2f71f908 100644 --- a/apps/pebbled/ChangeLog +++ b/apps/pebbled/ChangeLog @@ -1,2 +1,4 @@ 0.01: first release 0.02: Tell clock widgets to hide. +0.03: Swipe down to see widgets + Support for fast loading diff --git a/apps/pebbled/metadata.json b/apps/pebbled/metadata.json index 70820f2c8..9e71a914b 100644 --- a/apps/pebbled/metadata.json +++ b/apps/pebbled/metadata.json @@ -2,7 +2,7 @@ "id": "pebbled", "name": "Pebble Clock with distance", "shortName": "Pebble + distance", - "version": "0.02", + "version": "0.03", "description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).", "readme": "README.md", "icon": "pebbled.png", diff --git a/apps/pebbled/pebbled.app.js b/apps/pebbled/pebbled.app.js index 472ac8fff..627a7651c 100644 --- a/apps/pebbled/pebbled.app.js +++ b/apps/pebbled/pebbled.app.js @@ -8,14 +8,16 @@ Graphics.prototype.setFontLECO1976Regular22 = function(scale) { g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/nA/+cD/5wP/nAAAAAAAAPwAA/gAD+AAPwAAAAAD+AAP4AA/gAAAAAAAAAAAAAcOAP//A//8D//wP//AHDgAcOAP//A//8D//wP//AHDgAAAAAAAAH/jgf+OB/44H/jj8OP/w4//Dj/8OPxw/4HD/gcP+Bw/4AAAAAAAP+AA/8AD/wQOHHA4c8D//wP/8A//gAD4AAfAAH/8A//wP//A84cDjhwIP/AA/8AB/wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8ABwAAAAAAAAD8AAP4AA/gAD8AAAAAAAAAAAEAAD+AB//A///v/D//gB/wABwAAAAAADgAA/wAf/4P8///wf/4AP8AAOAAAAAAAAAyAAHcAAPwAD/gAP/AA/8AA/AAH8AAMwAAAAAAAAAAAAADgAAOAAA4AAf8AD/wAP/AA/8AAOAAA4AADgAAAAAAAAAAD8AAfwAB/AAD8AAAAAAAADgAAOAAA4AADgAAOAAA4AADgAAAAAAAAAADgAAOAAA4AADgAAAAAAAAABwAB/AA/8A//gP/gA/wADwAAIAAAAAAD//wP//A//8D//wOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA4AcDgBwOAHA//8D//wP//A//8AABwAAHAAAcAAAAAAAA+f8D5/wPn/A+f8DhxwOHHA4ccDhxwP/HA/8cD/xwP/HAAAAAAAAOAHA4AcDhxwOHHA4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/wAP/AA/8AD/wAAHAAAcAABwAAHAA//8D//wP//A//8AAAAAAAA/98D/3wP/fA/98DhxwOHHA4ccDhxwOH/A4f8Dh/wOH/AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccDh/wOH/A4f8Dh/wAAAAAAAD4AAPgAA+AADgAAOAAA4AADgAAP//A//8D//wP//AAAAAAAAP//A//8D//wP//A4ccDhxwOHHA4ccD//wP//A//8D//wAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA//8D//wP//A//8AAAAAAAAOA4A4DgDgOAOA4AAAAAAAAOA/A4H8DgfwOA/AAAAAAAAB4AAPwAA/AAD8AAf4ABzgAPPAA8cAHh4AAAAAAAAAAAAHHAAccABxwAHHAAccABxwAHHAAccABxwAHHAAAAAAAAAOHAA4cADzwAPPAAf4AB/gAD8AAPwAAeAAB4AAAAAAAAA+AAD4AAPgAA+ecDh9wOH3A4fcDhwAP/AA/8AD/wAP/AAAAAAAAAP//4///j//+P//44ADjn/OOf845/zjnHOP8c4//zj//OP/84AAAAAAAP//A//8D//wP//A4cADhwAOHAA4cAD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA//8D//wP9/A/j8AAAAAAAA//8D//wP//A//8DgBwOAHA4AcDgBwOAHA4AcDgBwOAHAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA8A8D//wH/+AP/wAf+AAAAAAAAD//wP//A//8D//wOHHA4ccDhxwOHHA4ccDhxwOAHA4AcAAAAAAAA//8D//wP//A//8DhwAOHAA4cADhwAOHAA4cADgAAOAAAAAAD//wP//A//8D//wOAHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA//8D//wP//A//8ABwAAHAAAcAABwAP//A//8D//wP//AAAAAAAAP//A//8D//wP//AAAAAAAAOAHA4AcDgBwOAHA4AcDgBwOAHA//8D//wP//A//8AAAAAAAA//8D//wP//A//8AHwAA/AAP8AB/wAPn/A8f8DB/wIH/AAAAAAAAP//A//8D//wP//AAAcAABwAAHAAAcAABwAAHAAAAAAAAP//A//8D//wP//Af8AAP+AAH/AAD8AAHwAD/AB/wAf8AP+AA//8D//wP//AAAAAAAAP//A//8D//wP//AfwAAfwAAfwAAfwAAfwP//A//8D//wAAAAAAAAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//wP//A//8D//wAAAAAAAD//wP//A//8D//wOHAA4cADhwAOHAA/8AD/wAP/AA/8AAAAAP//A//8D//wP//A4AcDgBwOAHA4AcD//+P//4///j//+AAA4AADgAAAP//A//8D//wP//A4eADh+AOH8A4f4D/3wP/HA/8MD/wQAAAAAAAD/xwP/HA/8cD/xwOHHA4ccDhxwOHHA4f8Dh/wOH/A4f8AAAAAAAA4AADgAAOAAA//8D//wP//A//8DgAAOAAA4AADgAAAAAA//8D//wP//A//8AABwAAHAAAcAABwP//A//8D//wP//AAAADAAAPgAA/wAD/4AB/8AA/8AAfwAB/AA/8Af+AP/AA/wAD4AAMAAA4AAD+AAP/gA//8AH/wAB/AAf8Af/wP/4A/4AD/gAP/4AH/8AB/wAB/AB/8D//wP/gA/gADgAAIABA4AcDwDwPw/Afn4Af+AA/wAD/AA//AH5+A/D8DwDwOAHAgAEAAAAP/AA/8AD/wAP/AAAf8AB/wAH/AAf8D/wAP/AA/8AD/wAAAAAAAADh/wOH/A4f8Dh/wOHHA4ccDhxwOHHA/8cD/xwP/HA/8cAAAAAAAAf//9///3///f//9wAA3AADcAAMAAAOAAA/gAD/wAH/8AB/8AA/wAAPAAAEAAAAHAADcAANwAB3///f//9///wAA"), 32, atob("BwYLDg4UDwYJCQwMBgkGCQ4MDg4ODg4NDg4GBgwMDA4PDg4ODg4NDg4GDQ4MEg8ODQ8ODgwODhQODg4ICQg="), 22+(scale<<8)+(1<<16)); }; +{ const SETTINGS_FILE = "pebbleDistance.json"; let settings; +let drawTimeout; -function loadSettings() { +let loadSettings = function() { settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'avStep': 0.75}; -} +}; -var img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA==")); +const img = require("heatshrink").decompress(atob("oFAwkEogA/AH4A/AH4A/AH4A/AE8AAAoeXoAfeDQUBmcyD7A+Dh///8QD649CiAfaHwUvD4sEHy0DDYIfEICg+Cn4fHICY+DD4nxcgojOHwgfEIAYfRCIQaDD4ZAFD5r7DH4//kAfRCIZ/GAAnwD5p9DX44fTHgYSBf4ofVDAQEBl4fFUAgfOXoQzBgIfFBAIfPP4RAEAoYAB+cRiK/SG4h/WIBAfXIA7CBAAswD55AHn6fUIBMCD65AHl4gCmcziAfQQJqfQQJpiDgk0IDXxQLRAEECaBM+QgRYRYgUIA0CD4ggSQJiDCiAKBICszAAswD55AHABKBVD7BAFABIqBD5pAFABPxD55AOD6BADiIAJQAyxLABwf/gaAPAH4A/AH4ARA==")); const h = g.getHeight(); const w = g.getWidth(); @@ -25,12 +27,12 @@ const h3 = 7*h/8 - 10; let batteryWarning = false; -function draw() { +let draw = function() { let date = new Date(); let da = date.toString().split(" "); let timeStr = da[4].substr(0,5); const t = 6; - const stps = getSteps(); + let stps = Bangle.getHealthStatus("day").steps; // turn the warning on once we have dipped below 15% if (E.getBattery() < 15) @@ -80,17 +82,24 @@ function draw() { g.setColor(settings.bg); g.drawImage(img, w/2 + ((w/2) - 64)/2, -2, { scale: 1 }); drawCalendar(((w/2) - 42)/2, 11, 42, 4, da[2]); - - // distance + + // distance if (settings.color == 'Blue' || settings.color == 'Red') g.setColor('#fff'); // white on blue or red best contrast else g.setColor('#000'); // otherwise black regardless of theme g.drawString((stps / 1000 * settings.avStep).toFixed(2) + ' KM', w/2, ha + 107); -} + + // queue next draw + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +}; // at x,y width:wi thicknes:th -function drawCalendar(x,y,wi,th,str) { +let drawCalendar = function(x,y,wi,th,str) { g.setColor(g.theme.fg); g.fillRect(x, y, x + wi, y + wi); g.setColor(g.theme.bg); @@ -106,24 +115,23 @@ function drawCalendar(x,y,wi,th,str) { g.setFontLECO1976Regular22(); g.setFontAlign(0, 0); g.drawString(str, x + wi/2, y + wi/2 + th); -} +}; -function getSteps() { - if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return '0'; -} +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontLECO1976Regular22; + delete Graphics.prototype.setFontLECO1976Regular42; + require("widget_utils").show(); // re-show widgets + }}); -Bangle.setUI("clock"); g.clear(); Bangle.loadWidgets(); -/* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ -for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} +require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe loadSettings(); -setInterval(draw, 15000); // refresh every 15s draw(); +} diff --git a/apps/pisteinen/ChangeLog b/apps/pisteinen/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/pisteinen/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/pisteinen/README.md b/apps/pisteinen/README.md new file mode 100644 index 000000000..20e8bf9a1 --- /dev/null +++ b/apps/pisteinen/README.md @@ -0,0 +1,7 @@ +# Pisteinen + +By Jukio Kallio + +A Minimal digital watch face consisting of dots. Big dots for hours, small dots for minutes. + +![](screenshot1.png) diff --git a/apps/pisteinen/app-icon.js b/apps/pisteinen/app-icon.js new file mode 100644 index 000000000..d8ad05c50 --- /dev/null +++ b/apps/pisteinen/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkDmYA0/4AKCpM/CxYAB+YtTGJQuOGBAWPGAwuQGAwXvCyJgFC+UhiQDNC43ygEAl4DLC4/xBYMfAZYXfI653XX/6//X/6//O5gBKU5gGBAZAXfI66//C7s/CyPzC+ZgSCwgwRFwowRCwwwPFw4xOCpIArA==")) diff --git a/apps/pisteinen/app.js b/apps/pisteinen/app.js new file mode 100644 index 000000000..a455875ec --- /dev/null +++ b/apps/pisteinen/app.js @@ -0,0 +1,121 @@ +// Pisteinen +// +// Bangle.js 2 watch face +// by Jukio Kallio +// www.jukiokallio.com + + +// settings +const watch = { + x:0, y:0, w:0, h:0, + bgcolor:g.theme.bg, + fgcolor:g.theme.fg, +}; + +// set some additional settings +watch.w = g.getWidth(); // size of the background +watch.h = g.getHeight(); +watch.x = watch.w * 0.5; // position of the circles +watch.y = watch.h * 0.5; + +var wait = 60000; // wait time, normally a minute + + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, wait - (Date.now() % wait)); +} + + +// main function +function draw() { + // make date object + var date = new Date(); + + // Reset the state of the graphics library + g.reset(); + + // Clear the area where we want to draw the time + g.setColor(watch.bgcolor); + g.fillRect(0, 0, watch.w, watch.h); + + // setup watch face + const hball = { + size: 9, + pad: 9, + }; + const mball = { + size: 3, + pad: 4, + pad2: 2, + }; + + // get hours and minutes + var hour = date.getHours(); + var minute = date.getMinutes(); + + // calculate size of the hour face + var hfacew = (hball.size * 2 + hball.pad) * 6 - hball.pad; + var hfaceh = (hball.size * 2 + hball.pad) * 4 - hball.pad; + var mfacew = (mball.size * 2 + mball.pad) * 15 - mball.pad + mball.pad2 * 2; + var mfaceh = (mball.size * 2 + mball.pad) * 4 - mball.pad; + var faceh = hfaceh + mfaceh + hball.pad + mball.pad; + + g.setColor(watch.fgcolor); // set foreground color + + // draw hour balls + for (var i = 0; i < 24; i++) { + var x = ((hball.size * 2 + hball.pad) * (i % 6)) + (watch.x - hfacew / 2) + hball.size; + var y = watch.y - faceh / 2 + hball.size; + if (i >= 6) y += hball.size * 2 + hball.pad; + if (i >= 12) y += hball.size * 2 + hball.pad; + if (i >= 18) y += hball.size * 2 + hball.pad; + + if (i < hour) g.fillCircle(x, y, hball.size); else g.drawCircle(x, y, hball.size); + } + + // draw minute balls + for (var j = 0; j < 60; j++) { + var x2 = ((mball.size * 2 + mball.pad) * (j % 15)) + (watch.x - mfacew / 2) + mball.size; + if (j % 15 >= 5) x2 += mball.pad2; + if (j % 15 >= 10) x2 += mball.pad2; + var y2 = watch.y - faceh / 2 + hfaceh + mball.size + hball.pad + mball.pad; + if (j >= 15) y2 += mball.size * 2 + mball.pad; + if (j >= 30) y2 += mball.size * 2 + mball.pad; + if (j >= 45) y2 += mball.size * 2 + mball.pad; + + if (j < minute) g.fillCircle(x2, y2, mball.size); else g.drawCircle(x2, y2, mball.size); + } + + + // queue draw + queueDraw(); +} + + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + + +// Show launcher when middle button pressed +Bangle.setUI("clock"); diff --git a/apps/pisteinen/app.png b/apps/pisteinen/app.png new file mode 100644 index 000000000..a6c441423 Binary files /dev/null and b/apps/pisteinen/app.png differ diff --git a/apps/pisteinen/metadata.json b/apps/pisteinen/metadata.json new file mode 100644 index 000000000..f1137e589 --- /dev/null +++ b/apps/pisteinen/metadata.json @@ -0,0 +1,16 @@ +{ "id": "pisteinen", + "name": "Pisteinen - Dotted watch face", + "shortName":"Pisteinen", + "version":"0.01", + "description": "A minimal digital watch face made with dots.", + "icon": "app.png", + "screenshots": [{"url":"screenshot1.png"}], + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"pisteinen.app.js","url":"app.js"}, + {"name":"pisteinen.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/pisteinen/screenshot1.png b/apps/pisteinen/screenshot1.png new file mode 100644 index 000000000..556c004c0 Binary files /dev/null and b/apps/pisteinen/screenshot1.png differ diff --git a/apps/podadrem/ChangeLog b/apps/podadrem/ChangeLog new file mode 100644 index 000000000..c26e40c0e --- /dev/null +++ b/apps/podadrem/ChangeLog @@ -0,0 +1,6 @@ +0.01: Inital release. +0.02: Misc fixes. Add Search and play. +0.03: Simplify "Search and play" function after some bugfixes to Podcast +Addict. +0.04: New layout. +0.05: Add widget field, tweak layout. diff --git a/apps/podadrem/README.md b/apps/podadrem/README.md new file mode 100644 index 000000000..3760e6b5b --- /dev/null +++ b/apps/podadrem/README.md @@ -0,0 +1,21 @@ +Requires Gadgetbridge 71.0 or later. Allow intents in Gadgetbridge in order for this app to work. + +Touch input: + +Press the different ui elements to control Podcast Addict and open menus. +Press left or right arrow to move backward/forward in current playlist. + +Swipe input: + +Swipe left/right to jump backward/forward within the current podcast episode. +Swipe up/down to change the volume. + +It's possible to start a podcast by searching with the remote. It's also possible to change the playback speed. + +The swipe logic was inspired by the implementation in [rigrig](https://git.tubul.net/rigrig/)'s Scrolling Messages. + +Podcast Addict Remote was created by [thyttan](https://github.com/thyttan/). + +Podcast Addict is developed by [Xavier Guillemane](https://twitter.com/xguillem) and can be installed via the [Google Play Store](https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US). + +The Podcast Addict icon is used with permission. diff --git a/apps/podadrem/app-icon.js b/apps/podadrem/app-icon.js new file mode 100644 index 000000000..fc4406666 --- /dev/null +++ b/apps/podadrem/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwgHEhvdABnQDwwVNAAYtTGI4WSGAgWTGAYXUGAJGUGAQXXCyoXKmf/AAPznogQn4WCAAQYP6YWFDB4WFJQhFSA4gwMIYogEGBffLg0zKAYwKRgwTDBQP9Ix09n7DCpowBJBKNEBwIXBAQIsBMwgXKIQReCDoRgJOwYQDLQU/poMBC5B2DIAUzLwIKBnoXBPBAXEIQQVDA4IXNCIQXaWAgXNI4kzNQoXLO4wXLU4a+CU4gXR7ovBcIoXIBobMFPAgXILQKPDmgxCR5omDc4QAHC5ITCC6hgCC6hICC6owBC6phBC6zcFAAMzeogALdQjdBC6AZCeYfTmczAwfQhocOAAwXYgAXVgAXVFwMAJCgXCDCYWDJKYWEGKAtEA==")) diff --git a/apps/podadrem/app.js b/apps/podadrem/app.js new file mode 100644 index 000000000..b04d80b17 --- /dev/null +++ b/apps/podadrem/app.js @@ -0,0 +1,375 @@ +/* +Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}})); + +Podcast Addict is developed by Xavier Guillemane and can be downloaded on Google Play Store: https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US + +Podcast Addict can be controlled through the sending of remote commands called 'Intents'. +Some 3rd parties apps specialized in task automation will then allow you to control Podcast Addict. For example, you will be able to wake up to the sound of your playlist or to start automatically playing when some NFC tag has been detected. +In Tasker, you just need to copy/paste one of the following intent in the task Action field ("Misc" action type then select "Send Itent") . +If you prefer Automate It, you can use the Podcast Addict plugin that will save you some configuration time (https://play.google.com/store/apps/details?id=com.smarterapps.podcastaddictplugin ) +Before using an intent make sure to set the following: +Package: com.bambuna.podcastaddict +Class (UPDATE intent only): com.bambuna.podcastaddict.receiver.PodcastAddictBroadcastReceiver +Class (every other intent): com.bambuna.podcastaddict.receiver.PodcastAddictPlayerReceiver +Here are the supported commands (Intents) : +com.bambuna.podcastaddict.service.player.toggle – Toggle the playlist +com.bambuna.podcastaddict.service.player.stop – Stop the player and release its resources +com.bambuna.podcastaddict.service.player.play – Start playing the playlist +com.bambuna.podcastaddict.service.player.pause – Pause the playlist +com.bambuna.podcastaddict.service.player.nexttrack – Start playing next track +com.bambuna.podcastaddict.service.player.previoustrack – Start playing previous track +com.bambuna.podcastaddict.service.player.jumpforward – Jump 30s forward +com.bambuna.podcastaddict.service.player.jumpbackward – Jump 15s backward +com.bambuna.podcastaddict.service.player.1xspeed - Disable the variable playback speed +com.bambuna.podcastaddict.service.player.1.5xspeed – Force the playback speed at 1.5x +com.bambuna.podcastaddict.service.player.2xspeed – Force the playback speed at 2.0x +com.bambuna.podcastaddict.service.player.stoptimer – Disable the timer +com.bambuna.podcastaddict.service.player.15mntimer – Set the timer at 15 minutes +com.bambuna.podcastaddict.service.player.30mntimer – Set the timer at 30 minutes +com.bambuna.podcastaddict.service.player.60mntimer – Set the timer at 1 hour +com.bambuna.podcastaddict.service.update – Trigger podcasts update +com.bambuna.podcastaddict.openmainscreen – Open the app on the Main screen +com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen +com.bambuna.podcastaddict.openplayer – Open the app on the Player screen +com.bambuna.podcastaddict.opennewepisodes – Open the app on the New episodes screen +com.bambuna.podcastaddict.opendownloadedepisodes – Open the app on the Downloaded episodes screen +com.bambuna.podcastaddict.service.player.playfirstepisode – Start playing the first episode in the playlist +com.bambuna.podcastaddict.service.player.customspeed – Select playback speed +In order to use this intent you need to pass a float argument called "arg1". Valid values are within [0.1, 5.0] +com.bambuna.podcastaddict.service.player.customtimer – Start a custom timer +In order to use this intent you need to pass an int argument called "arg1" containing the number of minutes. Valid values are within [1, 1440] +com.bambuna.podcastaddict.service.player.deletecurrentskipnexttrack – Delete the current episode and skip to the next one. It behaves the same way as long pressing on the player >| button, but doesn't display any confirmation popup. +com.bambuna.podcastaddict.service.player.deletecurrentskipprevioustrack – Delete the current episode and skip to the previous one. It behaves the same way as long pressing on the player |< button, but doesn't display any confirmation popup. +com.bambuna.podcastaddict.service.player.boostVolume – Toggle the Volume Boost audio effect +You can pass a, optional boolean argument called "arg1" in order to create a ON or OFF button for the volume boost. Without this parameter the app will just toggle the current value +com.bambuna.podcastaddict.service.player.quickBookmark – Creates a bookmark at the current playback position so you can easily retrieve it later. +com.bambuna.podcastaddict.service.download.pause – Pause downloads +com.bambuna.podcastaddict.service.download.resume – Resume downloads +com.bambuna.podcastaddict.service. download.toggle – Toggle downloads +com.bambuna.podcastaddict.service.player.favorite – Mark the current episode playing as favorite. +com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen +You can pass an optional string argument called "arg1" in order to select the playlist to open. Without this parameter the app will open the current playlist +Here's how it works: +##AUDIO## will open the Audio playlist screen +##VIDEO## will open the Video playlist screen +##RADIO## will open the Radio screen +Any other argument will be used as a CATEGORY name. The app will then open this category under the playlist CUSTOM tab +You can pass an optional boolean argument called "arg2" in order to select if the app UI should be opened. Without this parameter the playlist will be displayed +You can pass an optional boolean argument called "arg3" in order to select if the app should start playing the selected playlist. Without this parameter the playback won't start +Since v2020.3 +com.bambuna.podcastaddict.service.full_backup – Trigger a full backup of the app data (relies on the app automatic backup settings for the folder and the # of backup to keep) +This task takes a lot of resources and might take up to a minute to complete, so please avoid using the app at the same time +Since v2020.15 +com.bambuna.podcastaddict.service.player.toggletimer – This will toggle the Sleep Timer using the last duration and parameter used in the app. +Since v2020.16 +com.bambuna.podcastaddict.service.player.togglespeed – This will toggle the Playback speed for the episode currently playing (alternate between selected speed and 1.0x). +*/ + +var R; +var backToMenu = false; +var dark = g.theme.dark; // bool + +// The main layout of the app +function gfx() { + //Bangle.drawWidgets(); + R = Bangle.appRect; + marigin = 8; + // g.drawString(str, x, y, solid) + g.clearRect(R); + g.reset(); + + if (dark) {g.setColor(0xFD20);} else {g.setColor(0xF800);} // Orange on dark theme, RED on light theme. + g.setFont("4x6:2"); + g.setFontAlign(1, 0, 0); + g.drawString("->", R.x2 - marigin, R.y + R.h/2); + + g.setFontAlign(-1, 0, 0); + g.drawString("<-", R.x + marigin, R.y + R.h/2); + + g.setFontAlign(-1, 0, 1); + g.drawString("<-", R.x + R.w/2, R.y + marigin); + + g.setFontAlign(1, 0, 1); + g.drawString("->", R.x + R.w/2, R.y2 - marigin); + + g.setFontAlign(0, 0, 0); + g.drawString("Play\nPause", R.x + R.w/2, R.y + R.h/2); + + g.setFontAlign(-1, -1, 0); + g.drawString("Menu", R.x + 2*marigin, R.y + 2*marigin); + + g.setFontAlign(-1, 1, 0); + g.drawString("Wake", R.x + 2*marigin, R.y + R.h - 2*marigin); + + g.setFontAlign(1, -1, 0); + g.drawString("Srch", R.x + R.w - 2*marigin, R.y + 2*marigin); + + g.setFontAlign(1, 1, 0); + g.drawString("Speed", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin); +} + +// Touch handler for main layout +function touchHandler(_, xy) { + x = xy.x; + y = xy.y; + len = (R.wb-1 instead of a>b. + if ((R.x-1 { + if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); + } + ); + Bangle.on("touch", touchHandler); + Bangle.on("swipe", swipeHandler); +} + +/* +The functions for interacting with Android and the Podcast Addict app +*/ + +pkg = "com.bambuna.podcastaddict"; +standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver"; +updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver"; +speed = 1.0; + +simpleSearch = ""; + +function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track) + require("textinput").input({ + text: simpleSearch + }).then(result => { + simpleSearch = result; + }).then(() => { + E.showMenu(searchMenu); + }); +} + +function searchPlayWOTags() { //make a search and play using entered terms + searchString = simpleSearch; + Bluetooth.println(JSON.stringify({ + t: "intent", + action: "android.media.action.MEDIA_PLAY_FROM_SEARCH", + package: pkg, + target: "activity", + extra: { + query: searchString + }, + flags: ["FLAG_ACTIVITY_NEW_TASK"] + })); +} + +function gadgetbridgeWake() { + Bluetooth.println(JSON.stringify({ + t: "intent", + target: "activity", + flags: ["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], + package: "gadgetbridge", + class: "nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity" + })); +} + +// For stringing together the action for Podcast Addict to perform +function actFn(actName, activOrServ) { + return "com.bambuna.podcastaddict." + (activOrServ == "service" ? "service." : "") + actName; +} + +// Send the intent message to Gadgetbridge +function btMsg(activOrServ, cls, actName, xtra) { + + Bluetooth.println(JSON.stringify({ + t: "intent", + action: actFn(actName, activOrServ), + package: pkg, + class: cls, + target: "broadcastreceiver", + extra: xtra + })); +} + +// Get back to the main layout +function backToGfx() { + E.showMenu(); + g.clear(); + g.reset(); + Bangle.removeAllListeners("touch"); + Bangle.removeAllListeners("swipe"); + setUI(); + gfx(); + backToMenu = false; +} + +// Podcast Addict Menu +var paMenu = { + "": { + title: " ", + back: backToGfx + }, + "Controls": () => { + E.showMenu(controlMenu); + }, + "Speed Controls": () => { + E.showMenu(speedMenu); + }, + "Search and play": () => { + E.showMenu(searchMenu); + }, + "Navigate and play": () => { + E.showMenu(navigationMenu); + }, + "Wake the android": () => { + gadgetbridgeWake(); + gadgetbridgeWake(); + }, + "Exit PA Remote": ()=>{load();} +}; + + +var controlMenu = { + "": { + title: " ", + back: () => {if (backToMenu) E.showMenu(paMenu); + if (!backToMenu) backToGfx(); + } + }, + "Toggle Play/Pause": () => { + btMsg("service", standardCls, "player.toggle"); + }, + "Jump Backward": () => { + btMsg("service", standardCls, "player.jumpbackward"); + }, + "Jump Forward": () => { + btMsg("service", standardCls, "player.jumpforward"); + }, + "Previous": () => { + btMsg("service", standardCls, "player.previoustrack"); + }, + "Next": () => { + btMsg("service", standardCls, "player.nexttrack"); + }, + "Play": () => { + btMsg("service", standardCls, "player.play"); + }, + "Pause": () => { + btMsg("service", standardCls, "player.pause"); + }, + "Stop": () => { + btMsg("service", standardCls, "player.stop"); + }, + "Update": () => { + btMsg("service", updateCls, "update"); + }, + "Messages Music Controls": () => { + load("messagesmusic.app.js"); + }, +}; + +var speedMenu = { + "": { + title: " ", + back: () => {if (backToMenu) E.showMenu(paMenu); + if (!backToMenu) backToGfx(); + } + }, + "Regular Speed": () => { + speed = 1.0; + btMsg("service", standardCls, "player.1xspeed"); + }, + "1.5x Regular Speed": () => { + speed = 1.5; + btMsg("service", standardCls, "player.1.5xspeed"); + }, + "2x Regular Speed": () => { + speed = 2.0; + btMsg("service", standardCls, "player.2xspeed"); + }, + //"Faster" : ()=>{speed+=0.1; speed=((speed>5.0)?5.0:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});}, + //"Slower" : ()=>{speed-=0.1; speed=((speed<0.1)?0.1:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});}, +}; + +var searchMenu = { + "": { + title: " ", + + back: () => {if (backToMenu) E.showMenu(paMenu); + if (!backToMenu) backToGfx();} + + }, + "Search term": () => { + simpleSearchTerm(); + }, + "Execute search and play": () => { + btMsg("service", standardCls, "player.play"); + setTimeout(() => { + searchPlayWOTags(); + setTimeout(() => { + btMsg("service", standardCls, "player.play"); + }, 200); + }, 1500); + }, + "Simpler search and play" : searchPlayWOTags, +}; + +var navigationMenu = { + "": { + title: " ", + back: () => {if (backToMenu) E.showMenu(paMenu); + if (!backToMenu) backToGfx();} + }, + "Open Main Screen": () => { + btMsg("activity", standardCls, "openmainscreen"); + }, + "Open Player Screen": () => { + btMsg("activity", standardCls, "openplayer"); + }, +}; + +Bangle.loadWidgets(); +setUI(); +gfx(); diff --git a/apps/podadrem/app.png b/apps/podadrem/app.png new file mode 100644 index 000000000..b9cdf4fed Binary files /dev/null and b/apps/podadrem/app.png differ diff --git a/apps/podadrem/metadata.json b/apps/podadrem/metadata.json new file mode 100644 index 000000000..929269762 --- /dev/null +++ b/apps/podadrem/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "podadrem", + "name": "Podcast Addict Remote", + "shortName": "PA Remote", + "version": "0.05", + "description": "Control Podcast Addict on your android device.", + "readme": "README.md", + "type": "app", + "tags": "remote,podcast,podcasts,radio,player,intent,intents,gadgetbridge,podadrem,pa remote", + "icon": "app.png", + "screenshots" : [ {"url":"screenshot1.png"}, {"url":"screenshot2.png"} ], + "supports": ["BANGLEJS2"], + "dependencies": { "textinput":"type"}, + "storage": [ + {"name":"podadrem.app.js","url":"app.js"}, + {"name":"podadrem.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/podadrem/screenshot1.png b/apps/podadrem/screenshot1.png new file mode 100644 index 000000000..50f3f17f1 Binary files /dev/null and b/apps/podadrem/screenshot1.png differ diff --git a/apps/podadrem/screenshot2.png b/apps/podadrem/screenshot2.png new file mode 100644 index 000000000..e20c808a2 Binary files /dev/null and b/apps/podadrem/screenshot2.png differ diff --git a/apps/poikkipuinen/ChangeLog b/apps/poikkipuinen/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/poikkipuinen/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/poikkipuinen/README.md b/apps/poikkipuinen/README.md new file mode 100644 index 000000000..12f8d5d7e --- /dev/null +++ b/apps/poikkipuinen/README.md @@ -0,0 +1,7 @@ +# Poikkipuinen + +By Jukio Kallio + +A Minimal digital watch face. Follows the theme colors. + +![](screenshot1.png) diff --git a/apps/poikkipuinen/app-icon.js b/apps/poikkipuinen/app-icon.js new file mode 100644 index 000000000..d7ddba399 --- /dev/null +++ b/apps/poikkipuinen/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEogA0/4AKCpNPCxYAB+gtTGJQuOGBAWPGAwuQGAwXamQULkYXGBQUgn4WJ+cCMAwXNiQXV+MBC6swh4XU+cAn4XU+IUBC6kgj4XUIwKnV+EDC6sQl4XU+UBd6q8BC6q8BC6i8CC6i8CC6a8DC6a8DC6a8DC6S8EC6S8EC6S8EC6K8FC6K8FC6C8BIwwXOXgwXQXgwXQkIWHd6IXPp4GBmQWJAAMjAQP0C4wAPC7hgDABwWEGCIuFGCIWGGB4uHGJwVJAFY=")) diff --git a/apps/poikkipuinen/app.js b/apps/poikkipuinen/app.js new file mode 100644 index 000000000..0bf09c5e5 --- /dev/null +++ b/apps/poikkipuinen/app.js @@ -0,0 +1,158 @@ +// Poikkipuinen +// +// Bangle.js 2 watch face +// by Jukio Kallio +// www.jukiokallio.com + +require("Font5x9Numeric7Seg").add(Graphics); +require("FontSinclair").add(Graphics); + +// settings +const watch = { + x:0, y:0, w:0, h:0, + bgcolor:g.theme.bg, + fgcolor:g.theme.fg, + font: "5x9Numeric7Seg", fontsize: 1, + font2: "Sinclair", font2size: 1, + finland:true, // change if you want Finnish style date, or US style +}; + + +// set some additional settings +watch.w = g.getWidth(); // size of the background +watch.h = g.getHeight(); +watch.x = watch.w * 0.5; // position of the circles +watch.y = watch.h * 0.41; + +const dateWeekday = { 0: "SUN", 1: "MON", 2: "TUE", 3: "WED", 4:"THU", 5:"FRI", 6:"SAT" }; // weekdays + +var wait = 60000; // wait time, normally a minute + + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, wait - (Date.now() % wait)); +} + + +// main function +function draw() { + // make date object + var date = new Date(); + + // work out the date string + var dateDay = date.getDate(); + var dateMonth = date.getMonth() + 1; + var dateYear = date.getFullYear(); + var dateStr = dateMonth + "." + dateDay + "." + dateYear; + if (watch.finland) dateStr = dateDay + "." + dateMonth + "." + dateYear; // the true way of showing date + var dateStr2 = dateWeekday[date.getDay()]; + + // Reset the state of the graphics library + g.reset(); + + // Clear the area where we want to draw the time + g.setColor(watch.bgcolor); + g.fillRect(0, 0, watch.w, watch.h); + + // set foreground color + g.setColor(watch.fgcolor); + g.setFontAlign(1,-1).setFont(watch.font, watch.fontsize); + + // watch face size + var facew, faceh; // halves of the size for easier calculation + facew = 50; + faceh = 59; + + // save hour and minute y positions + var houry, minutey; + + // draw hour meter + g.drawLine(watch.x - facew, watch.y - faceh, watch.x - facew, watch.y + faceh); + var lines = 13; + var lineh = faceh * 2 / (lines - 2); + for (var i = 1; i < lines; i++) { + var w = 3; + var y = faceh - lineh * (i - 1); + + if (i % 3 == 0) { + // longer line and numbers every 3 + w = 5; + g.drawString(i, watch.x - facew - 2, y + watch.y); + } + + g.drawLine(watch.x - facew, y + watch.y, watch.x - facew + w, y + watch.y); + + // get hour y position + var hour = date.getHours() % 12; // modulate away the 24h + if (hour == 0) hour = 12; // fix a problem with 0-23 hours + //var hourMin = date.getMinutes() / 60; // move hour line by minutes + var hourMin = Math.floor(date.getMinutes() / 15) / 4; // move hour line by 15-minutes + if (hour == 12) hourMin = 0; // don't do minute moving if 12 (line ends there) + if (i == hour) houry = y - (lineh * hourMin); + } + + // draw minute meter + g.drawLine(watch.x + facew, watch.y - faceh, watch.x + facew, watch.y + faceh); + g.setFontAlign(-1,-1); + lines = 60; + lineh = faceh * 2 / (lines - 1); + for (i = 0; i < lines; i++) { + var mw = 3; + var my = faceh - lineh * i; + + if (i % 15 == 0 && i != 0) { + // longer line and numbers every 3 + mw = 5; + g.drawString(i, watch.x + facew + 4, my + watch.y); + } + + //if (i % 2 == 0 || i == 15 || i == 45) + g.drawLine(watch.x + facew, my + watch.y, watch.x + facew - mw, my + watch.y); + + // get minute y position + if (i == date.getMinutes()) minutey = my; + } + + // draw the time + var timexpad = 8; + g.drawLine(watch.x - facew + timexpad, watch.y + houry, watch.x + facew - timexpad, watch.y + minutey); + + // draw date + var datey = 14; + g.setFontAlign(0,-1); + g.drawString(dateStr, watch.x, watch.y + faceh + datey); + g.setFontAlign(0,-1).setFont(watch.font2, watch.font2size); + g.drawString(dateStr2, watch.x, watch.y + faceh + datey*2); + + // queue draw + queueDraw(); +} + + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + + +// Show launcher when middle button pressed +Bangle.setUI("clock"); diff --git a/apps/poikkipuinen/app.png b/apps/poikkipuinen/app.png new file mode 100644 index 000000000..fa506c886 Binary files /dev/null and b/apps/poikkipuinen/app.png differ diff --git a/apps/poikkipuinen/metadata.json b/apps/poikkipuinen/metadata.json new file mode 100644 index 000000000..ec95ab7ce --- /dev/null +++ b/apps/poikkipuinen/metadata.json @@ -0,0 +1,16 @@ +{ "id": "poikkipuinen", + "name": "Poikkipuinen - Minimal watch face", + "shortName":"Poikkipuinen", + "version":"0.01", + "description": "A minimal digital watch face.", + "icon": "app.png", + "screenshots": [{"url":"screenshot1.png"}], + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"poikkipuinen.app.js","url":"app.js"}, + {"name":"poikkipuinen.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/poikkipuinen/screenshot1.png b/apps/poikkipuinen/screenshot1.png new file mode 100644 index 000000000..23fcc348c Binary files /dev/null and b/apps/poikkipuinen/screenshot1.png differ diff --git a/apps/pomoplus/ChangeLog b/apps/pomoplus/ChangeLog new file mode 100644 index 000000000..1a137aad0 --- /dev/null +++ b/apps/pomoplus/ChangeLog @@ -0,0 +1,3 @@ +0.01: New app! +0.02-0.04: Bug fixes +0.05: Submitted to the app loader \ No newline at end of file diff --git a/apps/pomoplus/app.js b/apps/pomoplus/app.js new file mode 100644 index 000000000..73af5c935 --- /dev/null +++ b/apps/pomoplus/app.js @@ -0,0 +1,157 @@ +Bangle.POMOPLUS_ACTIVE = true; //Prevent the boot code from running. To avoid having to reload on every interaction, we'll control the vibrations from here when the user is in the app. + +const storage = require("Storage"); +const common = require("pomoplus-com.js"); + +//Expire the state if necessary +if ( + common.settings.pausedTimerExpireTime != 0 && + !common.state.running && + (new Date()).getTime() - common.state.pausedTime > common.settings.pausedTimerExpireTime +) { + common.state = common.STATE_DEFAULT; +} + +function drawButtons() { + //Draw the backdrop + const BAR_TOP = g.getHeight() - 24; + g.setColor(0, 0, 1).setFontAlign(0, -1) + .clearRect(0, BAR_TOP, g.getWidth(), g.getHeight()) + .fillRect(0, BAR_TOP, g.getWidth(), g.getHeight()) + .setColor(1, 1, 1); + + if (!common.state.wasRunning) { //If the timer was never started, only show a play button + g.drawImage(common.BUTTON_ICONS.play, g.getWidth() / 2, BAR_TOP); + } else { + g.drawLine(g.getWidth() / 2, BAR_TOP, g.getWidth() / 2, g.getHeight()); + if (common.state.running) { + g.drawImage(common.BUTTON_ICONS.pause, g.getWidth() / 4, BAR_TOP) + .drawImage(common.BUTTON_ICONS.skip, g.getWidth() * 3 / 4, BAR_TOP); + } else { + g.drawImage(common.BUTTON_ICONS.reset, g.getWidth() / 4, BAR_TOP) + .drawImage(common.BUTTON_ICONS.play, g.getWidth() * 3 / 4, BAR_TOP); + } + } +} + +function drawTimerAndMessage() { + g.reset() + .setFontAlign(0, 0) + .setFont("Vector", 36) + .clearRect(0, 24, 176, 152) + + //Draw the timer + .drawString((() => { + let timeLeft = common.getTimeLeft(); + let hours = timeLeft / 3600000; + let minutes = (timeLeft % 3600000) / 60000; + let seconds = (timeLeft % 60000) / 1000; + + function pad(number) { + return ('00' + parseInt(number)).slice(-2); + } + + if (hours >= 1) return `${parseInt(hours)}:${pad(minutes)}:${pad(seconds)}`; + else return `${parseInt(minutes)}:${pad(seconds)}`; + })(), g.getWidth() / 2, g.getHeight() / 2) + + //Draw the phase label + .setFont("Vector", 12) + .drawString(((currentPhase, numShortBreaks) => { + if (!common.state.wasRunning) return "Not started"; + else if (currentPhase == common.PHASE_WORKING) return `Work ${numShortBreaks + 1}/${common.settings.numShortBreaks + 1}` + else if (currentPhase == common.PHASE_SHORT_BREAK) return `Short break ${numShortBreaks + 1}/${common.settings.numShortBreaks}`; + else return "Long break!"; + })(common.state.phase, common.state.numShortBreaks), + g.getWidth() / 2, g.getHeight() / 2 + 18); + + //Update phase with vibation if needed + if (common.getTimeLeft() <= 0) { + common.nextPhase(true); + } +} + +drawButtons(); +Bangle.on("touch", (button, xy) => { + //If we support full touch and we're not touching the keys, ignore. + //If we don't support full touch, we can't tell so just assume we are. + if (xy !== undefined && xy.y <= g.getHeight() - 24) return; + + if (!common.state.wasRunning) { + //If we were never running, there is only one button: the start button + let now = (new Date()).getTime(); + common.state = { + wasRunning: true, + running: true, + startTime: now, + pausedTime: now, + elapsedTime: 0, + phase: common.PHASE_WORKING, + numShortBreaks: 0 + }; + setupTimerInterval(); + drawButtons(); + + } else if (common.state.running) { + //If we are running, there are two buttons: pause and skip + if (button == 1) { + //Record the exact moment that we paused + let now = (new Date()).getTime(); + common.state.pausedTime = now; + + //Stop the timer + common.state.running = false; + clearInterval(timerInterval); + timerInterval = undefined; + drawTimerAndMessage(); + drawButtons(); + + } else { + common.nextPhase(false); + } + + } else { + //If we are stopped, there are two buttons: Reset and continue + if (button == 1) { + //Reset the timer + common.state = common.STATE_DEFAULT; + drawTimerAndMessage(); + drawButtons(); + + } else { + //Start the timer and record old elapsed time and when we started + let now = (new Date()).getTime(); + common.state.elapsedTime += common.state.pausedTime - common.state.startTime; + common.state.startTime = now; + common.state.running = true; + drawTimerAndMessage(); + setupTimerInterval(); + drawButtons(); + } + } +}); + +let timerInterval; + +function setupTimerInterval() { + if (timerInterval !== undefined) { + clearInterval(timerInterval); + } + setTimeout(() => { + timerInterval = setInterval(drawTimerAndMessage, 1000); + drawTimerAndMessage(); + }, common.timeLeft % 1000); +} + +drawTimerAndMessage(); +if (common.state.running) { + setupTimerInterval(); +} + +//Save our state when the app is closed +E.on('kill', () => { + storage.writeJSON(common.STATE_PATH, common.state); +}); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/pomoplus/boot.js b/apps/pomoplus/boot.js new file mode 100644 index 000000000..edc233853 --- /dev/null +++ b/apps/pomoplus/boot.js @@ -0,0 +1,19 @@ +const POMOPLUS_storage = require("Storage"); +const POMOPLUS_common = require("pomoplus-com.js"); + +function setNextTimeout() { + setTimeout(() => { + //Make sure that the pomoplus app isn't in the foreground. The pomoplus app handles the vibrations when it is in the foreground in order to avoid having to reload every time the user changes state. That means that when the app is in the foreground, we shouldn't do anything here. + //We do this after the timer rather than before because the timer will start before the app executes. + if (Bangle.POMOPLUS_ACTIVE === undefined) { + POMOPLUS_common.nextPhase(true); + setNextTimeout(); + POMOPLUS_storage.writeJSON(POMOPLUS_common.STATE_PATH, POMOPLUS_common.state) + } + }, POMOPLUS_common.getTimeLeft()); +} + +//Only start the timeout if the timer is running +if (POMOPLUS_common.state.running) { + setNextTimeout(); +} \ No newline at end of file diff --git a/apps/pomoplus/common.js b/apps/pomoplus/common.js new file mode 100644 index 000000000..b1cd42de8 --- /dev/null +++ b/apps/pomoplus/common.js @@ -0,0 +1,118 @@ +const storage = require("Storage"); +const heatshrink = require("heatshrink"); + +exports.STATE_PATH = "pomoplus.state.json"; +exports.SETTINGS_PATH = "pomoplus.json"; + +exports.PHASE_WORKING = 0; +exports.PHASE_SHORT_BREAK = 1; +exports.PHASE_LONG_BREAK = 2; + +exports.BUTTON_ICONS = { + play: heatshrink.decompress(atob("jEYwMAkAGBnACBnwCBn+AAQPgAQPwAQP8AQP/AQXAAQPwAQP8AQP+AQgICBwQUCEAn4FggyBHAQ+CIgQ")), + pause: heatshrink.decompress(atob("jEYwMA/4BBAX4CEA")), + reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI=")), + skip: heatshrink.decompress(atob("jEYwMAwEIgHAhkA8EOgHwh8A/EPwH8h/A/0P8H/h/w/+P/H/5/8//v/3/AAoICBwQUCDQIgCEwQsCGQQ4CHwRECA")) +}; + +exports.settings = storage.readJSON(exports.SETTINGS_PATH); +if (!exports.settings) { + exports.settings = { + workTime: 1500000, //Work for 25 minutes + shortBreak: 300000, //5 minute short break + longBreak: 900000, //15 minute long break + numShortBreaks: 3, //3 short breaks for every long break + pausedTimerExpireTime: 21600000, //If the timer was left paused for >6 hours, reset it on next launch + widget: false //If a widget is added in the future, whether the user wants it + }; +} + +//Store the minimal amount of information to be able to reconstruct the state of the timer at any given time. +//This is necessary because it is necessary to write to flash to let the timer run in the background, so minimizing the writes is necessary. +exports.STATE_DEFAULT = { + wasRunning: false, //If the timer ever was running. Used to determine whether to display a reset button + running: false, //Whether the timer is currently running + startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously. + pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused. + elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages. + phase: exports.PHASE_WORKING, //What phase the timer is currently in + numShortBreaks: 0 //Number of short breaks that have occured so far +}; +exports.state = storage.readJSON(exports.STATE_PATH); +if (!exports.state) { + exports.state = exports.STATE_DEFAULT; +} + +//Get the number of milliseconds until the next phase change +exports.getTimeLeft = function () { + if (!exports.state.wasRunning) { + //If the timer never ran, the time left is just the amount of work time. + return exports.settings.workTime; + } else if (exports.state.running) { + //If the timer is running, the time left is current time - start time + preexisting time + var runningTime = (new Date()).getTime() - exports.state.startTime + exports.state.elapsedTime; + } else { + //If the timer is not running, the same as above but use when the timer was paused instead of now. + var runningTime = exports.state.pausedTime - exports.state.startTime + exports.state.elapsedTime; + } + + if (exports.state.phase == exports.PHASE_WORKING) { + return exports.settings.workTime - runningTime; + } else if (exports.state.phase == exports.PHASE_SHORT_BREAK) { + return exports.settings.shortBreak - runningTime; + } else { + return exports.settings.longBreak - runningTime; + } +} + +//Get the next phase to change to +exports.getNextPhase = function () { + if (exports.state.phase == exports.PHASE_WORKING) { + if (exports.state.numShortBreaks < exports.settings.numShortBreaks) { + return exports.PHASE_SHORT_BREAK; + } else { + return exports.PHASE_LONG_BREAK; + } + } else { + return exports.PHASE_WORKING; + } +} + +//Change to the next phase and update numShortBreaks, and optionally vibrate. DOES NOT WRITE STATE CHANGE TO STORAGE! +exports.nextPhase = function (vibrate) { + a = { + startTime: 0, //When the timer was last started. Difference between this and now is how long timer has run continuously. + pausedTime: 0, //When the timer was last paused. Used for expiration and displaying timer while paused. + elapsedTime: 0, //How much time the timer had spent running before the current start time. Update on pause or user skipping stages. + phase: exports.PHASE_WORKING, //What phase the timer is currently in + numShortBreaks: 0 //Number of short breaks that have occured so far + } + let now = (new Date()).getTime(); + exports.state.startTime = now; //The timer is being reset, so say it starts now. + exports.state.pausedTime = now; //This prevents a paused timer from having the start time moved to the future and therefore having been run for negative time. + exports.state.elapsedTime = 0; //Because we are resetting the timer, we no longer need to care about whether it was paused previously. + + let oldPhase = exports.state.phase; //Cache the old phase because we need to remember it when counting the number of short breaks + exports.state.phase = exports.getNextPhase(); + + if (oldPhase == exports.PHASE_SHORT_BREAK) { + //If we just left a short break, increase the number of short breaks + exports.state.numShortBreaks++; + } else if (oldPhase == exports.PHASE_LONG_BREAK) { + //If we just left a long break, set the number of short breaks to zero + exports.state.numShortBreaks = 0; + } + + if (vibrate) { + if (exports.state.phase == exports.PHASE_WORKING) { + Bangle.buzz(750, 1); + } else if (exports.state.phase == exports.PHASE_SHORT_BREAK) { + Bangle.buzz(); + setTimeout(Bangle.buzz, 400); + } else { + Bangle.buzz(); + setTimeout(Bangle.buzz, 400); + setTimeout(Bangle.buzz, 600); + } + } +} \ No newline at end of file diff --git a/apps/pomoplus/icon.js b/apps/pomoplus/icon.js new file mode 100644 index 000000000..e4ecc7d1c --- /dev/null +++ b/apps/pomoplus/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcBkmSpICG5AIHCLMmoQOKycJAoUyiQRLAgIOBAQQyKsmSpAROpgEBhmQzIHBC4JTIDIxcHDQYgCBQUSphEGpMJkwcEwgLByBoFCIMyCIgpDL4RQEBwWQ5ICBDoRKCBAIFBNYeSjJHDKYYaCR4YLBiYgDKYo4DEwQgECIpiECISqFkJlCCIILETwYRGDo1CsiJECIiPCdIaqCSoabFCgYRHAQ5iBCJ8hcAgRNKwOQgARLU4IRCvwRMa4QRPfwQR5YooR/cAYOGgAADvwEDCI8H/4AG/Ek5IRXGpMkzJZNoQGByYRNiQJCsgRLyAJDpgRQpIRLwgJEWxARBkIJFUg4RChIJGQA4RJNw4RKLg0kCJQ4DVoUACIY")) \ No newline at end of file diff --git a/apps/pomoplus/icon.png b/apps/pomoplus/icon.png new file mode 100644 index 000000000..60d8023db Binary files /dev/null and b/apps/pomoplus/icon.png differ diff --git a/apps/pomoplus/img/pause.png b/apps/pomoplus/img/pause.png new file mode 100644 index 000000000..ad31dadcf Binary files /dev/null and b/apps/pomoplus/img/pause.png differ diff --git a/apps/pomoplus/img/play.png b/apps/pomoplus/img/play.png new file mode 100644 index 000000000..6c20c24c5 Binary files /dev/null and b/apps/pomoplus/img/play.png differ diff --git a/apps/pomoplus/img/reset.png b/apps/pomoplus/img/reset.png new file mode 100644 index 000000000..7a317d097 Binary files /dev/null and b/apps/pomoplus/img/reset.png differ diff --git a/apps/pomoplus/img/skip.png b/apps/pomoplus/img/skip.png new file mode 100644 index 000000000..375318069 Binary files /dev/null and b/apps/pomoplus/img/skip.png differ diff --git a/apps/pomoplus/metadata.json b/apps/pomoplus/metadata.json new file mode 100644 index 000000000..068eeed91 --- /dev/null +++ b/apps/pomoplus/metadata.json @@ -0,0 +1,37 @@ +{ + "id": "pomoplus", + "name": "Pomodoro Plus", + "version": "0.05", + "description": "A configurable pomodoro timer that runs in the background.", + "icon": "icon.png", + "type": "app", + "tags": "pomodoro,cooking,tools", + "supports": [ + "BANGLEJS", + "BANGLEJS2" + ], + "allow_emulator": true, + "storage": [ + { + "name": "pomoplus.app.js", + "url": "app.js" + }, + { + "name": "pomoplus.img", + "url": "icon.js", + "evaluate": true + }, + { + "name": "pomoplus.boot.js", + "url": "boot.js" + }, + { + "name": "pomoplus-com.js", + "url": "common.js" + }, + { + "name": "pomoplus.settings.js", + "url": "settings.js" + } + ] +} \ No newline at end of file diff --git a/apps/pomoplus/settings.js b/apps/pomoplus/settings.js new file mode 100644 index 000000000..1ff52340a --- /dev/null +++ b/apps/pomoplus/settings.js @@ -0,0 +1,94 @@ +const SETTINGS_PATH = 'pomoplus.json'; +const storage = require("Storage"); + +(function (back) { + let settings = storage.readJSON(SETTINGS_PATH); + if (!settings) { + settings = { + workTime: 1500000, //Work for 25 minutes + shortBreak: 300000, //5 minute short break + longBreak: 900000, //15 minute long break + numShortBreaks: 3, //3 short breaks for every long break + pausedTimerExpireTime: 21600000, //If the timer was left paused for >6 hours, reset it on next launch + widget: false //If a widget is added in the future, whether the user wants it + }; + } + + function save() { + storage.writeJSON(SETTINGS_PATH, settings); + } + + const menu = { + '': { 'title': 'Pomodoro Plus' }, + '< Back': back, + 'Work time': { + value: settings.workTime, + step: 60000, //1 minute + min: 60000, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.workTime = value; + save(); + }, + format: function (value) { + return '' + (value / 60000) + 'm' + } + }, + 'Short break time': { + value: settings.shortBreak, + step: 60000, + min: 60000, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.shortBreak = value; + save(); + }, + format: function (value) { + return '' + (value / 60000) + 'm' + } + }, + '# Short breaks': { + value: settings.numShortBreaks, + step: 1, + min: 0, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.numShortBreaks = value; + save(); + } + }, + 'Long break time': { + value: settings.longBreak, + step: 60000, + min: 60000, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.longBreak = value; + save(); + }, + format: function (value) { + return '' + (value / 60000) + 'm' + } + }, + 'Timer expiration': { + value: settings.pausedTimerExpireTime, + step: 900000, //15 minutes + min: 0, + // max: 10800000, + // wrap: true, + onchange: function (value) { + settings.pausedTimerExpireTime = value; + save(); + }, + format: function (value) { + if (value == 0) return "Off" + else return `${Math.floor(value / 3600000)}h ${(value % 3600000) / 60000}m` + } + }, + }; + E.showMenu(menu) +}) \ No newline at end of file diff --git a/apps/pooqroman/ChangeLog b/apps/pooqroman/ChangeLog index c4f3171d3..b21b34b58 100644 --- a/apps/pooqroman/ChangeLog +++ b/apps/pooqroman/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial check-in. 0.02: Make internal menu time out + small fixes. 0.03: Autolight feature. +0.04: Added adjustment for Bangle.js magnetometer heading fix diff --git a/apps/pooqroman/app.js b/apps/pooqroman/app.js index fcb2437e1..7bd749ac4 100644 --- a/apps/pooqroman/app.js +++ b/apps/pooqroman/app.js @@ -70,7 +70,7 @@ class Options { delay ); } - + bless(k) { Object.defineProperty(this, k, { get: () => this.backing[k], @@ -103,7 +103,7 @@ class Options { if (this.bored) clearTimeout(this.bored); this.bored = setTimeout(_ => this.showMenu(), 15000); } - + reset() { this.backing = {__proto__: this.constructor.defaults}; this.writeBack(0); @@ -145,7 +145,7 @@ class RomanOptions extends Options { Defaults: _ => {this.reset(); this.interact();} }; } - + interact() {this.showMenu(this.menu);} } @@ -337,7 +337,7 @@ const events = { // colour: colour, dramatic?: bool, event?: any} fixed: [{time: Number.POSITIVE_INFINITY}], // indexed by ms absolute wall: [{time: Number.POSITIVE_INFINITY}], // indexed by nominal ms + TZ ms - + clean: function(now, l) { let o = now.getTimezoneOffset() * 60000; let tf = now.getTime() + l, tw = tf - o; @@ -345,7 +345,7 @@ const events = { while (this.wall[0].time <= tw) this.wall.shift(); while (this.fixed[0].time <= tf) this.fixed.shift(); }, - + scan: function(now, from, to, f) { result = Infinity; let o = now.getTimezoneOffset() * 60000; @@ -482,7 +482,7 @@ class Sidebar { compassI, this.x + 4 + imageWidth(compassI) / 2, this.y + 4 + imageHeight(compassI) / 2, - a ? {rotate: c.heading / 180 * Math.PI} : undefined + a ? {rotate: (360-c.heading) / 180 * Math.PI} : undefined ); this.y += 4 + imageHeight(compassI); } @@ -535,13 +535,13 @@ class Roman { static pos(p, r) { let h = r * rectW / 2; let v = r * rectH / 2; - p = (p + 1) % 12; + p = (p + 1) % 12; return p <= 2 ? [faceCX + h * (p - 1), faceCY - v] : p < 6 ? [faceCX + h, faceCY + v / 2 * (p - 4)] : p <= 8 ? [faceCX - h * (p - 7), faceCY + v] : [faceCX - h, faceCY - v / 2 * (p - 10)]; } - + alert(e, date, now, past) { const g = this.g; g.setColor(e.colour); @@ -564,7 +564,7 @@ class Roman { } return Infinity; } - + render(d, rate) { const g = this.g; const state = this.state || (g.clear(true), this.state = {}); @@ -625,7 +625,7 @@ class Roman { for (let h = keyHour; h < keyHour + 12; h++) { g.drawString( numeral(h % 24, options), - faceX + layout[h % 12 * 2], + faceX + layout[h % 12 * 2], faceY + layout[h % 12 * 2 + 1] ); } @@ -643,7 +643,7 @@ class Roman { (e, t, p) => this.alert(e, t, d, p) ); if (rate > requestedRate) rate = requestedRate; - + // Hands // Here we are using incremental hands for hours and minutes. // If we quantised, we could use hand-crafted bitmaps, though. @@ -668,7 +668,7 @@ class Clock { this.rates = {}; this.options.on('done', () => this.start()); - + this.listeners = { charging: _ => {face.doIcons('charging'); this.active();}, lock: _ => {face.doIcons('locked'); this.active();}, @@ -723,7 +723,7 @@ class Clock { this.face.reset(); // Cancel any ongoing background rendering return this; } - + active() { const prev = this.rate; const now = Date.now(); diff --git a/apps/pooqroman/metadata.json b/apps/pooqroman/metadata.json index 8cdbea728..0294e22a0 100644 --- a/apps/pooqroman/metadata.json +++ b/apps/pooqroman/metadata.json @@ -1,7 +1,7 @@ { "id": "pooqroman", "name": "pooq Roman watch face", "shortName":"pooq Roman", - "version":"0.03", + "version":"0.04", "description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!", "icon": "app.png", "type": "clock", diff --git a/apps/powermanager/ChangeLog b/apps/powermanager/ChangeLog index f0b60a45a..a83e8c676 100644 --- a/apps/powermanager/ChangeLog +++ b/apps/powermanager/ChangeLog @@ -1,3 +1,5 @@ 0.01: New App! 0.02: Allow forcing monotonic battery voltage/percentage 0.03: Use default Bangle formatter for booleans +0.04: Remove calibration with current voltage (Calibrate->Auto) as it is now handled by settings app + Allow automatic calibration on every charge longer than 3 hours diff --git a/apps/powermanager/README.md b/apps/powermanager/README.md index 434ec814e..88b3c370a 100644 --- a/apps/powermanager/README.md +++ b/apps/powermanager/README.md @@ -3,8 +3,9 @@ Manages settings for charging. Features: * Warning threshold to be able to disconnect the charger at a given percentage -* Set the battery calibration offset. +* Set the battery calibration offset * Force monotonic battery percentage or voltage +* Automatic calibration on charging uninterrupted longer than 3 hours (reloads of the watch reset the timer). ## Internals diff --git a/apps/powermanager/boot.js b/apps/powermanager/boot.js index 077e24413..2bc2aaa35 100644 --- a/apps/powermanager/boot.js +++ b/apps/powermanager/boot.js @@ -5,7 +5,6 @@ ); if (settings.warnEnabled){ - print("Charge warning enabled"); var chargingInterval; function handleCharging(charging){ @@ -48,4 +47,13 @@ return v; }; } + + if (settings.autoCalibration){ + let chargeStart; + Bangle.on("charging", (charging)=>{ + if (charging) chargeStart = Date.now(); + if (chargeStart && !charging && (Date.now() - chargeStart > 1000*60*60*3)) require("powermanager").setCalibration(); + if (!charging) chargeStart = undefined; + }); + } })(); diff --git a/apps/powermanager/lib.js b/apps/powermanager/lib.js new file mode 100644 index 000000000..f4a7e3378 --- /dev/null +++ b/apps/powermanager/lib.js @@ -0,0 +1,6 @@ +// set battery calibration value by either applying the given value or setting the currently read battery voltage +exports.setCalibration = function(calibration){ + let s = require('Storage').readJSON("setting.json", true) || {}; + s.batFullVoltage = calibration?calibration:((analogRead(D3) + analogRead(D3) + analogRead(D3) + analogRead(D3)) / 4); + require('Storage').writeJSON("setting.json", s); +} diff --git a/apps/powermanager/metadata.json b/apps/powermanager/metadata.json index dd1727657..0777feee3 100644 --- a/apps/powermanager/metadata.json +++ b/apps/powermanager/metadata.json @@ -2,7 +2,7 @@ "id": "powermanager", "name": "Power Manager", "shortName": "Power Manager", - "version": "0.03", + "version": "0.04", "description": "Allow configuration of warnings and thresholds for battery charging and display.", "icon": "app.png", "type": "bootloader", @@ -12,6 +12,7 @@ "storage": [ {"name":"powermanager.boot.js","url":"boot.js"}, {"name":"powermanager.settings.js","url":"settings.js"}, + {"name":"powermanager","url":"lib.js"}, {"name":"powermanager.default.json","url":"default.json"} ] } diff --git a/apps/powermanager/settings.js b/apps/powermanager/settings.js index 7cc683024..9eeb29e00 100644 --- a/apps/powermanager/settings.js +++ b/apps/powermanager/settings.js @@ -23,7 +23,6 @@ '': { 'title': 'Power Manager' }, - '< Back': back, 'Monotonic percentage': { value: !!settings.forceMonoPercentage, onchange: v => { @@ -44,29 +43,29 @@ } }; - function roundToDigits(number, stepsize) { return Math.round(number / stepsize) * stepsize; } - function getCurrentVoltageDirect() { - return (analogRead(D3) + analogRead(D3) + analogRead(D3) + analogRead(D3)) / 4; - } - var stepsize = 0.0002; - var full = 0.32; + var full = 0.3144; function getInitialCalibrationOffset() { return roundToDigits(systemsettings.batFullVoltage - full, stepsize) || 0; } - var submenu_calibrate = { '': { - title: "Calibrate" + title: "Calibrate", + back: function() { + E.showMenu(mainmenu); + }, }, - '< Back': function() { - E.showMenu(mainmenu); + 'Autodetect': { + value: !!settings.autoCalibration, + onchange: v => { + writeSettings("autoCalibration", v); + } }, 'Offset': { min: -0.05, @@ -75,25 +74,9 @@ value: getInitialCalibrationOffset(), format: v => roundToDigits(v, stepsize).toFixed((""+stepsize).length - 2), onchange: v => { - print(typeof v); - systemsettings.batFullVoltage = v + full; - require("Storage").writeJSON("setting.json", systemsettings); + require("powermanager").setCalibration(v + full); } }, - 'Auto': function() { - var newVoltage = getCurrentVoltageDirect(); - E.showAlert("Please charge fully before auto setting").then(() => { - E.showPrompt("Set current charge as full").then((r) => { - if (r) { - systemsettings.batFullVoltage = newVoltage; - require("Storage").writeJSON("setting.json", systemsettings); - //reset value shown in menu to the newly set one - submenu_calibrate.Offset.value = getInitialCalibrationOffset(); - E.showMenu(mainmenu); - } - }); - }); - }, 'Clear': function() { E.showPrompt("Clear charging offset?").then((r) => { if (r) { @@ -109,10 +92,10 @@ var submenu_chargewarn = { '': { - title: "Charge warning" - }, - '< Back': function() { - E.showMenu(mainmenu); + title: "Charge warning", + back: function() { + E.showMenu(mainmenu); + }, }, 'Enabled': { value: !!settings.warnEnabled, diff --git a/apps/primetimelato/ChangeLog b/apps/primetimelato/ChangeLog new file mode 100644 index 000000000..46690e360 --- /dev/null +++ b/apps/primetimelato/ChangeLog @@ -0,0 +1,2 @@ +0.01: first release +0.02: added option to buzz on prime, with settings diff --git a/apps/primetimelato/README.md b/apps/primetimelato/README.md new file mode 100644 index 000000000..924a6fae6 --- /dev/null +++ b/apps/primetimelato/README.md @@ -0,0 +1,19 @@ +# Prime Time Lato (clock) + +A watchface that displays time and its prime factors in the Lato font. +For example when the time is 21:05, the prime factors are 5,421. +Displays 'Prime Time!' when the time is a prime number. + +There is a settings option added in the Settings App. If 'Buzz on +Prime' is ticked then the buzzer will sound when 'Prime Time!' is +detected. Note the buzzer is limited to between 8am and 8pm so it +should not go off when you want to sleep. + + +![](screenshot.jpg) + +Written by: [Hugh Barney](https://github.com/hughbarney) + +Adapted from primetime by [Eve Bury](https://www.github.com/eveeeon) + +For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/primetimelato/app.js b/apps/primetimelato/app.js new file mode 100644 index 000000000..817da7cda --- /dev/null +++ b/apps/primetimelato/app.js @@ -0,0 +1,132 @@ +const h = g.getHeight(); +const w = g.getWidth(); +const SETTINGS_FILE = "primetimelato.json"; +let settings; + +Graphics.prototype.setFontLato = function(scale) { + // Actual height 41 (43 - 3) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('ACEfAokB/AGEn+AAocDBgsfBgkB+A6Yg4FEgYgF/4FEv/gHIhAEh/+DwgYEgP/4AeJn4eF/hDEDwxrE/4eFKAgeFgJDERQ5DEXJI0Eh//IIZlB/4pDAoP/HgQYBAAIaBPARDDv4JBj5WBh5ZCv4CBPATPCeQcPwDYECAIMGPId4gEeSIYMHDIYMCExZAGh6ICn5QEK4RnGOgqBGaoKOECYSiDn78FAFgxFR4bcCKISOBgF+RwYCBTgQMCOIQMCj5eCBgIfDBgQfCBgSbDWoSHC/61CAwYMUAAYzCAAZNCBkYAfgYmBgJ7CTYsPTYQMBgJnBgYMCn4MBv64CBgMPXYSBBD4cfBgQIBgf3RwK7CvybCGQXPBgIfBGQIMBeoU/DoIMCF4IMDgP8BgQmBn8AEwb+BBgQIBIQJAC4BYBIgTNBDYIMBg///xRDn//OoIMBcYISBBgUHNATpCgjCngIEDFIM+AoV8TYKYCMIJQBAQLCCgZcBYQIJBX4UHCwSJBYQaSCMYQDCYQabEBgngW4V4WoQBBOgIMN+AoCEwY+BGYZfBYwQIBv+AG4X+j/8K4TCB+bECM4P+YgcD//B/67Ch/4h//K4SSBv5IBAAdAWqMYAokDD4t+AokfBgi0DAAX/Bgkf7wSE/IME/6PBCQfBBgcD/AME/ypCCQXABgYSBBgg+BBgYSBBgatBBggSBBgYSBBgcB/4ACZ4QGDZ4IMLFARAEAAJaEBjQAUhAnFMgIABuD5CVwWAboUDLwZ0DNYKLBM4IMBh50C55rBCoWACAIMC+EPFIN4EQQMBg4MSHgYzEBgIzBIAUAvhND+EH8DZCBAN/bIfwMQP/OgIMBLgalCBgKlDg//V4kfCQIAqWgYzC/gFDIAJgBZoS3CAwV//4TDh/+j5UCCQOAn4SD8DPCCQSbCCQR/BNAV/3i1CB4PzAYLCBgP8AYIMCv+HBgcP+AMDCQMHEwb2BYQLPDgYMBIAKoBOYLPCwDNBZ4UQBQPhJ4J0EDYJbCZ4R7EAoZiDSoaUDADBNBFQj5EKwQMEGAoMEOgQFCnAMEQIYFBgaOCBgTRBBgc/AYIMCaIQMCgb2CBgX/JQIMCDAQMCh/8JoYYBJoiNDBgIYDBgIYDBgPzUwkfUwisBOokfDAYMCQIq/ERwwAcn4pCgfwg42D//B/6hBCAP+KwYQBMQKbBgF//9+g5EBh4YB4CfC/EHDwK1Dn7PD8A0BgF4gEeAIUHBgQBBBi4mEGYpAEAIMP4BNELQpnGOgM/ZYaBFGQMPYos/JAIAuj4xEKgJrBfoX//hEE/4TDCQJSCCoN/gZfBjCBCj+AgaOCAIiKBg4OCgKKBvgbCWYMDToK1CgE8JIQMC4ZCBBgU4HYTNCz4JBEwV7KoQzCUIYvBLYZNBn60CLQPfCQcDM4LHCEALHDZ4TaCCYaODHYK8fh6FDEwKSCF4Uf4COCBgJsBn4MDDIJPDVgYAZA='))), + 46, + atob("CxMeHh4eHh4eHh4eDQ=="), + 52+(scale<<8)+(1<<16) + ); + return this; +}; + +Graphics.prototype.setFontLatoSmall = function(scale) { + // Actual height 21 (20 - 0) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AB0B/+ch/88EAgQPHg/AgE+A4cPwEAvwTHoEAscQgc/wE//EP/0Au0wgEz/ED/+A//gg9jgEDiEAAIIACgcBwF+h0H+EwmPBwOf/4AB8Ng4cDg84hkfwFAvA/EgZfBneAwOEjkMkPAuccgPzwAbBCQJeBuZBBKYNxxkOhkgsFjgUD+B5BNox+Bgf4g/Y8F/wcDjkYjFw4HB40Gg8wjkfQgJLBj6bB84fBjCQDU5AAFj/wg//+A1B9xEGhkAg18gPx//+bgJmBAAckAQOgDQa+BgI2BjQ0HsBoBJogSBZgX/DQIrBG4IUFAAs7CQKVBFJ0GB4kEAQNwYQYACEIKaBRYX4RoQGCCoIADMwMf/kB8HwdQPA4EGGAMwmEBwPAjkHwfACgMAv4iBAAxICuEMSQNguEDgf//Ef/5ZEmCDDAAkBgHgnEOgfA8Eeg0B2EwjOBweMh04sE/wZlBfYomCjkPEIM8VwNgsI+BhkYjHg4HnFoPv8EOQYIAFQwMHJAN8gEOW4PjAgMYQwUPcIN/cI4ACkCNBgP8hkPsFgsY+BHoMYD4PHjkGj/gkAxBAAoHBh/wgPxV4J8B7EwnnBwPGhkMnHggLUBg/gXI6oDCgLiBsE+gb0BjD7B74bDni1CAAk8gP3+EP/ZTBfYMYfYI+Bw0Mh148E+HwPj+A6DAAIpBj6HB/0EhkwPwPPgcE+EYt4ZB8EDDwMP4EAiAeDgUCgE4nEB4INBAAcBKQMcjuA8BhBAAh2BgY5BjwUBJAMMDwPGJoMYf4ImFAAoUCswhBEgMZEgPMDgL7BmYpBzAUDABnBwEDhhTBFIiIBh4PBuDKCCwR6BgITBDAKSB88Dh84jAKB/geBHAQcBj/wgPhcIMDgOBhkYn0w4exw0wwkhxkhxHBhl/hFj7BKBo0Mg0wnnjgF+LIkEbQc/gEH8EBHgM/sEH4bGBjEB/HAgf2JIKvBCgICBJ4MOaYv//kP//gsHDga/BjEw4HBw0GjkwnHhwH9/kH54hBnggDkB1D//AjydBPIIyBFIMDgcAFIILB4EGg0AFIILBCgLLHj4kB//+CgUwTwOAhi8DFIccZANghkD4Pgh/+D4L7IFIpuBmHBwOGFIMwsFhwcDaAJTCwECSRasBJgIjBgDXBgxMBEYMBwUAhAdEn+Ag//gF8vkDwHgPoJoIXgnhw0Dh/gjF/Mo8D//4NASDBIgICBPQJLBIgJZBwEAF4IwBgB9BIYPwE44pHBYoiHg4EBvAUBwAZBExMAv//FIQbCKYU8AgJABnPggeHwE4jyKBnC8CgC8LEQZ2PNBA4BgIgCDYMHCwMfJIYNCnwMBB4N4gEPBYY+CNAJyHU4U/QoIxCFwQNBjwCBnACBHgpoGAAz4BB4PAj5OB8E4hwDBsEDPwMYSQXAgx+BmE4TwIUBPAOAh65Bj5wBAAxTBwI+BhgiBsHAgYiBjChB4OAg8cDwJVBvgfHB4KCBvl8HQmBwEMXoNgKY/ghwUBPAP+SoPhwF8ghOH//+U4UwsEBwbnBKYXwgcPRQPngF+jzMBuAbBOYnAn8GgP4nEc8HA4aSBjkwmHhwPDzkGNwMgNwYwBDoMAQgKoBcYIqBfYhoBChQAFv/4YIPwewIgBboIoFSIJnBgEHAgPwj6nBDIQVEAwMH8BBBAQQ6BdIUeCAc/BgR4BHwI6BCYJTCEIQCBj4CBh4CCE4QVBDQP+dYYLBnwcBBIMBAQoUBg/Agf8KwIZBHYIoBNIQSBfYUcIQP4nkB8YZBDwMPLoK5BJIXz8EPg+A8EeU4KmCXgYFBngCBDwL2BLAJGBMwIHCB4JKBgF4BwIWBiBGCgQpBuEwg+BwF8hkPsFg+cDj0YjPg4HcgwhBmBXBCgJnDAALmB/+f//8JYMkIoIMCHYJqCAQUfBYICCUYU4EgkIJINAgETFIgPEgAhBHoJtEgb3DQoacBSAQAGkBLDGYMAGYMCQ4gYBgiLCAQMYAQIzBDQQAFsbZBMYMxzEBxlAhlmgFikCIBwEPLowABn//4P/aIMwCgJUBjBpB4FgWYISBMIP/OYYAEg4MBv/Ag4UBmEYEAPAhjlBsEwgLyCAAcBIYMf+EB4IUFHwcIj//8C5BLA4cB4EBBgMdjkA5BTBocAmQ+ByBGBx0AjlgDISoCfwJ4BQ4P4jKwB5kAgwTDsEP3+A//mg03mEwzOBwnMU4Ngv8zgfh+EYPAKEEFIIuB4BnBEoMAPgTcEHgMAh/4NQ8gBwOf/kcPoKWEyEAhvP//uv7UBQgiSETgJ4BAgLJBnPAgeHcweAGALDGfYMP/4XBAAxmBSoP4FYQgBNAsPcIN/8CBCmBADCgV/dwP/BAIpIYgQpHLoIpCdwT7Mj08EIL7BDoMwfYOA4EOhwxBT4KUFHwXwZ4ITB4EMaQNgmEDDoMYH4LeBgZtBgZdFHwscCgI+HwOAUoPgv5eIPp8QCgcD4YUBFIOYKYPGgFjKYMfwEIvAUCgi8Dj5qBcwJoGGQQADn7JCH4J9BbRHAKYoAEuBLBVIMHAQMPEIMPBoIUBJYIMCvhoDAAQZFDgkeBQUBDwIGBEYUBXgIKCgYUBNAM/wA+CSQi8CnE4gH3B4PwFIINBIIMPSQPh8AUCAAMBLQR3Bn4KBn4SBv4fDDgJlBgI2BTw0AU4XBFIMfgEx7EB3nAh+GgF4XgOBF4JRCGAP/8f+/+YbAINBkArGbwIQB/5BBAArwBgLSBhP/v/H/6QBBAIACjhrDKwVgdAwHBRQThBgIbD'))), + 32, + atob("BAcIDAwRDwUGBggMBAcECAwMDAwMDAwMDAwFBQwMDAgRDg4OEAwMDxAGCQ4LExARDREOCwwPDhUODQ0GCAYMCAYLDAoMCwcLDAUFCwURDAwMDAgJCAwLEAsLCgYGBgwA"), + 21+(scale<<8)+(1<<16) + ); + return this; +}; + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.buzz_on_prime = (settings.buzz_on_prime === undefined ? false : settings.buzz_on_prime); +} + +// creates a list of prime factors of n and outputs them as a string, if n is prime outputs "Prime Time!" +function primeFactors(n) { + const factors = []; + let divisor = 2; + + while (n >= 2) { + if (n % divisor == 0) { + factors.push(divisor); + n = n / divisor; + } else { + divisor++; + } + } + if (factors.length === 1) { + return "Prime Time!"; + } + else + return factors.toString(); +} + + +// converts time HR:MIN to integer HRMIN e.g. 15:35 => 1535 +function timeToInt(t) { + var arr = t.split(':'); + var intTime = parseInt(arr[0])*100+parseInt(arr[1]); + + return intTime; +} + +function draw() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + var intTime = timeToInt(timeStr); + var primeStr = primeFactors(intTime); + + g.reset(); + g.setColor(0,0,0); + g.fillRect(Bangle.appRect); + + g.setFontLato(); + g.setFontAlign(0, 0); + g.setColor(100,100,100); + g.drawString(timeStr, w/2, h/2); + + g.setFontLatoSmall(); + g.drawString(primeStr, w/2, 3*h/4); + + // Buzz if Prime Time and between 8am and 8pm + if (settings.buzz_on_prime && primeStr == "Prime Time!" && intTime >= 800 && intTime <= 2000) + buzzer(2); + queueDraw(); +} + +// timeout for multi-buzzer +var buzzTimeout; + +// n buzzes +function buzzer(n) { + if (n-- < 1) return; + Bangle.buzz(250); + + if (buzzTimeout) clearTimeout(buzzTimeout); + buzzTimeout = setTimeout(function() { + buzzTimeout = undefined; + buzzer(n); + }, 500); +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + + +loadSettings(); +g.clear(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); diff --git a/apps/primetimelato/app.png b/apps/primetimelato/app.png new file mode 100644 index 000000000..2a84c62a0 Binary files /dev/null and b/apps/primetimelato/app.png differ diff --git a/apps/primetimelato/icon.js b/apps/primetimelato/icon.js new file mode 100644 index 000000000..06f93e2ef --- /dev/null +++ b/apps/primetimelato/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCEBAU/AQP4gfAj8AgPwAoMPwED8AFBg/AAYIBDA4ngg4TB4EBApkPKgJSBJQIFTMgIFCJIIFDKoIFEvgFBGoMAnw7DP4IFEh+BAoItBg+DNIQwBMIaeCKoKxCPoIzCEgKVHUIqtFXIrFFaIrdFdIwAV")) diff --git a/apps/primetimelato/metadata.json b/apps/primetimelato/metadata.json new file mode 100644 index 000000000..6b292c380 --- /dev/null +++ b/apps/primetimelato/metadata.json @@ -0,0 +1,18 @@ +{ "id": "primetimelato", + "name": "Prime Time Lato Clock", + "version": "0.02", + "type": "clock", + "description": "A clock that tells you the primes of the time in the Lato font", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "tags": "clock", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"primetimelato.app.js","url":"app.js"}, + {"name":"primetimelato.img","url":"icon.js","evaluate":true}, + {"name":"primetimelato.settings.js","url":"settings.js"} + + ], + "data": [{"name":"primetimelato.json"}] +} diff --git a/apps/primetimelato/screenshot.png b/apps/primetimelato/screenshot.png new file mode 100644 index 000000000..7f6e7cc0d Binary files /dev/null and b/apps/primetimelato/screenshot.png differ diff --git a/apps/primetimelato/settings.js b/apps/primetimelato/settings.js new file mode 100644 index 000000000..5550055eb --- /dev/null +++ b/apps/primetimelato/settings.js @@ -0,0 +1,34 @@ +(function(back) { + const SETTINGS_FILE = "primetimelato.json"; + + // initialize with default settings... + let s = { + 'buzz_on_prime': true + } + + // ...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 = storage.readJSON(SETTINGS_FILE, 1) || {} + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s; + storage.write(SETTINGS_FILE, settings); + } + + E.showMenu({ + '': { 'title': 'Prime Time Lato' }, + '< Back': back, + 'Buzz on Prime': { + value: !!s.buzz_on_prime, + onchange: v => { + s.buzz_on_prime = v; + save(); + }, + } + }) +}) diff --git a/apps/qcenter/ChangeLog b/apps/qcenter/ChangeLog new file mode 100644 index 000000000..900b9017c --- /dev/null +++ b/apps/qcenter/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Fix fast loading on swipe to clock diff --git a/apps/qcenter/README.md b/apps/qcenter/README.md new file mode 100644 index 000000000..4931b9c7f --- /dev/null +++ b/apps/qcenter/README.md @@ -0,0 +1,20 @@ +# Quick Center + +An app with a status bar showing various information and up to six shortcuts for your favorite apps! +Designed for use with any kind of quick launcher, such as Quick Launch or Pattern Launcher. + +![](screenshot.png) + +## Usage + +Pin your apps with settings, then launch them with your favorite quick launcher to access them quickly. +If you don't have any apps pinned, the settings and about apps will be shown as an example. + +## Features + +Battery and GPS status display (for now) +Up to six shortcuts to your favorite apps + +## Upcoming features +- Quick switches for toggleable features such as Bluetooth or HID mode +- Customizable status information \ No newline at end of file diff --git a/apps/qcenter/app-icon.js b/apps/qcenter/app-icon.js new file mode 100644 index 000000000..bfc94d10a --- /dev/null +++ b/apps/qcenter/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UB6cA/4ACBYNVAElQHAsFBYZFHCxIYEoALHgILNOxILChWqAAmgBYNUBZMVBYIAIBc0C1WAlWoAgQL/O96D/Qf4LZqoLJqoLMoAKHgILNqALHgoLBGBAKCDA4WDAEQA=")) \ No newline at end of file diff --git a/apps/qcenter/app.js b/apps/qcenter/app.js new file mode 100644 index 000000000..be28db3b6 --- /dev/null +++ b/apps/qcenter/app.js @@ -0,0 +1,129 @@ +{ +require("Font8x12").add(Graphics); + +// load pinned apps from config +let settings = require("Storage").readJSON("qcenter.json", 1) || {}; +let pinnedApps = settings.pinnedApps || []; +let exitGesture = settings.exitGesture || "swipeup"; + +// if empty load a default set of apps as an example +if (pinnedApps.length == 0) { + pinnedApps = [ + { src: "setting.app.js", icon: "setting.img" }, + { src: "about.app.js", icon: "about.img" }, + ]; +} + +// button drawing from Layout.js, edited to have completely custom button size with icon +let drawButton = function(l) { + let x = l.x + (0 | l.pad), + y = l.y + (0 | l.pad), + w = l.w - (l.pad << 1), + h = l.h - (l.pad << 1); + let poly = [ + x, + y + 4, + x + 4, + y, + x + w - 5, + y, + x + w - 1, + y + 4, + x + w - 1, + y + h - 5, + x + w - 5, + y + h - 1, + x + 4, + y + h - 1, + x, + y + h - 5, + x, + y + 4, + ], + bg = l.selected ? g.theme.bgH : g.theme.bg2; + g.setColor(bg) + .fillPoly(poly) + .setColor(l.selected ? g.theme.fgH : g.theme.fg2) + .drawPoly(poly); + if (l.src) + g.setBgColor(bg).drawImage( + "function" == typeof l.src ? l.src() : l.src, + l.x + l.w / 2, + l.y + l.h / 2, + { scale: l.scale || undefined, rotate: Math.PI * 0.5 * (l.r || 0) } + ); +} + +// function to split array into group of 3, for button placement +let groupBy3 = function(data) { + let result = []; + for (let i = 0; i < data.length; i += 3) result.push(data.slice(i, i + 3)); + return result; +} + +// generate object with buttons for apps by group of 3 +let appButtons = groupBy3(pinnedApps).map((appGroup, i) => { + return appGroup.map((app, j) => { + return { + type: "custom", + render: drawButton, + width: 50, + height: 50, + pad: 5, + src: require("Storage").read(app.icon), + scale: 0.75, + cb: (l) => load(app.src), + }; + }); +}); + +// create basic layout content with status info and sensor status on top +let layoutContent = [ + { + type: "h", + pad: 5, + fillx: 1, + c: [ + { type: "txt", font: "8x12", pad: 3, scale: 2, label: E.getBattery() + "%" }, + { type: "txt", font: "8x12", pad: 3, scale: 2, label: "GPS: " + (Bangle.isGPSOn() ? "ON" : "OFF") }, + ], + }, +]; + +// create rows for buttons and add them to layoutContent +appButtons.forEach((appGroup) => { + layoutContent.push({ + type: "h", + pad: 2, + c: appGroup, + }); +}); + +// create layout with content + +Bangle.loadWidgets(); + +let Layout = require("Layout"); +let layout = new Layout({ + type: "v", + c: layoutContent +}, { + remove: ()=>{ + Bangle.removeListener("swipe", onSwipe); + delete Graphics.prototype.setFont8x12; + } +}); +g.clear(); +layout.render(); +Bangle.drawWidgets(); + +// swipe event listener for exit gesture +let onSwipe = function (lr, ud) { + if(exitGesture == "swipeup" && ud == -1) Bangle.showClock(); + if(exitGesture == "swipedown" && ud == 1) Bangle.showClock(); + if(exitGesture == "swipeleft" && lr == -1) Bangle.showClock(); + if(exitGesture == "swiperight" && lr == 1) Bangle.showClock(); +} + +Bangle.on("swipe", onSwipe); +} diff --git a/apps/qcenter/app.png b/apps/qcenter/app.png new file mode 100644 index 000000000..27ec75f1c Binary files /dev/null and b/apps/qcenter/app.png differ diff --git a/apps/qcenter/metadata.json b/apps/qcenter/metadata.json new file mode 100644 index 000000000..a325de10f --- /dev/null +++ b/apps/qcenter/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "qcenter", + "name": "Quick Center", + "shortName": "QCenter", + "version": "0.02", + "description": "An app for quickly launching your favourite apps, inspired by the control centres of other watches.", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{ "url": "screenshot.png" }], + "storage": [ + { "name": "qcenter.app.js", "url": "app.js" }, + { "name": "qcenter.settings.js", "url": "settings.js" }, + { "name": "qcenter.img", "url": "app-icon.js", "evaluate": true } + ], + "data": [{"name":"qcenter.json"}] +} diff --git a/apps/qcenter/screenshot.png b/apps/qcenter/screenshot.png new file mode 100644 index 000000000..8c0a335aa Binary files /dev/null and b/apps/qcenter/screenshot.png differ diff --git a/apps/qcenter/settings.js b/apps/qcenter/settings.js new file mode 100644 index 000000000..2c97f8a5f --- /dev/null +++ b/apps/qcenter/settings.js @@ -0,0 +1,133 @@ +// make sure to enclose the function in parentheses +(function (back) { + let settings = require("Storage").readJSON("qcenter.json", 1) || {}; + var apps = require("Storage") + .list(/\.info$/) + .map((app) => { + var a = require("Storage").readJSON(app, 1); + return ( + a && { + name: a.name, + type: a.type, + sortorder: a.sortorder, + src: a.src, + icon: a.icon, + } + ); + }) + .filter( + (app) => + app && + (app.type == "app" || + app.type == "launch" || + app.type == "clock" || + !app.type) + ); + apps.sort((a, b) => { + var n = (0 | a.sortorder) - (0 | b.sortorder); + if (n) return n; // do sortorder first + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + + function save(key, value) { + settings[key] = value; + require("Storage").write("qcenter.json", settings); + } + + var pinnedApps = settings.pinnedApps || []; + var exitGesture = settings.exitGesture || "swipeup"; + + function showMainMenu() { + var mainmenu = { + "": { title: "Quick Center", back: back}, + }; + + // Set exit gesture + mainmenu["Exit Gesture: " + exitGesture] = function () { + E.showMenu(exitGestureMenu); + }; + + //List all pinned apps, redirecting to menu with options to unpin and reorder + pinnedApps.forEach((app, i) => { + mainmenu[app.name] = function () { + E.showMenu({ + "": { title: app.name, back: showMainMenu }, + "Unpin": () => { + pinnedApps.splice(i, 1); + save("pinnedApps", pinnedApps); + showMainMenu(); + }, + "Move Up": () => { + if (i > 0) { + pinnedApps.splice(i - 1, 0, pinnedApps.splice(i, 1)[0]); + save("pinnedApps", pinnedApps); + showMainMenu(); + } + }, + "Move Down": () => { + if (i < pinnedApps.length - 1) { + pinnedApps.splice(i + 1, 0, pinnedApps.splice(i, 1)[0]); + save("pinnedApps", pinnedApps); + showMainMenu(); + } + }, + }); + }; + }); + + // Show pin app menu, or show alert if max amount of apps are pinned + mainmenu["Pin App"] = function () { + if (pinnedApps.length < 6) { + E.showMenu(pinAppMenu); + } else { + E.showAlert("Max apps pinned").then(showMainMenu); + } + }; + + return E.showMenu(mainmenu); + } + + // menu for adding apps to the quick launch menu, listing all apps + var pinAppMenu = { + "": { title: "Add App", back: showMainMenu } + }; + apps.forEach((a) => { + pinAppMenu[a.name] = function () { + // strip unncecessary properties + delete a.type; + delete a.sortorder; + pinnedApps.push(a); + save("pinnedApps", pinnedApps); + showMainMenu(); + }; + }); + + // menu for setting exit gesture + var exitGestureMenu = { + "": { title: "Exit Gesture", back: showMainMenu } + }; + exitGestureMenu["Swipe Up"] = function () { + exitGesture = "swipeup"; + save("exitGesture", "swipeup"); + showMainMenu(); + }; + exitGestureMenu["Swipe Down"] = function () { + exitGesture = "swipedown"; + save("exitGesture", "swipedown"); + showMainMenu(); + }; + exitGestureMenu["Swipe Left"] = function () { + exitGesture = "swipeleft"; + save("exitGesture", "swipeleft"); + showMainMenu(); + }; + exitGestureMenu["Swipe Right"] = function () { + exitGesture = "swiperight"; + save("exitGesture", "swiperight"); + showMainMenu(); + }; + + showMainMenu(); +}); diff --git a/apps/qrcode/custom.html b/apps/qrcode/custom.html index 9955ea6c9..a3362f101 100644 --- a/apps/qrcode/custom.html +++ b/apps/qrcode/custom.html @@ -106,8 +106,8 @@ - - + + + + - diff --git a/loader.js b/loader.js index 9d97b45b5..c525fd963 100644 --- a/loader.js +++ b/loader.js @@ -16,11 +16,14 @@ 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 = "2v15"; +var RECOMMENDED_VERSION = "2v16"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); +// Where we get our usage data from +Const.APP_USAGE_JSON = "https://banglejs.com/apps/appusage.json"; +Const.APP_DATES_CSV = "appdates.csv"; // Set up source code URL (function() { @@ -156,6 +159,11 @@ window.addEventListener('load', (event) => {
+
`; showPrompt("Which Bangle.js?",html,{},false); + var usageStats = document.getElementById("usage_stats"); + usageStats.addEventListener("change",event=>{ + console.log("Send Usage Stats "+(event.target.checked?"on":"off")); + SETTINGS.sendUsageStats = event.target.checked; + saveSettings(); + }); htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { button.addEventListener("click",event => { - let rememberDevice = document.getElementById("remember_device").checked; - + let rememberDevice = !!document.getElementById("remember_device").checked; let button = event.currentTarget; let deviceId = button.getAttribute("deviceid"); hidePrompt(); @@ -193,7 +206,9 @@ window.addEventListener('load', (event) => { // Button to install all default apps in one go document.getElementById("reinstallall").addEventListener("click",event=>{ var promise = showPrompt("Reinstall","Really re-install all apps?").then(() => { - getInstalledApps().then(installedapps => { + Comms.reset().then(_ => + getInstalledApps() + ).then(installedapps => { console.log(installedapps); var promise = Promise.resolve(); installedapps.forEach(app => { @@ -203,10 +218,12 @@ window.addEventListener('load', (event) => { app = appJSON.find(a => a.id==oldApp.id); if (!app) return console.log(`Ignoring ${oldApp.id} as not found`); - promise = promise.then(() => updateApp(app)); + promise = promise.then(() => updateApp(app, {noReset:true, noFinish:true})); }); return promise; - }).catch(err=>{ + }).then( _ => + Comms.showUploadFinished() + ).catch(err=>{ Progress.hide({sticky:true}); showToast("App re-install failed, "+err,"error"); }); diff --git a/modules/.eslintrc.json b/modules/.eslintrc.json new file mode 100644 index 000000000..d656c2555 --- /dev/null +++ b/modules/.eslintrc.json @@ -0,0 +1,163 @@ +{ + "env": { + // TODO: "espruino": false + // TODO: "banglejs": false + }, + "extends": "eslint:recommended", + "globals": { + // Methods and Fields at https://banglejs.com/reference + "Array": "readonly", + "ArrayBuffer": "readonly", + "ArrayBufferView": "readonly", + "Bangle": "readonly", + "BluetoothDevice": "readonly", + "BluetoothRemoteGATTCharacteristic": "readonly", + "BluetoothRemoteGATTServer": "readonly", + "BluetoothRemoteGATTService": "readonly", + "Boolean": "readonly", + "console": "readonly", + "DataView": "readonly", + "Date": "readonly", + "E": "readonly", + "Error": "readonly", + "Flash": "readonly", + "Float32Array": "readonly", + "Float64Array": "readonly", + "fs": "readonly", + "Function": "readonly", + "Graphics": "readonly", + "heatshrink": "readonly", + "I2C": "readonly", + "Int16Array": "readonly", + "Int32Array": "readonly", + "Int8Array": "readonly", + "InternalError": "readonly", + "JSON": "readonly", + "Math": "readonly", + "Modules": "readonly", + "NRF": "readonly", + "Number": "readonly", + "Object": "readonly", + "OneWire": "readonly", + "Pin": "readonly", + "process": "readonly", + "Promise": "readonly", + "ReferenceError": "readonly", + "RegExp": "readonly", + "Serial": "readonly", + "SPI": "readonly", + "Storage": "readonly", + "StorageFile": "readonly", + "String": "readonly", + "SyntaxError": "readonly", + "tensorflow": "readonly", + "TFMicroInterpreter": "readonly", + "TypeError": "readonly", + "Uint16Array": "readonly", + "Uint24Array": "readonly", + "Uint32Array": "readonly", + "Uint8Array": "readonly", + "Uint8ClampedArray": "readonly", + "Waveform": "readonly", + // Methods and Fields at https://banglejs.com/reference + "analogRead": "readonly", + "analogWrite": "readonly", + "arguments": "readonly", + "atob": "readonly", + "Bluetooth": "readonly", + "BTN": "readonly", + "BTN1": "readonly", + "BTN2": "readonly", + "BTN3": "readonly", + "BTN4": "readonly", + "BTN5": "readonly", + "btoa": "readonly", + "changeInterval": "readonly", + "clearInterval": "readonly", + "clearTimeout": "readonly", + "clearWatch": "readonly", + "decodeURIComponent": "readonly", + "digitalPulse": "readonly", + "digitalRead": "readonly", + "digitalWrite": "readonly", + "dump": "readonly", + "echo": "readonly", + "edit": "readonly", + "encodeURIComponent": "readonly", + "eval": "readonly", + "getPinMode": "readonly", + "getSerial": "readonly", + "getTime": "readonly", + "global": "readonly", + "HIGH": "readonly", + "I2C1": "readonly", + "Infinity": "readonly", + "isFinite": "readonly", + "isNaN": "readonly", + "LED": "readonly", + "LED1": "readonly", + "LED2": "readonly", + "load": "readonly", + "LoopbackA": "readonly", + "LoopbackB": "readonly", + "LOW": "readonly", + "NaN": "readonly", + "parseFloat": "readonly", + "parseInt": "readonly", + "peek16": "readonly", + "peek32": "readonly", + "peek8": "readonly", + "pinMode": "readonly", + "poke16": "readonly", + "poke32": "readonly", + "poke8": "readonly", + "print": "readonly", + "require": "readonly", + "reset": "readonly", + "save": "readonly", + "Serial1": "readonly", + "setBusyIndicator": "readonly", + "setInterval": "readonly", + "setSleepIndicator": "readonly", + "setTime": "readonly", + "setTimeout": "readonly", + "setWatch": "readonly", + "shiftOut": "readonly", + "SPI1": "readonly", + "Terminal": "readonly", + "trace": "readonly", + "VIBRATE": "readonly", + // Aliases and not defined at https://banglejs.com/reference + "g": "readonly", + "WIDGETS": "readonly" + }, + "parserOptions": { + "ecmaVersion": 11 + }, + "rules": { + "indent": [ + "off", + 2, + { + "SwitchCase": 1 + } + ], + "no-case-declarations": "off", + "no-constant-condition": "off", + "no-delete-var": "off", + "no-empty": "off", + "no-global-assign": "off", + "no-inner-declarations": "off", + "no-octal": "off", + "no-prototype-builtins": "off", + "no-redeclare": "off", + "no-unreachable": "warn", + "no-cond-assign": "warn", + "no-useless-catch": "warn", + // TODO: "no-undef": "warn", + "no-undef": "off", + "no-unused-vars": "off", + "no-useless-escape": "off", + "no-control-regex" : "off" + } +} diff --git a/modules/ClockFace.js b/modules/ClockFace.js index a89281d2d..b1b007be9 100644 --- a/modules/ClockFace.js +++ b/modules/ClockFace.js @@ -9,7 +9,7 @@ function ClockFace(options) { if (![ "precision", "init", "draw", "update", - "pause", "resume", + "pause", "resume", "remove", "up", "down", "upDown", "settingsFile", ].includes(k)) throw `Invalid ClockFace option: ${k}`; @@ -27,6 +27,7 @@ function ClockFace(options) { if (options.init) this.init = options.init; if (options.pause) this._pause = options.pause; if (options.resume) this._resume = options.resume; + if (options.remove) this._remove = options.remove; if ((options.up || options.down) && options.upDown) throw "ClockFace up/down and upDown cannot be used together"; if (options.up || options.down) this._upDown = (dir) => { if (dir<0 && options.up) options.up.apply(this); @@ -44,8 +45,15 @@ function ClockFace(options) { ["showDate", "loadWidgets"].forEach(k => { if (this[k]===undefined) this[k] = true; }); + let s = require("Storage").readJSON("setting.json",1)||{}; + if ((global.__FILE__===undefined || global.__FILE__===s.clock) + && s.clockHasWidgets!==this.loadWidgets) { + // save whether we can Fast Load + s.clockHasWidgets = this.loadWidgets; + require("Storage").writeJSON("setting.json", s); + } // use global 24/12-hour setting if not set by clock-settings - if (!('is12Hour' in this)) this.is12Hour = !!(require("Storage").readJSON("setting.json", true) || {})["12hour"]; + if (!('is12Hour' in this)) this.is12Hour = !!(s["12hour"]); } ClockFace.prototype.tick = function() { @@ -82,19 +90,30 @@ ClockFace.prototype.start = function() { /* Some widgets want to know if we're in a clock or not (like chrono, widget clock, etc). Normally .CLOCK is set by Bangle.setUI('clock') but we want to load widgets so we can check appRect and *then* call setUI. see #1864 */ - Bangle.CLOCK = 1; + Bangle.CLOCK = 1; if (this.loadWidgets) Bangle.loadWidgets(); if (this.init) this.init.apply(this); - if (this._upDown) Bangle.setUI("clockupdown", d=>this._upDown.apply(this,[d])); - else Bangle.setUI("clock"); + const uiRemove = this._remove ? () => this.remove() : undefined; + if (this._upDown) { + Bangle.setUI({ + mode: "clockupdown", + remove: uiRemove, + }, d => this._upDown.apply(this, [d])); + } else { + Bangle.setUI({ + mode: "clock", + remove: uiRemove, + }); + } delete this._last; this.paused = false; this.tick(); - Bangle.on("lcdPower", on => { + this._onLcd = on => { if (on) this.resume(); else this.pause(); - }); + }; + Bangle.on("lcdPower", this._onLcd); }; ClockFace.prototype.pause = function() { @@ -111,6 +130,11 @@ ClockFace.prototype.resume = function() { if (this._resume) this._resume.apply(this); this.tick(); }; +ClockFace.prototype.remove = function() { + if (this._timeout) clearTimeout(this._timeout); + Bangle.removeListener("lcdPower", this._onLcd); + if (this._remove) this._remove.apply(this); +}; /** * Force a complete redraw diff --git a/modules/ClockFace.md b/modules/ClockFace.md index 85482213c..f123d38c0 100644 --- a/modules/ClockFace.md +++ b/modules/ClockFace.md @@ -77,6 +77,11 @@ var clock = new ClockFace({ resume: function() { // optional, called when the screen turns on // for example: turn GPS/compass back on }, + remove: function() { // optional, used for Fast Loading + // for example: remove listeners + // Fast Loading will not be used unless this function is present, + // if there is nothing to clean up, you can just leave it empty. + }, up: function() { // optional, up handler }, down: function() { // optional, down handler diff --git a/modules/Layout.js b/modules/Layout.js index fdcf0ceae..f8e27b66b 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -10,18 +10,20 @@ */ -function Layout(layout, options) { +function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? - this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; + this.options = options || {}; this.lazy = this.options.lazy || false; + this.physBtns = 1; let btnList; if (process.env.HWVERSION!=2) { + this.physBtns = 3; // no touchscreen, find any buttons in 'layout' btnList = []; - function btnRecurser(l) { + function btnRecurser(l) {"ram"; if (l.type=="btn") btnList.push(l); if (l.c) l.c.forEach(btnRecurser); } @@ -64,7 +66,7 @@ function Layout(layout, options) { this.setUI(); // recurse over layout doing some fixing up if needed var ll = this; - function recurser(l) { + function recurser(l) {"ram"; // add IDs if (l.id) ll[l.id] = l; // fix type up @@ -81,7 +83,7 @@ Layout.prototype.setUI = function() { let uiSet; if (this.buttons) { // multiple buttons so we'll jus use back/next/select - Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{ + Bangle.setUI({mode:"updown", back:this.options.back, remove:this.options.remove}, dir=>{ var s = this.selectedButton, l=this.buttons.length; if (dir===undefined && this.buttons[s]) return this.buttons[s].cb(); @@ -98,7 +100,7 @@ Layout.prototype.setUI = function() { }); uiSet = true; } - if (this.options.back && !uiSet) Bangle.setUI({mode: "custom", back: this.options.back}); + if ((this.options.back || this.options.remove) && !uiSet) Bangle.setUI({mode: "custom", back: this.options.back, remove: this.options.remove}); // physical buttons -> actual applications if (this.b) { // Handler for button watch events @@ -153,26 +155,25 @@ Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.updateNeeded) this.update(); - function render(l) {"ram" - g.reset(); - if (l.col!==undefined) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); + var gfx=g; // define locally, because this is faster + function render(l) {"ram"; + gfx.reset(); + if (l.col!==undefined) gfx.setColor(l.col); + if (l.bgCol!==undefined) gfx.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); cb[l.type](l); } var cb = { "":function(){}, - "txt":function(l){ + "txt":function(l){"ram"; if (l.wrap) { - g.setFont(l.font).setFontAlign(0,-1); - var lines = g.wrapString(l.label, l.w); - var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); - // TODO: on 2v11 we can just render in a single drawString call - lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); + var lines = gfx.setFont(l.font).setFontAlign(0,-1).wrapString(l.label, l.w); + var y = l.y+((l.h-gfx.getFontHeight()*lines.length)>>1); + gfx.drawString(lines.join("\n"), l.x+(l.w>>1), y); } else { - g.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + gfx.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } - }, "btn":function(l){ + }, "btn":function(l){"ram"; var x = l.x+(0|l.pad), y = l.y+(0|l.pad), w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ @@ -185,27 +186,26 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ], bg = l.selected?g.theme.bgH:g.theme.bg2; - g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); - if (l.col!==undefined) g.setColor(l.col); - if (l.src) g.setBgColor(bg).drawImage( + ], bg = l.selected?gfx.theme.bgH:gfx.theme.bg2; + gfx.setColor(bg).fillPoly(poly).setColor(l.selected ? gfx.theme.fgH : gfx.theme.fg2).drawPoly(poly); + if (l.col!==undefined) gfx.setColor(l.col); + if (l.src) gfx.setBgColor(bg).drawImage( "function"==typeof l.src?l.src():l.src, l.x + l.w/2, l.y + l.h/2, {scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)} ); - else g.setFont(l.font||"6x8:2").setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); - }, "img":function(l){ - g.drawImage( + else gfx.setFont(l.font||"6x8:2").setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + }, "img":function(l){"ram"; + gfx.drawImage( "function"==typeof l.src?l.src():l.src, l.x + l.w/2, l.y + l.h/2, {scale: l.scale||undefined, rotate: Math.PI*0.5*(l.r||0)} ); - }, "custom":function(l){ - l.render(l); - },"h":function(l) { l.c.forEach(render); }, - "v":function(l) { l.c.forEach(render); } + }, "custom":function(l){"ram"; l.render(l); + }, "h":function(l) { "ram"; l.c.forEach(render); + }, "v":function(l) { "ram"; l.c.forEach(render); } }; if (this.lazy) { @@ -218,7 +218,7 @@ Layout.prototype.render = function (l) { prepareLazyRender(l, rectsToClear, drawList, this.rects, null); for (var h in rectsToClear) delete this.rects[h]; var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r); + for (var r of clearList) gfx.setBgColor(r.bg).clearRect.apply(g, r); drawList.forEach(render); } else { // non-lazy render(l); @@ -231,9 +231,8 @@ Layout.prototype.forgetLazyState = function () { Layout.prototype.layout = function (l) { // l = current layout element - // exw,exh = extra width/height available - switch (l.type) { - case "h": { + var cb = { + "h" : function(l) {"ram"; var acc_w = l.x + (0|l.pad); var accfillx = 0; var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); @@ -247,11 +246,10 @@ Layout.prototype.layout = function (l) { c.w = 0|(x - c.x); c.h = 0|(c.filly ? l.h - (l.pad<<1) : c._h); c.y = 0|(l.y + (0|l.pad) + ((1+(0|c.valign))*(l.h-(l.pad<<1)-c.h)>>1)); - if (c.c) this.layout(c); + if (c.c) cb[c.type](c); }); - break; - } - case "v": { + }, + "v" : function(l) {"ram"; var acc_h = l.y + (0|l.pad); var accfilly = 0; var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); @@ -265,11 +263,11 @@ Layout.prototype.layout = function (l) { c.h = 0|(y - c.y); c.w = 0|(c.fillx ? l.w - (l.pad<<1) : c._w); c.x = 0|(l.x + (0|l.pad) + ((1+(0|c.halign))*(l.w-(l.pad<<1)-c.w)>>1)); - if (c.c) this.layout(c); + if (c.c) cb[c.type](c); }); - break; } - } + }; + cb[l.type](l); }; Layout.prototype.debug = function(l,c) { if (!l) l = this._l; @@ -282,50 +280,51 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; + var gfx=g; // define locally, because this is faster // update sizes - function updateMin(l) {"ram" + function updateMin(l) {"ram"; cb[l.type](l); if (l.r&1) { // rotation var t = l._w;l._w=l._h;l._h=t; } - l._w = 0|Math.max(l._w + (l.pad<<1), 0|l.width); - l._h = 0|Math.max(l._h + (l.pad<<1), 0|l.height); + l._w = Math.max(l._w + (l.pad<<1), 0|l.width); + l._h = Math.max(l._h + (l.pad<<1), 0|l.height); } var cb = { - "txt" : function(l) { + "txt" : function(l) {"ram"; if (l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); + l.font = "Vector"+Math.round(gfx.getHeight()*l.font.slice(0,-1)/100); if (l.wrap) { l._h = l._w = 0; } else { var m = g.setFont(l.font).stringMetrics(l.label); l._w = m.width; l._h = m.height; } - }, "btn": function(l) { + }, "btn": function(l) {"ram"; if (l.font && l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont(l.font||"6x8:2").stringMetrics(l.label); + l.font = "Vector"+Math.round(gfx.getHeight()*l.font.slice(0,-1)/100); + var m = l.src?gfx.imageMetrics("function"==typeof l.src?l.src():l.src):gfx.setFont(l.font||"6x8:2").stringMetrics(l.label); l._h = 16 + m.height; l._w = 20 + m.width; - }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + }, "img": function(l) {"ram"; + var m = gfx.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image l._w = m.width*s; l._h = m.height*s; - }, "": function(l) { + }, "": function(l) {"ram"; // size should already be set up in width/height l._w = 0; l._h = 0; - }, "custom": function(l) { + }, "custom": function(l) {"ram"; // size should already be set up in width/height l._w = 0; l._h = 0; - }, "h": function(l) { + }, "h": function(l) {"ram"; l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>Math.max(a,b._h),0); l._w = l.c.reduce((a,b)=>a+b._w,0); if (l.fillx == null && l.c.some(c=>c.fillx)) l.fillx = 1; if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; - }, "v": function(l) { + }, "v": function(l) {"ram"; l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>a+b._h,0); l._w = l.c.reduce((a,b)=>Math.max(a,b._w),0); @@ -336,6 +335,7 @@ Layout.prototype.update = function() { var l = this._l; updateMin(l); + delete cb; if (l.fillx || l.filly) { // fill all l.w = Bangle.appRect.w; l.h = Bangle.appRect.h; diff --git a/modules/Layout.md b/modules/Layout.md index 7a4177957..67db21858 100644 --- a/modules/Layout.md +++ b/modules/Layout.md @@ -59,6 +59,7 @@ layout.render(); - `cb` - a callback function - `cbl` - a callback function for long presses - `back` - a callback function, passed as `back` into Bangle.setUI (which usually adds an icon in the top left) +- `remove` - a cleanup function, passed as `remove` into Bangle.setUI (allows to cleanly remove the app from memory) If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically determine what objects have changed or moved, clear their previous locations, and re-render just those objects. diff --git a/modules/Layout.min.js b/modules/Layout.min.js index b5a924358..19e60f7a0 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -1,14 +1,14 @@ -function p(b,k){function d(h){h.id&&(f[h.id]=h);h.type||(h.type="");h.c&&h.c.forEach(d)}this._l=this.l=b;this.physBtns=2==process.env.HWVERSION?1:3;this.options=k||{};this.lazy=this.options.lazy||!1;let a;if(2!=process.env.HWVERSION){a=[];function h(m){"btn"==m.type&&a.push(m);m.c&&m.c.forEach(h)}h(b);a.length&&(this.physBtns=0,this.buttons=a,this.selectedButton=-1)}if(this.options.btns)if(b=this.options.btns,this.physBtns>=b.length){this.b=b;let h=Math.floor(Bangle.appRect.h/ -this.physBtns);for(2b.length;)b.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:b.map(m=>(m.type="txt",m.font="6x8",m.height=h,m.r=1,m))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:b.map(h=>(h.type="btn",h.filly=1,h.width=32,h.r=1,h))}]},a&&a.push.apply(a,this._l.c[1].c);this.setUI();var f=this;d(this._l);this.updateNeeded=!0}function r(b, -k,d,a,f){var h=null==b.bgCol?f:g.toColor(b.bgCol);if(h!=f||"txt"==b.type||"btn"==b.type||"img"==b.type||"custom"==b.type){var m=b.c;delete b.c;var c="H"+E.CRC32(E.toJS(b));m&&(b.c=m);delete k[c]||((a[c]=[b.x,b.y,b.x+b.w-1,b.y+b.h-1]).bg=null==f?g.theme.bg:f,d&&(d.push(b),d=null))}if(b.c)for(var l of b.c)r(l,k,d,a,h)}p.prototype.setUI=function(){Bangle.setUI();let b;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back},k=>{var d=this.selectedButton,a=this.buttons.length;if(void 0===k&& -this.buttons[d])return this.buttons[d].cb();this.buttons[d]&&(delete this.buttons[d].selected,this.render(this.buttons[d]));d=(d+a+k)%a;this.buttons[d]&&(this.buttons[d].selected=1,this.render(this.buttons[d]));this.selectedButton=d}),b=!0);this.options.back&&!b&&Bangle.setUI({mode:"custom",back:this.options.back});if(this.b){function k(d,a){.75=d.x&&a.y>=d.y&&a.x<=d.x+d.w&&a.y<=d.y+d.h&&(2==a.type&&d.cbl?d.cbl(a):d.cb&&d.cb(a));d.c&&d.c.forEach(f=>k(f,a))}Bangle.touchHandler=(d,a)=>k(this._l,a);Bangle.on("touch",Bangle.touchHandler)}}; -p.prototype.render=function(b){function k(c){"ram";g.reset();void 0!==c.col&&g.setColor(c.col);void 0!==c.bgCol&&g.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);d[c.type](c)}b||(b=this._l);this.updateNeeded&&this.update();var d={"":function(){},txt:function(c){if(c.wrap){g.setFont(c.font).setFontAlign(0,-1);var l=g.wrapString(c.label,c.w),e=c.y+(c.h-g.getFontHeight()*l.length>>1);l.forEach((n,q)=>g.drawString(n,c.x+(c.w>>1),e+g.getFontHeight()*q))}else g.setFont(c.font).setFontAlign(0, -0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){var l=c.x+(0|c.pad),e=c.y+(0|c.pad),n=c.w-(c.pad<<1),q=c.h-(c.pad<<1);l=[l,e+4,l+4,e,l+n-5,e,l+n-1,e+4,l+n-1,e+q-5,l+n-5,e+q-1,l+4,e+q-1,l,e+q-5,l,e+4];e=c.selected?g.theme.bgH:g.theme.bg2;g.setColor(e).fillPoly(l).setColor(c.selected?g.theme.fgH:g.theme.fg2).drawPoly(l);void 0!==c.col&&g.setColor(c.col);c.src?g.setBgColor(e).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI* -(c.r||0)}):g.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){g.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){c.render(c)},h:function(c){c.c.forEach(k)},v:function(c){c.c.forEach(k)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),f=[];r(b,a,f,this.rects,null);for(var h in a)delete this.rects[h];b=Object.keys(a).map(c=>a[c]).reverse(); -for(var m of b)g.setBgColor(m.bg).clearRect.apply(g,m);f.forEach(k)}else k(b)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(b){switch(b.type){case "h":var k=b.x+(0|b.pad),d=0,a=b.c&&b.c.reduce((e,n)=>e+(0|n.fillx),0);a||(k+=b.w-b._w>>1,a=1);var f=k;b.c.forEach(e=>{e.x=0|f;k+=e._w;d+=0|e.fillx;f=k+Math.floor(d*(b.w-b._w)/a);e.w=0|f-e.x;e.h=0|(e.filly?b.h-(b.pad<<1):e._h);e.y=0|b.y+(0|b.pad)+((1+(0|e.valign))*(b.h-(b.pad<<1)-e.h)>>1);e.c&&this.layout(e)});break; -case "v":var h=b.y+(0|b.pad),m=0,c=b.c&&b.c.reduce((e,n)=>e+(0|n.filly),0);c||(h+=b.h-b._h>>1,c=1);var l=h;b.c.forEach(e=>{e.y=0|l;h+=e._h;m+=0|e.filly;l=h+Math.floor(m*(b.h-b._h)/c);e.h=0|l-e.y;e.w=0|(e.fillx?b.w-(b.pad<<1):e._w);e.x=0|b.x+(0|b.pad)+((1+(0|e.halign))*(b.w-(b.pad<<1)-e.w)>>1);e.c&&this.layout(e)})}};p.prototype.debug=function(b,k){b||(b=this._l);k=k||1;g.setColor(k&1,k&2,k&4).drawRect(b.x+k-1,b.y+k-1,b.x+b.w-k,b.y+b.h-k);b.pad&&g.drawRect(b.x+b.pad-1,b.y+b.pad-1,b.x+b.w-b.pad,b.y+ -b.h-b.pad);k++;b.c&&b.c.forEach(d=>this.debug(d,k))};p.prototype.update=function(){function b(a){"ram";k[a.type](a);if(a.r&1){var f=a._w;a._w=a._h;a._h=f}a._w=0|Math.max(a._w+(a.pad<<1),0|a.width);a._h=0|Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var k={txt:function(a){a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var f=g.setFont(a.font).stringMetrics(a.label);a._w=f.width;a._h=f.height}},btn:function(a){a.font&& -a.font.endsWith("%")&&(a.font="Vector"+Math.round(g.getHeight()*a.font.slice(0,-1)/100));var f=a.src?g.imageMetrics("function"==typeof a.src?a.src():a.src):g.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+f.height;a._w=20+f.width},img:function(a){var f=g.imageMetrics("function"==typeof a.src?a.src():a.src),h=a.scale||1;a._w=f.width*h;a._h=f.height*h},"":function(a){a._w=0;a._h=0},custom:function(a){a._w=0;a._h=0},h:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>Math.max(f,h._h),0);a._w= -a.c.reduce((f,h)=>f+h._w,0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)},v:function(a){a.c.forEach(b);a._h=a.c.reduce((f,h)=>f+h._h,0);a._w=a.c.reduce((f,h)=>Math.max(f,h._w),0);null==a.fillx&&a.c.some(f=>f.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(f=>f.filly)&&(a.filly=1)}},d=this._l;b(d);d.fillx||d.filly?(d.w=Bangle.appRect.w,d.h=Bangle.appRect.h,d.x=Bangle.appRect.x,d.y=Bangle.appRect.y):(d.w=d._w,d.h=d._h,d.x=Bangle.appRect.w-d.w>>1,d.y= -Bangle.appRect.y+(Bangle.appRect.h-d.h>>1));this.layout(d)};p.prototype.clear=function(b){b||(b=this._l);g.reset();void 0!==b.bgCol&&g.setBgColor(b.bgCol);g.clearRect(b.x,b.y,b.x+b.w-1,b.y+b.h-1)};exports=p \ No newline at end of file +function p(d,h){function b(e){"ram";e.id&&(a[e.id]=e);e.type||(e.type="");e.c&&e.c.forEach(b)}this._l=this.l=d;this.options=h||{};this.lazy=this.options.lazy||!1;this.physBtns=1;let f;if(2!=process.env.HWVERSION){this.physBtns=3;f=[];function e(l){"ram";"btn"==l.type&&f.push(l);l.c&&l.c.forEach(e)}e(d);f.length&&(this.physBtns=0,this.buttons=f,this.selectedButton=-1)}if(this.options.btns)if(d=this.options.btns,this.physBtns>=d.length){this.b=d;let e=Math.floor(Bangle.appRect.h/this.physBtns); +for(2d.length;)d.push({label:""});this._l.width=g.getWidth()-8;this._l={type:"h",filly:1,c:[this._l,{type:"v",pad:1,filly:1,c:d.map(l=>(l.type="txt",l.font="6x8",l.height=e,l.r=1,l))}]}}else this._l.width=g.getWidth()-32,this._l={type:"h",c:[this._l,{type:"v",c:d.map(e=>(e.type="btn",e.filly=1,e.width=32,e.r=1,e))}]},f&&f.push.apply(f,this._l.c[1].c);this.setUI();var a=this;b(this._l);this.updateNeeded=!0}function t(d,h,b,f,a){var e= +null==d.bgCol?a:g.toColor(d.bgCol);if(e!=a||"txt"==d.type||"btn"==d.type||"img"==d.type||"custom"==d.type){var l=d.c;delete d.c;var k="H"+E.CRC32(E.toJS(d));l&&(d.c=l);delete h[k]||((f[k]=[d.x,d.y,d.x+d.w-1,d.y+d.h-1]).bg=null==a?g.theme.bg:a,b&&(b.push(d),b=null))}if(d.c)for(var c of d.c)t(c,h,b,f,e)}p.prototype.setUI=function(){Bangle.setUI();let d;this.buttons&&(Bangle.setUI({mode:"updown",back:this.options.back,remove:this.options.remove},h=>{var b=this.selectedButton,f=this.buttons.length;if(void 0=== +h&&this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);!this.options.back&&!this.options.remove||d||Bangle.setUI({mode:"custom",back:this.options.back,remove:this.options.remove});if(this.b){function h(b,f){.75=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler=(b,f)=>h(this._l,f);Bangle.on("touch", +Bangle.touchHandler)}};p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>>1),n)}else b.setFont(c.font).setFontAlign(0, +0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=c.selected?b.theme.bgH:b.theme.bg2;b.setColor(n).fillPoly(m).setColor(c.selected?b.theme.fgH:b.theme.fg2).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5* +Math.PI*(c.r||0)}):b.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),e=[];t(d,a,e,this.rects,null);for(var l in a)delete this.rects[l];d= +Object.keys(a).map(c=>a[c]).reverse();for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-(b.pad<<1):k._h);k.y=0|b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<< +1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram";var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);h=h||1;g.setColor(h&1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h); +d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad,d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label); +a._w=e.width;a._h=e.height}},btn:function(a){"ram";a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*l},"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0}, +h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=this._l;d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h, +f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w,f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p \ No newline at end of file diff --git a/modules/README.md b/modules/README.md index 62ce90a97..3f8e90b06 100644 --- a/modules/README.md +++ b/modules/README.md @@ -13,7 +13,8 @@ Development When apps that use these modules are 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. +when developing via the IDE the module won't get pulled in by default +so you may see the error "Module not found" in the IDE when sending code to the Bangle. To fix this you have three options: @@ -52,3 +53,6 @@ to the module. to `https://banglejs.com/apps/modules|https://www.espruino.com/modules` The next time you upload your app, the module will automatically be included. + +**Note:** You can optionally use `https://raw.githubusercontent.com/espruino/BangleApps/master/modules|https://www.espruino.com/modules` +as the module URL to pull in modules direct from the development app loader (which could be slightly newer than the ones on https://banglejs.com/apps) diff --git a/modules/clock_info.js b/modules/clock_info.js index 76a93ba49..238888b1c 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -1,24 +1,30 @@ -var exports = {}; /* Module that allows for loading of clock 'info' displays that can be scrolled through on the clock face. `load()` returns an array of menu objects, where each object contains a list of menu items: -* 'name' : text to display and identify menu object (e.g. weather) -* 'img' : a 24x24px image -* 'items' : menu items such as temperature, humidity, wind etc. +* `name` : text to display and identify menu object (e.g. weather) +* `img` : a 24x24px image +* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date) +* `items` : menu items such as temperature, humidity, wind etc. Note that each item is an object with: -* 'item.name' : friendly name to identify an item (e.g. temperature) -* 'item.get' : function that resolves with: - { - 'text' : the text to display for this item - 'img' : a 24x24px image to display for this item - } -* 'item.show' : called when item should be shown. Enables updates. Call BEFORE 'get' -* 'item.hide' : called when item should be hidden. Disables updates. -* .on('redraw', ...) : event that is called when 'get' should be called again (only after 'item.show') -* 'item.run' : (optional) called if the info screen is tapped - can perform some action. Return true if the caller should feedback the user. +* `item.name` : friendly name to identify an item (e.g. temperature) +* `item.hasRange` : if `true`, `.get` returns `v/min/max` values (for progress bar/guage) +* `item.get` : function that returns an object: + +{ + 'text' // the text to display for this item + 'short' : (optional) a shorter text to display for this item (at most 6 characters) + 'img' // optional: a 24x24px image to display for this item + 'v' // (if hasRange==true) a numerical value + 'min','max' // (if hasRange==true) a minimum and maximum numerical value (if this were to be displayed as a guage) +} + +* `item.show` : called when item should be shown. Enables updates. Call BEFORE 'get' +* `item.hide` : called when item should be hidden. Disables updates. +* `.on('redraw', ...)` : event that is called when 'get' should be called again (only after 'item.show') +* `item.run` : (optional) called if the info screen is tapped - can perform some action. Return true if the caller should feedback the user. See the bottom of this file for example usage... @@ -30,10 +36,12 @@ example.clkinfo.js : img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }), items: [ { name : "Item1", - get : () => ({ text : "TextOfItem1", - img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }), + get : () => ({ text : "TextOfItem1", v : 10, min : 0, max : 100, + img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") + }), show : () => {}, hide : () => {} + // run : () => {} optional (called when tapped) } ] }; @@ -41,6 +49,15 @@ example.clkinfo.js : */ +let storage = require("Storage"); +let stepGoal = undefined; +// Load step goal from health app and pedometer widget +let d = storage.readJSON("health.json", true) || {}; +stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined; +if (stepGoal == undefined) { + d = storage.readJSON("wpedom.json", true) || {}; + stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000; +} exports.load = function() { // info used for drawing... @@ -63,21 +80,29 @@ exports.load = function() { img: atob("GBiBAf8B//4B//4B//4B//4A//x4//n+f/P/P+fPn+fPn+fP3+/Px+/Px+fn3+fzn+f/n/P/P/n+f/x4//4A//4B//4B//4B//8B/w=="), items: [ { name : "Battery", - get : () => ({ - text : E.getBattery() + "%", - img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") }), + hasRange : true, + get : () => { let v = E.getBattery(); return { + text : v + "%", v : v, min:0, max:100, + img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") + }}, show : function() { this.interval = setInterval(()=>this.emit('redraw'), 60000); Bangle.on("charging", batteryUpdateHandler); batteryUpdateHandler(); }, hide : function() { clearInterval(this.interval); delete this.interval; Bangle.removeListener("charging", batteryUpdateHandler); }, }, - { name : "Steps", get : () => ({ - text : Bangle.getHealthStatus("day").steps, - img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") }), + { name : "Steps", + hasRange : true, + get : () => { let v = Bangle.getHealthStatus("day").steps; return { + text : v, v : v, min : 0, max : stepGoal, + img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") + }}, show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); }, hide : function() { Bangle.removeListener("step", stepUpdateHandler); }, }, - { name : "HRM", get : () => ({ - text : Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", - img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") }), + { name : "HRM", + hasRange : true, + get : () => { let v = Math.round(Bangle.getHealthStatus("last").bpm); return { + text : v + " bpm", v : v, min : 40, max : 200, + img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") + }}, show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); hrmUpdateHandler(); }, hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; }, } @@ -86,9 +111,11 @@ exports.load = function() { var bangleItems = menu[0].items; if (Bangle.getPressure){ // Altimeter may not exist - bangleItems.push({ name : "Altitude", get : () => ({ - text : alt, - img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") }), + bangleItems.push({ name : "Altitude", + get : () => ({ + text : alt, v : alt, + img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") + }), show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); }, hide : function() { clearInterval(this.interval); delete this.interval; }, }); @@ -103,7 +130,7 @@ exports.load = function() { if(b) b.items = b.items.concat(a.items); else menu = menu.concat(a); } catch(e){ - console.log("Could not load clock info.") + console.log("Could not load clock info "+E.toJS(fn)) } }); @@ -111,12 +138,160 @@ exports.load = function() { return menu; }; +/** Adds an interactive menu that could be used on a clock face by swiping. +Simply supply the menu data (from .load) and a function to draw the clock info. -// Code for testing +For example: + +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { + x : 20, y: 20, w: 80, h:80, // dimensions of area used for clock_info + draw : (itm, info, options) => { + g.reset().clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); + if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); // show if focused + var midx = options.x+options.w/2; + if (info.img) g.drawImage(info.img, midx-12,options.y+4); + g.setFont("6x8:2").setFontAlign(0,1).drawString(info.text, midx,options.y+44); + } +}); +// then when clock 'unloads': +clockInfoMenu.remove(); +delete clockInfoMenu; + +Then if you need to unload the clock info so it no longer +uses memory or responds to swipes, you can call clockInfoMenu.remove() +and delete clockInfoMenu + +clockInfoMenu is the 'options' parameter, with the following added: + +* `index` : int - which instance number are we? Starts at 0 +* `menuA` : int - index in 'menu' of showing clockInfo item +* `menuB` : int - index in 'menu[menuA].items' of showing clockInfo item +* `remove` : function - remove this clockInfo item +* `redraw` : function - force a redraw +* `focus` : function - bool to show if menu is focused or not + +You can have more than one clock_info at once as well, sfor instance: + +let clockInfoDraw = (itm, info, options) => { + g.reset().setBgColor(options.bg).setColor(options.fg).clearRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); + if (options.focus) g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) + var midx = options.x+options.w/2; + if (info.img) g.drawImage(info.img, midx-12,options.y); + g.setFont("6x15").setFontAlign(0,1).drawString(info.text, midx,options.y+41); +}; +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg }); +let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { x:0, y:120, w:50, h:40, draw : clockInfoDraw, bg : bgColor, fg : g.theme.bg}); + +*/ +exports.addInteractive = function(menu, options) { + if (!menu.length || !menu[0].items.length) return; // no infos - can't load a clock_info + if ("function" == typeof options) options = {draw:options}; // backwards compatibility + options.index = 0|exports.loadCount; + exports.loadCount = options.index+1; + options.focus = options.index==0 && options.x===undefined; // focus if we're the first one loaded and no position has been defined + const appName = "default:"+options.index; + + { // load the currently showing clock_infos + let settings = require("Storage").readJSON("clock_info.json",1)||{}; + if (settings[appName]) { + let a = settings[appName].a|0; + let b = settings[appName].b|0; + if (menu[a] && menu[a].items[b]) { // all ok + options.menuA = a; + options.menuB = b; + } + } + } + if (options.menuA===undefined) options.menuA = 0; + if (options.menuB===undefined) options.menuB = Math.min(exports.loadCount, menu[options.menuA].items.length)-1; + function drawItem(itm) { + options.draw(itm, itm.get(), options); + } + function menuShowItem(itm) { + options.redrawHandler = ()=>drawItem(itm); + itm.on('redraw', options.redrawHandler); + itm.uses = (0|itm.uses)+1; + if (itm.uses==1) itm.show(); + itm.emit("redraw"); + } + function menuHideItem(itm) { + itm.removeListener('redraw',options.redrawHandler); + delete options.redrawHandler; + itm.uses--; + if (!itm.uses) + itm.hide(); + } + // handling for swipe between menu items + function swipeHandler(lr,ud){ + if (!options.focus) return; // ignore if we're not focussed + var oldMenuItem; + if (ud) { + if (menu[options.menuA].items.length==1) return; // 1 item - can't move + oldMenuItem = menu[options.menuA].items[options.menuB]; + options.menuB += ud; + if (options.menuB<0) options.menuB = menu[options.menuA].items.length-1; + if (options.menuB>=menu[options.menuA].items.length) options.menuB = 0; + } else if (lr) { + if (menu.length==1) return; // 1 item - can't move + oldMenuItem = menu[options.menuA].items[options.menuB]; + options.menuA += lr; + if (options.menuA<0) options.menuA = menu.length-1; + if (options.menuA>=menu.length) options.menuA = 0; + options.menuB = 0; + } + if (oldMenuItem) { + menuHideItem(oldMenuItem); + oldMenuItem.removeAllListeners("draw"); + menuShowItem(menu[options.menuA].items[options.menuB]); + } + // save the currently showing clock_info + let settings = require("Storage").readJSON("clock_info.json",1)||{}; + settings[appName] = {a:options.menuA,b:options.menuB}; + require("Storage").writeJSON("clock_info.json",settings); + } + Bangle.on("swipe",swipeHandler); + var touchHandler; + if (options.x!==undefined && options.y!==undefined && options.w && options.h) { + touchHandler = function(_,e) { + if (e.x(options.x+options.w) || e.y>(options.y+options.h)) { + if (options.focus) { + options.focus=false; + options.redraw(); + } + return; // outside area + } + if (!options.focus) { + options.focus=true; // if not focussed, set focus + options.redraw(); + } else if (menu[options.menuA].items[options.menuB].run) + menu[options.menuA].items[options.menuB].run(); // allow tap on an item to run it (eg home assistant) + else options.focus=true; + }; + Bangle.on("touch",touchHandler); + } + // draw the first item + menuShowItem(menu[options.menuA].items[options.menuB]); + // return an object with info that can be used to remove the info + options.remove = function() { + Bangle.removeListener("swipe",swipeHandler); + if (touchHandler) Bangle.removeListener("touch",touchHandler); + menuHideItem(menu[options.menuA].items[options.menuB]); + exports.loadCount--; + }; + options.redraw = function() { + drawItem(menu[options.menuA].items[options.menuB]); + }; + return options; +}; + +// Code for testing (plots all elements from first list) /* g.clear(); var menu = exports.load(); // or require("clock_info").load() -var itemsFirstMenu = menu[0].items; +var items = menu[0].items; items.forEach((itm,i) => { var y = i*24; console.log("Starting", itm.name); diff --git a/modules/widget_utils.js b/modules/widget_utils.js new file mode 100644 index 000000000..3440a01d2 --- /dev/null +++ b/modules/widget_utils.js @@ -0,0 +1,137 @@ +/// hide any visible widgets +exports.hide = function() { + exports.cleanup(); + if (!global.WIDGETS) return; + g.reset(); // reset colors + for (var w of global.WIDGETS) { + if (w._draw) return; // already hidden + w._draw = w.draw; + w.draw = () => {}; + w._area = w.area; + w.area = ""; + if (w.x!=undefined) g.clearRect(w.x,w.y,w.x+w.width-1,w.y+23); + } +}; + +/// Show any hidden widgets +exports.show = function() { + exports.cleanup(); + if (!global.WIDGETS) return; + for (var w of global.WIDGETS) { + if (!w._draw) return; // not hidden + w.draw = w._draw; + w.area = w._area; + delete w._draw; + delete w._area; + w.draw(w); + } +}; + +/// Remove any intervals/handlers/etc that we might have added. Does NOT re-show widgets that were hidden +exports.cleanup = function() { + delete Bangle.appRect; + if (exports.swipeHandler) { + Bangle.removeListener("swipe", exports.swipeHandler); + delete exports.swipeHandler; + } + if (exports.animInterval) { + clearInterval(exports.animInterval); + delete exports.animInterval; + } + if (exports.hideTimeout) { + clearTimeout(exports.hideTimeout); + delete exports.hideTimeout; + } + if (exports.origDraw) { + Bangle.drawWidgets = exports.origDraw; + delete exports.origDraw; + } +} + +/** Put widgets offscreen, and allow them to be swiped +back onscreen with a downwards swipe. Use .show to undo. +Bangle.js 2 only at the moment. */ +exports.swipeOn = function() { + exports.cleanup(); + if (!global.WIDGETS) return; + + /* TODO: maybe when widgets are offscreen we don't even + store them in an offscreen buffer? */ + + // 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}); + 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()); + let _g = g; + let offset = -24; // where on the screen are we? -24=hidden, 0=full visible + + function queueDraw() { + Bangle.appRect.y = offset+24; + Bangle.appRect.h = 1 + Bangle.appRect.y2 - Bangle.appRect.y; + if (offset>-24) Bangle.setLCDOverlay(og, 0, offset); + else Bangle.setLCDOverlay(); + } + + for (var w of global.WIDGETS) { + if (w._draw) return; // already hidden + w._draw = w.draw; + w.draw = function() { + g=og; + this._draw(this); + g=_g; + if (offset>-24) queueDraw(); + }; + w._area = w.area; + if (w.area.startsWith("b")) + w.area = "t"+w.area.substr(1); + } + + exports.origDraw = Bangle.drawWidgets; + Bangle.drawWidgets = ()=>{ + g=og; + exports.origDraw(); + g=_g; + }; + + function anim(dir, callback) { + if (exports.animInterval) clearInterval(exports.interval); + exports.animInterval = setInterval(function() { + offset += dir; + let stop = false; + if (dir>0 && offset>=0) { // fully down + stop = true; + offset = 0; + } else if (dir<0 && offset<-23) { // fully up + stop = true; + offset = -24; + } + if (stop) { + clearInterval(exports.animInterval); + delete exports.animInterval; + if (callback) callback(); + } + queueDraw(); + }, 50); + } + // On swipe down, animate to show widgets + exports.swipeHandler = function(lr,ud) { + if (exports.hideTimeout) { + clearTimeout(exports.hideTimeout); + delete exports.hideTimeout; + } + if (ud>0 && offset<0) anim(4, function() { + exports.hideTimeout = setTimeout(function() { + anim(-4); + }, 2000); + }); + if (ud<0 && offset>-24) anim(-4); + + }; + Bangle.on("swipe", exports.swipeHandler); +}; diff --git a/package-lock.json b/package-lock.json index 878b45cb2..e981abdb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2913 +1,8 @@ { "name": "BangleApps", "version": "0.0.1", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "BangleApps", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "acorn": "^7.2.0" - }, - "devDependencies": { - "eslint": "^8.14.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.26.0", - "npm-watch": "^0.11.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.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/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "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-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, - "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/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "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/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "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/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "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/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "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/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "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/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.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-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "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/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "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/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "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/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "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-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "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-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "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/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, - "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-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, - "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.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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.16", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", - "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" - }, - "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/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "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/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "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/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "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/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "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-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "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/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "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/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "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/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "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 - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - }, "dependencies": { "@eslint/eslintrc": { "version": "1.3.0", @@ -2943,21 +38,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2979,8 +59,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "ajv": { "version": "6.12.6", @@ -2994,15 +73,6 @@ "uri-js": "^4.2.2" } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3071,22 +141,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3106,38 +160,6 @@ "fill-range": "^7.0.1" } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -3154,12 +176,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3197,27 +213,6 @@ } } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3239,20 +234,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, "confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -3270,12 +251,6 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3285,33 +260,12 @@ "ms": "2.1.2" } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "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 }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -3331,36 +285,6 @@ "esutils": "^2.0.2" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "es-abstract": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", @@ -3412,12 +336,6 @@ "is-symbol": "^1.0.2" } }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3765,15 +683,6 @@ "has-symbols": "^1.0.3" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -3807,15 +716,6 @@ "is-glob": "^4.0.3" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, "globals": { "version": "13.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", @@ -3825,31 +725,6 @@ "type-fest": "^0.20.2" } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -3895,18 +770,6 @@ "has-symbols": "^1.0.2" } }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -3929,12 +792,6 @@ "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3957,12 +814,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -4008,15 +859,6 @@ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", @@ -4041,12 +883,6 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4056,28 +892,12 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, "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 }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4093,18 +913,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4142,12 +950,6 @@ "has-symbols": "^1.0.2" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -4157,12 +959,6 @@ "call-bind": "^1.0.2" } }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4178,12 +974,6 @@ "argparse": "^2.0.1" } }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4205,24 +995,6 @@ "minimist": "^1.2.0" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4249,36 +1021,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4307,21 +1049,21 @@ "dev": true }, "nodemon": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", - "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", "dev": true, "requires": { "chokidar": "^3.5.2", "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", + "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", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "dependencies": { "debug": { @@ -4371,12 +1113,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, "npm-watch": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.11.0.tgz", @@ -4456,12 +1192,6 @@ "word-wrap": "^1.2.3" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -4486,18 +1216,6 @@ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4543,69 +1261,18 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true - }, "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 }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "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 }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true - } - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -4643,24 +1310,6 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -4678,15 +1327,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4708,15 +1348,6 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4743,30 +1374,21 @@ "object-inspect": "^1.9.0" } }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "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==", + "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, "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } } }, "string.prototype.trimend": { @@ -4791,6 +1413,15 @@ "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", @@ -4842,12 +1473,6 @@ "readable-stream": "3" } }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4893,15 +1518,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -4920,48 +1536,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4971,15 +1545,6 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5014,61 +1579,17 @@ "is-symbol": "^1.0.3" } }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, "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 }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "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 - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } } diff --git a/package.json b/package.json index e11e79ae5..08f3a19ce 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "scripts": { "lint-apps": "eslint ./apps --ext .js", - "test": "node bin/sanitycheck.js && eslint ./apps --ext .js", + "test": "node bin/sanitycheck.js && eslint ./apps --ext .js && eslint ./modules --ext .js", "update-local-apps": "./bin/create_apps_json.sh apps.local.json", "local": "npm-watch & npx http-server -a localhost -c-1", "start": "npx http-server -c-1" diff --git a/tsconfig.json b/tsconfig.json index db3db1fc3..8da08b8e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,9 @@ "strict": true, "typeRoots": ["./typescript/types"] }, - "include": ["./**/*"] + "include": ["./**/*"], + "exclude": [ + "**/gpconv.d.ts", + "**/gpconv_bg.wasm.d.ts" + ] } diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 8e498e884..15653acc2 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -8,7 +8,7 @@ "name": "Bangle.ts", "version": "0.0.1", "dependencies": { - "node-fetch": "^3.2.9" + "node-fetch": "^3.2.10" }, "devDependencies": { "typescript": "4.5.2" @@ -74,9 +74,9 @@ } }, "node_modules/node-fetch": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.9.tgz", - "integrity": "sha512-/2lI+DBecVvVm9tDhjziTVjo2wmTsSxSk58saUYP0P/fRJ3xxtfMDY24+CKTkfm0Dlhyn3CSXNL0SoRiCZ8Rzg==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -141,9 +141,9 @@ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" }, "node-fetch": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.9.tgz", - "integrity": "sha512-/2lI+DBecVvVm9tDhjziTVjo2wmTsSxSk58saUYP0P/fRJ3xxtfMDY24+CKTkfm0Dlhyn3CSXNL0SoRiCZ8Rzg==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", diff --git a/typescript/package.json b/typescript/package.json index 070257cc0..9533d77a7 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -10,6 +10,6 @@ "build": "tsc" }, "dependencies": { - "node-fetch": "^3.2.9" + "node-fetch": "^3.2.10" } } diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts index 3e14741ee..89921972c 100644 --- a/typescript/types/main.d.ts +++ b/typescript/types/main.d.ts @@ -5403,15 +5403,6 @@ declare class Bangle { */ static getHealthStatus(range?: "current" | "last" | "day"): HealthStatus; - /** - * Feature flag - If true, this Bangle.js firmware reads `setting.json` and - * modifies beep & buzz behaviour accordingly (the bootloader doesn't need to do - * it). - * @returns {boolean} - * @url http://www.espruino.com/Reference#l_Bangle_F_BEEPSET - */ - static F_BEEPSET: boolean; - /** * Reads debug info * @returns {any} @@ -13018,4 +13009,4 @@ type Libraries = { */ read(length: number, addr: number): any; } -} \ No newline at end of file +} diff --git a/webtools b/webtools new file mode 160000 index 000000000..2ab71a33d --- /dev/null +++ b/webtools @@ -0,0 +1 @@ +Subproject commit 2ab71a33d69bfda40465174ffe57adb03c21fc42