Compare commits

...

13 Commits

Author SHA1 Message Date
f85d14de1d add screenshot to readme
Some checks failed
Continuous Deployment / deploy (push) Has been cancelled
2023-09-06 19:52:26 +01:00
7b0f8442ea Add epitaph to README 2023-09-06 11:09:27 +01:00
975cfa371d Merge pull request #96 from 0hAodha/notanerror
Fix false "error fetching live train data" error
2023-08-15 11:18:55 +00:00
3be37dad99 Fix logs to reflect that empty snapshot is likely not an error
Additionally replace incorrect 404 message
2023-08-15 12:15:31 +01:00
63d2508423 Replace throwing error with console.warn() when no train data in response 2023-08-15 12:12:21 +01:00
4a9089fbdc Fix PDF link
Github doesn't like relative file paths with shell escaping; to be rendered on the repo homepage, file links in the README must use HTTP escaping
2023-08-15 12:01:53 +01:00
f051c0929d Add link to PDF writeup in README
List the group members, link to the project writeup, and tweak some wording in the Setup section
2023-08-15 11:56:51 +01:00
6ee4ff9878 Merge pull request #95 from 0hAodha/remove_dist
Remove dist directory and add it to .gitignore
2023-08-15 10:42:21 +00:00
5a9275b3e0 Remove dist directory and add it to .gitignore 2023-08-15 11:39:48 +01:00
ea87ee98dd Merge pull request #90 from 0hAodha/css
partial fix for issue #82
2023-07-11 21:27:56 +01:00
7fe10ebe5b fix stretched headerImage in StationSidebar 2023-07-11 21:24:15 +01:00
006dc0f21c fix strethed images in TrainSidebar 2023-07-11 21:21:21 +01:00
47baea86f7 fix some of the stretched images in StationSidebar 2023-07-11 21:19:28 +01:00
33 changed files with 35 additions and 2228 deletions

2
.gitignore vendored
View File

@ -68,3 +68,5 @@ node_modules/
# ide configurations
.idea/
.vscode/
dist/

View File

@ -1,5 +1,7 @@
# Irish Rail Tracker ([irishrailtracker.web.app](https://irishrailtracker.web.app/))
**THIS PROJECT IS NO LONGER MAINTAINED AND THE HOSTING IS NO LONGER ACTIVE. THE WEBSITE HAS CEASED TO OPERATE AS OF 2023-09-06.**
## Introduction
![screenshot](./screenshot.png)
A webapp which tracks the current locations of Irish Rail trains and plots them on a live map, built with Vue.js, Bootstrap and Firebase.
Live train data is periodically fetched from the official Irish Rail Developer REST API, and stored in the Firestore database. This data is fetched by the webapp client from the Firestore database upon page load and periodically after that. The data is plotted on a map using OpenLayers as the backend and OpenStreetMap as the source of the tile images.
@ -12,6 +14,8 @@ Additional features include:
- A live message ticker at the bottom of the page displaying the most recent public announcements from each train.
- Optional plotting of each train station on the map, which can also be filtered via searching and the aforementioned filtering system.
This webapp was developed as a group project by Andrew Hayes, Conor McNamara, Jack Lennox, & Owen Guillot for the CT216 Software Engineering module at the University of Galway. The project writeup can be read here: [CT216: Irish Rail Tracker Project Report.pdf](./CT216:%20Irish%20Rail%20Tracker%20Project%20Report.pdf).
## Setup
### Local Emulation
#### Prerequisites
@ -19,7 +23,7 @@ Additional features include:
- `npm`
#### Setup
1. Run `npm install` in the root directory of the project (`traintracker/`). Note that this may require root privileges on certain operating systems (i.e., to be ran with `sudo`).
1. Run `npm install` in the root directory of the project (`traintracker/`). Note that this may require root privileges on many operating systems.
2. Run `npm install` in the `traintracker/functions/` directory.
4. Start the Firebase emulator, which is needed to emulate the Firebase functions locally. Note that the Firebase emulator will occupy the focus of your terminal while it is running, so you will either need to keep it in its own dedicated terminal window or fork it into the background by appending `&` to the following command:
```bash
@ -48,6 +52,7 @@ There are also a series of Firebase integration tests which are automatically ra
cd functions && npm run test
```
## Manual Firebase Deployment
The webapp is automatically deployed to Firebase via Github Actions automatically when a branch is merged into `main`, provided that it successfully passed the Vue.js unit & Firebase integration tests. It can also be manually deployed with the following command ran in the `traintracker/` directory, **but this is not to be done without explicit approval of each of the project owners**. You will need the appropriate Firebase authentication tokens to do this.
```shell

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1 +0,0 @@
const s="/assets/314858_hidden_eye_icon-5431635a.png",_="/assets/315220_eye_icon-8d95e9db.png";export{s as _,_ as a};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -1 +0,0 @@
import{N as a}from"./Navbar-fcee2b9c.js";import{_ as o,r as n,o as t,c as r,a as s,F as c,b as _}from"./index-9a3ecfe5.js";const p={name:"404Page",components:{Navbar:a}},m=_("h1",null,"404 - You've been derailed :(",-1);function l(d,f,i,u,N,b){const e=n("Navbar");return t(),r(c,null,[s(e),m],64)}const g=o(p,[["render",l]]);export{g as default};

View File

@ -1 +0,0 @@
h1[data-v-b1085315]{color:#000;text-align:center}h3[data-v-b1085315]{font-size:18px;padding-top:20px}#passReset[data-v-b1085315]{font-size:17px;text-decoration:underline;color:#39d3fa}#passReset[data-v-b1085315]:hover{color:#3993fa;cursor:pointer}#accountDiv[data-v-b1085315]{position:absolute;right:0px;left:0px;bottom:0px;background-color:#fff;height:100%;display:flex;align-items:flex-start;justify-content:center}#accountDiv div[data-v-b1085315]{position:inherit;padding:15px;background-color:#fff;width:45%;height:80%;top:14%;text-align:left;box-shadow:0 0 4px 4px #b6b6b6}#emailUpdate[data-v-b1085315],#passUpdate[data-v-b1085315]{position:relative;left:10px;width:26%}input[data-v-b1085315]{border:none;border-bottom:1px solid #000000;background:transparent;outline:none}#delAcc[data-v-b1085315]{position:absolute;bottom:10px;left:10px}#delPref[data-v-b1085315]{position:absolute;bottom:10px;left:160px}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
div[data-v-37c13361],div[data-v-e35fddf5]{width:70%}.card-text[data-v-7860af3e],.card-stats[data-v-7860af3e]{font-size:17px}.piechart[data-v-7860af3e]{display:flex;justify-content:center;padding-bottom:0;margin-bottom:0;height:53%}#barChart[data-v-7860af3e]{position:relative;padding:10px;width:100%;top:20px;height:40%}th[data-v-7860af3e]{padding:15px;text-align:center;font-size:19px}#leaderboardTitleDiv p[data-v-7860af3e]{font-family:Franklin Gothic Medium,Arial Narrow,Arial,sans-serif;text-align:center;font-size:50px;padding-top:10px}table[data-v-7860af3e]{border-spacing:1;border-collapse:collapse;background:white;border-radius:6px;overflow:hidden;max-width:1400px;width:100%;margin:0 auto;position:relative;font-size:19px}*[data-v-7860af3e]{position:relative}td[data-v-7860af3e],th[data-v-7860af3e]{padding-left:8px}thead tr[data-v-7860af3e]{height:60px;background:#ffed86;font-size:16px}tbody tr[data-v-7860af3e]{height:48px;border-bottom:1px solid #e3f1d5}tbody tr[data-v-7860af3e]:last-child{border:0;border-bottom:2px solid #d5e0f1}td[data-v-7860af3e],th[data-v-7860af3e]{text-align:left}td.l[data-v-7860af3e],th.l[data-v-7860af3e]{text-align:right}@media screen and (max-width: 820px){table[data-v-7860af3e],table tr[data-v-7860af3e],td[data-v-7860af3e],th[data-v-7860af3e],*[data-v-7860af3e]{display:block}thead[data-v-7860af3e]{display:none}tbody tr[data-v-7860af3e]{height:auto;padding:8px 0}tbody tr td[data-v-7860af3e]{padding-left:45%;margin-bottom:12px}tbody tr td[data-v-7860af3e]:last-child{margin-bottom:0}tbody tr td[data-v-7860af3e]:before{position:absolute;font-weight:700;width:40%;left:10px;top:0}tbody tr td[data-v-7860af3e]:nth-child(1):before{content:"Code"}tbody tr td[data-v-7860af3e]:nth-child(2):before{content:"Time"}tbody tr td[data-v-7860af3e]:nth-child(3):before{content:"Type"}tbody tr td[data-v-7860af3e]:nth-child(4):before{content:"Origin"}tbody tr td[data-v-7860af3e]:nth-child(5):before{content:"Destination"}}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
#background[data-v-b3c98f88]{margin:0;padding:0;width:100%;height:100%;position:absolute;background-color:#e0e0e0;font-family:sans-serif}.loginbox[data-v-b3c98f88]{height:420px;width:320px;background:#000;color:#fff;top:50%;left:50%;position:absolute;transform:translate(-50%,-50%);box-sizing:border-box;padding:70px 30px}#imgDiv[data-v-b3c98f88]{height:10%;width:10%;right:40px;bottom:150px;position:absolute}#eyeImg[data-v-b3c98f88]{height:80%;width:100%}#eyeImg[data-v-b3c98f88]:hover{transform:scale(1.1)}h1[data-v-b3c98f88]{margin:0;padding:0 0 20px;font-size:22px;text-align:center}.loginbox p[data-v-b3c98f88]{margin:0;padding:0;font-weight:700}.loginbox input[data-v-b3c98f88]{width:100%;margin-bottom:20px}.loginbox input[type=email][data-v-b3c98f88],input[type=password][data-v-b3c98f88],input[type=text][data-v-b3c98f88]{border:none;border-bottom:1px solid #fff;background:transparent;outline:none;height:40px;color:#fff;font-size:16px}.loginbox input[type=submit][data-v-b3c98f88]:hover{cursor:pointer;background:#66a3ff;color:#000}.loginbox a[data-v-b3c98f88]{text-decoration:none;font-size:12px;line-height:20px;color:#a9a9a9;display:flex}.loginbox a[data-v-b3c98f88]:hover{color:#ffc107}.loginbox input[type=submit][data-v-b3c98f88]{border:none;outline:none;height:40px;background:#0052cc;font-size:18px;border-radius:20px}.avatar[data-v-b3c98f88]{width:100px;height:100px;border-radius:50%;position:absolute;top:-50px;left:calc(50% - 50px)}

View File

@ -1 +0,0 @@
import{_ as c,g as u,$ as _,n as f,r as p,o as a,c as n,a as h,b as e,q as d,v as m,w as v,F as P,e as k,h as y,p as b,j as E}from"./index-9a3ecfe5.js";import{t as T}from"./style-1561178c.js";import{N as C}from"./Navbar-fcee2b9c.js";import{_ as x,a as I}from"./315220_eye_icon-30328a0f.js";const B=u(),N={name:"LoginPage",data(){return{email:"",password:"",toastMessage:"",toastBackground:"",forgotPassword:!1,showPassword:!1,toast:()=>{T(this.toastMessage,{hideProgressBar:!0,timeout:4e3,toastBackgroundColor:this.toastBackground})}}},components:{Navbar:C},methods:{showToast(i,s){this.toastMessage=i,this.toastBackground=s,this.toast()},login(){const i=u(k);if(!this.email||!this.password){this.showToast("Missing credentials","red");return}_(i,this.email,this.password).then(()=>{this.showToast("Logged in successfully","green"),this.$router.push({path:"/"})}).catch(s=>{s.message.includes("email")?this.showToast("Invalid email","red"):s.message.includes("user")?this.showToast("Could not find this user","red"):this.showToast(s.message,"red")})},resetPasswordEmail(){if(!this.email){this.showToast("Missing credentials","red");return}f(B,this.email).then(()=>{this.showToast("Reset password email sent","green"),this.email=""}).catch(i=>{i.message.includes("email")?this.showToast("Invalid email","red"):i.message.includes("user")?this.showToast("Could not find this user","red"):this.showToast(i.message,"red")})}}},l=i=>(b("data-v-b3c98f88"),i=i(),E(),i),V={id:"background"},L={class:"loginbox"},M=l(()=>e("img",{src:"https://cdn.discordapp.com/attachments/1017419092447207436/1063092138029625394/pixil-frame-0.png",class:"avatar"},null,-1)),A={key:0},F=l(()=>e("h1",null,"Login",-1)),S=l(()=>e("p",null,"Email Address",-1)),U=l(()=>e("p",null,"Password",-1)),D={id:"imgDiv"},R={key:1},H=l(()=>e("h1",null,"Forgot Password",-1)),j=l(()=>e("p",null,"Email Address",-1));function q(i,s,G,W,o,r){const w=p("Navbar"),g=p("router-link");return a(),n(P,null,[h(w),e("div",V,[e("div",L,[M,o.forgotPassword?(a(),n("div",R,[H,j,d(e("input",{type:"email","onUpdate:modelValue":s[7]||(s[7]=t=>o.email=t),"aria-describedby":"emailHelp",placeholder:"Enter email"},null,512),[[m,o.email]]),e("input",{onClick:s[8]||(s[8]=(...t)=>r.resetPasswordEmail&&r.resetPasswordEmail(...t)),type:"submit",name:"",value:"Send Reset Email"}),e("a",{onClick:s[9]||(s[9]=t=>{o.forgotPassword=!o.forgotPassword,this.email=""})},"Go back")])):(a(),n("div",A,[F,S,d(e("input",{type:"email","onUpdate:modelValue":s[0]||(s[0]=t=>o.email=t),"aria-describedby":"emailHelp",placeholder:"Enter email"},null,512),[[m,o.email]]),U,e("div",D,[o.showPassword?(a(),n("img",{key:0,id:"eyeImg",src:x,onClick:s[1]||(s[1]=t=>this.showPassword=!this.showPassword),alt:"show"})):(a(),n("img",{key:1,id:"eyeImg",src:I,onClick:s[2]||(s[2]=t=>this.showPassword=!this.showPassword)}))]),o.showPassword?d((a(),n("input",{key:0,type:"text","onUpdate:modelValue":s[3]||(s[3]=t=>o.password=t),placeholder:"Enter password"},null,512)),[[m,o.password]]):d((a(),n("input",{key:1,type:"password","onUpdate:modelValue":s[4]||(s[4]=t=>o.password=t),placeholder:"Enter password"},null,512)),[[m,o.password]]),e("input",{onClick:s[5]||(s[5]=(...t)=>r.login&&r.login(...t)),type:"submit",name:"",value:"Login"}),e("a",{onClick:s[6]||(s[6]=t=>{o.forgotPassword=!o.forgotPassword,this.email=""})},"Forgot password?"),e("a",null,[h(g,{to:"/signup"},{default:v(()=>[y("Don't have an account?")]),_:1})])]))])])],64)}const Q=c(N,[["render",q],["__scopeId","data-v-b3c98f88"]]);export{Q as default};

View File

@ -1 +0,0 @@
#sidebarHeader[data-v-ba9cd2b3]{position:relative;top:0%;height:15%;width:100%;overflow:hidden}#sidebarDiv[data-v-ba9cd2b3]{position:absolute;height:80%;width:100%;color:#000}.headerImage[data-v-ba9cd2b3]{height:100%;width:40px;padding-top:10px}.imageDiv[data-v-ba9cd2b3]{display:flex;justify-content:center}#xButton[data-v-ba9cd2b3]{font-size:80%;font-family:Georgia;color:#000;position:absolute;top:10px;right:10px}#xButton[data-v-ba9cd2b3]:hover{color:red}#sidebarHeader[data-v-8a09d5be]{position:relative;top:0%;height:15%;width:100%;overflow:hidden}#sidebarDiv[data-v-8a09d5be]{position:absolute;height:80%;width:100%;color:#000}#headerImage[data-v-8a09d5be]{height:80%;width:auto;overflow:hidden;position:relative;top:10px}#xButton[data-v-8a09d5be]{font-size:80%;font-family:Georgia;color:#000;position:absolute;top:10px;right:10px}#xButton[data-v-8a09d5be]:hover{color:red}#mapCover[data-v-33827e44]{height:100%;width:100%;display:flex;justify-content:center}.overlay-content[data-v-33827e44]{width:1%}.trainMapIcon[data-v-33827e44]{width:28px;height:32px}.trainMapIcon[data-v-33827e44]:hover{transform:scale(1.2);cursor:pointer}.stationMapIcon[data-v-33827e44]{width:14px;height:17px}.stationMapIcon[data-v-33827e44]:hover{width:16px;height:19px;cursor:pointer}#dropdownMenuButton1[data-v-33827e44]{box-shadow:0 0 5px 2px #6e757dbe}#dropMenu[data-v-33827e44]{font-size:14.6px}#searchContainer[data-v-33827e44]{position:absolute;top:11%;background-color:#fff;width:190px;height:40px;z-index:3;display:flex;justify-content:center;align-items:center;color:#000;box-shadow:0 0 5px 2px #6e757dbe;border-radius:6%}#searchBar[data-v-33827e44]{text-align:center;font-family:Franklin Gothic Medium,Arial Narrow,Arial,sans-serif;border:none;border-bottom:1px solid black;z-index:3}#preferenceDropdown[data-v-33827e44]{position:absolute;z-index:3;right:1%;top:11%}#prefHeader[data-v-33827e44]{font-size:18px;font-family:Franklin Gothic Medium,Arial Narrow,Arial,sans-serif;text-align:center;position:relative}#sidebar[data-v-33827e44]{position:absolute;height:80%;width:20%;left:2%;top:14%;z-index:2;text-align:center;animation:gradient-33827e44 15s ease infinite;background:linear-gradient(45deg,#ffffff,#fef3f3,#ffffff,#f2f2f2);background-size:100%,100%;box-shadow:0 0 4px 2px #ccc;overflow:hidden;font-family:Franklin Gothic Medium,Arial Narrow,Arial,sans-serif}#savePref[data-v-33827e44]{left:2%;top:2px;width:95%;position:relative}.slideLeft-enter-active[data-v-33827e44],.slideLeft-leave-active[data-v-33827e44]{transition:opacity .5s;transition:all .8s}.slideLeft-enter-from[data-v-33827e44],.slideLeft-leave-to[data-v-33827e44]{opacity:0;transform:translate(-100px)}@keyframes gradient-33827e44{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}#publicMessageTicker[data-v-33827e44]{z-index:3;position:fixed;bottom:0px;width:100%;background-color:#ffff7d;color:#000;font-family:Franklin Gothic Medium,Arial Narrow,Arial,sans-serif;text-align:bottom;font-size:16px}@media screen and (max-width: 850px){#sidebar[data-v-33827e44]{height:75%;width:90%;left:5%;top:18%}#mapCover[data-v-33827e44]{height:100%;width:100%;display:flex;justify-content:left;z-index:3}#searchBar[data-v-33827e44]{width:100px}#searchContainer[data-v-33827e44]{position:absolute;top:11%;left:10px;background-color:#fff;width:120px;height:40px;z-index:3;display:flex;justify-content:center;align-items:center;color:#000;box-shadow:0 0 5px 2px #6e757dbe;border-radius:6%}}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.router-link-active[data-v-9f58d794]{color:#000;font-weight:600;cursor:pointer}a[data-v-9f58d794]{text-decoration:none;color:#000;font-weight:100}

View File

@ -1 +0,0 @@
import{d as b,_ as f,g as h,e as u,f as m,s as S,r as k,o as i,c as l,b as e,a,w as o,h as n,i as r,p as I,j as y}from"./index-9a3ecfe5.js";const p=b({insights:{},orderedTrains:[],selectedTrain:{},selectedStation:{},rawData:{},displaySelectedTrain:!1,displayedSelectedStation:!1,loggedIn:!1,isWaitingForLoginStatus:!0,setInsights(t){this.insights=t},setRawData(t){this.rawData=t},setOrderedTrains(t){t.sort((c,_)=>c.time-_.time),this.orderedTrains=t},setSelectedTrain(t){this.selectedTrain=t},setSelectedStation(t){this.selectedStation=t},setDisplaySelectedTrain(t){this.displaySelectedTrain=t},setDisplaySelectedStation(t){this.displaySelectedStation=t},setLoginStatus(t){this.loggedIn=t}});const L={name:"Navbar",data(){return{isLoggedIn:!1}},created(){const t=h(u);m(t,c=>{c?this.isLoggedIn=!0:this.isLoggedIn=!1,p.setLoginStatus(this.isLoggedIn),p.isWaitingForLoginStatus=!1})},methods:{logout(){S(h(u)).then(()=>{this.$router.push("/")})}}},g=t=>(I("data-v-9f58d794"),t=t(),y(),t),x={style:{"z-index":"4"},class:"navbar navbar-expand-sm navbar-light bg-light"},T={class:"container-fluid"},w=g(()=>e("img",{src:"https://cdn.discordapp.com/attachments/1017419092447207436/1063092138029625394/pixil-frame-0.png",alt:"mascot",width:"55",height:"40",class:"d-inline-block align-text-middle"},null,-1)),C=g(()=>e("b",null,"Irish Rail Tracker",-1)),N=g(()=>e("button",{class:"navbar-toggler",type:"button","data-bs-toggle":"collapse","data-bs-target":"#navbarSupportedContent","aria-controls":"navbarSupportedContent","aria-expanded":"false","aria-label":"Toggle navigation"},[e("span",{class:"navbar-toggler-icon"})],-1)),D={class:"collapse navbar-collapse",id:"navbarSupportedContent"},V={class:"navbar-nav me-auto mb-2 mb-lg-0"},A={class:"nav-item"},B={class:"nav-link"},F={class:"nav-item"},O={class:"nav-link"},R={class:"nav-item"},W={key:0,class:"nav-link"},j={key:1,class:"nav-link"},z={class:"nav-item"},E={key:0,id:"logout",class:"nav-link"},H={key:1,class:"nav-link"};function U(t,c,_,q,d,v){const s=k("router-link");return i(),l("nav",x,[e("div",T,[a(s,{tag:"a",style:{"text-decoration":"none",color:"black","font-weight":"100"},to:"/",class:"navbar-brand"},{default:o(()=>[w,C]),_:1}),N,e("div",D,[e("ul",V,[e("li",A,[e("a",B,[a(s,{to:"/"},{default:o(()=>[n("Home")]),_:1})])]),e("li",F,[e("a",O,[a(s,{to:"/insights"},{default:o(()=>[n("Insights")]),_:1})])]),e("li",R,[d.isLoggedIn?r("",!0):(i(),l("a",W,[a(s,{to:"/login"},{default:o(()=>[n("Login")]),_:1})])),d.isLoggedIn?(i(),l("a",j,[a(s,{to:"/account"},{default:o(()=>[n("Account Settings")]),_:1})])):r("",!0)]),e("li",z,[d.isLoggedIn?(i(),l("a",E,[a(s,{style:{"text-decoration":"none",color:"black","font-weight":"100"},onClick:v.logout,to:"/",class:"navlink"},{default:o(()=>[n("Logout")]),_:1},8,["onClick"])])):r("",!0),d.isLoggedIn?r("",!0):(i(),l("a",H,[a(s,{to:"/signup"},{default:o(()=>[n("Sign Up")]),_:1})]))])])])])])}const J=f(L,[["render",U],["__scopeId","data-v-9f58d794"]]);export{J as N,p as s};

View File

@ -1 +0,0 @@
import{_ as g,g as w,a3 as _,r as h,o as i,c as r,a as u,b as e,q as d,v as l,w as v,F as f,e as y,h as k,p as P,j as b}from"./index-9a3ecfe5.js";import{t as x}from"./style-1561178c.js";import{N as S}from"./Navbar-fcee2b9c.js";import{_ as T,a as B}from"./315220_eye_icon-30328a0f.js";const U={name:"SignupPage",data(){return{email:"",password:"",toastMessage:"",toastBackground:"",showPassword:!1,toast:()=>{x(this.toastMessage,{hideProgressBar:!0,timeout:4e3,toastBackgroundColor:this.toastBackground})}}},components:{Navbar:S},methods:{showToast(t,s){this.toastMessage=t,this.toastBackground=s,this.toast()},signup(){if(!this.email||!this.password){this.showToast("Missing credentials","red");return}if(this.password.length<6){this.showToast("Password must be 6 or more characters","red");return}const t=w(y);_(t,this.email,this.password).then(()=>{this.showToast("Signed up successfully","green"),this.$router.push({path:"/"})}).catch(s=>{s.message.includes("already")?this.showToast("Email already in use","red"):s.message.includes("email")?this.showToast("Invalid email","red"):this.showToast(s.message,"red")})}}},n=t=>(P("data-v-0ac92d6f"),t=t(),b(),t),E={id:"background"},I={class:"loginbox"},N=n(()=>e("img",{src:"https://cdn.discordapp.com/attachments/1017419092447207436/1063092138029625394/pixil-frame-0.png",class:"avatar"},null,-1)),C=n(()=>e("h1",null,"Sign Up",-1)),V=n(()=>e("p",null,"Email Address",-1)),M=n(()=>e("p",null,"Password (6+ characters)",-1)),A={id:"imgDiv"};function D(t,s,F,j,a,p){const c=h("Navbar"),m=h("router-link");return i(),r(f,null,[u(c),e("div",E,[e("div",I,[N,C,V,d(e("input",{type:"email","onUpdate:modelValue":s[0]||(s[0]=o=>a.email=o),"aria-describedby":"emailHelp",placeholder:"Enter email"},null,512),[[l,a.email]]),M,e("div",A,[a.showPassword?(i(),r("img",{key:0,id:"eyeImg",src:T,onClick:s[1]||(s[1]=o=>this.showPassword=!this.showPassword),alt:"show"})):(i(),r("img",{key:1,id:"eyeImg",src:B,onClick:s[2]||(s[2]=o=>this.showPassword=!this.showPassword)}))]),a.showPassword?d((i(),r("input",{key:0,type:"text","onUpdate:modelValue":s[3]||(s[3]=o=>a.password=o),placeholder:"Enter password"},null,512)),[[l,a.password]]):d((i(),r("input",{key:1,type:"password","onUpdate:modelValue":s[4]||(s[4]=o=>a.password=o),placeholder:"Enter password"},null,512)),[[l,a.password]]),e("input",{onClick:s[5]||(s[5]=(...o)=>p.signup&&p.signup(...o)),type:"submit",name:"",value:"Sign Up"}),e("a",null,[u(m,{to:"/login"},{default:v(()=>[k("Already have an account?")]),_:1})])])])],64)}const G=g(U,[["render",D],["__scopeId","data-v-0ac92d6f"]]);export{G as default};

View File

@ -1 +0,0 @@
#background[data-v-0ac92d6f]{margin:0;padding:0;width:100%;height:100%;position:absolute;background-color:#e0e0e0;font-family:sans-serif}.loginbox[data-v-0ac92d6f]{height:420px;width:320px;background:#000;color:#fff;top:50%;left:50%;position:absolute;transform:translate(-50%,-50%);box-sizing:border-box;padding:70px 30px}#imgDiv[data-v-0ac92d6f]{height:10%;width:10%;right:40px;bottom:150px;position:absolute}#eyeImg[data-v-0ac92d6f]{height:80%;width:100%}#eyeImg[data-v-0ac92d6f]:hover{transform:scale(1.1)}h1[data-v-0ac92d6f]{margin:0;padding:0 0 20px;font-size:22px;text-align:center}.loginbox p[data-v-0ac92d6f]{margin:0;padding:0;font-weight:700}.loginbox input[data-v-0ac92d6f]{width:100%;margin-bottom:20px}.loginbox input[type=email][data-v-0ac92d6f],input[type=password][data-v-0ac92d6f],input[type=text][data-v-0ac92d6f]{border:none;border-bottom:1px solid #fff;background:transparent;outline:none;height:40px;color:#fff;font-size:16px}.loginbox input[type=submit][data-v-0ac92d6f]:hover{cursor:pointer;background:#66a3ff;color:#000}.loginbox a[data-v-0ac92d6f]{text-decoration:none;font-size:12px;line-height:20px;color:#a9a9a9}.loginbox a[data-v-0ac92d6f]:hover{color:#ffc107}.loginbox input[type=submit][data-v-0ac92d6f]{border:none;outline:none;height:40px;background:#0052cc;font-size:18px;border-radius:20px}.avatar[data-v-0ac92d6f]{width:100px;height:100px;border-radius:50%;position:absolute;top:-50px;left:calc(50% - 50px)}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,106 +0,0 @@
import{x as y,C as I,y as N,z as D,A as g,B as C,D as O,G as _}from"./index-9a3ecfe5.js";/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/const P="type.googleapis.com/google.protobuf.Int64Value",S="type.googleapis.com/google.protobuf.UInt64Value";function w(e,t){const n={};for(const r in e)e.hasOwnProperty(r)&&(n[r]=t(e[r]));return n}function h(e){if(e==null)return null;if(e instanceof Number&&(e=e.valueOf()),typeof e=="number"&&isFinite(e)||e===!0||e===!1||Object.prototype.toString.call(e)==="[object String]")return e;if(e instanceof Date)return e.toISOString();if(Array.isArray(e))return e.map(t=>h(t));if(typeof e=="function"||typeof e=="object")return w(e,t=>h(t));throw new Error("Data cannot be encoded in JSON: "+e)}function d(e){if(e==null)return e;if(e["@type"])switch(e["@type"]){case P:case S:{const t=Number(e.value);if(isNaN(t))throw new Error("Data cannot be decoded from JSON: "+e);return t}default:throw new Error("Data cannot be decoded from JSON: "+e)}return Array.isArray(e)?e.map(t=>d(t)):typeof e=="function"||typeof e=="object"?w(e,t=>d(t)):e}/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/const m="functions";/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/const T={OK:"ok",CANCELLED:"cancelled",UNKNOWN:"unknown",INVALID_ARGUMENT:"invalid-argument",DEADLINE_EXCEEDED:"deadline-exceeded",NOT_FOUND:"not-found",ALREADY_EXISTS:"already-exists",PERMISSION_DENIED:"permission-denied",UNAUTHENTICATED:"unauthenticated",RESOURCE_EXHAUSTED:"resource-exhausted",FAILED_PRECONDITION:"failed-precondition",ABORTED:"aborted",OUT_OF_RANGE:"out-of-range",UNIMPLEMENTED:"unimplemented",INTERNAL:"internal",UNAVAILABLE:"unavailable",DATA_LOSS:"data-loss"};class u extends _{constructor(t,n,r){super(`${m}/${t}`,n||""),this.details=r}}function b(e){if(e>=200&&e<300)return"ok";switch(e){case 0:return"internal";case 400:return"invalid-argument";case 401:return"unauthenticated";case 403:return"permission-denied";case 404:return"not-found";case 409:return"aborted";case 429:return"resource-exhausted";case 499:return"cancelled";case 500:return"internal";case 501:return"unimplemented";case 503:return"unavailable";case 504:return"deadline-exceeded"}return"unknown"}function v(e,t){let n=b(e),r=n,i;try{const o=t&&t.error;if(o){const s=o.status;if(typeof s=="string"){if(!T[s])return new u("internal","internal");n=T[s],r=s}const c=o.message;typeof c=="string"&&(r=c),i=o.details,i!==void 0&&(i=d(i))}}catch{}return n==="ok"?null:new u(n,r,i)}/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/class L{constructor(t,n,r){this.auth=null,this.messaging=null,this.appCheck=null,this.auth=t.getImmediate({optional:!0}),this.messaging=n.getImmediate({optional:!0}),this.auth||t.get().then(i=>this.auth=i,()=>{}),this.messaging||n.get().then(i=>this.messaging=i,()=>{}),this.appCheck||r.get().then(i=>this.appCheck=i,()=>{})}async getAuthToken(){if(this.auth)try{const t=await this.auth.getToken();return t==null?void 0:t.accessToken}catch{return}}async getMessagingToken(){if(!(!this.messaging||!("Notification"in self)||Notification.permission!=="granted"))try{return await this.messaging.getToken()}catch{return}}async getAppCheckToken(){if(this.appCheck){const t=await this.appCheck.getToken();return t.error?null:t.token}return null}async getContext(){const t=await this.getAuthToken(),n=await this.getMessagingToken(),r=await this.getAppCheckToken();return{authToken:t,messagingToken:n,appCheckToken:r}}}/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/const p="us-central1";function R(e){let t=null;return{promise:new Promise((n,r)=>{t=setTimeout(()=>{r(new u("deadline-exceeded","deadline-exceeded"))},e)}),cancel:()=>{t&&clearTimeout(t)}}}class F{constructor(t,n,r,i,o=p,s){this.app=t,this.fetchImpl=s,this.emulatorOrigin=null,this.contextProvider=new L(n,r,i),this.cancelAllRequests=new Promise(c=>{this.deleteService=()=>Promise.resolve(c())});try{const c=new URL(o);this.customDomain=c.origin,this.region=p}catch{this.customDomain=null,this.region=o}}_delete(){return this.deleteService()}_url(t){const n=this.app.options.projectId;return this.emulatorOrigin!==null?`${this.emulatorOrigin}/${n}/${this.region}/${t}`:this.customDomain!==null?`${this.customDomain}/${t}`:`https://${this.region}-${n}.cloudfunctions.net/${t}`}}function U(e,t,n){e.emulatorOrigin=`http://${t}:${n}`}function x(e,t,n){return r=>M(e,t,r,n||{})}async function $(e,t,n,r){n["Content-Type"]="application/json";let i;try{i=await r(e,{method:"POST",body:JSON.stringify(t),headers:n})}catch{return{status:0,json:null}}let o=null;try{o=await i.json()}catch{}return{status:i.status,json:o}}function M(e,t,n,r){const i=e._url(t);return G(e,i,n,r)}async function G(e,t,n,r){n=h(n);const i={data:n},o={},s=await e.contextProvider.getContext();s.authToken&&(o.Authorization="Bearer "+s.authToken),s.messagingToken&&(o["Firebase-Instance-ID-Token"]=s.messagingToken),s.appCheckToken!==null&&(o["X-Firebase-AppCheck"]=s.appCheckToken);const c=r.timeout||7e4,l=R(c),a=await Promise.race([$(t,i,o,e.fetchImpl),l.promise,e.cancelAllRequests]);if(l.cancel(),!a)throw new u("cancelled","Firebase Functions instance was deleted.");const E=v(a.status,a.json);if(E)throw E;if(!a.json)throw new u("internal","Response is not valid JSON object.");let f=a.json.data;if(typeof f>"u"&&(f=a.json.result),typeof f>"u")throw new u("internal","Response is missing data field.");return{data:d(f)}}const A="@firebase/functions",k="0.9.3";/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/const H="auth-internal",J="app-check-internal",V="messaging-internal";function B(e,t){const n=(r,{instanceIdentifier:i})=>{const o=r.getProvider("app").getImmediate(),s=r.getProvider(H),c=r.getProvider(V),l=r.getProvider(J);return new F(o,s,c,l,i,e)};y(new I(m,n,"PUBLIC").setMultipleInstances(!0)),N(A,k,t),N(A,k,"esm2017")}/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/function K(e=O(),t=p){const r=D(g(e),m).getImmediate({identifier:t}),i=C("functions");return i&&X(r,...i),r}function X(e,t,n){U(g(e),t,n)}function q(e,t,n){return x(g(e),t,n)}B(fetch.bind(self));export{X as c,K as g,q as h};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M96 0C43 0 0 43 0 96V352c0 48 35.2 87.7 81.1 94.9l-46 46C28.1 499.9 33.1 512 43 512H82.7c8.5 0 16.6-3.4 22.6-9.4L160 448H288l54.6 54.6c6 6 14.1 9.4 22.6 9.4H405c10 0 15-12.1 7.9-19.1l-46-46c46-7.1 81.1-46.9 81.1-94.9V96c0-53-43-96-96-96H96zM64 96c0-17.7 14.3-32 32-32H352c17.7 0 32 14.3 32 32v96c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V96zM224 288a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/></svg>

Before

Width:  |  Height:  |  Size: 636 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M86.8 48c-12.2 0-23.6 5.5-31.2 15L42.7 79C34.5 89.3 19.4 91 9 82.7S-3 59.4 5.3 49L18 33C34.7 12.2 60 0 86.8 0H361.2c26.7 0 52 12.2 68.7 33l12.8 16c8.3 10.4 6.6 25.5-3.7 33.7s-25.5 6.6-33.7-3.7L392.5 63c-7.6-9.5-19.1-15-31.2-15H248V96h40c53 0 96 43 96 96V352c0 30.6-14.3 57.8-36.6 75.4l65.5 65.5c7.1 7.1 2.1 19.1-7.9 19.1H365.3c-8.5 0-16.6-3.4-22.6-9.4L288 448H160l-54.6 54.6c-6 6-14.1 9.4-22.6 9.4H43c-10 0-15-12.1-7.9-19.1l65.5-65.5C78.3 409.8 64 382.6 64 352V192c0-53 43-96 96-96h40V48H86.8zM160 160c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32H288c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32H160zm32 192a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm96 32a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/></svg>

Before

Width:  |  Height:  |  Size: 934 B

15
dist/index.html vendored
View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/assets/train-solid-e7249eb7.svg">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Irish Rail Tracker</title>
<script type="module" crossorigin src="/assets/index-9a3ecfe5.js"></script>
<link rel="stylesheet" href="/assets/index-a5eb0783.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -20,8 +20,8 @@ exports.getStationData = functions.https.onRequest((request, response) => {
// fetch the "stations" collection
admin.firestore().collection('stations').get().then((snapshot) => {
if (snapshot.empty) {
functions.logger.log("Error fetching station data from Firestore")
response.status(404).send({data: "Error fetching station data from Firestore"})
functions.logger.log("Empty snapshot received when fetching station data from Firestore")
response.status(404).send({data: "Empty snapshot received when fetching station data from Firestore"})
return;
}
// iterate through each of the collection's documents
@ -185,8 +185,8 @@ exports.getLiveTrainData = functions.https.onRequest((request, response) => {
// fetch the "liveTrainData" collection
admin.firestore().collection('liveTrainData').get().then((snapshot) => {
if (snapshot.empty) {
functions.logger.log("Error fetching live train data from Firestore")
response.status(404).send({data: "Successfully fetched live train data from Firestore"});
functions.logger.log("Empty snapshot received when fetching live train data from Firestore")
response.status(404).send({data: "Empty snapshot received when fetching live train data from Firestore"});
return;
}
// iterate through each of the collection's documents
@ -489,4 +489,4 @@ exports.deletePreferences = functions.https.onCall((data, context) => {
functions.logger.log("Successfully deleted user preferences from Firestore")
return "Successfully deleted user preferences from Firestore"
})
})
})

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -3,7 +3,7 @@
<div v-on:click="store.setDisplaySelectedStation(false)" id="xButton">🗙</div>
<h2>Station Code: {{ store.selectedStation["StationCode"][0] }}</h2>
<div id="sidebarHeader">
<img id="headerImage" src="../assets/station.png" alt="Station Icon">
<img id="headerImage" src="../assets/station.png" alt="Station Icon" style="object-fit: contain;">
</div>
<div id="sidebarDiv">
@ -70,6 +70,7 @@ export default {
}
.headerImage{
object-fit: contain;
height: 100%;
width: 100%;
padding: 10px;
@ -90,6 +91,7 @@ export default {
}
#typeDiv, #positionDiv, #descriptionDiv{
object-fit: contain;
background-color: rgb(230, 230, 230);
height: 12%;
position: absolute;
@ -98,6 +100,7 @@ export default {
}
#typeIcon, #positionIcon, #descriptionIcon{
object-fit: contain;
background-color: rgb(214, 214, 214);
width:20%;
height: 100%;
@ -108,6 +111,7 @@ export default {
}
#positionImage, #descriptionImage{
object-fit: contain;
padding-top: 2px;
padding-bottom: 2px;
}
@ -125,19 +129,24 @@ export default {
#latP{left: 3%;}
#typeImage{width: 70%;}
#typeImage{
object-fit: contain;
width: 70%;
}
#positionDiv{top: 16%;}
#descriptionDiv{top: 29.5%;}
@media screen and (max-width: 850px) {
.headerImage{
object-fit: contain;
height: 100%;
width: 100%;
padding: 10px;
}
#typeImage{
object-fit: contain;
width: 50%;
}

View File

@ -142,12 +142,14 @@ export default {
}
.headerImage{
object-fit: contain;
height: 100%;
width: 100%;
padding: 10px;
}
#imageDiv{
object-fit: contain;
background-color: rgb(230, 230, 230);
border-radius: 50%;
order:1;
@ -196,6 +198,7 @@ export default {
}
#typeIcon, #dateIcon, #positionIcon, #directionIcon, #publicMessageIcon, #punctualityIcon{
object-fit: contain;
background-color: rgb(214, 214, 214);
width:20%;
height: 100%;
@ -228,16 +231,19 @@ export default {
/*Different Icons CSS*/
#typeImage{
object-fit: contain;
width: 70%;
}
#dateImage, #positionImage, #directionImage, #punctualityImage, #publicMessageImage{
object-fit: contain;
padding-top: 2px;
padding-bottom: 2px;
}
#publicMessageImage{
object-fit: contain;
max-width: 70%;
max-height: 70%;
margin-left: auto;
@ -262,12 +268,14 @@ export default {
@media screen and (max-width: 850px) {
.headerImage{
object-fit: contain;
height: 100%;
width: 100%;
padding: 10px;
}
#typeImage{
object-fit: contain;
width: 45%;
}

View File

@ -189,7 +189,7 @@ export default {
const getTrainData = httpsCallable(functions, 'getLiveTrainData');
getTrainData().then((response) => {
try {
if (!response.data) throw new Error("Error fetching live train data from the database")
if (!response.data) console.warn("No train data in response!");
var unorderedTrains = [];
var insights = {
"totalNumTrains": 0,
@ -405,4 +405,4 @@ td.l, th.l{
content: "Destination";
}
}
</style>
</style>

View File

@ -415,7 +415,7 @@ export default {
const getTrainData = httpsCallable(functions, 'getLiveTrainData');
getTrainData().then((response) => {
try {
if (!response.data) throw new Error("Error fetching live train data from the database")
if (!response.data) console.warn("No train data in response!");
var unorderedTrains = [];
var currentMessages = [];
var insights = {