From caf546ae6ccc7157030e7a9928b819e2044f9d85 Mon Sep 17 00:00:00 2001 From: Filip Znachor Date: Wed, 14 Dec 2022 19:28:38 +0100 Subject: [PATCH] Improved web GUI & added stop selector --- server/api.py | 6 +- server/departures.py | 9 +-- server/lora.py | 4 +- server/main.py | 6 +- server/static/departure-list.js | 57 ++++++++++++++++++ server/static/index.html | 100 +++++--------------------------- server/static/style.css | 31 ++++++++++ 7 files changed, 116 insertions(+), 97 deletions(-) create mode 100644 server/static/departure-list.js create mode 100644 server/static/style.css diff --git a/server/api.py b/server/api.py index f419613..ab7651a 100644 --- a/server/api.py +++ b/server/api.py @@ -22,7 +22,7 @@ class API: def __init__(self, main): @route('/departures/') - def departures(stop_id: int): + def departures(stop_id: str): resp = [] for d in Departure.get(stop_id): resp.append(d.json()) @@ -33,6 +33,10 @@ class API: def stop(): return {'stops': main.config["stops"]} + @get("/static/") + def index(file: str): + return static_file(file, root="static") + @get("/") def static(): return static_file("index.html", root="static") diff --git a/server/departures.py b/server/departures.py index 7b492e9..384305e 100644 --- a/server/departures.py +++ b/server/departures.py @@ -12,7 +12,7 @@ class Departure: resp = [] for id in Departure.storage: d = Departure.storage[id] - if d.stop_id == int(stop_id): + if d.stop_id == stop_id: resp.append(d) return resp @@ -42,8 +42,6 @@ class Departure: departure = (parser.parse(departure)).timestamp() if -(datetime.now().timestamp() - (departure + delay*60))/60 <= -1: return - if len(last_stop) >= 21: - last_stop = last_stop[:20].strip() + "..." self.did = did self.stop_id = stop_id self.id = Departure.get_id(stop_id) @@ -72,7 +70,10 @@ class Departure: return None def __repr__(self): - return f"{self.id}|{self.type}|{self.line}|{self.last_stop}|{(self.get_departure()*10):.0f}" + last_stop = self.last_stop + if len(last_stop) >= 21: + last_stop = last_stop[:20].strip() + "..." + return f"{self.id}|{self.type}|{self.line}|{last_stop}|{(self.get_departure()*10):.0f}" def json(self): return { diff --git a/server/lora.py b/server/lora.py index 0cb5d0d..93ba697 100644 --- a/server/lora.py +++ b/server/lora.py @@ -35,12 +35,12 @@ class LoraController: data = requests.post(url, verify=False, headers=headers, data=json.dumps(data)).json() self.token = data["token"] - def new(self, id: int, stop_id: int): + def new(self, id: int, stop_id: str): self.devices.append(LoraDevice(self, id, stop_id)) class LoraDevice: - def __init__(self, controller: LoraController, deveui: int, stop_id: int): + def __init__(self, controller: LoraController, deveui: int, stop_id: str): self.controller = controller self.id = deveui self.stop_id = stop_id diff --git a/server/main.py b/server/main.py index 95893ff..12e77bf 100644 --- a/server/main.py +++ b/server/main.py @@ -23,13 +23,13 @@ class Main: lora_controller = LoraController(self) lora_controller.generate_token() for d in self.config["devices"]: - lora_controller.new(d["id"], d["stop_id"]) + lora_controller.new(d["id"], str(d["stop_id"])) self.controller = lora_controller self.thread = threading.Thread(target=self.update_loop) self.thread.start() self.api = API(self) - def fetch(self, stop_id, limit): + def fetch(self, stop_id: str, limit: int): url = "https://jizdnirady.pmdp.cz/odjezdy/vyhledat" @@ -77,7 +77,7 @@ class Main: if refetch == 0: for s in self.config["stops"]: - self.fetch(s["id"], 15) + self.fetch(f"{s}", 15) for d in self.controller.devices: d.get_updated_departures() diff --git a/server/static/departure-list.js b/server/static/departure-list.js new file mode 100644 index 0000000..b3d5add --- /dev/null +++ b/server/static/departure-list.js @@ -0,0 +1,57 @@ + +const { createApp } = Vue; +let app = createApp({ + data() { + return { + top: 0, + interval: null, + stop_id: null, + stops: [], + departures: [] + } + }, + methods: { + async set() { + localStorage.setItem("favstop", app.$data.stop_id); + this.update(); + }, + async update() { + if(app.$data.stop_id) app.$data.departures = (await api("/departures/"+app.$data.stop_id)).departures; + }, + async setup() { + app.$data.stops = (await api("/stops")).stops; + let ids = Object.keys(app.$data.stops); + if(ids.length < 1) { + alert("Žádné zastávky nejsou k dispozici!"); + if(app.$data.interval) clearTimeout(app.$data.interval); + return; + } + if(localStorage.getItem("favstop")) { + if(ids.indexOf(localStorage.getItem("favstop")) != -1) app.$data.stop_id = localStorage.getItem("favstop"); + } + if(!app.$data.stop_id) app.$data.stop_id = ids[0]; + window.addEventListener("scroll", () => { + app.$data.top = document.querySelector('html').scrollTop; + }); + app.$data.interval = setInterval(app.update, 5000); + app.update(); + } + } +}).mount('#app'); +async function api(url) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if(this.readyState !== 4) return; + if(!this.responseText) reject(this); + try { + resolve(JSON.parse(this.responseText)); + } catch(e) { + resolve(null); + } + }; + xhr.open("GET", url, true); + xhr.send(); + }); +} +app.setup(); \ No newline at end of file diff --git a/server/static/index.html b/server/static/index.html index 8f30e29..92cb1f3 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -2,43 +2,23 @@ Odjezdová tabule - + +
- + \ No newline at end of file diff --git a/server/static/style.css b/server/static/style.css new file mode 100644 index 0000000..27e09f8 --- /dev/null +++ b/server/static/style.css @@ -0,0 +1,31 @@ +:root { + --site-bg: #1C1C1C; + --site-alt-bg: #272727; +} + +* {box-sizing: border-box;} + +body {background-color: var(--site-bg); color: #fffd; margin: 0; margin-top: 35px; font-family: sans-serif;} + +.container {max-width: 700px; padding: 25px; margin: auto;} +.departure-grid {display: grid; grid-template-columns: 30px 1fr max-content max-content; gap: 10px 20px; line-height: 31px;} + +.header {line-height: 100%; opacity: .5; margin-bottom: 10px; font-size: 90%;} + +.line {background-color: var(--site-alt-bg); border-radius: 3px; box-shadow: 0 0 5px 0 #0004; text-align: center; font-weight: 700; width: 30px; height: 30px; color: #fff; text-shadow: 0 0 15px #000;} +.last-stop {font-weight: 700;} +.departure {text-align: right;} +.delay {text-align: right; color: #ff6e6e; font-size: 80%;} + +.line.type1 {background-color: #F0BE32;} +.line.type2 {background-color: #1E9641;} +.line.type3 {background-color: #CD2837;} + +h3 {color: #fff; display: flex; align-items: center; gap: 5px;} +.icon {fill: #fff5; width: 24px; height: 24px;} + +.nav {position: fixed; top: 0; left: 0; width: 100%; z-index: 10;} +.nav .inner {background: linear-gradient(to bottom, var(--site-bg) 70%, transparent 100%); padding: 5px 25px; margin: auto; max-width: 700px; padding-bottom: 25px; transition: padding .1s; position: relative;} +.nav.top .inner {padding-bottom: 0px;} +.nav .inner::after {content: ""; position: fixed; bottom: 0; left: 0; height: 20px; width: 100%; background: linear-gradient(to top, var(--site-bg) 0%, transparent 100%)} +.nav .inner select {opacity: 0; float: left; position: relative; top: -60px; height: 50px; width: 100%; cursor: pointer;}