├── .gitignore ├── .gitpod.yml ├── LICENSE ├── README.md ├── babel.config.js ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── msapplication-icon-144x144.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg ├── index.html ├── manifest.json ├── robots.txt └── tones │ ├── end.mp3 │ └── start.mp3 ├── src ├── App.vue ├── components │ ├── LinkSetting.vue │ ├── PeriodSetting.vue │ ├── ScheduleDay.vue │ └── SchedulePeriod.vue ├── main.js ├── material-icons.css ├── plugins │ └── vuetify.js ├── registerServiceWorker.js ├── router.js ├── sass │ └── main.scss ├── scripts │ └── sun.js └── views │ └── Home.vue ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | .netlify 5 | .quasar 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | .sentryclirc 25 | 26 | start_dev.sh -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: npm install 3 | command: npm run serve 4 | vscode: 5 | extensions: 6 | - octref.vetur@0.24.0:FtNScOJHzSTxcNY6NB60Cg== 7 | - christian-kohler.npm-intellisense@1.2.1:vghMIRDy8/ygZPJWq/9oEQ== 8 | - dbaeumer.vscode-eslint@2.1.8:02aHhbJ0Q4aGdjHXlTdVKg== -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bowen Yin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Harker Bell Schedule 2 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/HarkerDev/harker-bell.svg?style=flat) 3 | [![Netlify Status](https://api.netlify.com/api/v1/badges/af49fbbb-c506-4bc7-a158-ab0ae4e922bf/deploy-status)](https://app.netlify.com/sites/harker-bell/deploys) 4 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FHarkerDev%2Fharker-bell.svg?type=small)](https://app.fossa.com/projects/git%2Bgithub.com%2FHarkerDev%2Fharker-bell?ref=badge_small) 5 | 6 | #### [View Live Site](https://bell.harker.org) 7 | #### [View Changelog](https://github.com/HarkerDev/harker-bell/releases) 8 | #### [View Documentation](https://bell.harker.org/docs) 9 | 10 | 11 | 12 | 13 | 14 | ### License 15 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FHarkerDev%2Fharker-bell.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FHarkerDev%2Fharker-bell?ref=badge_large) 16 | ![](https://87f7290bbb154c8753a737c7b24a6d1e.m.pipedream.net/harker-bell) 17 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@vue/cli-plugin-babel/preset" 4 | ] 5 | }; -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/p/*" 3 | to = ":splat" 4 | status = 200 5 | headers = {Access-Control-Allow-Origin = "https://bell.harker.org"} 6 | [[redirects]] 7 | from = "/pgtm/*" 8 | to = "https://www.googletagmanager.com/gtm.js:splat" 9 | status = 200 10 | headers = {Access-Control-Allow-Origin = "https://bell.harker.org"} 11 | [[redirects]] 12 | from = "/submitevent" 13 | to = "https://docs.google.com/forms/d/e/1FAIpQLSeWUzPcr079_ZhXHlRcwKwsoMjJL5O_ZsWVulzMjqVxHOb7Fg/viewform" 14 | status = 302 15 | [[redirects]] 16 | from = "/docs/*" 17 | to = "https://harker-bell-docs.netlify.com/:splat" 18 | status = 200 19 | [[redirects]] 20 | from = "/admin/*" 21 | to = "https://harker-bell-admin.netlify.com/:splat" 22 | status = 200 23 | [[redirects]] 24 | from = "/api/*" 25 | to = "https://bell.dev.harker.org/api/:splat" 26 | status = 200 27 | [[redirects]] 28 | from = "/*" 29 | to = "/index.html" 30 | status = 200 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harker-bell", 3 | "version": "2.6.1", 4 | "private": true, 5 | "author": "HarkerDev", 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build --modern", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@sentry/browser": "^5.22.3", 13 | "@sentry/integrations": "^5.22.3", 14 | "autotrack": "^2.4.1", 15 | "core-js": "^3.6.5", 16 | "idb": "^4.0.5", 17 | "register-service-worker": "^1.7.1", 18 | "semver": "^5.7.0", 19 | "socket.io-client": "^2.3.0", 20 | "vue": "^2.6.12", 21 | "vue-content-loader": "^0.2.3", 22 | "vue-router": "^3.4.3", 23 | "vuetify": "^2.2.34", 24 | "web-vitals": "^0.2.4" 25 | }, 26 | "devDependencies": { 27 | "@vue/cli-plugin-babel": "^4.5.12", 28 | "@vue/cli-plugin-eslint": "^4.5.4", 29 | "@vue/cli-plugin-pwa": "^4.5.4", 30 | "@vue/cli-service": "^4.1.1", 31 | "babel-eslint": "^10.1.0", 32 | "deepmerge": "^3.3.0", 33 | "eslint": "^5.16.0", 34 | "eslint-plugin-vue": "^5.2.3", 35 | "fibers": "^4.0.3", 36 | "sass": "^1.26.10", 37 | "sass-loader": "^7.3.1", 38 | "vue-cli-plugin-vuetify": "^2.0.7", 39 | "vue-template-compiler": "^2.6.12", 40 | "vuetify-loader": "^1.6.0" 41 | }, 42 | "eslintConfig": { 43 | "root": true, 44 | "env": { 45 | "node": true 46 | }, 47 | "extends": [ 48 | "plugin:vue/strongly-recommended", 49 | "eslint:recommended" 50 | ], 51 | "parserOptions": { 52 | "parser": "babel-eslint" 53 | }, 54 | "rules": { 55 | "no-fallthrough": "off", 56 | "no-console": 1, 57 | "vue/attributes-order": 1, 58 | "vue/no-confusing-v-for-v-if": 1, 59 | "vue/no-v-html": 1, 60 | "vue/order-in-components": 1, 61 | "vue/this-in-template": 1, 62 | "vue/array-bracket-spacing": 1, 63 | "vue/arrow-spacing": 1, 64 | "vue/block-spacing": 1, 65 | "vue/brace-style": 1, 66 | "vue/camelcase": 1, 67 | "vue/comma-dangle": 1, 68 | "vue/component-name-in-template-casing": [ 69 | 1, 70 | "kebab-case" 71 | ], 72 | "vue/key-spacing": 1, 73 | "vue/match-component-file-name": 1, 74 | "vue/no-boolean-default": 1, 75 | "vue/no-restricted-syntax": 1, 76 | "vue/object-curly-spacing": 1, 77 | "vue/require-direct-export": 1, 78 | "vue/script-indent": 1, 79 | "vue/space-unary-ops": 1, 80 | "vue/v-on-function-call": 1, 81 | "vue/max-attributes-per-line": 0, 82 | "vue/html-closing-bracket-spacing": [ 83 | "error", 84 | { 85 | "selfClosingTag": 0 86 | } 87 | ], 88 | "vue/html-self-closing": [ 89 | "error", 90 | { 91 | "html": { 92 | "normal": "never", 93 | "component": "never" 94 | } 95 | } 96 | ], 97 | "vue/singleline-html-element-content-newline": 0, 98 | "vue/mustache-interpolation-spacing": [ 99 | "warning", 100 | "never" 101 | ] 102 | } 103 | }, 104 | "postcss": { 105 | "plugins": { 106 | "autoprefixer": {} 107 | } 108 | }, 109 | "browserslist": [ 110 | "> 1% in US", 111 | "last 2 versions", 112 | "not dead", 113 | "not IE <= 11" 114 | ], 115 | "bugs": { 116 | "url": "https://github.com/HarkerDev/harker-bell/issues" 117 | }, 118 | "homepage": "https://github.com/HarkerDev/harker-bell#readme", 119 | "license": "MIT", 120 | "repository": { 121 | "type": "git", 122 | "url": "git+https://github.com/HarkerDev/harker-bell" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Harker Bell Schedule - HarkerDev 13 | 14 | 15 | 16 | 20 | 21 | 22 |
23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Harker Bell Schedule", 3 | "short_name": "Harker Bell", 4 | "icons": [ 5 | { 6 | "src": "./img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "./?utm_source=a2hs&utm_medium=a2hs", 17 | "display": "standalone", 18 | "background_color": "#FFFFFF", 19 | "theme_color": "#005841" 20 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/tones/end.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/tones/end.mp3 -------------------------------------------------------------------------------- /public/tones/start.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarkerDev/harker-bell/06cd8a61e6126e32f4ea6f7a7cbd95d9bca4946d/public/tones/start.mp3 -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 501 | 502 | 1205 | 1206 | 1378 | -------------------------------------------------------------------------------- /src/components/LinkSetting.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/PeriodSetting.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 100 | -------------------------------------------------------------------------------- /src/components/ScheduleDay.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 160 | 161 | 196 | -------------------------------------------------------------------------------- /src/components/SchedulePeriod.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 70 | 71 | 80 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import Vue from "vue"; 3 | import App from "./App.vue"; 4 | import router from "./router"; 5 | import "./registerServiceWorker"; 6 | import vuetify from "./plugins/vuetify"; 7 | import {openDB} from "idb"; 8 | import * as Sentry from "@sentry/browser"; 9 | import * as Integrations from "@sentry/integrations"; 10 | import "autotrack/lib/plugins/event-tracker"; 11 | import "autotrack/lib/plugins/outbound-link-tracker"; 12 | import "autotrack/lib/plugins/page-visibility-tracker"; 13 | import "autotrack/lib/plugins/url-change-tracker"; 14 | import {getFCP, getLCP, getFID, getCLS, getTTFB} from "web-vitals"; 15 | import "./scripts/sun"; 16 | 17 | Sentry.init({ 18 | dsn: "https://74f7b85b2e13edde2c1935d20e3623e1@o4508111680176128.ingest.us.sentry.io/4509268934918144", 19 | integrations: [new Integrations.Vue({Vue, attachProps: true, logErrors: true})], 20 | release: "harker-bell@"+process.env.VUE_APP_VERSION, 21 | }); 22 | window.onload = () => { 23 | setTimeout(() => Sentry.setTag("nodes", document.getElementsByTagName("*").length), 2000); 24 | }; 25 | 26 | function saveWebVitals({name, delta, id}) { 27 | if (window.gtag) gtag('event', name, { 28 | event_category: 'Web Vitals', 29 | event_label: id, 30 | value: Math.round(name === 'CLS' ? delta * 1000 : delta), 31 | non_interaction: true 32 | }); 33 | } 34 | getFCP(saveWebVitals); 35 | getLCP(saveWebVitals); 36 | getFID(saveWebVitals); 37 | getCLS(saveWebVitals); 38 | getTTFB(saveWebVitals); 39 | 40 | const MS_PER_DAY = 24*60*60*1000; 41 | /** 42 | * Gets the next sunrise or sunset for a given date. 43 | * @param {Date} date a date 44 | * @return {Object} object containing a `time` and an `isSunrise` field 45 | */ 46 | window.getNextSunRiseSet = (date) => { 47 | let sunrise = date.sunrise(37.318, -121.971); // coordinates of Harker 48 | let sunset = date.sunset(37.318, -121.971); 49 | if (sunrise < date) sunrise = new Date(sunrise.getTime()+MS_PER_DAY); 50 | if (sunset < date) sunset = new Date(sunset.getTime()+MS_PER_DAY); 51 | if (sunrise < sunset) return {time: sunrise, isSunrise: true}; 52 | else return {time: sunset, isSunrise: false}; 53 | } 54 | /** Initializes auto dark mode and updates the current setting as necessary. */ 55 | window.initializeAutoDark = () => { 56 | window.nextSunRiseSet = window.getNextSunRiseSet(new Date()); 57 | if (nextSunRiseSet.isSunrise) localStorage.setItem("darkTheme", true); 58 | else localStorage.setItem("darkTheme", false); 59 | }; 60 | if (localStorage.getItem("autoDark") == "true") window.initializeAutoDark(); 61 | 62 | Vue.config.productionTip = false; 63 | 64 | //let timestamp = new Date(); 65 | if (window.indexedDB) 66 | openDB("harker-bell-db", 1, { 67 | upgrade(db) { 68 | db.createObjectStore("schedules", {keyPath: "date"}); 69 | }, 70 | }).then(db => { 71 | //console.log("==> DB: ", new Date()-timestamp); 72 | window.db = db; 73 | initVue(); 74 | //console.log("==> VUE: ", new Date()-timestamp); 75 | }).catch(err => { 76 | Sentry.captureException(err); 77 | window.db = null; 78 | initVue(); 79 | }); 80 | else initVue(); 81 | function initVue() { 82 | new Vue({ 83 | router, 84 | vuetify, 85 | methods: { 86 | /** Sends a basic event to GA */ 87 | sendAnalyticsHit: function(actionValue, type, category, additionalData) { 88 | // console.log(actionValue, type, category) 89 | 90 | if (window.gtag) window.gtag('event', category, { 91 | 'value': actionValue, 92 | 'hit_type': type, 93 | 'hit_time': new Date().getTime(), 94 | 'timestamp_minutes': Math.floor(new Date().getTime() / 60000) * 60000, 95 | 'clock': new Intl.DateTimeFormat('en-GB', { 96 | hour: '2-digit', 97 | minute: '2-digit', 98 | second: '2-digit', 99 | hour12: false 100 | }).format(new Date()), 101 | 'branch': window.nf_branch, 102 | 103 | ...additionalData 104 | }); 105 | } 106 | }, 107 | render: h => h(App), 108 | }).$mount("#app"); 109 | } 110 | localStorage.setItem("appVersion", process.env.VUE_APP_VERSION); 111 | 112 | gtag('set', 'user_properties', { 'appVersion': process.env.VUE_APP_VERSION || "not set" }); // dimension5 113 | if (window.GA_MEASUREMENT_ID) gtag('get', window.GA_MEASUREMENT_ID, 'client_id', function(clientId) { 114 | Sentry.setTag("clientId", clientId) 115 | }); 116 | 117 | ga("require", "eventTracker", {events: ["click", "contextmenu", "focus"]}); 118 | ga("require", "outboundLinkTracker", { 119 | events: ["click", "contextmenu", "auxclick"], 120 | shouldTrackOutboundLink: () => true}); 121 | ga("require", "pageVisibilityTracker", {visibleThreshold: 500, visibleMetricIndex: 1}); 122 | ga("require", "urlChangeTracker"); -------------------------------------------------------------------------------- /src/material-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Material Icons Outlined"; 3 | font-style: normal; 4 | font-display: block; 5 | font-weight: 400; 6 | src: local("Material Icons Outlined"), 7 | local("MaterialIcons-Outlined"), 8 | url(assets/fonts/MaterialIcons-Outlined.woff2) format("woff2"), /* Modern browsers */ 9 | url(assets/fonts/MaterialIcons-Outlined.woff) format("woff"); /* IE11 */ 10 | } 11 | .material-icons { 12 | font-family: "Material Icons Outlined"; 13 | font-weight: normal; 14 | font-style: normal; 15 | font-size: 24px; /* Preferred icon size */ 16 | display: inline-block; 17 | line-height: 1; 18 | text-transform: none; 19 | letter-spacing: normal; 20 | word-wrap: normal; 21 | white-space: nowrap; 22 | direction: ltr; 23 | 24 | /* Support for all WebKit browsers. */ 25 | -webkit-font-smoothing: antialiased; 26 | /* Support for Safari and Chrome. */ 27 | text-rendering: optimizeLegibility; 28 | 29 | /* Support for Firefox. */ 30 | -moz-osx-font-smoothing: grayscale; 31 | 32 | /* Support for IE. */ 33 | font-feature-settings: "liga"; 34 | } -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify from "vuetify/lib"; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({ 7 | theme: { 8 | themes: { 9 | light: { 10 | primary: "#FFFFFF", 11 | secondary: "#BDC1C6", 12 | accent: "#005841", 13 | error: "#D93025", 14 | warning: "#EA8600", 15 | success: "#188038", 16 | info: "#1A73E8", 17 | anchor: "#1A73E8", 18 | red2: {base: "#EA4335", lighten5: "#FAEAEE", darken4: "#CF2035"}, 19 | deeporange2: {lighten5: "#FEEDE8", darken4: "#FA7B17"}, 20 | orange2: {base: "#FA7B17", lighten5: "#FEEFE3", darken4: "#B36600"}, 21 | yellow2: {base: "#FBBC04", lighten5: "#FEF7E0", darken4: "#B06000"}, 22 | lightgreen2: {lighten5: "#E9FAF7", darken4: "#027E5A"}, 23 | green2: {base: "#0F9D58", lighten5: "#E6F4EA", darken4: "#137333"}, 24 | teal2: {base: "#00C3A6", lighten5: "#E4F7FB", darken4: "#007B86"}, 25 | lightblue2: {lighten5: "#F6FAFE", darken4: "#047CBD"}, 26 | blue2: {base: "#1A73E8", lighten4: "#E8F0FE", lighten5: "#E9EEFF", darken3: "#1A73E8", darken4: "#2A56C6"}, 27 | indigo2: {lighten5: "#F1F5FA", darken4: "#1E267D"}, 28 | purple2: {base: "#A142F4", lighten5: "#F4EAFF", darken4: "#9345BD"}, 29 | pink2: {base: "#F538A0", lighten5: "#FDE7F3", darken4: "#B80672"}, 30 | bluegrey2: {base: "#9AA0A6", lighten5: "#E8EAED", darken4: "#2C323D"}, 31 | grey2: {lighten5: "#FFFFFF", darken4: "#202124"}, 32 | }, 33 | dark: { 34 | primary: "#202124", 35 | secondary: "#9AA0A6", 36 | accent: "#098060", 37 | error: "#EA4335", 38 | warning: "#FBBC04", 39 | success: "#34A853", 40 | info: "#8AB4F8", 41 | anchor: "#8AB4F8", 42 | red2: {base: "#EA4335", lighten5: "#534343", darken4: "#F6AEA9"}, 43 | deeporange2: {lighten5: "#634947", darken4: "#F5BBA9"}, 44 | orange2: {base: "#FA7B17", lighten5: "#635247", darken4: "#F5CBA9"}, 45 | yellow2: {base: "#FBBC04", lighten5: "#554C33", darken4: "#FDD663"}, 46 | lightgreen2: {lighten5: "#374A41", darken4: "#9DEDB4"}, 47 | green2: {base: "#34A853", lighten5: "#37493F", darken4: "#7FD095"}, 48 | teal2: {base: "#00C3A6", lighten5: "#31504C", darken4: "#00DCB0"}, 49 | lightblue2: {lighten5: "#474D58", darken4: "#A6C5F9"}, 50 | blue2: {base: "#4285F4", lighten4: "#394456", lighten5: "#394456", darken3: "#8AB4F8", darken4: "#8AB4F8"}, 51 | indigo2: {lighten5: "#223957", darken4: "#87A3FF"}, 52 | purple2: {base: "#A142F4", lighten5: "#473A57", darken4: "#BBB3FF"}, 53 | pink2: {base: "#FF63B8", lighten5: "#4F2F43", darken4: "#FDB0C9"}, 54 | bluegrey2: {base: "#9AA0A6", lighten5: "#3C4043", darken4: "#E8EAED"}, 55 | grey2: {lighten5: "#202124", darken4: "#FFFFFF"}, 56 | }, 57 | }, 58 | options: { 59 | customProperties: true 60 | }, 61 | }, 62 | icons: { 63 | iconfont: "mdiSvg" 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | registrationOptions: { 8 | scope: '/' 9 | }, 10 | ready () { 11 | console.log( 12 | 'App is being served from cache by a service worker.\n' + 13 | 'For more details, visit https://goo.gl/AFskqB' 14 | ) 15 | }, 16 | registered () { 17 | console.log('Service worker has been registered.') 18 | }, 19 | cached () { 20 | console.log('Content has been cached for offline use.') 21 | window.dispatchEvent(new Event("pwaOfflineReady")); 22 | }, 23 | updatefound () { 24 | console.log('New content is downloading.') 25 | }, 26 | updated () { 27 | console.log('New content is available; please refresh.') 28 | window.dispatchEvent(new Event("pwaUpdated")); 29 | }, 30 | offline () { 31 | console.log('No internet connection found. App is running in offline mode.') 32 | }, 33 | error (error) { 34 | console.error('Error during service worker registration:', error) 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | import Home from "./views/Home.vue"; 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | mode: "history", 9 | base: process.env.BASE_URL, 10 | routes: [ 11 | { 12 | path: "/", 13 | name: "home", 14 | component: Home 15 | }, 16 | { 17 | path: "/index.html", 18 | name: "index", 19 | component: Home 20 | }, 21 | { 22 | path: "/settings", 23 | name: "settings", 24 | component: Home 25 | }, 26 | { 27 | path: "/export", 28 | name: "export", 29 | component: Home 30 | }, 31 | { 32 | path: "/:year/:month/:day", 33 | name: "day", 34 | component: Home 35 | }, 36 | ] 37 | }); -------------------------------------------------------------------------------- /src/sass/main.scss: -------------------------------------------------------------------------------- 1 | $spacer: 2px; 2 | 3 | @import "~vuetify/src/styles/styles.sass"; 4 | 5 | $headings: map-merge($headings, ( 6 | h5: map-merge( 7 | map-get($headings, "h5"), 8 | ("letter-spacing": -.02rem) 9 | ), 10 | h6: map-merge( 11 | map-get($headings, "h6"), 12 | ("letter-spacing": -.02rem) 13 | ), 14 | "body-1": map-merge( 15 | map-get($headings, "body-1"), 16 | ("size": .875rem) 17 | ), 18 | "body-2": map-merge( 19 | map-get($headings, "body-2"), 20 | ("size": .8125rem) 21 | ), 22 | caption: map-merge( 23 | map-get($headings, "caption"), 24 | ("letter-spacing": 0.01rem) 25 | ) 26 | )); 27 | $material-light: map-merge($material-light, ( 28 | "background": map-get($shades, "white"), 29 | )); 30 | $material-dark: map-merge($material-dark, ( 31 | "background": #202124, 32 | "cards": #202124, 33 | )); 34 | $border-radius-root: 2px; 35 | $btn-outline-border-width: 1px; 36 | $tooltip-font-size: 12px; 37 | $icon-outlined-border-width: 1px; 38 | $list-item-min-height: 36px; 39 | $timeline-dot-small-size: 7px; 40 | $timeline-item-padding: 4px; 41 | $timeline-divider-width: 17px; 42 | $timeline-line-width: 1px; -------------------------------------------------------------------------------- /src/scripts/sun.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sunrise/sunset script. By Matt Kane. 3 | * 4 | * Based loosely and indirectly on Kevin Boone's SunTimes Java implementation 5 | * of the US Naval Observatory's algorithm. 6 | * 7 | * Copyright © 2012 Triggertrap Ltd. All rights reserved. 8 | * 9 | * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General 10 | * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 11 | * any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied 14 | * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | * details. 16 | * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to 17 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, 18 | * or connect to: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 19 | */ 20 | 21 | 22 | Date.prototype.sunrise = function(latitude, longitude, zenith) { 23 | return this.sunriseSet(latitude, longitude, true, zenith); 24 | } 25 | 26 | Date.prototype.sunset = function(latitude, longitude, zenith) { 27 | return this.sunriseSet(latitude, longitude, false, zenith); 28 | } 29 | 30 | Date.prototype.sunriseSet = function(latitude, longitude, sunrise, zenith) { 31 | if(!zenith) { 32 | zenith = 90.8333; 33 | } 34 | 35 | 36 | var hoursFromMeridian = longitude / Date.DEGREES_PER_HOUR, 37 | dayOfYear = this.getDayOfYear(), 38 | approxTimeOfEventInDays, 39 | sunMeanAnomaly, 40 | sunTrueLongitude, 41 | ascension, 42 | rightAscension, 43 | lQuadrant, 44 | raQuadrant, 45 | sinDec, 46 | cosDec, 47 | cosLocalHourAngle, 48 | localHourAngle, 49 | localHour, 50 | localMeanTime, 51 | time; 52 | 53 | if (sunrise) { 54 | approxTimeOfEventInDays = dayOfYear + ((6 - hoursFromMeridian) / 24); 55 | } else { 56 | approxTimeOfEventInDays = dayOfYear + ((18.0 - hoursFromMeridian) / 24); 57 | } 58 | 59 | sunMeanAnomaly = (0.9856 * approxTimeOfEventInDays) - 3.289; 60 | 61 | sunTrueLongitude = sunMeanAnomaly + (1.916 * Math.sinDeg(sunMeanAnomaly)) + (0.020 * Math.sinDeg(2 * sunMeanAnomaly)) + 282.634; 62 | sunTrueLongitude = Math.mod(sunTrueLongitude, 360); 63 | 64 | ascension = 0.91764 * Math.tanDeg(sunTrueLongitude); 65 | rightAscension = 360 / (2 * Math.PI) * Math.atan(ascension); 66 | rightAscension = Math.mod(rightAscension, 360); 67 | 68 | lQuadrant = Math.floor(sunTrueLongitude / 90) * 90; 69 | raQuadrant = Math.floor(rightAscension / 90) * 90; 70 | rightAscension = rightAscension + (lQuadrant - raQuadrant); 71 | rightAscension /= Date.DEGREES_PER_HOUR; 72 | 73 | sinDec = 0.39782 * Math.sinDeg(sunTrueLongitude); 74 | cosDec = Math.cosDeg(Math.asinDeg(sinDec)); 75 | cosLocalHourAngle = ((Math.cosDeg(zenith)) - (sinDec * (Math.sinDeg(latitude)))) / (cosDec * (Math.cosDeg(latitude))); 76 | 77 | localHourAngle = Math.acosDeg(cosLocalHourAngle) 78 | 79 | if (sunrise) { 80 | localHourAngle = 360 - localHourAngle; 81 | } 82 | 83 | localHour = localHourAngle / Date.DEGREES_PER_HOUR; 84 | 85 | localMeanTime = localHour + rightAscension - (0.06571 * approxTimeOfEventInDays) - 6.622; 86 | 87 | time = localMeanTime - (longitude / Date.DEGREES_PER_HOUR); 88 | time = Math.mod(time, 24); 89 | 90 | var midnight = new Date(0); 91 | midnight.setUTCFullYear(this.getUTCFullYear()); 92 | midnight.setUTCMonth(this.getUTCMonth()); 93 | midnight.setUTCDate(this.getUTCDate()); 94 | 95 | 96 | 97 | var milli = midnight.getTime() + (time * 60 *60 * 1000); 98 | 99 | 100 | return new Date(milli); 101 | } 102 | 103 | Date.DEGREES_PER_HOUR = 360 / 24; 104 | 105 | 106 | // Utility functions 107 | 108 | Date.prototype.getDayOfYear = function() { 109 | var onejan = new Date(this.getFullYear(),0,1); 110 | return Math.ceil((this - onejan) / 86400000); 111 | } 112 | 113 | Math.degToRad = function(num) { 114 | return num * Math.PI / 180; 115 | } 116 | 117 | Math.radToDeg = function(radians){ 118 | return radians * 180.0 / Math.PI; 119 | } 120 | 121 | Math.sinDeg = function(deg) { 122 | return Math.sin(deg * 2.0 * Math.PI / 360.0); 123 | } 124 | 125 | 126 | Math.acosDeg = function(x) { 127 | return Math.acos(x) * 360.0 / (2 * Math.PI); 128 | } 129 | 130 | Math.asinDeg = function(x) { 131 | return Math.asin(x) * 360.0 / (2 * Math.PI); 132 | } 133 | 134 | 135 | Math.tanDeg = function(deg) { 136 | return Math.tan(deg * 2.0 * Math.PI / 360.0); 137 | } 138 | 139 | Math.cosDeg = function(deg) { 140 | return Math.cos(deg * 2.0 * Math.PI / 360.0); 141 | } 142 | 143 | Math.mod = function(a, b) { 144 | var result = a % b; 145 | if(result < 0) { 146 | result += b; 147 | } 148 | return result; 149 | } 150 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 121 | 122 | 302 | 303 | 437 | 444 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | process.env.VUE_APP_VERSION = require("./package.json").version; 2 | 3 | module.exports = { 4 | devServer: { 5 | disableHostCheck: true, 6 | public: '127.0.0.1:8080' 7 | }, 8 | productionSourceMap: true, 9 | transpileDependencies: [], 10 | css: { 11 | loaderOptions: { 12 | sass: { 13 | data: `@import "~@/sass/main.scss"` 14 | } 15 | } 16 | }, 17 | pwa: { 18 | name: "Harker Bell Schedule", 19 | themeColor: "#FFFFFF", 20 | msTileColor: "#005841", 21 | appleMobileWebAppCapable: "yes", 22 | workboxOptions: { 23 | cleanupOutdatedCaches: true, 24 | clientsClaim: true, 25 | exclude: [/_redirects/], 26 | navigateFallback: "/index.html", 27 | navigateFallbackBlacklist: [/api/, /docs/, /admin/, /submitevent/, /p/], 28 | offlineGoogleAnalytics: { 29 | parameterOverrides: { 30 | cd14: "offline", 31 | }, 32 | hitFilter: params => { 33 | const queueTime = Math.round(params.get("qt")/1000); 34 | params.set("cm3", queueTime); 35 | }, 36 | }, 37 | runtimeCaching: [{ 38 | urlPattern: /^https:\/\/fonts\.googleapis\.com/, 39 | handler: "staleWhileRevalidate", 40 | }, { 41 | urlPattern: /^https:\/\/fonts\.gstatic\.com/, 42 | handler: "cacheFirst", 43 | options: { 44 | cacheName: "google-fonts-files", 45 | expiration: { 46 | maxAgeSeconds: 60*60*24*365, 47 | maxEntries: 30 48 | } 49 | } 50 | }, { 51 | urlPattern: "https://www.google-analytics.com/analytics.js", 52 | handler: "staleWhileRevalidate" 53 | }, { 54 | urlPattern: "https://ipmeta.io/plugin.js", 55 | handler: "staleWhileRevalidate" 56 | }, { 57 | urlPattern: "https://qbw8rkkv7x0h.statuspage.io/embed/script.js", 58 | handler: "staleWhileRevalidate" 59 | }], 60 | skipWaiting: true, 61 | }, 62 | }, 63 | }; --------------------------------------------------------------------------------