├── website ├── src │ ├── boot │ │ ├── .gitkeep │ │ ├── axios.js │ │ └── i18n.js │ ├── components │ │ ├── .gitkeep │ │ ├── GlanceFooter.vue │ │ ├── Gallery.vue │ │ ├── GlanceHeader.vue │ │ ├── Navbar.vue │ │ ├── Donations.vue │ │ └── Features.vue │ ├── store │ │ ├── module-example │ │ │ ├── state.js │ │ │ ├── actions.js │ │ │ ├── getters.js │ │ │ ├── mutations.js │ │ │ └── index.js │ │ └── index.js │ ├── i18n │ │ ├── index.js │ │ └── en-us │ │ │ └── index.js │ ├── App.vue │ ├── css │ │ ├── app.styl │ │ └── quasar.variables.styl │ ├── pages │ │ ├── Error404.vue │ │ ├── Index.vue │ │ ├── Troubleshooting.vue │ │ └── Customize.vue │ ├── router │ │ ├── routes.js │ │ └── index.js │ ├── index.template.html │ ├── layouts │ │ └── MyLayout.vue │ └── assets │ │ ├── sad.svg │ │ └── quasar-logo-full.svg ├── .eslintignore ├── README.md ├── public │ └── statics │ │ ├── bg.jpg │ │ ├── bg2.jpg │ │ ├── bg3.jpg │ │ ├── bg4.jpg │ │ ├── logo.png │ │ ├── favicon.png │ │ ├── gallery.png │ │ ├── header.jpg │ │ ├── morocco.png │ │ ├── tomato1.jpg │ │ ├── tomato2.jpg │ │ ├── gallery10.png │ │ ├── gallery11.jpg │ │ ├── gallery2.png │ │ ├── gallery3.png │ │ ├── gallery4.jpg │ │ ├── gallery5.png │ │ ├── gallery6.png │ │ ├── gallery7.jpg │ │ ├── gallery8.jpg │ │ ├── gallery8.png │ │ ├── gallery9.png │ │ ├── dark-paths.png │ │ ├── decisionTree.png │ │ ├── paypal-logo.png │ │ ├── icons │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── ms-icon-144x144.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-167x167.png │ │ ├── apple-icon-180x180.png │ │ └── safari-pinned-tab.svg │ │ ├── Gallery │ │ ├── gallery2.png │ │ ├── gallery3.png │ │ ├── gallery4.png │ │ └── gallery5.png │ │ └── app-logo-128x128.png ├── .editorconfig ├── .postcssrc.js ├── babel.config.cjs ├── .gitignore ├── .stylintrc ├── package.json ├── .eslintrc.cjs └── quasar.conf.js ├── resources ├── img │ ├── add.png │ ├── degree.png │ ├── heart.png │ ├── home.png │ ├── step.png │ ├── battery.png │ ├── checked.png │ ├── syringe.png │ ├── arrows │ │ ├── Flat.png │ │ ├── None.png │ │ ├── DoubleUp.png │ │ ├── SingleUp.png │ │ ├── loading.png │ │ ├── warning.png │ │ ├── DoubleDown.png │ │ ├── FortyFiveUp.png │ │ ├── SingleDown.png │ │ └── FortyFiveDown.png │ └── hamburger.png ├── widget.defs ├── styles.css ├── styles~300x300.css ├── styles~336x336.css └── index.view ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── tasks.json ├── modules ├── app │ ├── sharedPreferences.js │ ├── treatments.js │ ├── userActivity.js │ ├── batteryLevels.js │ ├── errors.js │ ├── transfer.js │ ├── dateTime.js │ ├── alerts.js │ └── bloodline.js └── companion │ ├── transfer.js │ ├── weather.js │ ├── logs.js │ ├── sizeof.js │ ├── fetch.js │ └── dexcom.js ├── .github ├── dependabot.yml └── workflows │ ├── dependency-review.yml │ ├── codeql.yml │ └── scorecards.yml ├── package.json ├── README.md ├── companion └── index.js ├── app └── index.js └── settings └── index.jsx /website/src/boot/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /website/src/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Quasar App 2 | 3 | > WIP 4 | -------------------------------------------------------------------------------- /website/src/store/module-example/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 3 | }; 4 | -------------------------------------------------------------------------------- /resources/img/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/add.png -------------------------------------------------------------------------------- /website/src/store/module-example/actions.js: -------------------------------------------------------------------------------- 1 | export function someAction(/* context */) {} 2 | -------------------------------------------------------------------------------- /website/src/store/module-example/getters.js: -------------------------------------------------------------------------------- 1 | export function someGetter(/* state */) {} 2 | -------------------------------------------------------------------------------- /resources/img/degree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/degree.png -------------------------------------------------------------------------------- /resources/img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/heart.png -------------------------------------------------------------------------------- /resources/img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/home.png -------------------------------------------------------------------------------- /resources/img/step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/step.png -------------------------------------------------------------------------------- /website/src/store/module-example/mutations.js: -------------------------------------------------------------------------------- 1 | export function someMutation(/* state */) {} 2 | -------------------------------------------------------------------------------- /resources/img/battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/battery.png -------------------------------------------------------------------------------- /resources/img/checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/checked.png -------------------------------------------------------------------------------- /resources/img/syringe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/syringe.png -------------------------------------------------------------------------------- /resources/img/arrows/Flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/Flat.png -------------------------------------------------------------------------------- /resources/img/arrows/None.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/None.png -------------------------------------------------------------------------------- /resources/img/hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/hamburger.png -------------------------------------------------------------------------------- /website/public/statics/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/bg.jpg -------------------------------------------------------------------------------- /website/public/statics/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/bg2.jpg -------------------------------------------------------------------------------- /website/public/statics/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/bg3.jpg -------------------------------------------------------------------------------- /website/public/statics/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/bg4.jpg -------------------------------------------------------------------------------- /website/public/statics/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/logo.png -------------------------------------------------------------------------------- /website/src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import enUS from "./en-us"; 2 | 3 | export default { 4 | "en-us": enUS 5 | }; 6 | -------------------------------------------------------------------------------- /resources/img/arrows/DoubleUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/DoubleUp.png -------------------------------------------------------------------------------- /resources/img/arrows/SingleUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/SingleUp.png -------------------------------------------------------------------------------- /resources/img/arrows/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/loading.png -------------------------------------------------------------------------------- /resources/img/arrows/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/warning.png -------------------------------------------------------------------------------- /website/public/statics/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/favicon.png -------------------------------------------------------------------------------- /website/public/statics/gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery.png -------------------------------------------------------------------------------- /website/public/statics/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/header.jpg -------------------------------------------------------------------------------- /website/public/statics/morocco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/morocco.png -------------------------------------------------------------------------------- /website/public/statics/tomato1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/tomato1.jpg -------------------------------------------------------------------------------- /website/public/statics/tomato2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/tomato2.jpg -------------------------------------------------------------------------------- /resources/img/arrows/DoubleDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/DoubleDown.png -------------------------------------------------------------------------------- /resources/img/arrows/FortyFiveUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/FortyFiveUp.png -------------------------------------------------------------------------------- /resources/img/arrows/SingleDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/SingleDown.png -------------------------------------------------------------------------------- /website/public/statics/gallery10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery10.png -------------------------------------------------------------------------------- /website/public/statics/gallery11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery11.jpg -------------------------------------------------------------------------------- /website/public/statics/gallery2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery2.png -------------------------------------------------------------------------------- /website/public/statics/gallery3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery3.png -------------------------------------------------------------------------------- /website/public/statics/gallery4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery4.jpg -------------------------------------------------------------------------------- /website/public/statics/gallery5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery5.png -------------------------------------------------------------------------------- /website/public/statics/gallery6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery6.png -------------------------------------------------------------------------------- /website/public/statics/gallery7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery7.jpg -------------------------------------------------------------------------------- /website/public/statics/gallery8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery8.jpg -------------------------------------------------------------------------------- /website/public/statics/gallery8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery8.png -------------------------------------------------------------------------------- /website/public/statics/gallery9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/gallery9.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules 3 | /node_modules 4 | 5 | # build folder 6 | build 7 | /build 8 | 9 | -------------------------------------------------------------------------------- /resources/img/arrows/FortyFiveDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/resources/img/arrows/FortyFiveDown.png -------------------------------------------------------------------------------- /website/public/statics/dark-paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/dark-paths.png -------------------------------------------------------------------------------- /website/public/statics/decisionTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/decisionTree.png -------------------------------------------------------------------------------- /website/public/statics/paypal-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/paypal-logo.png -------------------------------------------------------------------------------- /website/public/statics/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/favicon.ico -------------------------------------------------------------------------------- /website/public/statics/Gallery/gallery2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/Gallery/gallery2.png -------------------------------------------------------------------------------- /website/public/statics/Gallery/gallery3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/Gallery/gallery3.png -------------------------------------------------------------------------------- /website/public/statics/Gallery/gallery4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/Gallery/gallery4.png -------------------------------------------------------------------------------- /website/public/statics/Gallery/gallery5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/Gallery/gallery5.png -------------------------------------------------------------------------------- /website/public/statics/app-logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/app-logo-128x128.png -------------------------------------------------------------------------------- /website/src/boot/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default async ({ app, Vue }) => { 4 | app.$axios = axios 5 | }; 6 | -------------------------------------------------------------------------------- /website/public/statics/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/favicon-16x16.png -------------------------------------------------------------------------------- /website/public/statics/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/favicon-32x32.png -------------------------------------------------------------------------------- /website/public/statics/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/favicon-96x96.png -------------------------------------------------------------------------------- /website/public/statics/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/icon-128x128.png -------------------------------------------------------------------------------- /website/public/statics/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/icon-192x192.png -------------------------------------------------------------------------------- /website/public/statics/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/icon-256x256.png -------------------------------------------------------------------------------- /website/public/statics/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/icon-384x384.png -------------------------------------------------------------------------------- /website/public/statics/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/icon-512x512.png -------------------------------------------------------------------------------- /website/public/statics/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /website/public/statics/icons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/apple-icon-120x120.png -------------------------------------------------------------------------------- /website/public/statics/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /website/public/statics/icons/apple-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/apple-icon-167x167.png -------------------------------------------------------------------------------- /website/public/statics/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rytiggy/Glance/HEAD/website/public/statics/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /website/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /website/src/i18n/en-us/index.js: -------------------------------------------------------------------------------- 1 | // This is just an example, 2 | // so you can safely delete all default props below 3 | 4 | export default { 5 | failed: "Action failed", 6 | success: "Action was successful" 7 | }; 8 | -------------------------------------------------------------------------------- /website/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /website/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /website/src/store/module-example/index.js: -------------------------------------------------------------------------------- 1 | import state from "./state"; 2 | import * as getters from "./getters"; 3 | import * as mutations from "./mutations"; 4 | import * as actions from "./actions"; 5 | 6 | export default { 7 | namespaced: true, 8 | getters, 9 | mutations, 10 | actions, 11 | state 12 | }; 13 | -------------------------------------------------------------------------------- /website/src/boot/i18n.js: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | import messages from "src/i18n"; 3 | 4 | export default async ({ app, Vue }) => { 5 | const i18n = createI18n({ 6 | locale: "en-us", 7 | fallbackLocale: "en-us", 8 | messages 9 | }); 10 | 11 | // Set i18n instance on app 12 | app.use(i18n); 13 | }; 14 | -------------------------------------------------------------------------------- /website/babel.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | module.exports = api => { 4 | return { 5 | presets: [ 6 | [ 7 | '@quasar/babel-preset-app', 8 | api.caller(caller => caller && caller.target === 'node') 9 | ? { targets: { node: 'current' } } 10 | : {} 11 | ] 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | .quasar 2 | .DS_Store 3 | .thumbs.db 4 | node_modules 5 | /dist 6 | /src-cordova/node_modules 7 | /src-cordova/platforms 8 | /src-cordova/plugins 9 | /src-cordova/www 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gitleaks/gitleaks 3 | rev: v8.16.3 4 | hooks: 5 | - id: gitleaks 6 | - repo: https://github.com/pre-commit/mirrors-eslint 7 | rev: v8.38.0 8 | hooks: 9 | - id: eslint 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: end-of-file-fixer 14 | - id: trailing-whitespace 15 | -------------------------------------------------------------------------------- /website/src/css/app.styl: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Faster+One&display=swap'); 2 | .glance-logo { 3 | font-family: 'Faster One', cursive; 4 | } 5 | body { 6 | min-width: 320px; 7 | } 8 | .description-lead { 9 | max-width: 600px; 10 | margin: auto; 11 | } 12 | .bullet { 13 | display: list-item; 14 | list-style-type: disc; 15 | list-style-position: inside; 16 | } 17 | 18 | .no-underline:hover { 19 | text-decoration: none; 20 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "problemMatcher": [] 10 | }, 11 | { 12 | "type": "npm", 13 | "script": "build-and-install", 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "install", 19 | "problemMatcher": [] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /website/src/pages/Error404.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /website/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | 4 | // import example from './module-example' 5 | 6 | Vue.use(Vuex); 7 | 8 | /* 9 | * If not building with SSR mode, you can 10 | * directly export the Store instantiation 11 | */ 12 | 13 | export default function(/* { ssrContext } */) { 14 | const Store = new Vuex.Store({ 15 | modules: { 16 | // example 17 | }, 18 | 19 | // enable strict mode (adds overhead!) 20 | // for dev mode only 21 | strict: process.env.DEV 22 | }); 23 | 24 | return Store; 25 | } 26 | -------------------------------------------------------------------------------- /modules/app/sharedPreferences.js: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs'; 2 | import { me } from 'appbit'; 3 | 4 | const FILE_NAME = 'datamatabatafata.cbor'; 5 | 6 | export let preferences = {}; 7 | 8 | try { 9 | preferences = readFileSync(FILE_NAME, 'cbor'); 10 | } catch (error) { 11 | console.warn('Failed to load ' + FILE_NAME + '. It is OK if no values were stored yet.'); 12 | } 13 | 14 | me.addEventListener('unload', () => { 15 | try { 16 | writeFileSync(FILE_NAME, preferences, 'cbor'); 17 | } catch (error) { 18 | console.error('Failed to save ' + FILE_NAME); 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /website/src/router/routes.js: -------------------------------------------------------------------------------- 1 | const routes = [ 2 | { 3 | path: '/', 4 | component: () => import('layouts/MyLayout.vue'), 5 | children: [ 6 | { path: '', component: () => import('pages/Index.vue') }, 7 | { 8 | path: "/troubleshooting", 9 | component: () => import("pages/Troubleshooting.vue") 10 | }, 11 | { path: "/customize", component: () => import("pages/Customize.vue") } 12 | ] 13 | }, 14 | 15 | // Always leave this as last one, 16 | // but you can also remove it 17 | { 18 | path: '/:catchAll(.*)*', 19 | component: () => import('pages/Error404.vue') 20 | } 21 | ] 22 | 23 | export default routes 24 | -------------------------------------------------------------------------------- /website/src/css/quasar.variables.styl: -------------------------------------------------------------------------------- 1 | // Quasar Stylus Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Stylus variables found in Quasar's source Stylus files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // It's highly recommended to change the default colors 9 | // to match your app's branding. 10 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 11 | 12 | $primary = rgb(75, 162, 220); 13 | $secondary = rgba(34, 34, 34, 0.9); 14 | $accent = #9C27B0 15 | 16 | $positive = #21BA45 17 | $negative = #C10015 18 | $info = #31CCEC 19 | $warning = gold 20 | 21 | $facebook = #3b5998 22 | -------------------------------------------------------------------------------- /website/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | core: 9 | update-types: 10 | - "major" 11 | - "minor" 12 | - "patch" 13 | - package-ecosystem: "npm" 14 | directory: "/website" 15 | schedule: 16 | interval: "weekly" 17 | groups: 18 | website: 19 | update-types: 20 | - "major" 21 | - "minor" 22 | - "patch" 23 | - package-ecosystem: "github-actions" 24 | directory: "/" 25 | schedule: 26 | interval: "weekly" 27 | groups: 28 | github-actions: 29 | update-types: 30 | - "major" 31 | - "minor" 32 | - "patch" 33 | -------------------------------------------------------------------------------- /resources/widget.defs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "fitbit": { 3 | "appUUID": "7b5d9822-7e8e-41f9-a2a7-e823548c001c", 4 | "appType": "clockface", 5 | "appDisplayName": "Glance", 6 | "iconFile": "resources/icon.png", 7 | "wipeColor": "#673ab7", 8 | "requestedPermissions": [ 9 | "access_user_profile", 10 | "access_location", 11 | "access_internet", 12 | "run_background", 13 | "access_activity", 14 | "access_heart_rate" 15 | ], 16 | "buildTargets": [ 17 | "vulcan", 18 | "atlas" 19 | ], 20 | "i18n": { 21 | "en": { 22 | "name": "Glance 2.0" 23 | } 24 | } 25 | }, 26 | "devDependencies": { 27 | "@fitbit/sdk": "~5.0.1", 28 | "@fitbit/sdk-cli": "^1.7.3" 29 | }, 30 | "scripts": { 31 | "build": "fitbit-build", 32 | "build-and-install": "printf 'build-and-install' | npx fitbit", 33 | "install": "printf 'install' | npx fitbit" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /website/src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 37 | 46 | -------------------------------------------------------------------------------- /website/.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": "never", 3 | "brackets": "never", 4 | "colons": "never", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "depthLimit": false, 10 | "duplicates": true, 11 | "efficient": "always", 12 | "extendPref": false, 13 | "globalDupe": true, 14 | "indentPref": 2, 15 | "leadingZero": "never", 16 | "maxErrors": false, 17 | "maxWarnings": false, 18 | "mixed": false, 19 | "namingConvention": false, 20 | "namingConventionStrict": false, 21 | "none": "never", 22 | "noImportant": false, 23 | "parenSpace": "never", 24 | "placeholder": false, 25 | "prefixVarsWithDollar": "always", 26 | "quotePref": "single", 27 | "semicolons": "never", 28 | "sortOrder": false, 29 | "stackedProperties": "never", 30 | "trailingWhitespace": "never", 31 | "universal": "never", 32 | "valid": true, 33 | "zeroUnits": "never", 34 | "zIndexNormalize": false 35 | } 36 | -------------------------------------------------------------------------------- /modules/app/treatments.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import document from "document"; 17 | let enterTreatment = document.getElementById("enterTreatment"); 18 | 19 | export default class treatments { 20 | check() { 21 | console.log('app - treatments - check()') 22 | 23 | } 24 | 25 | }; 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /modules/app/userActivity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import { today } from 'user-activity'; 17 | import { HeartRateSensor } from "heart-rate"; 18 | 19 | let hrm = new HeartRateSensor(); 20 | hrm.start(); 21 | 22 | export default class userActivity { 23 | get() { 24 | const userActivity = { 25 | steps: today.adjusted.steps, 26 | heartRate: hrm.heartRate, 27 | } 28 | return userActivity; 29 | } 30 | }; -------------------------------------------------------------------------------- /website/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers' 2 | import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' 3 | import routes from './routes' 4 | 5 | /* 6 | * If not building with SSR mode, you can 7 | * directly export the Router instantiation; 8 | * 9 | * The function below can be async too; either use 10 | * async/await or return a Promise which resolves 11 | * with the Router instance. 12 | */ 13 | 14 | export default route(function (/* { store, ssrContext } */) { 15 | const createHistory = process.env.SERVER 16 | ? createMemoryHistory 17 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) 18 | 19 | const Router = createRouter({ 20 | scrollBehavior: () => ({ left: 0, top: 0 }), 21 | routes, 22 | 23 | // Leave this as is and make changes in quasar.conf.js instead! 24 | // quasar.conf.js -> build -> vueRouterMode 25 | // quasar.conf.js -> build -> publicPath 26 | history: createHistory(process.env.VUE_ROUTER_BASE) 27 | }) 28 | 29 | return Router 30 | }) 31 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Harden Runner 20 | uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 21 | with: 22 | egress-policy: audit 23 | 24 | - name: 'Checkout Repository' 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - name: 'Dependency Review' 27 | uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 28 | -------------------------------------------------------------------------------- /modules/companion/transfer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import Logs from "./logs.js"; 17 | const logs = new Logs(); 18 | // This module handles all messaging protocols 19 | import { outbox } from "file-transfer"; 20 | import { encode } from 'cbor'; 21 | import * as messaging from "messaging"; 22 | 23 | export default class transfer { 24 | // Send data to the watchface 25 | send(data) { 26 | logs.add('Line 19: companion - transfer - send()') 27 | outbox.enqueue("responce2.json", encode(data)); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /modules/app/batteryLevels.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | 17 | import { charger, battery } from "power"; 18 | 19 | export default class batteryLevels { 20 | get() { 21 | console.log('app - batteryLevels - get()') 22 | let percent = Math.floor(battery.chargeLevel) 23 | let level = .3 * percent; 24 | let color = '#75bd78'; 25 | if(percent <= 30 && percent >= 15) { 26 | color = 'orange'; 27 | } else if( percent <= 15) { 28 | color = 'red'; 29 | } 30 | return { 31 | percent: percent, 32 | level: level, 33 | color: color, 34 | } 35 | } 36 | }; -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glance", 3 | "version": "0.0.1", 4 | "description": "Glance is a application for use with Fitbit devices to view your blood glucose levels along with a variety of other health stats on the watch face. You can see your stats at a glance!", 5 | "productName": "Glance Watchface", 6 | "cordovaId": "org.cordova.quasar.app", 7 | "author": "Ryan Mason ", 8 | "private": true, 9 | "scripts": { 10 | "lint": "eslint --ext .js,.vue src", 11 | "test": "echo \"No test specified\" && exit 0" 12 | }, 13 | "dependencies": { 14 | "@quasar/extras": "^1.16.12", 15 | "axios": "^1.7.7", 16 | "quasar": "^2.17.1", 17 | "stylus-loader": "^8.1.1", 18 | "vue": "^3.5.12", 19 | "vue-i18n": "^10.0.4" 20 | }, 21 | "devDependencies": { 22 | "@babel/eslint-parser": "^7.25.9", 23 | "@quasar/app-webpack": "^3.14.2", 24 | "eslint": "^9.14.0", 25 | "eslint-config-prettier": "^9.1.0", 26 | "eslint-plugin-vue": "^9.30.0", 27 | "eslint-webpack-plugin": "^4.2.0", 28 | "prettier": "^3.3.3" 29 | }, 30 | "engines": { 31 | "node": ">= 18.12.0", 32 | "npm": ">= 5.6.0", 33 | "yarn": ">= 1.6.0" 34 | }, 35 | "browserslist": [ 36 | "last 1 version, not dead, ie >= 11" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /modules/companion/weather.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import { geolocation } from "geolocation"; 17 | 18 | export default class weather { 19 | async get(tempType) { 20 | console.log('companion - weather - get()') 21 | const position = await getCurrentPosition(); 22 | const { latitude, longitude } = position.coords; 23 | let query = 'select item.condition from weather.forecast where woeid in (SELECT woeid FROM geo.places WHERE text="('+ latitude + "," +longitude+')") and u="'+tempType+'"'; 24 | let endPointURL = "https://query.yahooapis.com/v1/public/yql?q=" +escape(query) + "&format=json"; 25 | return endPointURL; 26 | }; 27 | }; 28 | 29 | 30 | function getCurrentPosition(options = {}) { 31 | return new Promise((resolve, reject) => { 32 | geolocation.getCurrentPosition(resolve, reject, options); 33 | }); 34 | }; -------------------------------------------------------------------------------- /modules/app/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import document from "document"; 17 | let errorLine = document.getElementById("errorLine"); 18 | let largeGraphErrorLine = document.getElementById("largeGraphErrorLine"); 19 | 20 | let sgv = document.getElementById("sgv"); 21 | let largeGraphsSgv = document.getElementById("largeGraphsSgv"); 22 | 23 | export default class errors { 24 | check(timeSenseLastSGV) { 25 | console.log('app - errors - check()') 26 | // if the bloodsugar is stale 27 | if (parseInt(timeSenseLastSGV, 10) >= 15) { 28 | errorLine.style.display = "inline"; 29 | largeGraphErrorLine.style.display = "inline"; 30 | errorLine.style.fill = 'gray'; 31 | largeGraphErrorLine.style.fill = 'gray' 32 | sgv.style.fill = 'gray'; 33 | largeGraphsSgv.style.fill = 'gray' 34 | } else { 35 | errorLine.style.display = "none"; 36 | largeGraphErrorLine.style.display = "none"; 37 | } 38 | } 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /modules/companion/logs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import { settingsStorage } from "settings"; 17 | import Sizeof from "./sizeof.js"; 18 | 19 | const sizeof = new Sizeof(); 20 | 21 | export default class logs { 22 | add(value) { 23 | console.log(value) 24 | let d = new Date(); 25 | // console.error(sizeof.size(settingsStorage.getItem('logs'))) 26 | if (settingsStorage.getItem('logs') && sizeof.size(settingsStorage.getItem('logs')) > 130000) { 27 | settingsStorage.setItem('logs', JSON.stringify({"name":''})); 28 | } 29 | 30 | if( settingsStorage.getItem('logs') && JSON.parse(settingsStorage.getItem('logs')).name ) { 31 | settingsStorage.setItem('logs', JSON.stringify({"name":( `${ d.getHours() } : ${ d.getMinutes() } ${ value } |,| ${JSON.parse(settingsStorage.getItem('logs')).name}`)})); 32 | } else {// if there are no logs 33 | settingsStorage.setItem('logs', JSON.stringify({'name':( `${ d.getHours() } : ${ d.getMinutes() } ${ value } `)})); 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | 10 | # Glance ![twerp logo](https://image.ibb.co/gbWF2H/twerp_bowtie_64.png) 11 | 12 | Glance is an application for use with Fitbit devices to view your blood glucose levels along with a variety of other health stats on the watch face. You can see your stats at a glance! 13 | Click here to learn how to set up Glance! 14 | 15 | 16 | 17 | ## Donation 18 | I developed Glance to help people with diabetes! 50% of all donations will go to the Faustman lab. The remaining 50% will be dedicated to future research and development of Glance. 19 | 20 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/ryanmasonjar) 21 | ## Features 22 | - Current BG 23 | - Insulin on Board (IOB) 24 | - Carbs on Board (COB) 25 | - Trend direction 26 | - Delta 27 | - Time since last pull 28 | - Graph of BG's over time 29 | - Error reporting 30 | - Temperature 31 | - Step count 32 | - Heart rate 33 | - Time 34 | - Date 35 | - Battery levels 36 | - Vibration Alerts 37 | - Changing background color 38 | 39 | ## [User Agreement](https://github.com/Rytiggy/Glance/wiki/User-Agreement) 40 | Glance must not be used to make medical decisions, by using glance you agree to the [user agreement](https://github.com/Rytiggy/Glance/wiki/User-Agreement). 41 | 42 | -------------------------------------------------------------------------------- /website/src/components/GlanceFooter.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 45 | 46 | 56 | -------------------------------------------------------------------------------- /modules/app/transfer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | // Import the messaging module 17 | import * as messaging from "messaging"; 18 | 19 | export default class transfer { 20 | // Send data 21 | send(data) { 22 | console.log('app - transfer - send') 23 | if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) { 24 | // Send a command to the companion 25 | messaging.peerSocket.send({ 26 | command: 'forceCompanionTransfer', 27 | data: data, 28 | }); 29 | } 30 | } 31 | }; 32 | 33 | 34 | // Events 35 | 36 | // // Listen for the onopen event 37 | // messaging.peerSocket.onopen = function() { 38 | // // Fetch weather when the connection opens 39 | // fetchWeather(); 40 | // } 41 | 42 | // Listen for messages from the companion 43 | // messaging.peerSocket.onmessage = function(evt) { 44 | // if (evt.data) { 45 | // console.log("The temperature is: " + evt.data.temperature); 46 | // } 47 | // } 48 | 49 | // // Listen for the onerror event 50 | // messaging.peerSocket.onerror = function(err) { 51 | // // Handle any errors 52 | // console.log("Connection error: " + err.code + " - " + err.message); 53 | // } 54 | 55 | -------------------------------------------------------------------------------- /website/src/components/Gallery.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 59 | 60 | 74 | -------------------------------------------------------------------------------- /modules/companion/sizeof.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | sizeof.js 4 | 5 | A function to calculate the approximate memory usage of objects 6 | 7 | Created by Kate Morley - http://code.iamkate.com/ - and released under the terms 8 | of the CC0 1.0 Universal legal code: 9 | 10 | http://creativecommons.org/publicdomain/zero/1.0/legalcode 11 | 12 | */ 13 | 14 | 15 | 16 | export default class sizeof { 17 | size(object){ 18 | 19 | // initialise the list of objects and size 20 | var objects = [object]; 21 | var size = 0; 22 | 23 | // loop over the objects 24 | for (var index = 0; index < objects.length; index ++){ 25 | 26 | // determine the type of the object 27 | switch (typeof objects[index]){ 28 | 29 | // the object is a boolean 30 | case 'boolean': size += 4; break; 31 | 32 | // the object is a number 33 | case 'number': size += 8; break; 34 | 35 | // the object is a string 36 | case 'string': size += 2 * objects[index].length; break; 37 | 38 | // the object is a generic object 39 | case 'object': 40 | 41 | // if the object is not an array, add the sizes of the keys 42 | if (Object.prototype.toString.call(objects[index]) != '[object Array]'){ 43 | for (var key in objects[index]) size += 2 * key.length; 44 | } 45 | 46 | // loop over the keys 47 | for (var key in objects[index]){ 48 | 49 | // determine whether the value has already been processed 50 | var processed = false; 51 | for (var search = 0; search < objects.length; search ++){ 52 | if (objects[search] === objects[index][key]){ 53 | processed = true; 54 | break; 55 | } 56 | } 57 | 58 | // queue the value to be processed if appropriate 59 | if (!processed) objects.push(objects[index][key]); 60 | 61 | } 62 | 63 | } 64 | 65 | } 66 | 67 | // return the calculated size 68 | return size; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /website/src/components/GlanceHeader.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 65 | 66 | 80 | -------------------------------------------------------------------------------- /website/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | parserOptions: { 8 | parser: '@babel/eslint-parser', 9 | ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module' // Allows for the use of imports 11 | }, 12 | 13 | env: { 14 | browser: true 15 | }, 16 | 17 | // Rules order is important, please avoid shuffling them 18 | extends: [ 19 | // Base ESLint recommended rules 20 | // 'eslint:recommended', 21 | 22 | // Uncomment any of the lines below to choose desired strictness, 23 | // but leave only one uncommented! 24 | // See https://eslint.vuejs.org/rules/#available-rules 25 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 26 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 27 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 28 | 29 | // https://github.com/prettier/eslint-config-prettier#installation 30 | // usage with Prettier, provided by 'eslint-config-prettier'. 31 | 'prettier' 32 | ], 33 | 34 | plugins: [ 35 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files 36 | // required to lint *.vue files 37 | 'vue', 38 | 39 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 40 | // Prettier has not been included as plugin to avoid performance impact 41 | // add it as an extension for your IDE 42 | 43 | ], 44 | 45 | globals: { 46 | ga: 'readonly', // Google Analytics 47 | cordova: 'readonly', 48 | __statics: 'readonly', 49 | __QUASAR_SSR__: 'readonly', 50 | __QUASAR_SSR_SERVER__: 'readonly', 51 | __QUASAR_SSR_CLIENT__: 'readonly', 52 | __QUASAR_SSR_PWA__: 'readonly', 53 | process: 'readonly', 54 | Capacitor: 'readonly', 55 | chrome: 'readonly' 56 | }, 57 | 58 | // add your custom rules here 59 | rules: { 60 | 61 | 'prefer-promise-reject-errors': 'off', 62 | 63 | // allow console.log during development only 64 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 65 | 66 | // allow debugger during development only 67 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 68 | 69 | // Most of the components use only single word names 70 | 'vue/multi-word-component-names': 'off' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /modules/app/dateTime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | export default class dateTime { 15 | getDate(dateFormat, enableDOW) { 16 | console.log("app - dateTime - getDate()"); 17 | let dateObj = new Date(); 18 | let month = ("0" + (dateObj.getMonth() + 1)).slice(-2); 19 | let date = ("0" + dateObj.getDate()).slice(-2); 20 | let year = dateObj.getFullYear(); 21 | let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 22 | 23 | if (enableDOW) { 24 | year = year.toString().substr(-2); 25 | } 26 | 27 | let shortDate = month + "/" + date + "/" + year; 28 | 29 | if (dateFormat) { 30 | if (dateFormat == "DD/MM/YYYY") { 31 | shortDate = date + "/" + month + "/" + year; 32 | } else if (dateFormat == "YYYY/MM/DD") { 33 | shortDate = year + "/" + month + "/" + date; 34 | } else if (dateFormat == "DD.MM.YYYY") { 35 | shortDate = date + "." + month + "." + year; 36 | } 37 | } 38 | 39 | if (enableDOW) { 40 | shortDate += " " + days[dateObj.getDay()]; 41 | } 42 | return shortDate; 43 | } 44 | 45 | getTime(timeFormat) { 46 | console.log("app - dateTime - getTime()"); 47 | let timeNow = new Date(); 48 | let hh = timeNow.getHours(); 49 | let mm = timeNow.getMinutes(); 50 | let ss = timeNow.getSeconds(); 51 | if (!timeFormat) { 52 | let formatAMPM = hh >= 12 ? "PM" : "AM"; 53 | hh = hh % 12 || 12; 54 | 55 | // if(hh < 10) { 56 | // hh = '0' + hh; 57 | // } 58 | } 59 | if (mm < 10) { 60 | mm = "0" + mm; 61 | } 62 | return hh + ":" + mm; 63 | } 64 | 65 | getTimeSenseLastSGV(sgvDateTime) { 66 | console.log("app - dateTime - getTimeSenseLastSGV()"); 67 | let currentTime = new Date(); 68 | let lastSGVTime = new Date(sgvDateTime); 69 | let secondsDiff = (currentTime.getTime() - lastSGVTime.getTime()) / 1000; 70 | let timeSense = ""; 71 | let timeSenseNumber = ""; 72 | if (secondsDiff > 86400) { 73 | timeSense = "~" + Math.floor(secondsDiff / 86400) + "D"; 74 | timeSenseNumber = Math.floor(secondsDiff / 60); 75 | } else if (secondsDiff > 3600) { 76 | timeSense = "~" + Math.floor(secondsDiff / 3600) + "h"; 77 | timeSenseNumber = Math.floor(secondsDiff / 60); 78 | } else if (secondsDiff > 0) { 79 | timeSense = Math.floor(secondsDiff / 60) + " min"; 80 | timeSenseNumber = Math.floor(secondsDiff / 60); 81 | } 82 | return [timeSense, timeSenseNumber]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /modules/companion/fetch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import Logs from "./logs.js"; 17 | const logs = new Logs(); 18 | 19 | 20 | export default class messaging { 21 | //Fetch data from an API endpoint and return a promise 22 | async get(url) { 23 | const trimmedURL = url.replace(/ /g,""); 24 | logs.add('Line 16: companion - fetch - get() ' + trimmedURL) 25 | return await fetch(trimmedURL) 26 | .then(handleResponse) 27 | .then((data) => { 28 | logs.add(`Line 28: companion - fetch - get() Data Okay return`) 29 | return data; 30 | }).catch((error) => { 31 | // not found 32 | if(!error.status) { 33 | error.status = '404' 34 | } 35 | logs.add(`Line 35 ERROR companion - fetch - get() ${JSON.stringify(error)}`) 36 | let errorMsg = { 37 | text: 'Line 38: Error with companion - fetch - get()', 38 | error: error, 39 | url: trimmedURL, 40 | } 41 | return errorMsg; 42 | }); 43 | }; 44 | }; 45 | 46 | function handleResponse (response) { 47 | let contentType = response.headers.get('content-type') 48 | if (contentType.includes('application/json')) { 49 | return handleJSONResponse(response) 50 | } else if (contentType.includes('text/html')) { 51 | return handleTextResponse(response) 52 | } else { 53 | // Other response types as necessary. I haven't found a need for them yet though. 54 | throw new Error(`Sorry, content-type ${contentType} not supported`) 55 | } 56 | } 57 | 58 | function handleJSONResponse (response) { 59 | return response.json() 60 | .then(json => { 61 | // console.error(JSON.stringify(json)) 62 | 63 | if (response.ok) { 64 | logs.add(`Line 83 companion - fetch - handleJSONResponse() response.ok`) 65 | return json 66 | } else { 67 | return Promise.reject(Object.assign({}, json, { 68 | status: response.status, 69 | statusText: response.statusText 70 | })) 71 | } 72 | }) 73 | } 74 | // This doesnt work 75 | function handleTextResponse (response) { 76 | return response.text() 77 | .then(text => { 78 | if (response.ok) { 79 | logs.add(`Line 98 companion - fetch - handleTextResponse() response.ok`) 80 | return JSON.parse(text) 81 | } else { 82 | return Promise.reject({ 83 | status: response.status, 84 | statusText: response.statusText, 85 | err: text 86 | }) 87 | } 88 | }) 89 | } -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["main"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["main"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["javascript"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Harden Runner 44 | uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 45 | with: 46 | egress-policy: audit 47 | 48 | - name: Checkout repository 49 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 50 | 51 | # Initializes the CodeQL tools for scanning. 52 | - name: Initialize CodeQL 53 | uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 54 | with: 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | 60 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 61 | # If this step fails, then you should remove it and run the build manually (see below) 62 | - name: Autobuild 63 | uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 64 | 65 | # ℹ️ Command-line programs to run using the OS shell. 66 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 67 | 68 | # If the Autobuild fails above, remove it and uncomment the following three lines. 69 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 70 | 71 | # - run: | 72 | # echo "Run, Build Application using script" 73 | # ./location_of_script_within_repo/buildscript.sh 74 | 75 | - name: Perform CodeQL Analysis 76 | uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 77 | with: 78 | category: "/language:${{matrix.language}}" 79 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["main"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | 32 | steps: 33 | - name: Harden Runner 34 | uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 35 | with: 36 | egress-policy: audit 37 | 38 | - name: "Checkout code" 39 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | with: 41 | persist-credentials: false 42 | 43 | - name: "Run analysis" 44 | uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 45 | with: 46 | results_file: results.sarif 47 | results_format: sarif 48 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 49 | # - you want to enable the Branch-Protection check on a *public* repository, or 50 | # - you are installing Scorecards on a *private* repository 51 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 52 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 53 | 54 | # Public repositories: 55 | # - Publish results to OpenSSF REST API for easy access by consumers 56 | # - Allows the repository to include the Scorecard badge. 57 | # - See https://github.com/ossf/scorecard-action#publishing-results. 58 | # For private repositories: 59 | # - `publish_results` will always be set to `false`, regardless 60 | # of the value entered here. 61 | publish_results: true 62 | 63 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 64 | # format to the repository Actions tab. 65 | - name: "Upload artifact" 66 | uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 67 | with: 68 | name: SARIF file 69 | path: results.sarif 70 | retention-days: 5 71 | 72 | # Upload the results to GitHub's code scanning dashboard. 73 | - name: "Upload to code-scanning" 74 | uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 75 | with: 76 | sarif_file: results.sarif 77 | -------------------------------------------------------------------------------- /website/src/layouts/MyLayout.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /website/src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 123 | 124 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /website/src/pages/Troubleshooting.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 99 | 100 | 106 | -------------------------------------------------------------------------------- /modules/companion/dexcom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | import Logs from "./logs.js"; 14 | const logs = new Logs(); 15 | 16 | const applicationId = "d8665ade-9673-4e27-9ff6-92db4ce13d13"; 17 | export default class dexcom { 18 | async login(dexcomUsername, dexcomPassword, subDomain) { 19 | // let body = { 20 | // accountName: dexcomUsername, 21 | // applicationId: "d8665ade-9673-4e27-9ff6-92db4ce13d13", 22 | // password: dexcomPassword, 23 | // }; 24 | 25 | let body = { 26 | accountName: dexcomUsername, 27 | applicationId: applicationId, 28 | password: dexcomPassword, 29 | }; 30 | let accountId = await fetch( 31 | `https://${subDomain}.dexcom.com/ShareWebServices/Services/General/AuthenticatePublisherAccount`, 32 | { 33 | body: JSON.stringify(body), 34 | json: true, 35 | headers: { 36 | Accept: "application/json", 37 | "Content-Type": "application/json", 38 | }, 39 | method: "post", 40 | rejectUnauthorized: false, 41 | } 42 | ) 43 | .then(function (response) { 44 | return response.text(); 45 | }) 46 | .then(function (data) { 47 | return data; 48 | }) 49 | .catch((e) => console.error(e)); 50 | return accountId.replace(/['"]+/g, '') 51 | } 52 | 53 | async getSessionId(dexcomUsername, dexcomPassword, subDomain) { 54 | let accountId = await this.login(dexcomUsername, dexcomPassword, subDomain); 55 | console.log(accountId); 56 | 57 | let body = { 58 | password: dexcomPassword, 59 | applicationId: applicationId, 60 | accountId: accountId, 61 | }; 62 | console.log(applicationId, dexcomPassword, accountId.toString()) 63 | let url = `https://${subDomain}.dexcom.com/ShareWebServices/Services/General/LoginPublisherAccountById`; 64 | let sessionId = await fetch(url, { 65 | body: JSON.stringify(body), 66 | json: true, 67 | headers: { 68 | Accept: "application/json", 69 | "Content-Type": "application/json", 70 | }, 71 | method: "post", 72 | rejectUnauthorized: false, 73 | }) 74 | .then(function (response) { 75 | return response.text(); 76 | }) 77 | .then(function (data) { 78 | return data; 79 | }) 80 | .catch((e) => console.error("here", e)); 81 | 82 | console.log(sessionId); 83 | return sessionId; 84 | } 85 | 86 | async getData(sessionId, subDomain) { 87 | let url = 88 | `https://${subDomain}.dexcom.com/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues?sessionId=${sessionId}&minutes=1440&maxCount=47`.replace( 89 | /"/g, 90 | "" 91 | ); 92 | return await fetch(url, { 93 | headers: { 94 | Accept: "application/json", 95 | "Content-Type": "application/json", 96 | }, 97 | method: "post", 98 | }) 99 | .then(function (response) { 100 | return response.json(); 101 | }) 102 | .then(function (data) { 103 | return data; 104 | }) 105 | .catch((e) => console.error(e)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /website/public/statics/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/src/components/Donations.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 115 | 116 | 130 | -------------------------------------------------------------------------------- /companion/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | import { settingsStorage } from "settings"; 15 | 16 | import Settings from "../modules/companion/settings.js"; 17 | import Transfer from "../modules/companion/transfer.js"; 18 | import Fetch from "../modules/companion/fetch.js"; 19 | import Standardize from "../modules/companion/standardize.js"; 20 | // import Weather from "../modules/companion/weather.js"; 21 | import Logs from "../modules/companion/logs.js"; 22 | import Sizeof from "../modules/companion/sizeof.js"; 23 | import Dexcom from "../modules/companion/dexcom.js"; 24 | 25 | import * as messaging from "messaging"; 26 | import { me } from "companion"; 27 | 28 | const settings = new Settings(); 29 | const transfer = new Transfer(); 30 | const fetch = new Fetch(); 31 | const standardize = new Standardize(); 32 | const dexcom = new Dexcom(); 33 | 34 | // const weatherURL = new Weather(); 35 | const logs = new Logs(); 36 | const sizeof = new Sizeof(); 37 | let dataReceivedFromWatch = null; 38 | 39 | async function sendData() { 40 | // Get settings 41 | const store = await settings.get(dataReceivedFromWatch); 42 | 43 | // Get SGV data 44 | let bloodsugars = null; 45 | let extraData = null; 46 | if (store.url === "dexcom") { 47 | let USAVSInternational = store.USAVSInternational; 48 | let subDomain = "share2"; 49 | if (USAVSInternational) { 50 | subDomain = "shareous1"; 51 | } 52 | 53 | let dexcomUsername = store.dexcomUsername 54 | ? store.dexcomUsername.replace(/\s+/g, "") 55 | : ""; 56 | let dexcomPassword = store.dexcomPassword 57 | ? store.dexcomPassword.replace(/\s+/g, "") 58 | : ""; 59 | let sessionId = await dexcom.getSessionId( 60 | dexcomUsername, 61 | dexcomPassword, 62 | subDomain 63 | ); 64 | if (store.dexcomUsername && store.dexcomPassword) { 65 | bloodsugars = await dexcom.getData(sessionId, subDomain); 66 | } else { 67 | bloodsugars = { 68 | error: { 69 | status: "500", 70 | }, 71 | }; 72 | } 73 | } else { 74 | bloodsugars = await fetch.get(store.url); 75 | if (store.extraDataUrl) { 76 | extraData = await fetch.get(store.extraDataUrl); 77 | } 78 | } 79 | 80 | // Get weather data 81 | // let weather = await fetch.get(await weatherURL.get(store.tempType)); 82 | Promise.all([bloodsugars, extraData]).then(function (values) { 83 | let dataToSend = { 84 | bloodSugars: standardize.bloodsugars(values[0], values[1], store), 85 | settings: standardize.settings(store), 86 | // weather: values[2].query.results.channel.item.condition, 87 | }; 88 | logs.add( 89 | "Line 59: companion - sendData - DataToSend size: " + 90 | sizeof.size(dataToSend) + 91 | " bytes" 92 | ); 93 | logs.add( 94 | "Line 60: companion - sendData - DataToSend: " + 95 | JSON.stringify(dataToSend) 96 | ); 97 | transfer.send(dataToSend); 98 | }); 99 | } 100 | 101 | // Listen for messages from the device 102 | messaging.peerSocket.onmessage = function (evt) { 103 | if (evt.data.command === "forceCompanionTransfer") { 104 | logs.add("Line 58: companion - Watch to Companion Transfer request"); 105 | // pass in data that was recieved from the watch 106 | console.log(JSON.stringify(evt.data.data)); 107 | dataReceivedFromWatch = evt.data.data; 108 | sendData(); 109 | } 110 | }; 111 | 112 | // Listen for the onerror event 113 | messaging.peerSocket.onerror = function (err) { 114 | // Handle any errors 115 | console.log("Connection error: " + err.code + " - " + err.message); 116 | }; 117 | 118 | settingsStorage.onchange = function (evt) { 119 | logs.add("Line 70: companion - Settings changed send to watch"); 120 | sendData(); 121 | if (evt.key === "authorizationCode") { 122 | // Settings page sent us an oAuth token 123 | let data = JSON.parse(evt.newValue); 124 | dexcom.getAccessToken(data.name); 125 | } 126 | }; 127 | 128 | const MINUTE = 1000 * 60; 129 | me.wakeInterval = 5 * MINUTE; 130 | 131 | if (me.launchReasons.wokenUp) { 132 | // The companion started due to a periodic timer 133 | console.error("Started due to wake interval!"); 134 | sendData(); 135 | } else { 136 | // Close the companion and wait to be awoken 137 | me.yield(); 138 | } 139 | // wait 1 seconds before getting things started 140 | setTimeout(sendData, 1000); 141 | -------------------------------------------------------------------------------- /website/src/components/Features.vue: -------------------------------------------------------------------------------- 1 | 103 | 104 | 107 | 108 | 122 | -------------------------------------------------------------------------------- /resources/styles.css: -------------------------------------------------------------------------------- 1 | .text { 2 | font-size: 27; 3 | font-family: SevilleSharp-Bold; 4 | font-weight: regular; 5 | width: 100%; 6 | height: 100%; 7 | fill: white; 8 | text-length: 64; 9 | } 10 | 11 | .xs-text { 12 | font-size: 20; 13 | font-family: Seville-Condensed; 14 | font-weight: regular; 15 | width: 100%; 16 | height: 100%; 17 | fill: white; 18 | text-length: 64; 19 | } 20 | 21 | .h2 { 22 | font-size: 55; 23 | font-family: SevilleSharp-Regular; 24 | width: 100%; 25 | height: 100%; 26 | fill: white; 27 | text-length: 64; 28 | } 29 | .h1 { 30 | font-size: 95; 31 | font-family: Tungsten-Medium; 32 | width: 100%; 33 | height: 100%; 34 | fill: white; 35 | text-length: 64; 36 | } 37 | 38 | .text-gray{ 39 | fill: #B8C3C4; 40 | } 41 | 42 | #battery-level { 43 | width: 30; 44 | height: 17; 45 | fill: white; 46 | x: 3%; 47 | y: 4%; 48 | } 49 | 50 | #batteryPercent { 51 | x: 13%; 52 | y: 10%+3; 53 | } 54 | 55 | .battery-image { 56 | width: 32; 57 | height: 33; 58 | x: 3%; 59 | y: 1%; 60 | } 61 | 62 | #date { 63 | x: 2%; 64 | y: 21%; 65 | } 66 | 67 | 68 | #error { 69 | x: 95%+3; 70 | y: 59%+2; 71 | } 72 | 73 | #delta { 74 | x: 98%; 75 | y: 10%; 76 | } 77 | 78 | #sgv { 79 | x: 85%; 80 | y: 28%; 81 | } 82 | 83 | #timeOfLastSgv { 84 | x: 98%; 85 | y: 40%; 86 | } 87 | 88 | #time { 89 | x: 3%; 90 | y: 50%; 91 | } 92 | 93 | #arrows { 94 | x: 87%; 95 | y: 11%; 96 | width: 45; 97 | height: 45; 98 | fill: white; 99 | } 100 | 101 | #largeGraphArrows { 102 | x: 84%; 103 | y: 11%; 104 | width: 45; 105 | height: 45; 106 | fill: white; 107 | } 108 | 109 | #weather { 110 | x: 36%; 111 | y: 10%+3; 112 | } 113 | 114 | .stat-image{ 115 | width: 16; 116 | height: 16; 117 | } 118 | 119 | #graph { 120 | x: 57%-25; 121 | y: 55%; 122 | width: 175; 123 | height: 100; 124 | } 125 | .graph-point { 126 | fill:#b0b1b2; 127 | } 128 | .gval { 129 | fill:#b0b1b2; 130 | } 131 | #dismiss { 132 | y: 57%; 133 | x: 2%; 134 | width: 146; 135 | height: 100; 136 | } 137 | 138 | #goToTreatment { 139 | y: 61%; 140 | x: 2%; 141 | width: 170; 142 | height: 100; 143 | fill:black; 144 | } 145 | #goToLargeGraph { 146 | y: 23%; 147 | x: 5%; 148 | width: 170; 149 | height: 90; 150 | fill: black; 151 | } 152 | 153 | #errorLine { 154 | x1: 62%; 155 | y1: 19%+2; 156 | x2: 92%; 157 | y2: 19%+2; 158 | fill: white; 159 | } 160 | 161 | #high { 162 | x: 97%; 163 | y: 30%; 164 | width:40; 165 | } 166 | #largeGraphHigh { 167 | x: 97%; 168 | y: 30%; 169 | width:40; 170 | } 171 | 172 | #mean { 173 | x: 100%+9; 174 | y: 65; 175 | } 176 | 177 | #low { 178 | x: 97%; 179 | y: 88%; 180 | width:40; 181 | } 182 | #largeGraphLow { 183 | x: 97%; 184 | y: 88%; 185 | width:40; 186 | } 187 | 188 | #degreeIcon { 189 | width: 8; 190 | height: 8; 191 | fill: #B8C3C4; 192 | x: 44%; 193 | y: 2%; 194 | } 195 | 196 | #syringe { 197 | x: 2%; 198 | y: 53%+2; 199 | } 200 | #hamburger { 201 | x: 2%; 202 | y: 64%+2; 203 | } 204 | 205 | #steps { 206 | x: 9%+3; 207 | y: 82%+2; 208 | } 209 | #heart { 210 | x: 9%+3; 211 | y: 94%+2; 212 | } 213 | 214 | #stepIcon { 215 | x: 2%; 216 | y: 76%; 217 | } 218 | 219 | #heartIcon { 220 | x: 2%; 221 | y: 88%; 222 | } 223 | 224 | #iob { 225 | x: 9%+3; 226 | y: 60%+2; 227 | fill: white; 228 | } 229 | 230 | #cob { 231 | x: 9%+3; 232 | y: 71%+2; 233 | } 234 | 235 | #popup-title { 236 | x: 50%; 237 | y: 30%; 238 | } 239 | 240 | #carbsTreatment { 241 | x: 35%; 242 | y: 10%; 243 | width: 48%; 244 | } 245 | 246 | #insulinTreatment { 247 | x: 35%; 248 | width: 48%; 249 | } 250 | 251 | .item { height: 90; width:50; } 252 | .item text { font-size: 80; fill: white; x: 5; } 253 | 254 | .itemTenth { height: 90; width:30; } 255 | .itemTenth text { font-size: 80; fill: white; x: 0; } 256 | 257 | .treatment-image { 258 | width: 50; 259 | height: 50; 260 | } 261 | 262 | #largeGraphsSgv { 263 | text-anchor:"end"; 264 | x: 85%; 265 | y: 28%; 266 | } 267 | 268 | #largeGraphArrows { 269 | x: 87%; 270 | y: 11%; 271 | width: 45; 272 | height: 45; 273 | } 274 | 275 | #largeGraphDelta { 276 | x: 98%; 277 | y: 10% 278 | } 279 | #largeGraphTimeOfLastSgv { 280 | x: 98%; 281 | y: 40%; 282 | } 283 | 284 | #rawbg { 285 | x: 75%; 286 | y: 40%; 287 | } 288 | 289 | #tempBasal { 290 | x: 3%; 291 | y: 30%; 292 | /* y: 8%+3; */ 293 | } 294 | 295 | #largeGraph { 296 | y: 55%; 297 | x: 7; 298 | } 299 | 300 | #exitLargeGraph { 301 | y: 70%; 302 | x: 50%; 303 | width: 170; 304 | height: 100; 305 | fill: black; 306 | } 307 | #largeGraphSyringe { 308 | x: 3%; 309 | y: 3%; 310 | } 311 | #largeGraphHamburger { 312 | x: 3%; 313 | y: 13% 314 | } 315 | #largeGraphIob { 316 | x: 9%; 317 | y: 8%+3; 318 | /* y: 19%; */ 319 | } 320 | 321 | #largeGraphCob { 322 | x: 9%; 323 | y: 19%; 324 | /* y: 29%; */ 325 | } 326 | 327 | #predictedBg { 328 | x: 3%; 329 | y: 40%; 330 | } 331 | 332 | #largeGraphTime { 333 | x: 50%; 334 | y: 8%+4; 335 | } 336 | 337 | #largeGraphLoopStatus { 338 | x: 3%; 339 | y: 50%; 340 | } 341 | 342 | #largeGraphErrorLine { 343 | x1: 62%; 344 | y1: 19%+2; 345 | x2: 92%; 346 | y2: 19%+2; 347 | fill: white; 348 | } 349 | 350 | #alertArrows { 351 | x: 65%; 352 | y: 14%; 353 | width: 45; 354 | height: 45; 355 | fill: white; 356 | } 357 | -------------------------------------------------------------------------------- /resources/styles~300x300.css: -------------------------------------------------------------------------------- 1 | .text { 2 | font-size: 23; 3 | font-family: Colfax-Medium; 4 | font-weight: regular; 5 | width: 100%; 6 | height: 100%; 7 | fill: white; 8 | text-length: 64; 9 | } 10 | 11 | .xs-text { 12 | font-size: 15; 13 | font-family: Seville-Condensed; 14 | font-weight: regular; 15 | width: 100%; 16 | height: 100%; 17 | fill: white; 18 | text-length: 64; 19 | } 20 | 21 | .h2 { 22 | font-size: 60; 23 | font-family: SevilleSharp-Regular; 24 | width: 100%; 25 | height: 100%; 26 | fill: white; 27 | text-length: 64; 28 | } 29 | .h1 { 30 | font-size: 110; 31 | font-family: Tungsten-Medium; 32 | width: 100%; 33 | height: 100%; 34 | fill: white; 35 | text-length: 64; 36 | } 37 | 38 | .text-gray{ 39 | fill: #B8C3C4; 40 | } 41 | 42 | #battery-level { 43 | width: 30; 44 | height: 15; 45 | fill: white; 46 | x: 3%; 47 | y: 4%; 48 | } 49 | 50 | .battery-image { 51 | width: 32; 52 | height: 33; 53 | x: 3%; 54 | y: 1%; 55 | } 56 | #batteryPercent { 57 | /* x: 13%-2; 58 | y: 7%+2; */ 59 | x: 15%; 60 | y: 8%+3; 61 | /* font-size: 11; 62 | fill: #7f7f7f; 63 | font-family: Colfax-Medium; */ 64 | } 65 | 66 | #date { 67 | x: 3%; 68 | y: 18%; 69 | } 70 | 71 | #error { 72 | x: 95%+3; 73 | y: 59%+2; 74 | } 75 | 76 | 77 | #iob { 78 | x: 11%+3; 79 | y: 64%+2; 80 | } 81 | 82 | #cob { 83 | x: 11%+3; 84 | y: 75%; 85 | } 86 | 87 | #delta { 88 | x: 97%; 89 | y: 8%+3; 90 | } 91 | 92 | 93 | #sgv { 94 | text-anchor:"end"; 95 | x: 97%; 96 | y: 26%; 97 | } 98 | 99 | #timeOfLastSgv{ 100 | x: 97%; 101 | y: 33%; 102 | } 103 | 104 | #time { 105 | x: 3%; 106 | y: 47%; 107 | } 108 | 109 | #arrows { 110 | x: 83%; 111 | y: 32%; 112 | width: 45; 113 | height: 45; 114 | fill: white; 115 | } 116 | 117 | #weather { 118 | x: 36%; 119 | y: 8%+3; 120 | } 121 | 122 | #steps { 123 | x: 10%+3; 124 | y: 85%+2; 125 | } 126 | #heart { 127 | x: 10%+3; 128 | y: 95%; 129 | } 130 | 131 | #stepIcon { 132 | x: 3%; 133 | y: 80%; 134 | } 135 | #heartIcon { 136 | x: 3%; 137 | y: 90%; 138 | } 139 | .stat-image{ 140 | width: 16; 141 | height: 16; 142 | } 143 | 144 | #graph { 145 | x: 49%-24; 146 | y: 60%+3; 147 | width: 175; 148 | height: 100; 149 | } 150 | 151 | .graph-point { 152 | fill:#b0b1b2; 153 | } 154 | 155 | .gval { 156 | fill:#b0b1b2; 157 | } 158 | 159 | #dismiss { 160 | y: 59%; 161 | x: 5%; 162 | width: 100; 163 | height: 115; 164 | } 165 | 166 | #goToTreatment { 167 | y: 66%; 168 | x: 2%; 169 | width: 145; 170 | height: 100; 171 | fill: black; 172 | } 173 | #goToLargeGraph { 174 | y: 25%; 175 | x: 20%; 176 | width: 145; 177 | height: 90; 178 | fill: black; 179 | } 180 | 181 | #errorLine { 182 | x1: 68%; 183 | y1: 19%; 184 | x2: 96%; 185 | y2: 19%; 186 | fill: white; 187 | } 188 | 189 | #high { 190 | x: 97%; 191 | y: 30%; 192 | width: 40; 193 | } 194 | #largeGraphHigh { 195 | x: 98%; 196 | y: 30%; 197 | width: 40; 198 | } 199 | 200 | #mean { 201 | x: 100%+9; 202 | y: 65; 203 | } 204 | 205 | #low { 206 | x: 97%; 207 | y: 88%; 208 | width: 40; 209 | } 210 | #largeGraphLow { 211 | x: 97%; 212 | y: 88%; 213 | width: 40; 214 | } 215 | 216 | #degreeIcon { 217 | width: 8; 218 | height: 8; 219 | fill: #B8C3C4; 220 | x: 45%; 221 | y: 2%; 222 | } 223 | 224 | #syringe { 225 | x: 3%; 226 | y: 60%; 227 | } 228 | #hamburger { 229 | x: 3%; 230 | y: 70%; 231 | } 232 | #popup-title { 233 | x: 50%; 234 | y: 30% 235 | } 236 | 237 | #carbsTreatment { 238 | x: 50%; 239 | y: 27%; 240 | width: 50%; 241 | } 242 | 243 | #insulinTreatment { 244 | x: 50%; 245 | y: $; 246 | width: 50%; 247 | } 248 | 249 | #graphPoints { 250 | fill: red; 251 | } 252 | 253 | .item { height: 90; width:50; } 254 | .item text { font-size: 80; fill: white; x: 10; } 255 | 256 | .itemTenth { height: 90; width:30; } 257 | .itemTenth text { font-size: 80; fill: white; x: 0; } 258 | 259 | .treatment-image { 260 | width: 50; 261 | height: 50; 262 | } 263 | 264 | #largeGraphsSgv { 265 | text-anchor:"end"; 266 | x: 79%; 267 | y: 25%; 268 | } 269 | 270 | #largeGraphArrows { 271 | x: 83%; 272 | y: 11%; 273 | width: 45; 274 | height: 45; 275 | fill: white; 276 | } 277 | 278 | #largeGraphDelta { 279 | x: 97%; 280 | y: 8%+3; 281 | } 282 | #largeGraphTimeOfLastSgv { 283 | x: 97%; 284 | y: 33%; 285 | } 286 | 287 | #rawbg { 288 | x: 97%; 289 | y: 40%; 290 | } 291 | 292 | #largeGraph { 293 | y: 61%; 294 | x: 3%; 295 | width:97%; 296 | } 297 | 298 | #exitLargeGraph { 299 | y: 67%; 300 | x: 43%; 301 | width: 170; 302 | height: 100; 303 | fill: black; 304 | } 305 | 306 | 307 | #largeGraphSyringe { 308 | x: 3%; 309 | y: 4%; 310 | } 311 | #largeGraphHamburger { 312 | x: 3%; 313 | y: 12%; 314 | } 315 | #largeGraphIob { 316 | x: 10%; 317 | y: 8%+4; 318 | } 319 | 320 | #largeGraphCob { 321 | x: 10%; 322 | y: 17%; 323 | } 324 | 325 | #predictedBg { 326 | x: 3%; 327 | y: 25%; 328 | } 329 | 330 | #tempBasal { 331 | x: 3%; 332 | y: 33%; 333 | } 334 | 335 | #largeGraphTime { 336 | x: 50%; 337 | y: 8%+4; 338 | } 339 | 340 | #largeGraphLoopStatus { 341 | x: 3%; 342 | y: 40%; 343 | } 344 | 345 | #largeGraphErrorLine { 346 | x1: 52%; 347 | y1: 19%; 348 | x2: 79%; 349 | y2: 19%; 350 | fill: white; 351 | } 352 | 353 | #alertArrows { 354 | x: 70%; 355 | y: 15%; 356 | width: 45; 357 | height: 45; 358 | fill: white; 359 | } 360 | -------------------------------------------------------------------------------- /resources/styles~336x336.css: -------------------------------------------------------------------------------- 1 | .text { 2 | font-size: 23; 3 | font-family: Colfax-Medium; 4 | font-weight: regular; 5 | width: 100%; 6 | height: 100%; 7 | fill: white; 8 | text-length: 64; 9 | } 10 | 11 | .xs-text { 12 | font-size: 15; 13 | font-family: Seville-Condensed; 14 | font-weight: regular; 15 | width: 100%; 16 | height: 100%; 17 | fill: white; 18 | text-length: 64; 19 | } 20 | 21 | .h3 { 22 | font-size: 45; 23 | font-family: Colfax-Medium; 24 | font-weight: regular; 25 | width: 100%; 26 | height: 100%; 27 | fill: white; 28 | text-length: 64; 29 | } 30 | .h2 { 31 | font-size: 60; 32 | font-family: SevilleSharp-Regular; 33 | width: 100%; 34 | height: 100%; 35 | fill: white; 36 | text-length: 64; 37 | } 38 | .h1 { 39 | font-size: 120; 40 | font-family: Tungsten-Medium; 41 | width: 100%; 42 | height: 100%; 43 | fill: white; 44 | text-length: 64; 45 | } 46 | 47 | .text-gray{ 48 | fill: #B8C3C4; 49 | } 50 | 51 | #battery-level { 52 | width: 30; 53 | height: 18; 54 | fill: #75bd78; 55 | x: 15%; 56 | y: 5%+3; 57 | } 58 | 59 | .battery-image { 60 | width: 32; 61 | height: 34; 62 | x: 15%; 63 | y: 3%; 64 | } 65 | #batteryPercent { 66 | x: 25%; 67 | y: 11%; 68 | } 69 | 70 | #date { 71 | x: 6%; 72 | y: 18%; 73 | } 74 | 75 | #error { 76 | x: 95%+3; 77 | y: 59%+2; 78 | } 79 | 80 | 81 | #iob { 82 | x: 9%; 83 | y: 58%; 84 | } 85 | 86 | #cob { 87 | x: 9%; 88 | y: 67%; 89 | } 90 | 91 | #delta { 92 | x: 85%; 93 | y: 11%; 94 | } 95 | 96 | #sgv { 97 | text-anchor:"end"; 98 | x: 95%; 99 | y: 28%; 100 | } 101 | 102 | #timeOfLastSgv{ 103 | x: 94%; 104 | y: 35%; 105 | } 106 | 107 | #time { 108 | x: 3%; 109 | y: 47%; 110 | } 111 | 112 | #arrows { 113 | x: 83%; 114 | y: 35%; 115 | width: 45; 116 | height: 45; 117 | fill: white; 118 | } 119 | 120 | #weather { 121 | x: 36%; 122 | y: 8%+3; 123 | opacity: 0; 124 | } 125 | 126 | #steps { 127 | x: 9%; 128 | y: 76%; 129 | } 130 | #heart { 131 | x: 9%; 132 | y: 85%; 133 | } 134 | 135 | #stepIcon { 136 | x: 3%; 137 | y: 71%; 138 | } 139 | #heartIcon { 140 | x: 3%; 141 | y: 80%; 142 | } 143 | .stat-image{ 144 | width: 16; 145 | height: 16; 146 | } 147 | 148 | #graph { 149 | x: 49%-24; 150 | y: 60%+3; 151 | width: 175; 152 | height: 100; 153 | } 154 | 155 | .graph-point { 156 | fill:#b0b1b2; 157 | } 158 | 159 | .gval { 160 | fill:#b0b1b2; 161 | } 162 | 163 | #dismiss { 164 | y: 59%; 165 | x: 5%; 166 | width: 100; 167 | height: 115; 168 | } 169 | 170 | #dismiss #text { 171 | fill:white; 172 | } 173 | 174 | #goToTreatment { 175 | y: 66%; 176 | x: 2%; 177 | width: 145; 178 | height: 100; 179 | fill: black; 180 | } 181 | #goToLargeGraph { 182 | y: 25%; 183 | x: 20%; 184 | width: 145; 185 | height: 90; 186 | fill: black; 187 | } 188 | 189 | #errorLine { 190 | x1: 68%; 191 | y1: 19%; 192 | x2: 96%; 193 | y2: 19%; 194 | fill: white; 195 | } 196 | 197 | #high { 198 | x: 97%; 199 | y: 30%; 200 | width: 40; 201 | } 202 | #largeGraphHigh { 203 | x: 98%; 204 | y: 30%; 205 | width: 40; 206 | } 207 | 208 | #mean { 209 | x: 100%+9; 210 | y: 65; 211 | } 212 | 213 | #low { 214 | x: 97%; 215 | y: 88%; 216 | width: 40; 217 | } 218 | #largeGraphLow { 219 | x: 97%; 220 | y: 88%; 221 | width: 40; 222 | } 223 | 224 | #degreeIcon { 225 | width: 8; 226 | height: 8; 227 | fill: #B8C3C4; 228 | x: 45%; 229 | y: 2%; 230 | opacity: 0; 231 | } 232 | 233 | #syringe { 234 | x: 3%; 235 | y: 53%; 236 | } 237 | #hamburger { 238 | x: 3%; 239 | y: 62%; 240 | } 241 | #popup-title { 242 | x: 50%; 243 | y: 30% 244 | } 245 | 246 | #carbsTreatment { 247 | x: 50%; 248 | y: 27%; 249 | width: 50%; 250 | } 251 | 252 | #insulinTreatment { 253 | x: 50%; 254 | y: $; 255 | width: 50%; 256 | } 257 | 258 | #graphPoints { 259 | fill: red; 260 | } 261 | 262 | .item { height: 90; width:50; } 263 | .item text { font-size: 80; fill: white; x: 10; } 264 | 265 | .itemTenth { height: 90; width:30; } 266 | .itemTenth text { font-size: 80; fill: white; x: 0; } 267 | 268 | .treatment-image { 269 | width: 50; 270 | height: 50; 271 | } 272 | 273 | #largeGraphsSgv { 274 | text-anchor:"end"; 275 | x: 95%; 276 | y: 28%; 277 | } 278 | 279 | 280 | #largeGraphArrows { 281 | x: 83%; 282 | y: 35%; 283 | width: 45; 284 | height: 45; 285 | fill: white; 286 | } 287 | 288 | #largeGraphDelta { 289 | x: 85%; 290 | y: 11%; 291 | } 292 | #largeGraphTimeOfLastSgv { 293 | x: 94%; 294 | y: 35%; 295 | } 296 | 297 | #rawbg { 298 | x: 75%; 299 | y: 35%; 300 | } 301 | 302 | #largeGraph { 303 | y: 61%; 304 | x: 3%; 305 | width:97%; 306 | } 307 | 308 | #exitLargeGraph { 309 | y: 67%; 310 | x: 43%; 311 | width: 170; 312 | height: 100; 313 | fill: black; 314 | } 315 | 316 | 317 | #largeGraphSyringe { 318 | x: 3%; 319 | y: 53%; 320 | } 321 | #largeGraphHamburger { 322 | x: 3%; 323 | y: 62%; 324 | } 325 | #largeGraphIob { 326 | x: 9%; 327 | y: 58%; 328 | } 329 | 330 | #largeGraphCob { 331 | x: 9%; 332 | y: 67%; 333 | } 334 | 335 | #predictedBg { 336 | x: 3%; 337 | y: 25%; 338 | } 339 | 340 | #tempBasal { 341 | x: 3%; 342 | y: 33%; 343 | } 344 | 345 | #largeGraphTime { 346 | x: 20%; 347 | y: 15%; 348 | } 349 | 350 | #largeGraphLoopStatus { 351 | x: 3%; 352 | y: 40%; 353 | } 354 | 355 | #largeGraphErrorLine { 356 | x1: 52%; 357 | y1: 19%; 358 | x2: 79%; 359 | y2: 19%; 360 | fill: white; 361 | } 362 | 363 | #alertArrows { 364 | x: 70%; 365 | y: 15%; 366 | width: 45; 367 | height: 45; 368 | fill: white; 369 | } 370 | -------------------------------------------------------------------------------- /website/quasar.conf.js: -------------------------------------------------------------------------------- 1 | // Configuration for your app 2 | // https://quasar.dev/quasar-cli/quasar-conf-js 3 | 4 | const ESLintPlugin = require('eslint-webpack-plugin') 5 | 6 | module.exports = function (ctx) { 7 | return { 8 | // app boot file (/src/boot) 9 | // --> boot files are part of "main.js" 10 | boot: ["i18n", "axios"], 11 | 12 | css: ["app.styl"], 13 | 14 | extras: [ 15 | // 'ionicons-v4', 16 | // 'mdi-v3', 17 | 'fontawesome-v5', 18 | // 'eva-icons', 19 | // 'themify', 20 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 21 | 22 | "roboto-font", // optional, you are not bound to it 23 | "material-icons" // optional, you are not bound to it 24 | ], 25 | 26 | framework: { 27 | // iconSet: 'ionicons-v4', 28 | // lang: 'de', // Quasar language 29 | 30 | // all: true, // --- includes everything; for dev only! 31 | cssAddon: true, 32 | components: [ 33 | "QLayout", 34 | "QHeader", 35 | "QDrawer", 36 | "QPageContainer", 37 | "QPage", 38 | "QToolbar", 39 | "QToolbarTitle", 40 | "QBtn", 41 | "QIcon", 42 | "QList", 43 | "QItem", 44 | "QItemSection", 45 | "QItemLabel", 46 | "QCarousel", 47 | "QCarouselControl", 48 | "QCarouselSlide", 49 | "QPageSticky", 50 | "QParallax", 51 | "QAvatar", 52 | "QCard", 53 | "QCardSection", 54 | "QCardActions", 55 | "QChip", 56 | "QImg", 57 | "QSeparator", 58 | "QBtnGroup", 59 | "QStepper", 60 | "QStep", 61 | "QStepperNavigation", 62 | "QTabs", 63 | "QTab", 64 | "QRouteTab", 65 | "QTabPanel", 66 | "QTabPanels", 67 | "QImg", 68 | "QBadge", 69 | "QSpace", 70 | "QBtnDropdown" 71 | ], 72 | 73 | directives: ["Ripple"], 74 | 75 | // Quasar plugins 76 | plugins: ["Notify"] 77 | }, 78 | 79 | supportIE: true, 80 | 81 | build: { 82 | scopeHoisting: true, 83 | vueRouterMode: 'history', 84 | // vueCompiler: true, 85 | // gzip: true, 86 | // analyze: true, 87 | // extractCSS: false, 88 | 89 | chainWebpack (chain) { 90 | chain.plugin('eslint-webpack-plugin') 91 | .use(ESLintPlugin, [{ extensions: [ 'js', 'vue' ] }]) 92 | } 93 | }, 94 | 95 | devServer: { 96 | // https: true, 97 | // port: 8080, 98 | open: true // opens browser window automatically 99 | }, 100 | 101 | // animations: 'all', // --- includes all animations 102 | animations: [], 103 | 104 | ssr: { 105 | pwa: false, 106 | 107 | chainWebpackWebserver (chain) { 108 | chain.plugin('eslint-webpack-plugin') 109 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 110 | }, 111 | }, 112 | 113 | pwa: { 114 | // workboxPluginMode: 'InjectManifest', 115 | // workboxOptions: {}, // only for NON InjectManifest 116 | 117 | chainWebpackCustomSW (chain) { 118 | chain.plugin('eslint-webpack-plugin') 119 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 120 | }, 121 | 122 | manifest: { 123 | // name: 'Glance Watchface', 124 | // short_name: 'Glance Watchface', 125 | // description: 'Glance is a application for use with Fitbit devices to view your blood glucose levels along with a variety of other health stats on the watch face. You can see your stats at a glance!', 126 | display: "standalone", 127 | orientation: "portrait", 128 | background_color: "#ffffff", 129 | theme_color: "#027be3", 130 | icons: [ 131 | { 132 | src: "statics/icons/icon-128x128.png", 133 | sizes: "128x128", 134 | type: "image/png" 135 | }, 136 | { 137 | src: "statics/icons/icon-192x192.png", 138 | sizes: "192x192", 139 | type: "image/png" 140 | }, 141 | { 142 | src: "statics/icons/icon-256x256.png", 143 | sizes: "256x256", 144 | type: "image/png" 145 | }, 146 | { 147 | src: "statics/icons/icon-384x384.png", 148 | sizes: "384x384", 149 | type: "image/png" 150 | }, 151 | { 152 | src: "statics/icons/icon-512x512.png", 153 | sizes: "512x512", 154 | type: "image/png" 155 | } 156 | ] 157 | } 158 | }, 159 | 160 | cordova: { 161 | // id: 'org.cordova.quasar.app', 162 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 163 | }, 164 | 165 | electron: { 166 | // bundler: 'builder', // or 'packager' 167 | 168 | extendWebpack(cfg) { 169 | // do something with Electron main process Webpack cfg 170 | // chainWebpack also available besides this extendWebpack 171 | }, 172 | 173 | packager: { 174 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 175 | // OS X / Mac App Store 176 | // appBundleId: '', 177 | // appCategoryType: '', 178 | // osxSign: '', 179 | // protocol: 'myapp://path', 180 | // Windows only 181 | // win32metadata: { ... } 182 | }, 183 | 184 | builder: { 185 | // https://www.electron.build/configuration/configuration 186 | // appId: 'glance' 187 | } 188 | } 189 | }; 190 | } 191 | -------------------------------------------------------------------------------- /modules/app/alerts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | import document from "document"; 15 | import { vibration } from "haptics"; 16 | import Transfer from "./transfer.js"; 17 | 18 | import DateTime from "./dateTime.js"; 19 | 20 | const transfer = new Transfer(); 21 | 22 | let sgv = document.getElementById("sgv"); 23 | let largeGraphsSgv = document.getElementById("largeGraphsSgv"); 24 | let errorLine = document.getElementById("errorLine"); 25 | // let largeGraphErrorLine = document.getElementById("largeGraphErrorLine"); 26 | let popup = document.getElementById("popup"); 27 | let alertHeader = document.getElementById("alertHeader"); 28 | let dismiss = popup.getElementById("dismiss"); 29 | let popupTitle = document.getElementById("popup-title"); 30 | let alertArrows = document.getElementById("alertArrows"); 31 | let popupLeadText = popup.getElementById("popup-title"); 32 | 33 | const dateTime = new DateTime(); 34 | 35 | export default class alerts { 36 | check(bg, settings, DISABLE_ALERTS, timeSenseLastSGV) { 37 | let currentBG = bg.currentbg; 38 | let loopstatus = bg.loopstatus; 39 | let staleData = 40 | parseInt(timeSenseLastSGV, 10) >= settings.staleDataAlertAfter; // Boolean true if timeSenseLastSGV > 15 41 | 42 | alertArrows.href = "../resources/img/arrows/" + bg.direction + ".png"; 43 | alertArrows.style.display = "inline"; 44 | console.log("app - Alerts - Check()"); 45 | sgv.style.fill = "#75bd78"; 46 | largeGraphsSgv.style.fill = "#75bd78"; 47 | errorLine.style.fill = "#75bd78"; 48 | // largeGraphErrorLine.style.fill ="#75bd78"; 49 | popupLeadText.text = "Check Blood Sugar!"; 50 | 51 | let timeSenseLastSGV = dateTime.getTimeSenseLastSGV(bg.datetime)[1]; 52 | if (bg.sgv <= parseInt(settings.lowThreshold) && !staleData) { 53 | if (!settings.disableAlert) { 54 | if (!DISABLE_ALERTS) { 55 | if (settings.lowAlerts) { 56 | if (timeSenseLastSGV <= 8) { 57 | console.log("low BG"); 58 | vibration.start("ring"); 59 | popup.style.display = "inline"; 60 | popupTitle.style.display = "inline"; 61 | popupTitle.text = currentBG; 62 | } 63 | } 64 | } 65 | } 66 | sgv.style.fill = "#de4430"; 67 | largeGraphsSgv.style.fill = "#de4430"; 68 | 69 | popupTitle.style.fill = "#de4430"; 70 | errorLine.style.fill = "#de4430"; 71 | // largeGraphErrorLine.style.fill ="#de4430"; 72 | } 73 | if (bg.sgv >= parseInt(settings.highThreshold) && !staleData) { 74 | if (!settings.disableAlert) { 75 | if (!DISABLE_ALERTS) { 76 | if (settings.highAlerts) { 77 | if (timeSenseLastSGV <= 8) { 78 | console.log("high BG"); 79 | vibration.start("ring"); 80 | popup.style.display = "inline"; 81 | popupTitle.style.display = "inline"; 82 | popupTitle.text = currentBG; 83 | } 84 | } 85 | } 86 | } 87 | sgv.style.fill = "orange"; 88 | largeGraphsSgv.style.fill = "orange"; 89 | 90 | popupTitle.style.fill = "orange"; 91 | errorLine.style.fill = "orange"; 92 | // largeGraphErrorLine.style.fill ="orange"; 93 | if (bg.sgv >= parseInt(settings.highThreshold) + 35) { 94 | sgv.style.fill = "#de4430"; 95 | largeGraphsSgv.style.fill = "#de4430"; 96 | popupTitle.style.fill = "#de4430"; 97 | errorLine.style.fill = "#de4430"; 98 | // largeGraphErrorLine.style.fill ="#de4430"; 99 | } 100 | } 101 | 102 | /** 103 | * loopstatus 104 | */ 105 | if (loopstatus === "Warning" && !staleData) { 106 | if (!settings.disableAlert) { 107 | if (!DISABLE_ALERTS) { 108 | if (settings.loopstatus) { 109 | console.log("loopstatus"); 110 | alertArrows.style.display = "none"; 111 | popupTitle.style.fill = "#de4430"; 112 | vibration.start("ring"); 113 | popup.style.display = "inline"; 114 | popupTitle.style.display = "inline"; 115 | popupTitle.text = loopstatus; 116 | popupLeadText.text = "Loop Status"; 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Check for rapid change in bg 123 | if (bg.direction === "DoubleDown" && !staleData) { 124 | if (!settings.disableAlert) { 125 | if (!DISABLE_ALERTS) { 126 | if (settings.rapidFall) { 127 | alertArrows.style.display = "none"; 128 | console.log("Double Down"); 129 | popupTitle.style.fill = "#de4430"; 130 | vibration.start("ring"); 131 | popup.style.display = "inline"; 132 | popupTitle.style.display = "inline"; 133 | popupTitle.text = "Rapid Fall!"; 134 | } 135 | } 136 | } 137 | } else if (bg.direction === "DoubleUp" && !staleData) { 138 | if (!settings.disableAlert) { 139 | if (!DISABLE_ALERTS) { 140 | if (settings.rapidRise) { 141 | alertArrows.style.display = "none"; 142 | console.log("Double Up"); 143 | popupTitle.style.fill = "#de4430"; 144 | vibration.start("ring"); 145 | popup.style.display = "inline"; 146 | popupTitle.style.display = "inline"; 147 | popupTitle.text = "Rapid Rise!"; 148 | } 149 | } 150 | } 151 | } 152 | 153 | // check if stale data 154 | if (staleData) { 155 | if (!settings.disableAlert) { 156 | if (!DISABLE_ALERTS) { 157 | if (settings.staleData) { 158 | alertArrows.style.display = "none"; 159 | popupTitle.style.fill = "#de4430"; 160 | vibration.start("ring"); 161 | popup.style.display = "inline"; 162 | popupTitle.style.display = "inline"; 163 | popupTitle.text = "Stale data"; 164 | } 165 | } 166 | } 167 | } 168 | } 169 | stop() { 170 | console.log("app - Alerts - stop()"); 171 | vibration.stop(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /modules/app/bloodline.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | 15 | 16 | import { me as device } from "device"; 17 | import document from "document"; 18 | 19 | let highNumber = document.getElementById("high"); 20 | let lowNumber = document.getElementById("low"); 21 | let largeGraphHigh = document.getElementById("largeGraphHigh"); 22 | let largeGraphLow = document.getElementById("largeGraphLow"); 23 | 24 | let meanNumber = document.getElementsByClassName("mean"); 25 | let highLine = document.getElementsByClassName("highLine"); 26 | let meanLine = document.getElementsByClassName("meanLine"); 27 | let lowLine = document.getElementsByClassName("lowLine"); 28 | 29 | let graphPoints = document.getElementsByClassName("graphPoints"); 30 | let largeGraphGraphPoints = document.getElementsByClassName("largeGraphGraphPoints"); 31 | 32 | export default class bloodline { 33 | update(bloodsugars, high, low, settings) { 34 | let isMmol = settings.glucoseUnits === 'mmol'; 35 | 36 | console.log('app - bloodline - update()') 37 | let reverseBloodsugars = bloodsugars.reverse(); 38 | 39 | let predictedValues = reverseBloodsugars.filter((bg) => { 40 | if (bg.p) { 41 | return bg; 42 | } 43 | }); 44 | let smallReverseBloodsugars = reverseBloodsugars.filter((bg,index) => { 45 | // bg.p loop = 18 40 ar2 = 5 28 46 | if(!settings.enableSmallGraphPrediction && !(bg.p) && index >= (reverseBloodsugars.length - (predictedValues.length + 24)) ) { 47 | return bg; 48 | } else if (index >= (reverseBloodsugars.length - 24)) { 49 | return bg; 50 | } 51 | }); 52 | 53 | let ymin = low; 54 | let ymax = high; 55 | let height = 100; 56 | // map all sgv to an array then filter out LOS values 57 | let sgvArray = reverseBloodsugars.map(bg => bg.sgv).filter(bg => bg !== 'LOS'); 58 | const currentHighestBg = Math.max(...sgvArray); 59 | const currentLowBg = Math.min(...sgvArray); 60 | if(currentHighestBg >= 400) { 61 | ymax = 340; 62 | } 63 | if(currentHighestBg >= 350 && currentHighestBg < 400) { 64 | ymax = 300; 65 | } 66 | if(currentHighestBg >= 300 && currentHighestBg < 350) { 67 | ymax = 270; 68 | } 69 | if(currentHighestBg >= 250 && currentHighestBg < 300) { 70 | ymax = 260; 71 | } 72 | if(currentHighestBg >= 220 && currentHighestBg < 250) { 73 | ymax = 220; 74 | if( high >= 220) { 75 | ymax = 250; 76 | } 77 | } 78 | if(currentHighestBg >= 160 && currentHighestBg < 220) { 79 | ymax = 180; 80 | if( high >= 220) { 81 | ymax = 240; 82 | } 83 | } 84 | if(currentLowBg < 60) { 85 | ymin = 60; 86 | } 87 | if(currentLowBg < 50) { 88 | ymin = 50; 89 | } 90 | if(currentLowBg < 40) { 91 | ymin = 40; 92 | } 93 | 94 | let highY = (height - (height * (Math.round(((high - ymin) / (ymax - ymin)) * 100) / 100))); 95 | let lowY = (height - (height * (Math.round(((low - ymin) / (ymax - ymin)) * 100) / 100))); 96 | highLine[0].y1 = highY; 97 | highLine[0].y2 = highY; 98 | meanLine[0].y1 = (highY + lowY)/2; 99 | meanLine[0].y2 = (highY + lowY)/2; 100 | lowLine[0].y1 = lowY; 101 | lowLine[0].y2 = lowY; 102 | 103 | highLine[1].y1 = highY; 104 | highLine[1].y2 = highY; 105 | meanLine[1].y1 = (highY + lowY)/2; 106 | meanLine[1].y2 = (highY + lowY)/2; 107 | lowLine[1].y1 = lowY; 108 | lowLine[1].y2 = lowY; 109 | 110 | highNumber.y = highY; 111 | lowNumber.y = lowY; 112 | 113 | largeGraphHigh.y = highY; 114 | largeGraphLow.y = lowY; 115 | 116 | let tempHigh = high; 117 | let tempLow = low; 118 | let tempMean = (high + low)/2; 119 | if (isMmol) { 120 | tempHigh = mmol(tempHigh); 121 | tempLow = mmol(tempLow); 122 | } 123 | 124 | highNumber.text = tempHigh; 125 | lowNumber.text = tempLow; 126 | largeGraphHigh.text = tempHigh; 127 | largeGraphLow.text = tempLow; 128 | 129 | // loop over bloodsugars and plot graph points 130 | // 22 loops 131 | graphPoints.forEach((point, index) => { 132 | try { 133 | let bg = smallReverseBloodsugars[index]; 134 | if(smallReverseBloodsugars[index].sgv === 'LOS') { 135 | graphPoints[index].style.opacity = 0; 136 | } else { 137 | graphPoints[index].style.opacity = 1; 138 | let pointY = (height - (height * (Math.round(((bg.sgv - ymin) / (ymax - ymin)) * 100) / 100))); 139 | // - TODO: compare time of current sgv to time of last sgv and make sure its equal 5m if not add spacing 140 | graphPoints[index].cy = pointY; 141 | graphPoints[index].style.fill = "#708090"; // gray 142 | // - check sgv point is in range if not change color 143 | if(bg.p) { 144 | graphPoints[index].style.fill = "#f76ac5"; // pink 145 | // graphPoints[index].r = 3; 146 | } else if (parseInt(bg.sgv, 10) <= low){ 147 | graphPoints[index].style.fill = "#de4430"; //red 148 | } else if ( parseInt(bg.sgv, 10) >= high) { 149 | graphPoints[index].style.fill = "orange"; // orange 150 | if ( parseInt(bg.sgv, 10) >= (parseInt(high) + 35)) { 151 | graphPoints[index].style.fill = "#de4430"; // red 152 | } 153 | } else { 154 | graphPoints[index].style.fill = "#75bd78"; // green 155 | } 156 | } 157 | } catch(e) { 158 | console.error(e) 159 | } 160 | 161 | 162 | }); 163 | 164 | // 47 loops 165 | for (let index = 0; index < reverseBloodsugars.length; index++) { 166 | if(reverseBloodsugars[index].sgv === 'LOS') { 167 | largeGraphGraphPoints[index].style.opacity = 0; 168 | } else { 169 | largeGraphGraphPoints[index].style.opacity = 1; 170 | let pointY = (height - (height * (Math.round(((reverseBloodsugars[index].sgv - ymin) / (ymax - ymin)) * 100) / 100))); 171 | // - TODO: compare time of current sgv to time of last sgv and make sure its equal 5m if not add spacing 172 | largeGraphGraphPoints[index].cy = pointY; 173 | largeGraphGraphPoints[index].style.fill = "#708090"; // gray 174 | // - check sgv point is in range if not change color 175 | if(reverseBloodsugars[index].p) { 176 | largeGraphGraphPoints[index].style.fill = "#f76ac5"; // pink 177 | } else if (parseInt(reverseBloodsugars[index].sgv, 10) <= low){ 178 | //- INFO: largeGraphGraphPoints has to be at the 22 index becase it is ALL the points on both graphs combined 179 | largeGraphGraphPoints[index].style.fill = "#de4430"; //red 180 | } else if ( parseInt(reverseBloodsugars[index].sgv, 10) >= high) { 181 | largeGraphGraphPoints[index].style.fill = "orange"; // orange 182 | if ( parseInt(reverseBloodsugars[index].sgv, 10) >= (parseInt(high) + 35)) { 183 | largeGraphGraphPoints[index].style.fill = "#de4430"; // red 184 | } 185 | } else { 186 | largeGraphGraphPoints[index].style.fill = "#75bd78"; // green 187 | } 188 | } 189 | } 190 | reverseBloodsugars.reverse(); 191 | } 192 | }; 193 | 194 | // converts a mg/dL to mmoL 195 | function mmol(bg , roundToHundredths) { 196 | let mmolBG = (Math.round((bg / 18) * 10) / 10).toFixed(1); 197 | return mmolBG; 198 | } 199 | 200 | // converts mmoL to mg/dL 201 | function mgdl( bg ) { 202 | let mgdlBG =(Math.round(bg * 18.018).toFixed(0)); 203 | return mgdlBG; 204 | } 205 | -------------------------------------------------------------------------------- /website/src/assets/sad.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /website/src/pages/Customize.vue: -------------------------------------------------------------------------------- 1 | 254 | 255 | 262 | 263 | -------------------------------------------------------------------------------- /resources/index.view: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 99 8 | -- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -- 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 40 | -- 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /website/src/assets/quasar-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 66 | 69 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 104 | 105 | 106 | 107 | 113 | 118 | 126 | 133 | 142 | 151 | 160 | 169 | 178 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Ryan Mason - All Rights Reserved 3 | * 4 | * Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. 5 | * 6 | * https://github.com/Rytiggy/Glance/blob/master/LICENSE 7 | * ------------------------------------------------ 8 | * 9 | * You are free to modify the code but please leave the copyright in the header. 10 | * 11 | * ------------------------------------------------ 12 | */ 13 | 14 | import document from "document"; 15 | import { inbox } from "file-transfer"; 16 | import fs from "fs"; 17 | import { vibration } from "haptics"; 18 | import DateTime from "../modules/app/dateTime.js"; 19 | import BatteryLevels from "../modules/app/batteryLevels.js"; 20 | import Graph from "../modules/app/bloodline.js"; 21 | import UserActivity from "../modules/app/userActivity.js"; 22 | import Alerts from "../modules/app/alerts.js"; 23 | import Errors from "../modules/app/errors.js"; 24 | import Transfer from "../modules/app/transfer.js"; 25 | // import { preferences, save, load } from "../modules/app/sharedPreferences"; 26 | import { memory } from "system"; 27 | 28 | const dateTime = new DateTime(); 29 | const batteryLevels = new BatteryLevels(); 30 | const graph = new Graph(); 31 | const userActivity = new UserActivity(); 32 | const alerts = new Alerts(); 33 | const errors = new Errors(); 34 | const transfer = new Transfer(); 35 | 36 | let main = document.getElementById("main"); 37 | let sgv = document.getElementById("sgv"); 38 | let rawbg = document.getElementById("rawbg"); 39 | let tempBasal = document.getElementById("tempBasal"); 40 | let largeGraphsSgv = document.getElementById("largeGraphsSgv"); 41 | let delta = document.getElementById("delta"); 42 | let largeGraphDelta = document.getElementById("largeGraphDelta"); 43 | let timeOfLastSgv = document.getElementById("timeOfLastSgv"); 44 | let largeGraphTimeOfLastSgv = document.getElementById( 45 | "largeGraphTimeOfLastSgv" 46 | ); 47 | let largeGraphIob = document.getElementById("largeGraphIob"); 48 | let largeGraphCob = document.getElementById("largeGraphCob"); 49 | let iob = document.getElementById("iob"); 50 | let cob = document.getElementById("cob"); 51 | 52 | let dateElement = document.getElementById("date"); 53 | let timeElement = document.getElementById("time"); 54 | let largeGraphTime = document.getElementById("largeGraphTime"); 55 | let weather = document.getElementById("weather"); 56 | let arrows = document.getElementById("arrows"); 57 | let largeGraphArrows = document.getElementById("largeGraphArrows"); 58 | let alertArrows = document.getElementById("alertArrows"); 59 | let batteryLevel = document.getElementById("battery-level"); 60 | let steps = document.getElementById("steps"); 61 | let stepIcon = document.getElementById("stepIcon"); 62 | let heart = document.getElementById("heart"); 63 | let heartIcon = document.getElementById("heartIcon"); 64 | let bgColor = document.getElementById("bgColor"); 65 | let largeGraphBgColor = document.getElementById("largeGraphBgColor"); 66 | let batteryPercent = document.getElementById("batteryPercent"); 67 | let popup = document.getElementById("popup"); 68 | let dismiss = popup.getElementById("dismiss"); 69 | let errorText = document.getElementById("error"); 70 | let popupTitle = document.getElementById("popup-title"); 71 | let degreeIcon = document.getElementById("degreeIcon"); 72 | let goToLargeGraph = document.getElementById("goToLargeGraph"); 73 | 74 | let largeGraphLoopStatus = document.getElementById("largeGraphLoopStatus"); 75 | let largeGraphView = document.getElementById("largeGraphView"); 76 | let exitLargeGraph = document.getElementById("exitLargeGraph"); 77 | 78 | let largeGraphSyringe = document.getElementById("largeGraphSyringe"); 79 | let largeGraphHamburger = document.getElementById("largeGraphHamburger"); 80 | let syringe = document.getElementById("syringe"); 81 | let hamburger = document.getElementById("hamburger"); 82 | let predictedBg = document.getElementById("predictedBg"); 83 | 84 | let dismissHighFor = 120; 85 | let dismissLowFor = 15; 86 | 87 | let data = null; 88 | let DISABLE_ALERTS = false; 89 | 90 | // Data to send back to phone 91 | let dataToSend = { 92 | heart: 0, 93 | steps: userActivity.get().steps, 94 | }; 95 | dismiss.onclick = function (evt) { 96 | console.log("DISMISS"); 97 | popup.style.display = "none"; 98 | popupTitle.style.display = "none"; 99 | vibration.stop(); 100 | DISABLE_ALERTS = true; 101 | let currentBgFromBloodSugars = getFistBgNonpredictiveBG(data.bloodSugars.bgs); 102 | 103 | if (currentBgFromBloodSugars.sgv >= parseInt(data.settings.highThreshold)) { 104 | console.log("HIGH " + dismissHighFor); 105 | setTimeout(disableAlertsFalse, dismissHighFor * 1000 * 60); 106 | } else { 107 | // 15 mins 108 | console.log("LOW " + dismissLowFor); 109 | 110 | setTimeout(disableAlertsFalse, dismissLowFor * 1000 * 60); 111 | } 112 | }; 113 | 114 | function disableAlertsFalse() { 115 | DISABLE_ALERTS = false; 116 | } 117 | 118 | sgv.text = "---"; 119 | rawbg.text = ""; 120 | delta.text = ""; 121 | largeGraphDelta.text = ""; 122 | iob.text = "0.0"; 123 | cob.text = "0.0"; 124 | largeGraphIob.text = "0.0"; 125 | largeGraphCob.text = "0.0"; 126 | dateElement.text = ""; 127 | timeOfLastSgv.text = ""; 128 | weather.text = "--"; 129 | steps.text = "--"; 130 | heart.text = "--"; 131 | batteryPercent.text = "%"; 132 | bgColor.gradient.colors.c1 = "#390263"; 133 | largeGraphBgColor.gradient.colors.c1 = "#390263"; 134 | errorText.text = ""; 135 | update(); 136 | setInterval(update, 10000); 137 | 138 | timeElement.text = dateTime.getTime(); 139 | largeGraphTime.text = dateTime.getTime(); 140 | batteryLevel.width = batteryLevels.get().level; 141 | 142 | inbox.onnewfile = () => { 143 | console.log("New file!"); 144 | let fileName; 145 | do { 146 | // If there is a file, move it from staging into the application folder 147 | fileName = inbox.nextFile(); 148 | if (fileName) { 149 | data = fs.readFileSync(fileName, "cbor"); 150 | update(); 151 | } 152 | } while (fileName); 153 | }; 154 | 155 | function update() { 156 | console.log("app - update()"); 157 | console.warn("JS memory: " + memory.js.used + "/" + memory.js.total); 158 | let heartrate = userActivity.get().heartRate; 159 | if (!heartrate) { 160 | heartrate = 0; 161 | } 162 | // Data to send back to phone 163 | dataToSend = { 164 | heart: heartrate, 165 | steps: userActivity.get().steps, 166 | }; 167 | 168 | if (data) { 169 | console.warn("GOT DATA"); 170 | batteryLevel.width = batteryLevels.get().level; 171 | batteryLevel.style.fill = batteryLevels.get().color; 172 | batteryPercent.text = "" + batteryLevels.get().percent + "%"; 173 | timeElement.text = dateTime.getTime(data.settings.timeFormat); 174 | largeGraphTime.text = dateTime.getTime(data.settings.timeFormat); 175 | 176 | dismissHighFor = data.settings.dismissHighFor; 177 | dismissLowFor = data.settings.dismissLowFor; 178 | weather.text = ""; // data.weather.temp; 179 | degreeIcon.style.display = "none"; 180 | 181 | // colors 182 | bgColor.gradient.colors.c1 = data.settings.bgColor; 183 | bgColor.gradient.colors.c2 = data.settings.bgColorTwo; 184 | 185 | largeGraphBgColor.gradient.colors.c1 = data.settings.bgColor; 186 | largeGraphBgColor.gradient.colors.c2 = data.settings.bgColorTwo; 187 | 188 | setTextColor(data.settings.textColor); 189 | // bloodsugars 190 | let currentBgFromBloodSugars = getFistBgNonpredictiveBG( 191 | data.bloodSugars.bgs 192 | ); 193 | 194 | // Layout options 195 | if ( 196 | currentBgFromBloodSugars[data.settings.layoutOne] && 197 | data.settings.layoutOne != "iob" 198 | ) { 199 | iob.text = currentBgFromBloodSugars[data.settings.layoutOne]; 200 | syringe.style.display = "none"; 201 | iob.x = 10; 202 | } else { 203 | iob.text = commas(userActivity.get().steps); 204 | syringe.style.display = "inline"; 205 | iob.x = 35; 206 | if (currentBgFromBloodSugars.iob && currentBgFromBloodSugars.iob != 0) { 207 | iob.text = currentBgFromBloodSugars.iob + ""; 208 | largeGraphIob.text = currentBgFromBloodSugars.iob + ""; 209 | syringe.style.display = "inline"; 210 | largeGraphSyringe.style.display = "inline"; 211 | } else { 212 | iob.text = ""; 213 | largeGraphIob.text = ""; 214 | syringe.style.display = "none"; 215 | largeGraphSyringe.style.display = "none"; 216 | } 217 | } 218 | 219 | if ( 220 | currentBgFromBloodSugars[data.settings.layoutTwo] && 221 | data.settings.layoutTwo != "cob" 222 | ) { 223 | cob.text = currentBgFromBloodSugars[data.settings.layoutTwo]; 224 | hamburger.style.display = "none"; 225 | cob.x = 10; 226 | } else { 227 | cob.text = userActivity.get().heartRate; 228 | hamburger.style.display = "inline"; 229 | cob.x = 35; 230 | if (currentBgFromBloodSugars.cob && currentBgFromBloodSugars.cob != 0) { 231 | cob.text = currentBgFromBloodSugars.cob + ""; 232 | largeGraphCob.text = currentBgFromBloodSugars.cob + ""; 233 | hamburger.style.display = "inline"; 234 | largeGraphHamburger.style.display = "inline"; 235 | } else { 236 | cob.text = ""; 237 | largeGraphCob.text = ""; 238 | hamburger.style.display = "none"; 239 | largeGraphHamburger.style.display = "none"; 240 | } 241 | } 242 | 243 | if ( 244 | currentBgFromBloodSugars[data.settings.layoutThree] && 245 | data.settings.layoutThree != "steps" 246 | ) { 247 | steps.text = currentBgFromBloodSugars[data.settings.layoutThree]; 248 | stepIcon.style.display = "none"; 249 | steps.x = 10; 250 | } else { 251 | steps.text = commas(userActivity.get().steps); 252 | stepIcon.style.display = "inline"; 253 | steps.x = 35; 254 | } 255 | 256 | if ( 257 | currentBgFromBloodSugars[data.settings.layoutFour] && 258 | data.settings.layoutFour != "heart" 259 | ) { 260 | heart.text = currentBgFromBloodSugars[data.settings.layoutFour]; 261 | heartIcon.style.display = "none"; 262 | heart.x = 10; 263 | } else { 264 | heart.text = userActivity.get().heartRate; 265 | heartIcon.style.display = "inline"; 266 | heart.x = 35; 267 | } 268 | 269 | sgv.text = currentBgFromBloodSugars.currentbg; 270 | largeGraphsSgv.text = currentBgFromBloodSugars.currentbg; 271 | if (currentBgFromBloodSugars.rawbg) { 272 | rawbg.text = currentBgFromBloodSugars.rawbg + " "; 273 | } else { 274 | rawbg.text = ""; 275 | } 276 | 277 | if (currentBgFromBloodSugars.tempbasal) { 278 | tempBasal.text = currentBgFromBloodSugars.tempbasal; 279 | } else { 280 | tempBasal.text = ""; 281 | } 282 | 283 | if (currentBgFromBloodSugars.predictedbg) { 284 | predictedBg.text = currentBgFromBloodSugars.predictedbg; 285 | } else { 286 | predictedBg.text = ""; 287 | } 288 | 289 | timeOfLastSgv.text = dateTime.getTimeSenseLastSGV( 290 | currentBgFromBloodSugars.datetime 291 | )[0]; 292 | largeGraphTimeOfLastSgv.text = dateTime.getTimeSenseLastSGV( 293 | currentBgFromBloodSugars.datetime 294 | )[0]; 295 | 296 | dateElement.text = dateTime.getDate( 297 | data.settings.dateFormat, 298 | data.settings.enableDOW 299 | ); 300 | 301 | let timeSenseLastSGV = dateTime.getTimeSenseLastSGV( 302 | currentBgFromBloodSugars.datetime 303 | )[1]; 304 | // if DISABLE_ALERTS is true check if user is in range 305 | if (DISABLE_ALERTS && data.settings.resetAlertDismissal) { 306 | if ( 307 | parseInt(timeSenseLastSGV, 10) < data.settings.staleDataAlertAfter && 308 | currentBgFromBloodSugars.direction != "DoubleDown" && 309 | currentBgFromBloodSugars.direction != "DoubleUp" && 310 | currentBgFromBloodSugars.loopstatus != "Warning" 311 | ) { 312 | // Dont reset alerts for LOS, DoubleUp, doubleDown, Warning 313 | if ( 314 | currentBgFromBloodSugars.sgv > parseInt(data.settings.lowThreshold) && 315 | currentBgFromBloodSugars.sgv < parseInt(data.settings.highThreshold) 316 | ) { 317 | // if the BG is between the threshold 318 | console.error("here", DISABLE_ALERTS, parseInt(timeSenseLastSGV, 10)); 319 | disableAlertsFalse(); 320 | } 321 | } 322 | } 323 | 324 | alerts.check( 325 | currentBgFromBloodSugars, 326 | data.settings, 327 | DISABLE_ALERTS, 328 | timeSenseLastSGV 329 | ); 330 | 331 | errors.check(timeSenseLastSGV, currentBgFromBloodSugars.currentbg); 332 | let deltaText = currentBgFromBloodSugars.bgdelta; 333 | // add Plus 334 | if (deltaText > 0) { 335 | deltaText = "+" + deltaText; 336 | } 337 | delta.text = deltaText + " " + data.settings.glucoseUnits; 338 | largeGraphDelta.text = deltaText + " " + data.settings.glucoseUnits; 339 | largeGraphLoopStatus.text = ""; // currentBgFromBloodSugars.loopstatus; 340 | 341 | arrows.href = 342 | "../resources/img/arrows/" + currentBgFromBloodSugars.direction + ".png"; 343 | largeGraphArrows.href = 344 | "../resources/img/arrows/" + currentBgFromBloodSugars.direction + ".png"; 345 | 346 | graph.update( 347 | data.bloodSugars.bgs, 348 | data.settings.highThreshold, 349 | data.settings.lowThreshold, 350 | data.settings 351 | ); 352 | 353 | if (data.settings.largeGraph) { 354 | goToLargeGraph.style.display = "inline"; 355 | } else { 356 | goToLargeGraph.style.display = "none"; 357 | } 358 | // if (data.settings.treatments) { 359 | // goToTreatment.style.display = "inline"; 360 | // } else { 361 | // goToTreatment.style.display = "none"; 362 | // } 363 | } else { 364 | console.warn("NO DATA"); 365 | steps.text = commas(userActivity.get().steps); 366 | heart.text = userActivity.get().heartRate; 367 | batteryLevel.width = batteryLevels.get().level; 368 | batteryPercent.text = "" + batteryLevels.get().percent + "%"; 369 | 370 | timeElement.text = dateTime.getTime(); 371 | largeGraphTime.text = dateTime.getTime(); 372 | 373 | dateElement.text = dateTime.getDate(); 374 | } 375 | } 376 | 377 | function commas(value) { 378 | return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 379 | } 380 | /** 381 | * Get Fist BG that is not a predictive BG 382 | * @param {Array} bgs 383 | * @returns {Array} 384 | */ 385 | function getFistBgNonpredictiveBG(bgs) { 386 | return bgs.filter((bg) => { 387 | if (bg.bgdelta || bg.bgdelta === 0) { 388 | return true; 389 | } 390 | })[0]; 391 | } 392 | 393 | function setTextColor(color) { 394 | let domElemets = [ 395 | "iob", 396 | "cob", 397 | "heart", 398 | "steps", 399 | "batteryPercent", 400 | "date", 401 | "delta", 402 | "timeOfLastSgv", 403 | "time", 404 | "high", 405 | "low", 406 | "largeGraphHigh", 407 | "largeGraphLow", 408 | "largeGraphDelta", 409 | "largeGraphTimeOfLastSgv", 410 | "largeGraphIob", 411 | "largeGraphCob", 412 | "predictedBg", 413 | "largeGraphTime", 414 | "largeGraphLoopStatus", 415 | "tempBasal", 416 | ]; 417 | domElemets.forEach((ele) => { 418 | document.getElementById(ele).style.fill = color; 419 | }); 420 | } 421 | 422 | goToLargeGraph.onclick = (e) => { 423 | console.log("goToLargeGraph Activated!"); 424 | vibration.start("bump"); 425 | largeGraphView.style.display = "inline"; 426 | main.style.display = "none"; 427 | }; 428 | 429 | exitLargeGraph.onclick = (e) => { 430 | console.log("exitLargeGraph Activated!"); 431 | vibration.start("bump"); 432 | largeGraphView.style.display = "none"; 433 | main.style.display = "inline"; 434 | }; 435 | 436 | timeElement.onclick = (e) => { 437 | console.log("FORCE Activated!"); 438 | transfer.send(dataToSend); 439 | vibration.start("bump"); 440 | arrows.href = "../resources/img/arrows/loading.png"; 441 | largeGraphArrows.href = "../resources/img/arrows/loading.png"; 442 | alertArrows.href = "../resources/img/arrows/loading.png"; 443 | }; 444 | 445 | // wait 2 seconds 446 | setTimeout(function () { 447 | transfer.send(dataToSend); 448 | }, 1500); 449 | setInterval(function () { 450 | transfer.send(dataToSend); 451 | }, 180000); 452 | 453 | //
Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY
Icons made by Designerz Base from www.flaticon.com is licensed by CC 3.0 BY
Icons made by Twitter from www.flaticon.com is licensed by CC 3.0 BY
454 | -------------------------------------------------------------------------------- /settings/index.jsx: -------------------------------------------------------------------------------- 1 | import { settings } from "settings"; 2 | import { XMLHttpRequest } from "xmlhttprequest"; 3 | 4 | function mySettings(props) { 5 | return ( 6 | 7 | 8 | 13 |   14 |   15 | 16 | Glance is a solution for use with Fitbit devices to view your blood 17 | glucose levels along with a variety of other health stats on the watch 18 | face. You can see your stats at a glance! 19 | 20 |   21 | 22 | 23 | Click here to learn how to set up Glance! 24 | 25 | 26 | 27 | 28 |
31 | Data Source Settings 32 | 33 | } 34 | > 35 | 74 | ) : null 75 | ) : null} 76 | 77 | {props.settings.dataSource ? ( 78 | JSON.parse(props.settings.dataSource).values[0].value == 79 | "nightscout" ? ( (JSON.parse(props.settings.nightscoutSiteHost).values[0].value == "" ? ( 80 | 81 | https://FullCustomDomain.com 82 | 83 | ) : ( 84 | 85 | https://SiteName.NightscoutHostSite.com 86 | 87 | ) 88 | ) 89 | ) : null 90 | ) : null} 91 | {props.settings.dataSource ? ( 92 | JSON.parse(props.settings.dataSource).values[0].value == 93 | "nightscout" ? ( 94 | 99 | ) : null 100 | ) : null} 101 | 102 | {props.settings.dataSource ? ( 103 | JSON.parse(props.settings.dataSource).values[0].value == 104 | "nightscout" ? ( 105 | 110 | ) : null 111 | ) : null} 112 | 113 | {props.settings.dataSource ? ( 114 | JSON.parse(props.settings.dataSource).values[0].value == "dexcom" ? ( 115 |
118 | Dexcom 119 | 120 | } 121 | > 122 | 123 | Dexcom 124 | 125 | 130 | 135 | 139 |
140 | ) : null 141 | ) : null} 142 |
143 | 144 |
147 | Glucose Settings 148 | 149 | } 150 | > 151 | 226 | 334 | 362 | 438 | 439 | // Treatment 440 | // {((props.settings.dataSource) ? ((JSON.parse(props.settings.dataSource).values[0].value == 'xdrip') ? 441 | // xDrip does not support treatments through API calls. maybe in the future it will! : null) : null)} 442 | // {((props.settings.dataSource) ? ((JSON.parse(props.settings.dataSource).values[0].value != 'xdrip') ? 443 | // : null) : null)} 444 | // Tap the lower right hand side of the watch faces screen to enter treatment info. 445 | // {((props.settings.dataSource) ? ((JSON.parse(props.settings.dataSource).values[0].value == 'nightscout') ? 446 | // : null) : null)} 447 | --------------------------------------------------------------------------------