diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..345bce54f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: espruino +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['http://www.espruino.com/Donate']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1eb009153..a3469e7bb 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,4 +1,4 @@ -name: Node CI +name: build on: [push, pull_request] @@ -6,29 +6,25 @@ jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [16.x] - steps: - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + - name: Use Node.js 16.x + uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} - - name: install testing dependencies - run: npm i - - name: test all apps and widgets - run: npm run test - - name: install typescript dependencies + node-version: 16.x + - name: Install testing dependencies + run: npm ci + - name: Test all apps and widgets + run: npm test + - name: Install typescript dependencies working-directory: ./typescript run: npm ci - - name: build types + - name: Build types working-directory: ./typescript run: npm run build:types - - name: build all TS apps and widgets + - name: Build all TS apps and widgets working-directory: ./typescript - run: npm run build \ No newline at end of file + run: npm run build diff --git a/.gitignore b/.gitignore index 231851dd6..a9398e871 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .htaccess node_modules -package-lock.json .DS_Store *.js.bak appdates.csv diff --git a/README.md b/README.md index b3da9f685..ea485da86 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Bangle.js App Loader (and Apps) ================================ -[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](https://app.travis-ci.com/github/espruino/BangleApps) +[![Build Status](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml/badge.svg)](https://github.com/espruino/BangleApps/actions/workflows/nodejs.yml) * Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) * Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/) @@ -191,7 +191,7 @@ widget bar at the top of the screen they can add themselves to the global ``` WIDGETS["mywidget"]={ - area:"tl", // tl (top left), tr (top right) + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) sortorder:0, // (Optional) determines order of widgets in the same corner width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout draw:draw // called to draw the widget diff --git a/apps/2ofthemclk/ChangeLog b/apps/2ofthemclk/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/2ofthemclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/2ofthemclk/README.md b/apps/2ofthemclk/README.md new file mode 100644 index 000000000..7ac2cf779 --- /dev/null +++ b/apps/2ofthemclk/README.md @@ -0,0 +1,11 @@ +# two of them clock + +You can now wear teh memez on your wrist. + +![](screenshot.png) + +Also serves as an example of displaying seconds only when unlocked or charging and only refreshing on the minute otherwise. +Widgets not supported + +## Creator +- [Kilrah](https://github.com/kilrah) diff --git a/apps/2ofthemclk/app-icon.js b/apps/2ofthemclk/app-icon.js new file mode 100644 index 000000000..9bfb8a550 --- /dev/null +++ b/apps/2ofthemclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgZC/AH4ADkAPOgVJkgEBAQQAJiQRByEJgmQCJWSpMEAQMkyQJCpASHhAOBpAmBJJgjBCIUJCRg4CCIJxFMQ2SoARCkmACI0EBAJHCCIMLj4RFiUBskAgIXBEAU5A4P34CtCiEJsEJ/AHBCgOBAoQAEi0H////HciQsBwywICIXWzkG4A+BEY0gif46dt6/cgnIgkWnHfLIP/MoUWwHbpvC/kAjEEj0HNYQCCkEfGgP/64RB2EAifHLwMAjg1CCIMD/0H/0B8EAh+HgeAkARCE4IjC/4jBYIMPLIcIAYUPB4OBCIQABhu/AoShCHYIRBx6QBDgUw2//8OHPwcJ39//ILBCIU9LgMBSQgsBJAYRBkE/CIIABgRHD3wRFkk/2zBDAYU//3b/oRB8ARBj6ABgEE7YREEYf+oMkSwINCyClCn//z//+4RBgMkgU3EgUcwFJgEeboOXCIP2EYJCDAAVJkkGWoIuBgf2EYQPDkECCIOGd4ffyEJkgFBAAcSoEkwQCBhw+BwQaByVAkGAKwIFBBANLkEQgAyBCIVIkBpBgmSBYOQoApBgcgiQRCAQIyCCgsSjIFBCIcgRgJNCCgQyBpAgDAQT2BCgIOBBAQUCCIpfBCIwCKP4QRNpCSDCLyJBCIbjBTwYRLboJ0BCI4QD")) diff --git a/apps/2ofthemclk/app.js b/apps/2ofthemclk/app.js new file mode 100644 index 000000000..78415fba7 --- /dev/null +++ b/apps/2ofthemclk/app.js @@ -0,0 +1,90 @@ +const img = require("heatshrink").decompress(atob("2GwgZC/ADEIAQMBgEQAgMChEEAoQA4wACEAHKDBAQYA9X/YACgQFGkBH1HAQFCwEIgkAAQVAH2GAAwtBQwcQgMAwQ/vGQI4CAQRHCwFAkACCQYTRFAEqzBAopHCgMEAQiSHAEsQAYT1ChBEEPoMQBAMQAoQRCX9BEDOgR6BHwkgwVBQw4aEIlKGCPoWCI4REBBAMEQYUCH856CNwJ9CO4YCBHwOSgCGBRIREBJQICBAAOAH0UQGQJ0CHYTCDAQOQpKABZAICCZAREBQccgIgJHBoJrBXIQFBIISDFwALBKwTEkd4RrBPobIBHAMkIgICBQYoODSoIfBRIYmEACjjBMoIgBwAsEF4YFDkmSAoUBBATgDDQIjEADSqBEwI4BgJxCPoUEiVIkmCpI7BoLCBpBTEC4MChAjEfy6hCL4J3EAQMJHwLCDBwJEBQYMkiQODI4JcBQAICDEwKMCRK4dBYoJrCHYMgyB3BHYWShKGCBweAJoLXDQAJiBZAQAVC4ZZBU4KADPQR3BQYLCByA+BBAWCJoabCUIR6BcwQIFaIQAPC4KABwC8CoC/DPQSDDoICBHwREBJQabBHwRHBNYSqDZYQAMCIQCBQYT7DAQKADQwVJAQY7BAoJEBHAJZCQwYmBEANANATXBI4IvBPpJQFiEAPoYCBWwg7CBYIFCAQIFGcAYCBX4ImBIgQaBBwLDPwCDCoMAEYS/BfwJECoJEDAQuCSQLOBBwJ9DQYY7BHwaDDABYRBQwIgBDQMSVoMSHwK5BIgKDDIIyACKwMEyDLChKGEZwckIgWCIhwXBYQY7BEwJHBHZICHSoSeCMogCCQYYOBYho+BTwQmBQYQvCQAUSIJyACZAKDCMobsDEANAH5hTDTYYmDF4ICBQaICCLgSeBEAKACJohKCRgJBIwCeBC4IdBEALsBQAYCCHx4XBLIRlBkkQRgRBBdgMEJoSDMgJEBKwRcCU4J9SAQYaCGoI4BAoUQVobFBJQL4BGoICBAA4UDL4QsCiRBVAQjdBiS8CAQMShLRDfAUQH44+BC4sgyAjBHzC/CGoKnCySqCagJuCiEIQYNAQZIRCyAXBQALFXQxBHEaIQIBQYZNBH4sSQYVAKwIgBIgQ+ZMQYFBHwMBVQiGCGoILBIAsEgBNBTwWCZASAdMQkSVQaDBKAOCXIVBZALLBIIY+BwBWBQYbCfQYbCBEwRHBRIRxBQwMCQgiYDCIOShBcCQbxEBEYJHChIIBAQMQQYK5CwFAH4JHBiCYCQYg+eUgaDBAQYLCeoR6BAQUBQYcCQYJNCCgRBgQAZrBBAZ0BYoUQAQMAQYaJBKwLaDDQgCdQA6MCBYI4BiEJIgKDCYoVAHwIOBCgRBggTvChLCDRIMEGQTFBAQKDCgAOBhI+kyVIj4mBgkCRgzFGgRHBQYRKBKAJcCIkP/+UIJpB3BJoMQAoOQoBBBgCYDQclJgF+YQZEIkkQKAKDBgGAQAKSBQYNBIMfgh0BHY2SXgI1BQANIgjLCQYKPBLJZBcgPBggIDoI+BHYL4CgUBBAI/BwDIBKYUSIMsAnApFfYQABHwOQoEEiACCZYKPBQctIGwSDEfYIAEX4L7BiQLCQYTFmpEDwAuBgSAByVAIIoABhMAYoKDBH0xBHGoJ6BABMkYoJTBAoTFqgf+H5QABkGQYQOAQdNPGQUcvxBMgGAoMEiVBkmQExYOBILkfwP4jhAKgmShEEYRxNBCIJEBiVJkBKSo4zDv//8CDLFgTdBkmCExWCC4gFBIIM5IK8fwDHMEx0EDBEB/5BQoED+EAgbFBQZIjBAYK2CVozRBAoMSLRUPDoJBPvEAvw1FEZJNBgI4DAQa2CAoKeKgf+g/kIJ84//+IIX4AYMHgfx4Ech0HjgqFiUJDol/8AFBkANBUJDsC8ECIJuT/H/MQ0H8f+n//x/AFQwmFyE4j4FBL4PHKwwAB/gmBJoNBTAYCIyP4CgJBG//wn/j+CvIgLFFgFwIIMAnAUHHwU4AQMIQZmf/5BIIgjyJII0OAQMPTA8B/4CBBYjILv//cZAIBMQRBNyAHCn41B/AXEMQMP/AcHhJBIk//KwIAJQwRBRKwN/4BBC8Fx4Hj/5BBJoJBNpEDCgQAWII8AMgP4j+PAgIADYQMPOIsBQZEfwE4IMEfHooAD/EDwE/dIqDI/g4QEYJBLg//+ARBIJKDBAA8EII1HcAZBN/kBEwxBFMQSDKDQSzEuACBIIySDIJsHgEOQZkcIJeOn4XCGQhHBiRBGOgZALuAaBIJeSg4vCSoJBHwP48F/45xDvxBKDoKCMTwQAHEAeRaIccIIuP/5rBg///x0DgeB44EBIIpKBv//8EPfAwAGQZeTDQN/DoU///x44+Bn6tCj/+cwJBCwAFCIImRQAMPIIIaBH5UHgf+IJf4jggDg59C8EOAoKhBj47DEwYCBhIgE/4RBn4gEIJKnBIJkfXwL4C/kBJQJBB8fwCwSJDAAK8BIIrmBIIIdBBgP/DQYAGBZDmIF4XgIIMD+E4XgIKBSQJuFHAMBIIn4/4UBAQf/45oCAAb1BVQRBJyCtCNwQCBuE48eABAQgCFYJHCAAMcgBBEyE/OIUfWwIVBLIxiBWwOOYpZZBhwJBgPAgF+JQU/IIopDMoK8CEAeAagUHgf/4BBCGw0B/5BNDAIODHwN+AgNxLgI1B8CDJUgoLDMoZ6BGwxoBj6VBIJORLYXAfwMHHAPAjgjBHYMHB4RBFEAwLFh6kBgJBDZAhBSNwMH/l/HwP/QYKtEIIk/KwIgEiE4BYM4YoP48DXBEwM/8Y7Dn//IJ74BhxBBh4IDAAPHHwIgBIIUB47RCgiDDHwIvCUIIvCL4P/YoqJEIJn/+I+FAAPwZwMBQwK5EIIxxCx0AU4Q0HQYjmLyAOBIJXgZwILBZwIgEBYJBDkGAg68BgDFBIIMB/BBIQZhBEbod/IIZfDAARBEgYFBkCDDg6/EHwMBYQhHDeQJBLwYwC4ED/wMBh4ICEwYOCIIg4DQYYvCa4JfB/CGBUIQmCIIU4IJmBDAP+O4SkCIIhHDIIs4IJH/xyDCMoP/46YD+IpBQZ2AHwPxX5H4AQofHIIcEIIQgBHwKGCHYQABuBZCn6MDII4gBIIahC//wC4KhBJYZBBMQomBgIgCQYMAUgQPDgLIBTYbgGQZJBCx40BRIQmCZAYAEFIyDDEALCC/8cgLmCQYhBQBwRBDHwLIHAAakGh0BIIQHCIIX/wLaDI4IFD8FwSQhBGpBBC8ZBB/kOHYSkCIIccv4CBDokcuADBQYjjDMQP8QY4IBI4ZBHSQZiEfwaDD+BfCFgIgHkCDEgEPIIUAbRDOKIIOQAofjHoWOAwJ6BYov8uLFHAALFCQYL1Bh/4L4Uf+A7Hv5BPL4UAv0HfwcBAgQADgEONgxBCAgI7BUIYADLIrjFIIyYDnEfQALpDPoStCNYTODcxCDBeohBFboKPBAQLpBIJRnD/ED/wUB+IgDjkOn7RDQYTwHIIQgC8FwIIOP45BEgCGBFgJ3DIJQoDDYPwC4OPHAIICJoStHIIsgCgPgagMfGoV/Rgf8v4eBIKK8Cx/AHAUD+E/JQM4BAXjU4yDFC4LXCQwIQEcYRfIB4cgA4pWCEYImCIIKtBwBEBJQaDKgE4GoPHGQ6hDIBIABIIk/gfxfYl+BQNx4DIDQZNJII6hBwLCDUghALQY3jGgZZCL4OPEwKPEjggGhJBEn4gBHwaDFDQ4gGIIcOYooAB+ChD/gLEj+AIJVwa4SbBACpfCIIkPEQLIEAA/HVQ7FDkkOQYP+n/gEwRBZcYI+LR4ofEgRBDkBBB8EB//gHR1wAomCIIgODX4YAJR4TFGQYihB/hBBYipiCA4hfBnBALF4SDCOgcSQYhBCAQPj+P4HRcfMQkEL4QOEOgPAgF/IJMDAYWAnDFIQYRQBg/jx4jBABUDIJAIEGIRxC8YmBIAnAAgaDBAAYgCAQSSEgeOXxn8AohBDyQHCHAYRFI4ILBPoMfx4PBwPAYpBlBn6kDABscAogdDyVIA4IgBMQICBn7sB444B45KBIgPxYoU/EIZBHa4JBEuA/HBAV+AwUBIIlJgg4C/yGD8F/YgjLFwP4IJTCB/4OEIJFxEALUDII0gh4wC/ACCEwIABX4MfIwgOCageCIImQnEcLgMcHxDCCv4vBWwIABhJBFkkBF4SGBGQI7Dd4JAGAAR6BgAgFyUHgBlCGQYAHBwMcAwYdFpIIBWwaADAAWOn4FD+ILFgEEIK8AnAEDgRBGQYLCD8AjC/w+BYoIAC/jXDAAXAQY9ADoZBMAAhBHgDjBAQSzBRIYAFeoQADGQKDHgH8gBcCMoI9KgbjCwRBGkBfBUgX8uEHj+BX4f8v0AJAxBJpBBBL4JBBQZ8SII0EgIsDAQUDX4fHRgMAj5BF/0AcwyDBAAQUBIJ5fHQYKhBO4j1BXgXwnEcNwRBHkAjGo62DIIPjMoLFJ+EBL45BD/+BHwfgZYLpCjkP/DFIEY8/GYYXBfYYAJIJMkBgLmBEAQ0DIITCGIIcBU42TX4kBMoIAMhJBLEAg0DJQM4j/wYoXHB4IFBQZGBIIgAOiA+HAQSbBj6eEIQfx/8AuEHRIUAhwOBIJGRIKcCIJWQg7FDX4wsBv5EBJouAU5E/KYYAOHxBBDn5BFgb7DgEcPoJNGIJMAIIsggFAHw0B4EEIJeSQYx9C/+OnAIE/BNCCgIjJHYL7FpCMGgP4HxSkD//+QY/x4AJDh7IBJoPgiQjKiAVCBAVIYhCDOgeAII4IFwPH/+P/DpMHYRQDIJDgIAQtATYx6BjgHEIIP4SoRlNI45BVpDyBAAf8boLgBX4IADg8f/0BHyBrDIA0CLJ5BEg4FDh/AEQrPB5JBbgBBPYocOgEfwAFDII3+HyICCDgsHgESCJFBBApBEg8cAgMgQY0H//yIKl4IQvgIJICGHYUAgPH/gXBglwQY0cdJ4CFo5fF8C/KIIy8Dg/kIIMn+CnHHyQCCC4IdFwTFPn+OC4U/GoUjII0AghBbnAdKoAIGxwaBv/4JQZBHHyYCCp/gd4fACJOQYo0kh//gfwgIICng/FgPBIK1AIIcAhIRKNY+R/EfwAXDn+AEIccuPJIK1INAIeBuBBJkGQYo4aBkECBAZBBnCEEHygmDgFx4BrBBxFBkkAIJACBiQFDkaDCh/AjgLEASsCgEgHAQLFyCDBYpCVIP4RBdyDCJOgNBgiDLAQkncwQABjg+XaJpNBfYMQQaH+H4MB4HgIMgsBiUJgGAQZ8kxxBBh0AghBjQAICBoDFBQZ8kx/Aj/+gKAjkmCoKABAQMAhAXPyZBB+DCkAQMJkkCgEgwSDQyVIkA+kpMEiEIgACBkCDRAVB9BgMEYYMAQwJB4QYNAQYUEgTLBQfGAQIQABwA+2AQNIkiDCgEIZYMIIO8JQIkBgjC3AQNBgkQIISDByBB3HAKDEoKA3AQOCgEgIAWChJB6hCDEhBB5iFAQf6DDgMAgSD7YYcEiVJkBB3kGAIIaDBYvMgH4cIkGCaIpBypBBEAAI7zAQtBH4kBgkSYocgIOdIQQrFBoIOEI4YCuoBAEoKDBpMEBwTLzQY5BBHeICFIAqDByAODkBBywRAEgUBgi/zAQmQoBBDiFAPosEIOaDFhI7zAQpAEgkSH26DIBw4JBhEEiFIkGQAQRBmpA/EAAVAkGAhEAwUAA==")); + +var battery = E.getBattery(); + +// Positions on screen +const timeX = 88, timeY = 52; +const dateX = 5, dateY = 180; +const battX = 172, battY = 175; + +// Draw on every second if unlocked or charging, minute otherwise, start at with seconds on load +var drawTimeout; +var drawInterval = 1000; + +// schedule a draw for the next interval +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, drawInterval - (Date.now() % drawInterval)); +} + +// Update display and timeout on lock/unlock and charge state change +Bangle.on('lock',on=>{ + draw(); +}); + +Bangle.on('charging',charging=>{ + draw(); +}); + +function draw() { + // work out how to display the current time + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + var time = (" "+h).substr(-2) + ":" + ("0"+m).substr(-2); + var seconds = ("0"+d.getSeconds()).substr(-2); + + // g.clear(); // Unneeded if background image takes the whole screen + + // Draw background + g.drawImage(img); + g.setColor(1, 1, 1); + + // draw the current time + g.setFontAlign(1,1); // align right bottom + g.setFont("6x15",3); + g.drawString(time, timeX, timeY, false); + + // Draw battery % + g.setFont("6x15",1); + var battStr = ""; + if(Bangle.isCharging()) { + battStr = "+"; + } + g.drawString(battStr + battery + "%", battX, battY, false); + + // Draw date + g.setFontAlign(-1,1); // align left bottom + g.setFont("6x15",2); + var dateStr = require("locale").date(d)+" "; + g.drawString(dateStr, dateX, dateY, false); + + // draw the seconds only if unlocked, set next timeout + if(!Bangle.isLocked() || Bangle.isCharging()) { + drawInterval = 1000; + g.setFont("6x15",2); + g.drawString(seconds, timeX+2, timeY-4, false); + } + else + drawInterval = 60000; + + // Schedule next draw + queueDraw(); + // console.log("Draw " + time + ":" + seconds); +} + +function refreshBattery() { + battery = E.getBattery(); +} + +// Only update displayed battery level every minute as it fluctuates a lot +var batteryInterval = setInterval(refreshBattery, 60000); + +Bangle.setUI("clock"); +Bangle.setLocked(false); +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); diff --git a/apps/2ofthemclk/app.png b/apps/2ofthemclk/app.png new file mode 100644 index 000000000..d304f27d9 Binary files /dev/null and b/apps/2ofthemclk/app.png differ diff --git a/apps/2ofthemclk/bg.png b/apps/2ofthemclk/bg.png new file mode 100644 index 000000000..5f65ca3c7 Binary files /dev/null and b/apps/2ofthemclk/bg.png differ diff --git a/apps/2ofthemclk/metadata.json b/apps/2ofthemclk/metadata.json new file mode 100644 index 000000000..fa02b3e2f --- /dev/null +++ b/apps/2ofthemclk/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "2ofthemclk", + "name": "two of them clock", + "version": "0.01", + "description": "You can now wear teh memez on your wrist.", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"2ofthemclk.app.js","url":"app.js"}, + {"name":"2ofthemclk.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/2ofthemclk/screenshot.png b/apps/2ofthemclk/screenshot.png new file mode 100644 index 000000000..b9a80a2c5 Binary files /dev/null and b/apps/2ofthemclk/screenshot.png differ diff --git a/apps/_example_widget/widget.js b/apps/_example_widget/widget.js index f7aed6991..226aea589 100644 --- a/apps/_example_widget/widget.js +++ b/apps/_example_widget/widget.js @@ -9,7 +9,7 @@ currently-running apps */ // add your widget WIDGETS["mywidget"]={ - area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right), be aware that not all apps support widgets at the bottom of the screen width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout draw:draw // called to draw the widget }; diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog index 4edb72aa5..da897b899 100644 --- a/apps/activityreminder/ChangeLog +++ b/apps/activityreminder/ChangeLog @@ -4,3 +4,4 @@ 0.04: Obey system quiet mode 0.05: Battery optimisation, add the pause option, bug fixes 0.06: Add a temperature threshold to detect (and not alert) if the BJS isn't worn. Better support for the peoples using the app at night +0.07: Fix bug on the cutting edge firmware \ No newline at end of file diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js index f3d72976e..c2b626fb3 100644 --- a/apps/activityreminder/app.js +++ b/apps/activityreminder/app.js @@ -1,42 +1,46 @@ -function drawAlert() { - E.showPrompt("Inactivity detected", { - title: "Activity reminder", - buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 } - }).then(function (v) { - if (v == 1) { - activityreminder_data.okDate = new Date(); +(function () { + // load variable before defining functions cause it can trigger a ReferenceError + const activityreminder = require("activityreminder"); + const storage = require("Storage"); + const activityreminder_settings = activityreminder.loadSettings(); + let activityreminder_data = activityreminder.loadData(); + + function drawAlert() { + E.showPrompt("Inactivity detected", { + title: "Activity reminder", + buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 } + }).then(function (v) { + if (v == 1) { + activityreminder_data.okDate = new Date(); + } + if (v == 2) { + activityreminder_data.dismissDate = new Date(); + } + if (v == 3) { + activityreminder_data.pauseDate = new Date(); + } + activityreminder.saveData(activityreminder_data); + load(); + }); + + // Obey system quiet mode: + if (!(storage.readJSON('setting.json', 1) || {}).quiet) { + Bangle.buzz(400); + } + setTimeout(load, 20000); } - if (v == 2) { - activityreminder_data.dismissDate = new Date(); + + function run() { + if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) { + drawAlert(); + } else { + eval(storage.read("activityreminder.settings.js"))(() => load()); + } } - if (v == 3) { - activityreminder_data.pauseDate = new Date(); - } - activityreminder.saveData(activityreminder_data); - load(); - }); - // Obey system quiet mode: - if (!(storage.readJSON('setting.json', 1) || {}).quiet) { - Bangle.buzz(400); - } - setTimeout(load, 20000); -} - -function run() { - if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) { - drawAlert(); - } else { - eval(storage.read("activityreminder.settings.js"))(() => load()); - } -} - - -const activityreminder = require("activityreminder"); -const storage = require("Storage"); -g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -const activityreminder_settings = activityreminder.loadSettings(); -const activityreminder_data = activityreminder.loadData(); -run(); + g.clear(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + run(); + +})(); \ No newline at end of file diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js index 4ae9548c2..f97cf274d 100644 --- a/apps/activityreminder/boot.js +++ b/apps/activityreminder/boot.js @@ -1,65 +1,70 @@ -function run() { - if (isNotWorn()) return; - let now = new Date(); - let h = now.getHours(); - - if (isDuringAlertHours(h)) { - let health = Bangle.getHealthStatus("day"); - if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed - || health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch - activityreminder_data.stepsOnDate = health.steps; - activityreminder_data.stepsDate = now; - activityreminder.saveData(activityreminder_data); - /* todo in a futur release - Add settimer to trigger like 30 secs after going in this part cause the person have been walking - (pass some argument to run() to handle long walks and not triggering so often) - */ - } - - if(activityreminder.mustAlert(activityreminder_data, activityreminder_settings)){ - load('activityreminder.app.js'); - } - } - -} - -function isNotWorn() { - return (Bangle.isCharging() || activityreminder_settings.tempThreshold >= E.getTemperature()); -} - -function isDuringAlertHours(h) { - if(activityreminder_settings.startHour < activityreminder_settings.endHour){ // not passing through midnight - return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour) - } else{ // passing through midnight - return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour) - } -} - -Bangle.on('midnight', function() { - /* - Usefull trick to have the app working smothly for people using it at night - */ - let now = new Date(); - let h = now.getHours(); - if (activityreminder_settings.enabled && isDuringAlertHours(h)){ - // updating only the steps and keeping the original stepsDate on purpose - activityreminder_data.stepsOnDate = 0; - activityreminder.saveData(activityreminder_data); - } -}); - -const activityreminder = require("activityreminder"); -const activityreminder_settings = activityreminder.loadSettings(); -if (activityreminder_settings.enabled) { - const activityreminder_data = activityreminder.loadData(); - if(activityreminder_data.firstLoad){ +(function () { + // load variable before defining functions cause it can trigger a ReferenceError + const activityreminder = require("activityreminder"); + const activityreminder_settings = activityreminder.loadSettings(); + let activityreminder_data = activityreminder.loadData(); + + if (activityreminder_data.firstLoad) { activityreminder_data.firstLoad = false; activityreminder.saveData(activityreminder_data); } - setInterval(run, 60000); - /* todo in a futur release - increase setInterval time to something that is still sensible (5 mins ?) - when we added a settimer - */ -} + function run() { + if (isNotWorn()) return; + let now = new Date(); + let h = now.getHours(); + + if (isDuringAlertHours(h)) { + let health = Bangle.getHealthStatus("day"); + if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed + || health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch + activityreminder_data.stepsOnDate = health.steps; + activityreminder_data.stepsDate = now; + activityreminder.saveData(activityreminder_data); + /* todo in a futur release + Add settimer to trigger like 30 secs after going in this part cause the person have been walking + (pass some argument to run() to handle long walks and not triggering so often) + */ + } + + if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) { + load('activityreminder.app.js'); + } + } + + } + + function isNotWorn() { + return (Bangle.isCharging() || activityreminder_settings.tempThreshold >= E.getTemperature()); + } + + function isDuringAlertHours(h) { + if (activityreminder_settings.startHour < activityreminder_settings.endHour) { // not passing through midnight + return (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour); + } else { // passing through midnight + return (h >= activityreminder_settings.startHour || h < activityreminder_settings.endHour); + } + } + + Bangle.on('midnight', function () { + /* + Usefull trick to have the app working smothly for people using it at night + */ + let now = new Date(); + let h = now.getHours(); + if (activityreminder_settings.enabled && isDuringAlertHours(h)) { + // updating only the steps and keeping the original stepsDate on purpose + activityreminder_data.stepsOnDate = 0; + activityreminder.saveData(activityreminder_data); + } + }); + + + if (activityreminder_settings.enabled) { + setInterval(run, 60000); + /* todo in a futur release + increase setInterval time to something that is still sensible (5 mins ?) + when we added a settimer + */ + } +})(); diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js index 08fffd5f4..704d35641 100644 --- a/apps/activityreminder/lib.js +++ b/apps/activityreminder/lib.js @@ -1,5 +1,3 @@ -const storage = require("Storage"); - exports.loadSettings = function () { return Object.assign({ enabled: true, @@ -10,20 +8,20 @@ exports.loadSettings = function () { pauseDelayMin: 120, minSteps: 50, tempThreshold: 27 - }, storage.readJSON("activityreminder.s.json", true) || {}); + }, require("Storage").readJSON("activityreminder.s.json", true) || {}); }; exports.writeSettings = function (settings) { - storage.writeJSON("activityreminder.s.json", settings); + require("Storage").writeJSON("activityreminder.s.json", settings); }; exports.saveData = function (data) { - storage.writeJSON("activityreminder.data.json", data); + require("Storage").writeJSON("activityreminder.data.json", data); }; exports.loadData = function () { let health = Bangle.getHealthStatus("day"); - const data = Object.assign({ + let data = Object.assign({ firstLoad: true, stepsDate: new Date(), stepsOnDate: health.steps, @@ -31,7 +29,7 @@ exports.loadData = function () { dismissDate: new Date(1970), pauseDate: new Date(1970), }, - storage.readJSON("activityreminder.data.json") || {}); + require("Storage").readJSON("activityreminder.data.json") || {}); if(typeof(data.stepsDate) == "string") data.stepsDate = new Date(data.stepsDate); diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json index 752c6c101..fb9423e7c 100644 --- a/apps/activityreminder/metadata.json +++ b/apps/activityreminder/metadata.json @@ -3,7 +3,7 @@ "name": "Activity Reminder", "shortName":"Activity Reminder", "description": "A reminder to take short walks for the ones with a sedentary lifestyle", - "version":"0.06", + "version":"0.07", "icon": "app.png", "type": "app", "tags": "tool,activity", diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js index f25697de0..ce7cdc913 100644 --- a/apps/activityreminder/settings.js +++ b/apps/activityreminder/settings.js @@ -1,85 +1,85 @@ (function (back) { - // Load settings - const activityreminder = require("activityreminder"); - const settings = activityreminder.loadSettings(); + // Load settings + const activityreminder = require("activityreminder"); + let settings = activityreminder.loadSettings(); - // Show the menu - E.showMenu({ - "": { "title": "Activity Reminder" }, - "< Back": () => back(), - 'Enable': { - value: settings.enabled, - format: v => v ? "Yes" : "No", - onchange: v => { - settings.enabled = v; - activityreminder.writeSettings(settings); - } - }, - 'Start hour': { - value: settings.startHour, - min: 0, max: 24, - onchange: v => { - settings.startHour = v; - activityreminder.writeSettings(settings); - } - }, - 'End hour': { - value: settings.endHour, - min: 0, max: 24, - onchange: v => { - settings.endHour = v; - activityreminder.writeSettings(settings); - } - }, - 'Max inactivity': { - value: settings.maxInnactivityMin, - min: 15, max: 120, - onchange: v => { - settings.maxInnactivityMin = v; - activityreminder.writeSettings(settings); - }, - format: x => { - return x + " min"; - } - }, - 'Dismiss delay': { - value: settings.dismissDelayMin, - min: 5, max: 60, - onchange: v => { - settings.dismissDelayMin = v; - activityreminder.writeSettings(settings); - }, - format: x => { - return x + " min"; - } - }, - 'Pause delay': { - value: settings.pauseDelayMin, - min: 30, max: 240, step: 5, - onchange: v => { - settings.pauseDelayMin = v; - activityreminder.writeSettings(settings); - }, - format: x => { - return x + " min"; - } - }, - 'Min steps': { - value: settings.minSteps, - min: 10, max: 500, step: 10, - onchange: v => { - settings.minSteps = v; - activityreminder.writeSettings(settings); - } - }, - 'Temp Threshold': { - value: settings.tempThreshold, - min: 20, max: 40, step: 0.5, - format: v => v + "°C", - onchange: v => { - settings.tempThreshold = v; - activityreminder.writeSettings(settings); - } - } - }); + // Show the menu + E.showMenu({ + "": { "title": "Activity Reminder" }, + "< Back": () => back(), + 'Enable': { + value: settings.enabled, + format: v => v ? "Yes" : "No", + onchange: v => { + settings.enabled = v; + activityreminder.writeSettings(settings); + } + }, + 'Start hour': { + value: settings.startHour, + min: 0, max: 24, + onchange: v => { + settings.startHour = v; + activityreminder.writeSettings(settings); + } + }, + 'End hour': { + value: settings.endHour, + min: 0, max: 24, + onchange: v => { + settings.endHour = v; + activityreminder.writeSettings(settings); + } + }, + 'Max inactivity': { + value: settings.maxInnactivityMin, + min: 15, max: 120, + onchange: v => { + settings.maxInnactivityMin = v; + activityreminder.writeSettings(settings); + }, + format: x => { + return x + " min"; + } + }, + 'Dismiss delay': { + value: settings.dismissDelayMin, + min: 5, max: 60, + onchange: v => { + settings.dismissDelayMin = v; + activityreminder.writeSettings(settings); + }, + format: x => { + return x + " min"; + } + }, + 'Pause delay': { + value: settings.pauseDelayMin, + min: 30, max: 240, step: 5, + onchange: v => { + settings.pauseDelayMin = v; + activityreminder.writeSettings(settings); + }, + format: x => { + return x + " min"; + } + }, + 'Min steps': { + value: settings.minSteps, + min: 10, max: 500, step: 10, + onchange: v => { + settings.minSteps = v; + activityreminder.writeSettings(settings); + } + }, + 'Temp Threshold': { + value: settings.tempThreshold, + min: 20, max: 40, step: 0.5, + format: v => v + "°C", + onchange: v => { + settings.tempThreshold = v; + activityreminder.writeSettings(settings); + } + } + }); }) diff --git a/apps/agenda/ChangeLog b/apps/agenda/ChangeLog new file mode 100644 index 000000000..56dfffa0d --- /dev/null +++ b/apps/agenda/ChangeLog @@ -0,0 +1 @@ +0.01: Basic agenda with events from GB diff --git a/apps/agenda/README.md b/apps/agenda/README.md new file mode 100644 index 000000000..a546e0a89 --- /dev/null +++ b/apps/agenda/README.md @@ -0,0 +1,3 @@ +# Agenda + +Basic agenda reading the events synchronised from GadgetBridge diff --git a/apps/agenda/agenda-icon.js b/apps/agenda/agenda-icon.js new file mode 100644 index 000000000..891543955 --- /dev/null +++ b/apps/agenda/agenda-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwg1yhGIxAPMBwIPFhH//GAC5n/C4oHBC5/IGwoXBHQQAKC4OIFAWOxHv9GO9wAKI4XoC4foEIIWLC4IABC4gIBFxnuE4IqBC4gARC4ZzNAAwXaxe7ACO4C625C4m4xIJBzAeCxGbCAOIFgQOBC4pOBxe4AYIPBAYQKCAYYXE3GL/ADBx/oxb3BC4X+xG4xwOBC4uP/YDB54MBf4Po3eM/4XBx/+C4pTBGIIkBLgOYAYIvB9GJBwI6BL45zCL4aCCL4h3GU64ALdYS1CI55bBAAgXFO4mMO4QDBDIO/////YxBU53IxIVB/GfDAWYa5wtC/GPAYWIL4wXBL4oSBC4jcBC4m4QIWYSwWIIQIAG/CnMMAIAC/JLCMIIvMIwZHFJAJfLC5yPHAYIRDAoy/KCIi7BMon4d4+Od4IXBxAZBEQLtB/+YxIXDL4SLCL4WPzAXCNgRFBLIKnKLIrcEI4gXNAAp3CxGZAAzCBC5KnCKAIAICxBlBC4IAJxG/C4/4wAXLhBgD/IcD3AXMGAIqDDgRGNGAoXDFxxhEI4W4FxwwCaoYWBFx4YDAAQWRAEQ")) diff --git a/apps/agenda/agenda.js b/apps/agenda/agenda.js new file mode 100644 index 000000000..f39e31c75 --- /dev/null +++ b/apps/agenda/agenda.js @@ -0,0 +1,127 @@ +/* CALENDAR is a list of: + {id:int, + type, + timestamp, + durationInSeconds, + title, + description, + location, + allDay: bool, + } +*/ + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var FILE = "android.calendar.json"; + +var Locale = require("locale"); + +var fontSmall = "6x8"; +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; +var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + +//FIXME maybe write the end from GB already? Not durationInSeconds here (or do while receiving?) +var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; + +CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp) + +function getDate(timestamp) { + return new Date(timestamp*1000); +} +function formatDateLong(date, includeDay) { + if(includeDay) + return Locale.date(date)+" "+Locale.time(date,1); + return Locale.time(date,1); +} +function formatDateShort(date) { + return Locale.date(date).replace(/\d\d\d\d/,"")+Locale.time(date,1); +} + +var lines = []; +function showEvent(ev) { + var bodyFont = fontBig; + if(!ev) return; + g.setFont(bodyFont); + //var lines = []; + if (ev.title) lines = g.wrapString(ev.title, g.getWidth()-10) + var titleCnt = lines.length; + var start = getDate(ev.timestamp); + var end = getDate((+ev.timestamp) + (+ev.durationInSeconds)); + var includeDay = true; + if (titleCnt) lines.push(""); // add blank line after title + if(start.getDay() == end.getDay() && start.getMonth() == end.getMonth()) + includeDay = false; + if(includeDay) { + lines = lines.concat( + /*LANG*/"Start:", + g.wrapString(formatDateLong(start, includeDay), g.getWidth()-10), + /*LANG*/"End:", + g.wrapString(formatDateLong(end, includeDay), g.getWidth()-10)); + } else { + lines = lines.concat( + g.wrapString(Locale.date(start), g.getWidth()-10), + g.wrapString(/*LANG*/"Start"+": "+formatDateLong(start, includeDay), g.getWidth()-10), + g.wrapString(/*LANG*/"End"+": "+formatDateLong(end, includeDay), g.getWidth()-10)); + } + if(ev.location) + lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10)); + if(ev.description) + lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10)); + lines = lines.concat(["",/*LANG*/"< Back"]); + E.showScroller({ + h : g.getFontHeight(), // height of each menu item in pixels + c : lines.length, // number of menu items + // a function to draw a menu item + draw : function(idx, r) { + // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 + g.setBgColor(idx=lines.length-2) + showList(); + }, + back : () => showList() + }); +} + +function showList() { + if(CALENDAR.length == 0) { + E.showMessage("No events"); + return; + } + E.showScroller({ + h : 52, + c : Math.max(CALENDAR.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) + draw : function(idx, r) {"ram" + var ev = CALENDAR[idx]; + g.setColor(g.theme.fg); + g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + if (!ev) return; + var isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000; + var x = r.x+2, title = ev.title; + var body = formatDateShort(getDate(ev.timestamp))+"\n"+ev.location; + var m = ev.title+"\n"+ev.location, longBody=false; + if (title) g.setFontAlign(-1,-1).setFont(fontBig) + .setColor(isPast ? "#888" : g.theme.fg).drawString(title, x,r.y+2); + if (body) { + g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg); + var l = g.wrapString(body, r.w-(x+14)); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + longBody = l.length>2; + g.drawString(l.join("\n"), x+10,r.y+20); + } + //if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2); + g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items + }, + select : idx => showEvent(CALENDAR[idx]), + back : () => load() + }); +} +showList(); diff --git a/apps/agenda/agenda.png b/apps/agenda/agenda.png new file mode 100644 index 000000000..c850b0e5d Binary files /dev/null and b/apps/agenda/agenda.png differ diff --git a/apps/agenda/metadata.json b/apps/agenda/metadata.json new file mode 100644 index 000000000..ce8438686 --- /dev/null +++ b/apps/agenda/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "agenda", + "name": "Agenda", + "version": "0.02", + "description": "Simple agenda", + "icon": "agenda.png", + "screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}], + "tags": "agenda", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"agenda.app.js","url":"agenda.js"}, + {"name":"agenda.settings.js","url":"settings.js"}, + {"name":"agenda.img","url":"agenda-icon.js","evaluate":true} + ] +} diff --git a/apps/agenda/screenshot_agenda_event1.png b/apps/agenda/screenshot_agenda_event1.png new file mode 100644 index 000000000..581da286b Binary files /dev/null and b/apps/agenda/screenshot_agenda_event1.png differ diff --git a/apps/agenda/screenshot_agenda_event2.png b/apps/agenda/screenshot_agenda_event2.png new file mode 100644 index 000000000..f5edcaae8 Binary files /dev/null and b/apps/agenda/screenshot_agenda_event2.png differ diff --git a/apps/agenda/screenshot_agenda_overview.png b/apps/agenda/screenshot_agenda_overview.png new file mode 100644 index 000000000..a2030d05f Binary files /dev/null and b/apps/agenda/screenshot_agenda_overview.png differ diff --git a/apps/agenda/settings.js b/apps/agenda/settings.js new file mode 100644 index 000000000..fe9dab2d8 --- /dev/null +++ b/apps/agenda/settings.js @@ -0,0 +1,37 @@ +(function(back) { + function gbSend(message) { + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(message)); + } + var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; + var mainmenu = { + "" : { "title" : "Agenda" }, + "< Back" : back, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" }, + /*LANG*/"Force calendar sync" : () => { + if(NRF.getSecurityStatus().connected) { + E.showPrompt(/*LANG*/"Do you want to also clear the internal database first?", { + buttons: {/*LANG*/"Yes": 1, /*LANG*/"No": 2, /*LANG*/"Cancel": 3} + }).then((v)=>{ + switch(v) { + case 1: + require("Storage").writeJSON("android.calendar.json",[]); + CALENDAR = []; + /* falls through */ + case 2: + gbSend({t:"force_calendar_sync", ids: CALENDAR.map(e=>e.id)}); + E.showAlert(/*LANG*/"Request sent to the phone").then(()=>E.showMenu(mainmenu)); + break; + case 3: + default: + E.showMenu(mainmenu); + return; + } + }); + } else { + E.showAlert(/*LANG*/"You are not connected").then(()=>E.showMenu(mainmenu)); + } + }, + }; + E.showMenu(mainmenu); +}) diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 00187fa7c..1be0dcdd5 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -30,3 +30,4 @@ 0.28: Fix bug with alarms not firing when configured to fire only once 0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday 0.30: Fix "Enable All" +0.31: Add seconds to timers diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 2aff43de2..0a7bb8f24 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -268,11 +268,20 @@ function showEditTimerMenu(selectedTimer, timerIndex) { wrap: true, onchange: v => time.m = v }, + /*LANG*/"Seconds": { + value: time.s, + min: 0, + max: 59, + step: 1, + wrap: true, + onchange: v => time.s = v + }, /*LANG*/"Enabled": { value: timer.on, onchange: v => timer.on = v }, /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v), + /*LANG*/"Cancel": () => showMainMenu() }; if (!isNew) { diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index 3c17ee177..54472a12c 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,7 +2,7 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.30", + "version": "0.31", "description": "Set alarms and timers on your Bangle", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/android/boot.js b/apps/android/boot.js index efd7e7e46..9cdc019a6 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -90,6 +90,35 @@ sched.setAlarms(alarms); 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); + if (!cal || !Array.isArray(cal)) cal = []; + var i = cal.findIndex(e=>e.id==event.id); + if(i<0) + cal.push(event); + else + cal[i] = event; + require("Storage").writeJSON("android.calendar.json", cal); + }, + "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; + cal = cal.filter(e=>e.id!=event.id); + require("Storage").writeJSON("android.calendar.json", cal); + }, + //triggered by GB, send all ids + "force_calendar_sync_start" : function() { + var cal = require("Storage").readJSON("android.calendar.json",true); + if (!cal || !Array.isArray(cal)) cal = []; + gbSend({t:"force_calendar_sync", ids: cal.map(e=>e.id)}); + } }; var h = HANDLERS[event.t]; if (h) h(); else console.log("GB Unknown",event); diff --git a/apps/android/metadata.json b/apps/android/metadata.json index bf37b8407..27b77cf2f 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.10", + "version": "0.11", "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", @@ -15,6 +15,6 @@ {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], - "data": [{"name":"android.settings.json"}], + "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}], "sortorder": -8 } diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index 197a24738..ba44ecef8 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -11,3 +11,4 @@ 0.11: Use ClockFace.is12Hour 0.12: Add settings to hide date,widgets 0.13: Add font setting +0.14: Use ClockFace_menu.addItems diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json index 0a0b73778..0c227dc52 100644 --- a/apps/barclock/metadata.json +++ b/apps/barclock/metadata.json @@ -1,7 +1,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.13", + "version": "0.14", "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/barclock/settings.js b/apps/barclock/settings.js index 3e97688a1..dfe25581c 100644 --- a/apps/barclock/settings.js +++ b/apps/barclock/settings.js @@ -1,26 +1,26 @@ (function(back) { - let s = require('Storage').readJSON("barclock.settings.json", true) || {}; + let s = require("Storage").readJSON("barclock.settings.json", true) || {}; - function saver(key) { - return value => { - s[key] = value; - require('Storage').writeJSON("barclock.settings.json", s); - } + function save(key, value) { + s[key] = value; + require("Storage").writeJSON("barclock.settings.json", s); } const fonts = [/*LANG*/"Bitmap",/*LANG*/"Vector"]; - const menu = { + let menu = { "": {"title": /*LANG*/"Bar Clock"}, /*LANG*/"< Back": back, - /*LANG*/"Show date": require("ClockFace_menu").showDate(s.showDate, saver('showDate')), - /*LANG*/"Load widgets": require("ClockFace_menu").loadWidgets(s.loadWidgets, saver('loadWidgets')), /*LANG*/"Font": { value: s.font|0, - min:0,max:1,wrap:true, - format:v=>fonts[v], - onchange:saver('font'), + min: 0, max: 1, wrap: true, + format: v => fonts[v], + onchange: v => save("font", v), }, }; + require("ClockFace_menu").addItems(menu, save, { + showDate: s.showDate, + loadWidgets: s.loadWidgets, + }); E.showMenu(menu); }); diff --git a/apps/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog index 2b6fcc7cb..09cc978fb 100644 --- a/apps/bigdclock/ChangeLog +++ b/apps/bigdclock/ChangeLog @@ -1,3 +1,5 @@ 0.01: Initial version 0.02: setTimeout bug fix; no leading zero on date; lightmode; 12 hour format; cleanup 0.03: Internationalisation; bug fix - battery icon responds promptly to charging state +0.04: bug fix +0.05: proper fix for the race condition in queueDraw() diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js index 7b26d4f17..c013c6188 100644 --- a/apps/bigdclock/bigdclock.app.js +++ b/apps/bigdclock/bigdclock.app.js @@ -12,12 +12,12 @@ Graphics.prototype.setFontOpenSans = function(scale) { var drawTimeout; -// schedule a draw for the next minute -function queueDraw() { +function queueDraw(millis_now) { if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = setTimeout(function () { + drawTimeout = undefined; draw(); - }, 60300 - (Date.now() % 60000)); // We aim for 300ms into the next minute to ensure we make it! + }, 60000 - (millis_now % 60000)); } function draw() { @@ -69,7 +69,7 @@ function draw() { // widget redraw Bangle.drawWidgets(); - queueDraw(); + queueDraw(date.getTime()); } Bangle.on('lcdPower', on => { diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json index e80dd9a04..7359bcf20 100644 --- a/apps/bigdclock/metadata.json +++ b/apps/bigdclock/metadata.json @@ -1,7 +1,7 @@ { "id": "bigdclock", "name": "Big digit clock containing just the essentials", "shortName":"Big digit clk", - "version":"0.03", + "version":"0.05", "description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.", "icon": "bigdclock.png", "type": "clock", diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog index f00b3fa0a..419810021 100644 --- a/apps/cassioWatch/ChangeLog +++ b/apps/cassioWatch/ChangeLog @@ -7,4 +7,5 @@ 0.6: Add Settings Page 0.7: Update Rocket Sequences Scope to not use memory all time 0.8: Update Some Variable Scopes to not use memory until need -0.9: Remove ESLint spaces \ No newline at end of file +0.9: Remove ESLint spaces +0.10: Show daily steps, heartrate and the temperature if weather information is available. \ No newline at end of file diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md index 1342af8e6..aaeb3f122 100644 --- a/apps/cassioWatch/README.md +++ b/apps/cassioWatch/README.md @@ -6,5 +6,6 @@ Clock with Space Cassio Watch Style. It displays current temperature,day,steps,battery.heartbeat and weather. + **To-do**: -Integrate heartbeat and Weather, 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 93538ec50..6bbb9e823 100644 --- a/apps/cassioWatch/app.js +++ b/apps/cassioWatch/app.js @@ -1,11 +1,9 @@ +const storage = require('Storage'); + require("Font6x12").add(Graphics); require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg").add(Graphics); -let ClockInterval; -let RocketInterval; -let BatteryInterval; - function bigThenSmall(big, small, x, y) { g.setFont("7x11Numeric7Seg", 2); g.drawString(big, x, y); @@ -14,16 +12,6 @@ function bigThenSmall(big, small, x, y) { g.drawString(small, x, y); } -function ClearIntervals(inoreclock) { - if (RocketInterval) clearInterval(RocketInterval); - if (BatteryInterval) clearInterval(BatteryInterval); - RocketInterval = undefined; - BatteryInterval = undefined; - if (inoreclock) return; - if (ClockInterval) clearInterval(ClockInterval); - ClockInterval = undefined; -} - function getBackgroundImage() { 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==")); } @@ -41,15 +29,31 @@ function getRocketSequences() { }; } -let rocket_sequence = 1; - -let settings = require('Storage').readJSON("cassioWatch.settings.json", true) || {}; +let rocketSequence = 1; +let settings = storage.readJSON("cassioWatch.settings.json", true) || {}; let rocketSpeed = settings.rocketSpeed || 700; delete settings; -g.clear(); +// 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)); +} -function DrawClock() { + +function clearIntervals() { + if (rocketInterval) clearInterval(rocketInterval); + rocketInterval = undefined; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; +} + +function drawClock() { g.setFont("7x11Numeric7Seg", 3); g.clearRect(80, 57, 170, 96); g.setColor(0, 255, 255); @@ -66,23 +70,57 @@ function DrawClock() { g.drawString(time < 10 ? "0" + time : time, 78, 137); } -function DrawBattery() { +function drawBattery() { bigThenSmall(E.getBattery(), "%", 135, 21); } -function DrawRocket() { +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[rocket_sequence], 5, 65, { scale: 0.7 }); + g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 }); g.setColor(0, 0, 0); - rocket_sequence = rocket_sequence + 1; - if (rocket_sequence > 8) rocket_sequence = 1; + rocketSequence = rocketSequence + 1; + if(rocketSequence > 8) rocketSequence = 1; } -function DrawScene() { +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"; +} + + +function draw() { + queueDraw(); + g.reset(); g.clear(); g.setColor(0, 255, 255); @@ -94,40 +132,44 @@ function DrawScene() { g.drawString("Launching Process", 30, 20); g.setFont("8x12"); g.drawString("ACTIVATE", 40, 35); + + g.setFontAlign(0,-1); g.setFont("8x12", 2); - g.drawString("30", 142, 132); - g.drawString("55", 95, 98); - g.setFont("8x12", 1); - g.drawString(Bangle.getStepCount(), 143, 104); - ClockInterval = setInterval(DrawClock, 30000); - DrawClock(); - RocketInterval = setInterval(DrawRocket, rocketSpeed); - DrawRocket(); - BatteryInterval = setInterval(DrawBattery, 5 * 60000); - DrawBattery(); + g.drawString(getTemperature(), 155, 132); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98); + g.drawString(getSteps(), 158, 98); + + g.setFontAlign(-1,-1); + drawClock(); + drawRocket(); + drawBattery(); + + // Hide widgets + for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} } Bangle.on("lcdPower", (on) => { - if (!on) { - g.clear(); - ClearIntervals(true); + if (on) { + draw(); + } else { + clearIntervals(); } }); + Bangle.on("lock", (locked) => { - if (locked) { - ClearIntervals(true); - } else { - ClearIntervals(); - DrawScene(); + clearIntervals(); + draw(); + if (!locked) { + rocketInterval = setInterval(drawRocket, rocketSpeed); } }); + +// Load widgets, but don't show them +Bangle.loadWidgets(); +Bangle.setUI("clock"); + g.reset(); g.clear(); -Bangle.setUI("clock"); -DrawScene(); - -if (Bangle.isLocked()) { - ClearIntervals(true); -} \ No newline at end of file +draw(); \ No newline at end of file diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index 70cd9c242..dabdc2c93 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.9", + "version": "0.10", "type": "clock", "tags": "clock, weather, cassio, retro", "supports": ["BANGLEJS2"], diff --git a/apps/cogclock/ChangeLog b/apps/cogclock/ChangeLog index 3158b6116..f4bfe77a5 100644 --- a/apps/cogclock/ChangeLog +++ b/apps/cogclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: New clock 0.02: Use ClockFace library, add settings +0.03: Use ClockFace_menu.addSettingsFile diff --git a/apps/cogclock/metadata.json b/apps/cogclock/metadata.json index 40733bcd1..29000b589 100644 --- a/apps/cogclock/metadata.json +++ b/apps/cogclock/metadata.json @@ -1,7 +1,7 @@ { "id": "cogclock", "name": "Cog Clock", - "version": "0.02", + "version": "0.03", "description": "A cross-shaped clock inside a cog", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/cogclock/settings.js b/apps/cogclock/settings.js index 4eadc32c2..a91b033d0 100644 --- a/apps/cogclock/settings.js +++ b/apps/cogclock/settings.js @@ -1,19 +1,10 @@ (function(back) { - let s = require('Storage').readJSON("cogclock.settings.json", true) || {}; - - function saver(key) { - return value => { - s[key] = value; - require('Storage').writeJSON("cogclock.settings.json", s); - } - } - - const menu = { + let menu = { "": {"title": /*LANG*/"Cog Clock"}, /*LANG*/"< Back": back, - /*LANG*/"Show date": require("ClockFace_menu").showDate(s.showDate, saver('showDate')), - /*LANG*/"Load widgets": require("ClockFace_menu").loadWidgets(s.loadWidgets, saver('loadWidgets')), }; - + require("ClockFace_menu").addSettingsFile(menu, "cogclock.settings.json", [ + "showDate", "loadWidgets" + ]); E.showMenu(menu); }); diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 09804b82e..a360e4875 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -11,4 +11,5 @@ 0.11: Fix bangle.js 1 white icons not displaying 0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock. 0.13: Added swipeExit setting so that left-right to exit is an option -0.14: Don't move pages when doing exit swipe. +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. diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md index 55c9f53b8..1835bc842 100644 --- a/apps/dtlaunch/README.md +++ b/apps/dtlaunch/README.md @@ -27,7 +27,7 @@ Bangle 2: ## Controls- Bangle 2 -**Touch** - icon to select, scond touch launches app +**Touch** - icon to select, second touch launches app **Swipe Left/Up** - move to next page of app icons diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index 46194ec5d..8cd5790bb 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -89,7 +89,7 @@ function drawPage(p){ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ selected = 0; oldselected=-1; - if(settings.swipeExit && dirLeftRight==1) showClock(); + if(settings.swipeExit && dirLeftRight==1) load(); if (dirUpDown==-1||dirLeftRight==-1){ ++page; if (page>maxPage) page=0; drawPage(page); @@ -99,12 +99,6 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{ } }); -function showClock(){ - var app = require("Storage").readJSON('setting.json', 1).clock; - if (app) load(app); - else E.showMessage("clock\nnot found"); -} - function isTouched(p,n){ if (n<0 || n>3) return false; var x1 = (n%2)*72+XOFF; var y1 = n>1?72+YOFF:YOFF; diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index 4a0b8067c..9711a6964 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.14", + "version": "0.15", "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/espruinoctrl/metadata.json b/apps/espruinoctrl/metadata.json index 5798c7842..253307fa0 100644 --- a/apps/espruinoctrl/metadata.json +++ b/apps/espruinoctrl/metadata.json @@ -6,7 +6,7 @@ "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", "icon": "app.png", "tags": "", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "storage": [ diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog index cb520193b..6d2f50119 100644 --- a/apps/ffcniftya/ChangeLog +++ b/apps/ffcniftya/ChangeLog @@ -2,3 +2,5 @@ 0.02: Shows the current week number (ISO8601), can be disabled via settings 0.03: Call setUI before loading widgets Improve settings page +0.04: Use ClockFace library + diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 4000a1578..2c1a54f6e 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -1,22 +1,3 @@ -const locale = require("locale"); -const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"]; -const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"]; - -/* Clock *********************************************/ -const scale = g.getWidth() / 176; - -const widget = 24; - -const viewport = { - width: g.getWidth(), - height: g.getHeight(), -} - -const center = { - x: viewport.width / 2, - y: Math.round(((viewport.height - widget) / 2) + widget), -} - // copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 function ISO8601_week_no(date) { var tdt = new Date(date.valueOf()); @@ -30,77 +11,49 @@ function ISO8601_week_no(date) { return 1 + Math.ceil((firstThursday - tdt) / 604800000); } -function d02(value) { - return ('0' + value).substr(-2); +function format(value) { + return ("0" + value).substr(-2); } -function draw() { - g.reset(); - g.clearRect(0, widget, viewport.width, viewport.height); - const now = new Date(); +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; - const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); - const minutes = d02(now.getMinutes()); - const day = d02(now.getDate()); - const month = d02(now.getMonth() + 1); - const year = now.getFullYear(now); - const weekNum = d02(ISO8601_week_no(now)); - const monthName = locale.month(now, 3); - const dayName = locale.dow(now, 3); + this.viewport = { + width: appRect.w, + height: appRect.h + }; - const centerTimeScaleX = center.x + 32 * scale; - g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, centerTimeScaleX, center.y - 31 * scale); - g.drawString(minutes, centerTimeScaleX, center.y + 46 * scale); + this.center = { + x: this.viewport.width / 2, + y: Math.round((this.viewport.height / 2) + appRect.y) + }; - g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + this.scale = g.getWidth() / this.viewport.width; + this.centerTimeScaleX = this.center.x + 32 * this.scale; + this.centerDatesScaleX = this.center.x + 40 * this.scale; + }, + draw: function (date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; + const monthName = require("date_utils").month(month, 1); + const dayName = require("date_utils").dow(date.getDay(), 1); - const centerDatesScaleX = center.x + 40 * scale; - g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); - g.drawString(year, centerDatesScaleX, center.y - 62 * scale); - g.drawString(month, centerDatesScaleX, center.y - 44 * scale); - g.drawString(day, centerDatesScaleX, center.y - 26 * scale); - if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale); - g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); - g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); -} + g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale); + g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale); + g.drawString(format(date.getMinutes()), this.centerTimeScaleX, this.center.y + 46 * this.scale); + g.fillRect(this.center.x + 30 * this.scale, this.center.y - 72 * this.scale, this.center.x + 32 * this.scale, this.center.y + 74 * this.scale); -/* Minute Ticker *************************************/ - -let tickTimer; - -function clearTickTimer() { - if (tickTimer) { - clearTimeout(tickTimer); - tickTimer = undefined; - } -} - -function queueNextTick() { - clearTickTimer(); - tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); -} - -function tick() { - draw(); - queueNextTick(); -} - -/* Init **********************************************/ - -// Clear the screen once, at startup -g.clear(); -tick(); - -Bangle.on('lcdPower', (on) => { - if (on) { - tick(); - } else { - clearTickTimer(); - } + g.setFontAlign(-1, 0).setFont("Vector", 16 * this.scale); + g.drawString(date.getFullYear(date), this.centerDatesScaleX, this.center.y - 62 * this.scale); + g.drawString(format(month), this.centerDatesScaleX, this.center.y - 44 * this.scale); + g.drawString(format(date.getDate()), this.centerDatesScaleX, this.center.y - 26 * this.scale); + if (this.showWeekNum) g.drawString(format(ISO8601_week_no(date)), this.centerDatesScaleX, this.center.y + 15 * this.scale); + g.drawString(monthName, this.centerDatesScaleX, this.center.y + 48 * this.scale); + g.drawString(dayName, this.centerDatesScaleX, this.center.y + 66 * this.scale); + }, + settingsFile: "ffcniftya.json" }); - -Bangle.setUI("clock"); -Bangle.loadWidgets(); -Bangle.drawWidgets(); +clock.start(); \ No newline at end of file diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json index 91b426cd0..015c56119 100644 --- a/apps/ffcniftya/metadata.json +++ b/apps/ffcniftya/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftya", "name": "Nifty-A Clock", - "version": "0.03", + "version": "0.04", "description": "A nifty clock with time and date", "icon": "app.png", "screenshots": [{"url":"screenshot_nifty.png"}], diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog index 9fc7e3c5c..83b11eb78 100644 --- a/apps/ffcniftyb/ChangeLog +++ b/apps/ffcniftyb/ChangeLog @@ -3,3 +3,4 @@ 0.03: Call setUI before loading widgets Fix bug with black being unselectable Improve settings page +0.04: Use ClockFace library diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 65c74dbd7..540924fa5 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -1,20 +1,10 @@ -const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"]; -const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED +var scale; +var screen; +var center; +var buf; +var img; -/* Clock *********************************************/ -const scale = g.getWidth() / 176; - -const screen = { - width: g.getWidth(), - height: g.getHeight() - 24, -}; - -const center = { - x: screen.width / 2, - y: screen.height / 2, -}; - -function d02(value) { +function format(value) { return ("0" + value).substr(-2); } @@ -22,91 +12,69 @@ function renderEllipse(g) { g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); } -function renderText(g) { - const now = new Date(); +function renderText(g, date) { + const hour = date.getHours() - (this.is12Hour && date.getHours() > 12 ? 12 : 0); + const month = date.getMonth() + 1; - const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); - const minutes = d02(now.getMinutes()); - const day = d02(now.getDate()); - const month = d02(now.getMonth() + 1); - const year = now.getFullYear(); - - const month2 = require("locale").month(now, 3); - const day2 = require("locale").dow(now, 3); + const monthName = require("date_utils").month(month, 1); + const dayName = require("date_utils").dow(date.getDay(), 1); g.setFontAlign(1, 0).setFont("Vector", 90 * scale); - g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); - g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.drawString(format(hour), center.x + 32 * scale, center.y - 31 * scale); + g.drawString(format(date.getMinutes()), center.x + 32 * scale, center.y + 46 * scale); g.setFontAlign(1, 0).setFont("Vector", 16 * scale); - g.drawString(year, center.x + 80 * scale, center.y - 42 * scale); - g.drawString(month, center.x + 80 * scale, center.y - 26 * scale); - g.drawString(day, center.x + 80 * scale, center.y - 10 * scale); - g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale); - g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); + g.drawString(date.getFullYear(), center.x + 80 * scale, center.y - 42 * scale); + g.drawString(format(month), center.x + 80 * scale, center.y - 26 * scale); + g.drawString(format(date.getDate()), center.x + 80 * scale, center.y - 10 * scale); + g.drawString(monthName, center.x + 80 * scale, center.y + 44 * scale); + g.drawString(dayName, center.x + 80 * scale, center.y + 60 * scale); } -const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { - msb: true +const ClockFace = require("ClockFace"); +const clock = new ClockFace({ + init: function () { + const appRect = Bangle.appRect; + + screen = { + width: appRect.w, + height: appRect.h + }; + + center = { + x: screen.width / 2, + y: screen.height / 2 + }; + + buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { msb: true }); + + scale = g.getWidth() / screen.width; + + img = { + width: screen.width, + height: screen.height, + transparent: 0, + bpp: 1, + buffer: buf.buffer + }; + + // default to RED (see settings.js) + // don't use || to default because 0 is a valid color + this.color = this.color === undefined ? 63488 : this.color; + }, + draw: function (date) { + // render outside text with ellipse + buf.clear(); + renderText(buf.setColor(1), date); + renderEllipse(buf.setColor(0)); + g.setColor(this.color).drawImage(img, 0, 24); + + // render ellipse with inside text + buf.clear(); + renderEllipse(buf.setColor(1)); + renderText(buf.setColor(0), date); + g.setColor(this.color).drawImage(img, 0, 24); + }, + settingsFile: "ffcniftyb.json" }); - -function draw() { - - const img = { - width: screen.width, - height: screen.height, - transparent: 0, - bpp: 1, - buffer: buf.buffer - }; - - // cleat screen area - g.clearRect(0, 24, g.getWidth(), g.getHeight()); - - // render outside text with ellipse - buf.clear(); - renderText(buf.setColor(1)); - renderEllipse(buf.setColor(0)); - g.setColor(color).drawImage(img, 0, 24); - - // render ellipse with inside text - buf.clear(); - renderEllipse(buf.setColor(1)); - renderText(buf.setColor(0)); - g.setColor(color).drawImage(img, 0, 24); -} - - -/* Minute Ticker *************************************/ - -let ticker; - -function stopTick() { - if (ticker) { - clearTimeout(ticker); - ticker = undefined; - } -} - -function startTick(run) { - stopTick(); - run(); - ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); -} - -/* Init **********************************************/ - -g.clear(); -startTick(draw); - -Bangle.on("lcdPower", (on) => { - if (on) { - startTick(draw); - } else { - stopTick(); - } -}); - -Bangle.setUI("clock"); -Bangle.loadWidgets(); -Bangle.drawWidgets(); +clock.start(); \ No newline at end of file diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json index 3d26c27ea..019ae6eb3 100644 --- a/apps/ffcniftyb/metadata.json +++ b/apps/ffcniftyb/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftyb", "name": "Nifty-B Clock", - "version": "0.03", + "version": "0.04", "description": "A nifty clock (series B) with time, date and colour configuration", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/football/ChangeLog b/apps/football/ChangeLog index 9b91672a5..66b9882cc 100644 --- a/apps/football/ChangeLog +++ b/apps/football/ChangeLog @@ -1 +1,2 @@ 1.00: Initial implementation +1.01: Bug fixes and performance and visual improvements diff --git a/apps/football/app.js b/apps/football/app.js index 8350bea88..d12f07e2b 100644 --- a/apps/football/app.js +++ b/apps/football/app.js @@ -1,4 +1,19 @@ +// globals. TODO: move into an object const digit = []; +let part = 0; +let endInc = 0; +var endGame = false; +let goalFrame = 0; +var stopped = true; +let score0 = 0; +let score1 = 0; +let inc = 0; +let msinc = 0; +let seq0 = 0; +let seq1 = 0; +let goaler = -1; +const w = g.getWidth(); +let owner = -1; const dash = { width: 75, @@ -6,6 +21,7 @@ const dash = { bpp: 1, buffer: require('heatshrink').decompress(atob('AH4A/AH4A/AH4A/AH4A/AB0D/4AB+AJEBAX/BAk/CQ8PCQ4kDCQoIDCQgkDCQgkECQgIE4ASHFxH8JRgSEEgYSEPJAkEAH4A/AH4A/AH4A/AH4A/ACg=')) }; + function loadDigits () { digit[0] = { width: 80, @@ -58,8 +74,8 @@ function loadDigits () { digit[7] = { width: 80, height: 128, - bpp: 1, - buffer: require('heatshrink').decompress(atob('AGUH/4AE/wJBgYJF/gJBgIJF+AeCBJN/BIngsAJBn4JE4HgBIMfBImBBIUPBIkDBIRQE/0HBIRQE/kPBIRQE/EfBIRQE+E/BIZQD8AJEKAfABYIJCKAYsBBIYADIAIJHKgIJHNAIJ/BP4J/BP4Jzg//4AJGgf/wAJGgP/BAwAB/wJIvgJInAJIiAJIAH5PPMZJ3JRZCfJWZLHJfM4J/BP4J/BP4JNg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIeg4JIgYJIgIJIgAJJsAJIkAJIAH4AQA=')) + bpp: 4, + buffer : require("heatshrink").decompress(atob("AH4A/AEtVADdQE5Nf/4AayAnJgoma/J4LKDR2KKDZODUMadChKhiJwefE5RQXJwbxLKCxOEE5hQVJwgnNKCZOFE5pQTJwonOKCJOGE5xQRD4Q8EE5xQPJw4nPgFZzIAMqCdFE6IARJwgnhJwonhJwonhe5In/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4nlr4mE/NQE78FE4n1Ez5QGE0JQEJ0RQETsBQFJ0gABrJOkAH4A/AH4A/ADNZqAmkgv/yAnkr///JQjJwIABypOkAAP5J0oABUMJODKAShgEwhQiE/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/E/4n/AA+fE80JE8xQGE8JQFE8JQFE8RQEE8RQEE8ZQDE8ZQDE8hQCE8hQCE8pQBE8pQBE80JE80AE84A/AH4A/AH4A/AAQA==")) }; digit[8] = { @@ -170,9 +186,6 @@ const gol11 = { loadDigits(); -let goalFrame = 0; -let score0 = 0; -let score1 = 0; function printNumber (n, x, y, options) { if (n > 9 || n < -1) { @@ -197,13 +210,7 @@ function printNumber (n, x, y, options) { g.drawImage(img, x, y, options); } } -let inc = 0; -let msinc = 0; -let seq0 = 0; -let seq1 = 0; -let goaler = -1; -const w = g.getWidth(); -let owner = -1; + g.setBgColor(0, 0, 0); g.clear(); g.setColor(1, 1, 1); @@ -247,43 +254,63 @@ function onStop () { refresh(); refresh_ms(); } -var stopped = true; -Bangle.on('tap', function (pos) { - console.log('touch', pos); + +function onButtonPress() { + console.log('on.tap'); + setWatch(() => { + onButtonPress(); +}, BTN1); + Bangle.beep(); if (endGame) { - Bangle.beep(); score0 = 0; score1 = 0; seq0 = 0; seq1 = 0; + part = 0; inc = 0; msinc = 0; stopped = true; endGame = false; } else { if (inc == 0) { - autogame(); + // autogame(); + stopped = false; } else { onStop(); } } +} + +setWatch(() => { + onButtonPress(); +}, BTN1); +/*Bangle.on('tap', function () { + onButtonPress(); }); +*/ g.setFont12x20(3); -let part = 0; -let endInc = 0; -var endGame = false; + function refresh () { g.clear(); if (inc > 59) { inc = 0; part++; } + if (part >= 2 && inc > 30) { + part = 2; + Bangle.buzz(); + endGame = true; + endInc = inc; + inc = 0; + } if (inc > 44) { + inc = 0; if (part < 2) { part++; } if (part >= 2) { if (score0 != score1) { + Bangle.buzz(); endGame = true; endInc = inc; inc = 0; @@ -342,6 +369,12 @@ function refresh_pixels () { let bx = (owner == 0) ? w / 3 : w / 2; bx += 2; g.drawImage(frame4 ? ball0 : ball1, bx, 10, { scale: 5 }); + const liney = 60; + if (owner) { + g.drawLine(w-8, liney, 2*(w/3), liney); + } else { + g.drawLine(8, liney, w/3, liney); + } } let dots = 0; function refresh_dots () { @@ -437,4 +470,5 @@ function autogame () { } Bangle.setOptions({ lockTimeout: 0, backlightTimeout: 0 }); -autogame(); +// autogame(); + diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 0321e46bc..3f8f50b3f 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -82,6 +82,7 @@ function onInit(device) { if (crc==46757280) version = "2v11.58"; if (crc==3508163280 || crc==1418074094) version = "2v12"; if (crc==4056371285) version = "2v13"; + if (crc==1038322422) version = "2v14"; if (!ok) { version += `(⚠ update required)`; } diff --git a/apps/health/interface.html b/apps/health/interface.html index 0791acd24..a708e2645 100644 --- a/apps/health/interface.html +++ b/apps/health/interface.html @@ -113,7 +113,7 @@ function getMonthList() { Util.showModal("Deleting..."); Util.eraseStorage(filename,()=>{ Util.hideModal(); - getTrackList(); + getMonthList(); }); } if (task=="downloadcsv") { diff --git a/apps/hwid_a_battery_widget/ChangeLog b/apps/hwid_a_battery_widget/ChangeLog new file mode 100644 index 000000000..9d5ee1264 --- /dev/null +++ b/apps/hwid_a_battery_widget/ChangeLog @@ -0,0 +1,7 @@ +0.01: Release for Bangle 2 (2021/11/18) +0.02: Internal id update to wid_* as per Gordon's request (2021/11/21) +0.03: Support dark themes +0.04: Increase screen update rate when charging +0.05: Deleting Background - making Font larger +0.06: Fixing refresh issues +0.07 \ No newline at end of file diff --git a/apps/hwid_a_battery_widget/README.md b/apps/hwid_a_battery_widget/README.md new file mode 100644 index 000000000..638272b3f --- /dev/null +++ b/apps/hwid_a_battery_widget/README.md @@ -0,0 +1,15 @@ +# A Battery Widget (with percentage) + +Show the current battery level and charging status in the top right of the clock, with charge percentage + +* Works with Bangle 2 +* Simple design, no settings + * Red when the batterly level is below 30% + * Blue when charging +* 40 pixels wide + +![](a_battery_widget-pic.jpg) + +## Creator +[@alainsaas](https://github.com/alainsaas) +Mod by Hank diff --git a/apps/hwid_a_battery_widget/h_battery_widget-pic.jpg b/apps/hwid_a_battery_widget/h_battery_widget-pic.jpg new file mode 100644 index 000000000..9d9a4decc Binary files /dev/null and b/apps/hwid_a_battery_widget/h_battery_widget-pic.jpg differ diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json new file mode 100644 index 000000000..38fd503a2 --- /dev/null +++ b/apps/hwid_a_battery_widget/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "hwid_a_battery_widget", + "name": "A Battery Widget (with percentage) - Hanks Mod", + "shortName":"H Battery Widget", + "icon": "widget.png", + "version":"0.07", + "type": "widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "description": "Simple and slim battery widget with charge status and percentage", + "tags": "widget,battery", + "storage": [ + {"name":"hwid_a_battery_widget.wid.js","url":"widget.js"} + ] +} diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js new file mode 100644 index 000000000..708e8cbbf --- /dev/null +++ b/apps/hwid_a_battery_widget/widget.js @@ -0,0 +1,54 @@ +(function(){ + const intervalLow = 60000; // update time when not charging + const intervalHigh = 2000; // update time when charging + var old_l; + + let COLORS = { + 'white': g.theme.dark ? "#000" : "#fff", + 'black': g.theme.dark ? "#fff" : "#000", + 'charging': "#08f", + 'high': g.theme.dark ? "#fff" : "#000", + 'low': "#f00", + }; + + const levelColor = (l) => { + if (Bangle.isCharging()) return COLORS.charging; + if (l >= 30) return COLORS.high; + return COLORS.low; + }; + + function draw() { + var s = 29; + var x = this.x, y = this.y; + const l = E.getBattery(); + let xl = x+4+l*(s-12)/100; + if (l != old_l){ // Delete the old value from screen + old_l = l; + let xl_old = x+4+old_l*(s-12)/100; + g.setColor(COLORS.white); + // g.fillRect(x+2,y+5,x+s-6,y+18); + g.fillRect(x,y,xl+4,y+16+3); //Clear + g.setFontAlign(0,0); + g.setFont('Vector',16); + g.drawString(old_l, x + 14, y + 10); + g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar + } + + g.setColor(levelColor(l)); + g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar + g.fillRect((x+4+100*(s-12)/100)-1,y+14+3,x+4+100*(s-12)/100,y+16+3); // charging bar "full mark" + // Show percentage + g.setColor(COLORS.black); + g.setFontAlign(0,0); + g.setFont('Vector',16); + g.drawString(l, x + 14, y + 10); + + if (Bangle.isCharging()) changeInterval(id, intervalHigh); + else changeInterval(id, intervalLow); + } + + Bangle.on('charging',function(charging) { draw(); }); + var id = setInterval(()=>WIDGETS["wid_a_battery_widget"].draw(), intervalLow); + + WIDGETS["wid_a_battery_widget"]={area:"tr",width:30,draw:draw}; +})(); diff --git a/apps/hwid_a_battery_widget/widget.png b/apps/hwid_a_battery_widget/widget.png new file mode 100644 index 000000000..b9c9594b1 Binary files /dev/null and b/apps/hwid_a_battery_widget/widget.png differ diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog new file mode 100644 index 000000000..72b25a7c6 --- /dev/null +++ b/apps/hworldclock/ChangeLog @@ -0,0 +1,7 @@ +0.15: Initial release - be patient as this is the first try :) +0.16: Fix timing +0.17: Fix hours +0.18: Code cleanup and major changes with seconds timing. New feature: if watch is locked, seconds get refreshed every 10 seconds. +0.19: Fix PM Hours +0.20: Add theme support +0.21: Add Settings \ No newline at end of file diff --git a/apps/hworldclock/README.md b/apps/hworldclock/README.md new file mode 100644 index 000000000..0f4f9296c --- /dev/null +++ b/apps/hworldclock/README.md @@ -0,0 +1,31 @@ +# Hanks World Clock - See the time in four locations + +In addition to the main clock and date in your current location, you can add up to three other locations. Great for travel or remote working. +Additionally we show the sunset/sunrise and seconds for the current location and the day name is shown in your locale. +If watch is locked, seconds get refreshed every 10 seconds. + +![](hworldclock.png) + +## Usage + +Provide names and the UTC offsets for up to three other timezones in the app store. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India). + +The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones. + + +## Requests + +Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if you have feature requests or notice bugs. + +## Creator + +Created by Hank. + +Based on the great work of +================= +World Clock - 4 time zones +Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock). +===== a n d ===== +Sun Clock +[Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock) +================= diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js new file mode 100644 index 000000000..d4c677d26 --- /dev/null +++ b/apps/hworldclock/app.js @@ -0,0 +1,389 @@ +// ------- Settings file +const SETTINGSFILE = "hworldclock.json"; +var secondsMode; +var showSunInfo; +var colorWhenDark; +// ------- Settings file + +const big = g.getWidth()>200; +// Font for primary time and date +const primaryTimeFontSize = big?6:5; +const primaryDateFontSize = big?3:2; +require("Font5x9Numeric7Seg").add(Graphics); +require("FontTeletext10x18Ascii").add(Graphics); + +// Font for single secondary time +const secondaryTimeFontSize = 4; +const secondaryTimeZoneFontSize = 2; + +// Font / columns for multiple secondary times +const secondaryRowColFontSize = 2; +const xcol1 = 10; +const xcol2 = g.getWidth() - xcol1; + +const font = "6x8"; + +/* TODO: we could totally use 'Layout' here and +avoid a whole bunch of hard-coded offsets */ + +const xyCenter = g.getWidth() / 2; +const xyCenterSeconds = xyCenter + (big ? 85 : 68); +const yAmPm = xyCenter - (big ? 70 : 48); +const yposTime = big ? 70 : 55; +const yposTime2 = yposTime + (big ? 100 : 60); +const yposDate = big ? 135 : 95; +const yposWorld = big ? 170 : 120; + +const OFFSET_TIME_ZONE = 0; +const OFFSET_HOURS = 1; + +var PosInterval = 0; + +var offsets = require("Storage").readJSON("hworldclock.settings.json") || []; + +//=======Sun +setting = require("Storage").readJSON("setting.json",1); +E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ +SunCalc = require("hsuncalc.js"); +const LOCATION_FILE = "mylocation.json"; +var rise = "07:00"; +var set = "20:00"; +var pos = {altitude: 20, azimuth: 135}; +var noonpos = {altitude: 37, azimuth: 180}; +//=======Sun + +var ampm = "AM"; + +// TESTING CODE +// Used to test offset array values during development. +// Uncomment to override secondary offsets value +/* +const mockOffsets = { + zeroOffsets: [], + oneOffset: [["UTC", 0]], + twoOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ], + fourOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Miami", -5], + ], +};*/ + + +// Example hworldclock.settings.json +// [["London","0"],["NY","-5"],["Denver","-6"]] + + +// Uncomment one at a time to test various offsets array scenarios +//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time +//offsets = mockOffsets.oneOffset; // should render larger in two rows +//offsets = mockOffsets.twoOffsets; // should render two in columns +//offsets = mockOffsets.fourOffsets; // should render in columns + +// END TESTING CODE + + +// Load settings +function loadMySettings() { + // Helper function default setting + function def (value, def) {return value !== undefined ? value : def;} + + var settings = require('Storage').readJSON(SETTINGSFILE, true) || {}; + secondsMode = def(settings.secondsMode, "when unlocked"); + showSunInfo = def(settings.showSunInfo, true); + colorWhenDark = def(settings.colorWhenDark, "green"); +} + + +// Check settings for what type our clock should be +var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false; + +// timeout used to update every minute +var drawTimeout; +var drawTimeoutSeconds; +var secondsTimeout; + +g.setBgColor(g.theme.bg); + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +// schedule a draw for the next second +function queueDrawSeconds() { + if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); + drawTimeoutSeconds = setTimeout(function() { + drawTimeoutSeconds = undefined; + drawSeconds(); + //console.log("TO: " + secondsTimeout); + }, secondsTimeout - (Date.now() % secondsTimeout)); +} + +function doublenum(x) { + return x < 10 ? "0" + x : "" + x; +} + +function getCurrentTimeFromOffset(dt, offset) { + return new Date(dt.getTime() + offset * 60 * 60 * 1000); +} + +function updatePos() { + coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":53.3,"lon":10.1,"location":"Pattensen"}; + pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon); + times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon); + rise = times.sunrise.toString().split(" ")[4].substr(0,5); + set = times.sunset.toString().split(" ")[4].substr(0,5); + noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon); +} + + +function drawSeconds() { + // get date + var d = new Date(); + var da = d.toString().split(" "); + + // default draw styles + g.reset(); + g.setBgColor(g.theme.bg); + + // drawSting centered + g.setFontAlign(0, 0); + + // draw time + var time = da[4].split(":"); + var seconds = time[2]; + + g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3); + if (g.theme.dark) { + if (colorWhenDark == "green") { + g.setColor("#22ff05"); + } else { + g.setColor(g.theme.fg); + } + } else { + g.setColor(g.theme.fg); + } + //console.log("---"); + //console.log(seconds); + if (Bangle.isLocked() && secondsMode != "always") seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x + //console.log(seconds); + g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true); + queueDrawSeconds(); + +} + +function draw() { + // get date + var d = new Date(); + var da = d.toString().split(" "); + + // default draw styles + g.reset(); + g.setBgColor(g.theme.bg); + + // drawSting centered + g.setFontAlign(0, 0); + + // draw time + var time = da[4].split(":"); + var hours = time[0], + minutes = time[1]; + + + if (_12hour){ + //do 12 hour stuff + if (hours > 12) { + ampm = "PM"; + hours = hours - 12; + if (hours < 10) hours = doublenum(hours); + } else { + ampm = "AM"; + } + } + + //g.setFont(font, primaryTimeFontSize); + g.setFont("5x9Numeric7Seg",primaryTimeFontSize); + if (g.theme.dark) { + if (colorWhenDark == "green") { + g.setColor("#22ff05"); + } else { + g.setColor(g.theme.fg); + } + } else { + g.setColor(g.theme.fg); + } + g.drawString(`${hours}:${minutes}`, xyCenter-10, yposTime, true); + + // am / PM ? + if (_12hour){ + //do 12 hour stuff + //var ampm = require("locale").medidian(new Date()); Not working + g.setFont("Vector", 17); + g.drawString(ampm, xyCenterSeconds, yAmPm, true); + } + + if (secondsMode != "none") drawSeconds(); // To make sure... + + // draw Day, name of month, Date + //DATE + var localDate = require("locale").date(new Date(), 1); + localDate = localDate.substring(0, localDate.length - 5); + g.setFont("Vector", 17); + g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true); + + g.setFont(font, primaryDateFontSize); + // set gmt to UTC+0 + var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000); + + // Loop through offset(s) and render + offsets.forEach((offset, index) => { + dx = getCurrentTimeFromOffset(gmt, offset[OFFSET_HOURS]); + hours = doublenum(dx.getHours()); + minutes = doublenum(dx.getMinutes()); + + + if (offsets.length === 1) { + var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; + // For a single secondary timezone, draw it bigger and drop time zone to second line + const xOffset = 30; + g.setFont(font, secondaryTimeFontSize); + g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); + g.setFont(font, secondaryTimeZoneFontSize); + g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true); + + // draw Day, name of month, Date + g.setFont(font, secondaryTimeZoneFontSize); + g.drawString(date, xyCenter, yposDate, true); + } else if (index < 3) { + // For > 1 extra timezones, render as columns / rows + g.setFont(font, secondaryRowColFontSize); + g.setFontAlign(-1, 0); + g.drawString( + offset[OFFSET_TIME_ZONE], + xcol1, + yposWorld + index * 15, + true + ); + g.setFontAlign(1, 0); + g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true); + } + }); + + if (showSunInfo) { + g.setFontAlign(-1, 0); + g.setFont("Vector",12); + g.drawString(`^${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw riseset + g.setFontAlign(1, 0); + g.drawString(`v${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw riseset + } + //debug settings + //g.setFontAlign(1, 0); + //g.drawString(secondsMode, xcol2, 3 + yposWorld + 3 * 15, true); + //g.drawString(showSunInfo, xcol2, 3 + yposWorld + 3 * 15, true); + //g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true); + + + queueDraw(); + + if (secondsMode != "none") queueDrawSeconds(); +} + +// clean app screen +g.clear(); + +// Init the settings of the app +loadMySettings(); + +// Show launcher when button pressed +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + + +// draw immediately at first, queue update +draw(); + + +if (!Bangle.isLocked()) { // Initial state + if (showSunInfo) { + if (PosInterval != 0) clearInterval(PosInterval); + PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins + } + + secondsTimeout = 1000; + if (secondsMode != "none") { + if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); + drawTimeoutSeconds = undefined; + } + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + draw(); // draw immediately, queue redraw + if (showSunInfo) updatePos(); + }else{ + if (secondsMode == "always") secondsTimeout = 1000; + if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000; + + if (secondsMode != "none") { + if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); + drawTimeoutSeconds = undefined; + } + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + if (showSunInfo) { + if (PosInterval != 0) clearInterval(PosInterval); + PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins + } + draw(); // draw immediately, queue redraw + if (showSunInfo) updatePos(); + } + + + + + +Bangle.on('lock',on=>{ + if (!on) { // UNlocked + if (showSunInfo) { + if (PosInterval != 0) clearInterval(PosInterval); + PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins + } + + secondsTimeout = 1000; + if (secondsMode != "none") { + if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); + drawTimeoutSeconds = undefined; + } + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + draw(); // draw immediately, queue redraw + if (showSunInfo) updatePos(); + }else{ // locked + + if (secondsMode == "always") secondsTimeout = 1000; + if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000; + + if (secondsMode != "none") { + if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); + drawTimeoutSeconds = undefined; + } + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + + if (PosInterval != 0) clearInterval(PosInterval); + PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins + draw(); // draw immediately, queue redraw + if (showSunInfo) updatePos(); + } + }); \ No newline at end of file diff --git a/apps/hworldclock/app.png b/apps/hworldclock/app.png new file mode 100644 index 000000000..506fa45b9 Binary files /dev/null and b/apps/hworldclock/app.png differ diff --git a/apps/hworldclock/custom.html b/apps/hworldclock/custom.html new file mode 100644 index 000000000..896d999f5 --- /dev/null +++ b/apps/hworldclock/custom.html @@ -0,0 +1,76 @@ + + + + + + +

You can add up to 3 timezones. Please give a name and UTC offset in hours. + If you want less than 3, clear the checkbox to the left.

+ + + + + + + +
Enabled?NameUTC Offset
+ +

Click

+ + + + + + diff --git a/apps/hworldclock/hsuncalc.js b/apps/hworldclock/hsuncalc.js new file mode 100644 index 000000000..b1af0a0d9 --- /dev/null +++ b/apps/hworldclock/hsuncalc.js @@ -0,0 +1,298 @@ +/* Module suncalc.js + (c) 2011-2015, Vladimir Agafonkin + SunCalc is a JavaScript library for calculating sun/moon position and light phases. + https://github.com/mourner/suncalc + +PB: Usage: +E.setTimeZone(2); // 1 = MEZ, 2 = MESZ +SunCalc = require("suncalc.js"); +pos = SunCalc.getPosition(Date.now(), 53.3, 10.1); +times = SunCalc.getTimes(Date.now(), 53.3, 10.1); +rise = times.sunrise; // Date object +rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm +*/ +var exports={}; + +// shortcuts for easier to read formulas + +var PI = Math.PI, + sin = Math.sin, + cos = Math.cos, + tan = Math.tan, + asin = Math.asin, + atan = Math.atan2, + acos = Math.acos, + rad = PI / 180; + +// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas + +// date/time constants and conversions + +var dayMs = 1000 * 60 * 60 * 24, + J1970 = 2440588, + J2000 = 2451545; + +function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } +function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021 +function toDays(date) { return toJulian(date) - J2000; } + + +// general calculations for position + +var e = rad * 23.4397; // obliquity of the Earth + +function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } +function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } + +function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } +function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } + +function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } + +function astroRefraction(h) { + if (h < 0) // the following formula works for positive altitudes only. + h = 0; // if h = -0.08901179 a div/0 would occur. + + // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: + return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); +} + +// general sun calculations + +function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } + +function eclipticLongitude(M) { + + var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center + P = rad * 102.9372; // perihelion of the Earth + + return M + C + P + PI; +} + +function sunCoords(d) { + + var M = solarMeanAnomaly(d), + L = eclipticLongitude(M); + + return { + dec: declination(L, 0), + ra: rightAscension(L, 0) + }; +} + +// calculates sun position for a given date and latitude/longitude + +exports.getPosition = function (date, lat, lng) { + + var lw = rad * -lng, + phi = rad * lat, + d = toDays(date), + + c = sunCoords(d), + H = siderealTime(d, lw) - c.ra; + + return { + azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg + altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg + }; +}; + + +// sun times configuration (angle, morning name, evening name) + +var times = [ + [-0.833, 'sunrise', 'sunset' ] +]; + +// calculations for sun times +var J0 = 0.0009; + +function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } + +function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } +function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } + +function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } +function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } + +// returns set time for the given sun altitude +function getSetJ(h, lw, phi, dec, n, M, L) { + + var w = hourAngle(h, phi, dec), + a = approxTransit(w, lw, n); + return solarTransitJ(a, M, L); +} + + +// calculates sun times for a given date, latitude/longitude, and, optionally, +// the observer height (in meters) relative to the horizon + +exports.getTimes = function (date, lat, lng, height) { + + height = height || 0; + + var lw = rad * -lng, + phi = rad * lat, + + dh = observerAngle(height), + + d = toDays(date), + n = julianCycle(d, lw), + ds = approxTransit(0, lw, n), + + M = solarMeanAnomaly(ds), + L = eclipticLongitude(M), + dec = declination(L, 0), + + Jnoon = solarTransitJ(ds, M, L), + + i, len, time, h0, Jset, Jrise; + + + var result = { + solarNoon: fromJulian(Jnoon), + nadir: fromJulian(Jnoon - 0.5) + }; + + for (i = 0, len = times.length; i < len; i += 1) { + time = times[i]; + h0 = (time[0] + dh) * rad; + + Jset = getSetJ(h0, lw, phi, dec, n, M, L); + Jrise = Jnoon - (Jset - Jnoon); + + result[time[1]] = fromJulian(Jrise); + result[time[2]] = fromJulian(Jset); + } + + return result; +}; + + +// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas + +function moonCoords(d) { // geocentric ecliptic coordinates of the moon + + var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude + M = rad * (134.963 + 13.064993 * d), // mean anomaly + F = rad * (93.272 + 13.229350 * d), // mean distance + + l = L + rad * 6.289 * sin(M), // longitude + b = rad * 5.128 * sin(F), // latitude + dt = 385001 - 20905 * cos(M); // distance to the moon in km + + return { + ra: rightAscension(l, b), + dec: declination(l, b), + dist: dt + }; +} + +getMoonPosition = function (date, lat, lng) { + + var lw = rad * -lng, + phi = rad * lat, + d = toDays(date), + + c = moonCoords(d), + H = siderealTime(d, lw) - c.ra, + h = altitude(H, phi, c.dec), + // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); + + h = h + astroRefraction(h); // altitude correction for refraction + + return { + azimuth: azimuth(H, phi, c.dec), + altitude: h, + distance: c.dist, + parallacticAngle: pa + }; +}; + + +// calculations for illumination parameters of the moon, +// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and +// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + +getMoonIllumination = function (date) { + + var d = toDays(date || new Date()), + s = sunCoords(d), + m = moonCoords(d), + + sdist = 149598000, // distance from Earth to Sun in km + + phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)), + inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)), + angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - + cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra)); + + return { + fraction: (1 + cos(inc)) / 2, + phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, + angle: angle + }; +}; + + +function hoursLater(date, h) { + return new Date(date.valueOf() + h * dayMs / 24); +} + +// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article + +getMoonTimes = function (date, lat, lng, inUTC) { + var t = new Date(date); + if (inUTC) t.setUTCHours(0, 0, 0, 0); + else t.setHours(0, 0, 0, 0); + + var hc = 0.133 * rad, + h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, + h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; + + // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) + for (var i = 1; i <= 24; i += 2) { + h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; + h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; + + a = (h0 + h2) / 2 - h1; + b = (h2 - h0) / 2; + xe = -b / (2 * a); + ye = (a * xe + b) * xe + h1; + d = b * b - 4 * a * h1; + roots = 0; + + if (d >= 0) { + dx = Math.sqrt(d) / (Math.abs(a) * 2); + x1 = xe - dx; + x2 = xe + dx; + if (Math.abs(x1) <= 1) roots++; + if (Math.abs(x2) <= 1) roots++; + if (x1 < -1) x1 = x2; + } + + if (roots === 1) { + if (h0 < 0) rise = i + x1; + else set = i + x1; + + } else if (roots === 2) { + rise = i + (ye < 0 ? x2 : x1); + set = i + (ye < 0 ? x1 : x2); + } + + if (rise && set) break; + + h0 = h2; + } + + var result = {}; + + if (rise) result.rise = hoursLater(t, rise); + if (set) result.set = hoursLater(t, set); + + if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; + + return result; +}; \ No newline at end of file diff --git a/apps/hworldclock/hworldclock-icon.js b/apps/hworldclock/hworldclock-icon.js new file mode 100644 index 000000000..6e05d254c --- /dev/null +++ b/apps/hworldclock/hworldclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgJC/ABEE+EA4EAj9E8HF//gn/gwP///wt/MgF//8gh/8gYLBwEP+EHAofghgFD4EOj//gEPA4ILBGgIxB/wFBgwFB/lsgCKBj/4oxHBvAFBJoV8gP4TQX+gJUBAAN/Aok+AoVgAoXogAfBjkA8AfBAoXAAoUYY4cAiCDEAooA/ABg")) diff --git a/apps/hworldclock/hworldclock.png b/apps/hworldclock/hworldclock.png new file mode 100644 index 000000000..565e0dc6b Binary files /dev/null and b/apps/hworldclock/hworldclock.png differ diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json new file mode 100644 index 000000000..3d9be3bc7 --- /dev/null +++ b/apps/hworldclock/metadata.json @@ -0,0 +1,25 @@ +{ + "id": "hworldclock", + "name": "Hanks World Clock", + "shortName": "Hanks World Clock", + "version": "0.21", + "description": "Current time zone plus up to three others", + "allow_emulator":true, + "icon": "app.png", + "screenshots": [{"url":"screenshot_hworld.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "custom": "custom.html", + "storage": [ + {"name":"hworldclock.app.js","url":"app.js"}, + {"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true}, + {"name":"hworldclock.settings.js","url":"settings.js"}, + {"name":"hsuncalc.js","url":"hsuncalc.js"} + ], + "data": [ + {"name":"hworldclock.settings.json"}, + {"name":"hworldclock.json"} + ] +} \ No newline at end of file diff --git a/apps/hworldclock/screenshot_hworld.png b/apps/hworldclock/screenshot_hworld.png new file mode 100644 index 000000000..565e0dc6b Binary files /dev/null and b/apps/hworldclock/screenshot_hworld.png differ diff --git a/apps/hworldclock/settings.js b/apps/hworldclock/settings.js new file mode 100644 index 000000000..60092d21e --- /dev/null +++ b/apps/hworldclock/settings.js @@ -0,0 +1,59 @@ +// Settings menu for the enhanced Anton clock + +(function(back) { + var FILE = "hworldclock.json"; + // Load settings + var settings = Object.assign({ + secondsOnUnlock: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Helper method which uses int-based menu item for set of string values + function stringItems(startvalue, writer, values) { + return { + value: (startvalue === undefined ? 0 : values.indexOf(startvalue)), + format: v => values[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + writer(values[v]); + writeSettings(); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values) { + return stringItems(settings[name], v => settings[name] = v, values); + } + + var mainmenu = { + "": { + "title": "Hanks World Clock" + }, + "< Back": () => back(), + "Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]), + "Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]), + "Show SunInfo": { + value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true), + format: v => v ? "On" : "Off", + onchange: v => { + settings.showSunInfo = v; + writeSettings(); + } + } + }; + + + + // Actually display the menu + E.showMenu(mainmenu); + +}); + +// end of file diff --git a/apps/kanawatch/ChangeLog b/apps/kanawatch/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/kanawatch/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/kanawatch/README.md b/apps/kanawatch/README.md new file mode 100644 index 000000000..1fdf1927c --- /dev/null +++ b/apps/kanawatch/README.md @@ -0,0 +1,12 @@ +# kanawatch + +A simple watchface design with hiragana and katakana +cards for learning. + +## Author + +Written by pancake in 2022, powered by insomnia + +## Screenshots + +![hiragana and katakana](screenshot.jpg) diff --git a/apps/kanawatch/app-icon.js b/apps/kanawatch/app-icon.js new file mode 100644 index 000000000..a17f21d56 --- /dev/null +++ b/apps/kanawatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxEBAH4A/AEn/AAgrrAA4ttGL4hF9fGsU1pMNmti43rGLwcD/3MxEAud413p6uuvFzgGI5n+GDQaD6F8i2p8KKH8Opi186AwYC4Xv08A0fnXhfn0cA0/vGCoVC7+ItHNE4vQ+oxH5toxHfGCYTC8t/xaKH5VY+CUIxd/8owSCIPxymB8wkH8UA2yTI82Byn4F6AXCwNH7YjI7UATAwAD7dHHgYuP4sAc5XLgHrBpXAjngGBwOCrmJ/whJ1syBgXw7v6Bov+xObF5rWDgHWKJWEt3l4mQjkAoHzBwvWgHhGBgMC1WIDQuw1/L427z8ygAABp+R3vqH4+I1QvO/1R5YZF+t1FINWuMAy/W+BuKZ4NRT4ReL7kc+waG/fy/n/9kA74tLAAP2jncAgPBF5W5yIeLZgPxEgf3CJOR3JTCF5WU3wvL6sA/YFC7e0CJO+ygDB94vKt3aF5fHoQDB+/dzdL4nb+YRG7VuAYP5F5VF9ovL3dP3t8pOKgFw0+CjmT84RE9tFAYP+F6/uwMm1Hd/vCk3oQYWGl3XF6aPK/e0oVwrohCmu9Bof5sVF+yPSd5PtuWA9m7o///uCwH9B4m9gHKd6W5yIuG9NV3v+//Gjn/2VA9wQF6UA2AFCyO5AYPcF5Xcjh1DAAPnp/SEYnJiy2EAAXTgGvAgP2jncAgPBF44wC/1R5a7EsZHCAAPegEA3afH4sA4wEB5dROgP/FxBgD1WIPgky/QGD5MAxYfCAAuGjnvAgNHuBLCF5nhgHWAoWvuwEC9mWLwN+Fw6aB1wEB60A44EB6ovJGAebxJSC1lF4/AyMNoXBzUN/IuF5kmyP8VgOJrgKCFxUB8QOB8Ec4CnCLIMAmWr+v/9Vy/otD+WWmu7BAXAjnFF5xgD21H7f//u+0vN/CKH9Ojse4+QHC7dH2wuPgPVCAP4yk98wqHAAf734OF82ByhCDF5pgD/9/xfhGBYAF8OLv/lFyIABU4XfxFo5ouP5toxHfFyZhE9+ngGj84tL8+jgGn94uVSQvQvkW1KUI8Opi186AIDFygwF/3MxEAuew6fp9PT2FzgGI5n+FzQwFAAPr42fu9JpN3z/G9YPFFzAxIABYtbGKItfGZYrlAH4A+A")) diff --git a/apps/kanawatch/app.js b/apps/kanawatch/app.js new file mode 100644 index 000000000..ada6aa6df --- /dev/null +++ b/apps/kanawatch/app.js @@ -0,0 +1,825 @@ +const stripe_width = 32; +const stripe_pos = 40; +const stripe2_pos = 110; +const h = g.getHeight(); +const w = g.getWidth(); + +/// ///////////////////////////////////////// +const katakana = {}; +const hiragana = {}; +katakana.A = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAjAEBfv4B/+yeAXwAOgBAAPAAAEHAAABzAAAAPgAAADgAAAAwAAAAMAAAAGAAAABgAAAAYAAAAMAAAADAAAABgAAAAYAAAAMAAAAGAAAADAAAABgAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.A = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAACAAAAAwAAAAIAAAACAAAABgAAAAZ4AAGf4AAA/gAAAAQAAAAEAAAABBAAAAQwAAAN/wAADiGAADxAwABswEAAhYBgAQUAYAMHAEACBgDABh4AwAZ2AYAD4gcAAQAcAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.I = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAwAAAAGAAAADwAAAA0AAAAYAAAAUgAAAGAAAAFAAAADgAAAA8AAAA2AAAAZgAAAYYAAAMGAAAMFgAAGAYAAGAGAACABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.I = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAwAAgAEAAEABAAAgAQAAMAGAABAAgAAYAIAAGACAAAwAQAAMAEAADABiAAQAIgAAADQAAAAcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.U = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAQAAAAHAAAAAwAAAAICAAACIAAAAgIABQa3AAP7q4ADQANAAwADAAMABgADAAYAAwAGAAMADAADAAwAAwAYAAMAGAABADAAAABgAAAAwAAAAMAAAAGAAAACQAAADAAAABgAAAAwAAAAoAAAAAAAAAAAAAAAgAAA=') +}; +hiragana.U = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAIAAAABwAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAA4YAAA4CAAAAAgAAAAIAAAACAAAAAgAAAAYAAAAGAAAABAAAAAQAAAAIAAAACAAAABAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.E = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAJXAAe+20ADRQAAAAOAAAABgAAAAQAAAAMAAAABAAAAAwAAAAEAACABAEAgJbvgP9qSsByAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.E = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAADgAAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAdwAAAcYAAB8MAAAIGAAAADAAAABgAAAAwAAAAYAAAAMAAAAGAAAADIAAAB4gAAA4EAAAMAgAACAOGAAAB/wAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.O = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAADwAAAAOAAAADAAAAAwAAAAMAAAAjAABAAydAbff/wH/XAUAwDwAAAB0AAAA7AAAAMwAAAHMAAADjAAABkwAAA4MAAAZDAAAMEwAAGEMAAGQzAADAHwABAA8AAAAHAAAABAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.O = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAADACAAAwAYAAMADAADIAQAA/AGAF+AAAAyAAAAAgAAAAIAAAACAAAAAg/gAAJwOAADgBgABgAMAAoADAAyAAwAIgAMAEIAGABCADAAJgBgAD4AgAAMAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.HA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAcGAAADgwAAB4HAAA4A4AAMgHAAHAA4ADAAXAAwAA4AYAAHAMAABwGAAAMGAAACDAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.HA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAABAACAAYAAwAGAAMABgACAAYAAgAHwAIAD4AGAfYABAAGAAQABgAEAAYABAAGAAQABgAEAAYABAAGAAQABgAEAAYABAOGAAQEfgAFCA8ABggPwAYG+GAGAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.HI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAXAAAABwAAAAYAgAAGAMAABgDgABYD0AAWF4gABvwAAAfAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAgAAal8AAD//gAAJQAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.HI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwEAAA8BAAB2AYAABACAAAwAQAAIAEAAGAJgABACIAAwAjAAIAIYACACGABABAwAQAQEAEAEAABADAAAAAgAAEAYAABAEAAAYDAAADDgAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.HU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAALwAYt/vAD/0DwAcABwAACAcAAAAGAAAADIAAAAwAAAAYAAAAOAAAAGAAAADgAAAAwAAAA0AAAAaAAAAOAAAAHAAAAHAAAAHAAAAGgAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.HU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAGAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAACAAAAAQAAAACAgAAAgEAAAMBgAABAYAgAwDAMAMAgBgCAAAYHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.HE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAANwAAAGHAAADA4AABwDgAIwBOADcABwQeAAHgDAAA8AIAADwAAAAeAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.HE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAIMAAAMAgAAGAGAADAAwAAAADAAAAAYAAAADgAAAAMAAAABwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.HO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAEAAAADQAAAAYAAAASAAAABgAAAAIAACAGK4A273dAHoYAAAAGAAAAAgAAAIIQAAECGAABAgwAAgYGAAIGAwAGAgGADAIBwBiCAaAYRgDAMDIAgAAeAAAADgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.HO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAQB+AAEAgAABAAQAAQAGAAIABgACAAQAAgAHwAIAD4ACAfQABAAEAAQABAAEAAQABAAGAAQABgAEAAYABAAGAAQBdgAHAg4ABwAHgAIB+OACAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.KA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAUAAAADwAAAAcAAAAGAAAABgAAAAYFABAOvwAfv9eAD6wHAAQMBwAADAYAABwGAAAYBgAAGAYAADAOAAAwDAAAYgwAAMgcAADEmAAFgzgAAwHwAA4B4ABYAcAAMABAAEAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.KA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAwAAAAIAAAACAAAABAAAAAQAgAAMAEAAD8AwAHggGAHQIBgAECAMADAgDAAgIAQAIGAEAEBAAABAwAAAwIAAAYGAAAGBgAAADwAAAAcAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.KI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAACwAAAAeAAAADgAAAAYAAAATBgAAAz8AAAP5AAxfQAAH8YAAA4GAAAABgHAAAYf4AAD+pAAF8AAMPsAAC/hgAAPAYAABAGAAAABwAAAAYAAAAHAAAAAwAAAAOAAAADAAAAAYAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.KI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAQAAAAGAAAAAgAAAAIAAAADDAAAAfwAAAeAAAA4gAAAwIAAAABAAAAAZwAAADwAAAHwAAAOGAAAAAgAAAAMAAAADAAAAAQAAAAAAAAAAAAAAAAAAEAAAABgAAAAPmAAAAfwAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.KU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAQAAAAHAAAAA4AAAAMAAAAHBwAAB/+AAA0XAAAaBkAAGA4AADAOAABgHAAAwBwAAYA4AAMAMAAGAHAAAADgAAABwAAAA0AAAAaAAAAOAAAAHAAAADIAAADgAAACgAAABgAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.KU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAADAAAABgAAAAQAAAAIAAAAGAAAABAAAAAgAAAAQAAAAEAAAACAAAAAQAAAAEAAAAAgAAAAEAAAABgAAAAIAAAADAAAAAYAAAAGAAAAAwAAAAMAAAABgAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.KE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAABwAAAAOAAAADgAAABwAAAAYAQAAGAAgABgF8AA79/gAb7gAAGQcQADAHgABgBgAAYAwAAZAMAAMAHAADABgAAgAwAAAAMAAAAGAAAALAAAABwAAAAYAAAAYAAAAMgAAAGAAAACAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.KE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAABAAAAAYAAIAGAAGABgABgAYAAYAGAAEAB+ABAB/gAQHmAAEABgADAAYAAgAGAAIABgACAAYAAgAGAAIABAACAAQAAgAEAAKABAADgAwAAYAIAAGACAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.KO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwCtwAH//8AA+oGAAEABgAAAAYAAAAGAAAABgAAAAQAAAAsAAAADAAIAFwADv//AAf1CQACAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.KO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AAAADwAAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAYAAAAD8EAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.MA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAFcAIG/3ga/0h4H6gA4AcAAcACAAOAAAAHAAAYDAAAFjgAAAPgAAAB4AAAAOAAAABwAAAAMAAAADAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.MA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABBAAAAf8AAD+AAAOBAAAAAQAAAAGAAAABgAAAAZwAAAHwAAB/gAAAAYAAAAGAAAABgAAAAYAAAAGAAAARgAAAR4AAAIHgAACDPAAARg4AABAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.MI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAegAAAC+gAAAB8AAAAHgAAAAYAAAAQAAAAgAAAegAAAB+AAAAD4AAAAPAAAABwAAAAMAAAAAAAAAAAAAAAAAAAUAAAAF8AAAAHwAAAAPgAAAA8AAAAHwAAAAeAAAADAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.MI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAA+YAAAEMAAAADAAAABgAAAAQAAAAMAAAACAAAABgAAAAQAAAAIAIAAGAGAADgBgAO/wQAEIH8ACEAH4AiABnAJgAQQBgAIAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.MU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAACgAAAAcAAAADgAAAA4AAAAcAAAAHAAAABkAAAAYAAAAMQAAADEAAABwwAAAYGAAAGAwAADAHAAAwA4AAIAOAAWBfwBBX9OAf/oDgH+gAYA6AAGAAEAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.MU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAYAAAACAAAAAgAwAAIAGAACIAwAA/gEAB+ABAB2AAAAAgAAAAYAAAAGAAAABgAAAAYAAAAGAEAABgBAAGQAQAA0AEAAFABAAAwAQAAEAMAARgCAAGWHgAA8fgAAGAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.ME = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAcAAAABgAAAAcAAAAHAAAADgAAAAwAAABcAABgGAAAfDgAAAewAAAB8AAAAPAAAAD8AAABzgAAA44AAAcHAAAGAwAADAAAACgAAABwAAAAoAAAAcAAAAMAAAAMAAAABAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.ME = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABgAAAAYAABAGAAAIBAAACAwAAAgP4AAMeDgABZgMAAYQBgAOMAYAGiACADJgAgAjQAIAQcACAEGABgBBgAQARsAIAHwAEAAQAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.MO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAABAAUAASXfgAD7EQAAQwAAAAOAAAABAAAAAwAAAAEAUBADd/wNfaRID1EAAAIDAAAAAwAAAAEAAAADAAAAAQAAAAMAAAABiQAAAf+AAABKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.MO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAIAAAACAAAAB+AAAA/wAAB0AAAABAAAAAQAAAAEAAAABAAAAAQAAAAEYAAAf+AAABwAAAAMAAAACAIAAAgCAAAIAgAACAIAAAgCAAAEBAAABgwAAAP4AAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.NA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAPAAAAA4AAAAOAAAADAAAAAwBAAAMQAAADAaBAT9/wf/vbcD6DAAAQAxAAAAMAAAADAAAAAwAAAAMAAAAGAAAABgAAAAYAAAAMAAAAGAAAABgAAAAwAAAAYAAAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.NA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAgAAAAJgAAAH4AAA/gAAAMQAIAAIABAACAAYABgACAAQAAAAMAAAACAIAAAgCAAAAAgAAAAIAAAACAAAAAgAAAPIAAAEOAAABB4AAAQTgAAD4MAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.NI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgALAANb/8AB/6pAAMAAAAAACAAAAAAAAAAAAAAAAAAAAAAABAAAIAAAJvAKN//4D/1EGAdAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.NI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAGAAAABgA/AAYBwAAEAAAABAAAAAQAAAAMAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAAEAAAIAgAADAH/gAwAAAAMAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.NU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAFgAML38AB/9OAAOgHAAAQBgABAA4AAAAMAADoHAAAPRgAAAfYAAAB4gAAAPQAAADeAAABjwAABwcAAI4DgAA4AcAAcADAAcAAQAaAAAAWAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.NU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAABAwAAAIMAAACDf4AAg4DAAIYAYACeACAAZAAgAMQAIAFMACACSAAgBDgAIAwwOGAIMEbACHBDgAjYPsAPCABgBggAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.NE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAGAAAAA4AAAAHAAAAA4AAAAGAAAABgAAAAJYABAv/AAf+nwAD4DoAAQB4AAAA8AAAB8AAAAeAAAAPQAAAHzgAAHMeAAHDB4ADgwOAHgMBwHADAMKgAwAgAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAEAAAAAAAAAAAAA=') +}; +hiragana.NE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAADAAAAAwAAAAIAAAADAHgAA8GIAA+CCAAzBAwAAhAMAAIgDAAGQAwABoAMAAsADAASAAwAFgAMAC4ACAAyAugAcgIYAGYCHABGAecABgABgAYAAIAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.NO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAGAAAABwAAAAeAAAAOAAAADgAAAAwAAAAcAAAANAAAADAAAABwAAAAYAAAAOAAAAHAAAABgAAAAwAAAAYAAAAMAAAAGAAAAGQAAADAAAADgAAAAkAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.NO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAD44AADEBAABBAIABgQBAAwMAYAICACAEBgAgAAQAIAgMACAICAAgCBgAYAwwAGAEIADABmAAgAPAAQADgAYAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.RA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAEAAQAIAANTvgAD/u0AAOAAAAAACAAAAAAABAACAAgAt4APf/vAB/UDQAIAJwAAAA4AAAAOAAAAHAAAABgAAAA4AAAAcAAAAOAAAAGgAAADQAAABoAAAA4AAAAcAAAAaAAAAMgAAAEIAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.RA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAQAAAACAAAAAwAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAgAAAAYAAAAEAAAABAAAAAQAAAAEA+AADBwQAA3gCAAPgAgADAAIAAAAGAAAADAAAABgAAAAwAAAAgAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.RI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAQBwAAHgOAAAsDgAAGAwAABgMAAAYDAAAGAwAABgMAAAYDAAAGAwAABgMAAAYDAAAGAwAABgMAAAYDAAACAwAAAAYAAAAGAAAADAAAAEwAAAA0AAAAcAAAAOAAAAOAAAAOAAAAGAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.RI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAABAAAAAYAAAAGAAAABAQAAAQGAAAEAgAABAIAAAwCAAAIAgAACAIAAAoCAAAOAgAADAIAAAQCAAAEAgAAAAYAAAAGAAAABAAAAAQAAAAEAAAACAAAAAgAAAAAAAAAEAAAACAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.RU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAABAAAAAeAAAADgAAAA4AAAcGAAADhgAAA4YAAAMGABAGDAAwBkYAYAYGAMAMDAOADAYHAAwGDgAYBjgAMgbwADAHyABgD4AAwA4AAYAEAAMAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.RU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAO4AAA8MAAAAGAAAADAAAAAgAAAAQAAAAIAAAAGAAAABAAAAAgAAAAQAAAAIYMAAE4BgAB4AIAA4ACAAMAAgAAAAYAAAAEAAATCAAAEZAAAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.RE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAACgAAAAeAAAADgAAAAwAAAAMAAAADAAAAAwAAYAMAAMADAAGAAwAGAAsADgADABgAAwBwAAMBwAADA4AAAw8AAAM8AAAD8AAAA+AAAAGAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.RE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAGAAAYDgAAfBIAANgiAAMQwgAAMYIAAHMCAAB2AgAAnAIAAJgCAAEwAgAAcAIAAvACAAewAggHMAIwBDADwAAwAAAAMAAAABAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.RO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAYABk3/AAP/a4ADQAYAAwAGAAMABgABAAwAAwAMAAMADAABAAwAAQEMAAEASAADEt4AA/++QAGgAAADAAQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.RO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAF8wAAAwYAAAAMAAAACAAAABAAAAAwAAAAYAAAAEAAAACAAAABAAAAAg/gAARgGAAPgAgAHgAMADgADAAQAAwAAAAYAAAAOAAAAGAAAAGAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.SA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAFAAAAA4AABAHAAAdBgAABwYAAAYGAAAGBgAABgYABAYGrAYu//4H/aomA4YGAAAGBgAABgYAAAYGAAAGBgAABgwAAAYMAAACGAAAABgAAAAwAAAAYAAAAOAAAAGAAAADgAAABgAAAAgAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.SA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAGAAAABgAAAAIAAAADAAAAAQgAAAG8AAAB4AAAB8AAAPhgAAAAIAAAABAAAAAYAAAADAAAABwAAAAGAAAAAgAAAAAAAAAAAAAAAAAAAAAAAEAAAAAwAAAAH/AAAAHwAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.SI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAHgAAAAcAAAAjgAAAAYABAAAAAwEAAAYB4AAMAHgAGAA4ADAAWABgAAgAyAAAAYAAAAcAAAAOAAAAOAAAIHAAAUHgAABnwAAAPwAAAB4AAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.SI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAQAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAIAQAADA4AAAf4AAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.SU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAEAAGC/gAE/9cAAOgOAAAgHAAAABwAAAA4AAAAcAAAAGAAAAHgAAAB2AAAA44AAAYHAAAcA4AAOAHAALAA4AHAAOAGgABgDAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.SU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAQAAAAE/gAAf/4AH4QAAHAEAAAABAAAAAQAAAGkAAABFAAAARQAAAEcAAABDAAAAYwAAAB8AAAAGAAAABgAAAAQAAAAMAAAAGAAAABAAAABgAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.SE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAA4AAAAHgAAAA4AAAAMAAAADABAAAwG4CAN/vAw36DgH+wDgA6MBwACDA4AAAwZAAAMUAAADMAAAAyAAAAMAAAADAAAAAwAAAAMAAAADAGAAA//gAAL94AAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.SE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAgCAAAMAgAADAIAAAwCAAAMAgAADAf8AAx+AAAPhgAAPAQAA8wEAAMMBAAADAwAAAwcAAAEGAAABAAAAAQAAAACAAAAA8OAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.SO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAQAAAALAAIAA4ADAAeAAcAHAADIBwAAYA4AAHAOAAAwDAAAIBwAAAAYAAAAMgAAAHAAAADAAAABwAAAAYAAAAMAAAAOAAAAHAAAADgAAADgAAADgAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.SO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAYAAAAzAAAHxgAAAwwAAAAYAAAAEAAAACAAAABAAAAAgAAAAQDwAAIDwAAEGQAACOIAABeEAAAMCAAAAAAAAAAQAAAAEAAAABAAAAAYAAAADAAAAAYAAAADwAAAAMAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.TA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAOAAAABwAAAAcAAAAGBYAAB/fAAA1DgAAMA4AAGAcAABgHAAA4DAABdwwAAMPcAAGA+AADADkABgB8AAQAzAAAAMAAAAGAAAADAAAADgAAABwAAAA4AAAAYAAAAcAAAAMAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.TA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAYAAAAEAAAADHgAAA/gAAH8AAAAmAAAABAAAAAQAAAAMAAAACAfgABg4AAAQAAAAEAAAADAAAAAgAAAAYAAAAEAAAADAAAAAwDjgAIAP8ACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.TI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAOAAAAH4AAAPwAAAvgAADe4AAL4OAABADAAABAwAQAAMV4ECv//B7/0IwPQmAAAgDAAAAQwAAAAMAAAADAAAABgAAAAYAAAAMAAAAGAAAACgAAAAwAAAAwAAAAsAAAAMAAAACAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.TI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAABAAAAAIAAAAGAAAABgAAAAQAAAAEAAAABHAAAB/AAAH4AAAACAAAAAgAAAAQAAAAEAAAABAAAAAQAAAAIPcAACMBgAAsAIAAcACAAGAAgAAAAIAAAAGAAAADAAAABgAAABgAAABgAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.TU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAACAAAAAyBgAgHAOAGA4DwAwOA8AGBgcABwYHAAcADgADAA4AAQAcAAAAGAAAALgAAABwAAAAYAAAAMAAAAOAAAADAAAADgAAABwAAABwAAABwAAADRAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.TU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/HAAB4AYADwACAPwAAwBgAAMAAAADAAAAAgAAAAYAAAAEAAAADAAAADAAAADgAAADAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.TE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAACAACAn4AC3/vAAHoAAACQAAAAAAIAAAIAAAgAFfQG3+/8B/YwBAGAOAAAADgAAABgAAAAcAAAAGAAAADAAAABQAAAAYAAAAOAAAADAAAABgAAAAwAAAAwAAAAYAAAAKAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.TE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAPAAAAbgAAA5gAABwgAADwYAAHgEAAAgCAAAABAAAAAQAAAAAAAAACAAAAAgAAAAIAAAACAAAAAQAAAAEAAAAAgAAAAOAAAABwAAAAHAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.TO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAA4AAAAHgAAAA4AAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAD6AAAAzwAAAMPAAADA4AAAwHAAAMAwAADAEAAAwAAAAMAAAADAAAAAwAAAAMAAAAGAAAABwAAAAMAAAAAAAAAAAAAAAIAAAAAAAA=') +}; +hiragana.TO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAGAAAAAgAAAAMAYAABAHAAAQHAAAGDAAAAhgAAAIwAAABwAAAAYAAAAMAAAAEAAAACAAAABAAAAAQAAAAAAAAACAAAAAAAAAAGAAAAAf/wAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.WA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAACAAANACsAB7//gAPtI4AJgAOAAYADAAMABwABgAYACYAGAAOABgABgA4AAwAcAAGAHAABADgAAAAwAAAAcAAAAOAAAAHAAAADgAAADgAAADgAAAFwAAACgAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.WA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAADAAAAAwAAAAMAAAADAAAAA8AAAAfAAAAfgAAAIwAAAAIDnAAGCAYACiACAArAAwATAAMAJgADAD4AAgByAAYARgAEAAYACAAGACAABgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.WI = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAQAAAAPAAAAA4AAAAMAAAADAAAAAwAAAAsEABhLvgAP//cAB5MAAAGDAAADAwAAAQMAAAMDAAADAwcBg19/wf/7UsD1AwAAIAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAQAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.WI = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAbAAAB4wAAAMIAAAACAAAABgAAAAQAAAAEAAAADAAAAA3+AAAeAwAAeAGAAZAAgAMQAMAEMADACCAAwBBgAMAwQACAIMDxgBCBGwARAQYADgCcAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.WE = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAFBK4AB//+AAPUHgABADgAAARwAAAOwAAABwAAAAMAAAADAAAAAwAAAAMAAAALAAAgAyVgPv//+B/qIrgIAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.WE = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAA8AAAB3AAAHhgAAAAwAAAAYAAAAMAAAAGAAAADAAAABhwAAAzDAAAaAYAAOAGAADADAAACRgAAANgAAAGAAAADAAAABgAAAAgAAAAwAcAAYAxwAfggGAOGwAwDA4AAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.WO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAABAAAgAAwAO37/AB//bwAMgAwAAABYAAgAHAAAABgAGFb4AA//uAAHQDAAAAQwAAAAYAAAAOAAAADAAAABgAAAAwAAAAsAAAAGAAAADAAAABgAAABoAAAAwAAAA4AAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.WO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAMAAAACOAAAB/AAAPwAAAAMAAAACAAAABAAAAAwAgAAIAcAAHMMAADBOAAAAeAAAAGAAAADgAAADIAAABCAAAAgAAAAIAAAACAAAAAgAAAAGBwAAAP8AAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.YA = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAEAAAAD4AAAAOAAAADAAAAAwAIAAGADAABgX4AAa/fAAX6OAZfwHAD9MDgAcDBgAEAwwAAAmYAAABogAAAYAAAAGAAAABgAAAAcAAAADAAAAAwAAAAOAAAADgAAAA4AAAAGAAAABgAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.YA = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAgAAAAGAAAAAgAAAAMAAAQAAAAEAAAABAHGAAQOAwAGcAEAA4ADAA4AAwA7AAYA4QA4AAEAAAAAgAAAAIAAAADAAAAAQAAAAGAAAAAgAAAAMAAAADAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.YU = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAABAC4AAd//AAH2hoAAQAyAAAAMAAAALAAAAAwAAAAMAAAADAAABAwQEABe+Bt//9wP+kAIBwAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.YU = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAgAAAAIAACADAAAwD3AAIDIIACBCDAAgggQAIwIGACICBgBkAgYASAIEAEACBABQBgwAcB4YAGAGcABgB8AAYAQAACAIAAAACAAAABAAAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +katakana.YO = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAIAlgAD//8AAfUGAACABgAAAAYAAAAMAABABgABBLwAAf/8AAF0DAAAgAwAAAAMAAAADAAAQAwAAgAMAANN3AAD/3wAANAIAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.YO = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAEAAAABgAAAAMAAAACAAAAAgAAAAIAAAACDAAAA3wAAAOAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAPCAAAEPgAABA8AAAQHwAAADPAAA/g8AAAADgAAAAYAAAAAAAAAAAAAAAAA=') +}; +katakana.N = { + width: 32, + height: 32, + bpp: 1, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAEAAAABgAAAAOAAAABwAAIAMgAGADgADAAwABgAAAAwAAAAwAAABYAAABOAAAAHAAAAHAAAADgAAADkAAABwAACB4AAAx4AAAP4QAAB8AAAAOAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +hiragana.N = { + width: 32, + height: 32, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAACAAAABgAAAAQAAAAMAAAACAAAABAAAAAQAAAAIAAAAGAAAABAAAAAkAAAALgAAAFIAIADiAAAAwwBAAYMAQAEBAIADAQGAAgGDAAYAzgAEAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') +}; +/// ///////////////////////////////////////// + +let kana = katakana.KI; +let scroll = 0; + +function drawWheel () { + if (scroll > 20 || scroll < -20) { + scroll = 0; + next(); + } +} +let hiramode = false; +let curkana = 'KA'; +function next () { + let found = false; + for (const k of Object.keys(katakana).sort()) { + if (found) { + kana = hiramode ? hiragana[k] : katakana[k]; + curkana = k; + return; + } + if (curkana === k) { + found = true; + } + } + curkana = 'KA'; + kana = hiramode ? hiragana[curkana] : katakana[curkana]; +} + +function prev () { + let oldk = ''; + let count = 0; + for (const k of Object.keys(katakana).sort()) { + if (curkana === k) { + if (count > 0) { + curkana = oldk; + kana = katakana[curkana]; + return; + } else { + } + } + oldk = k; + count++; + } + curkana = oldk; + kana = katakana[curkana]; +} + +const kanacolors = { + A: [] +}; + +const clocktop = false; + +function updateWatch (hhmm) { + if (!hhmm) { + hhmm = ohhmm; + } + g.setBgColor(0, 0, 0); + g.setColor(0, 0, 0); + if (false) { + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + g.setColor(0.3, 0.3, 0.3); + g.setColor(1, 0, 0); + + g.fillRect(stripe_pos, 0, stripe_pos + stripe_width, h); + + g.fillRect(stripe2_pos, 0, stripe2_pos + stripe_width, h); + + for (i = 0; i < h; i += 8) { + g.setColor(0.15, 0.15, 0.15); + g.fillRect(0, i, g.getWidth(), i + 3); + g.setColor(0.4, 0.4, 0.4); + g.fillRect(stripe_pos, i, stripe_pos + stripe_width, i + 3); + g.fillRect(stripe2_pos, i, stripe2_pos + stripe_width, i + 3); + } + } else { + var whitecolor = false; + if (curkana.indexOf('A') != -1) { + g.setColor(1, 0, 0); + whitecolor = true; + } else if (curkana.indexOf('I') != -1) { + g.setColor(0, 1, 0); + } else if (curkana.indexOf('U') != -1) { + g.setColor(0, 0, 1); + whitecolor = true; + } else if (curkana.indexOf('E') != -1) { + g.setColor(1, 1, 0); + } else { + g.setColor(0, 1, 1); + } + g.fillRect(0, 0, w, h); + } + + // GOOD FONT SIZE g.setFont("Vector", 62); + g.setFont('Vector', 50); + const bignumbers = false; + if (bignumbers) { + g.setColor(1, 1, 1); + g.drawString(hhmm, 12, 12); + g.setColor(0, 0, 0); + g.drawString(hhmm, 10, 10); + } else { + if (whitecolor) { + g.setColor(0, 0, 0); + } else { + g.setColor(0.5, 0.5, 0.5); + } + if (clocktop) { + x = 26; y = 26; + } else { + x = 26; y = h - 42; + } + g.drawString(hhmm, x - 3, y - 3); + if (whitecolor) { + g.setColor(1, 1, 1); + } else { + g.setColor(0, 0, 0); + } + g.drawString(hhmm, x, y - 1); + } + // drawKana(hira_a, 0, 60); + drawKana(hiragana.KA, g.getWidth() / 6, 60); + Bangle.drawWidgets(); +} +function drawKana (img, x, y) { + g.setColor(0, 0, 0); + + // g.fillRect(0,0,g.getWidth(), h); + if (clocktop) { + g.fillRect(0, h / 2.5, g.getWidth(), h); + } else { + g.fillRect(0, 0, g.getWidth(), 6 * (h / 8) + 1); + } + + if (false) { + g.drawImage(hira_a, x, y); + g.setColor(1, 1, 1); + g.setFont('Vector', 30); + g.drawString(curkana, x + 32, y + 4); + } else { + if (clocktop) { + g.setColor(1, 1, 1); + g.drawImage(kana, x + 8, y + 12, { scale: 3.4 }); + g.setColor(1, 1, 1); + g.setFont('Vector', 30); + g.drawString(curkana, 0, y + 16); + g.drawString(hiramode ? 'H' : 'K', w - 20, y + 16); + } else { + g.setColor(1, 1, 1); + g.drawImage(kana, x + 8, 26, { scale: 3.4 }); + g.setColor(1, 1, 1); + g.setFont('Vector', 30); + g.drawString(curkana, 4, 32); + g.drawString(hiramode ? 'H' : 'K', w - 20, 32); + } + } +} + +var ohhmm = ''; + +function tickWatch () { + const now = Date(); + function zpad (n) { + return (n < 10) ? '0' + n : n; + } + const hhmm = zpad(now.getHours()) + ':' + zpad(now.getMinutes()); + if (hhmm !== ohhmm) { + updateWatch(hhmm); + } +} + +Bangle.on('touch', function (tap, top) { + if (top.y < h / 3) { + // clocktop = !clocktop; + return; + } + if (top.x < w / 4) { + prev(); + } else if (top.x > (w - (w / 4))) { + next(); + } else { + hiramode = !hiramode; + } + kana = hiramode ? hiragana[curkana] : katakana[curkana]; + tickWatch(); +}); + +Bangle.loadWidgets(); +tickWatch(); +setInterval(tickWatch, 1000); + diff --git a/apps/kanawatch/app.png b/apps/kanawatch/app.png new file mode 100644 index 000000000..cf081937b Binary files /dev/null and b/apps/kanawatch/app.png differ diff --git a/apps/kanawatch/metadata.json b/apps/kanawatch/metadata.json new file mode 100644 index 000000000..09bfc2d36 --- /dev/null +++ b/apps/kanawatch/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "kanawatch", + "name": "Kanawatch", + "shortName": "Kanawatch", + "version": "0.01", + "type": "clock", + "description": "Learn Hiragana and Katakana", + "icon": "app.png", + "allow_emulator": true, + "tags": "clock", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "kanawatch.app.js", + "url": "app.js" + }, + { + "name": "kanawatch.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.jpg" + } + ] +} diff --git a/apps/kanawatch/screenshot.jpg b/apps/kanawatch/screenshot.jpg new file mode 100644 index 000000000..ac7447ee8 Binary files /dev/null and b/apps/kanawatch/screenshot.jpg differ diff --git a/apps/messages/lib.js b/apps/messages/lib.js index 3af913f05..90847ccb0 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -111,11 +111,12 @@ function getNotificationImage() { function getFBIcon() { return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA=="); } -/* -* icons should be 24x24px with 1bpp colors and 'Transparency to Color' -* http://www.espruino.com/Image+Converter -*/ -exports.getMessageImage = function (msg) { + +exports.getMessageImage = function(msg) { + /* + * icons should be 24x24px with 1bpp colors and 'Transparency to Color' + * http://www.espruino.com/Image+Converter + */ if (msg.img) return atob(msg.img); var s = (msg.src||"").toLowerCase(); if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA="); @@ -123,16 +124,13 @@ exports.getMessageImage = function (msg) { if (s=="calendar") 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") return getFBIcon(); - if (s=="gmail") return getNotificationImage(); + 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=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA=="); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); 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=="mail") return getNotificationImage(); - if (s=="messenger") return getFBIcon(); 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=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); @@ -140,7 +138,6 @@ exports.getMessageImage = function (msg) { 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=="sms message") return getNotificationImage(); 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=="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=="); @@ -152,10 +149,11 @@ exports.getMessageImage = function (msg) { 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") 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="); - return getNotificationImage(); -} + // if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below) + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); +}; -exports.getMessageImageCol = function (msg,def) { +exports.getMessageImageCol = function(msg,def) { return { // generic colors, using B2-safe colors "alarm": "#fff", @@ -184,6 +182,7 @@ exports.getMessageImageCol = function (msg,def) { "snapchat": "#ff0", "teams": "#464eb8", "telegram": "#0088cc", + "telegram foss": "#0088cc", "threema": "#000", "to do": "#3999e5", "twitch": "#6441A4", @@ -192,4 +191,4 @@ exports.getMessageImageCol = function (msg,def) { "wordfeud": "#e7d3c7", "youtube": "#f00", }[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg); -} +}; diff --git a/apps/messages/settings.js b/apps/messages/settings.js index 44301fff6..1e6609128 100644 --- a/apps/messages/settings.js +++ b/apps/messages/settings.js @@ -1,7 +1,7 @@ (function(back) { function settings() { let settings = require('Storage').readJSON("messages.settings.json", true) || {}; - if (settings.vibrate===undefined) settings.vibrate="."; + if (settings.vibrate===undefined) settings.vibrate=":"; if (settings.repeat===undefined) settings.repeat=4; if (settings.unreadTimeout===undefined) settings.unreadTimeout=60; if (settings.maxMessages===undefined) settings.maxMessages=3; diff --git a/apps/messages/widget.js b/apps/messages/widget.js index fd2506e7a..13eb1c0ef 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -60,10 +60,9 @@ draw:function(recall) { WIDGETS["messages"].width=this.iconwidth * E.clip(msgs.length, 0, settings.maxMessages); WIDGETS["messages"].msgs = msgs; Bangle.drawWidgets(); - if (msgs.length !== 0) Bangle.setLCDPower(1);// turns screen on },buzz:function() { if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode - require("buzz").pattern((require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || "."); + require("buzz").pattern((require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ":"); },touch:function(b,c) { var w=WIDGETS["messages"]; if (!w||!w.width||c.xw.x+w.width||c.yw.y+w.iconwidth) return; diff --git a/apps/multitimer/alarm.js b/apps/multitimer/alarm.js index 97cbaa5fa..eb1b3b259 100644 --- a/apps/multitimer/alarm.js +++ b/apps/multitimer/alarm.js @@ -120,7 +120,7 @@ function showAlarm(alarm) { Bangle.setLocked(false); } - require("buzz").pattern(alarm.vibrate === undefined ? ".." : alarm.vibrate).then(() => { + require("buzz").pattern(alarm.vibrate === undefined ? "::" : alarm.vibrate).then(() => { if (buzzCount--) { setTimeout(buzz, settings.buzzIntervalMillis); } else if (alarm.as) { // auto-snooze diff --git a/apps/noteify/app.js b/apps/noteify/app.js index c19694ea4..2b3ee64f0 100644 --- a/apps/noteify/app.js +++ b/apps/noteify/app.js @@ -5,7 +5,7 @@ var notes = require("Storage").readJSON("noteify.json", true) || []; var alarms = require("sched").getAlarms(); msg = ""; -function startNote(idx) { +function startNote(idx) { idx == undefined ? note = "" : note = notes[idx].note; require("textinput").input({text:note}).then(result => { if (result != "") { @@ -23,20 +23,20 @@ function viewNote(idx) { textY += e.dy; g.setClipRect(0, 30, g.getWidth(), g.getHeight()); if (textY > 30) textY = 30; - if (textY < textBound) textY = textBound; + if (textY < textBound) textY = textBound; g.clearRect(0, 30, g.getWidth(), g.getHeight()).setColor(g.theme.fg).setFont("6x8:2").setFontAlign(-1, -1).drawString(g.wrapString(notes[idx].note, g.getWidth()).join("\n"), 0, textY); },back:()=>{ Bangle.setUI(); showEditMenu(idx); }}); - + } function showMainMenu() { var mainMenu = { "" : { "title" : "Noteify" }, "< Back" : function() { load(); }, - "New note" : function() { + "New note" : function() { E.showMenu(); startNote(); }, @@ -187,7 +187,7 @@ function editAlarm(alarmIndex, alarm) { as : false, dow : 0b1111111, last : 0, - vibrate : ".." + vibrate : "::" }; if (msg != "") a["msg"] = msg; if (!newAlarm) Object.assign(a, alarms[alarmIndex]); @@ -195,7 +195,7 @@ function editAlarm(alarmIndex, alarm) { var t = decodeTime(a.t); var alarmTitle = (a.msg == undefined) ? 'Alarm' : (a.msg.length > 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"..."; - + const menu = { '': { 'title': alarmTitle }, '< Back' : () => showAlarmMenu(), @@ -264,7 +264,7 @@ function editTimer(alarmIndex, alarm) { var t = decodeTime(a.timer); var timerTitle = (a.msg == undefined) ? 'Timer' : (a.msg.length > 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"..."; - + const menu = { '': { 'title': timerTitle }, '< Back' : () => showMainMenu(), diff --git a/apps/novaclock/README.md b/apps/novaclock/README.md new file mode 100644 index 000000000..b54f44241 --- /dev/null +++ b/apps/novaclock/README.md @@ -0,0 +1,9 @@ +# Nova Clock +A simple clock app that uses a clockwork star, from Kirby. + +*Note: This clock draws slightly into the widget area, but since it's in the middle, it shouln't matter that much (nobody has that many widgets... right?)* + +## Credits +Pixel art by me, [dronesflier](https://github.com/dronesflier) + +The Kirby series belongs to Nintendo/HAL Labs diff --git a/apps/novaclock/app-icon.js b/apps/novaclock/app-icon.js new file mode 100644 index 000000000..cf159ba1f --- /dev/null +++ b/apps/novaclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE3N5ouuGFovuFwYwrF9wuFGFIqC5nMF9guBF9ReD43GGFJeDF9ReFGFImCFwYvBGAReqMEAdCAAwvKAA4pWFxYwNGhQoJAAouHYQYAEGBwsIFBIANGRBgLFzIwDYhoweFx4xGGC4uSGDYuFFpqTIF1BhXFzBhVXSZhNF6QuWGAgvSFzAvS4wved6KOsSDovJ5gACF9IsCBIQxFF8ItEAAYxEF7qHDFowxGBwZeaFxgPEAwYvYABAONF74PVF64RbF6IThDZYVnDIoXtAH4A/AH4AkA==")) diff --git a/apps/novaclock/app.js b/apps/novaclock/app.js new file mode 100644 index 000000000..e5bd37b06 --- /dev/null +++ b/apps/novaclock/app.js @@ -0,0 +1,274 @@ +function nova() { + var nova = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHsAABZM/K5fM5kAv2igGi1EAvFUK/6v/K8ipC0SvCAQhM/K5l+AQ5XG5nM1BrBBgJX/V4WoWQRXHKoIYGBBBX2vxOBKIQFCLoRMD1BXHLPpOBK4gFCK4RIDK5IDFK+4AKIwpYFBYuiKfLtBABAPEK5i9IKtg8BAAUPABHMlYADgBXMAwyqrKQOiKA0OAIRWDqwACLAOoWgZQILFg4B0SoHKIYADiEr5moKwgABW4R0BKowUBK1f+VIkOU4QCBToQACJoRTBIgJWDK4YDBWQRVCWFZEBVZBQEAAxNDlbGBBQgABAoguEWE6IBKoxUCIAr8EUIQIDCQUALgOiAAJnCWQavoVgb8BKoeoKoRBBfQZYEBAa9E5mo1FUK4IOEV9JWBVYyUBSoZCBTAJXCLAfMAohgD0V+WITIDLAivkKw6qFTIV+AQKZCKIagDAAl+qgUBV4pYEK8ZWFVgJQBVISvDLIQCCIQigELwQHB0V4vCuCC4IPEK8ZWGKoSvDAAhdC5pYEq3MWgQOCOgJbD1AGBPoZrCgGoK075CHoQAELQi6C0RXCLAKhEDIwADYgYqCK0xGBKoStGBAKaCLAbxDTgRVC5nNCIN+DIQRDCogSBK8ZWBKopPDAIJZDBgZYFUIS8EvwPCAoKvENgZXeKwpJDABxlFK4ZMBK4ZmCAQXNWoJYDYghWk64AEh0Oh8OhAKFLAmiHwVO0RVBvFUBoQAEBgKyBKwVWLDxXGKwpSBKoQOBLIxYE5oFD0V+VQKyCAIIFBV4IABLIZXCqxXaKwcOVw5VF1gIBA4JXGIoUAACitCWDhXCEgJWGJoIABGYYKELApXBDIpuCh4GB1gACBYIICDIJXFlaubLAJXFHgJWFLASyDWAwZFhCmGaghXKWC5XDVw8OHwI9HK4xYCDIoYHDIZ8BDIhXjdgi4DAAxWDdwrIFK5KwOAoJWYgCvHJAMOHpJZCSwZXGJIIZIB4MHa4JXELIxLJVyBXFFwKVJLIavKhBxJDASvFKwpXJgGoK6GiK4pYBK5QMBK5UHDJgNBK4tWK5cA0SvXdoQuDK8ANCDIZSBK4YOBJI9+K5RWHHoiwBF4OsKxo9B1BXEBoRWJP4hXDAApOGV5gMBK4eiK4I+EK4RYHBQMOhyuE1ByBAA6cDAQQNJK5pCBV547BWBBZBSYsIMYZXDDIQfB1B5BAYIlDAAXN5wECNoSuHK45YCK56vDLApOBAIYCBdIJWILAYABEQRTD5uoqmi5otCTYJWIYJKvUK4vXBwIACKoRWEK5RZHWoRXBAgRXHqxXCZwQADAwKvRFAKwGLIwKFKAZzDWAt+BYJbC5oDBWoIRBK5eivF+AAl4K540BQIQ+CAB5VCIQL7CeoKrFAQS1EB4YeCLQwvJK5kOAYItBbomiJIRgEforfBN4IYBUoIEC5pjDL4K9FBgQsBb4ZYGJw4vBK54uDFQRMFLwIADVgj+DUAV4IoqxEBAmiQgRgFLQZXGgF+V5sPUghKCeALiEAAr3GKoQCBVAKxBBYQZKFYLCBQ4xXJRYJXPIII4BLgRYEABCRDDIZEBUoYECDZd45qGFgA9BAAJLH1BXOS4gBBdwTZBHwwGEHAKuCAoQMELAZgDJQLeDCgKEFK4MPAYJMIK5JYDh0AdAZCCFILlE0QABGgI1CCQiXBJYYRBVw5YDEgYsCKx4AMK4UIUgeoE4StBJYRBDRYiSCKIacCKY5MFCoUAbQKuSK56vBRIpWCKQYADBAZRCKYRKDJILPCOIQABvwDCBYSxBZQRtDK7cQDYKwCFAOiFoL5DAQQAIIgIDEKIZoDAAJxDAYJeBgAHCCQRWCK66wDK4KVDK4JSDGoKYCSwShGfggcBBYahCMAYdCvALBGIOiAoZWZK4qvBJwIhBTIRfCTwb4EdAQOCYoLAKa4QkBvx2BvCDEK8CwCIQQEBGAKbBAARBEewRaCL4JXDXQmoUALGDPAgrCKwpXZLAcIEwZYDKopZEWYgBBTANUgF4vwYEDYqzCNgR7EgEOKzSwELAjyDRIIxBHoiwCWoZWCegRpFCwgUDFIQoCKwZXbLAYhBLAeip0Ac4Y3CK4YGB0V+NIKqB5gCBLoJQFVgQACVoIVB0QRBgBVBKziwEK4ifDWYXNfoKYDUwIACqheDlUrCwZ4DOYQKC1EqvxYBFQKueLBd+qkqqhODAAl4p1OSwXNLAVUAIMqCo4SBp1UB4N4WgKufLBg1BAAIzBAolO1F4TwSkDLAb9BJoN+DIJSCB4ICCVoJWiK4pYF5oyBKgQDDJ4RTCAoRcECQV+BId+CAQjBKIMPhxXiLBWoAASrBAAIEBMgZIBLohICJ4JWBAgN4VwRwDgBVCh+iK0CxMKIQCCVIhbEAAYFCUoQYCCYmoVsxYIh0ALIg+DWoKhBUwRHC0QKBW4IKEAAqmBKwMPiBWmWI6yFAAq1BV4YKGNYYAFKoQACK05YJLJCnBJZBVKKwMOK1hYDLJ2iUwJPH5pVIAAJXBBARWpLAX+LApZCLQqxCLIvNK4YVCDQMIVoXMFIQAsJwZZFLQZbDLAKqFBwYVChEIKoQMCK1qyLLY4AGfwZUBVYSsxWRJaKABy5EK2ZZELRKiCUYZUKKu5aG/y0OKYXMCwYA/WgoAE0QEDB4RV/LRQAJJn4A/AH4A/AFo")); + return nova; +} + +function novaEyesStage1() { + var novaEyesStage1 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5gAFH58rDIXN5movwZRKs+oAIg/OgEr5mi1Go0V+AYIFCLN7oGABIZiK0fXAAkOh0Ph0IBQo+HDI0PhEHhEPDJpWoKQJVCh5ZHHwoZFCgcPg50BLIpYpHohVF1hFChxXODAQACL4hXsHgwABgAACBQg+HDIhuChAZEOYJYtJYkIKwo+EBoJXKJYIABDIyxGK8yUFSYpXKHwTIFKw4NFWFKUGHg0AKwaWGDIrHGWGA9QLIUIK5LJBDJAaDK9o8BhA9JHwSvLK5QQBhEOK9qVLgAMBK5bJJAAMHK9yuBJIRXWh78BDJBWBDIpXnWAIvB1hWNK4xYCOJK7BDIxYoGAZYGBQMOdg6wFBgZWFg7IGK9JYCGQI8FdYpXILAMIDI7IILFZOBAIYCBIoI8KOQ4VDYwRWtHorwCAAQ/DBohXKLAZZCLYQZKLFRZFBQo8HDJLLBDJpYlAB4ZiLE3M5moAQIAFHhgZD5oaCDofNK14/E1Go0QADHaBZDKwQBBKuTyKDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4A==")); + return novaEyesStage1; +} + +function novaEyesStage0() { + var novaEyesStage0 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj64AFH58rDLBVrH6EAlYZXK0Y7KH4gZiK1MOh0Ph0IHxoZGh8Ig8Ih5YwHgpSBKoUPLI4+FDIoUDh8HOgJZFLFI9EKousIoUOK5wYCAARfEK9g8GAAMAAAQKEHw4ZENwUIDIhzBLFpLEhBWFHwgNBK5RLBAAIZGWIxXmSgqTFK5Q+CZApWHBoqwpSgw8GgBWDSwwZFY4ywwHqBZChBXJZIIZIDQZXtHgMIHpI+CV5ZXKCAMIhxXtSpcABgJXLZJIABg5XuVwJJCK60PfgIZIKwIZFK86wBF4OsKxpXGLARxJXYIZGLFAwDLAwKBhzsHWAoMDKwsHZAxXpLAQyBHgrrFK5BYBhAZHZBBYrJwIBDAQJFBHhRyHCobGCK1o9FeAQACH4YNEK5RYDLIRbCDJRYqLIoKFHg4ZJZYIZNLFYAIHhIZZLEo/LBgIZkLNw7QDLJZnAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA")); + return novaEyesStage0; +} + +function novaEyesStage2() { + var novaEyesStage2 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCDwZYqKwKoGLowFDHwpWCDIRRBAQPMAwd+vANBWNRXBJASoDeRKWGDIoEBOoQDDDAQeBK9CUBJQiTCAAq2CB4Q+DDITGFCAZ2DOohYngAAVDLhWj64ACh0PhA0G6+sBoMPCQY+BgAKCBYMOhwZHBoYZFK88PAAKMHKwYABK4oZEKw5YEDIxXpHpRZChBXJZIIZIDQZXtHgMIHpI+CV5ZXKCAMIhxXtSpcABgJXLZJIABg5XuVwJJCK60PfgIZIKwIZFK86wBF4OsgErKxZXGLARxJXYIZGLEoAVDLhXk1AAC5mo0QGDABJXEBIgaBDYfMAA4ZELEouEHgIALHgsABAN+5hUB0R1CDJywlRIvNAQYECMgZXGY4ZXBCAIDCKQTIILFAAISYo8IOQYUDAoQDCBQRWrHwQABcoxdFHhJyCC4i3DDoWiK1hYDRgSQCAAgMBDJ3NDQJWE5oZMLM6ZGHaBZDOgQBBKuQ/FAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA")); + return novaEyesStage2; +} + +function novaEyesStage3() { + var novaEyesStage3 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCBQV+LFRWBVAxdGAoY+FKwQZCJgICB5hZCvF+qgFBWNRXBJASoDeROiK4ytDCIOoC4R5DAYQIBK9A8BGIY2EABQ+DZARRBUoJVCDYzTELE49CVgo7KHosABoocFZYgBDK8yuGABJfGHwIZQLYQcDLEsAAC4ZaK8nXAAMPh8OGhHX1gPBhATCK4QZDh0PJ5IaCh4ZEK848BhCOKHwI9FOIpXKCAMIhxXpdrAZZK8mo1Gi0V+AIYABBAOiBoIAEBAJXCAwQYDDIwAFDYRXoJAozCAYRDCAAN4I4RXCJAoFELBIdBK8o+DGoyzDKQKcDHgpyBYwquECoYMCAQJWmWAgvCUYadHdgzKF5jMCAITTD5gIDK84+B5gABG4SZDd5A8FZQZLDLQIWGDJCwlLAQAC5oCDAgQABVwoZEKIQeCAYTGFDI5YmcogEEAAg8IOQYUDAoQDCBQRWrHwQABJoV+SQ48KOQRpEW4YdC0RWsLAaMCAIQAEBgIZO5oaCKwfNDJhZnTIw7QLIZ0EKuQ/FAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA==")); + return novaEyesStage3; +} + +function novaEyesStage4() { + var novaEyesStage4 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCBQV+LFRWBVAxdGAoY+FKwQZCJgICB5hZCvF+qgFBWNRXBJASoDeROiK4ytDCIOoC4R5DAYQIBK9EAJQg2EBAoFEHwauBUIN4p1UKoQbGaYhYnK4KvCKoQFBHYi6EHosrBQQaCYwRbBDQd4XYSwoKwIuB5oACIAJBBAYpMDAAI+BKwIKDDAJKBvC2BEgIUDEwawmdgIvDGIhVDLYYAEHoIZBVIQPBKwJeBZoLJEXgYHBK8wzBHISMF5xdFK45GCMwQDBNYonBBAN+XAV+K8wAPlYHGDKIAHV8ztCqiHBWo6gEdgOoK4SpEAgQODDwIjBAAIjBCIRXm5g1CHQQCDHgJWCAgIAD0RXDDIPM5oRC5oECDYR+CK9aVES4ZQFHQShEK4yvEAAbJGAARXlHwI9BJYIAEAwLsEql4vwBBHgZYCCQRrEEYgMDAQJWmWAgvCJQJaDUQQ8DVwYZFVAS9CAITED5gIDK84+BcIQ3CTIa5HKwjKFJYZaBCwwZIWEpYCAAXNAQYECAAJkBK4ysCYYR2DAQIICAAZXpd4zqFAAg8IOQYUDAoQDCBQRWrHwQABJoV+SQ48KOQRpEW4YdC0RWsLAaMCAIQAEBgIZO5oaCKwfNDJhZnTIw7QLIZ0EKuQ/FAAgZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA==")); + return novaEyesStage4; + +} + +function novaEyesWhiteStage0() { + var novaEyesWhiteStage0 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj64AFH58rDLBVrH6EAlYZXK0Y7KH4gZiK2Q+JDLJW0Hw4ZZK/5X6HiY+FDLJX/K/Q8VHwYZZK/5X/K/5X/K/5X/K/5X/K+Y+WHgYZZK/5X7Hyg8FDLJX/K/Y+SHg4ZZLGg8JDLJYlH5YMBDMhZuHaAZZLM4AEDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4A=")); + return novaEyesWhiteStage0; +} + +function novaEyesTransStage1() { + var novaEyesTransStage1 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5gAFH58rDIXN5movwZRKs+oAIg/OgEr5mi1Go0V+AYIFCLN7oGABIZiK0YRYDLJW0CY4ZZK/5X6FCoVDDLJX/K/QmXC4IZZK/5X/K/5X/K/5X/K/5X/K+YmWCoYZZK/5X7FCgTFDLJX/K/YqSCI4ZZLEoAPDMRYm5nM1ACBAAo8MDIfNDQQdD5pWvH4mo1GiAAY7QLIZWCAIJVyeRQZsAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AfA")); + return novaEyesTransStage1; +} + +function novaEyesTransStage2() { + var novaEyesTransStage2 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCDwZYqKwKoGLowFDHwpWCDIRRBAQPMAwd+vANBWNRXBJASoDeRKWGDIoEBOoQDDDAQeBK9CUBJQiTCAAq2CB4Q+DDITGFCAZ2DOohYngAAVDLhWjC7AZZK/5X/K/5X/K/5X/K/5X/K+YmBACoZcK8moAAXM1GiAwYAJK4gJEDQIbD5gAHDIhYlFwg8BABY8FgAIBv3MKgOiOoQZOWEqJF5oCDAgRkDK4zHDK4IQBAYRSCZBBYoABCTFHhByDCgYFCAYQKCK1Y+CAALlGLoo8JOQQXEW4YdC0RWsLAaMCSAQAEBgIZO5oaBKwnNDJhZnTIw7QLIZ0CAIJVyH4oAEDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4A==")); + return novaEyesTransStage2; +} + +function novaEyesTransStage3() { + var novaEyesTransStage3 = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4ArgAAFDNhVj5nM1ABC5g/PlYQB1AYCvwZRKsxWE0RZPBgOiv2iAQNUAYIACLN6RBHYQAETQKcDHxMAvwACLIYaEOYRWtGQYAD1BAELBKsBBgRRCWIQBCBQV+LFRWBVAxdGAoY+FKwQZCJgICB5hZCvF+qgFBWNRXBJASoDeROiK4ytDCIOoC4R5DAYQIBK9A8BGIY2EABQ+DZARRBUoJVCDYzTELE49CVgo7KHosABoocFZYgBDK8yuGABJfGHwIZQLYQcDLEsAAC4ZaK8gXYDLJX/K/jtYDLJXk1Go0WivwBDAAIIB0QNBAAgIBK4QGCDAYZGAAobCK9BIFGYQDCIYQABvBHCK4RIFAohYJDoJXlHwY1GWYZSBTgY8FOQLGFVwgVDBgQCBK0ywEF4SjDTo7sGZQvMZgQBCaYfMBAZXnHwPMAAI3CTIbvIHgrKDJYZaBCwwZIWEpYCAAXNAQYECAAKuFDIhRCDwQDCYwoZHLEzlEAggAEHhByDCgYFCAYQKCK1Y+CAAJNCvySHHhRyCNIi3DDoWiK1hYDRgQBCAAgMBDJ3NDQRWD5oZMLM6ZGHaBZDOghVyH4oAEDNgA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AD4=")); + return novaEyesTransStage3; +} + +function novaTopRedraw() { + var novaTopRedraw = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHsAABZM/K5fM5kAv2igGi1EAvFUK/6v/K8isDAQV+BAK4BJn5XL1BOB1GoLod4LQIRE0XM5vM1HN5pX/5hXBIwJdGKwgYGA45X3TgJRDLQZXFAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AB8AIH4A/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/AH4AP")); + return novaTopRedraw; +} + +function star() { + var backgroundstar = require("heatshrink").decompress(atob("rFYxH+AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A9qxA/K/5W/AH5XGLH6w/K34A/AA2BwJB/K62sIP5XWIH4AW1iv/K/5X/AHFWqwCCq2BJ4ICB1mBAAJZBAQIQBlYUCAYMrAgJZ8KQICBU4QCBLIJVBK4QOCNAJSCOQQA/XpQA/K6q3CAH5XUwJB/ACqu/V7BA/AC2BwJB/LC5A/V/4AtqxX/AC2slZB/ACuBqxB/LC5A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4Ao")); + return backgroundstar; +} + +function novaOpenEyes(speed, white, animation) { + if (!white) { + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + setTimeout(function() { + g.drawImage(novaEyesStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + g.drawImage(novaEyesStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + g.drawImage(novaEyesStage1(), -10, -10, { + scale: 2.2 + }); + }, speed * 4); + if (animation) { + setTimeout(function() { + g.drawImage(novaEyesStage0(), -10, -10, { + scale: 2.2 + }); + }, speed * 5); + } else {} + } else { + + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + timedraw(true); + g.drawImage(novaEyesTransStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + timedraw(true); + g.drawImage(novaEyesTransStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + timedraw(true); + g.drawImage(novaEyesTransStage1(), -10, -10, { + scale: 2.2 + }); + open = true; + }, speed * 4); + if (animation) { + setTimeout(function() { + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + open = true; + }, speed * 5); + } else {} + } +} + +function novaCloseEyes(speed, white, animation) { + if (!white) { // for other + if (animation) { + g.drawImage(novaEyesStage0(), -10, -10, { + scale: 2.2 + }); + } else {} + setTimeout(function() { + g.drawImage(novaEyesStage1(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + g.drawImage(novaEyesStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + g.drawImage(novaEyesStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 4); + setTimeout(function() { + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + }, speed * 5); + } else { // for time + + if (animation) { + timedraw(true); + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + } else {} + setTimeout(function() { + timedraw(true); + g.drawImage(novaEyesTransStage1(), -10, -10, { + scale: 2.2 + }); + }, speed * 2); + setTimeout(function() { + timedraw(true); + g.drawImage(novaEyesTransStage2(), -10, -10, { + scale: 2.2 + }); + }, speed * 3); + setTimeout(function() { + timedraw(true); + g.drawImage(novaEyesTransStage3(), -10, -10, { + scale: 2.2 + }); + }, speed * 4); + setTimeout(function() { + g.drawImage(novaEyesStage4(), -10, -10, { + scale: 2.2 + }); + }, speed * 5); + open = false; + } +} + +function timedraw(animation) { + if (open && timemode || animation) { + g.setFont("6x8", 4); + g.setColor("#00F"); + var d = new Date(); + var h = d.getHours(), + m = d.getMinutes(); + g.drawImage(novaEyesWhiteStage0(), -10, -10, { + scale: 2.2 + }); + g.drawImage(novaEyesTransStage1(), -10, -10, { + scale: 2.2 + }); + // Check if single digit + var hourDigits = h.toString(); + if (hourDigits.length === 1) { // if hour digits only one, render in middle + g.drawString(h, 50, 66); + } else { + g.drawString(h, 38, 66); + } + var minutes = m.toString(); + if (minutes.length === 1) { // same for mins + g.drawString(m, 107, 66); + } else { + g.drawString(m, 94, 66); + } + } +} + +function main() { + Bangle.on("lock", function(lock) { + g.drawImage(novaTopRedraw(), -10, novaYPos, { + scale: 2.2 + }); + if (lock) { + novaCloseEyes(200, true, false); + setTimeout(function() { + novaOpenEyes(100, false, false); + timemode = false; + }, 1200); + }else{ + novaCloseEyes(100, false, false); + setTimeout(function() { + timemode = true; + novaOpenEyes(200, true, false); + }, 600); + } + }); +} + + +g.setFont("6x8", 4); +g.setColor("#FFF"); +var open = false; +var timemode = true; +var clockmode; +var novaYPos = -7; +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +g.drawImage(nova(), -10, -10, { + scale: 2.2 +}); +Bangle.setUI("clock"); + +g.drawImage(star(), 5, -5, {scale:0.8}); +g.drawImage(star(), -10, 120, {scale:0.8}); +g.drawImage(star(), 120, -5, {scale:0.8}); + + + +var secondInterval = setInterval(function() { + timedraw(); + g.drawImage(novaTopRedraw(), -10, novaYPos, { + scale: 2.2 + }); +}, 1000); + +novaOpenEyes(300, true, false); +main(); diff --git a/apps/novaclock/app.png b/apps/novaclock/app.png new file mode 100644 index 000000000..6270be5d3 Binary files /dev/null and b/apps/novaclock/app.png differ diff --git a/apps/novaclock/metadata.json b/apps/novaclock/metadata.json new file mode 100644 index 000000000..c1dad60a1 --- /dev/null +++ b/apps/novaclock/metadata.json @@ -0,0 +1,15 @@ +{ "id": "novaclock", + "name": "Nova Clock", + "shortName":"Nova Clock", + "icon": "app.png", + "type": "clock", + "version":"0.1", + "description": "A clock inspired by the Kirby series", + "tags": "clock", + "supports": ["BANGLEJS2"], + "readme":"README.md", + "storage": [ + {"name":"novaclock.app.js","url":"app.js"}, + {"name":"novaclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/pongclock/ChangeLog b/apps/pongclock/ChangeLog index 7b83706bf..9b83b345f 100644 --- a/apps/pongclock/ChangeLog +++ b/apps/pongclock/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: added missing type i metadata diff --git a/apps/pongclock/metadata.json b/apps/pongclock/metadata.json index dbd044517..c714e9a10 100644 --- a/apps/pongclock/metadata.json +++ b/apps/pongclock/metadata.json @@ -2,8 +2,9 @@ "name": "Pong Clock", "shortName":"Pong Clock", "icon": "pongclock.png", - "version":"0.01", + "version":"0.02", "description": "A Pong playing clock", + "type": "clock", "tags": "", "allow_emulator":true, "supports": ["BANGLEJS", "BANGLEJS2"], diff --git a/apps/qrcode/ChangeLog b/apps/qrcode/ChangeLog index 6d9cc0569..52eadbcf9 100644 --- a/apps/qrcode/ChangeLog +++ b/apps/qrcode/ChangeLog @@ -3,3 +3,4 @@ 0.03: Forces integer scaling and adds more configuration (error correction, description, display) 0.04: Allow scanning of QR codes from camera or file 0.05: Change brightness on touch +0.06: Add ability to generate contact info (MeCard format) QR code diff --git a/apps/qrcode/custom.html b/apps/qrcode/custom.html index 7ae3eb3af..9955ea6c9 100644 --- a/apps/qrcode/custom.html +++ b/apps/qrcode/custom.html @@ -8,6 +8,8 @@

+ +

@@ -64,6 +66,14 @@ +
+

First Name:

+

Last Name:

+

Phone Number:

+

Email:

+

Website:

+
+

Try your QR Code:

@@ -156,7 +166,7 @@ function toggleVis(id){ console.info("Got id", id); - ["srcScanFile", "srcText", "srcWifi", "srcScanCam"].forEach(function (item){ + ["srcScanFile", "srcText", "srcWifi", "srcScanCam", "srcMeCard"].forEach(function (item){ document.getElementById(item).style.display = "none"; }); if (id != undefined && id != null) document.getElementById(id).style.display = "block"; @@ -188,6 +198,37 @@ } return qrstring; } + + function generateMeCardString(meNameFirst, meNameLast, mePhoneNumber, meEmail, meWebsite){ + var meCardStringOutput = 'MECARD:'; + + //first & Last name part of string, can have one or both + if (meNameFirst.trim().length != 0 && meNameLast.trim().length != 0) { + meCardStringOutput += 'N:'+meNameLast.trim()+','+meNameFirst.trim()+';'; + } + else if (meNameLast.trim().length != 0) { + meCardStringOutput += 'N:'+meNameLast.trim()+';'; + } + else if (meNameFirst.trim().length != 0) { + meCardStringOutput += 'N:'+meNameFirst.trim()+';'; + } + + if (mePhoneNumber.trim().length != 0) { + meCardStringOutput += 'TEL:'+mePhoneNumber.trim()+';'; + } + + if (meEmail.trim().length != 0) { + meCardStringOutput += 'EMAIL:'+meEmail.trim()+';'; + } + + if (meWebsite.trim().length != 0) { + meCardStringOutput += 'URL:'+meWebsite.trim()+';'; + } + + meCardStringOutput += ';'; + return meCardStringOutput; + } + function refreshQRCode(){ if (qrcode == null){ qrcode = new QRCode("qrcode", { @@ -206,6 +247,14 @@ const hidden = document.getElementById("hidden").checked; const wifiString = generateWifiString(ssid, password, hidden, encryption); qrText= wifiString; + } else if (document.getElementById("useMECARD").checked) { + const meNameFirst = document.getElementById("meNameFirst").value; + const meNameLast = document.getElementById("meNameLast").value; + const mePhoneNumber = document.getElementById("mePhoneNumber").value; + const meEmail = document.getElementById("meEmail").value; + const meWebsite = document.getElementById("meWebsite").value; + const meCardString = generateMeCardString(meNameFirst, meNameLast, mePhoneNumber, meEmail, meWebsite); + qrText = meCardString; } else if (document.getElementById("useCAM").checked) { qrText= document.getElementById("camQrResult").innerText; } else if (document.getElementById("useFILE").checked) { @@ -258,6 +307,14 @@ } document.getElementById("useTEXT").addEventListener("change",function(){toggleVis("srcText");}); + + document.getElementById("useMECARD").addEventListener("change",function(){toggleVis("srcMeCard");}); + document.getElementById("meNameFirst").addEventListener("change",refreshQRCode); + document.getElementById("meNameLast").addEventListener("change",refreshQRCode); + document.getElementById("mePhoneNumber").addEventListener("change",refreshQRCode); + document.getElementById("meEmail").addEventListener("change",refreshQRCode); + document.getElementById("meWebsite").addEventListener("change",refreshQRCode); + document.getElementById("useCAM").addEventListener("change",function(){ initQrScanner(); initQrCam(); @@ -314,7 +371,6 @@ g.setColor(1,1,1); }); - document.getElementById('camList').addEventListener('change', event => { scanner.setCamera(event.target.value).then(updateFlashAvailability); }); diff --git a/apps/qrcode/metadata.json b/apps/qrcode/metadata.json index 22f8f7b53..24af7b813 100644 --- a/apps/qrcode/metadata.json +++ b/apps/qrcode/metadata.json @@ -1,7 +1,7 @@ { "id": "qrcode", "name": "Custom QR Code", - "version": "0.05", + "version": "0.06", "description": "Use this to upload a customised QR code to Bangle.js", "icon": "app.png", "tags": "qrcode", diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index 4b415c1c5..4e2e76484 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -6,3 +6,4 @@ 0.06: Add 12h support and autocycle control 0.07: added localization, removed deprecated code 0.08: removed unused font, fix autocycle, imported suncalc and trimmed, removed pedometer dependency, "tap to cycle" setting +0.09: fix battery icon size \ No newline at end of file diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index e28c67784..ec7650f53 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,7 +2,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.08", + "version": "0.09", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "readme": "README.md", "icon": "rebble.png", diff --git a/apps/rebble/rebble.app.js b/apps/rebble/rebble.app.js index 8ba61f818..fc91fe0ac 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -236,7 +236,7 @@ function drawBattery(x,y,wi,hi) { g.clearRect(x+2,y+2+2,x+wi-4-2,y+2+hi-2); // centre g.setColor(g.theme.fg); g.fillRect(x+wi-3,y+2+(((hi - 1)/2)-1),x+wi-2,y+2+(((hi - 1)/2)-1)+4); // contact - g.fillRect(x+3, y+5, x +4 + E.getBattery()*(wi-12)/100, y+hi-1); // the level + g.fillRect(x+3, y+5, x +3 + E.getBattery()*(wi-10)/100, y+hi-1); // the level if( Bangle.isCharging() ) { diff --git a/apps/rgb/app.js b/apps/rgb/app.js index aa18d93ae..373ee6f54 100644 --- a/apps/rgb/app.js +++ b/apps/rgb/app.js @@ -1,4 +1,5 @@ const rgb = [0, 0, 0]; +const hex = '0123456789ABCDEF'; const w = g.getWidth(); const h = g.getHeight(); function drawToggle (value, x, y, options) { diff --git a/apps/sched/settings.js b/apps/sched/settings.js index a2b3a5241..b73cd41d1 100644 --- a/apps/sched/settings.js +++ b/apps/sched/settings.js @@ -4,11 +4,10 @@ E.showMenu({ "": { "title": /*LANG*/"Scheduler" }, - /*LANG*/"< Back": () => back(), + "< Back": () => back(), /*LANG*/"Unlock at Buzz": { value: settings.unlockAtBuzz, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: v => { settings.unlockAtBuzz = v; require("sched").setSettings(settings); @@ -17,7 +16,6 @@ /*LANG*/"Default Auto Snooze": { value: settings.defaultAutoSnooze, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: v => { settings.defaultAutoSnooze = v; require("sched").setSettings(settings); @@ -38,7 +36,6 @@ /*LANG*/"Default Repeat": { value: settings.defaultRepeat, - format: v => v ? /*LANG*/"Yes" : /*LANG*/"No", onchange: v => { settings.defaultRepeat = v; require("sched").setSettings(settings); diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index 208058472..6bf296342 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -7,3 +7,7 @@ use Layout library and display ETA 0.07: Add check for day of week 0.08: Update to new time_utils module +0.09: Vibrate with configured pattern + Add setting to defer start of algorithm + Add setting to disable scheduler alarm + diff --git a/apps/sleepphasealarm/README.md b/apps/sleepphasealarm/README.md index c33c9c807..ecb3feb06 100644 --- a/apps/sleepphasealarm/README.md +++ b/apps/sleepphasealarm/README.md @@ -4,10 +4,19 @@ The alarm must be in the next 24h. The display shows: -- the current time -- time of the next alarm or timer -- time difference between current time and alarm time (ETA) -- current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging +- The current time. +- Time of the next alarm or timer. +- Time difference between current time and alarm time (ETA). +- Current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging. State can also be "Deferred", see the "Run before alarm"-option. + +## Settings + +* **Keep alarm enabled** + - Yes: (default) Alert will stay enabled, e.g. for an alarm at 7:00 the clock will buzz at the calculated time from the ESS algorithm (for example 6:45) and again at 7:00. + - No: No action at configured alarm time from scheduler. +* **Run before alarm** + - disabled: (default) The ESS algorithm starts immediately when the application starts. + - 1..23: The ESS algorithm starts the configured time before the alarm. E.g. when set to 1h for an alarm at 7:00 the ESS algorithm will start at 6:00. This improves battery life. ## Logging diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index febc8a259..b19799c4b 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -1,9 +1,18 @@ const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2 +const CONFIGFILE = "sleepphasealarm.json"; const Layout = require("Layout"); const locale = require('locale'); const alarms = require("Storage").readJSON("sched.json",1) || []; -const config = require("Storage").readJSON("sleepphasealarm.json",1) || {logs: []}; +const config = Object.assign({ + logs: [], // array of length 31 with one entry for each day of month + settings: { + startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time + disableAlarm: false, + } +}, require("Storage").readJSON(CONFIGFILE,1) || {}); const active = alarms.filter(a=>a.on); +const schedSettings = require("sched").getSettings(); +let buzzCount = schedSettings.buzzCount; let logs = []; // Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS): @@ -43,7 +52,8 @@ function calc_ess(acc_magn) { } // locate next alarm -var nextAlarm; +var nextAlarmDate; +var nextAlarmConfig; active.forEach(alarm => { const now = new Date(); const time = require("time_utils").decodeTime(alarm.t); @@ -52,8 +62,9 @@ active.forEach(alarm => { dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000)); } if ((alarm.dow >> dateAlarm.getDay()) & 1) { // check valid day of week - if (nextAlarm === undefined || dateAlarm < nextAlarm) { - nextAlarm = dateAlarm; + if (nextAlarmDate === undefined || dateAlarm < nextAlarmDate) { + nextAlarmDate = dateAlarm; + nextAlarmConfig = alarm; } } }); @@ -69,8 +80,8 @@ var layout = new Layout({ }, {lazy:true}); function drawApp() { - var alarmHour = nextAlarm.getHours(); - var alarmMinute = nextAlarm.getMinutes(); + var alarmHour = nextAlarmDate.getHours(); + var alarmMinute = nextAlarmDate.getMinutes(); if (alarmHour < 10) alarmHour = "0" + alarmHour; if (alarmMinute < 10) alarmMinute = "0" + alarmMinute; layout.alarm_date.label = "Alarm at " + alarmHour + ":" + alarmMinute; @@ -80,82 +91,108 @@ function drawApp() { if (Bangle.isLCDOn()) { const now = new Date(); layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2 - const diff = nextAlarm - now; + const diff = nextAlarmDate - now; const diffHour = Math.floor((diff % 86400000) / 3600000).toString(); const diffMinutes = Math.floor(((diff % 86400000) % 3600000) / 60000).toString(); layout.eta.label = "ETA: -"+ diffHour + ":" + diffMinutes.padStart(2, '0'); layout.render(); } + + setTimeout(()=>{ + drawTime(); + }, 1000 - (Date.now() % 1000)); } drawTime(); - setInterval(drawTime, 500); // 2Hz } -var buzzCount = 19; function buzz() { if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence - Bangle.setLCDPower(1); - Bangle.buzz().then(()=>{ - if (buzzCount--) { - setTimeout(buzz, 500); - } else { - // back to main after finish - setTimeout(load, 1000); - } - }); + Bangle.setLCDPower(1); + require("buzz").pattern(nextAlarmConfig.vibrate || ";"); + if (buzzCount--) { + setTimeout(buzz, schedSettings.buzzIntervalMillis); + } else { + // back to main after finish + setTimeout(load, 1000); + } } function addLog(time, type) { logs.push({time: time, type: type}); - require("Storage").writeJSON("sleepphasealarm.json", config); + if (logs.length > 1) { // Do not write if there is only one state + require("Storage").writeJSON(CONFIGFILE, config); + } } // run var minAlarm = new Date(); var measure = true; -if (nextAlarm !== undefined) { - config.logs[nextAlarm.getDate()] = []; // overwrite log on each day of month - logs = config.logs[nextAlarm.getDate()]; +if (nextAlarmDate !== undefined) { + config.logs[nextAlarmDate.getDate()] = []; // overwrite log on each day of month + logs = config.logs[nextAlarmDate.getDate()]; g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); let swest_last; // minimum alert 30 minutes early - minAlarm.setTime(nextAlarm.getTime() - (30*60*1000)); - Bangle.on('accel', (accelData) => { // 12.5Hz - const now = new Date(); - const acc = accelData.mag; - const swest = calc_ess(acc); + minAlarm.setTime(nextAlarmDate.getTime() - (30*60*1000)); + run = () => { + layout.state.label = "Start"; + layout.render(); + Bangle.setOptions({powerSave: false}); // do not dynamically change accelerometer poll interval + Bangle.setPollInterval(80); // 12.5Hz + Bangle.on('accel', (accelData) => { + const now = new Date(); + const acc = accelData.mag; + const swest = calc_ess(acc); - if (swest !== undefined) { - if (Bangle.isLCDOn()) { - layout.state.label = swest ? "Sleep" : "Awake"; - layout.render(); - } - // log - if (swest_last != swest) { - if (swest) { - addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz - } else { - addLog(now, "awake"); + if (swest !== undefined) { + if (Bangle.isLCDOn()) { + layout.state.label = swest ? "Sleep" : "Awake"; + layout.render(); + } + // log + if (swest_last != swest) { + if (swest) { + addLog(new Date(now - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz + } else { + addLog(now, "awake"); + } + swest_last = swest; } - swest_last = swest; } - } - if (now >= nextAlarm) { - // The alarm widget should handle this one - addLog(now, "alarm"); - setTimeout(load, 1000); - } else if (measure && now >= minAlarm && swest === false) { - addLog(now, "alarm"); - buzz(); - measure = false; - } - }); + if (now >= nextAlarmDate) { + // The alarm widget should handle this one + addLog(now, "alarm"); + setTimeout(load, 1000); + } else if (measure && now >= minAlarm && swest_last === false) { + addLog(now, "alarm"); + buzz(); + measure = false; + if (config.settings.disableAlarm) { + // disable alarm for scheduler + nextAlarmConfig.last = now.getDate(); + require("Storage").writeJSON("sched.json", alarms); + } + } + }); + }; drawApp(); + if (config.settings.startBeforeAlarm === 0) { + // Start immediately + run(); + } else { + // defer start + layout.state.label = "Deferred"; + layout.render(); + const diff = nextAlarmDate - Date.now(); + let timeout = diff-config.settings.startBeforeAlarm*60*60*1000; + if (timeout < 0) timeout = 0; + setTimeout(run, timeout); + } } else { E.showMessage('No Alarm'); setTimeout(load, 1000); diff --git a/apps/sleepphasealarm/interface.html b/apps/sleepphasealarm/interface.html index 9a7cb0f93..f45c183e1 100644 --- a/apps/sleepphasealarm/interface.html +++ b/apps/sleepphasealarm/interface.html @@ -1,7 +1,6 @@ -

Please select a wakeup day:

diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json index c74a617ab..6ec5f4180 100644 --- a/apps/sleepphasealarm/metadata.json +++ b/apps/sleepphasealarm/metadata.json @@ -2,7 +2,7 @@ "id": "sleepphasealarm", "name": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm", - "version": "0.08", + "version": "0.09", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "icon": "app.png", "tags": "alarm", @@ -11,6 +11,7 @@ "dependencies": {"scheduler":"type"}, "storage": [ {"name":"sleepphasealarm.app.js","url":"app.js"}, + {"name":"sleepphasealarm.settings.js","url":"settings.js"}, {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"sleepphasealarm.json","storageFile":true}], diff --git a/apps/sleepphasealarm/settings.js b/apps/sleepphasealarm/settings.js new file mode 100644 index 000000000..a79abb598 --- /dev/null +++ b/apps/sleepphasealarm/settings.js @@ -0,0 +1,37 @@ +(function(back) { + const CONFIGFILE = "sleepphasealarm.json"; + // Load settings + const config = Object.assign({ + logs: [], // array of length 31 with one entry for each day of month + settings: { + startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time + disableAlarm: false, + } + }, require("Storage").readJSON(CONFIGFILE,1) || {}); + + function writeSettings() { + require('Storage').writeJSON(CONFIGFILE, config); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "SleepPhaseAlarm" }, + 'Keep alarm enabled': { + value: !!config.settings.disableAlarm, + format: v => v?"No":"Yes", + onchange: v => { + config.settings.disableAlarm = v; + writeSettings(); + } + }, "< Back" : () => back(), + 'Run before alarm': { + format: v => v === 0 ? 'disabled' : v+'h', + value: config.settings.startBeforeAlarm, + min: 0, max: 23, + onchange: v => { + config.settings.startBeforeAlarm = v; + writeSettings(); + } + }, + }); +}) diff --git a/apps/vectorclock/ChangeLog b/apps/vectorclock/ChangeLog index 02831edde..6693f57ec 100644 --- a/apps/vectorclock/ChangeLog +++ b/apps/vectorclock/ChangeLog @@ -6,3 +6,4 @@ 0.06: Redraw widgets when time is updated 0.07: Fix problem with "Bangle.CLOCK": github.com/espruino/BangleApps/issues/1437 0.08: Redraw widgets only once per minute +0.09: Workaround for issue in 2v14 firmware (fix #1959) diff --git a/apps/vectorclock/app.js b/apps/vectorclock/app.js index 663a4c84f..ee3a4ea53 100644 --- a/apps/vectorclock/app.js +++ b/apps/vectorclock/app.js @@ -16,7 +16,7 @@ var commands = []; var showSeconds = true; function pushCommand(command) { - let hash = E.CRC32(E.toJS(arguments)); + var hash = E.CRC32(E.toJS(arguments)); if (!delete rectsToClear[hash]) { commands.push({hash: hash, command: Function.apply.bind(command, null, arguments.slice(1))}); } diff --git a/apps/vectorclock/metadata.json b/apps/vectorclock/metadata.json index 541766fa2..245aad044 100644 --- a/apps/vectorclock/metadata.json +++ b/apps/vectorclock/metadata.json @@ -1,7 +1,7 @@ { "id": "vectorclock", "name": "Vector Clock", - "version": "0.08", + "version": "0.09", "description": "A digital clock that uses the built-in vector font.", "icon": "app.png", "type": "clock", diff --git a/apps/widbaroalarm/ChangeLog b/apps/widbaroalarm/ChangeLog index 5786741c7..86a902605 100644 --- a/apps/widbaroalarm/ChangeLog +++ b/apps/widbaroalarm/ChangeLog @@ -1,3 +1,5 @@ 0.01: Initial version -0.02: Do not warn multiple times for the same exceedance +0.02: Do not warn multiple times for the same exceed 0.03: Fix crash +0.04: Use Prompt with dismiss and pause + Improve barometer value median calculation diff --git a/apps/widbaroalarm/README.md b/apps/widbaroalarm/README.md index fdc239170..59d91ff66 100644 --- a/apps/widbaroalarm/README.md +++ b/apps/widbaroalarm/README.md @@ -15,7 +15,8 @@ Get a notification when the pressure reaches defined thresholds. 0 to disable this alarm. * Show widget: Enable/disable widget visibility * Buzz on alarm: Enable/disable buzzer on alarm - +* Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min +* Pause delay: Same as Dismiss delay but longer (useful for meetings and such). From 30 to 240 min ## Widget The widget shows two rows: pressure value of last measurement and pressure average of the the last three hours. diff --git a/apps/widbaroalarm/default.json b/apps/widbaroalarm/default.json index 3d81baa81..696c70819 100644 --- a/apps/widbaroalarm/default.json +++ b/apps/widbaroalarm/default.json @@ -7,5 +7,7 @@ "drop3halarm": 2, "raise3halarm": 0, "show": true, - "interval": 15 + "interval": 15, + "dismissDelayMin": 15, + "pauseDelayMin": 60 } diff --git a/apps/widbaroalarm/metadata.json b/apps/widbaroalarm/metadata.json index 134f03623..41b8d3e17 100644 --- a/apps/widbaroalarm/metadata.json +++ b/apps/widbaroalarm/metadata.json @@ -2,13 +2,12 @@ "id": "widbaroalarm", "name": "Barometer Alarm Widget", "shortName": "Barometer Alarm", - "version": "0.03", + "version": "0.04", "description": "A widget that can alarm on when the pressure reaches defined thresholds.", "icon": "widget.png", "type": "widget", "tags": "tool,barometer", "supports": ["BANGLEJS2"], - "dependencies": {"notify":"type"}, "readme": "README.md", "storage": [ {"name":"widbaroalarm.wid.js","url":"widget.js"}, diff --git a/apps/widbaroalarm/settings.js b/apps/widbaroalarm/settings.js index bea6319d1..ee8ce82c2 100644 --- a/apps/widbaroalarm/settings.js +++ b/apps/widbaroalarm/settings.js @@ -87,6 +87,26 @@ }, onchange: x => save('buzz', x) }, + 'Dismiss delay': { + value: settings.dismissDelayMin, + min: 5, max: 60, + onchange: v => { + save('dismissDelayMin', v) + }, + format: x => { + return x + " min"; + } + }, + 'Pause delay': { + value: settings.pauseDelayMin, + min: 30, max: 240, + onchange: v => { + save('pauseDelayMin', v) + }, + format: x => { + return x + " min"; + } + }, }; E.showMenu(menu); } diff --git a/apps/widbaroalarm/widget.js b/apps/widbaroalarm/widget.js index 2745db8ad..e1516b6f1 100644 --- a/apps/widbaroalarm/widget.js +++ b/apps/widbaroalarm/widget.js @@ -2,6 +2,7 @@ let medianPressure; let threeHourAvrPressure; let currentPressures = []; + let stop = false; // semaphore const LOG_FILE = "widbaroalarm.log.json"; const SETTINGS_FILE = "widbaroalarm.json"; @@ -32,24 +33,50 @@ let history3 = storage.readJSON(LOG_FILE, true) || []; // history of recent 3 hours - function showAlarm(body, title) { + function showAlarm(body, key) { if (body == undefined) return; + stop = true; - require("notify").show({ - title: title || "Pressure", - body: body, - icon: require("heatshrink").decompress(atob("jEY4cA///gH4/++mkK30kiWC4H8x3BGDmSGgYDCgmSoEAg3bsAIDpAIFkmSpMAm3btgIFDQwIGNQpTYkAIJwAHEgMoCA0JgMEyBnBCAW3KoQQDhu3oAIH5JnDBAW24IIBEYm2EYwACBCIACA")) + E.showPrompt(body, { + title: "Pressure alarm", + buttons: { + "Ok": 1, + "Dismiss": 2, + "Pause": 3 + } + }).then(function(v) { + const tsNow = Math.round(Date.now() / 1000); // seconds + + if (v == 1) { + saveSetting(key, tsNow); + } + if (v == 2) { + // save timestamp of the future so that we do not warn again for the same event until then + saveSetting(key, tsNow + 60 * setting('dismissDelayMin')); + } + if (v == 3) { + // save timestamp of the future so that we do not warn again for the same event until then + saveSetting(key, tsNow + 60 * setting('pauseDelayMin')); + } + stop = false; + load(); }); if (setting("buzz") && !(storage.readJSON('setting.json', 1) || {}).quiet) { Bangle.buzz(); } + + setTimeout(function() { + stop = false; + load(); + }, 20000); } - function didWeAlreadyWarn(key) { - return setting(key) == undefined || setting(key) > 0; + function doWeNeedToWarn(key) { + const tsNow = Math.round(Date.now() / 1000); // seconds + return setting(key) == 0 || setting(key) < tsNow; } function checkForAlarms(pressure) { @@ -77,31 +104,25 @@ if (setting("lowalarm")) { // Is below the alarm threshold? if (pressure <= setting("min")) { - if (!didWeAlreadyWarn("lastLowWarningTs")) { - showAlarm("Pressure low: " + Math.round(pressure) + " hPa"); - saveSetting("lastLowWarningTs", ts); + if (!doWeNeedToWarn("lastLowWarningTs")) { + showAlarm("Pressure low: " + Math.round(pressure) + " hPa", "lastLowWarningTs"); alreadyWarned = true; } } else { saveSetting("lastLowWarningTs", 0); } - } else { - saveSetting("lastLowWarningTs", 0); } if (setting("highalarm")) { // Is above the alarm threshold? if (pressure >= setting("max")) { - if (!didWeAlreadyWarn("lastHighWarningTs")) { - showAlarm("Pressure high: " + Math.round(pressure) + " hPa"); - saveSetting("lastHighWarningTs", ts); + if (doWeNeedToWarn("lastHighWarningTs")) { + showAlarm("Pressure high: " + Math.round(pressure) + " hPa", "lastHighWarningTs"); alreadyWarned = true; } } else { saveSetting("lastHighWarningTs", 0); } - } else { - saveSetting("lastHighWarningTs", 0); } if (history3.length > 0 && !alreadyWarned) { @@ -110,22 +131,22 @@ const raise3halarm = setting("raise3halarm"); if (drop3halarm > 0 || raise3halarm > 0) { // we need at least 30min of data for reliable detection - if (history3[0]["ts"] > ts - (30 * 60)) { + const diffDateAge = Math.abs(history3[0]["ts"] - ts); + if (diffDateAge < 10 * 60) { // todo change to 1800 return; } // Get oldest entry: const oldestPressure = history3[0]["p"]; if (oldestPressure != undefined && oldestPressure > 0) { - const diff = oldestPressure - pressure; + const diffPressure = Math.abs(oldestPressure - pressure); // drop alarm if (drop3halarm > 0 && oldestPressure > pressure) { - if (Math.abs(diff) > drop3halarm) { - if (!didWeAlreadyWarn("lastDropWarningTs")) { - showAlarm((Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h from " + - Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "Pressure drop"); - saveSetting("lastDropWarningTs", ts); + if (diffPressure > drop3halarm) { + if (doWeNeedToWarn("lastDropWarningTs")) { + showAlarm((Math.round(diffPressure * 10) / 10) + " hPa/3h from " + + Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "lastDropWarningTs"); } } else { saveSetting("lastDropWarningTs", 0); @@ -136,11 +157,10 @@ // raise alarm if (raise3halarm > 0 && oldestPressure < pressure) { - if (Math.abs(diff) > raise3halarm) { - if (!didWeAlreadyWarn("lastRaiseWarningTs")) { - showAlarm((Math.round(Math.abs(diff) * 10) / 10) + " hPa/3h from " + - Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "Pressure raise"); - saveSetting("lastRaiseWarningTs", ts); + if (diffPressure > raise3halarm) { + if (doWeNeedToWarn("lastRaiseWarningTs")) { + showAlarm((Math.round(diffPressure * 10) / 10) + " hPa/3h from " + + Math.round(oldestPressure) + " to " + Math.round(pressure) + " hPa", "lastRaiseWarningTs"); } } else { saveSetting("lastRaiseWarningTs", 0); @@ -157,51 +177,52 @@ storage.writeJSON(LOG_FILE, history3); // calculate 3h average for widget - let sum = 0; - for (let i = 0; i < history3.length; i++) { - sum += history3[i]["p"]; - } - threeHourAvrPressure = sum / history3.length; - } - - - - function baroHandler(data) { - if (data) { - const pressure = Math.round(data.pressure); - if (pressure == undefined || pressure <= 0) return; - currentPressures.push(pressure); + if (history3.length > 0) { + let sum = 0; + for (let i = 0; i < history3.length; i++) { + sum += history3[i]["p"]; + } + threeHourAvrPressure = sum / history3.length; + } else { + threeHourAvrPressure = undefined; } } + /* turn on barometer power - take 5 measurements + take multiple measurements sort the results take the middle one (median) turn off barometer power */ function check() { + if (stop) return; + const MEDIANLENGTH = 20; Bangle.setBarometerPower(true, "widbaroalarm"); + Bangle.on('pressure', function(e) { + while (currentPressures.length > MEDIANLENGTH) currentPressures.pop(); + currentPressures.unshift(e.pressure); + median = currentPressures.slice().sort(); + + if (median.length > 10) { + var mid = median.length >> 1; + medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9); + if (medianPressure > 0) { + turnOff(); + checkForAlarms(medianPressure); + } + } + }); + setTimeout(function() { - currentPressures = []; + turnOff(); + }, 10000); + } - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - Bangle.getPressure().then(baroHandler); - - setTimeout(function() { - Bangle.setBarometerPower(false, "widbaroalarm"); - - currentPressures.sort(); - - // take median value - medianPressure = currentPressures[3]; - checkForAlarms(medianPressure); - }, 1000); - }, 500); + function turnOff() { + if (Bangle.isBarometerOn()) + Bangle.setBarometerPower(false, "widbaroalarm"); } function reload() { @@ -209,31 +230,39 @@ } function draw() { - if (global.WIDGETS != undefined && typeof WIDGETS === "object") { - WIDGETS["baroalarm"] = { + if (global.WIDGETS != undefined && typeof global.WIDGETS === "object") { + global.WIDGETS["baroalarm"] = { width: setting("show") ? 24 : 0, reload: reload, area: "tr", draw: draw }; } - g.reset(); - if (setting("show") && medianPressure != undefined) { + if (setting("show")) { g.setFont("6x8", 1).setFontAlign(1, 0); - g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6); + if (medianPressure == undefined) { + check(); + const x = this.x, + y = this.y; + g.drawString("...", x + 24, y + 6); + setTimeout(function() { + g.setFont("6x8", 1).setFontAlign(1, 0); + g.drawString(Math.round(medianPressure), x + 24, y + 6); + }, 10000); + } else { + g.drawString(Math.round(medianPressure), this.x + 24, this.y + 6); + } + if (threeHourAvrPressure != undefined && threeHourAvrPressure > 0) { g.drawString(Math.round(threeHourAvrPressure), this.x + 24, this.y + 6 + 10); } } } - // Let's delay the first check a bit - setTimeout(function() { - check(); - if (interval > 0) { - setInterval(check, interval * 60000); - } - }, 1000); + if (interval > 0) { + setInterval(check, interval * 60000); + } + draw(); })(); diff --git a/apps/wristlight/Icon.png b/apps/wristlight/Icon.png new file mode 100644 index 000000000..0bbcd3e36 Binary files /dev/null and b/apps/wristlight/Icon.png differ diff --git a/apps/wristlight/README.md b/apps/wristlight/README.md new file mode 100644 index 000000000..e69d61b76 --- /dev/null +++ b/apps/wristlight/README.md @@ -0,0 +1,9 @@ +# Wrist Light + +A flash light on your wrist with different colors + +![](screenshot.png) +![](screenshot_red.png) + +## Creator +[@pidajo](https://github.com/pidajo) diff --git a/apps/wristlight/app.js b/apps/wristlight/app.js new file mode 100644 index 000000000..24bc1eab2 --- /dev/null +++ b/apps/wristlight/app.js @@ -0,0 +1,52 @@ +function draw(color) { + if (color == undefined) { + color = -1; + } + g.clear(); + g.setColor(color); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); +} + +function draw2Pattern() { + colors = ["ff0000", "8080ff", "00ff00", + "ffffff"]; + drawPattern(2, colors); +} + +function draw3Pattern() { + colors = ["ff0000", "00ff00", "0000ff", + "ff00ff", "ffffff", "00ffff", + "ffff00", "ff8000", "ff0080"]; + drawPattern(3, colors); +} + +function drawPattern(size, colors) { + g.clear(); + var w = g.getWidth() / size; + var h = g.getHeight() / size; + for (var i = 0; i < size; i++) { + for (var j = 0; j < size; j++) { + var color = colors[i*size + j]; + g.setColor("#" + color); + g.fillRect(j * w, i * h, j * w + w, i * h + h); + } + } + Bangle.on("touch", function(btn, xy) { + var x = parseInt((xy.x) / w); + var y = parseInt((xy.y) / h); + draw("#" + colors[y * size + x]); + }); +} + +// Clear the screen once, at startup +// draw immediately at first +draw3Pattern(); + +/* +require("Storage").write("wristlight.info",{ + "id":"wristlight", + "name":"Wrist Light", + "src":"wristlight.app.js", + "icon":"wristlight.img" +}); +*/ diff --git a/apps/wristlight/metadata.json b/apps/wristlight/metadata.json new file mode 100644 index 000000000..af1d700df --- /dev/null +++ b/apps/wristlight/metadata.json @@ -0,0 +1,16 @@ +{ "id": "wristlight", + "name": "Wrist Light", + "shortName":"Wrist Light", + "icon": "wristlight48.png", + "version":"0.01", + "description": "A flash light with different colors on your wrist", + "tags": "flash,light", + "allow_emulator":true, + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme":"README.md", + "screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot_red.png" } ], + "storage": [ + {"name":"wristlight.app.js","url":"app.js"}, + {"name":"wristlight.img","url":"wristlight-icon.js","evaluate":true} + ] +} diff --git a/apps/wristlight/screenshot.png b/apps/wristlight/screenshot.png new file mode 100644 index 000000000..31850d1ca Binary files /dev/null and b/apps/wristlight/screenshot.png differ diff --git a/apps/wristlight/screenshot_red.png b/apps/wristlight/screenshot_red.png new file mode 100644 index 000000000..06ac7a190 Binary files /dev/null and b/apps/wristlight/screenshot_red.png differ diff --git a/apps/wristlight/wristlight-icon.js b/apps/wristlight/wristlight-icon.js new file mode 100644 index 000000000..689372499 --- /dev/null +++ b/apps/wristlight/wristlight-icon.js @@ -0,0 +1 @@ +atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMzMzMAN3d3d3d3QDu7u7u7gAAAAAADMzMzMzMDd3d3d3d3dDu7u7u7uAAAAAAzMzMzMzMDd3d3d3d3dDu7u7u7u4AAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAMzMzMzMzMDd3d3d3d3dDu7u7u7u7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu7u7u7uwAAAA//8AAAAHd3d3d3cAAAALu7u7u7u7AAD/////AAB3d3d3d3dwAAALu7u7u7u7AA//////8AB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7D/////////B3d3d3d3dwAAALu7u7u7u7A////////zB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AP///////wB3d3d3d3dwAAALu7u7u7u7AA//////8AB3d3d3d3dwAAALu7u7u7u7AAD/////AAB3d3d3d3dwAAAMu7u7u7u8AAACMzMgAAB3d3d3d3dwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALu7u7u7u7AIiIiIiIiACIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAALu7u7u7u7CYiIiIiIiJCIiIiIiIiAAAAAu7u7u7u7CYiIiIiIiJCIiIiIiIgAAAAAq7u7u7u7CYiIiIiIiJCIiIiIiIAAAAAAALu7u7u7CoiIiIiIiKCIiIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") diff --git a/apps/wristlight/wristlight48.png b/apps/wristlight/wristlight48.png new file mode 100644 index 000000000..8f0ff59f7 Binary files /dev/null and b/apps/wristlight/wristlight48.png differ diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 7940e551d..fd8072e06 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -1,4 +1,4 @@ -#!/usr/bin/nodejs +#!/usr/bin/node /* Mashes together a bunch of different apps into a big binary blob. We then store this *inside* the Bangle.js firmware and can use it diff --git a/core b/core index cdab12fcf..c46b4edd2 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit cdab12fcf85bc140e1f5c3f7b15218108c3f7d32 +Subproject commit c46b4edd2052d0df37fea41f8839af8175a78ec9 diff --git a/loader.js b/loader.js index e9887f3d4..0e0fdcba0 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v13"; +var RECOMMENDED_VERSION = "2v14"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles diff --git a/modules/ClockFace.md b/modules/ClockFace.md index 6b59e68e7..b2332c805 100644 --- a/modules/ClockFace.md +++ b/modules/ClockFace.md @@ -174,4 +174,42 @@ Bangle.on('step', function(steps) { if (clock.paused === false) // draw step count }); +``` + + +ClockFace_menu +============== +If your clock comes with a settings menu, you can use this library to easily add +some common options: + +```js + +let settings = require("Storage").readJSON(".settings.json", true)||{}; +function save(key, value) { + settings[key] = value; + require("Storage").writeJSON(".settings.json", settings); +} + +let menu = { + "": {"title": /*LANG*/" Settings"}, +}; +require("ClockFace_menu").addItems(menu, save, { + showDate: settings.showDate, + loadWidgets: settings.loadWidgets, +}); +E.showMenu(menu); + +``` + +Or even simpler, if you just want to use a basic settings file: +```js +let menu = { + "": {"title": /*LANG*/" Settings"}, + /*LANG*/"< Back": back, +}; +require("ClockFace_menu").addSettingsFile(menu, ".settings.json", [ + "showDate", "loadWidgets" +]); +E.showMenu(menu); + ``` \ No newline at end of file diff --git a/modules/ClockFace_menu.js b/modules/ClockFace_menu.js index cd99ea39f..f2267d9ca 100644 --- a/modules/ClockFace_menu.js +++ b/modules/ClockFace_menu.js @@ -1,10 +1,48 @@ -// boolean options, which default to true -exports.showDate = -exports.loadWidgets = - function(value, callback) { - if (value === undefined) value = true; - return { - value: !!value, - onchange: v=>callback(v), - }; - }; +/** + * Add setting items to a menu + * + * @param {object} menu Menu to add items to + * @param {function} callback Callback when value changes + * @param {object} items Menu items to add, with their current value + */ +exports.addItems = function(menu, callback, items) { + Object.keys(items).forEach(key => { + let value = items[key]; + const label = { + showDate:/*LANG*/"Show date", + loadWidgets:/*LANG*/"Load widgets", + }[key]; + switch(key) { + case "showDate": + case "loadWidgets": + // boolean options, which default to true + if (value===undefined) value = true; + menu[label] = { + value: !!value, + onchange: v => callback(key, v), + }; + } + }); +}; + +/** + * Create a basic settings menu for app, reading/writing to settings file + * + * @param {object} menu Menu to add settings to + * @param {string} settingsFile File to read/write settings to/from + * @param {string[]} items List of settings to add + */ +exports.addSettingsFile = function(menu, settingsFile, items) { + let s = require("Storage").readJSON(settingsFile, true) || {}; + + function save(key, value) { + s[key] = value; + require("Storage").writeJSON(settingsFile, s); + } + + let toAdd = {}; + items.forEach(function(key) { + toAdd[key] = s[key]; + }); + exports.addItems(menu, save, toAdd); +}; \ No newline at end of file diff --git a/modules/Layout.js b/modules/Layout.js index 19cfabe11..fd5809a93 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -2,6 +2,14 @@ // See Layout.md for documentation +/* Minify to 'Layout.min.js' by: + + * checking out: https://github.com/espruino/EspruinoDocs + * run: ../EspruinoDocs/bin/minify.js modules/Layout.js modules/Layout.min.js + +*/ + + function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? @@ -71,7 +79,7 @@ function Layout(layout, options) { Layout.prototype.setUI = function() { Bangle.setUI(); // remove all existing input handlers - var uiSet; + let uiSet; if (this.buttons) { // multiple buttons so we'll jus use back/next/select Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{ diff --git a/modules/Layout.min.js b/modules/Layout.min.js new file mode 100644 index 000000000..4523c547c --- /dev/null +++ b/modules/Layout.min.js @@ -0,0 +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;if(2!=process.env.HWVERSION){var 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(this.b=b=this.options.btns,this.physBtns>=b.length){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 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..6872e616c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2168 @@ +{ + "name": "BangleApps", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@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, + "requires": { + "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" + } + }, + "@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, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@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 + }, + "@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", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "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 + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "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 + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "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", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "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 + }, + "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, + "requires": { + "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" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "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 + }, + "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 + }, + "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", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "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", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + }, + "dependencies": { + "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, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "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", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "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", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "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, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "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", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "has": "^1.0.3" + } + }, + "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, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "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", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "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, + "requires": { + "@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" + } + }, + "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, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + } + }, + "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, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "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, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "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, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + } + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "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, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "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, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "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", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "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", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "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 + }, + "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, + "requires": { + "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", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "parent-module": "^1.0.0", + "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", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "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, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "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", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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 + }, + "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", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "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, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "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", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "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, + "requires": { + "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", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "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, + "requires": { + "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", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "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, + "requires": { + "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", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "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 + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "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 + }, + "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", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "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 + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nodemon": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", + "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "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 + }, + "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", + "integrity": "sha512-wAOd0moNX2kSA2FNvt8+7ORwYaJpQ1ZoWjUYdb1bBCxq4nkWuU0IiJa9VpVxrj5Ks+FGXQd62OC/Bjk0aSr+dg==", + "dev": true, + "requires": { + "nodemon": "^2.0.7", + "through2": "^4.0.2" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "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, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "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" + } + }, + "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", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "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 + }, + "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", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "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 + }, + "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", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "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", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "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 + }, + "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", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "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 + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "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", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "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-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" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "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", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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 + }, + "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 + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "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, + "requires": { + "nopt": "~1.0.10" + } + }, + "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, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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 + }, + "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", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "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", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "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", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "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 + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "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/tests/Layout/bin/runtest.sh b/tests/Layout/bin/runtest.sh index e06dec86b..9bac72283 100755 --- a/tests/Layout/bin/runtest.sh +++ b/tests/Layout/bin/runtest.sh @@ -18,24 +18,35 @@ SRCJS=$1 SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp echo "TEST $SRCJS ($SRCBMP)" -cat ../../modules/Layout.js > $TESTJS -echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS -echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS -cat $SRCJS >> $TESTJS || exit 1 -echo 'layout.render()' >> $TESTJS -#echo 'layout.debug()' >> $TESTJS -echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS +run_test () { + LAYOUTFILE=$1 + echo 'exports = {};' > $TESTJS + cat $LAYOUTFILE >> $TESTJS + echo ';' >> $TESTJS + echo 'Layout = exports;' >> $TESTJS + echo 'Bangle = { setUI : function(){}, appRect:{x:0,y:0,w:176,h:176,x2:175,y2:175} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS + echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS + cat $SRCJS >> $TESTJS || exit 1 + echo 'layout.render()' >> $TESTJS + #echo 'layout.debug()' >> $TESTJS + echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS + echo ============================================= + echo TESTING $LAYOUTFILE $SRCJS + bin/espruino $TESTJS || exit 1 + if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1 + then + echo ============================================= + echo $LAYOUTFILE + echo $TESTBMP $SRCBMP differ + echo ============================================== + convert "+append" $TESTBMP $SRCBMP testresult.bmp + display testresult.bmp + exit 1 + else + echo Files are the same + exit 0 + fi +} -bin/espruino $TESTJS || exit 1 -if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1 -then - echo ============================================= - echo $TESTBMP $SRCBMP differ - echo ============================================== - convert "+append" $TESTBMP $SRCBMP testresult.bmp - display testresult.bmp - exit 1 -else - echo Files are the same - exit 0 -fi +run_test ../../modules/Layout.js +run_test ../../modules/Layout.min.js diff --git a/typescript/types/globals.d.ts b/typescript/types/globals.d.ts index e82c3da3d..e2da49a0e 100644 --- a/typescript/types/globals.d.ts +++ b/typescript/types/globals.d.ts @@ -33,7 +33,7 @@ done "heatshrink": "readonly", "Math": "readonly", "Modules": "readonly", "NRF": "readonly", - "Number": "readonly", + "Number": "readonly", "Object": "readonly", "OneWire": "readonly", "Pin": "readonly", @@ -176,8 +176,9 @@ declare type GraphicsApi = { declare const Graphics: GraphicsApi; declare const g: GraphicsApi; +type WidgetArea = 'tl' | 'tr' | 'bl' | 'br'; declare type Widget = { - area: 'tr' | 'tl'; + area: WidgetArea; width: number; draw: (this: { x: number; y: number }) => void; };