New door-graph, Improved slideshow, moved video

This commit is contained in:
Felix Albrigtsen 2021-10-13 10:10:17 +02:00
parent a4ce890a36
commit 6c891b3f79
11 changed files with 5898 additions and 163 deletions

View File

@ -157,11 +157,10 @@ nav #usermenu li:first-child:hover {
border-radius: 5px; border-radius: 5px;
padding: 8px 8px; padding: 8px 8px;
margin: 4px 4px; margin: 4px 4px;
color: white;
} }
#doorIndicator > p > abbr[title] { text-decoration: none; border-bottom: none; cursor: inherit; }
.doorIndicator_OPEN { border: 2px solid green; } .doorIndicator_OPEN { border: 2px solid green; }
.doorIndicator_CLOSED { border: 2px dotted red; } .doorIndicator_CLOSED { border: 2px dotted red; }
.doorStateMobileOnly { display: none; }
#mazeMapper { #mazeMapper {
width: 90%; width: 90%;

13
www/door/chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/*!
* chartjs-adapter-moment v1.0.0
* https://www.chartjs.org
* (c) 2021 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("moment"),require("chart.js")):"function"==typeof define&&define.amd?define(["moment","chart.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).moment,e.Chart)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=n(e);const a={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};t._adapters._date.override("function"==typeof f.default?{_id:"moment",formats:function(){return a},parse:function(e,t){return"string"==typeof e&&"string"==typeof t?e=f.default(e,t):e instanceof f.default||(e=f.default(e)),e.isValid()?e.valueOf():null},format:function(e,t){return f.default(e).format(t)},add:function(e,t,n){return f.default(e).add(t,n).valueOf()},diff:function(e,t,n){return f.default(e).diff(f.default(t),n)},startOf:function(e,t,n){return e=f.default(e),"isoWeek"===t?(n=Math.trunc(Math.min(Math.max(0,n),6)),e.isoWeekday(n).startOf("day").valueOf()):e.startOf(t).valueOf()},endOf:function(e,t){return f.default(e).endOf(t).valueOf()}}:{})}));
//# sourceMappingURL=chartjs-adapter-moment.min.js.map

View File

@ -5,20 +5,38 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inngangsverkstedet</title> <title>Inngangsverkstedet</title>
<style>
body {
text-align: center;
width: 80vw;
margin: auto auto;
}
#graphDiv {
display: flex;
flex-direction: column;
}
</style>
</head> </head>
<body> <body>
<!-- Felixalb 2021 -->
<h2>En kort analyse av nerders døgnrytme i deres naturlige habitat, PVV</h2> <h2>En kort analyse av nerders døgnrytme i deres naturlige habitat, PVV</h2>
<h3 id="infoText"></h3> <div id="graphDiv">
<canvas id="doorGraph1"></canvas> <h4>Siste 24 timer</h4>
<script src="./p5.min.js"></script> <canvas id="doorGraphDay"></canvas>
<h4>Siste 7 dager</h4>
<canvas id="doorGraphWeek"></canvas>
</div>
<script src="chart.min.js"></script>
<script src="moment.js"></script>
<script src="chartjs-adapter-moment.js"></script>
<script> <script>
const infoEl = document.getElementById("infoText"); const graphElDay = document.getElementById("doorGraphDay");
const graphEl1 = document.getElementById("doorGraph1"); const graphElWeek = document.getElementById("doorGraphWeek");
const XHR = new XMLHttpRequest(); const XHR = new XMLHttpRequest();
const url="/door/?period=day&edgeonly=true"; const url="/door/?period=week";
XHR.open("GET", url); XHR.open("GET", url);
XHR.send(); XHR.send();
@ -26,76 +44,102 @@
XHR.onreadystatechange = ()=>{ XHR.onreadystatechange = ()=>{
if (XHR.readyState == 4 && XHR.status == 200) { if (XHR.readyState == 4 && XHR.status == 200) {
console.log("Response 200 from API") console.log("Response 200 from API")
response = JSON.parse(XHR.responseText); response = JSON.parse(XHR.responseText); //Should be try-catched?
if (response.status != "OK") { if (response.status != "OK") {
infoEl.innerHTML = "Error when connecting to API."; console.log("Error when connecting to API.");
return return
} else { } else {
let datapoints = response.entries; const allDatapoints = response.entries;
console.log("Success, " + datapoints.length + " datapoints received."); console.log("Success, " + allDatapoints.length + " datapoints received.");
// displayLineDiagram(graphEl1, datapoints);
displayBar(datapoints); const dayDatapoints = getLastDay(allDatapoints);
displayLineDiagram(graphElDay, dayDatapoints, "hour");
displayLineDiagram(graphElWeek, allDatapoints, "day");
} }
} }
} }
// function getDateString(time) { function getLastDay(data) {
// let dateObj = new Date(time*1e3); let date = new Date();
// return dateObj.toLocaleString(); let curTime = date.getTime();
// } let targetTime = parseInt(curTime/1e3) - (60*60*24);
// function displayLineDiagram(canv, data) { let i;
// let ctx = canv.getContext("2d"); for (i = 0; i < data.length; i++) {
// let chart = new Chart(ctx, { if (data[i].time < targetTime) {
// type: 'line', break;
// data: { }
// labels: data.map(entry=> getDateString(entry.time)), }
// // labels: data.map(entry=> 1e3 * entry.time), return data.slice(0, i);
// datasets: [{
// data: data.map(entry => entry.open)
// }],
// }
// });
// }
function setup() {
createCanvas(800, 200);
noLoop();
background(50);
} }
function draw() {}
function displayBar(data) {
const fullLength = 60*60*24;
// const dateObj = new Date();
const curTime = Math.floor(Date.now() / 1000)
let borderPositions = [0];
//Convert timestamps to a position on the graph function displayLineDiagram(canv, data, timeunit) {
for(let i = data.length-1; i > 0; i--) { let ctx = canv.getContext("2d");
const ts = data[i]["time"]; let dotColor = data.map(entry => entry.open ? "rgb(10, 150, 10)" : "rgb(200, 100, 100)");
const pixelPos = width - (((curTime - ts) / fullLength) * width);
borderPositions.push(pixelPos);
}
console.log(borderPositions); let chart = new Chart(ctx, {
type: 'line',
let sectionColors = ["gray"]; data: {
//Define list of colors, gray=?, green=open, red=closed labels: data.map(entry=> 1e3 * entry.time),
for(let i = 0; i < data.length; i++) { datasets: [{
sectionColors.push((data[i]["open"]) ? "green" : "red"); data: data.map(entry => entry.open),
} stepped: "before",
console.log(sectionColors); borderColor: dotColor,
for(let i = 0; i < borderPositions.length-1; i++) { backgroundColor: dotColor
fill(sectionColors[i]); }],
rect(borderPositions[i], 0, borderPositions[i+1], height); },
console.log(`${sectionColors[i]} from ${borderPositions[i]}px to ${borderPositions[i+1]}px`) options: {
} scales: {
xAxis: {
type: "time",
time: {
unit: timeunit
},
},
yAxis: {
suggestedMin: -0.1,
suggestedMax: 1.1,
grid: {display: false},
ticks: {
callback: function(label, index, labels) {
if (label == 0) {
return "Stengt";
} else if (label == 1) {
return "Åpent";
} else {
return "";
}
}
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
const value = tooltipItem.formattedValue;
if (value == 0) {
return "Stengt";
} else if (value == 1) {
return "Åpent";
} else {
return "";
}
}
}
}
}
}
});
} }
</script> </script>
<!-- <script src="./chart.min.js"></script> -->
</body> </body>
</html> </html>

View File

@ -38,7 +38,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
$lines = $door->getEntriesAfter($startTime); $lines = $door->getEntriesAfter($startTime);
if (isset($_GET["period"]) && (bool)htmlspecialchars($_GET["edgeonly"])) { if (isset($_GET["edgeonly"]) && (bool)htmlspecialchars($_GET["edgeonly"])) {
//Ignore repeats //Ignore repeats
$lines = getChanges($lines); $lines = getChanges($lines);
} }

5670
www/door/moment.js Normal file

File diff suppressed because it is too large Load Diff

3
www/door/p5.min.js vendored

File diff suppressed because one or more lines are too long

32
www/galleri/slideshow.js Normal file
View File

@ -0,0 +1,32 @@
const SLIDESHOWDELAYMS = 3500;
//Defined in slideshow.php: const slideshowFnames
let slideshowIndex = 1;
let slideshowInterval;
let ssi1 = document.getElementById("slideshowImage1");
let ssi2 = document.getElementById("slideshowImage2");
function stepSlideshow(imgs) {
//Swap image elements
let tmp = ssi1;
ssi1 = ssi2;
ssi2 = tmp;
//Swap visibility
ssi2.classList.remove("slideshowactive");
ssi1.classList.add("slideshowactive");
setTimeout(()=>{
//Change source to next picture after it is faded out
slideshowIndex = (slideshowIndex + 1) % imgs.length;
ssi2.src = slideshowFnames[slideshowIndex];
}, 800);
}
//Initialize slideshow, start interval
if (slideshowFnames.length > 1) {
slideshowInterval = setInterval(()=>{
stepSlideshow(slideshowFnames);
}, SLIDESHOWDELAYMS);
}

30
www/galleri/slideshow.php Normal file
View File

@ -0,0 +1,30 @@
<?php
//Short path to search folder, full to display in <img>
$relativePath = "/bilder/slideshow/";
$absolutePath = "/galleri" . $relativePath;
//Path to first image in slideshow and fallback image if no others are present
$splashImg = "/PNG/PVV-logo-big-bluebg.png";
$filenames = sCaNdIr(__DIR__ . $relativePath);
//Remove the expected non-images
foreach($filenames as $k => $value) {
if(in_array($value, [".gitkeep", ".", ".."])) {
unset($filenames[$k]);
}
}
function getFullPath($fname) { return ($GLOBALS["absolutePath"] . $fname ); }
//Sort filenames alphabetically and prepend the path prefix to each item.
asort($filenames);
$slideshowimagefilenames = aRrAy_MaP("getFullPath", $filenames);
//Prepend the cover photo
ArRaY_uNsHiFt($slideshowimagefilenames, $splashImg);
eChO('<img class="slideshowimg slideshowactive" id="slideshowImage1" src="' . $slideshowimagefilenames[0] . '">');
ecHo('<img class="slideshowimg" id="slideshowImage2" src="' . $slideshowimagefilenames[1] . '">');
//Store list of file names in a globel JS variable
EchO("<script> const slideshowFnames =" . jSoN_eNcOdE($slideshowimagefilenames) . "; </script>");
?>

View File

@ -10,9 +10,13 @@ $motd = $motdfetcher->getMOTD();
$door = new \pvv\side\Door($pdo); $door = new \pvv\side\Door($pdo);
$doorEntry = (object)($door->getCurrent()); $doorEntry = (object)($door->getCurrent());
$isDoorOpen = $doorEntry->open; if ($doorEntry->time < (time() - 60*30)) {
if (date("Y-m-d") == date("Y-m-d", $doorEntry->time)) { $doorTime = date("H:i", $doorEntry->time); $doorStateText = "Ingen data fra dørsensor";
} else { $doorTime = date("H:i d/m", $doorEntry->time);} } else {
if ($doorEntry->open) { $doorStateText = "Døren er <b>åpen</b>";
} else { $doorStateText = "Døren er <b>ikke åpen</b>"; }
}
$doorTime = date("H:i", $doorEntry->time);
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="no"> <html lang="no">
@ -36,96 +40,9 @@ if (date("Y-m-d") == date("Y-m-d", $doorEntry->time)) { $doorTime = date("H:i",
</nav> </nav>
<header class="landing"> <header class="landing">
<!-- Statisk bilde:
<img class="logo" src="css/logo-white.png"/>
-->
<!-- Youtube-iframe:
<style>
.iframe-container {
position: relative;
overflow: hidden;
width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio (divide 9 by 16 = 0.5625) */
}
/* Then style the iframe to fit in the container div with full height and width */
.responsive-iframe {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
}
</style>
<div class="iframe-container" style="max-width: 100em;">
<iframe class="responsive-iframe" src="https://www.youtube.com/embed/l-iEkaQNQdk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen ></iframe>
</div>
-->
<div id="imageSlideshow"> <div id="imageSlideshow">
<?php <?php include("galleri/slideshow.php"); ?>
<script src="galleri/slideshow.js"></script>
$path = "/galleri/bilder/slideshow/";
$splashImg = "/PNG/PVV-logo-big-bluebg.png";
//Find all files/dirs in folder and discard . and .. directories
$filenames = aRrAy_SlIcE(sCaNdIr(__DIR__ . $path), 2);
function getFullPath($fname) { return ($GLOBALS["path"] . $fname ); }
//Sort filenames alphabetically and prepend the path prefix to each item.
asort($filenames);
$slideshowimagefilenames = aRrAy_MaP("getFullPath", $filenames);
//Prepend the cover photo
ArRaY_uNsHiFt($slideshowimagefilenames, $splashImg);
eChO('<img class="slideshowimg slideshowactive" id="slideshowImage1" src="' . $slideshowimagefilenames[0] . '">');
ecHo('<img class="slideshowimg" id="slideshowImage2" src="' . $slideshowimagefilenames[1] . '">');
//Store list of file names in a globel JS variable
EchO("<script> const slideshowFnames =" . jSoN_eNcOdE($slideshowimagefilenames) . "; </script>");
?>
<script>
const SLIDESHOWDELAYMS = 3500; //Minimum 3 * fade time (3*800=2400ms)
let slideshowIndex = 1;
let slideshowInterval;
const ssi1 = document.getElementById("slideshowImage1");
const ssi2 = document.getElementById("slideshowImage2");
function stepSlideshow(imgs) {
//Change visible picture, ssi2 active, fades with css
ssi1.classList.remove("slideshowactive");
ssi2.classList.add("slideshowactive");
setTimeout(()=>{
//Change to ssi1 active, no visible change
ssi1.src = ssi2.src;
ssi1.classList.add("slideshowactive");
// Hide ssi2 after ssi1 has appeared, no visible change
setTimeout(()=>{
ssi2.classList.remove("slideshowactive");
}, 800);
//Prepare for next cycle, no visible change
setTimeout(()=>{
slideshowIndex = (slideshowIndex + 1) % imgs.length;
ssi2.src = imgs[slideshowIndex];
}, 1600);
}, 800);
}
//Initialize slideshow, start interval
if (slideshowFnames.length > 1) {
slideshowInterval = setInterval(()=>{
stepSlideshow(slideshowFnames);
}, SLIDESHOWDELAYMS);
}
</script>
</div> </div>
<div class="info"> <div class="info">
@ -136,9 +53,9 @@ if (date("Y-m-d") == date("Y-m-d", $doorEntry->time)) { $doorTime = date("H:i",
<a class="btn" href="om/"><li>Om PVV</li></a> <a class="btn" href="om/"><li>Om PVV</li></a>
<a class="btn focus" href="paamelding/"><li>Bli medlem!</li></a> <a class="btn focus" href="paamelding/"><li>Bli medlem!</li></a>
<a class="btn" href="https://use.mazemap.com/#config=ntnu&v=1&zlevel=2&center=10.406281,63.417093&zoom=19.5&campuses=ntnu&campusid=1&sharepoitype=poi&sharepoi=38159&utm_medium=longurl">Veibeskrivelse</li></a> <a class="btn" href="https://use.mazemap.com/#config=ntnu&v=1&zlevel=2&center=10.406281,63.417093&zoom=19.5&campuses=ntnu&campusid=1&sharepoitype=poi&sharepoi=38159&utm_medium=longurl">Veibeskrivelse</li></a>
<div id="doorIndicator" class="<?php echo($isDoorOpen ? "doorIndicator_OPEN" : "doorIndicator_CLOSED"); ?>"> <div id="doorIndicator" class="<?php echo($doorEntry->open ? "doorIndicator_OPEN" : "doorIndicator_CLOSED"); ?>" onclick="location.href='/door/graph.html'">
<p class="doorStateText"><abbr title="Oppdatert <?php echo($doorTime) ?>">Døren er <b><?php echo($isDoorOpen ? "" : "ikke") ?> åpen</b>.</abbr></p> <p class="doorStateText"><?php echo($doorStateText) ?></p>
<p class="doorStateTime doorStateMobileOnly">(Oppdatert <?php echo($doorTime) ?>)</p> <p class="doorStateTime">(Oppdatert <?php echo($doorTime) ?>)</p>
</div> </div>
</ul> </ul>
</div> </div>

View File

@ -26,6 +26,31 @@ p {hyphens: auto;}
<p>Velkommen til Programvareverkstedets nettside. Programvareverkstedet (PVV) er en studentorganisasjon ved Norges Teknisk-Naturvitenskapelige Universitet (NTNU). PVVs formål er å skape et miljø for datainteresserte personer tilknyttet universitetet. Nåværende og tidligere studenter ved NTNU, samt ansatte ved NTNU og tilstøtende miljø, kan bli medlemmer.</p> <p>Velkommen til Programvareverkstedets nettside. Programvareverkstedet (PVV) er en studentorganisasjon ved Norges Teknisk-Naturvitenskapelige Universitet (NTNU). PVVs formål er å skape et miljø for datainteresserte personer tilknyttet universitetet. Nåværende og tidligere studenter ved NTNU, samt ansatte ved NTNU og tilstøtende miljø, kan bli medlemmer.</p>
</article> </article>
<article>
<style>
.iframe-container {
position: relative;
overflow: hidden;
width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio (divide 9 by 16 = 0.5625) */
}
/* Then style the iframe to fit in the container div with full height and width */
.responsive-iframe {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
}
</style>
<div class="iframe-container" style="max-width: 100em;">
<iframe class="responsive-iframe" src="https://www.youtube.com/embed/l-iEkaQNQdk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen ></iframe>
</div>
</article>
<article> <article>
<h2>Hva betyr det å være et medlem av PVV?</h2> <h2>Hva betyr det å være et medlem av PVV?</h2>