From c6854a8db2951518ee8cc6c57d2b2ffa48a9ba09 Mon Sep 17 00:00:00 2001 From: Nick Breland Date: Wed, 13 Sep 2023 21:39:43 +0100 Subject: [PATCH] Add main app code --- apps/habitbuilder/.gitignore | 172 +++++++++++++++++++++++++++++ apps/habitbuilder/TODO.md | 6 + apps/habitbuilder/app.js | 89 ++++++++++----- apps/habitbuilder/boot.js | 7 ++ apps/habitbuilder/lib.js | 18 +++ apps/habitbuilder/metadata.json | 1 + apps/habitbuilder/settings-form.js | 7 +- 7 files changed, 272 insertions(+), 28 deletions(-) create mode 100644 apps/habitbuilder/.gitignore create mode 100644 apps/habitbuilder/TODO.md create mode 100644 apps/habitbuilder/boot.js create mode 100644 apps/habitbuilder/lib.js diff --git a/apps/habitbuilder/.gitignore b/apps/habitbuilder/.gitignore new file mode 100644 index 0000000000..3d75225a1b --- /dev/null +++ b/apps/habitbuilder/.gitignore @@ -0,0 +1,172 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# IntelliJ based IDEs +.idea diff --git a/apps/habitbuilder/TODO.md b/apps/habitbuilder/TODO.md new file mode 100644 index 0000000000..7b2a7d60ed --- /dev/null +++ b/apps/habitbuilder/TODO.md @@ -0,0 +1,6 @@ +- [ ] Test saving reminder time as ms since midnight +- [ ] Create habit daily recording screen +- [ ] Save records to CSV (date (YYYY-MM-DD), question, response (1 or 0)) +- [ ] Reset alarm for the next day +- [ ] Make sure CSV can be downloaded +- [ ] Determine what happens if questions are changed from AL interface diff --git a/apps/habitbuilder/app.js b/apps/habitbuilder/app.js index 6f82014775..6290f9d49d 100644 --- a/apps/habitbuilder/app.js +++ b/apps/habitbuilder/app.js @@ -1,39 +1,74 @@ -// place your const, vars, functions or classes here -const storage = require("Storage"); -const FILENAME = "habitbuilder.json"; +const Storage = require("Storage"); +const Layout = require("Layout"); -if (!storage.read(FILENAME)) { - storage.writeJSON(FILENAME, {}); -} +const filename = "habitbuilder.json"; +const data = Storage.readJSON(filename); -// clear the screen -g.clear(); +const questions = Object.keys(data.questions); -var n = 0; +function showQuestion(questionIdx) { + const layout = new Layout( { + type: "v", c: [ + {type:"txt", wrap: true, fillx: 1, height: 50, font:"6x8:2", label:questions[questionIdx], id:questionIdx}, + {type: "h", c: [ + {type:"btn", font:"6x8:2", pad:2, width: 75, label:"Yes", cb: l=>setAnswer(questionIdx, true) }, + {type:"btn", font:"6x8:2", pad:2, width: 75, label:"No", cb: l=>setAnswer(questionIdx, false) } + ]} + ] + }); + g.clear(); + layout.render(); +} -// redraw the screen -function draw() { - g.reset().clearRect(Bangle.appRect); - g.setFont("6x8").setFontAlign(0,0).drawString("Up / Down",g.getWidth()/2,g.getHeight()/2 - 20); - g.setFont("Vector",60).setFontAlign(0,0).drawString(n,g.getWidth()/2,g.getHeight()/2 + 30); +function getDateString(date) { + return date.toISOString().substr(0,10); } -// Respond to user input -Bangle.setUI({mode: "updown"}, function(dir) { - if (dir<0) { - n--; - draw(); - } else if (dir>0) { - n++; - draw(); +function setAnswer(questionIdx, answer) { + const date = new Date(); + const dateStr = getDateString(date); + data.questions[questions[questionIdx]].responses[dateStr] = answer; + if (questions[questionIdx + 1]) { + showQuestion(questionIdx + 1); } else { - n = 0; - draw(); + Storage.writeJSON(filename, data); + E.showMessage("All done for today!"); + } +} + +/* +Might use these for a widget later + +function getFeaturedQuestion() { + const entries = Object.entries(questionData); + let question, data; + for (let i = 0; i < entries.length; i++) { + question = entries[i][0]; + data = entries[i][1]; + if (data.featured === true) { + return question; + } } -}); + return false; +} + + +function getStreak(question, endDate) { + const responses = questionData[question].responses; + let streak = 0; + const date = endDate; + let dateString = getDateString(date); + + while (responses[dateString] && responses[dateString] === true) { + streak++; + date.setDate(date.getDate() - 1); + dateString = getDateString(date); + } + return streak; +} +*/ -// First draw... -draw(); +showQuestion(0); // Load widgets Bangle.loadWidgets(); diff --git a/apps/habitbuilder/boot.js b/apps/habitbuilder/boot.js new file mode 100644 index 0000000000..7dae845426 --- /dev/null +++ b/apps/habitbuilder/boot.js @@ -0,0 +1,7 @@ +require("sched").setAlarm("habitbuilder", { + appid: "habitbuilder", + js: "load(habitbuilder.app.js)", + t: require("Storage").readJSON('habitbuilder.json').reminderTime, + rp: true +}); +//require("sched").reload(); diff --git a/apps/habitbuilder/lib.js b/apps/habitbuilder/lib.js new file mode 100644 index 0000000000..037edc3177 --- /dev/null +++ b/apps/habitbuilder/lib.js @@ -0,0 +1,18 @@ +// ex. 08:00 -> 28800000 +function getMillisecondsSinceMidnight(timeStr) { + const [hours, minutes] = timeStr.split(":"); + return hours * 3600000 + minutes * 60000; +} + +exports.setHabitAlarm = function () { + const FILENAME = "habitbuilder.json"; + const data = require("Storage").readJSON(FILENAME, true); + + const reminderTime = data.reminderTime || "21:00"; + + sched.setAlarm("habitbuilder", { + t: getMillisecondsSinceMidnight(reminderTime), + appid: "habitbuilder", + js: "load(habitbuilder.app.js)" + }); +}; diff --git a/apps/habitbuilder/metadata.json b/apps/habitbuilder/metadata.json index 10a3ef75df..c0801ed674 100644 --- a/apps/habitbuilder/metadata.json +++ b/apps/habitbuilder/metadata.json @@ -6,6 +6,7 @@ "tags": "", "supports" : ["BANGLEJS2"], "readme": "README.md", + "dependencies": {"scheduler": "type"}, "interface": "interface.html", "custom": "custom.html", "storage": [ diff --git a/apps/habitbuilder/settings-form.js b/apps/habitbuilder/settings-form.js index 09c69d7c9b..14c7c6a444 100644 --- a/apps/habitbuilder/settings-form.js +++ b/apps/habitbuilder/settings-form.js @@ -27,10 +27,15 @@ function getSettingsFromForm() { return false; } - return { questions, reminderTime }; + return { questions, reminderTime: getMsFromMidnight(reminderTime) }; } function questionComponent(question, i) { return `

Question ${i+1}

`; } +// ex. 08:00 -> 28800000 +function getMsFromMidnight(timeStr) { + const [hours, minutes] = timeStr.split(":"); + return hours * 60 * 60 * 1000 + minutes * 60 * 1000; +}