diff --git a/.gitignore b/.gitignore index 273fdeae4..523dc5f20 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ appdates.csv _config.yml tests/Layout/bin/tmp.* tests/Layout/testresult.bmp -apps.json +apps.local.json \ No newline at end of file diff --git a/apps.json b/apps.json index 1455a2b13..537a4f697 100644 --- a/apps.json +++ b/apps.json @@ -1,25 +1,38 @@ --- +# ================================================================= +# ALL THE INFORMATION INSIDE APPS.JSON HAS NOW BEEN MOVED +# +# You'll find it inside a file called apps/yourapp/metadata.json +# +# Otherwise nothing has changed. GitHub Pages will automatically +# create apps.json as your site is hosted, or if you're hosting +# yourself you can run bin/create_apps_json.sh +# +# If you serve the store from localhost for development/testing, +# the loader looks for apps.local.json instead, you can run +# `bin/create_apps_json.sh apps.local.json` to create that file. +# ================================================================= + +# Uncomment the following line if you only want explicitly listed +# apps to be available on your site + +# restricted: ["boot", "launch", "antonclk", "health", "setting", "about", "widbat", "widbt", "widlock", "widid"] --- -{% comment %} -================================================================= - ALL THE INFORMATION INSIDE APPS.JSON HAS NOW BEEN MOVED - - You'll find it inside a file called apps/yourapp/metadata.json - - Otherwise nothing has changed. GitHub Pages will automatically - create apps.json as your site is hosted, or if you're hosting - yourself you can run bin/create_apps_json.sh - -================================================================= -{% endcomment %} -{%- assign apps = site.static_files | where: "name", "metadata.json" -%} +{%- if page.restricted == nil -%} + {%- assign apps = site.static_files | where: "name", "metadata.json" | map: "path" -%} +{%- else -%} + {%- capture temp -%} + {%- for app in page.restricted %} /apps/{{app}}/metadata.json {%- endfor -%} + {%- endcapture -%} + {%- assign apps = temp | strip | split: " " -%} +{%- endif -%} [ -{%- include_relative {{ apps.first.path }} -%} +{%- include_relative {{ apps.first }} -%} {%- for app in apps offset:1 -%} -,{%- include_relative {{ app.path }} -%} +,{%- include_relative {{ app }} -%} {%- endfor -%} ] diff --git a/apps/UI4swatch/metadata.json b/apps/UI4swatch/metadata.json index b142c1e7d..379d173c3 100644 --- a/apps/UI4swatch/metadata.json +++ b/apps/UI4swatch/metadata.json @@ -8,6 +8,7 @@ "tags": "Color,input,buttons,touch,UI", "supports": ["BANGLEJS"], "readme": "README.md", + "screenshots": [{"url":"UI4swatch_icon.png"},{"url":"UI4swatch_s1.png"}], "storage": [ {"name":"UI4swatch.app.js","url":"app.js"}, {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} diff --git a/apps/activepedom/metadata.json b/apps/activepedom/metadata.json index 22861ed9c..4deb7006d 100644 --- a/apps/activepedom/metadata.json +++ b/apps/activepedom/metadata.json @@ -8,6 +8,7 @@ "tags": "outdoors,widget", "supports": ["BANGLEJS"], "readme": "README.md", + "screenshots": [{"url":"600.png"},{"url":"10600.png"},{"url":"1600.png"}], "storage": [ {"name":"activepedom.wid.js","url":"widget.js"}, {"name":"activepedom.settings.js","url":"settings.js"}, diff --git a/apps/daysl/metadata.json b/apps/daysl/metadata.json index f68e0e8ac..3dfeea745 100644 --- a/apps/daysl/metadata.json +++ b/apps/daysl/metadata.json @@ -5,7 +5,7 @@ "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", "icon": "app.png", "tags": "", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": false, "storage": [ {"name":"daysl.app.js","url":"app.js"}, diff --git a/apps/fd6fdetect/ChangeLog b/apps/fd6fdetect/ChangeLog index b85df5ace..2ab32d15a 100644 --- a/apps/fd6fdetect/ChangeLog +++ b/apps/fd6fdetect/ChangeLog @@ -1,2 +1,2 @@ -0.1: Added source code -0.2: Added a README file +0.01: Added source code +0.02: Added a README file diff --git a/apps/fd6fdetect/metadata.json b/apps/fd6fdetect/metadata.json index c59b19db9..d795ef8da 100644 --- a/apps/fd6fdetect/metadata.json +++ b/apps/fd6fdetect/metadata.json @@ -2,7 +2,7 @@ "id": "fd6fdetect", "name": "fd6fdetect", "shortName": "fd6fdetect", - "version": "0.2", + "version": "0.02", "description": "Allows you to see 0xFD6F beacons near you.", "icon": "app.png", "tags": "tool", diff --git a/apps/mysticdock/ChangeLog b/apps/mysticdock/ChangeLog index 34fe53627..eacafb944 100644 --- a/apps/mysticdock/ChangeLog +++ b/apps/mysticdock/ChangeLog @@ -1 +1 @@ -1.00: First published version. +0.01: First published version. diff --git a/apps/mysticdock/metadata.json b/apps/mysticdock/metadata.json index f594c2f0a..54ebedd93 100644 --- a/apps/mysticdock/metadata.json +++ b/apps/mysticdock/metadata.json @@ -1,7 +1,7 @@ { "id": "mysticdock", "name": "Mystic Dock", - "version": "1.00", + "version": "0.01", "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", "icon": "mystic-dock.png", "type": "dock", diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index 00090fcd1..c81d0cacc 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -9,3 +9,5 @@ 0.09: Added dependancy on Pedometer Widget 0.10: Added Weather line, fixed issues on a Bangle 1, update every minute 0.11: Changed cycle on minute to prevInfo to avoid the 2nd one being the blank line +0.12: Removed dependancy on widpedom, now uses Bangle.getHealthStatus("day").steps + which requires 2.11.27 firmware to reset at midnight diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json index ff0d11256..1a311599c 100644 --- a/apps/pastel/metadata.json +++ b/apps/pastel/metadata.json @@ -2,10 +2,10 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.11", - "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", + "version": "0.12", + "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times. Requires firmware 2.11.27", "icon": "pastel.png", - "dependencies": {"mylocation":"app", "widpedom":"app","weather":"app"}, + "dependencies": {"mylocation":"app","weather":"app"}, "screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}], "type": "clock", "tags": "clock, weather, tool", diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 3e64cdd9c..d9dfb0da5 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -64,13 +64,15 @@ function loadFonts() { require("f_lato").add(Graphics); } -function stepsWidget() { - if (WIDGETS.activepedom !== undefined) { - return WIDGETS.activepedom; - } else if (WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom; +function getSteps() { + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return '???' } - return undefined; } const infoData = { @@ -79,7 +81,7 @@ const infoData = { ID_DAY: { calc: () => {var d = require("locale").dow(new Date).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, ID_SR: { calc: () => 'Sunrise: ' + sunRise }, ID_SS: { calc: () => 'Sunset: ' + sunSet }, - ID_STEP: { calc: () => 'Steps: ' + stepsWidget().getSteps() }, + ID_STEP: { calc: () => 'Steps: ' + getSteps() }, ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' }, ID_MEM: { calc: () => {var val = process.memory(); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} }, ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} }, diff --git a/apps/pebbled/ChangeLog b/apps/pebbled/ChangeLog index a678cffdb..9db0e26c5 100644 --- a/apps/pebbled/ChangeLog +++ b/apps/pebbled/ChangeLog @@ -1 +1 @@ -0.1: first release +0.01: first release diff --git a/apps/pebbled/metadata.json b/apps/pebbled/metadata.json index cf80bd73b..c16025f6f 100644 --- a/apps/pebbled/metadata.json +++ b/apps/pebbled/metadata.json @@ -2,7 +2,7 @@ "id": "pebbled", "name": "Pebble Clock with distance", "shortName": "Pebble + distance", - "version": "0.1", + "version": "0.01", "description": "Fork of Pebble Clock with distance in KM. Both step count and the distance are on the main screen. Default step length = 0.75m (can be changed in settings).", "readme": "README.md", "icon": "pebbled.png", diff --git a/apps/presentor/ChangeLog b/apps/presentor/ChangeLog index 00833cf91..807f41e79 100644 --- a/apps/presentor/ChangeLog +++ b/apps/presentor/ChangeLog @@ -1,8 +1,8 @@ -0.1: Start of app. -0.5: BLE keyboard functionality. -1.0: BLE mouse functionality to scroll back/forward. -1.5: Added accelerator style mouse. -2.0: Added touchpad style mouse. -2.1: Initial internal git(hub) release. Added icon and such. -2.2: Begin work on presentation parts. -3.0: Presentation parts! \ No newline at end of file +0.01: Start of app. +0.02: BLE keyboard functionality. +0.03: BLE mouse functionality to scroll back/forward. +0.04: Added accelerator style mouse. +0.05: Added touchpad style mouse. +0.06: Initial internal git(hub) release. Added icon and such. +0.07: Begin work on presentation parts. +0.08: Presentation parts! diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index 139a81d90..e5b5e289f 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -1,7 +1,7 @@ { "id": "presentor", "name": "Presentor", - "version": "3.0", + "version": "0.08", "description": "Use your Bangle to present!", "icon": "app.png", "type": "app", diff --git a/apps/ruuviwatch/ChangeLog b/apps/ruuviwatch/ChangeLog index ebde871fa..7953548cb 100644 --- a/apps/ruuviwatch/ChangeLog +++ b/apps/ruuviwatch/ChangeLog @@ -1,2 +1,2 @@ -1.00: Hello Ruuvi Watch! -1.01: Clear gfx on startup. \ No newline at end of file +0.01: Hello Ruuvi Watch! +0.02: Clear gfx on startup. diff --git a/apps/ruuviwatch/metadata.json b/apps/ruuviwatch/metadata.json index 215df105a..12f9ff4a0 100644 --- a/apps/ruuviwatch/metadata.json +++ b/apps/ruuviwatch/metadata.json @@ -2,7 +2,7 @@ "name": "Ruuvi Watch", "shortName":"Ruuvi Watch", "icon": "ruuviwatch.png", - "version":"1.01", + "version":"0.02", "description": "Keep an eye on RuuviTag devices (https://ruuvi.com). Only shows RuuviTags using the v5 format.", "readme":"README.md", "tags": "bluetooth", diff --git a/apps/showimg/ChangeLog b/apps/showimg/ChangeLog index 296bc78d0..e8d890f83 100644 --- a/apps/showimg/ChangeLog +++ b/apps/showimg/ChangeLog @@ -1,2 +1,2 @@ -0.1: Initial release -0.2: Fixed launcher image +0.01: Initial release +0.02: Fixed launcher image diff --git a/apps/showimg/metadata.json b/apps/showimg/metadata.json index 56b2f5057..d5e44c0ee 100644 --- a/apps/showimg/metadata.json +++ b/apps/showimg/metadata.json @@ -2,7 +2,7 @@ "id": "showimg", "name": "simple image viewer", "shortName":"showImage", - "version":"0.2", + "version":"0.02", "description": "Displays the image in \"showimg.user.img\". The file has to be uploaded via the espruino IDE. Returns to watch face after 60s or button push. I use it to display my vaccination certificate.", "icon": "app.png", "tags": "tool", diff --git a/apps/sonicclk/Changelog b/apps/sonicclk/Changelog index 7c83f6988..1f1fc7386 100644 --- a/apps/sonicclk/Changelog +++ b/apps/sonicclk/Changelog @@ -1,2 +1,2 @@ -1.00 Added sonic clock app -1.01 Fixed text alignment issue; Increased acceleration required to activate twist; \ No newline at end of file +0.01 Added sonic clock app +0.02 Fixed text alignment issue; Increased acceleration required to activate twist; diff --git a/apps/sonicclk/metadata.json b/apps/sonicclk/metadata.json index 68a4090ed..5c907347f 100644 --- a/apps/sonicclk/metadata.json +++ b/apps/sonicclk/metadata.json @@ -1,7 +1,7 @@ { "id": "sonicclk", "name": "Sonic Clock", - "version": "1.01", + "version": "0.02", "description": "A classic sonic clock featuring run, stop and wait animations.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index de5c9c221..09d33f615 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -5,6 +5,6 @@ 0.05: Add setting to turn vibrate on/off. 0.06: Tweaks to vibration settings. 0.07: Switch to BTN1 for Max toggle and reset function. -1.00: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted. -1.01: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. -1.02: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. +0.08: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted. +0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight. +0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings. diff --git a/apps/speedalt/metadata.json b/apps/speedalt/metadata.json index e90770985..458023278 100644 --- a/apps/speedalt/metadata.json +++ b/apps/speedalt/metadata.json @@ -2,7 +2,7 @@ "id": "speedalt", "name": "GPS Adventure Sports", "shortName": "GPS Adv Sport", - "version": "1.02", + "version": "0.10", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "icon": "app.png", "type": "app", diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog index fa2e32f5b..09a5eb8cc 100644 --- a/apps/speedalt2/ChangeLog +++ b/apps/speedalt2/ChangeLog @@ -1,4 +1,3 @@ 0.01: Initial import. -0.07: Add swipe to change screens. -1.06: Misc memory and screen optimisations. -1.10: ... +0.02: Add swipe to change screens. +0.03: Misc memory and screen optimisations. diff --git a/apps/speedalt2/metadata.json b/apps/speedalt2/metadata.json index f05611338..a78039f54 100644 --- a/apps/speedalt2/metadata.json +++ b/apps/speedalt2/metadata.json @@ -2,7 +2,7 @@ "id": "speedalt2", "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", - "version":"1.10", + "version":"0.03", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "icon": "app.png", "type": "app", diff --git a/apps/teatimer/metadata.json b/apps/teatimer/metadata.json index d82f10322..acace0402 100644 --- a/apps/teatimer/metadata.json +++ b/apps/teatimer/metadata.json @@ -1,7 +1,7 @@ { "id": "teatimer", "name": "Tea Timer", - "version": "1.00", + "version": "0.01", "description": "A simple timer. You can easyly set up the time.", "icon": "teatimer.png", "type": "app", diff --git a/apps/timerclk/ChangeLog b/apps/timerclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/timerclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/timerclk/README.md b/apps/timerclk/README.md new file mode 100644 index 000000000..fd6d2b16b --- /dev/null +++ b/apps/timerclk/README.md @@ -0,0 +1,72 @@ +# Timer Clock + +A clock based on the Anton Clock with stopwatches, timers and alarms based on the Stopwatch Touch style and an alarm widget based on the one from Default alarm & timer. + +## Features + +* two slots for stopwatches / timers on the clock screen +* configurable font and size (Anton font has fixed size) +* stopwatch with modifiable start value +* timer that can be paused +* alarms +* multiple stopwatches, timers and alarms +* stopwatches and timers keep running in the background + +## Images + +![](screenshot.png) + +### Stopwatch + +![](screenshot_stopwatch1.png) + +![](screenshot_stopwatch2.png) + +### Settings + +![](screenshot_settings1.png) + +![](screenshot_settings2.png) + +![](screenshot_settings3.png) + +## Controls + +### Bangle.js 1 + +#### Clock + +* Left: Stopwatch +* Right: Timer +* Button 1 / 2: Alarm + +#### Stopwatch / Timer / Alarm + +* Button 1: + * edit mode: increase + * control mode: play / pause +* Button 2: switch between edit / control mode +* Button 3: + * edit mode: decrease + * control mode: reset / remove +* Left: + * edit mode: previous index + * control mode: previous stopwatch / timer / alarm +* Right: + * edit mode: next index + * control mode: next stopwatch / timer / alarm + +### Bangle.js 2 + +#### Clock + +* Swipe left: Stopwatch +* Swipe right: Timer +* Swipe over date: Alarm + +#### Stopwatch / Timer / Alarm + +* Swipe left: previous stopwatch / timer / alarm +* Swipe right: next stopwatch / timer / alarm +* Swipe up: increase index swiped over +* Swipe down: decrease index swiped over \ No newline at end of file diff --git a/apps/timerclk/alarm.alert.js b/apps/timerclk/alarm.alert.js new file mode 100644 index 000000000..f4b61822a --- /dev/null +++ b/apps/timerclk/alarm.alert.js @@ -0,0 +1,57 @@ +if (timerclkAlarmTimeout) clearInterval(timerclkAlarmTimeout); +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "vibrate":10 +}, settings.alarm||{}); + +function showAlarm(alarm) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + Bangle.setLocked(false); + E.showPrompt("Alarm!",{ + title:"ALARM!", + buttons : {/*LANG*/"Ok":true} + }).then(function(ok) { + buzzCount = 0; + if (ok) { + alarm.last = new Date().getDate(); + } + require("Storage").write("timerclk.alarm.json",JSON.stringify(alarms)); + load(); + }); + function vibrate(counter) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 100); + if (--counter) setTimeout(() => vibrate(counter), 250); + } + function buzz() { + if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence + vibrate(4); + if (buzzCount--) + setTimeout(buzz, 3000); + else { // auto-snooze + buzzCount = settings.vibrate; + setTimeout(buzz, 600000); + } + } + var buzzCount = settings.vibrate; + buzz(); +} + +// Check for alarms +console.log("checking for alarms..."); +var alarms = require("Storage").readJSON("timerclk.alarm.json",1)||[]; +var active = alarms.filter(e=>e.on); +if (active.length) { + // if there's an alarm, show it + active = active.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000); + if (active[0].last != new Date().getDate()) { + showAlarm(active[0]); + } else { + setTimeout(load, 100); + } +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/timerclk/alarm.info b/apps/timerclk/alarm.info new file mode 100644 index 000000000..1289f8cef --- /dev/null +++ b/apps/timerclk/alarm.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Alarm","src":"timerclk.alarm.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/alarm.js b/apps/timerclk/alarm.js new file mode 100644 index 000000000..4acaa6cf0 --- /dev/null +++ b/apps/timerclk/alarm.js @@ -0,0 +1,116 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.alarm.json") || []; +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":4, +}, settings = settings.alarm||{}); +var defaultElement = {time:43200000, on:true, last:null}; + +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; +var dragBorderHrsMins=0, dragBorderMinsSecs=0; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function activate() { + all[current].on = !all[current].on; + all[current].last = null; + update(); + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} + +function edit(position, change) { + if (position == 1) all[current].time += change*1000; + else if (position == 2) all[current].time += change*60000; + else if (position == 3) all[current].time += change*3600000; + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} + +var buttons = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:"#f50"}, // remove + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: activate, img: timerclk.play_img, col:"#0ff"}, // active +}; + + +function drawButtons() { + if (all[current].on) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + var timeStr = timerclk.formatTime(all[current].time, false, false, true); + g.drawString(timeStr,x,y); + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(":"); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/app-icon.js b/apps/timerclk/app-icon.js new file mode 100644 index 000000000..278cf4bb6 --- /dev/null +++ b/apps/timerclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/AFHzvmf+f8z/8tnv/vs9/1t/v+kv94jR/H4n/wn4CBAYPwnEP8AFDg/AAoUwAoPgmABBwfQAonwAo0/4gFC4AFE4gFLmGEAoQDBxgFCwEQAIIFIj4FD/k//hNBAoZZBAoc8j6oS8/P+1NAoP63+7+wMCz/u/YEB/v/v4dI1+pAQIFBx/J/2/AoP5tFJr71eA==")) diff --git a/apps/timerclk/app-icon.png b/apps/timerclk/app-icon.png new file mode 100644 index 000000000..074db4ed7 Binary files /dev/null and b/apps/timerclk/app-icon.png differ diff --git a/apps/timerclk/app.js b/apps/timerclk/app.js new file mode 100644 index 000000000..eeb3ac4cd --- /dev/null +++ b/apps/timerclk/app.js @@ -0,0 +1,151 @@ +Graphics.prototype.setFontAnton = function(scale) { +// Actual height 69 (68 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78+(scale<<8)+(1<<16)); +}; + +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "timeFont":"Anton", + "timeFontSize":0, + "dateFont":"6x8", + "dateFontSize":2, + "dowFont":"6x8", + "dowFontSize":2, + "specialFont":"6x8", + "specialFontSize":2, + "shortDate":true, + "showStopwatches":true, + "showTimers":true, +}, settings.clock||{}); + +var stopwatches = [], timers = []; +if (settings.showStopwatches) { + stopwatches = require("Storage").readJSON("timerclk.stopwatch.json") || []; + stopwatches = stopwatches.filter(e=>e.start||e.time); +} +if (settings.showTimers) { + timers = require("Storage").readJSON("timerclk.timer.json") || []; + timers = timers.filter(e=>e.start||e.timeAdd); +} + +// timeout used to update every minute +var drawTimeout; +var drawSpecialTimeout; +// border between time and date/dow +var dragBorder = g.getHeight()/2; + +// schedule a draw for the next minute +function queueDraw(timeout, interval, func) { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(function() { + timeout = undefined; + func(); + }, interval - (Date.now() % interval)); +} + +function drawSpecial() { + var interval = 60000; + var stopwatch = 0, timer = 0, time; + var x = g.getWidth()/4; + g.setColor(g.theme.fg); + g.setFontAlign(0,0).setFont(settings.specialFont, settings.specialFontSize); + var y = Bangle.appRect.y + g.stringMetrics("00:00").height/2; + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y+g.stringMetrics("00:00").height); + + if (stopwatches.length) { + time = timerclk.getTime(stopwatches[stopwatch]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + stopwatch++; + } else if (timers.length > 1) { + time = timers[timer].time - timerclk.getTime(timers[timer]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + timer++; + } + x += g.getWidth()/2; + if (timers.length) { + time = timers[timer].time - timerclk.getTime(timers[timer]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + } else if (stopwatches.length > 1) { + time = timerclk.getTime(stopwatches[stopwatch]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + } + queueDraw(drawSpecialTimeout, interval, drawSpecial); +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + var date = new Date(); + var timeStr = require("locale").time(date,1); + var dateStr = require("locale").date(date,settings.shortDate).toUpperCase(); + var dowStr = require("locale").dow(date).toUpperCase(); + + // draw time + if (settings.timeFont == "Anton") { + g.setFontAlign(0,0).setFont("Anton"); + } else { + g.setFontAlign(0,0).setFont(settings.timeFont, settings.timeFontSize); + } + g.clearRect(Bangle.appRect.x, x-g.stringMetrics(timeStr).height/2, Bangle.appRect.x2, Bangle.appRect.y2); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += g.stringMetrics(timeStr).height/2; + g.setFontAlign(0,0).setFont(settings.dateFont, settings.dateFontSize); + dragBorder = y; + y += g.stringMetrics(dateStr).height/2; + g.drawString(dateStr,x,y); + //draw day of week + y += g.stringMetrics(dateStr).height/2; + g.setFontAlign(0,0).setFont(settings.dowFont, settings.dowFontSize); + y += g.stringMetrics(dowStr).height/2; + g.drawString(dowStr,x,y); + // queue draw in one minute + queueDraw(drawTimeout, 60000, draw); +} + +if (process.env.HWVERSION==1) { + setWatch(()=>load("timerclk.stopwatch.js"), BTN4); + setWatch(()=>load("timerclk.timer.js"), BTN5); + setWatch(()=>load("timerclk.alarm.js"), BTN3); + setWatch(()=>load("timerclk.alarm.js"), BTN1); +} else { + var absY, lastX, lastY; + Bangle.on('drag', e=>{ + if (!e.b) { + if (lastX > 50) { // right + if (absY < dragBorder) { // drag over time + load("timerclk.timer.js"); + }else { // drag over date/dow + load("timerclk.alarm.js"); + } + } else if (lastX < -50) { // left + if (absY < dragBorder) { // drag over time + load("timerclk.stopwatch.js"); + }else { // drag over date/dow + load("timerclk.alarm.js"); + } + } else if (lastY > 50) { // down + } else if (lastY < -50) { // up + } + lastX = 0; + lastY = 0; + } else { + lastX = lastX + e.dx; + lastY = lastY + e.dy; + absY = e.y; + } + }); +} + +Bangle.setUI("clock"); // Show launcher when middle button pressed +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +if (stopwatches || timers) drawSpecial(); diff --git a/apps/timerclk/boot.js b/apps/timerclk/boot.js new file mode 100644 index 000000000..9a09f68f3 --- /dev/null +++ b/apps/timerclk/boot.js @@ -0,0 +1,48 @@ +var timerclkTimerTimeout; +var timerclkAlarmTimeout; +function timerclkCheckTimers() { + if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout); + var timers = require('Storage').readJSON('timerclk.timer.json',1)||[]; + timers = timers.filter(e=>e.start); + if (timers.length) { + timers = timers.sort((a,b)=>{ + var at = a.timeAdd; + if (a.start) at += Date.now()-a.start; + at = a.period-at; + var bt = b.timeAdd; + if (b.start) bt += Date.now()-b.start; + bt = b.period-bt; + return at-bt; + }); + if (!require('Storage').read("timerclk.timer.alert.js")) { + console.log("No timer app!"); + } else { + var time = timers[0].timeAdd; + if (timers[0].start) time += Date.now()-timers[0].start; + time = timers[0].time - time; + if (time<1000) t=1000; + if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout); + timerclkTimerTimeout = setTimeout(() => load("timerclk.timer.alert.js"),time); + } + } +} +function timerclkCheckAlarms() { + if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout); + var alarms = require('Storage').readJSON('timerclk.alarm.json',1)||[]; + var currentTime = require("timerclk.lib.js").getCurrentTime(); + alarms = alarms.filter(e=>e.on); + if (alarms.length) { + alarms = alarms.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000); + if (!require('Storage').read("timerclk.alarm.alert.js")) { + console.log("No alarm app!"); + } else { + var time = alarms[0].time-currentTime; + if (alarms[0].last == new Date().getDate() || time < 0) time += 86400000; + if (time<1000) t=1000; + if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout); + timerclkAlarmTimeout = setTimeout(() => load("timerclk.alarm.alert.js"),time); + } + } +} +timerclkCheckTimers(); +timerclkCheckAlarms(); diff --git a/apps/timerclk/lib.js b/apps/timerclk/lib.js new file mode 100644 index 000000000..718962fe0 --- /dev/null +++ b/apps/timerclk/lib.js @@ -0,0 +1,127 @@ +exports.pause_img = atob("GBiBAf///////////+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B////////////w=="); +exports.play_img = atob("GBiBAf////////////P///D///A///Af//AH//AB//AAf/AAH/AAB/AAB/AAH/AAf/AB//AH//Af//A///D///P//////////////w=="); +exports.reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w=="); +exports.remove_img = atob("GBiBAf///////////+P/x+H/h+D/B/B+D/g8H/wYP/4Af/8A//+B//+B//8A//4Af/wYP/g8H/B+D+D/B+H/h+P/x////////////w=="); + +exports.formatTime = function(t, short, tnthEnable, fullTime) { + var negative = ""; + if (t < 0) { + t = t*(-1); + negative = "-"; + } + let hrs = Math.floor(t/3600000); + let mins = Math.floor(t/60000)%60; + let secs = Math.floor(t/1000)%60; + var tnth = ""; + if (tnthEnable) { + tnth = Math.floor(t/100)%10; + tnth = "."+tnth; + } + var hrsStr = hrs; + if (hrs < 10 && !negative) hrsStr = "0"+hrs; + var text; + if (short) { + if (hrs === 0) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + else text = negative + hrsStr + "/" + ("0"+mins).substr(-2); + } else { + if (hrs === 0 && !fullTime) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + tnth; + else text = negative + hrsStr + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + } + return text; +}; + +exports.getTime = function(e) { + var time = e.timeAdd; + if (e.start) { + time += Date.now() - e.start; + } + return time; +}; + +exports.getCurrentTime = function() { + var date = new Date(); + return date.getHours()*3600000+date.getMinutes()*60000+date.getSeconds()*1000+date.getMilliseconds(); +}; + +exports.registerControls = function(o) { + if (process.env.HWVERSION==1) { + setWatch(()=>{ + if (o.editIndex == 0) o.buttons.play.callback(); + else o.edit(o.editIndex, 1); + o.draw(); + }, BTN1, {repeat:true}); + setWatch(()=>{ + o.editIndex = !o.editIndex; + o.draw(); + }, BTN2, {repeat:true}); + setWatch(()=>{ + if (o.editIndex == 0) o.buttons.reset.callback(); + else o.edit(o.editIndex, -1); + o.draw(); + }, BTN3, {repeat:true}); + setWatch(()=>{ + if (o.editIndex) { + o.editIndex++; + if (o.editIndex > 3) o.editIndex = 1; + } else if (o.current > 0) o.current--; + o.update(); + }, BTN4, {repeat:true}); + setWatch(()=>{ + if (o.editIndex) { + o.editIndex--; + if (o.editIndex < 1) o.editIndex = 3; + } else { + o.current++; + if (o.current == o.all.length) o.all.push(o.defaultElement.clone()); + } + o.update(); + }, BTN5, {repeat:true}); + } else { + setWatch(()=>load(), BTN1); + Bangle.on('touch',(n,e)=>{ + for (var button of o.buttons) { + if (e.x>=button.pos[0] && e.y>=button.pos[1] && + e.x{ + if (!e.b) { + if (lastX > 40) { // right + o.current++; + if (o.current == o.all.length) o.all.push(o.defaultElement.clone()); + } else if (lastX < -40) { // left + if (o.current > 0) { + o.current--; + } + } else if (lastY > 30) { // down + if (absX < o.dragBorderHrsMins) { + o.edit(3, -1); + } else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) { + o.edit(2, -1); + } else { + o.edit(1, -1); + } + } else if (lastY < -30) { // up + if (absX < o.dragBorderHrsMins) { + o.edit(3, 1); + } else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) { + o.edit(2, 1); + } else { + o.edit(1, 1); + } + } + lastX = 0; + lastY = 0; + o.update(); + } else { + absX = e.x; + lastX = lastX + e.dx; + lastY = lastY + e.dy; + } + }); + } +}; diff --git a/apps/timerclk/metadata.json b/apps/timerclk/metadata.json new file mode 100644 index 000000000..6b415c0fc --- /dev/null +++ b/apps/timerclk/metadata.json @@ -0,0 +1,38 @@ +{ + "id": "timerclk", + "name": "Timer Clock", + "shortName":"Timer Clock", + "version":"0.01", + "description": "A clock with stopwatches, timers and alarms build in.", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS","BANGLEJS2"], + "screenshots": [ + {"url":"screenshot.png"}, + {"url":"screenshot_stopwatch1.png"}, + {"url":"screenshot_stopwatch2.png"}, + {"url":"screenshot_settings1.png"}, + {"url":"screenshot_settings2.png"}, + {"url":"screenshot_settings3.png"} + ], + "readme": "README.md", + "storage": [ + {"name":"timerclk.app.js","url":"app.js"}, + {"name":"timerclk.img","url":"app-icon.js","evaluate":true}, + {"name":"timerclk.boot.js","url":"boot.js"}, + {"name":"timerclk.lib.js","url":"lib.js"}, + {"name":"timerclk.wid.js","url":"wid.js"}, + {"name":"timerclk.settings.js","url":"settings.js"}, + {"name":"timerclk.stopwatch.js","url":"stopwatch.js"}, + {"name":"timerclk.timer.js","url":"timer.js"}, + {"name":"timerclk.timer.alert.js","url":"timer.alert.js"}, + {"name":"timerclk.alarm.js","url":"alarm.js"}, + {"name":"timerclk.alarm.alert.js","url":"alarm.alert.js"}, + {"name":"timerclk.stopwatch.info","url":"stopwatch.info"}, + {"name":"timerclk.timer.info","url":"timer.info"}, + {"name":"timerclk.alarm.info","url":"alarm.info"} + ], + "data": [{"name":"timerclk.json"},{"name":"timerclk.stopwatch.json"},{"name":"timerclk.timer.json"},{"name":"timerclk.alarm.json"}], + "sortorder": 0 +} diff --git a/apps/timerclk/pause-24.png b/apps/timerclk/pause-24.png new file mode 100644 index 000000000..7ff72e906 Binary files /dev/null and b/apps/timerclk/pause-24.png differ diff --git a/apps/timerclk/play-24.png b/apps/timerclk/play-24.png new file mode 100644 index 000000000..26fa8d99c Binary files /dev/null and b/apps/timerclk/play-24.png differ diff --git a/apps/timerclk/remove-24.png b/apps/timerclk/remove-24.png new file mode 100644 index 000000000..b59505bcb Binary files /dev/null and b/apps/timerclk/remove-24.png differ diff --git a/apps/timerclk/reset-24.png b/apps/timerclk/reset-24.png new file mode 100644 index 000000000..73fb28dec Binary files /dev/null and b/apps/timerclk/reset-24.png differ diff --git a/apps/timerclk/screenshot.png b/apps/timerclk/screenshot.png new file mode 100644 index 000000000..1bccf6807 Binary files /dev/null and b/apps/timerclk/screenshot.png differ diff --git a/apps/timerclk/screenshot_settings1.png b/apps/timerclk/screenshot_settings1.png new file mode 100644 index 000000000..da187e496 Binary files /dev/null and b/apps/timerclk/screenshot_settings1.png differ diff --git a/apps/timerclk/screenshot_settings2.png b/apps/timerclk/screenshot_settings2.png new file mode 100644 index 000000000..4b12848d0 Binary files /dev/null and b/apps/timerclk/screenshot_settings2.png differ diff --git a/apps/timerclk/screenshot_settings3.png b/apps/timerclk/screenshot_settings3.png new file mode 100644 index 000000000..b1ef2381f Binary files /dev/null and b/apps/timerclk/screenshot_settings3.png differ diff --git a/apps/timerclk/screenshot_stopwatch1.png b/apps/timerclk/screenshot_stopwatch1.png new file mode 100644 index 000000000..f50d7a1d1 Binary files /dev/null and b/apps/timerclk/screenshot_stopwatch1.png differ diff --git a/apps/timerclk/screenshot_stopwatch2.png b/apps/timerclk/screenshot_stopwatch2.png new file mode 100644 index 000000000..89f91ae1b Binary files /dev/null and b/apps/timerclk/screenshot_stopwatch2.png differ diff --git a/apps/timerclk/settings.js b/apps/timerclk/settings.js new file mode 100644 index 000000000..556dded98 --- /dev/null +++ b/apps/timerclk/settings.js @@ -0,0 +1,292 @@ +(function(back) { + const FILE = "timerclk.json"; + const BOOL_FORMAT = v=>v?/*LANG*/"On":/*LANG*/"Off"; + // Load settings + var settings = require('Storage').readJSON(FILE, true) || {} + settings.clock = Object.assign({ + "timeFont":"Anton", + "timeFontSize":0, + "dateFont":"6x8", + "dateFontSize":2, + "dowFont":"6x8", + "dowFontSize":2, + "specialFont":"6x8", + "specialFontSize":2, + "shortDate":true, + "showStopwatches":true, + "showTimers":true, + }, settings.clock||{}); + settings.stopwatch = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + }, settings.stopwatch||{}); + settings.timer = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":10, + }, settings.timer||{}); + settings.alarm = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":10, + }, settings.alarm||{}); + var timeFonts = ["Anton"].concat(g.getFonts()); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + var mainMenu = { + "" : { "title" : "Timer Clock" }, + "< Back" : () => back(), + "Clock": ()=>{E.showMenu(clockMenu);}, + "Stopwatch": ()=>{E.showMenu(stopwatchMenu);}, + "Timer": ()=>{E.showMenu(timerMenu);}, + "Alarm": ()=>{E.showMenu(alarmMenu);}, + }; + var clockMenu = { + "" : { "title" : "Clock" }, + "< Back" : () => E.showMenu(mainMenu), + "time font":{ + value: 0|timeFonts.indexOf(settings.clock.timeFont), + format: v => timeFonts[v], + min: 0, max: timeFonts.length-1, + onchange: v => { + settings.clock.timeFont = timeFonts[v]; + writeSettings(); + } + }, + "time size":{ + value: 0|settings.clock.timeFontSize, + min: 0, + onchange: v => { + settings.clock.timeFontSize = v; + writeSettings(); + } + }, + "date font":{ + value: 0|g.getFonts().indexOf(settings.clock.dateFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.clock.dateFont = g.getFonts()[v]; + writeSettings(); + } + }, + "date size":{ + value: 0|settings.clock.dateFontSize, + min: 0, + onchange: v => { + settings.clock.dateFontSize = v; + writeSettings(); + } + }, + "dow font":{ + value: 0|g.getFonts().indexOf(settings.clock.dowFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.clock.dowFont = g.getFonts()[v]; + writeSettings(); + } + }, + "dow size":{ + value: 0|settings.clock.dowFontSize, + min: 0, + onchange: v => { + settings.clock.dowFontSize = v; + writeSettings(); + } + }, + "short date": { + value: !!settings.clock.shortDate, + format: BOOL_FORMAT, + onchange: v => { + settings.clock.shortDate = v; + writeSettings(); + } + }, + "stopwatches": { + value: !!settings.clock.showStopwatches, + format: v=>v?/*LANG*/"Show":/*LANG*/"Hide", + onchange: v => { + settings.clock.showStopwatches = v; + writeSettings(); + } + }, + "timers": { + value: !!settings.clock.showTimers, + format: v=>v?/*LANG*/"Show":/*LANG*/"Hide", + onchange: v => { + settings.clock.showTimers = v; + writeSettings(); + } + }, + }; + + var stopwatchMenu = { + "" : { "title" : "Stopwatch" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.stopwatch.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.stopwatch.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.stopwatch.fontSize, + min: 0, + onchange: v => { + settings.stopwatch.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.stopwatch.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.stopwatch.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.stopwatch.indexFontSize, + min: 0, + onchange: v => { + settings.stopwatch.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.stopwatch.buttonHeight, + min: 0, + onchange: v => { + settings.stopwatch.buttonHeight = v; + writeSettings(); + } + }, + }; + var timerMenu = { + "" : { "title" : "Timer" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.timer.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.timer.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.timer.fontSize, + min: 0, + onchange: v => { + settings.timer.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.timer.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.timer.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.timer.indexFontSize, + min: 0, + onchange: v => { + settings.timer.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.timer.buttonHeight, + min: 0, + onchange: v => { + settings.timer.buttonHeight = v; + writeSettings(); + } + }, + "vibrate":{ + value: 0|settings.timer.vibrate, + min: 0, + onchange: v=>{ + settings.timer.vibrate = v; + writeSettings(); + } + } + }; + var alarmMenu = { + "" : { "title" : "Alarm" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.alarm.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.alarm.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.alarm.fontSize, + min: 0, + onchange: v => { + settings.alarm.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.alarm.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.alarm.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.alarm.indexFontSize, + min: 0, + onchange: v => { + settings.alarm.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.alarm.buttonHeight, + min: 0, + onchange: v => { + settings.alarm.buttonHeight = v; + writeSettings(); + } + }, + "vibrate":{ + value: 0|settings.alarm.vibrate, + min: 0, + onchange: v=>{ + settings.alarm.vibrate = v; + writeSettings(); + } + } + }; + E.showMenu(mainMenu); +}); diff --git a/apps/timerclk/stopwatch.info b/apps/timerclk/stopwatch.info new file mode 100644 index 000000000..72ad418b1 --- /dev/null +++ b/apps/timerclk/stopwatch.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Stopwatch","src":"timerclk.stopwatch.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/stopwatch.js b/apps/timerclk/stopwatch.js new file mode 100644 index 000000000..8ac6d30a7 --- /dev/null +++ b/apps/timerclk/stopwatch.js @@ -0,0 +1,135 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.stopwatch.json") || []; + +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, +}, settings.stopwatch||{}); +var defaultElement = {start:null, timeAdd:0}; +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + var interval = Math.floor(timerclk.getTime(all[current])/3600000)?1000:100; + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, interval); draw();}, interval - (timerclk.getTime(all[current]) % interval)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function play() { + if (all[current].start) { // running + all[current].timeAdd += Date.now() - all[current].start; + all[current].start = null; + update(); + } else { // paused + all[current].start = Date.now(); + update(); + } + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} +function reset() { + all[current] = defaultElement.clone(); + update(); + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} + +function edit(position, change) { + if (position == 1) all[current].timeAdd += change*1000; + else if (position == 2) all[current].timeAdd += change*60000; + else if (position == 3) all[current].timeAdd += change*3600000; + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} + + +var buttonsRunning = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"}, +}; +var buttonsNormal = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col}, +}; +buttons = buttonsNormal; + +function drawButtons() { + if (all[current].start || all[current].time) { + buttons = buttonsRunning; + if (all[current].start) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + } else { + buttons = buttonsNormal; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + var timeStr = timerclk.formatTime(timerclk.getTime(all[current]), false, true); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + g.drawString(timeStr,x,y); + + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(".")[0].split(":"); + if (timeStr.length < 3) timeStr = [""].concat(timeStr); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/timer.alert.js b/apps/timerclk/timer.alert.js new file mode 100644 index 000000000..f51ea6767 --- /dev/null +++ b/apps/timerclk/timer.alert.js @@ -0,0 +1,62 @@ +if (timerclkTimerTimeout) clearInterval(timerclkTimerTimeout); +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "vibrate":10 +}, settings.timer||{}); + +function showTimer(timer) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + Bangle.setLocked(false); + E.showPrompt("Timer finished!",{ + title:"TIMER!", + buttons : {/*LANG*/"Ok":true} + }).then(function(ok) { + buzzCount = 0; + if (ok) { + timer.time += Date.now() - timer.start; + timer.start = null; + } + require("Storage").write("timerclk.timer.json",JSON.stringify(timers)); + load(); + }); + function vibrate(counter) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 100); + if (--counter) setTimeout(() => vibrate(counter), 250); + } + function buzz() { + if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence + vibrate(4); + if (buzzCount--) + setTimeout(buzz, 3000); + else { // auto-snooze + buzzCount = settings.vibrate; + setTimeout(buzz, 600000); + } + } + var buzzCount = settings.vibrate; + buzz(); +} + +// Check for timers +console.log("checking for timers..."); +var timers = require("Storage").readJSON("timerclk.timer.json",1)||[]; +var active = timers.filter(e=>e.start); +if (active.length) { + // if there's an timer, show it + active = active.sort((a,b)=>{ + var at = a.time; + if (a.start) at += Date.now()-a.start; + at = a.period-at; + var bt = b.time; + if (b.start) bt += Date.now()-b.start; + bt = b.period-bt; + return at-bt; + }); + showTimer(active[0]); +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/timerclk/timer.info b/apps/timerclk/timer.info new file mode 100644 index 000000000..39a338693 --- /dev/null +++ b/apps/timerclk/timer.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Timer","src":"timerclk.timer.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/timer.js b/apps/timerclk/timer.js new file mode 100644 index 000000000..060c07813 --- /dev/null +++ b/apps/timerclk/timer.js @@ -0,0 +1,139 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.timer.json") || []; +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":4, +}, settings = settings.timer||{}); +var defaultElement = {time:300000, start:null, timeAdd:0}; + +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; +var dragBorderHrsMins=0, dragBorderMinsSecs=0; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function play() { + if (all[current].start) { // running + all[current].timeAdd += Date.now() - all[current].start; + all[current].start = null; + update(); + } else { // paused + all[current].start = Date.now(); + update(); + } + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} +function reset() { + all[current] = defaultElement.clone(); + update(); + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} + +function edit(position, change) { + if (position == 1) all[current].time += change*1000; + else if (position == 2) all[current].time += change*60000; + else if (position == 3) all[current].time += change*3600000; + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} + +var buttonsRunning = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"}, +}; +var buttonsNormal = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col}, +}; +buttons = buttonsNormal; + + +function drawButtons() { + if (all[current].start || all[current].timeAdd) { + buttons = buttonsRunning; + if (all[current].start) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + } else { + buttons = buttonsNormal; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + var time = all[current].time - timerclk.getTime(all[current]); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + var timeStr = timerclk.formatTime(time, false, false, true); + g.drawString(timeStr,x,y); + + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(":"); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/wid.js b/apps/timerclk/wid.js new file mode 100644 index 000000000..e3ddeb791 --- /dev/null +++ b/apps/timerclk/wid.js @@ -0,0 +1,7 @@ +WIDGETS["timerclk.alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + WIDGETS["timerclk.alarm"].width = (require('Storage').readJSON('timerclk.alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + } +}; +WIDGETS["timerclk.alarm"].reload(); diff --git a/apps/toucher/metadata.json b/apps/toucher/metadata.json index 5d41c3fe0..8b2715f0c 100644 --- a/apps/toucher/metadata.json +++ b/apps/toucher/metadata.json @@ -7,6 +7,7 @@ "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", + "screenshots": [{"url":"screenshot1.jpg"}], "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/vernierrespirate/Vernier-go-direct-respiration-belt-screenshot.png b/apps/vernierrespirate/Vernier-go-direct-respiration-belt-screenshot.png new file mode 100644 index 000000000..f5d095582 Binary files /dev/null and b/apps/vernierrespirate/Vernier-go-direct-respiration-belt-screenshot.png differ diff --git a/apps/vernierrespirate/metadata.json b/apps/vernierrespirate/metadata.json index a51dc8c70..5e2baf2bb 100644 --- a/apps/vernierrespirate/metadata.json +++ b/apps/vernierrespirate/metadata.json @@ -8,6 +8,7 @@ "tags": "health,bluetooth", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "screenshots": [{"url":"Vernier-go-direct-respiration-belt-screenshot.png"}], "storage": [ {"name":"vernierrespirate.app.js","url":"app.js"}, {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} diff --git a/apps/widChargingStatus/ChangeLog b/apps/widChargingStatus/ChangeLog index d3175e1ab..1033c0cd3 100644 --- a/apps/widChargingStatus/ChangeLog +++ b/apps/widChargingStatus/ChangeLog @@ -1 +1 @@ -0.1: First release. \ No newline at end of file +0.01: First release. diff --git a/apps/widChargingStatus/metadata.json b/apps/widChargingStatus/metadata.json index 84879db01..f68ccf5b4 100644 --- a/apps/widChargingStatus/metadata.json +++ b/apps/widChargingStatus/metadata.json @@ -2,7 +2,7 @@ "name": "Charging Status", "shortName":"ChargingStatus", "icon": "widget.png", - "version":"0.1", + "version":"0.01", "type": "widget", "description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.", "tags": "widget", diff --git a/apps/wid_a_battery_widget/ChangeLog b/apps/wid_a_battery_widget/ChangeLog index 8a1538479..a111d259a 100644 --- a/apps/wid_a_battery_widget/ChangeLog +++ b/apps/wid_a_battery_widget/ChangeLog @@ -1,4 +1,4 @@ -1.00: Release for Bangle 2 (2021/11/18) -1.01: Internal id update to wid_* as per Gordon's request (2021/11/21) -1.02: Support dark themes -1.03: Increase screen update rate when charging +0.01: Release for Bangle 2 (2021/11/18) +0.02: Internal id update to wid_* as per Gordon's request (2021/11/21) +0.03: Support dark themes +0.04: Increase screen update rate when charging diff --git a/apps/wid_a_battery_widget/metadata.json b/apps/wid_a_battery_widget/metadata.json index 6095a16fe..6c507b7b3 100644 --- a/apps/wid_a_battery_widget/metadata.json +++ b/apps/wid_a_battery_widget/metadata.json @@ -3,7 +3,7 @@ "name": "A Battery Widget (with percentage)", "shortName":"A Battery Widget", "icon": "widget.png", - "version":"1.03", + "version":"0.04", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", diff --git a/apps/widbata/ChangeLog b/apps/widbata/ChangeLog index 42293a3aa..f23705341 100644 --- a/apps/widbata/ChangeLog +++ b/apps/widbata/ChangeLog @@ -1,2 +1,3 @@ 0.01: Created 0.02: Set sort order to -10 so always display in right hand corner +0.03: Set sort order from the code diff --git a/apps/widbata/metadata.json b/apps/widbata/metadata.json index fcfafd04e..d8149d13f 100644 --- a/apps/widbata/metadata.json +++ b/apps/widbata/metadata.json @@ -4,13 +4,12 @@ "shortName":"Battery Theme", "icon": "widbata.png", "screenshots": [{"url":"screenshot_widbata_1.png"}], - "version":"0.02", + "version":"0.03", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "description": "Shows the current battery level status in the top right using the clocks colour theme", "tags": "widget,battery", - "sortorder": -10, "storage": [ {"name":"widbata.wid.js","url":"widbata.wid.js"} ] diff --git a/apps/widbata/widbata.wid.js b/apps/widbata/widbata.wid.js index 1c04bf8ae..1de4c2fe9 100644 --- a/apps/widbata/widbata.wid.js +++ b/apps/widbata/widbata.wid.js @@ -2,7 +2,7 @@ setInterval(()=>WIDGETS["bata"].draw(), 60000); Bangle.on('lcdPower', function(on) { if (on) WIDGETS["bata"].draw(); }); -WIDGETS["bata"]={area:"tr",width:27,draw:function() { +WIDGETS["bata"]={area:"tr",sortorder:-10,width:27,draw:function() { var s = 26; var t = 13; // thickness var x = this.x, y = this.y; diff --git a/apps/widbatpc/metadata.json b/apps/widbatpc/metadata.json index cf4f2e13a..7da4e3e0c 100644 --- a/apps/widbatpc/metadata.json +++ b/apps/widbatpc/metadata.json @@ -9,6 +9,7 @@ "tags": "widget,battery", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "screenshots": [{"url":"widbatpc.full.jpg"},{"url":"widbatpc.part.jpg"}], "storage": [ {"name":"widbatpc.wid.js","url":"widget.js"}, {"name":"widbatpc.settings.js","url":"settings.js"} diff --git a/apps/widhwt/metadata.json b/apps/widhwt/metadata.json index 957940c73..375ddf498 100644 --- a/apps/widhwt/metadata.json +++ b/apps/widhwt/metadata.json @@ -7,6 +7,7 @@ "type": "widget", "tags": "widget,tool", "allow_emulator": true, + "screenshots": [{"url":"wash-hand-timer-screenshot.png"}], "supports": ["BANGLEJS", "BANGLEJS2"], "storage": [ {"name":"widhwt.app.js","url":"app.js"}, diff --git a/apps/widhwt/wash-hand-timer-screenshot.png b/apps/widhwt/wash-hand-timer-screenshot.png new file mode 100644 index 000000000..05859cd7a Binary files /dev/null and b/apps/widhwt/wash-hand-timer-screenshot.png differ diff --git a/apps/widlock/ChangeLog b/apps/widlock/ChangeLog index 2ac70f875..bb84c2d44 100644 --- a/apps/widlock/ChangeLog +++ b/apps/widlock/ChangeLog @@ -3,3 +3,4 @@ 0.03: Don't try to be fancy - just bail out on firmwares without a lock event 0.04: Set sortorder to -1 so that widget always takes up the furthest left position 0.05: Set sortorder to -10 so that others can take -1 etc +0.06: Set sortorder to -10 in widget code diff --git a/apps/widlock/metadata.json b/apps/widlock/metadata.json index 0f642b192..8635a5434 100644 --- a/apps/widlock/metadata.json +++ b/apps/widlock/metadata.json @@ -1,13 +1,12 @@ { "id": "widlock", "name": "Lock Widget", - "version": "0.05", + "version": "0.06", "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked", "icon": "widget.png", "type": "widget", "tags": "widget,lock", "supports": ["BANGLEJS","BANGLEJS2"], - "sortorder": -10, "storage": [ {"name":"widlock.wid.js","url":"widget.js"} ] diff --git a/apps/widlock/widget.js b/apps/widlock/widget.js index 4c59e6575..592361cd9 100644 --- a/apps/widlock/widget.js +++ b/apps/widlock/widget.js @@ -4,7 +4,7 @@ WIDGETS["lock"].width = Bangle.isLocked()?16:0; Bangle.drawWidgets(); }); - WIDGETS["lock"]={area:"tl",width:Bangle.isLocked()?16:0,draw:function(w) { + WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:0,draw:function(w) { if (Bangle.isLocked()) g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x+1, w.y+4); }}; diff --git a/apps/widpa/ChangeLog b/apps/widpa/ChangeLog index eb7e543a5..4a8809923 100644 --- a/apps/widpa/ChangeLog +++ b/apps/widpa/ChangeLog @@ -1,3 +1,4 @@ 0.01: First release 0.02: Size widget after step count is reset 0.03: set sortorder to -1 +0.04: set sortorder through code diff --git a/apps/widpa/metadata.json b/apps/widpa/metadata.json index 9fd9311c1..95a8a8a9c 100644 --- a/apps/widpa/metadata.json +++ b/apps/widpa/metadata.json @@ -4,13 +4,12 @@ "shortName":"Simple Pedometer", "icon": "screenshot_widpa.png", "screenshots": [{"url":"screenshot_widpa.png"}], - "version":"0.03", + "version":"0.04", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in 12x16 font, requires firmware v2.11.21 or later", "tags": "widget,battery", - "sortorder": -1, "storage": [ {"name":"widpa.wid.js","url":"widpa.wid.js"} ] diff --git a/apps/widpa/widpa.wid.js b/apps/widpa/widpa.wid.js index 1c0f27394..8cde29bc1 100644 --- a/apps/widpa/widpa.wid.js +++ b/apps/widpa/widpa.wid.js @@ -2,7 +2,7 @@ Bangle.on('step', function(s) { WIDGETS["widpa"].draw(); }); Bangle.on('lcdPower', function(on) { if (on) WIDGETS["widpa"].draw(); }); -WIDGETS["widpa"]={area:"tl",width:13,draw:function() { +WIDGETS["widpa"]={area:"tl",sortorder:-1,width:13,draw:function() { if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off var steps = Bangle.getHealthStatus("day").steps; var w = 1 + (steps.toString().length)*12; diff --git a/apps/widpb/ChangeLog b/apps/widpb/ChangeLog index 8f8b3c5e4..8a4a48b56 100644 --- a/apps/widpb/ChangeLog +++ b/apps/widpb/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fixed widget id to wibpb, Size widget after step count is reset 0.03: Fixed widget id in onStep() come on get it right! 0.04: set sortorder to -1 +0.05: set sortorder through code diff --git a/apps/widpb/metadata.json b/apps/widpb/metadata.json index 222b96c83..0c2ed747a 100644 --- a/apps/widpb/metadata.json +++ b/apps/widpb/metadata.json @@ -4,13 +4,12 @@ "shortName":"Lato Pedometer", "icon": "screenshot_widpb.png", "screenshots": [{"url":"screenshot_widpb.png"}], - "version":"0.04", + "version":"0.05", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "description": "Displays the current step count from `Bangle.getHealthStatus(\"day\").steps` in the Lato font, requires firmware v2.11.21 or later", "tags": "widget,battery", - "sortorder": -1, "storage": [ {"name":"widpb.wid.js","url":"widpb.wid.js"} ] diff --git a/apps/widpb/widpb.wid.js b/apps/widpb/widpb.wid.js index cd830ae4f..ceebb9937 100644 --- a/apps/widpb/widpb.wid.js +++ b/apps/widpb/widpb.wid.js @@ -3,7 +3,7 @@ Bangle.on('step', function(s) { WIDGETS["widpb"].draw(); }); Bangle.on('lcdPower', function(on) { if (on) WIDGETS["widpb"].draw(); }); -WIDGETS["widpb"]={area:"tl",width:13,draw:function() { +WIDGETS["widpb"]={area:"tl",sortorder:-1,width:13,draw:function() { if (!Bangle.isLCDOn()) return; // dont redraw if LCD is off var steps = Bangle.getHealthStatus("day").steps; var w = 1 + (steps.toString().length)*12; diff --git a/bin/apploader.js b/bin/apploader.js index 6b4c2202d..427a0ef99 100755 --- a/bin/apploader.js +++ b/bin/apploader.js @@ -13,6 +13,7 @@ for Noble. var SETTINGS = { pretokenise : true }; +var APPSDIR = __dirname+"/../apps/"; var Utils = require("../core/js/utils.js"); var AppInfo = require("../core/js/appinfo.js"); var noble; @@ -29,18 +30,27 @@ if (!noble) { console.log(" npm install noble") } -var apps; +var apps = []; function ERROR(msg) { console.error(msg); process.exit(1); } -try { - apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); -} catch(e) { - ERROR("'apps.json' could not be loaded"); -} +var apps = []; +var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true}); +dirs.forEach(dir => { + var appsFile; + if (dir.name.startsWith("_example") || !dir.isDirectory()) + return; + try { + appsFile = require("fs").readFileSync(APPSDIR+dir.name+"/metadata.json").toString(); + } catch (e) { + ERROR(dir.name+"/metadata.json does not exist"); + return; + } + apps.push(JSON.parse(appsFile)); +}); var args = process.argv; diff --git a/bin/create_apps_json.sh b/bin/create_apps_json.sh index adc5f8a62..dd883b22a 100755 --- a/bin/create_apps_json.sh +++ b/bin/create_apps_json.sh @@ -13,17 +13,36 @@ # # If you do this, please do not attempt to commit your modified # apps.json back into the main BangleApps repository! +# +# You can pass an optional filename to this script, and it will write +# to that instead, apps.local.json is used when opening the loader on localhost +outfile="${1:-apps.json}" cd `dirname $0`/.. -echo "[" > apps.json +echo "[" > "$outfile" +first=1 for app in apps/*/; do echo "Processing $app..."; if [[ "$app" =~ ^apps/_example.* ]]; then echo "Ignoring $app" else - cat ${app}metadata.json >> apps.json + if [ $first -eq 1 ]; then + first=0; + else + echo "," >> "$outfile" + fi; + cat ${app}metadata.json >> "$outfile" # echo ",\"$app\"," >> apps.json # DEBUG ONLY - echo "," >> apps.json fi done -echo "null]" >> apps.json +echo "]" >> "$outfile" + +if [ -z "$1"]; then + # Running with no arguments: prevent accidental commit of modified apps.json. + # You can use `create_apps.json.sh apps.json` if you really want to both + # overwrite and still commit apps.json + git update-index --skip-worktree apps.json + echo "Told git to ignore modified apps.json." + # If you want to unignore it, use + # 'git update-index --no-skip-worktree apps.json' +fi \ No newline at end of file diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 4bc2a70b2..9f7758ee5 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -10,7 +10,6 @@ var SETTINGS = { var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; -var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; var DEVICE = "BANGLEJS"; var APPS = [ // IDs of apps to install @@ -28,7 +27,6 @@ global.Const = { }; var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); -var appjson = JSON.parse(fs.readFileSync(APPJSON).toString()); var appfiles = []; function fileGetter(url) { @@ -58,8 +56,11 @@ function fileGetter(url) { } Promise.all(APPS.map(appid => { - var app = appjson.find(app=>app.id==appid); - if (app===undefined) throw new Error(`App ${appid} not found`); + try { + var app = JSON.parse(fs.readFileSync(APPDIR + "/" + appid + "metadata.json").toString()); + } catch (e) { + throw new Error(`App ${appid} not found`); + } return AppInfo.getFiles(app, { fileGetter : fileGetter, settings : SETTINGS, diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 14ced9ef8..e7042d2c3 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -16,7 +16,6 @@ var DEVICE = process.argv[2]; var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; -var APPJSON = ROOTDIR+'/apps.json'; var MINIFY = true; var OUTFILE, APPS; @@ -86,7 +85,6 @@ function atob(input) { } var AppInfo = require(ROOTDIR+"/core/js/appinfo.js"); -var appjson = JSON.parse(fs.readFileSync(APPJSON).toString()); var appfiles = []; function fileGetter(url) { @@ -134,8 +132,11 @@ function evaluateFile(file) { } Promise.all(APPS.map(appid => { - var app = appjson.find(app=>app.id==appid); - if (app===undefined) throw new Error(`App ${appid} not found`); + try { + var app = JSON.parse(fs.readFileSync(APPDIR + "/" + appid + "metadata.json").toString()); + } catch (e) { + throw new Error(`App ${appid} not found`); + } return AppInfo.getFiles(app, { fileGetter : fileGetter, settings : SETTINGS, diff --git a/core b/core index 5023ee122..3093d78a5 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 5023ee1228030130ba9f026d5dbe920f7527ee7d +Subproject commit 3093d78a5d752cbf03ea8f9a1a7c0b50b9c8123b diff --git a/lang/cs_CZ.json b/lang/cs_CZ.json new file mode 100644 index 000000000..3f4fc87f1 --- /dev/null +++ b/lang/cs_CZ.json @@ -0,0 +1,53 @@ +{ + "//":"Czech language translations", + "GLOBAL": { + "//":"Translations that apply for all apps", + "Alarm" : "Budík", + "Hour" : "Hodina", + "Hours" : "Hodiny", + "Minute" : "Minuta", + "Minutes" : "Minuty", + "Second" : "Sekunda", + "Seconds" : "Sekundy", + "Month" : "Měsíc", + "Enabled" : "Povoleno", + "Background" : "Pozadí", + "Connected" : "Připojeno", + "Settings" : "Nastavení", + "Save" : "Uložit", + "Back" : "Zpět", + "Repeat" : "Opakovat", + "Delete" : "Smazat", + "Sleep" : "Uspat", + "Alarms" : "Budíky", + "ALARM!" : "BUDÍK!", + + " (repeat)" : " (opakovat)", + "< Back" : "< Zpět", + "> Delete" : "> Smazat", + "> Save" : " > Uložit", + "ALARM " : "BUDÍK ", + + "Add Device" : "Přidat zařízení", + "App Settings" : "Nast. Aplikací", + "Apps" : "Aplikace" + + }, + "alarm": { + "//":"App-specific overrides", + "Alarm/Timer" : "Budik/Časovač", + "rpt" : "Opk.", + "New Alarm" : "Nový budík", + "New Timer" : "Nový časovač", + "Auto snooze" : "Auto odložit" + }, + "setting" : { + "Quiet Mode" : "Tichý režim" + + }, + "messages": { + "Are you sure?" : "Opravdu?" + } + + +} diff --git a/lang/index.json b/lang/index.json index f17bf5e03..3d492783d 100644 --- a/lang/index.json +++ b/lang/index.json @@ -1,5 +1,6 @@ [ {"code":"en_GB","name":"British English","url":"en_GB.json"}, + {"code":"cs_CZ","name":"Czech","url":"cs_CZ.json"}, {"code":"de_DE","name":"German","url":"de_DE.json"}, {"code":"es_ES","name":"Spanish","url":"es_ES.json"}, {"code":"fi_FI","name":"Finnish","url":"fi_FI.json"}, diff --git a/loader.js b/loader.js index 0355ea89c..d8ba26269 100644 --- a/loader.js +++ b/loader.js @@ -5,6 +5,11 @@ if (window.location.host=="banglejs.com") { document.title += " [Development]"; document.getElementById("apploaderlinks").innerHTML = 'This is the development Bangle.js App Loader - you can also try the Official Version for stable apps.'; +} else if (window.location.hostname==='localhost') { + document.title += " [Local]"; + Const.APPS_JSON_FILE = "apps.local.json"; + document.getElementById("apploaderlinks").innerHTML = + 'This is your local Bangle.js App Loader - you can try the Official Version here.'; } else { document.title += " [Unofficial]"; document.getElementById("apploaderlinks").innerHTML = diff --git a/package.json b/package.json index b796044c9..32c96e3ea 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,17 @@ "scripts": { "lint-apps": "eslint ./apps --ext .js", "test": "node bin/sanitycheck.js && eslint ./apps --ext .js", + "update-local-apps": "./bin/create_apps_json.sh apps.local.json", + "local": "npm-watch & npx http-server -a localhost -c-1", "start": "npx http-server -c-1" }, + "watch": { + "update-local-apps": "apps/*/metadata.json" + }, "dependencies": { "acorn": "^7.2.0" + }, + "devDpendencies": { + "npm-watch": "^0.11.0" } }