Testing my forge
This commit is contained in:
parent
4cc7fba772
commit
c42420de1e
8 changed files with 599 additions and 0 deletions
186
app.py
Normal file
186
app.py
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
from flask import Flask, request, redirect, render_template, send_from_directory, make_response, jsonify
|
||||||
|
from flask_caching import Cache
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import datetime as dt
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.json.sort_keys = False
|
||||||
|
app.config['CACHE_TYPE'] = 'SimpleCache'
|
||||||
|
app.config['CACHE_DEFAULT_TIMEOUT'] = 44444
|
||||||
|
cache = Cache(app)
|
||||||
|
|
||||||
|
def isValidDay(day):
|
||||||
|
t=dt.date.today()
|
||||||
|
return t<=day<=t+dt.timedelta(days=31)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
t=dt.date.today()
|
||||||
|
return render_template("index.html", today=str(t), dayafter=str(t+dt.timedelta(days=2)), later=str(t+dt.timedelta(days=31)))
|
||||||
|
|
||||||
|
@app.route("/today")
|
||||||
|
def index_today():
|
||||||
|
t=dt.date.today()
|
||||||
|
return index_day(t.year,t.month,t.day)
|
||||||
|
|
||||||
|
@app.route("/tomorrow")
|
||||||
|
def index_tomorrow():
|
||||||
|
t=dt.date.today()+dt.timedelta(days=1)
|
||||||
|
return index_day(t.year,t.month,t.day)
|
||||||
|
|
||||||
|
@app.route('/<int:y>-<int:m>-<int:d>')
|
||||||
|
def index_day(y,m,d):
|
||||||
|
day=dt.date(y,m,d)
|
||||||
|
if not (isValidDay(day)):
|
||||||
|
q=request.query_string.decode('utf-8')
|
||||||
|
if (q): q="?"+q
|
||||||
|
return redirect("today"+q)
|
||||||
|
return render_template("list.html", day=str(day))
|
||||||
|
|
||||||
|
@app.route('/<int:space>')
|
||||||
|
@cache.cached(timeout=86400)
|
||||||
|
def index_space(space):
|
||||||
|
return render_template("space.html", data=s(space))
|
||||||
|
|
||||||
|
@app.route('/event/<int:event>')
|
||||||
|
@cache.cached(timeout=600)
|
||||||
|
def index_event(event):
|
||||||
|
return render_template("event.html", data=e(event))
|
||||||
|
|
||||||
|
@app.route("/calendar.svg")
|
||||||
|
def static_calendar():
|
||||||
|
return send_from_directory(app.static_folder, "calendar.svg")
|
||||||
|
|
||||||
|
@app.route("/clock.svg")
|
||||||
|
def static_clock():
|
||||||
|
return send_from_directory(app.static_folder, "clock.svg")
|
||||||
|
|
||||||
|
@app.route("/location.svg")
|
||||||
|
def static_location():
|
||||||
|
return send_from_directory(app.static_folder, "location.svg")
|
||||||
|
|
||||||
|
@app.route('/<int:space>.json')
|
||||||
|
@cache.cached(timeout=86400)
|
||||||
|
def s(space):
|
||||||
|
return requests.post("https://25live.collegenet.com/25live/data/louisville/run/spaces.json?request_method=get",headers={"Content-Type":"application/json","Accept-Encoding":"gzip"},json={"mapxml":{"scope":"extended","space_id":space}}).json().get("spaces",[]).get("space",[])
|
||||||
|
|
||||||
|
@app.route('/event/<int:event>.json')
|
||||||
|
@cache.cached(timeout=600)
|
||||||
|
def e(event):
|
||||||
|
return requests.post("https://25live.collegenet.com/25live/data/louisville/run/events.json?request_method=get",headers={"Content-Type":"application/json","Accept-Encoding":"gzip"},json={"mapxml":{"scope":"extended","event_id":event}}).json().get("events",[]).get("event",[])
|
||||||
|
|
||||||
|
@app.route('/<int:y>-<int:m>-<int:d>.json')
|
||||||
|
@cache.cached(timeout=300)
|
||||||
|
def all(y,m,d):
|
||||||
|
day=dt.date(y,m,d)
|
||||||
|
if (not isValidDay(day)):
|
||||||
|
return "invalid"
|
||||||
|
weekday=str(day.weekday())
|
||||||
|
sday=str(day)
|
||||||
|
spaces=dict()
|
||||||
|
for restaurant, schedules in dining_data().items():
|
||||||
|
schedule = []
|
||||||
|
current_range = None
|
||||||
|
for period in schedules:
|
||||||
|
if period != "Standard":
|
||||||
|
if "-" in period:
|
||||||
|
s,e = [dt.date(day.year,*map(int, p.split("/"))) for p in period.split("-")]
|
||||||
|
if s <= day <= e:
|
||||||
|
current_range = period
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
d = dt.date(day.year,*map(int, period.split("/")))
|
||||||
|
if d == day:
|
||||||
|
current_range = period
|
||||||
|
break
|
||||||
|
else: current_range = "Standard"
|
||||||
|
for weekdays, meals in schedules[current_range].items():
|
||||||
|
if weekday in weekdays.split(","):
|
||||||
|
for meal, times in meals.items():
|
||||||
|
schedule.append({
|
||||||
|
"s":int(times.split("-")[0]),
|
||||||
|
"e":int(times.split("-")[1]),
|
||||||
|
"n":meal
|
||||||
|
})
|
||||||
|
spaces[restaurant] = {
|
||||||
|
"l":sorted(schedule, key=lambda x:x["s"])
|
||||||
|
}
|
||||||
|
data=requests.get(f"https://25live.collegenet.com/25live/data/louisville/run/home/calendar/calendardata.json?obj_cache_accl=0&page=1&compsubject=event&events_query_id=939025&start_dt={sday}&end_dt={sday}").json()
|
||||||
|
food=set()
|
||||||
|
for event in data["root"]["events"][0].get("rsrv", []):
|
||||||
|
food.add(event["event_id"])
|
||||||
|
data=requests.get(f"https://25live.collegenet.com/25live/data/louisville/run/availability/availabilitydata.json?obj_cache_accl=0&comptype=availability&compsubject=location&page_size=400&spaces_query_id=667440&include=closed+blackouts+pending+related+empty+requests+draft&start_dt={sday}").json()
|
||||||
|
for space in data.get('subjects', []):
|
||||||
|
spaces[space['itemName']] = {
|
||||||
|
"i":space['itemId'],
|
||||||
|
"l":sorted([{
|
||||||
|
"s":round(float(item['start'])*60)-480,
|
||||||
|
"e":round(float(item['end'])*60)-480,
|
||||||
|
"n":item['itemName'],
|
||||||
|
**({"i":item['itemId']} if item['itemId'] != 0 else {}),
|
||||||
|
**({"t":"food"} if item['itemId'] in food
|
||||||
|
else {"t":"exam"} if item.get('event_type_id') == 139
|
||||||
|
else {"t":"booked"} if item.get('event_type_id') == 143
|
||||||
|
else {"t":"class"} if item.get('event_type_id') == 172
|
||||||
|
else {})
|
||||||
|
} for item in space.get('items', [])], key=lambda x:x["s"])
|
||||||
|
}
|
||||||
|
return spaces
|
||||||
|
|
||||||
|
# [{ periodId : '2230', name : 'Breakfast' }, { periodId : '3180', name : 'Light Lunch' }, { periodId : '2232', name : 'Lunch' }, { periodId : '2233', name : 'Dinner' }]
|
||||||
|
@app.route('/dining.json')
|
||||||
|
@cache.cached(timeout=44444)
|
||||||
|
def dining_data():
|
||||||
|
data = cache.get("dining")
|
||||||
|
if data: return data
|
||||||
|
|
||||||
|
base = "https://louisville.campusdish.com/LocationsAndMenus/"
|
||||||
|
locations = dict()
|
||||||
|
for location in re.findall(r':"/LocationsAndMenus/([^",]+)', requests.get(base).text):
|
||||||
|
locations[location] = dict()
|
||||||
|
soup = BeautifulSoup(requests.get(base+location).text, 'html.parser')
|
||||||
|
|
||||||
|
schedule_dates = []
|
||||||
|
dates = soup.select_one(".location__scheduledates")
|
||||||
|
if dates: schedule_dates.append(dates.text.strip().replace(" ",""))
|
||||||
|
else: schedule_dates.append("Standard")
|
||||||
|
for timePeriod in soup.select("a.additionalschedule"):
|
||||||
|
match = re.search(r"(\d+/\d+-\d+/\d+)", timePeriod.text)
|
||||||
|
if not match: match = re.search(r"(\d+/\d+)", timePeriod.text)
|
||||||
|
if match: schedule_dates.append(match.group(1))
|
||||||
|
else: schedule_dates.append("Standard")
|
||||||
|
|
||||||
|
timeframes = [ul for ul in soup.find_all("div",class_="location__hours") if not ul.find("a.additionalschedule")]
|
||||||
|
for timeframe, date_range in zip(timeframes, schedule_dates):
|
||||||
|
meal_by_day = dict()
|
||||||
|
for meal_period in timeframe.find_all("li"):
|
||||||
|
period = meal_period.find("div", class_="mealPeriod")
|
||||||
|
if period:
|
||||||
|
period_range = period.text.strip()
|
||||||
|
for time_entry in meal_period.find_all("li"):
|
||||||
|
day = time_entry.find("span", class_="location__day")
|
||||||
|
times = time_entry.find("span", class_="location__times")
|
||||||
|
if day and times:
|
||||||
|
day_range = day.text.strip().replace("Mon","0").replace("Tue","1").replace("Wed","2").replace("Thu","3").replace("Fri","4").replace("Sat","5").replace("Sun","6")
|
||||||
|
d = []
|
||||||
|
for wd in day_range.split(", "):
|
||||||
|
if "-" in wd:
|
||||||
|
s,e = map(int, wd.split("-"))
|
||||||
|
d.extend(map(str, (range(s,e+1))))
|
||||||
|
else:
|
||||||
|
d.append(wd)
|
||||||
|
day_range = ",".join(d)
|
||||||
|
time_range = times.text.strip()
|
||||||
|
if "-" in time_range:
|
||||||
|
s,e = [int((dt.datetime.strptime(t, "%I:%M%p")-dt.datetime.strptime("8:00AM", "%I:%M%p")).total_seconds()/60) for t in time_range.split(" - ")]
|
||||||
|
if s < 0: s = 0
|
||||||
|
if e < 0 or e > 900: e = 900
|
||||||
|
time_range = str(s)+"-"+str(e)
|
||||||
|
else: continue
|
||||||
|
if day_range not in meal_by_day: meal_by_day[day_range]=dict()
|
||||||
|
meal_by_day[day_range][period_range] = time_range
|
||||||
|
locations[location][date_range] = meal_by_day
|
||||||
|
cache.set("dining", locations)
|
||||||
|
return locations
|
1
static/calendar.svg
Normal file
1
static/calendar.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="#63E6BE" d="M96 32l0 32L48 64C21.5 64 0 85.5 0 112l0 48 448 0 0-48c0-26.5-21.5-48-48-48l-48 0 0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 32L160 64l0-32c0-17.7-14.3-32-32-32S96 14.3 96 32zM448 192L0 192 0 464c0 26.5 21.5 48 48 48l352 0c26.5 0 48-21.5 48-48l0-272z"/></svg>
|
After Width: | Height: | Size: 346 B |
1
static/clock.svg
Normal file
1
static/clock.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#63E6BE" d="M256 0a256 256 0 1 1 0 512A256 256 0 1 1 256 0zM232 120V256c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2V120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"/></svg>
|
After Width: | Height: | Size: 274 B |
1
static/location.svg
Normal file
1
static/location.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#63E6BE" d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg>
|
After Width: | Height: | Size: 264 B |
109
templates/event.html
Normal file
109
templates/event.html
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{{ data.event_name }}</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta content="44live" property="og:site_name">
|
||||||
|
<meta content="{{ data.event_name }}{{ " ("+data.event_title+")" if data.event_title }}" property="og:title">
|
||||||
|
{%- if data.event_text %}
|
||||||
|
{%- if data.event_text.append is defined %}
|
||||||
|
{%- for i in data.event_text %}
|
||||||
|
{%- if i.text_type_id == 1 %}
|
||||||
|
<meta content="{{ i.text }}" property="og:description">
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- elif data.event_text.text_type_id == 1 %}
|
||||||
|
<meta content="{{ data.event_text.text }}" property="og:description">
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
<meta content='https://44live.stevenalexander.org/clock.svg' property='og:image'>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/clock.svg">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
background-color: lightblue;
|
||||||
|
padding: 5px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href="https://25live.collegenet.com/pro/louisville#!/home/event/{{ data.event_id }}/details">{{ data.event_name }}</a>{{ " ("+data.event_title+")" if data.event_title }}</h1>
|
||||||
|
<h2>
|
||||||
|
{%- if "Rsrv" in data.profile.profile_name %}
|
||||||
|
{%- if data.profile.reservation.append is defined %}
|
||||||
|
Start: {{ data.profile.reservation[0].event_start_dt }}<br>
|
||||||
|
Until: {{ data.profile.reservation[-1].event_end_dt }}
|
||||||
|
{%- else %}
|
||||||
|
Start: {{ data.profile.reservation.event_start_dt }}<br>
|
||||||
|
Until: {{ data.profile.reservation.event_end_dt }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- else %}
|
||||||
|
{{ data.profile.profile_name }}
|
||||||
|
{%- endif %}
|
||||||
|
</h2>
|
||||||
|
{%- if data.profile.append is not defined %}
|
||||||
|
<h2>
|
||||||
|
{%- if data.profile.reservation.append is defined %}
|
||||||
|
{%- if data.profile.reservation[0].space_reservation.append is defined %}
|
||||||
|
{%- for space in data.profile.reservation[0].space_reservation %}
|
||||||
|
<a href="/{{ space.space.space_id }}">{{ space.space.formal_name }}</a><br>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- else %}
|
||||||
|
<a href="/{{ data.profile.reservation[0].space_reservation.space_id }}">{{ data.profile.reservation[0].space_reservation.space.formal_name }}</a>
|
||||||
|
{%- endif %}
|
||||||
|
{%- else %}
|
||||||
|
{%- if data.profile.reservation.space_reservation.append is defined %}
|
||||||
|
{%- for space in data.profile.reservation.space_reservation %}
|
||||||
|
<a href="/{{ space.space.space_id }}">{{ space.space.formal_name }}</a><br>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- else %}
|
||||||
|
<a href="/{{ data.profile.reservation.space_reservation.space_id }}">{{ data.profile.reservation.space_reservation.space.formal_name }}</a>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
</h2>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if data.event_text %}
|
||||||
|
<h2>Description</h2>
|
||||||
|
{%- if data.event_text.append is defined %}
|
||||||
|
{%- for i in data.event_text %}
|
||||||
|
{%- if i.text_type_id == 1 %}
|
||||||
|
<h3>{{ i.text | safe }}</h3>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- elif data.event_text.text_type_id == 1 %}
|
||||||
|
<h3>{{ data.event_text.text | safe }}</h3>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
<h2>Contact(s)</h2>
|
||||||
|
{%- for i in data.role %}
|
||||||
|
<h3>{{ i.role_name }}: {{ i.contact.contact_first_name }} {{ i.contact.contact_middle_name }} {{ i.contact.contact_last_name }}</h3>
|
||||||
|
{%- if i.contact.email %}
|
||||||
|
<p>Email: {{ i.contact.email }}</p>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if i.contact.phone %}
|
||||||
|
<p>Phone: {{ i.contact.phone }}</p>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if i.contact.formatted_address %}
|
||||||
|
<p>Address: {{ i.contact.formatted_address }}</p>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- if data.custom_attribute %}
|
||||||
|
<h2>Custom Attribute(s)</h2>
|
||||||
|
{%- if data.custom_attribute.append is defined %}
|
||||||
|
{%- for i in data.custom_attribute %}
|
||||||
|
<h3>{{ i.attribute_name }}</h3>
|
||||||
|
<p>{{ i.attribute_value }}</p>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- else %}
|
||||||
|
<h3>{{ data.custom_attribute.attribute_name }}</h3>
|
||||||
|
<p>{{ data.custom_attribute.attribute_value }}</p>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
<footer>Last modified at {{ data.last_mod_dt }} by {{ data.last_mod_user }}</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
48
templates/index.html
Normal file
48
templates/index.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>UofL Events</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta content="44live" property="og:site_name">
|
||||||
|
<meta content="44live - find rooms at UofL" property="og:title">
|
||||||
|
<meta content="Work in progress. Don't expect much." property="og:description">
|
||||||
|
<meta content='https://44live.stevenalexander.org/calendar.svg' property='og:image'>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/calendar.svg">
|
||||||
|
<style>
|
||||||
|
*{font-family:monospace;font-size:0.8cm;}
|
||||||
|
input[type="checkbox"],input[type="radio"]{width:0.7cm;height:0.7cm;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Choose a day:</h1>
|
||||||
|
<input id="today" type="radio" name="date" checked>
|
||||||
|
<label for="today">today</label><br>
|
||||||
|
<input id="tomorrow" type="radio" name="date">
|
||||||
|
<label for="tomorrow">tomorrow</label><br>
|
||||||
|
<input id="custom" type="radio" name="date">
|
||||||
|
<label for="custom"><input id="date" type="date" min="{{today}}" max="{{later}}" value="{{dayafter}}" onchange="noClear(event)"></label>
|
||||||
|
<h1>[Optional] Select types of events to find:</h1>
|
||||||
|
{%- for i in ["available","class","exam","booked","food"] %}
|
||||||
|
<input id="{{i}}" type="checkbox">
|
||||||
|
<label for="{{i}}">{{i}}</label><br>
|
||||||
|
{%- endfor %}
|
||||||
|
<p><button onclick="changeURL()">Show me the events!</button></p>
|
||||||
|
<script>
|
||||||
|
function noClear(e) {
|
||||||
|
val=e.target.value
|
||||||
|
if(val===""||val<"{{today}}"||val>"{{later}}"){e.target.value="{{dayafter}}"}
|
||||||
|
document.getElementById("custom").click()
|
||||||
|
}
|
||||||
|
function changeURL(){
|
||||||
|
has=[]
|
||||||
|
document.querySelectorAll('input[type="checkbox"]:checked').forEach(i=>has.push(i.id))
|
||||||
|
if(has.length>0){has="?has="+has.join(",")
|
||||||
|
}else{has=""}
|
||||||
|
date=document.querySelector('input[type="radio"]:checked').id
|
||||||
|
if(date=="custom"){date=document.querySelector('input[type="date"]').value}
|
||||||
|
document.location.href=date+has
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
192
templates/list.html
Normal file
192
templates/list.html
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>44live</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta content="44live" property="og:site_name">
|
||||||
|
<meta content="44live - find rooms at UofL" property="og:title">
|
||||||
|
<meta content="Work in progress. Don't expect much." property="og:description">
|
||||||
|
<meta content='https://44live.stevenalexander.org/calendar.svg' property='og:image'>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/calendar.svg">
|
||||||
|
<base target="_blank">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
font: 12px monospace;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
background-color: black;
|
||||||
|
min-width: 100%;
|
||||||
|
width: 1600px;
|
||||||
|
max-width: 400%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
table-layout: fixed;
|
||||||
|
overflow-x: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
background-color: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
border-right: 2px solid black;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td:first-child {
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child, td:first-child {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
top: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, span {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.closed > a {color:white;}
|
||||||
|
a:hover{background-color:lightgray;color:black;}
|
||||||
|
.closed{background-color:black;color:white;}
|
||||||
|
.available{background-color:lightgreen;}
|
||||||
|
.other{background-color:white;}
|
||||||
|
.class{background-color:darkgray;}
|
||||||
|
.exam{background-color:gray;}
|
||||||
|
.booked{background-color:coral;}
|
||||||
|
.food{background-color:mediumpurple;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
<th colspan=60><a href="https://25live.collegenet.com/pro/louisville#!/home/event/form">Updating...</a></th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
<script>
|
||||||
|
const headRow = document.querySelector('tr');
|
||||||
|
const bookBtn = headRow.querySelector('a');
|
||||||
|
const tbody = document.querySelector('tbody');
|
||||||
|
|
||||||
|
for (let hour = 8; hour < 23; hour++) {
|
||||||
|
const th = document.createElement('th');
|
||||||
|
th.setAttribute("colspan", 60);
|
||||||
|
th.textContent = hour;
|
||||||
|
headRow.appendChild(th)
|
||||||
|
}
|
||||||
|
|
||||||
|
let json=localStorage.getItem("{{ day }}");
|
||||||
|
if (json) displayEvents(JSON.parse(json))
|
||||||
|
|
||||||
|
fetch("{{ day }}.json").then(r => {
|
||||||
|
bookBtn.innerText = "Book a Space";
|
||||||
|
return r.json()
|
||||||
|
}).then(json => {
|
||||||
|
tbody.innerHTML="";
|
||||||
|
displayEvents(json);
|
||||||
|
try {
|
||||||
|
localStorage.setItem("{{ day }}",JSON.stringify(json))
|
||||||
|
} catch {
|
||||||
|
localStorage.clear();
|
||||||
|
localStorage.setItem("{{ day }}",JSON.stringify(json))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function createBox(row, length, type, name, link) {
|
||||||
|
const td = document.createElement("td");
|
||||||
|
if (link) {
|
||||||
|
var e = document.createElement("a");
|
||||||
|
e.href = link
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
if (!e) var e = document.createElement("span")
|
||||||
|
e.textContent = name;
|
||||||
|
e.title = name;
|
||||||
|
td.appendChild(e)
|
||||||
|
}
|
||||||
|
if (type) {
|
||||||
|
if (type != "closed" || length == 900) {row.firstChild.classList.add(type)}
|
||||||
|
td.classList.add(type);
|
||||||
|
}
|
||||||
|
td.setAttribute("colspan", length);
|
||||||
|
row.appendChild(td);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayEvents(json) {
|
||||||
|
if (window.location.search) var has=new Set(new URLSearchParams(window.location.search).get("has").split(","))
|
||||||
|
for (space in json) {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
let link = json[space].i;
|
||||||
|
let defaultEmpty = "available";
|
||||||
|
let defaultType = null;
|
||||||
|
if (!json[space].i) {
|
||||||
|
link = "https://louisville.campusdish.com/LocationsAndMenus/"+space;
|
||||||
|
defaultEmpty = "closed";
|
||||||
|
defaultType = "food"
|
||||||
|
}
|
||||||
|
createBox(row, 60, null, space, link);
|
||||||
|
let now = 0;
|
||||||
|
if (json[space].l) {
|
||||||
|
for (e of json[space].l) {
|
||||||
|
start=e.s
|
||||||
|
end=e.e
|
||||||
|
if (now < start) {
|
||||||
|
createBox(row, start-now, defaultEmpty);
|
||||||
|
now = start
|
||||||
|
}
|
||||||
|
if (now <= start && now < end) {
|
||||||
|
type = defaultType ? defaultType : (e.i ? (e.t ? e.t : "other") : "closed");
|
||||||
|
link = e.i ? "event/" + e.i : null;
|
||||||
|
createBox(row, end-start, type, e.n, link)
|
||||||
|
now = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (now < 900) {
|
||||||
|
createBox(row, 900-now, defaultEmpty)
|
||||||
|
}
|
||||||
|
if (typeof has === "undefined") {
|
||||||
|
tbody.appendChild(row)
|
||||||
|
} else {
|
||||||
|
for (let i of row.firstChild.className.split(" ")) {
|
||||||
|
if (has.has(i)) {
|
||||||
|
tbody.appendChild(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.querySelector("script").remove();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
61
templates/space.html
Normal file
61
templates/space.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{{ data.space_name }}</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta content="44live" property="og:site_name">
|
||||||
|
<meta content="{{ data.space_name }}{{ " ("+data.building_name+")" if data.building_name }}" property="og:title">
|
||||||
|
<meta content='https://44live.stevenalexander.org/location.svg' property='og:image'>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/location.svg">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width:100%;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
background-color: lightblue;
|
||||||
|
padding: 5px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><a href="https://25live.collegenet.com/pro/louisville#!/home/location/{{ data.space_id }}/availability/daily">{{ data.space_name }}</a>{{ " ("+data.building_name+")" if data.building_name }}</h1>
|
||||||
|
<h2>Hours</h2>
|
||||||
|
<h3>
|
||||||
|
{%- for i in data.hours %}
|
||||||
|
{{ i.day_name[:3] }}: {{ i.open }} - {{ i.close }}<br>
|
||||||
|
{%- endfor %}
|
||||||
|
</h3>
|
||||||
|
{% if data.instructions %}
|
||||||
|
<h2>Instructions</h2>
|
||||||
|
<h3>{{ data.instructions|safe }}</h3>
|
||||||
|
{% endif %}
|
||||||
|
{%- if data.comments %}
|
||||||
|
<h2>Comments</h2>
|
||||||
|
<h3>{{ data.comments|safe }}</h3>
|
||||||
|
{%- endif %}
|
||||||
|
<h2>Layout(s)</h2>
|
||||||
|
{%- if data.layout %}
|
||||||
|
{%- if data.layout.append is defined %}
|
||||||
|
{%- for i in data.layout %}
|
||||||
|
{%- if i.layout_photo_id %}
|
||||||
|
<h3>{{ i.layout_name }} ({{ i.layout_capacity }} capacity)</h3>
|
||||||
|
<img src="https://25live.collegenet.com/25live/data/louisville/run/image?image_id={{ i.layout_photo_id }}">
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- elif data.layout.layout_photo_id %}
|
||||||
|
<h3>{{ data.layout.layout_name }} ({{ data.layout.layout_capacity }} capacity)</h3>
|
||||||
|
<img src="https://25live.collegenet.com/25live/data/louisville/run/image?image_id={{ data.layout.layout_photo_id }}">
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
<footer>Last modified at {{ data.last_mod_dt }} by {{ data.last_mod_user }}</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue