Optimized fetching from aule.usi.ch

And btw Joey follow the modeline for indentation or I'll start using 3
spaces instead of 2
This commit is contained in:
Claudio Maggioni 2019-11-04 12:15:04 +01:00
parent 724015cf62
commit 86f43a4b43
4 changed files with 326 additions and 326 deletions

201
app.js
View file

@ -8,49 +8,49 @@ const NOW = new Date();
const timeTable = {}; const timeTable = {};
function roomStatus(room, callback) { function roomStatus(room, callback) {
return new Promise((resolve, _) => { return new Promise((resolve, _) => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.addEventListener('load', () => { xhr.addEventListener('load', () => {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(xhr.responseText, 'text/html'); const doc = parser.parseFromString(xhr.responseText, 'text/html');
const lessons = doc.querySelectorAll( const lessons = doc.querySelectorAll(
'table.rsContentTable div.rsAptSimple'); 'table.rsContentTable div.rsAptSimple');
const parsed = []; const parsed = [];
for (let lesson of lessons) { for (let lesson of lessons) {
const time = lesson.querySelector('span[id$=lblOrario]'); const time = lesson.querySelector('span[id$=lblOrario]');
const start = new Date(); const start = new Date();
start.setHours(parseInt(time.innerText.substring(1,3))); start.setHours(parseInt(time.innerText.substring(1,3)));
start.setMinutes(parseInt(time.innerText.substring(4,6))); start.setMinutes(parseInt(time.innerText.substring(4,6)));
start.setSeconds(0); start.setSeconds(0);
const end = new Date(); const end = new Date();
end.setHours(parseInt(time.innerText.substring(7,9))); end.setHours(parseInt(time.innerText.substring(7,9)));
end.setMinutes(parseInt(time.innerText.substring(10,12))); end.setMinutes(parseInt(time.innerText.substring(10,12)));
end.setSeconds(0); end.setSeconds(0);
parsed.push({ parsed.push({
title: lesson.getAttribute('title'), title: lesson.getAttribute('title'),
start: start, start: start,
end: end end: end
});
}
resolve(parsed);
}); });
xhr.open('GET', URL + room); }
xhr.send();
resolve(parsed);
}); });
xhr.open('GET', URL + room);
xhr.send();
});
} }
const ROOMS = [ const ROOMS = [
'SI-003', 'SI-003',
'SI-015', 'SI-015',
'SI-004', 'SI-004',
'SI-006', 'SI-006',
'SI-013', 'SI-013',
'SI-007', 'SI-007',
'SI-008', 'SI-008',
]; ];
const ROOM_LIST = document.querySelector(".times"); const ROOM_LIST = document.querySelector(".times");
@ -59,100 +59,97 @@ const SLOT_TEMPLATE = document.getElementById("time-slot");
const FREE_SLOT_TEMPLATE = document.getElementById("time-free"); const FREE_SLOT_TEMPLATE = document.getElementById("time-free");
function getRoomNode() { function getRoomNode() {
return document.importNode(ROOM_TEMPLATE.content, true); return document.importNode(ROOM_TEMPLATE.content, true);
} }
function formatTime(date) { function formatTime(date) {
const twoDigits = (n) => { const twoDigits = (n) => {
return n < 10 ? "0" + n : n; return n < 10 ? "0" + n : n;
} }
return twoDigits(date.getHours()) + ':' + return twoDigits(date.getHours()) + ':' +
twoDigits(date.getMinutes()); twoDigits(date.getMinutes());
} }
function colorRoom(roomTitle, node, time = NOW /* QuantumLeap */) { function colorRoom(roomTitle, node, time = NOW /* QuantumLeap */) {
const data = timeTable[roomTitle]; const data = timeTable[roomTitle];
if (data == undefined) { if (data == undefined) {
return return
} }
const currentLecture = data.filter(d => d.start < time && d.end > time)[0]; const currentLecture = data.filter(d => d.start < time && d.end > time)[0];
const isFree = currentLecture === void(0); const isFree = currentLecture === void(0);
const block = document.getElementById(roomTitle); const block = document.getElementById(roomTitle);
block.className = block.className.replace(" room-in-use", "") block.className = block.className.replace(" room-in-use", "")
.replace(" room-free", ""); .replace(" room-free", "");
block.className += isFree ? " room-free" : " room-in-use"; block.className += isFree ? " room-free" : " room-in-use";
block.querySelector('p').innerHTML = isFree ? 'Free' : block.querySelector('p').innerHTML = isFree ? 'Free' :
currentLecture.title + "<br> (" + formatTime(currentLecture.start) + " - " + currentLecture.title + "<br> (" + formatTime(currentLecture.start) + " - " +
formatTime(currentLecture.end) + ")"; formatTime(currentLecture.end) + ")";
} }
async function buildRoomMarkup(roomTitle) { async function buildRoomMarkup(roomTitle) {
const data = await roomStatus(roomTitle); const data = await roomStatus(roomTitle);
const room = getRoomNode(); const room = getRoomNode();
const title = room.querySelector('.room-title'); const title = room.querySelector('.room-title');
title.innerHTML = roomTitle; title.innerHTML = roomTitle;
title.id = "schedule-" + roomTitle; title.id = "schedule-" + roomTitle;
const list = room.querySelector('.list'); const list = room.querySelector('.list');
for (const d of data) { for (const d of data) {
const slot = document.importNode(SLOT_TEMPLATE.content, true); const slot = document.importNode(SLOT_TEMPLATE.content, true);
const title = slot.querySelector('.title'); const title = slot.querySelector('.title');
title.innerHTML = d.title; title.innerHTML = d.title;
const start = slot.querySelector('.start'); const start = slot.querySelector('.start');
start.innerHTML = formatTime(d.start); start.innerHTML = formatTime(d.start);
const end = slot.querySelector('.end'); const end = slot.querySelector('.end');
end.innerHTML = formatTime(d.end); end.innerHTML = formatTime(d.end);
list.appendChild(slot); list.appendChild(slot);
} }
timeTable[roomTitle] = data; timeTable[roomTitle] = data;
colorRoom(roomTitle, room); colorRoom(roomTitle, room);
if (data.length == 0) { if (data.length == 0) {
list.appendChild(document.importNode(FREE_SLOT_TEMPLATE.content, true)); list.appendChild(document.importNode(FREE_SLOT_TEMPLATE.content, true));
} }
ROOM_LIST.appendChild(room); ROOM_LIST.appendChild(room);
} }
function setTimePreview(date) { function setTimePreview(date) {
const timePreview = document.getElementById('timepreviewer'); const timePreview = document.getElementById('timepreviewer');
timePreview.innerText = "Time: " + formatTime(date); timePreview.innerText = "Time: " + formatTime(date);
} }
function setupTimeMachine() { function setupTimeMachine() {
const slider = document.getElementById('timemachine'); const slider = document.getElementById('timemachine');
slider.min = 8 * 60 + 30; slider.min = 8 * 60 + 30;
slider.max = 19 * 60 + 30; slider.max = 19 * 60 + 30;
slider.addEventListener("input", (e) => {
const date = new Date();
date.setHours(0);
date.setMinutes(slider.value);
const node = getRoomNode();
setTimePreview(date);
ROOMS.forEach((roomTitle) => {
colorRoom(roomTitle, node, date);
});
});
slider.addEventListener("input", (e) => {
const date = new Date(); const date = new Date();
const mins = date.getHours() * 60 + date.getMinutes(); date.setHours(0);
slider.value = mins < slider.min ? slider.min : date.setMinutes(slider.value);
mins > slider.max ? slider.max : mins; const node = getRoomNode();
setTimePreview(date); setTimePreview(date);
ROOMS.forEach((roomTitle) => {
colorRoom(roomTitle, node, date);
});
});
const date = new Date();
const mins = date.getHours() * 60 + date.getMinutes();
slider.value = mins < slider.min ? slider.min :
mins > slider.max ? slider.max : mins;
setTimePreview(date);
} }
(async () => { // Thanks to Andrea Gallidabino and his mastery checks for this
for (const room of ROOMS) { Promise.all(ROOMS.map(buildRoomMarkup)).catch(console.error);
await buildRoomMarkup(room);
}
})();
setupTimeMachine(); setupTimeMachine();

View file

@ -1,4 +1,5 @@
/* Inter font */ /* Inter font */
/* vim: set ts=2 sw=2 et tw=80: */
@font-face { @font-face {
font-family: 'Inter'; font-family: 'Inter';
@ -6,7 +7,7 @@
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url("fonts/Inter-Regular.woff2") format("woff2"), src: url("fonts/Inter-Regular.woff2") format("woff2"),
url("fonts/Inter-Regular.woff") format("woff"); url("fonts/Inter-Regular.woff") format("woff");
} }
@font-face { @font-face {
@ -15,243 +16,243 @@
font-weight: 800; font-weight: 800;
font-display: swap; font-display: swap;
src: url("fonts/Inter-ExtraBold.woff2") format("woff2"), src: url("fonts/Inter-ExtraBold.woff2") format("woff2"),
url("fonts/Inter-ExtraBold.woff") format("woff"); url("fonts/Inter-ExtraBold.woff") format("woff");
} }
/* Base */ /* Base */
* { * {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
color: #212529; color: #212529;
} }
h1 { h1 {
text-align: center; text-align: center;
width: 100%; width: 100%;
} }
h2 { h2 {
text-align: center; text-align: center;
width: 100%; width: 100%;
} }
/* Slider */ /* Slider */
input[type=range] { input[type=range] {
-webkit-appearance: none; -webkit-appearance: none;
border: 0; border: 0;
height: 28px; height: 28px;
margin: 8px 0; margin: 8px 0;
outline: none; outline: none;
padding: 0 8px; padding: 0 8px;
width: 100%; width: 100%;
} }
input[type="range"]::-webkit-slider-runnable-track { input[type="range"]::-webkit-slider-runnable-track {
background: #b1b1bc; background: #b1b1bc;
border-radius: 2px; border-radius: 2px;
height: 4px; height: 4px;
} }
input[type="range"]::-moz-range-track { input[type="range"]::-moz-range-track {
background: #b1b1bc; background: #b1b1bc;
height: 4px; height: 4px;
} }
input[type="range"]::-webkit-slider-thumb { input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
background: #212529; background: #212529;
border: 4px solid #ededf0; border: 4px solid #ededf0;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
height: 28px; height: 28px;
margin-top: -12px; margin-top: -12px;
width: 28px; width: 28px;
} }
input[type="range"]::-moz-range-thumb { input[type="range"]::-moz-range-thumb {
background: #212529; background: #212529;
border: 4px solid #ededf0; border: 4px solid #ededf0;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
height: 20px; height: 20px;
width: 20px; width: 20px;
} }
input[type="range"]::-moz-focus-outer { input[type="range"]::-moz-focus-outer {
border: 0; border: 0;
} }
input[type="range"]::-moz-range-progress { input[type="range"]::-moz-range-progress {
background: #212529; background: #212529;
border-radius: 4px; border-radius: 4px;
height: 4px; height: 4px;
} }
/* Room map */ /* Room map */
.room-map { .room-map {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
padding-left: 10vw; padding-left: 10vw;
padding-right: 10vw; padding-right: 10vw;
text-align: center; text-align: center;
} }
.room { .room {
margin: 0.5rem; margin: 0.5rem;
background: #ededf0; background: #ededf0;
border-radius: 6px; border-radius: 6px;
padding: 0.5rem; padding: 0.5rem;
transition: all 0.5s ease; transition: all 0.5s ease;
} }
.room:hover { .room:hover {
box-shadow: 0 4px 8px #0000002a; box-shadow: 0 4px 8px #0000002a;
} }
.room a { .room a {
text-decoration: none; text-decoration: none;
} }
.room h3 { .room h3 {
font-size: 1.6rem; font-size: 1.6rem;
font-weight: 700; font-weight: 700;
} }
.room p { .room p {
font-size: 1.2rem; font-size: 1.2rem;
} }
.room-big { .room-big {
flex: 1 1 100%; flex: 1 1 100%;
} }
.room-small { .room-small {
flex: 1 1 45%; flex: 1 1 45%;
} }
.room-free { .room-free {
background-color: #3FE1B0; background-color: #3FE1B0;
} }
.room-in-use { .room-in-use {
background-color: #FF505F; background-color: #FF505F;
} }
@media screen and (max-width: 830px) { @media screen and (max-width: 830px) {
.room { .room {
padding: 0.2rem; padding: 0.2rem;
} }
.room-big { .room-big {
flex: 1 1 80%; flex: 1 1 80%;
} }
.room-small { .room-small {
flex: 1 1 35%; flex: 1 1 35%;
} }
} }
/* Time */ /* Time */
.timepicker { .timepicker {
border: #00000033 solid 1px; border: #00000033 solid 1px;
border-radius: 6px; border-radius: 6px;
box-shadow: 0 1px 3px #0000001a; box-shadow: 0 1px 3px #0000001a;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 1rem; padding: 1rem;
margin: 2rem 5vw; margin: 2rem 5vw;
text-align: center; text-align: center;
} }
/* Schedule */ /* Schedule */
.schedule { .schedule {
border: #00000033 solid 1px; border: #00000033 solid 1px;
border-radius: 6px; border-radius: 6px;
box-shadow: 0 1px 3px #0000001a; box-shadow: 0 1px 3px #0000001a;
padding: 1rem; padding: 1rem;
margin: 2rem 5vw; margin: 2rem 5vw;
} }
.schedule h2 { .schedule h2 {
font-weight: 800; font-weight: 800;
text-align: start; text-align: start;
} }
/* Dark mode */ /* Dark mode */
@media(prefers-color-scheme: dark) { @media(prefers-color-scheme: dark) {
* { * {
color: #f9f9fa; color: #f9f9fa;
} }
body { body {
background: #212529; background: #212529;
} }
input[type="range"] { input[type="range"] {
background-color: #2d3339; background-color: #2d3339;
} }
input[type="range"]::-webkit-slider-runnable-track { input[type="range"]::-webkit-slider-runnable-track {
background: #7a7a8b; background: #7a7a8b;
} }
input[type="range"]::-moz-range-track { input[type="range"]::-moz-range-track {
background: #7a7a8b; background: #7a7a8b;
} }
input[type="range"]::-webkit-slider-thumb { input[type="range"]::-webkit-slider-thumb {
background: #f9f9fa; background: #f9f9fa;
border: 4px solid #5e5e72; border: 4px solid #5e5e72;
} }
input[type="range"]::-moz-range-thumb { input[type="range"]::-moz-range-thumb {
background: #f9f9fa; background: #f9f9fa;
border: 4px solid #5e5e72; border: 4px solid #5e5e72;
} }
input[type="range"]::-moz-range-progress { input[type="range"]::-moz-range-progress {
background: #f9f9fa; background: #f9f9fa;
} }
.room { .room {
margin: 0.5rem; margin: 0.5rem;
background: #2d3339; background: #2d3339;
border-radius: 6px; border-radius: 6px;
padding: 0.5rem; padding: 0.5rem;
transition: all 0.5s ease; transition: all 0.5s ease;
} }
.room-free { .room-free {
background-color: #3FE1B0; background-color: #3FE1B0;
} }
.room-free:hover { .room-free:hover {
box-shadow: 0 4px 8px #b3ffe333; box-shadow: 0 4px 8px #b3ffe333;
} }
.room-in-use { .room-in-use {
background-color: #ff6a75; background-color: #ff6a75;
} }
.room-in-use:hover { .room-in-use:hover {
box-shadow: 0 4px 8px #ff6a7533; box-shadow: 0 4px 8px #ff6a7533;
} }
.timepicker { .timepicker {
border: #384047 solid 1px; border: #384047 solid 1px;
background-color: #2d3339; background-color: #2d3339;
box-shadow: 0 0 0 #00000000; box-shadow: 0 0 0 #00000000;
} }
.schedule { .schedule {
border: #384047 solid 1px; border: #384047 solid 1px;
background-color: #2d3339; background-color: #2d3339;
box-shadow: 0 0 0 #00000000; box-shadow: 0 0 0 #00000000;
} }
} }

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- vim: set ts=2 sw=2 et tw=80: --> <!-- vim: set ts=2 sw=2 et tw=80: -->
<html> <html>
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<title>USI • Rooms availability</title> <title>USI • Rooms availability</title>
@ -10,86 +10,86 @@
<link rel="icon" href="assets/images/icon-64.png"> <link rel="icon" href="assets/images/icon-64.png">
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="assets/index.css"> <link rel="stylesheet" href="assets/index.css">
</head> </head>
<body> <body>
<h1>USI INF room availability</h1> <h1>USI INF room availability</h1>
<div class="timepicker"> <div class="timepicker">
<h2 id="timepreviewer">Loading...</h2> <h2 id="timepreviewer">Loading...</h2>
<input type="range" id="timemachine" /> <input type="range" id="timemachine" />
</div> </div>
<div class="room-map"> <div class="room-map">
<div class="room room-big" id="SI-003"> <div class="room room-big" id="SI-003">
<a href="#schedule-SI-003"> <a href="#schedule-SI-003">
<h3>SI-003</h3> <h3>SI-003</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
<!-- Row --> <!-- Row -->
<div class="room room-small" id="SI-015"> <div class="room room-small" id="SI-015">
<a href="#schedule-SI-015"> <a href="#schedule-SI-015">
<h3>SI-015</h3> <h3>SI-015</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
<div class="room room-small" id="SI-004"> <div class="room room-small" id="SI-004">
<a href="#schedule-SI-004"> <a href="#schedule-SI-004">
<h3>SI-004</h3> <h3>SI-004</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
<!-- Row --> <!-- Row -->
<div class="room room-big" id="SI-006"> <div class="room room-big" id="SI-006">
<a href="#schedule-SI-006"> <a href="#schedule-SI-006">
<h3>SI-006</h3> <h3>SI-006</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
<!-- Row --> <!-- Row -->
<div class="room room-small" id="SI-013"> <div class="room room-small" id="SI-013">
<a href="#schedule-SI-013"> <a href="#schedule-SI-013">
<h3>SI-013</h3> <h3>SI-013</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
<div class="room room-small" id="SI-007"> <div class="room room-small" id="SI-007">
<a href="#schedule-SI-007"> <a href="#schedule-SI-007">
<h3>SI-007</h3> <h3>SI-007</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
<!-- Row --> <!-- Row -->
<div class="room room-big room" id="SI-008"> <div class="room room-big room" id="SI-008">
<a href="#schedule-SI-008"> <a href="#schedule-SI-008">
<h3>SI-008</h3> <h3>SI-008</h3>
<p>???</p> <p>???</p>
</a> </a>
</div> </div>
</div> </div>
<div class="schedule"> <div class="schedule">
<h1>Schedule for today</h1> <h1>Schedule for today</h1>
<section class="times"> <section class="times">
</section> </section>
<template id="room"> <template id="room">
<h2 class="room-title"></h2> <h2 class="room-title"></h2>
<ul class="list"></ul> <ul class="list"></ul>
</template> </template>
<template id="time-free"> <template id="time-free">
<li><strong>Free all day</strong></li> <li><strong>Free all day</strong></li>
</template> </template>
<template id="time-slot"> <template id="time-slot">
<li> <li>
<span class="title"></span> <span class="title"></span>
(<span class="start"></span>-<span class="end"></span>) (<span class="start"></span>-<span class="end"></span>)
</li> </li>
</template> </template>
</div> </div>
<script src='app.js'></script> <script src='app.js'></script>
</body> </body>
</html> </html>

View file

@ -1,21 +1,23 @@
{ {
"short_name": "USI Rooms", "_modeline": " vim: set ts=2 sw=2 et tw=80:",
"name": "USI INF Rooms",
"icons": [ "short_name": "USI Rooms",
{ "name": "USI INF Rooms",
"src": "./assets/images/icon-192.png", "icons": [
"type": "image/png", {
"sizes": "192x192" "src": "./assets/images/icon-192.png",
}, "type": "image/png",
{ "sizes": "192x192"
"src": "./assets/images/icon-512.png", },
"type": "image/png", {
"sizes": "512x512" "src": "./assets/images/icon-512.png",
} "type": "image/png",
], "sizes": "512x512"
"start_url": "./index.html", }
"background_color": "#fafafa", ],
"display": "standalone", "start_url": "./index.html",
"scope": "./", "background_color": "#fafafa",
"theme_color": "#333333" "display": "standalone",
"scope": "./",
"theme_color": "#333333"
} }