diff --git a/apps/bootgatthrm/ChangeLog b/apps/bootgatthrm/ChangeLog new file mode 100644 index 000000000..1e772af29 --- /dev/null +++ b/apps/bootgatthrm/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial release. +0.02: Added compatibility to OpenTracks and added HRM Location \ No newline at end of file diff --git a/apps/bootgatthrm/README.md b/apps/bootgatthrm/README.md new file mode 100644 index 000000000..15bb2b670 --- /dev/null +++ b/apps/bootgatthrm/README.md @@ -0,0 +1,16 @@ +# BLE GATT HRM Service + +Adds the GATT HRM Service to advertise the current HRM over Bluetooth. + +## Usage + +This boot code runs in the background and has no user interface. + +## Creator + +[Another Stranger](https://github.com/anotherstranger) + +## Aknowledgements + +Special thanks to [Jonathan Jefferies](https://github.com/jjok) for creating the +bootgattbat app, which was the inspiration for this App! diff --git a/apps/bootgatthrm/bluetooth.png b/apps/bootgatthrm/bluetooth.png new file mode 100644 index 000000000..1a884a62c Binary files /dev/null and b/apps/bootgatthrm/bluetooth.png differ diff --git a/apps/bootgatthrm/boot.js b/apps/bootgatthrm/boot.js new file mode 100644 index 000000000..aad210f6f --- /dev/null +++ b/apps/bootgatthrm/boot.js @@ -0,0 +1,70 @@ +(() => { + function setupHRMAdvertising() { + /* + * This function prepares BLE heart rate Advertisement. + */ + + NRF.setAdvertising( + { + 0x180d: undefined + }, + { + // We need custom Advertisement settings for Apps like OpenTracks + connectable: true, + discoverable: true, + scannable: true, + whenConnected: true, + } + ); + + NRF.setServices({ + 0x180D: { // heart_rate + 0x2A37: { // heart_rate_measurement + notify: true, + value: [0x06, 0], + }, + 0x2A38: { // Sensor Location: Wrist + value: 0x02, + } + } + }); + + } + function updateBLEHeartRate(hrm) { + /* + * Send updated heart rate measurement via BLE + */ + if (hrm === undefined || hrm.confidence < 50) return; + try { + NRF.updateServices({ + 0x180D: { + 0x2A37: { + value: [0x06, hrm.bpm], + notify: true + }, + 0x2A38: { + value: 0x02, + } + } + }); + } catch (error) { + if (error.message.includes("BLE restart")) { + /* + * BLE has to restart after service setup. + */ + NRF.disconnect(); + } + else if (error.message.includes("UUID 0x2a37")) { + /* + * Setup service if it wasn't setup correctly for some reason + */ + setupHRMAdvertising(); + } else { + console.log("[bootgatthrm]: Unexpected error occured while updating HRM over BLE! Error: " + error.message); + } + } + } + + setupHRMAdvertising(); + Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm); }); +})(); diff --git a/apps/bootgatthrm/metadata.json b/apps/bootgatthrm/metadata.json new file mode 100644 index 000000000..450066622 --- /dev/null +++ b/apps/bootgatthrm/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "bootgatthrm", + "name": "BLE GATT HRM Service", + "shortName": "BLE HRM Service", + "version": "0.02", + "description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n", + "icon": "bluetooth.png", + "type": "bootloader", + "tags": "hrm,health,ble,bluetooth,gatt", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"gatthrm.boot.js","url":"boot.js"} + ] +} diff --git a/apps/hwid_a_battery_widget/ChangeLog b/apps/hwid_a_battery_widget/ChangeLog index cbdccfecf..6c57f97a8 100644 --- a/apps/hwid_a_battery_widget/ChangeLog +++ b/apps/hwid_a_battery_widget/ChangeLog @@ -5,4 +5,5 @@ 0.05: Deleting Background - making Font larger 0.06: Fixing refresh issues 0.07: Fixed position after unlocking -0.08: Handling exceptions \ No newline at end of file +0.08: Handling exceptions +0.09: Add option for showing battery high mark diff --git a/apps/hwid_a_battery_widget/README.md b/apps/hwid_a_battery_widget/README.md index db105635a..fd7bbec67 100644 --- a/apps/hwid_a_battery_widget/README.md +++ b/apps/hwid_a_battery_widget/README.md @@ -8,6 +8,8 @@ Show the current battery level and charging status in the top right of the clock * Blue when charging * 40 pixels wide +The high-level marker (a little bar at the 100% point) can be toggled in settings. + ![](a_battery_widget-pic.jpg) ## Creator diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index 981b81079..73dfc7c92 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -3,7 +3,7 @@ "name": "A Battery Widget (with percentage) - Hanks Mod", "shortName":"H Battery Widget", "icon": "widget.png", - "version":"0.08", + "version":"0.09", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index e42c15355..027535051 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -1,7 +1,6 @@ (function(){ const intervalLow = 60000; // update time when not charging const intervalHigh = 2000; // update time when charging - var old_l; var old_x = this.x; var old_y = this.y; @@ -22,49 +21,36 @@ }; function draw() { - if (typeof old_x === 'undefined') old_x = this.x; - if (typeof old_y === 'undefined') old_y = this.y; - var s = 29; + var s = width - 1; var x = this.x; var y = this.y; if ((typeof x === 'undefined') || (typeof y === 'undefined')) { } else { + g.clearRect(old_x, old_y, old_x + width, old_y + height); + const l = E.getBattery(); // debug: Math.floor(Math.random() * 101); let xl = x+4+l*(s-12)/100; - if ((l != old_l) && (typeof old_l != 'undefined') ){ // Delete the old value from screen - let xl_old = x+4+old_l*(s-12)/100; - g.setColor(COLORS.white); - // g.fillRect(x+2,y+5,x+s-6,y+18); - g.fillRect(x,y,xl+4,y+16+3); //Clear - g.setFontAlign(0,0); - g.setFont('Vector',16); - //g.fillRect(old_x,old_y,old_x+4+l*(s-12)/100,old_y+16+3); // clear (lazy) - g.drawString(old_l, old_x + 14, old_y + 10); - g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar - - } - old_l = l; - //console.log(old_x); g.setColor(levelColor(l)); g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar - 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); - + } old_x = this.x; - old_y = this.y; - + old_y = this.y; + if (Bangle.isCharging()) changeInterval(id, intervalHigh); else changeInterval(id, intervalLow); } Bangle.on('charging',function(charging) { draw(); }); var id = setInterval(()=>WIDGETS["hwid_a_battery_widget"].draw(), intervalLow); + var width = 30; + var height = 19; - WIDGETS["hwid_a_battery_widget"]={area:"tr",width:30,draw:draw}; + WIDGETS["hwid_a_battery_widget"]={area:"tr",width,draw:draw}; })(); diff --git a/apps/kbmatry/ChangeLog b/apps/kbmatry/ChangeLog new file mode 100644 index 000000000..98d0187ab --- /dev/null +++ b/apps/kbmatry/ChangeLog @@ -0,0 +1,7 @@ +1.00: New keyboard +1.01: Change swipe interface to taps, speed up responses (efficiency tweaks). +1.02: Generalize drawing and letter scaling. Allow custom and auto-generated character sets. Improve documentation. +1.03: Attempt to improve keyboard load time. +1.04: Make code asynchronous and improve load time. +1.05: Fix layout issue and rename library +1.06: Touch up readme, prep for IPO, add screenshots \ No newline at end of file diff --git a/apps/kbmatry/README.md b/apps/kbmatry/README.md new file mode 100644 index 000000000..73767828d --- /dev/null +++ b/apps/kbmatry/README.md @@ -0,0 +1,119 @@ +# Matryoshka Keyboard + +![icon](icon.png) + +![screenshot](screenshot.png) ![screenshot](screenshot6.png) + +![screenshot](screenshot5.png) ![screenshot](screenshot2.png) +![screenshot](screenshot3.png) ![screenshot](screenshot4.png) + +Nested key input utility. + +## How to type + +Press your finger down on the letter group that contains the character you would like to type, then tap the letter you +want to enter. Once you are touching the letter you want, release your +finger. + +![help](help.png) + +Press "shft" or "caps" to access alternative characters, including upper case letters, punctuation, and special +characters. +Pressing "shft" also reveals a cancel button if you would like to terminate input without saving. + +Press "ok" to finish typing and send your text to whatever app called this keyboard. + +Press "del" to delete the leftmost character. + +## Themes and Colors + +This keyboard will attempt to use whatever theme or colorscheme is being used by your Bangle device. + +## How to use in a program + +This was developed to match the interface implemented for kbtouch, kbswipe, etc. + +In your app's metadata, add: + +```json + "dependencies": {"textinput": "type"} +``` + +From inside your app, call: + +```js +const textInput = require("textinput"); + +textInput.input({text: ""}) + .then(result => { + console.log("The user entered: ", result); + }); +``` + +Alternatively, if you want to improve the load time of the keyboard, you can pre-generate the data the keyboard needs +to function and render like so: + +```js +const textInput = require("textinput"); + +const defaultKeyboard = textInput.generateKeyboard(textInput.defaultCharSet); +const defaultShiftKeyboard = textInput.generateKeyboard(textInput.defaultCharSetShift); +// ... +textInput.input({text: "", keyboardMain: defaultKeyboard, keyboardShift: defaultShiftKeyboard}) + .then(result => { + console.log("The user entered: ", result); + // And it was faster! + }); +``` + +This isn't required, but if you are using a large character set, and the user is interacting with the keyboard a lot, +it can really smooth the experience. + +The default keyboard has a full set of alphanumeric characters as well as special characters and buttons in a +pre-defined layout. If your application needs something different, or you want to have a custom layout, you can do so: + +```js +const textInput = require("textinput"); + +const customKeyboard = textInput.generateKeyboard([ + ["1", "2", "3", "4"], ["5", "6", "7", "8"], ["9", "0", ".", "-"], "ok", "del", "cncl" +]); +// ... +textInput.input({text: "", keyboardMain: customKeyboard}) + .then(result => { + console.log("The user entered: ", result); + // And they could only enter numbers, periods, and dashes! + }); +``` + +This will give you a keyboard with six buttons. The first three buttons will open up a 2x2 keyboard. The second three +buttons are special keys for submitting, deleting, and cancelling respectively. + +Finally if you are like, super lazy, or have a dynamic set of keys you want to be using at any given time, you can +generate keysets from strings like so: + +```js +const textInput = require("textinput"); + +const customKeyboard = textInput.generateKeyboard(createCharSet("ABCDEFGHIJKLMNOP", ["ok", "shft", "cncl"])); +const customShiftKeyboard = textInput.generateKeyboard(createCharSet("abcdefghijklmnop", ["ok", "shft", "cncl"])); +// ... +textInput.input({text: "", keyboardMain: customKeyboard, keyboardShift: customShiftKeyboard}) + .then(result => { + console.log("The user entered: ", result); + // And the keyboard was automatically generated to include "ABCDEFGHIJKLMNOP" plus an OK button, a shift button, and a cancel button! + }); +``` + +The promise resolves when the user hits "ok" on the input or if they cancel. If the user cancels, undefined is +returned, although the user can hit "OK" with an empty string as well. If you define a custom character set and +do not include the "ok" button your user will be soft-locked by the keyboard. Fair warning! + +At some point I may add swipe-for-space and swipe-for-delete as well as swipe-for-submit and swipe-for-cancel +however I want to have a good strategy for the touch screen +[affordance](https://careerfoundry.com/en/blog/ux-design/affordances-ux-design/). + +## Secret features + +If you long press a key with characters on it, that will enable "Shift" mode. + diff --git a/apps/kbmatry/app-icon.js b/apps/kbmatry/app-icon.js new file mode 100644 index 000000000..a4b0ecc16 --- /dev/null +++ b/apps/kbmatry/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcBkmSpICVz//ABARGCBIRByA/Dk+AAgUH8AECgP4kmRCwX4n+PAoXH8YEC+IRC4HguE4/+P/EfCIXwgARHn4RG+P/j4RDJwgRBGQIRIEYNxCIRECGpV/CIXAgY1P4/8v41JOgeOn4RDGo4jER5Y1FCJWQg4RDYpeSNIQAMkmTCBwRBz4IG9YRIyA8COgJHBhMgI4+QyVJAYJrC9Mkw5rHwFAkEQCImSCJvAhIRBpazFGo3HEYVJkIjGCIIUCAQu/CKGSGo4jPLIhHMNayPLYo6zBYozpH9MvdI+TfaGSv4KHCI+Qg4GDI4IABg5HGyIYENYIAB45rGyPACKIIDx/4gF/CIPx/8fCIY1F4H8CJPA8BtCa4I1DCJFxCIYXBCILXBGpXHGplwn5HPuE4NaH4n6PLyC6CgEnYpeSpICDdJYRFz4RQARQ")) \ No newline at end of file diff --git a/apps/kbmatry/help.png b/apps/kbmatry/help.png new file mode 100644 index 000000000..6eef5694b Binary files /dev/null and b/apps/kbmatry/help.png differ diff --git a/apps/kbmatry/icon.png b/apps/kbmatry/icon.png new file mode 100644 index 000000000..058df4487 Binary files /dev/null and b/apps/kbmatry/icon.png differ diff --git a/apps/kbmatry/lib.js b/apps/kbmatry/lib.js new file mode 100644 index 000000000..a7a434dad --- /dev/null +++ b/apps/kbmatry/lib.js @@ -0,0 +1,501 @@ +/** + * Attempt to lay out a set of characters in a logical way to optimize the number of buttons with the number + * of characters per button. Useful if you need to dynamically (or frequently) change your character set + * and don't want to create a layout for ever possible combination. + * @param text The text you want to parse into a character set. + * @param specials Any special buttons you want to add to the keyboard (must match hardcoded special string values) + * @returns {*[]} + */ +function createCharSet(text, specials) { + specials = specials || []; + const mandatoryExtraKeys = specials.length; + const preferredNumChars = [1, 2, 4, 6, 9, 12]; + const preferredNumKeys = [4, 6, 9, 12].map(num => num - mandatoryExtraKeys); + let keyIndex = 0, charIndex = 0; + let keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex]; + while (keySpace < text.length) { + const numKeys = preferredNumKeys[keyIndex]; + const numChars = preferredNumChars[charIndex]; + const nextNumKeys = preferredNumKeys[keyIndex]; + const nextNumChars = preferredNumChars[charIndex]; + if (numChars <= numKeys) { + charIndex++; + } else if ((text.length / nextNumChars) < nextNumKeys) { + charIndex++; + } else { + keyIndex++; + } + keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex]; + } + const charsPerKey = preferredNumChars[charIndex]; + let charSet = []; + for (let i = 0; i < text.length; i += charsPerKey) { + charSet.push(text.slice(i, i + charsPerKey) + .split("")); + } + charSet = charSet.concat(specials); + return charSet; +} + +/** + * Given the width, height, padding (between chars) and number of characters that need to fit horizontally / + * vertically, this function attempts to select the largest font it can that will still fit within the bounds when + * drawing a grid of characters. Does not handle multi-letter entries well, assumes we are laying out a grid of + * single characters. + * @param width The total width available for letters (px) + * @param height The total height available for letters (px) + * @param padding The amount of space required between characters (px) + * @param gridWidth The number of characters wide the rendering is going to be + * @param gridHeight The number of characters high the rendering is going to be + * @returns {{w: number, h: number, font: string}} + */ +function getBestFont(width, height, padding, gridWidth, gridHeight) { + let font = "4x6"; + let w = 4; + let h = 6; + const charMaxWidth = width / gridWidth - padding * gridWidth; + const charMaxHeight = height / gridHeight - padding * gridHeight; + if (charMaxWidth >= 6 && charMaxHeight >= 8) { + w = 6; + h = 8; + font = "6x8"; + } + if (charMaxWidth >= 12 && charMaxHeight >= 16) { + w = 12; + h = 16; + font = "6x8:2"; + } + if (charMaxWidth >= 12 && charMaxHeight >= 20) { + w = 12; + h = 20; + font = "12x20"; + } + if (charMaxWidth >= 20 && charMaxHeight >= 20) { + font = "Vector" + Math.floor(Math.min(charMaxWidth, charMaxHeight)); + const dims = g.setFont(font) + .stringMetrics("W"); + w = dims.width + h = dims.height; + } + return {w, h, font}; +} + + +/** + * Generate a set of key objects given an array of arrays of characters to make available for typing. + * @param characterArrays + * @returns {Promise} + */ +function getKeys(characterArrays) { + if (Array.isArray(characterArrays)) { + return Promise.all(characterArrays.map((chars, i) => generateKeyFromChars(characterArrays, i))); + } else { + return generateKeyFromChars(characterArrays, 0); + } +} + +/** + * Given a set of characters, determine whether or not this needs to be a matryoshka key, a basic key, or a special key. + * Then generate that key. If the key is a matryoshka key, we queue up the generation of its sub-keys for later to + * improve load times. + * @param chars + * @param i + * @returns {Promise} + */ +function generateKeyFromChars(chars, i) { + return new Promise((resolve, reject) => { + let special; + if (!Array.isArray(chars[i]) && chars[i].length > 1) { + // If it's not an array we assume it's a string. Fingers crossed I guess, lol. Be nice to my functions! + special = chars[i]; + } + const key = getKeyByIndex(chars, i, special); + if (!special) { + key.chars = chars[i]; + } + if (key.chars.length > 1) { + key.pendingSubKeys = true; + key.getSubKeys = () => getKeys(key.chars); + resolve(key) + } else { + resolve(key); + } + }) +} + + +/** + * Given a set of characters (or sets of characters) get the position and dimensions of the i'th key in that set. + * @param charSet An array where each element represents a key on the hypothetical keyboard. + * @param i The index of the key in the set you want to get dimensions for. + * @param special The special property of the key - for example "del" for a key used for deleting characters. + * @returns {{special, bord: number, pad: number, w: number, x: number, h: number, y: number, chars: *[]}} + */ +function getKeyByIndex(charSet, i, special) { + // Key dimensions + const keyboardOffsetY = 40; + const margin = 3; + const padding = 4; + const border = 2; + const gridWidth = Math.ceil(Math.sqrt(charSet.length)); + const gridHeight = Math.ceil(charSet.length / gridWidth); + const keyWidth = Math.floor((g.getWidth()) / gridWidth) - margin; + const keyHeight = Math.floor((g.getHeight() - keyboardOffsetY) / gridHeight) - margin; + const gridx = i % gridWidth; + const gridy = Math.floor(i / gridWidth) % gridWidth; + const x = gridx * (keyWidth + margin); + const y = gridy * (keyHeight + margin) + keyboardOffsetY; + const w = keyWidth; + const h = keyHeight; + // internal Character spacing + const numChars = charSet[i].length; + const subGridWidth = Math.ceil(Math.sqrt(numChars)); + const subGridHeight = Math.ceil(numChars / subGridWidth); + const bestFont = getBestFont(w - padding, h - padding, 0, subGridWidth, subGridHeight); + const letterWidth = bestFont.w; + const letterHeight = bestFont.h; + const totalWidth = (subGridWidth - 1) * (w / subGridWidth) + padding + letterWidth + 1; + const totalHeight = (subGridHeight - 1) * (h / subGridHeight) + padding + letterHeight + 1; + const extraPadH = (w - totalWidth) / 2; + const extraPadV = (h - totalHeight) / 2; + return { + x, + y, + w, + h, + pad : padding, + bord : border, + chars: [], + special, + subGridWidth, + subGridHeight, + extraPadH, + extraPadV, + font : bestFont.font + }; +} + + +/** + * This is probably the most intense part of this keyboard library. If you don't do it ahead of time, it will happen + * when you call the keyboard, and it can take up to 0.5 seconds for a full alphanumeric keyboard. Depending on what + * is an acceptable user experience for you, and how many keys you are actually generating, you may choose to do this + * ahead of time and pass the result to the "input" function of this library. NOTE: This function would need to be + * called once per key set - so if you have a keyboard with a "shift" key you'd need to run it once for your base + * keyset and once for your shift keyset. + * @param charSets + * @returns {Promise} + */ +function generateKeyboard(charSets) { + if (!Array.isArray(charSets)) { + // User passed a string. We will divvy it up into a real set of subdivided characters. + charSets = createCharSet(charSets, ["ok", "del", "shft"]); + } + return getKeys(charSets); +} + +// Default layout +const defaultCharSet = [ + ["a", "b", "c", "d", "e", "f", "g", "h", "i"], + ["j", "k", "l", "m", "n", "o", "p", "q", "r"], + ["s", "t", "u", "v", "w", "x", "y", "z", "0"], + ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + [" ", "`", "-", "=", "[", "]", "\\", ";", "'"], + [",", ".", "/"], + "ok", + "shft", + "del" +]; + +// Default layout with shift pressed +const defaultCharSetShift = [ + ["A", "B", "C", "D", "E", "F", "G", "H", "I"], + ["J", "K", "L", "M", "N", "O", "P", "Q", "R"], + ["S", "T", "U", "V", "W", "X", "Y", "Z", ")"], + ["!", "@", "#", "$", "%", "^", "&", "*", "("], + ["~", "_", "+", "{", "}", "|", ":", "\"", "<"], + [">", "?"], + "ok", + "shft", + "del" +]; + +/** + * Given initial options, allow the user to type a set of characters and return their entry in a promise. If you do not + * submit your own character set, a default alphanumeric keyboard will display. + * @param options The object containing initial options for the keyboard. + * @param {string} options.text The initial text to display / edit in the keyboard + * @param {array[]|string[]} [options.keyboardMain] The primary keyboard generated with generateKeyboard() + * @param {array[]|string[]} [options.keyboardShift] Like keyboardMain, but displayed when shift / capslock is pressed. + * @returns {Promise} + */ +function input(options) { + options = options || {}; + let typed = options.text || ""; + let resolveFunction = () => {}; + let shift = false; + let caps = false; + let activeKeySet; + + const offsetX = 0; + const offsetY = 40; + + E.showMessage("Loading..."); + let keyboardPromise; + if (options.keyboardMain) { + keyboardPromise = Promise.all([options.keyboardMain, options.keyboardShift || Promise.resolve([])]); + } else { + keyboardPromise = Promise.all([generateKeyboard(defaultCharSet), generateKeyboard(defaultCharSetShift)]) + } + + let mainKeys; + let mainKeysShift; + + /** + * Draw an individual keyboard key - handles special formatting and the rectangle pad, followed by the character + * rendering. + * @param key + */ + function drawKey(key) { + let bgColor = g.theme.bg; + if (key.special) { + if (key.special === "ok") bgColor = "#0F0"; + if (key.special === "cncl") bgColor = "#F00"; + if (key.special === "del") bgColor = g.theme.bg2; + if (key.special === "spc") bgColor = g.theme.bg2; + if (key.special === "shft") { + bgColor = shift ? g.theme.bgH : g.theme.bg2; + } + if (key.special === "caps") { + bgColor = caps ? g.theme.bgH : g.theme.bg2; + } + g.setColor(bgColor) + .fillRect({x: key.x, y: key.y, w: key.w, h: key.h}); + } + g.setColor(g.theme.fg) + .drawRect({x: key.x, y: key.y, w: key.w, h: key.h}); + drawChars(key); + } + + /** + * Draw the characters for a given key - this handles the layout of all characters needed for the key, whether the + * key has 12 characters, 1, or if it represents a special key. + * @param key + */ + function drawChars(key) { + const numChars = key.chars.length; + if (key.special) { + g.setColor(g.theme.fg) + .setFont("12x20") + .setFontAlign(-1, -1) + .drawString(key.special, key.x + key.w / 2 - g.stringWidth(key.special) / 2, key.y + key.h / 2 - 10, false); + } else { + g.setColor(g.theme.fg) + .setFont(key.font) + .setFontAlign(-1, -1); + for (let i = 0; i < numChars; i++) { + const gridX = i % key.subGridWidth; + const gridY = Math.floor(i / key.subGridWidth) % key.subGridWidth; + const charOffsetX = gridX * (key.w / key.subGridWidth); + const charOffsetY = gridY * (key.h / key.subGridHeight); + const posX = key.x + key.pad + charOffsetX + key.extraPadH; + const posY = key.y + key.pad + charOffsetY + key.extraPadV; + g.drawString(key.chars[i], posX, posY, false); + } + } + } + + /** + * Get the key set corresponding to the indicated shift state. Allows easy switching between capital letters and + * lower case by just switching the boolean passed here. + * @param shift + * @returns {*[]} + */ + function getMainKeySet(shift) { + return shift ? mainKeysShift : mainKeys; + } + + /** + * Draw all the given keys on the screen. + * @param keys + */ + function drawKeys(keys) { + keys.forEach(key => { + drawKey(key); + }); + } + + /** + * Draw the text that the user has typed so far, includes a cursor and automatic truncation when the string is too + * long. + * @param text + * @param cursorChar + */ + function drawTyped(text, cursorChar) { + let visibleText = text; + let ellipsis = false; + const maxWidth = 176 - 40; + while (g.setFont("12x20") + .stringWidth(visibleText) > maxWidth) { + ellipsis = true; + visibleText = visibleText.slice(1); + } + if (ellipsis) { + visibleText = "..." + visibleText; + } + g.setColor(g.theme.bg2) + .fillRect(5, 5, 171, 30); + g.setColor(g.theme.fg2) + .setFont("12x20") + .drawString(visibleText + cursorChar, 15, 10, false); + } + + /** + * Clear the space on the screen that the keyboard occupies (not the text the user has written). + */ + function clearKeySpace() { + g.setColor(g.theme.bg) + .fillRect(offsetX, offsetY, 176, 176); + } + + /** + * Based on a touch event, determine which key was pressed by the user. + * @param touchEvent + * @param keys + * @returns {*} + */ + function getTouchedKey(touchEvent, keys) { + return keys.find((key) => { + let relX = touchEvent.x - key.x; + let relY = touchEvent.y - key.y; + return relX > 0 && relX < key.w && relY > 0 && relY < key.h; + }) + } + + /** + * On a touch event, determine whether a key is touched and take appropriate action if it is. + * @param button + * @param touchEvent + */ + function keyTouch(button, touchEvent) { + const pressedKey = getTouchedKey(touchEvent, activeKeySet); + if (pressedKey == null) { + // User tapped empty space. + swapKeySet(getMainKeySet(shift !== caps)); + return; + } + if (pressedKey.pendingSubKeys) { + // We have to generate the subkeys for this key still, but we decided to wait until we needed it! + pressedKey.pendingSubKeys = false; + pressedKey.getSubKeys() + .then(subkeys => { + pressedKey.subKeys = subkeys; + keyTouch(undefined, touchEvent); + }) + return; + } + // Haptic feedback + Bangle.buzz(25, 1); + if (pressedKey.subKeys) { + // Hold press for "shift!" + if (touchEvent.type > 1) { + shift = !shift; + swapKeySet(getMainKeySet(shift !== caps)); + } else { + swapKeySet(pressedKey.subKeys); + } + } else { + if (pressedKey.special) { + evaluateSpecialFunctions(pressedKey); + } else { + typed = typed + pressedKey.chars; + shift = false; + drawTyped(typed, ""); + swapKeySet(getMainKeySet(shift !== caps)); + } + } + } + + /** + * Manage setting, generating, and rendering new keys when a key set is changed. + * @param newKeys + */ + function swapKeySet(newKeys) { + activeKeySet = newKeys; + clearKeySpace(); + drawKeys(activeKeySet); + } + + /** + * Determine if the key contains any of the special strings that have their own special behaviour when pressed. + * @param key + */ + function evaluateSpecialFunctions(key) { + switch (key.special) { + case "ok": + setTimeout(() => resolveFunction(typed), 50); + break; + case "del": + typed = typed.slice(0, -1); + drawTyped(typed, ""); + break; + case "shft": + shift = !shift; + swapKeySet(getMainKeySet(shift !== caps)); + break; + case "caps": + caps = !caps; + swapKeySet(getMainKeySet(shift !== caps)); + break; + case "cncl": + setTimeout(() => resolveFunction(), 50); + break; + case "spc": + typed = typed + " "; + break; + } + } + + let isCursorVisible = true; + + const blinkInterval = setInterval(() => { + if (!activeKeySet) return; + isCursorVisible = !isCursorVisible; + if (isCursorVisible) { + drawTyped(typed, "_"); + } else { + drawTyped(typed, ""); + } + }, 200); + + + /** + * We return a promise but the resolve function is assigned to a variable in the higher function scope. That allows + * us to return the promise and resolve it after we are done typing without having to return the entire scope of the + * application within the promise. + */ + return new Promise((resolve, reject) => { + g.clear(true); + resolveFunction = resolve; + keyboardPromise.then((result) => { + mainKeys = result[0]; + mainKeysShift = result[1]; + swapKeySet(getMainKeySet(shift !== caps)); + Bangle.setUI({ + mode: "custom", touch: keyTouch + }); + Bangle.setLocked(false); + }) + }).then((result) => { + g.clearRect(Bangle.appRect); + clearInterval(blinkInterval); + Bangle.setUI(); + return result; + }); +} + +exports.input = input; +exports.generateKeyboard = generateKeyboard; +exports.defaultCharSet = defaultCharSet; +exports.defaultCharSetShift = defaultCharSetShift; +exports.createCharSet = createCharSet; \ No newline at end of file diff --git a/apps/kbmatry/metadata.json b/apps/kbmatry/metadata.json new file mode 100644 index 000000000..793286180 --- /dev/null +++ b/apps/kbmatry/metadata.json @@ -0,0 +1,14 @@ +{ "id": "kbmatry", + "name": "Matryoshka Keyboard", + "version":"1.06", + "description": "A library for text input via onscreen keyboard. Easily enter characters with nested keyboards.", + "icon": "icon.png", + "type":"textinput", + "tags": "keyboard", + "supports" : ["BANGLEJS2"], + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot6.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"},{"url":"screenshot5.png"},{"url": "help.png"}], + "readme": "README.md", + "storage": [ + {"name":"textinput","url":"lib.js"} + ] +} diff --git a/apps/kbmatry/screenshot.png b/apps/kbmatry/screenshot.png new file mode 100644 index 000000000..08bb366e4 Binary files /dev/null and b/apps/kbmatry/screenshot.png differ diff --git a/apps/kbmatry/screenshot2.png b/apps/kbmatry/screenshot2.png new file mode 100644 index 000000000..21874244d Binary files /dev/null and b/apps/kbmatry/screenshot2.png differ diff --git a/apps/kbmatry/screenshot3.png b/apps/kbmatry/screenshot3.png new file mode 100644 index 000000000..1f0c73265 Binary files /dev/null and b/apps/kbmatry/screenshot3.png differ diff --git a/apps/kbmatry/screenshot4.png b/apps/kbmatry/screenshot4.png new file mode 100644 index 000000000..de2f90bee Binary files /dev/null and b/apps/kbmatry/screenshot4.png differ diff --git a/apps/kbmatry/screenshot5.png b/apps/kbmatry/screenshot5.png new file mode 100644 index 000000000..b860c8438 Binary files /dev/null and b/apps/kbmatry/screenshot5.png differ diff --git a/apps/kbmatry/screenshot6.png b/apps/kbmatry/screenshot6.png new file mode 100644 index 000000000..20de7ddc1 Binary files /dev/null and b/apps/kbmatry/screenshot6.png differ diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 4231a9f26..2e8f9bb3c 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -91,4 +91,6 @@ 0.66: Updated Navigation handling to work with new Gadgetbridge release 0.67: Support for 'Ignore' for messages from Gadgetbridge Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons -0.68: More navigation icons (for roundabouts) \ No newline at end of file +0.68: More navigation icons (for roundabouts) +0.69: More navigation icons (keep/uturn left/right) + Nav messages with '/' now get split on newlines \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index d88fba6df..97396496f 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -17,6 +17,7 @@ require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title" // maps GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:966,action:"continue",eta:"08:39"}) GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:12345,action:"left_slight",eta:"08:39"}) +GB({t:"nav",src:"maps",title:"Navigation",instr:"Main St / I-29 ALT / Centerpoint Dr",distance:12345,action:"left_slight",eta:"08:39"}) // call require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true}) */ @@ -84,12 +85,13 @@ function showMapMessage(msg) { if (msg.distance!==undefined) distance = require("locale").distance(msg.distance); if (msg.instr) { - if (msg.instr.includes("towards") || msg.instr.includes("toward")) { - m = msg.instr.split(/towards|toward/); + var instr = msg.instr.replace(/\s*\/\s*/g," \/\n"); // convert slashes to newlines + if (instr.includes("towards") || instr.includes("toward")) { + m = instr.split(/towards|toward/); target = m[0].trim(); street = m[1].trim(); }else - target = msg.instr; + target = instr; } switch (msg.action) { case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break; @@ -97,10 +99,15 @@ function showMapMessage(msg) { case "right": img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA";break; case "left_slight": img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";break; case "right_slight": img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";break; + case "keep_left": img = "ERmBAACAAOAB+AD+AP+B/+H3+PO+8c8w4wBwADgAHgAPAAfAAfAAfAAfAAeAAeAAcAA8AA4ABwADgA==";break; + case "keep_right": img = "ERmBAACAAOAA/AD+AP+A//D/fPueeceY4YBwADgAPAAeAB8AHwAfAB8ADwAPAAcAB4ADgAHAAOAAAA==";break; + case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break; + case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break; case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break; - case "roundabout_left": img = "HhcCAAAAAAAAAAAADwAAAEAAAAP8AAGqkAAA/8ABqqqAAD/wAGqqqgAP/AAKpAakA/8AAakAGoD/////QACpP/////gABpP/////gABpD/////wACpA/8AAv4AGoAP/AAf/QakAD/wAL//qgAA/8AC//qAAAP8AAf/kAAADwAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAAAAAAAB/AAA=";break; - case "roundabout_right": img = "HhcCAAAAAAAAAAAAZAAADwAAAf/9AAP8AAB///gAP/AAH///4AD/wAP/Rv9AA/8Af8AH/AAP/AvwAD/////w/wAC/////8/wAC/////8vwAB/////wf8AGpAAP/AP/QaoAA/8AH//qkAD/wAB//6QAP/AAAf/0AAP8AAAA/QAADwAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAAA/QAAAAAAA=";break; - case "roundabout_straight": img = "EhwCAABQAAAAH0AAAAf9AAAB//QAAH//0AAf//9AB////QH/v+/0P+P8v8L4P8L4CQP8BgAC/+QAAP/+kAA//+pAC/4GqQD/AAqgH+AAagL8AAKkL8AAKkH+AAagD/AAqgC/4GqQA//+pAAP/+kAAC/+QAAAP8AAAAP8AAAAP8AA";break; + case "roundabout_left": img = "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=";break; + case "roundabout_right": img = "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=";break; + case "roundabout_straight": img = "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==";break; + case "roundabout_uturn": img = "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA";break; } //FIXME: what about countries where we drive on the right? How will we know to flip the icons? @@ -208,7 +215,7 @@ function showMessageScroller(msg) { var bodyFont = fontBig; g.setFont(bodyFont); var lines = []; - if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10) + if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10); var titleCnt = lines.length; if (titleCnt) lines.push(""); // add blank line after title lines = lines.concat(g.wrapString(msg.body, g.getWidth()-10),["",/*LANG*/"< Back"]); diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index f18f39096..dffff0c58 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.68", + "version": "0.69", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 46ea05918..94e2f28c2 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -32,3 +32,4 @@ 0.24: Can now specify `setRecording(true, {force:...` to not show a menu 0.25: Widget now has `isRecording()` for retrieving recording status. 0.26: Now record filename based on date +0.27: Fix first ever recorded filename being log0 (now all are dated) \ No newline at end of file diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index 598318e29..beb5648c8 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.26", + "version": "0.27", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 061859f9c..3c9afcf70 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -240,8 +240,9 @@ var settings = loadSettings(); options = options||{}; if (isOn && !settings.recording) { + var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10; if (!settings.file) { // if no filename set - settings.file = "recorder.log0.csv"; + settings.file = "recorder.log" + date + trackNo.toString(36) + ".csv"; } else if (require("Storage").list(settings.file).length){ // if file exists if (!options.force) { // if not forced, ask the question g.reset(); // work around bug in 2v17 and earlier where bg color wasn't reset @@ -263,7 +264,6 @@ require("Storage").open(settings.file,"r").erase(); } else if (options.force=="new") { // new file - use the current date - var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10; var newFileName; do { // while a file exists, add one to the letter after the date newFileName = "recorder.log" + date + trackNo.toString(36) + ".csv"; diff --git a/apps/sched/interface.html b/apps/sched/interface.html index f1ace7d0c..b67029fa2 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -86,31 +86,74 @@ function eventToAlarm(event, offsetMs) { } function upload() { + // kick off all the (active) timers + const now = new Date(); + const currentTime = now.getHours()*3600000 + + now.getMinutes()*60000 + + now.getSeconds()*1000; + + for (const alarm of alarms) + if (alarm.timer != undefined && alarm.on) + alarm.t = currentTime + alarm.timer; + Util.showModal("Saving..."); Util.writeStorage("sched.json", JSON.stringify(alarms), () => { - location.reload(); // reload so we see current data + Puck.write(`\x10require("sched").reload();\n`, () => { + location.reload(); // reload so we see current data + }); }); } function renderAlarm(alarm, exists) { - const localDate = dateFromAlarm(alarm); + const localDate = alarm.date ? dateFromAlarm(alarm) : null; const tr = document.createElement('tr'); tr.classList.add('event-row'); tr.dataset.uid = alarm.id; - const tdTime = document.createElement('td'); - tr.appendChild(tdTime); + const tdType = document.createElement('td'); + tdType.type = "text"; + tdType.classList.add('event-summary'); + tr.appendChild(tdType); const inputTime = document.createElement('input'); - inputTime.type = "datetime-local"; + if (localDate) { + tdType.textContent = "Event"; + inputTime.type = "datetime-local"; + inputTime.value = localDate.toISOString().slice(0,16); + inputTime.onchange = (e => { + const date = new Date(inputTime.value); + alarm.t = dateToMsSinceMidnight(date); + alarm.date = formatDate(date); + }); + } else { + const [hours, mins, secs] = msToHMS(alarm.timer || alarm.t); + + inputTime.type = "time"; + inputTime.step = 1; // display seconds + inputTime.value = `${hours}:${mins}:${secs}`; + + if (alarm.timer) { + tdType.textContent = "Timer"; + inputTime.onchange = e => { + alarm.timer = hmsToMs(inputTime.value); + // alarm.t is set on upload + }; + } else { + tdType.textContent = "Alarm"; + inputTime.onchange = e => { + alarm.t = hmsToMs(inputTime.value); + }; + } + } + if (!exists) { + const asterisk = document.createElement('sup'); + asterisk.textContent = '*'; + tdType.appendChild(asterisk); + } inputTime.classList.add('event-date'); inputTime.classList.add('form-input'); inputTime.dataset.uid = alarm.id; - inputTime.value = localDate.toISOString().slice(0,16); - inputTime.onchange = (e => { - const date = new Date(inputTime.value); - alarm.t = dateToMsSinceMidnight(date); - alarm.date = formatDate(date); - }); + const tdTime = document.createElement('td'); + tr.appendChild(tdTime); tdTime.appendChild(inputTime); const tdSummary = document.createElement('td'); @@ -130,13 +173,31 @@ function renderAlarm(alarm, exists) { tdSummary.appendChild(inputSummary); inputSummary.onchange(); + const tdOptions = document.createElement('td'); + tr.appendChild(tdOptions); + + const onOffCheck = document.createElement('input'); + onOffCheck.type = 'checkbox'; + onOffCheck.checked = alarm.on; + onOffCheck.onchange = e => { + alarm.on = !alarm.on; + if (alarm.on) delete alarm.last; + }; + const onOffIcon = document.createElement('i'); + onOffIcon.classList.add('form-icon'); + const onOff = document.createElement('label'); + onOff.classList.add('form-switch'); + onOff.appendChild(onOffCheck); + onOff.appendChild(onOffIcon); + tdOptions.appendChild(onOff); + const tdInfo = document.createElement('td'); tr.appendChild(tdInfo); const buttonDelete = document.createElement('button'); buttonDelete.classList.add('btn'); buttonDelete.classList.add('btn-action'); - tdInfo.prepend(buttonDelete); + tdInfo.appendChild(buttonDelete); const iconDelete = document.createElement('i'); iconDelete.classList.add('icon'); iconDelete.classList.add('icon-delete'); @@ -150,12 +211,53 @@ function renderAlarm(alarm, exists) { document.getElementById('upload').disabled = false; } +function msToHMS(ms) { + let secs = Math.floor(ms / 1000) % 60; + let mins = Math.floor(ms / 1000 / 60) % 60; + let hours = Math.floor(ms / 1000 / 60 / 60); + if (secs < 10) secs = "0" + secs; + if (mins < 10) mins = "0" + mins; + if (hours < 10) hours = "0" + hours; + return [hours, mins, secs]; +} + +function hmsToMs(hms) { + let [hours, mins, secs] = hms.split(":"); + hours = Number(hours); + mins = Number(mins); + secs = Number(secs); + return ((hours * 60 + mins) * 60 + secs) * 1000; +} + +function addEvent() { + const event = getAlarmDefaults(); + renderAlarm(event); + alarms.push(event); +} + function addAlarm() { const alarm = getAlarmDefaults(); + delete alarm.date; renderAlarm(alarm); alarms.push(alarm); } +function addTimer() { + const alarmDefaults = getAlarmDefaults(); + const timer = { + timer: hmsToMs("00:00:30"), + t: 0, + on: alarmDefaults.on, + dow: alarmDefaults.dow, + last: alarmDefaults.last, + rp: alarmDefaults.rp, + vibrate: alarmDefaults.vibrate, + as: alarmDefaults.as, + };; + renderAlarm(timer); + alarms.push(timer); +} + function getData() { Util.showModal("Loading..."); Util.readStorage('sched.json',data=>{ @@ -164,10 +266,19 @@ function getData() { Util.readStorage('sched.settings.json',data=>{ schedSettings = JSON.parse(data || "{}") || {}; Util.hideModal(); + alarms.sort((a, b) => { + let x; + + x = !!b.date - !!a.date; + if(x) return x; + + x = !!a.timer - !!b.timer; + if(x) return x; + + return a.t - b.t; + }); alarms.forEach(alarm => { - if (alarm.date) { - renderAlarm(alarm, true); - } + renderAlarm(alarm, true); }); }); }); @@ -183,16 +294,27 @@ function onInit() {

Manage dated events

+ +
- + + + diff --git a/apps/sensortools/ChangeLog b/apps/sensortools/ChangeLog index 7d9bdd6a8..6d2f5d2b4 100644 --- a/apps/sensortools/ChangeLog +++ b/apps/sensortools/ChangeLog @@ -2,3 +2,5 @@ 0.02: Less time used during boot if disabled 0.03: Fixed some test data 0.04: Correct type of time attribute in gps to Date +0.05: Fix gps emulation interpolation + Add setting for log output diff --git a/apps/sensortools/README.md b/apps/sensortools/README.md index 8b89add7c..f44a89090 100644 --- a/apps/sensortools/README.md +++ b/apps/sensortools/README.md @@ -5,40 +5,56 @@ This allows to simulate sensor behaviour for development purposes ## Per Sensor settings: -enabled: - true or false -mode: - emulate: Completely craft events for this sensor - modify: Take existing events from real sensor and modify their data -name: - name of the emulation or modification mode -power: - emulate: Simulate Bangle._PWR changes, but do not call real power function - nop: Do nothing, ignore all power calls for this sensor but return true - passthrough: Just pass all power calls unmodified - on: Do not allow switching the sensor off, all calls are switching the real sensor on +Enabled: +* **true** +* **false** + +Mode: +* **emulate**: Completely craft events for this sensor +* **modify**: Take existing events from real sensor and modify their data + +Name: +* name of the emulation or modification mode + +Power: +* **emulate**: Simulate Bangle._PWR changes, but do not call real power function +* **nop**: Do nothing, ignore all power calls for this sensor but return true +* **passthrough**: Just pass all power calls unmodified +* **on**: Do not allow switching the sensor off, all calls are switching the real sensor on ### HRM -Modes: modify, emulate +Modes: +* **modify**: Modify the original events from this sensor +* **emulate**: Create events simulating sensor activity + Modification: - bpmtrippled: Multiply the bpm value of the original HRM values with 3 +* **bpmtrippled**: Multiply the bpm value of the original HRM values with 3 + Emulation: - sin: Calculate bpm changes by using sin +* **sin**: Calculate bpm changes by using sin ### GPS -Modes: emulate +Modes: +* **emulate** + Emulation: - staticfix: static complete fix with all values - route: A square route starting in the SW corner and moving SW->NW->NO->SW... - routeFuzzy: Roughly the same square as route, but with 100m seqments with some variaton in course - nofix: All values NaN but time,sattelites,fix and fix == 0 - changingfix: A fix with randomly changing values +* **staticfix**: static complete fix with all values +* **route**: A square route starting in the SW corner and moving SW->NW->NO->SW... [Download as gpx](square.gpx) +* **routeFuzzy**: Roughly the same square as route, but with 100m seqments with some variaton in course [Download as gpx](squareFuzzy.gpx) +* **nofix**: All values NaN but time,sattelites,fix and fix == 0 +* **changingfix**: A fix with randomly changing values ### Compass -Modes: emulate +Modes: +* **emulate** + Emulation: - static: All values but heading are 1, heading == 0 - rotate: All values but heading are 1, heading rotates 360° +* **static**: All values but heading are 1, heading == 0 +* **rotate**: All values but heading are 1, heading rotates 360° + +# Creator + +[halemmerich](https://github.com/halemmerich) \ No newline at end of file diff --git a/apps/sensortools/default.json b/apps/sensortools/default.json index a85e1ddeb..0e0d0a9af 100644 --- a/apps/sensortools/default.json +++ b/apps/sensortools/default.json @@ -1,5 +1,6 @@ { "enabled": false, + "log": false, "mag": { "enabled": false, "mode": "emulate", diff --git a/apps/sensortools/lib.js b/apps/sensortools/lib.js index 7dfc6307d..5e1c199c2 100644 --- a/apps/sensortools/lib.js +++ b/apps/sensortools/lib.js @@ -5,6 +5,7 @@ exports.enable = () => { ); let log = function(text, param) { + if (!settings.log) return; let logline = new Date().toISOString() + " - " + "Sensortools - " + text; if (param) logline += ": " + JSON.stringify(param); print(logline); @@ -138,63 +139,63 @@ exports.enable = () => { function interpolate(a,b,progress){ return { - lat: a.lat * progress + b.lat * (1-progress), - lon: a.lon * progress + b.lon * (1-progress), - ele: a.ele * progress + b.ele * (1-progress) + lat: b.lat * progress + a.lat * (1-progress), + lon: b.lon * progress + a.lon * (1-progress), + alt: b.alt * progress + a.alt * (1-progress) } } function getSquareRoute(){ return [ - {lat:"47.2577411",lon:"11.9927442",ele:2273}, - {lat:"47.266761",lon:"11.9926673",ele:2166}, - {lat:"47.2667605",lon:"12.0059511",ele:2245}, - {lat:"47.2577516",lon:"12.0059925",ele:1994} + {lat:47.2577411,lon:11.9927442,alt:2273}, + {lat:47.266761,lon:11.9926673,alt:2166}, + {lat:47.2667605,lon:12.0059511,alt:2245}, + {lat:47.2577516,lon:12.0059925,alt:1994} ]; } function getSquareRouteFuzzy(){ return [ - {lat:"47.2578455",lon:"11.9929891",ele:2265}, - {lat:"47.258592",lon:"11.9923341",ele:2256}, - {lat:"47.2594506",lon:"11.9927412",ele:2230}, - {lat:"47.2603323",lon:"11.9924949",ele:2219}, - {lat:"47.2612056",lon:"11.9928175",ele:2199}, - {lat:"47.2621002",lon:"11.9929817",ele:2182}, - {lat:"47.2629025",lon:"11.9923915",ele:2189}, - {lat:"47.2637828",lon:"11.9926486",ele:2180}, - {lat:"47.2646733",lon:"11.9928167",ele:2191}, - {lat:"47.2655617",lon:"11.9930357",ele:2185}, - {lat:"47.2662862",lon:"11.992252",ele:2186}, - {lat:"47.2669305",lon:"11.993173",ele:2166}, - {lat:"47.266666",lon:"11.9944419",ele:2171}, - {lat:"47.2667579",lon:"11.99576",ele:2194}, - {lat:"47.2669409",lon:"11.9970579",ele:2207}, - {lat:"47.2666562",lon:"11.9983128",ele:2212}, - {lat:"47.2666027",lon:"11.9996335",ele:2262}, - {lat:"47.2667245",lon:"12.0009395",ele:2278}, - {lat:"47.2668457",lon:"12.002256",ele:2297}, - {lat:"47.2666126",lon:"12.0035373",ele:2303}, - {lat:"47.2664554",lon:"12.004841",ele:2251}, - {lat:"47.2669461",lon:"12.005948",ele:2245}, - {lat:"47.2660877",lon:"12.006323",ele:2195}, - {lat:"47.2652729",lon:"12.0057552",ele:2163}, - {lat:"47.2643926",lon:"12.0060123",ele:2131}, - {lat:"47.2634978",lon:"12.0058302",ele:2095}, - {lat:"47.2626129",lon:"12.0060759",ele:2066}, - {lat:"47.2617325",lon:"12.0058188",ele:2037}, - {lat:"47.2608668",lon:"12.0061784",ele:1993}, - {lat:"47.2600155",lon:"12.0057392",ele:1967}, - {lat:"47.2591203",lon:"12.0058233",ele:1949}, - {lat:"47.2582307",lon:"12.0059718",ele:1972}, - {lat:"47.2578014",lon:"12.004804",ele:2011}, - {lat:"47.2577232",lon:"12.0034834",ele:2044}, - {lat:"47.257745",lon:"12.0021656",ele:2061}, - {lat:"47.2578682",lon:"12.0008597",ele:2065}, - {lat:"47.2577082",lon:"11.9995526",ele:2071}, - {lat:"47.2575917",lon:"11.9982348",ele:2102}, - {lat:"47.2577401",lon:"11.996924",ele:2147}, - {lat:"47.257715",lon:"11.9956061",ele:2197}, - {lat:"47.2578996",lon:"11.9943081",ele:2228} + {lat:47.2578455,lon:11.9929891,alt:2265}, + {lat:47.258592,lon:11.9923341,alt:2256}, + {lat:47.2594506,lon:11.9927412,alt:2230}, + {lat:47.2603323,lon:11.9924949,alt:2219}, + {lat:47.2612056,lon:11.9928175,alt:2199}, + {lat:47.2621002,lon:11.9929817,alt:2182}, + {lat:47.2629025,lon:11.9923915,alt:2189}, + {lat:47.2637828,lon:11.9926486,alt:2180}, + {lat:47.2646733,lon:11.9928167,alt:2191}, + {lat:47.2655617,lon:11.9930357,alt:2185}, + {lat:47.2662862,lon:11.992252,alt:2186}, + {lat:47.2669305,lon:11.993173,alt:2166}, + {lat:47.266666,lon:11.9944419,alt:2171}, + {lat:47.2667579,lon:11.99576,alt:2194}, + {lat:47.2669409,lon:11.9970579,alt:2207}, + {lat:47.2666562,lon:11.9983128,alt:2212}, + {lat:47.2666027,lon:11.9996335,alt:2262}, + {lat:47.2667245,lon:12.0009395,alt:2278}, + {lat:47.2668457,lon:12.002256,alt:2297}, + {lat:47.2666126,lon:12.0035373,alt:2303}, + {lat:47.2664554,lon:12.004841,alt:2251}, + {lat:47.2669461,lon:12.005948,alt:2245}, + {lat:47.2660877,lon:12.006323,alt:2195}, + {lat:47.2652729,lon:12.0057552,alt:2163}, + {lat:47.2643926,lon:12.0060123,alt:2131}, + {lat:47.2634978,lon:12.0058302,alt:2095}, + {lat:47.2626129,lon:12.0060759,alt:2066}, + {lat:47.2617325,lon:12.0058188,alt:2037}, + {lat:47.2608668,lon:12.0061784,alt:1993}, + {lat:47.2600155,lon:12.0057392,alt:1967}, + {lat:47.2591203,lon:12.0058233,alt:1949}, + {lat:47.2582307,lon:12.0059718,alt:1972}, + {lat:47.2578014,lon:12.004804,alt:2011}, + {lat:47.2577232,lon:12.0034834,alt:2044}, + {lat:47.257745,lon:12.0021656,alt:2061}, + {lat:47.2578682,lon:12.0008597,alt:2065}, + {lat:47.2577082,lon:11.9995526,alt:2071}, + {lat:47.2575917,lon:11.9982348,alt:2102}, + {lat:47.2577401,lon:11.996924,alt:2147}, + {lat:47.257715,lon:11.9956061,alt:2197}, + {lat:47.2578996,lon:11.9943081,alt:2228} ]; } @@ -215,51 +216,43 @@ exports.enable = () => { let interpSteps; if (settings.gps.name == "routeFuzzy"){ route = getSquareRouteFuzzy(); - interpSteps = 5; + interpSteps = 74; } else { route = getSquareRoute(); - interpSteps = 50; + interpSteps = 740; } let step = 0; let routeIndex = 0; modGps(() => { let newIndex = (routeIndex + 1)%route.length; - + let followingIndex = (routeIndex + 2)%route.length; + let result = { - "speed": Math.random() * 3 + 2, + "speed": Math.random()*1 + 4.5, "time": new Date(), "satellites": Math.floor(Math.random()*5)+3, "fix": 1, "hdop": Math.floor(Math.random(30)+1) }; - + let oldPos = route[routeIndex]; - if (step != 0){ - oldPos = interpolate(route[routeIndex], route[newIndex], E.clip(0,1,step/interpSteps)); - } let newPos = route[newIndex]; - if (step < interpSteps - 1){ - newPos = interpolate(route[routeIndex], route[newIndex], E.clip(0,1,(step+1)%interpSteps/interpSteps)); + let followingPos = route[followingIndex]; + let interpPos = interpolate(oldPos, newPos, E.clip(0,1,step/interpSteps)); + + if (step > 0.5* interpSteps) { + result.course = bearing(interpPos, interpolate(newPos, followingPos, E.clip(0,1,(step-0.5*interpSteps)/interpSteps))); + } else { + result.course = bearing(oldPos, newPos); } - if (step == interpSteps - 1){ - let followingIndex = (routeIndex + 2)%route.length; - newPos = interpolate(route[newIndex], route[followingIndex], E.clip(0,1,1/interpSteps)); - } - - result.lat = oldPos.lat; - result.lon = oldPos.lon; - result.alt = oldPos.ele; - - result.course = bearing(oldPos,newPos); - step++; if (step == interpSteps){ routeIndex = (routeIndex + 1) % route.length; step = 0; } - return result; + return Object.assign(result, interpPos); }); } else if (settings.gps.name == "nofix") { modGps(() => { return { @@ -281,6 +274,7 @@ exports.enable = () => { let currentDir=1000; let currentAlt=500; let currentSats=5; + modGps(() => { currentLat += 0.01; if (currentLat > 50) currentLat = 20; diff --git a/apps/sensortools/metadata.json b/apps/sensortools/metadata.json index f5bace383..48b146617 100644 --- a/apps/sensortools/metadata.json +++ b/apps/sensortools/metadata.json @@ -2,7 +2,7 @@ "id": "sensortools", "name": "Sensor tools", "shortName": "Sensor tools", - "version": "0.04", + "version": "0.05", "description": "Tools for testing and debugging apps that use sensor input", "icon": "icon.png", "type": "bootloader", diff --git a/apps/sensortools/settings.js b/apps/sensortools/settings.js index 231ab8467..ae631e60c 100644 --- a/apps/sensortools/settings.js +++ b/apps/sensortools/settings.js @@ -88,6 +88,12 @@ writeSettings("enabled",v); }, }, + 'Log': { + value: !!settings.log, + onchange: v => { + writeSettings("log",v); + }, + }, 'GPS': ()=>{showSubMenu("GPS","gps",["nop", "staticfix", "nofix", "changingfix", "route", "routeFuzzy"],[]);}, 'Compass': ()=>{showSubMenu("Compass","mag",["nop", "static", "rotate"],[]);}, 'HRM': ()=>{showSubMenu("HRM","hrm",["nop", "static"],["bpmtrippled"],["sin"]);} diff --git a/apps/sensortools/square.gpx b/apps/sensortools/square.gpx new file mode 100644 index 000000000..0220b4261 --- /dev/null +++ b/apps/sensortools/square.gpx @@ -0,0 +1,33 @@ + + + + 1kmsquare + Export from GpsPrune + + + 1kmsquare + 1 + + + 2273 + Lower left + + + 2166 + Top left + + + 2245 + Top right + + + 1994 + Lower right + + + 2273 + Destination + + + + diff --git a/apps/sensortools/squareFuzzy.gpx b/apps/sensortools/squareFuzzy.gpx new file mode 100644 index 000000000..8f73cc72b --- /dev/null +++ b/apps/sensortools/squareFuzzy.gpx @@ -0,0 +1,144 @@ + + + + 1kmsquare100 + Export from GpsPrune + + + 1kmsquare100 + 1 + + + 2265 + Lower left + + + 2256 + + + 2230 + + + 2219 + + + 2199 + + + 2182 + + + 2189 + + + 2180 + + + 2191 + + + 2185 + + + 2186 + + + 2166 + Top left + + + 2171 + + + 2194 + + + 2207 + + + 2212 + + + 2262 + + + 2278 + + + 2297 + + + 2303 + + + 2251 + + + 2245 + Top right + + + 2195 + + + 2163 + + + 2131 + + + 2095 + + + 2066 + + + 2037 + + + 1993 + + + 1967 + + + 1949 + + + 1972 + + + 2011 + Lower right + + + 2044 + + + 2061 + + + 2065 + + + 2071 + + + 2102 + + + 2147 + + + 2197 + + + 2228 + + + 2265 + Destination + + + + diff --git a/apps/sunrise/ChangeLog b/apps/sunrise/ChangeLog new file mode 100644 index 000000000..490992812 --- /dev/null +++ b/apps/sunrise/ChangeLog @@ -0,0 +1,4 @@ +0.01: First release +0.02: Faster sinus line and fix button to open menu +0.03: Show day/month, add animations, fix !mylocation and text glitch +0.04: Always show the widgets, swifter animations and lighter sea line diff --git a/apps/sunrise/README.md b/apps/sunrise/README.md new file mode 100644 index 000000000..bafe9f76c --- /dev/null +++ b/apps/sunrise/README.md @@ -0,0 +1,25 @@ +# sunrise watchface + +This app mimics the Apple Watch watchface that shows the sunrise and sunset time. + +This is a work-in-progress app, so you may expect missfeatures, bugs and heavy +battery draining. There's still a lot of things to optimize and improve, so take +this into account before complaining :-) + +* Requires to configure the location in Settings -> Apps -> My Location +* Shows sea level and make the sun/moon glow depending on the x position +* The sinus is fixed, so the sea level is curved to match the sunrise/sunset positions) + +## TODO + +* Improved gradients and add support for banglejs1 +* Faster rendering, by reducing sinus stepsize, only refreshing whats needed, etc +* Show red vertical lines or dots inside the sinus if there are alarms + +## Author + +Written by pancake in 2023 + +## Screenshots + +![sunrise](screenshot.png) diff --git a/apps/sunrise/app-icon.js b/apps/sunrise/app-icon.js new file mode 100644 index 000000000..79e0fded7 --- /dev/null +++ b/apps/sunrise/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A/AH4A/AH4ADgMiAAMgCyoABiAXQiUjkQBCkIXQFYMzAAIEBIyMjn8z+cyMKECFwM+93uGAJIPgUzn4WBC4IwBIx0jC4njmQvOkUSkQXTicQL4JHSgSFBgUyO6QkCU4YWBF4KnLFwTXGIwMQRZQ7EC4IxBAYRHKgQjEVIIXCkcgiQwJQQxhBAAJQCGBBdEABIwIfJwmHFxwnFAYQuOFAuIcAMwC54pE1WpWgURMKUxhUKzWqDYLOKVQh1FGoOTnQaKdAR1HhWqzWKkUykK7GkKkM1WZyRsCGAikPhWZ1EzGoKHBaZ5rEGoQWRNgoXVAH4A5")) diff --git a/apps/sunrise/app.js b/apps/sunrise/app.js new file mode 100644 index 000000000..3feb4dfd4 --- /dev/null +++ b/apps/sunrise/app.js @@ -0,0 +1,401 @@ +// banglejs app made by pancake +// sunrise/sunset script by Matt Kane from https://github.com/Triggertrap/sun-js + +const LOCATION_FILE = 'mylocation.json'; +let location; + +Bangle.setUI('clock'); +Bangle.loadWidgets(); +// requires the myLocation app +function loadLocation () { + try { + return require('Storage').readJSON(LOCATION_FILE, 1); + } catch (e) { + return { lat: 41.38, lon: 2.168 }; + } +} +let frames = 0; // amount of pending frames to render (0 if none) +let curPos = 0; // x position of the sun +let realPos = 0; // x position of the sun depending on currentime +const latlon = loadLocation() || {}; +const lat = latlon.lat || 41.38; +const lon = latlon.lon || 2.168; + +/** + * Sunrise/sunset script. By Matt Kane. + * + * Based loosely and indirectly on Kevin Boone's SunTimes Java implementation + * of the US Naval Observatory's algorithm. + * + * Copyright © 2012 Triggertrap Ltd. All rights reserved. + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, + * or connect to: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + */ + +Date.prototype.sunrise = function (latitude, longitude, zenith) { + return this.sunriseSet(latitude, longitude, true, zenith); +}; + +Date.prototype.sunset = function (latitude, longitude, zenith) { + return this.sunriseSet(latitude, longitude, false, zenith); +}; + +Date.prototype.sunriseSet = function (latitude, longitude, sunrise, zenith) { + if (!zenith) { + zenith = 90.8333; + } + + const hoursFromMeridian = longitude / Date.DEGREES_PER_HOUR; + const dayOfYear = this.getDayOfYear(); + let approxTimeOfEventInDays; + let sunMeanAnomaly; + let sunTrueLongitude; + let ascension; + let rightAscension; + let lQuadrant; + let raQuadrant; + let sinDec; + let cosDec; + let localHourAngle; + let localHour; + let localMeanTime; + let time; + + if (sunrise) { + approxTimeOfEventInDays = dayOfYear + ((6 - hoursFromMeridian) / 24); + } else { + approxTimeOfEventInDays = dayOfYear + ((18.0 - hoursFromMeridian) / 24); + } + + sunMeanAnomaly = (0.9856 * approxTimeOfEventInDays) - 3.289; + + sunTrueLongitude = sunMeanAnomaly + (1.916 * Math.sinDeg(sunMeanAnomaly)) + (0.020 * Math.sinDeg(2 * sunMeanAnomaly)) + 282.634; + sunTrueLongitude = Math.mod(sunTrueLongitude, 360); + + ascension = 0.91764 * Math.tanDeg(sunTrueLongitude); + rightAscension = 360 / (2 * Math.PI) * Math.atan(ascension); + rightAscension = Math.mod(rightAscension, 360); + + lQuadrant = Math.floor(sunTrueLongitude / 90) * 90; + raQuadrant = Math.floor(rightAscension / 90) * 90; + rightAscension = rightAscension + (lQuadrant - raQuadrant); + rightAscension /= Date.DEGREES_PER_HOUR; + + sinDec = 0.39782 * Math.sinDeg(sunTrueLongitude); + cosDec = Math.cosDeg(Math.asinDeg(sinDec)); + cosLocalHourAngle = ((Math.cosDeg(zenith)) - (sinDec * (Math.sinDeg(latitude)))) / (cosDec * (Math.cosDeg(latitude))); + + localHourAngle = Math.acosDeg(cosLocalHourAngle); + + if (sunrise) { + localHourAngle = 360 - localHourAngle; + } + + localHour = localHourAngle / Date.DEGREES_PER_HOUR; + + localMeanTime = localHour + rightAscension - (0.06571 * approxTimeOfEventInDays) - 6.622; + + time = localMeanTime - (longitude / Date.DEGREES_PER_HOUR); + time = Math.mod(time, 24); + + const midnight = new Date(0); + // midnight.setUTCFullYear(this.getUTCFullYear()); + // midnight.setUTCMonth(this.getUTCMonth()); + // midnight.setUTCDate(this.getUTCDate()); + + const milli = midnight.getTime() + (time * 60 * 60 * 1000); + + return new Date(milli); +}; + +Date.DEGREES_PER_HOUR = 360 / 24; + +// Utility functions + +Date.prototype.getDayOfYear = function () { + const onejan = new Date(this.getFullYear(), 0, 1); + return Math.ceil((this - onejan) / 86400000); +}; + +Math.degToRad = function (num) { + return num * Math.PI / 180; +}; + +Math.radToDeg = function (radians) { + return radians * 180.0 / Math.PI; +}; + +Math.sinDeg = function (deg) { + return Math.sin(deg * 2.0 * Math.PI / 360.0); +}; + +Math.acosDeg = function (x) { + return Math.acos(x) * 360.0 / (2 * Math.PI); +}; + +Math.asinDeg = function (x) { + return Math.asin(x) * 360.0 / (2 * Math.PI); +}; + +Math.tanDeg = function (deg) { + return Math.tan(deg * 2.0 * Math.PI / 360.0); +}; + +Math.cosDeg = function (deg) { + return Math.cos(deg * 2.0 * Math.PI / 360.0); +}; + +Math.mod = function (a, b) { + let result = a % b; + if (result < 0) { + result += b; + } + return result; +}; + +const delta = 2; +const sunrise = new Date().sunrise(lat, lon); +const sr = sunrise.getHours() + ':' + sunrise.getMinutes(); +console.log('sunrise', sunrise); +const sunset = new Date().sunset(lat, lon); +const ss = sunset.getHours() + ':' + sunset.getMinutes(); +console.log('sunset', sunset); + +const w = g.getWidth(); +const h = g.getHeight(); +const oy = h / 1.7; + +let sunRiseX = 0; +let sunSetX = 0; +const sinStep = 12; + +function drawSinuses () { + let x = 0; + + g.setColor(0, 0, 0); + // g.fillRect(0,oy,w, h); + g.setColor(1, 1, 1); + let y = oy; + for (i = 0; i < w; i++) { + x = i; + x2 = x + sinStep + 1; + y2 = ypos(i); + if (x == 0) { + y = y2; + } + g.drawLine(x, y, x2, y2); + y = y2; + i += sinStep; // no need to draw all steps + } + + // sea level line + const hh0 = sunrise.getHours(); + const hh1 = sunset.getHours(); + const sl0 = seaLevel(hh0); + const sl1 = seaLevel(hh1); + sunRiseX = xfromTime(hh0) + (r / 2); + sunSetX = xfromTime(hh1) + (r / 2); + g.setColor(0, 0.5, 1); + g.drawLine(0, sl0, w, sl1); + g.setColor(0, 0.5, 1); + g.drawLine(0, sl0 + 1, w, sl1 + 1); + /* + g.setColor(0, 0, 1); + g.drawLine(0, sl0 + 1, w, sl1 + 1); + g.setColor(0, 0, 0.5); + g.drawLine(0, sl0 + 2, w, sl1 + 2); + */ +} + +function drawTimes () { + g.setColor(1, 1, 1); + g.setFont('6x8', 2); + g.drawString(sr, 10, h - 20); + g.drawString(ss, w - 60, h - 20); +} + +let pos = 0; +let realTime = true; +const r = 10; + +function drawGlow () { + const now = new Date(); + if (frames < 1 && realTime) { + pos = xfromTime(now.getHours()); + } + const rh = r / 2; + const x = pos; + const y = ypos(x - r); + const r2 = 0; + if (x > sunRiseX && x < sunSetX) { + g.setColor(0.2, 0.2, 0); + g.fillCircle(x, y, r + 20); + g.setColor(0.5, 0.5, 0); + // wide glow + } else { + g.setColor(0.2, 0.2, 0); + } + // smol glow + g.fillCircle(x, y, r + 8); +} + +function seaLevel (hour) { + // hour goes from 0 to 24 + // to get the X we divide the screen in 24 + return ypos(xfromTime(hour)); +} + +function ypos (x) { + const pc = (x * 100 / w); + return oy + (32 * Math.sin(1.7 + (pc / 16))); +} + +function xfromTime (t) { + return (w / 24) * t; +} + +function drawBall () { + let x = pos; + const now = new Date(); + if (frames < 1 && realTime) { + x = xfromTime(now.getHours()); + } + const y = ypos(x - r); + + // glow + if (x < sunRiseX || x > sunSetX) { + g.setColor(0.5, 0.5, 0); + } else { + g.setColor(1, 1, 1); + } + const rh = r / 2; + g.fillCircle(x, y, r); + g.setColor(1, 1, 0); + g.drawCircle(x, y, r); +} +function drawClock () { + const now = new Date(); + + let curTime = ''; + let fhours = 0.0; + let fmins = 0.0; + let ypos = 32; + if (realTime) { + fhours = now.getHours(); + fmins = now.getMinutes(); + } else { + ypos = 32; + fhours = 24 * (pos / w); + if (fhours > 23) { + fhours = 0; + } + const nexth = 24 * 60 * (pos / w); + fmins = 59 - ((24 * 60) - nexth) % 60; + if (fmins < 0) { + fmins = 0; + } + } + if (fmins > 59) { + fmins = 59; + } + const hours = ((fhours < 10) ? '0' : '') + (0 | fhours); + const mins = ((fmins < 10) ? '0' : '') + (0 | fmins); + curTime = hours + ':' + mins; + g.setFont('Vector', 30); + if (realTime) { + g.setColor(1, 1, 1); + } else { + g.setColor(0, 1, 1); + } + g.drawString(curTime, w / 1.9, ypos); + // day-month + if (realTime) { + const mo = now.getMonth() + 1; + const da = now.getDate(); + const daymonth = '' + da + '/' + mo; + g.setFont('6x8', 2); + g.drawString(daymonth, 5, 30); + } +} + +function renderScreen () { + g.setColor(0, 0, 0); + g.fillRect(0, 30, w, h); + realPos = xfromTime((new Date()).getHours()); + g.setFontAlign(-1, -1, 0); + + Bangle.drawWidgets(); + + drawGlow(); + drawSinuses(); + drawTimes(); + drawClock(); + drawBall(); +} + +Bangle.on('drag', function (tap, top) { + if (tap.y < h / 3) { + curPos = pos; + initialAnimation(); + } else { + pos = tap.x - 5; + realTime = false; + } + renderScreen(); +}); + +Bangle.on('lock', () => { + // TODO: render animation here + realTime = Bangle.isLocked(); + renderScreen(); +}); + +renderScreen(); + +realPos = xfromTime((new Date()).getHours()); + +function initialAnimationFrame () { + let distance = (realPos - curPos) / 4; + if (distance > 20) { + distance = 20; + } + curPos += distance; + pos = curPos; + renderScreen(); + if (curPos >= realPos) { + frame = 0; + } + frames--; + if (frames-- > 0) { + setTimeout(initialAnimationFrame, 50); + } else { + realTime = true; + renderScreen(); + } +} + +function initialAnimation () { + const distance = Math.abs(realPos - pos); + frames = distance / 4; + realTime = false; + initialAnimationFrame(); +} + +function main () { + g.setBgColor(0, 0, 0); + g.clear(); + setInterval(renderScreen, 60 * 1000); + pos = 0; + initialAnimation(); +} + +main(); diff --git a/apps/sunrise/app.png b/apps/sunrise/app.png new file mode 100644 index 000000000..a170dfa35 Binary files /dev/null and b/apps/sunrise/app.png differ diff --git a/apps/sunrise/metadata.json b/apps/sunrise/metadata.json new file mode 100644 index 000000000..051ff99bd --- /dev/null +++ b/apps/sunrise/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "sunrise", + "name": "Sunrise", + "shortName": "Sunrise", + "version": "0.04", + "type": "clock", + "description": "Show sunrise and sunset times", + "icon": "app.png", + "allow_emulator": true, + "tags": "clock", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "sunrise.app.js", + "url": "app.js" + }, + { + "name": "sunrise.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.png" + } + ] +} diff --git a/apps/sunrise/screenshot.png b/apps/sunrise/screenshot.png new file mode 100644 index 000000000..c85cdfbe9 Binary files /dev/null and b/apps/sunrise/screenshot.png differ diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog index e7ad4555c..f62e10940 100644 --- a/apps/swiperclocklaunch/ChangeLog +++ b/apps/swiperclocklaunch/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fix issue with mode being undefined 0.03: Update setUI to work with new Bangle.js 2v13 menu style 0.04: Update to work with new 'fast switch' clock->launcher functionality +0.05: Keep track of event listeners we "overwrite", and remove them at the start of setUI diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js index ea00a6735..11abb84c9 100644 --- a/apps/swiperclocklaunch/boot.js +++ b/apps/swiperclocklaunch/boot.js @@ -1,7 +1,13 @@ (function() { var sui = Bangle.setUI; + var oldSwipe; + Bangle.setUI = function(mode, cb) { + if (oldSwipe && oldSwipe !== Bangle.swipeHandler) + Bangle.removeListener("swipe", oldSwipe); sui(mode,cb); + oldSwipe = Bangle.swipeHandler; + if(!mode) return; if ("object"==typeof mode) mode = mode.mode; if (mode.startsWith("clock")) { diff --git a/apps/swiperclocklaunch/metadata.json b/apps/swiperclocklaunch/metadata.json index 4f27da528..d46c56693 100644 --- a/apps/swiperclocklaunch/metadata.json +++ b/apps/swiperclocklaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "swiperclocklaunch", "name": "Swiper Clock Launch", - "version": "0.04", + "version": "0.05", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", "type": "bootloader", diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog index f7ced965b..1282b846f 100644 --- a/apps/widbatv/ChangeLog +++ b/apps/widbatv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New widget 0.02: Make color depend on level +0.03: Stop battery widget clearing too far down \ No newline at end of file diff --git a/apps/widbatv/metadata.json b/apps/widbatv/metadata.json index 74e374601..d4cbf46ac 100644 --- a/apps/widbatv/metadata.json +++ b/apps/widbatv/metadata.json @@ -1,7 +1,7 @@ { "id": "widbatv", "name": "Battery Level Widget (Vertical)", - "version": "0.02", + "version": "0.03", "description": "Slim, vertical battery widget that only takes up 14px", "icon": "widget.png", "type": "widget", diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js index efc42fdad..f26733648 100644 --- a/apps/widbatv/widget.js +++ b/apps/widbatv/widget.js @@ -12,7 +12,7 @@ WIDGETS["batv"]={area:"tr",width:14,draw:function() { if (Bangle.isCharging()) { g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); } else { - g.clearRect(x,y,x+14,y+24); + g.clearRect(x,y,x+14,y+23); g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2); var battery = E.getBattery(); if (battery < 20) {g.setColor("#f00");} diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog index 4c2132122..74d31ada6 100644 --- a/apps/widbt/ChangeLog +++ b/apps/widbt/ChangeLog @@ -5,3 +5,4 @@ 0.06: Tweaking colors for dark/light themes and low bpp screens 0.07: Memory usage improvements 0.08: Disable LCD on, on bluetooth status change +0.09: Fix widget not showing on blue background diff --git a/apps/widbt/metadata.json b/apps/widbt/metadata.json index 1623db7a1..ec03fb353 100644 --- a/apps/widbt/metadata.json +++ b/apps/widbt/metadata.json @@ -1,7 +1,7 @@ { "id": "widbt", "name": "Bluetooth Widget", - "version": "0.08", + "version": "0.09", "description": "Show the current Bluetooth connection status in the top right of the clock", "icon": "widget.png", "type": "widget", diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index c7ef8c0ad..31b8e12d8 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,9 +1,14 @@ WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { g.reset(); - if (NRF.getSecurityStatus().connected) - g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - else + if (NRF.getSecurityStatus().connected) { + if (g.getBgColor() === 31) { // If background color is blue use cyan instead + g.setColor("#0ff"); + } else { + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + } + } else { g.setColor(g.theme.dark ? "#666" : "#999"); + } g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); },changed:function() { WIDGETS["bluetooth"].draw(); diff --git a/apps/widclkinfo/ChangeLog b/apps/widclkinfo/ChangeLog index 3ca502120..6c3b85b00 100644 --- a/apps/widclkinfo/ChangeLog +++ b/apps/widclkinfo/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Widget! -0.02: Now use an app ID (to avoid conflicts with clocks that also use ClockInfo) \ No newline at end of file +0.02: Now use an app ID (to avoid conflicts with clocks that also use ClockInfo) +0.03: Fix widget clearing too far down \ No newline at end of file diff --git a/apps/widclkinfo/metadata.json b/apps/widclkinfo/metadata.json index aca046d90..4ba9b2444 100644 --- a/apps/widclkinfo/metadata.json +++ b/apps/widclkinfo/metadata.json @@ -1,6 +1,6 @@ { "id": "widclkinfo", "name": "Clock Info Widget", - "version":"0.02", + "version":"0.03", "description": "Use 'Clock Info' in the Widget bar. Tap on the widget to select, then drag up/down/left/right to choose what information is displayed.", "icon": "widget.png", "screenshots" : [ { "url":"screenshot.png" }], diff --git a/apps/widclkinfo/widget.js b/apps/widclkinfo/widget.js index 95bc9a111..a802ba872 100644 --- a/apps/widclkinfo/widget.js +++ b/apps/widclkinfo/widget.js @@ -32,7 +32,7 @@ if (!require("clock_info").loadCount) { // don't load if a clock_info was alread // indicate focus - make background reddish //if (clockInfoMenu.focus) g.setBgColor(g.blendColor(g.theme.bg, "#f00", 0.25)); if (clockInfoMenu.focus) g.setColor("#f00"); - g.clearRect(o.x, o.y, o.x+o.w-1, o.y+o.h); + g.clearRect(o.x, o.y, o.x+o.w-1, o.y+o.h-1); if (clockInfoInfo) { var x = o.x; if (clockInfoInfo.img) { diff --git a/modules/widget_utils.js b/modules/widget_utils.js index e83555729..7124ac8c8 100644 --- a/modules/widget_utils.js +++ b/modules/widget_utils.js @@ -70,13 +70,13 @@ exports.swipeOn = function(autohide) { // force app rect to be fullscreen Bangle.appRect = { x: 0, y: 0, w: g.getWidth(), h: g.getHeight(), x2: g.getWidth()-1, y2: g.getHeight()-1 }; // setup offscreen graphics for widgets - let og = Graphics.createArrayBuffer(g.getWidth(),24,16,{msb:true}); + let og = Graphics.createArrayBuffer(g.getWidth(),26,16,{msb:true}); og.theme = g.theme; og._reset = og.reset; og.reset = function() { return this._reset().setColor(g.theme.fg).setBgColor(g.theme.bg); }; - og.reset().clearRect(0,0,og.getWidth(),og.getHeight()); + og.reset().clearRect(0,0,og.getWidth(),23).fillRect(0,24,og.getWidth(),25); let _g = g; let offset = -24; // where on the screen are we? -24=hidden, 0=full visible @@ -146,4 +146,4 @@ exports.swipeOn = function(autohide) { }; Bangle.on("swipe", exports.swipeHandler); Bangle.drawWidgets(); -}; +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5e531b4a6..eb7270554 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2166 @@ { "name": "BangleApps", "version": "0.0.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "BangleApps", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "acorn": "^7.2.0" + }, + "devDependencies": { + "eslint": "^8.14.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.26.0", + "npm-watch": "^0.11.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.17.0.tgz", + "integrity": "sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "dependencies": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-watch": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.11.0.tgz", + "integrity": "sha512-wAOd0moNX2kSA2FNvt8+7ORwYaJpQ1ZoWjUYdb1bBCxq4nkWuU0IiJa9VpVxrj5Ks+FGXQd62OC/Bjk0aSr+dg==", + "dev": true, + "dependencies": { + "nodemon": "^2.0.7", + "through2": "^4.0.2" + }, + "bin": { + "npm-watch": "cli.js" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, "dependencies": { "@eslint/eslintrc": { "version": "1.3.0", @@ -59,7 +2217,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.6", @@ -1391,6 +3550,15 @@ } } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -1413,15 +3581,6 @@ "es-abstract": "^1.19.5" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
DateTypeDate/Time SummaryOn?