commit
58254d9259
8 changed files with 339 additions and 5 deletions
157
35c3-events/calendar.js
Normal file
157
35c3-events/calendar.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import dateformat from 'dateformat';
|
||||||
|
import '../elements/time';
|
||||||
|
|
||||||
|
function sortEvents(a, b) {
|
||||||
|
if (a.start < b.start) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.start > b.start) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEvents(number) {
|
||||||
|
const now = new Date();
|
||||||
|
return fetch('http://c-flo.cbrp3.c-base.org/35c3/fahrplan')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
// res.schedule.conference.days[0].rooms.Adams[0]
|
||||||
|
const talks = [];
|
||||||
|
res.schedule.conference.days.forEach((day) => {
|
||||||
|
const dayEnd = new Date(day.day_end);
|
||||||
|
if (dayEnd < now) {
|
||||||
|
// Already done with this day
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.keys(day.rooms).forEach((room) => {
|
||||||
|
day.rooms[room].forEach((slot) => {
|
||||||
|
const start = new Date(slot.date);
|
||||||
|
const [durationH, durationM] = slot.duration.split(':').map(val => parseInt(val, 10));
|
||||||
|
const end = new Date(slot.date);
|
||||||
|
end.setHours(end.getHours() + durationH);
|
||||||
|
end.setMinutes(end.getMinutes() + durationM);
|
||||||
|
if (end < now) {
|
||||||
|
// This event is already over
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
talks.push({
|
||||||
|
allDay: false,
|
||||||
|
start: start.toISOString(),
|
||||||
|
end: end.toISOString(),
|
||||||
|
title: slot.title,
|
||||||
|
id: room,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return talks;
|
||||||
|
})
|
||||||
|
.then(talks => fetch('https://launchlibrary.net/1.3/launch/next/2')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
const launches = res.launches.map((launch) => {
|
||||||
|
const start = new Date(launch.windowstart);
|
||||||
|
const end = new Date(launch.windowend);
|
||||||
|
return {
|
||||||
|
allDay: false,
|
||||||
|
start: start.toISOString(),
|
||||||
|
end: end.toISOString(),
|
||||||
|
title: launch.name,
|
||||||
|
id: launch.lsp.abbrev,
|
||||||
|
type: 'launch',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return launches;
|
||||||
|
})
|
||||||
|
.then((launches) => {
|
||||||
|
console.log(launches, talks);
|
||||||
|
const allEvents = talks.concat(launches);
|
||||||
|
allEvents.sort(sortEvents);
|
||||||
|
return allEvents.slice(0, number);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let prevDate = new Date();
|
||||||
|
function renderEvent(event, container) {
|
||||||
|
const now = new Date();
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
const time = document.createElement('td');
|
||||||
|
time.className = 'time';
|
||||||
|
const destination = document.createElement('td');
|
||||||
|
destination.className = 'destination';
|
||||||
|
const code = document.createElement('td');
|
||||||
|
code.className = 'code';
|
||||||
|
const status = document.createElement('td');
|
||||||
|
status.className = 'status';
|
||||||
|
const startDate = new Date(event.start);
|
||||||
|
const pad = (t) => {
|
||||||
|
let val = `${t}`;
|
||||||
|
if (val.length === 1) {
|
||||||
|
val = `0${val}`;
|
||||||
|
}
|
||||||
|
if (val.length === 2) {
|
||||||
|
val = `0${val}`;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
};
|
||||||
|
const cleanTitle = (title) => {
|
||||||
|
let cleaned = title.replace(/\sBerlin$/, '');
|
||||||
|
cleaned = cleaned.replace(/\sUser Group/, ' UG');
|
||||||
|
return cleaned;
|
||||||
|
};
|
||||||
|
const timeFormat = 'HH:MM';
|
||||||
|
if (event.allDay) {
|
||||||
|
time.innerHTML = '--:--';
|
||||||
|
} else {
|
||||||
|
time.innerHTML = dateformat(startDate, timeFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof event.id === 'number') {
|
||||||
|
code.innerHTML = `C${pad(event.id)}`;
|
||||||
|
} else {
|
||||||
|
code.innerHTML = event.id;
|
||||||
|
}
|
||||||
|
destination.innerHTML = cleanTitle(event.title);
|
||||||
|
if (event.type === 'launch') {
|
||||||
|
destination.classList.add('launch');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate.toDateString() !== prevDate.toDateString()) {
|
||||||
|
status.innerHTML = dateformat(startDate, 'dd.mm.');
|
||||||
|
}
|
||||||
|
if (startDate.toDateString() === now.toDateString()) {
|
||||||
|
row.classList.add('today');
|
||||||
|
if (startDate < now) {
|
||||||
|
status.innerHTML = 'Ongoing';
|
||||||
|
status.classList.add('ongoing');
|
||||||
|
} else {
|
||||||
|
status.innerHTML = 'Today';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.appendChild(status);
|
||||||
|
row.appendChild(time);
|
||||||
|
row.appendChild(destination);
|
||||||
|
row.appendChild(code);
|
||||||
|
container.appendChild(row);
|
||||||
|
prevDate = startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const table = document.getElementById('events');
|
||||||
|
getEvents(17)
|
||||||
|
.then((events) => {
|
||||||
|
while (table.firstChild) {
|
||||||
|
table.removeChild(table.firstChild);
|
||||||
|
}
|
||||||
|
events.forEach(event => renderEvent(event, table));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageReady() {
|
||||||
|
window.removeEventListener('load', onPageReady, false);
|
||||||
|
render();
|
||||||
|
setInterval(render, 30000);
|
||||||
|
}
|
||||||
|
window.addEventListener('load', onPageReady, false);
|
118
35c3-events/index.html
Normal file
118
35c3-events/index.html
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>35c3 event calendar</title>
|
||||||
|
<script src="../vendor/webcomponentsjs/webcomponents-lite.js"></script>
|
||||||
|
<script src="../vendor/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||||
|
<script src="../lib/35c3-calendar.js"></script>
|
||||||
|
<link rel="stylesheet" href="../theme/c-base.css">
|
||||||
|
<style>
|
||||||
|
#events {
|
||||||
|
display: block;
|
||||||
|
height: 90vh;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
margin-left: 3vw;
|
||||||
|
margin-right: 3vw;
|
||||||
|
}
|
||||||
|
#events td {
|
||||||
|
font-size: 7vmin;
|
||||||
|
vertical-align: top;
|
||||||
|
text-shadow: #000000 1px 0 10px;
|
||||||
|
}
|
||||||
|
#events tr {
|
||||||
|
margin-bottom: 16vh;
|
||||||
|
}
|
||||||
|
#events tr.today {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#events td.destination {
|
||||||
|
color: hsl(35, 98%, 36%);
|
||||||
|
font-weight: bold;
|
||||||
|
max-width: 53vw;
|
||||||
|
max-height: 17vmin;
|
||||||
|
padding-right: 2vw;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#events td.destination.launch {
|
||||||
|
color: hsl(210, 98%, 36%);
|
||||||
|
}
|
||||||
|
#events tr.today td.destination {
|
||||||
|
color: hsl(35, 98%, 46%);
|
||||||
|
}
|
||||||
|
#events tr.today td.destination.launch {
|
||||||
|
color: hsl(210, 98%, 46%);
|
||||||
|
}
|
||||||
|
#events td.code {
|
||||||
|
font-family: 'ceva', sans-serif;
|
||||||
|
}
|
||||||
|
#events td.time {
|
||||||
|
padding-left: 2vw;
|
||||||
|
padding-right: 2vw;
|
||||||
|
}
|
||||||
|
#events td.status {
|
||||||
|
text-align: right;
|
||||||
|
text-transform: lowercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-left: 1vw;
|
||||||
|
padding-right: 1vw;
|
||||||
|
padding-top: 2vmin;
|
||||||
|
font-size: 5vmin;
|
||||||
|
}
|
||||||
|
#events tr.today td.status {
|
||||||
|
background-color: hsl(35, 98%, 46%);
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: none;
|
||||||
|
padding-top: 1vmin;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#events tr.today td.status.ongoing {
|
||||||
|
background-color: #73d216;
|
||||||
|
}
|
||||||
|
cbase-time, h1 {
|
||||||
|
font-family: 'ceva', sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 6vmin;
|
||||||
|
line-height: 10vh;
|
||||||
|
color: #fff;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#screen-title {
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
margin-left: 3vw;
|
||||||
|
}
|
||||||
|
#current-time {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 3vw;
|
||||||
|
}
|
||||||
|
@media (orientation: portrait) {
|
||||||
|
#events td.code {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1140px) {
|
||||||
|
#events td.code {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="station">
|
||||||
|
<h1 id="screen-title">c-base @ 35C3</h1>
|
||||||
|
<table id="events">
|
||||||
|
</table>
|
||||||
|
<cbase-time id="current-time" interval="1" format="HH:MM"></cbase-time>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
23
Dockerfile
23
Dockerfile
|
@ -1,2 +1,21 @@
|
||||||
FROM nginx
|
FROM node:10-alpine
|
||||||
COPY dist /usr/share/nginx/html
|
|
||||||
|
# Reduce npm install verbosity, overflows Travis CI log view
|
||||||
|
ENV NPM_CONFIG_LOGLEVEL warn
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
RUN mkdir -p /var/infoscreens
|
||||||
|
WORKDIR /var/infoscreens
|
||||||
|
COPY package.json /var/infoscreens
|
||||||
|
COPY server /var/infoscreens/server
|
||||||
|
COPY dist /var/infoscreens/dist
|
||||||
|
|
||||||
|
# Install NoFlo and dependencies
|
||||||
|
RUN npm install --only=production
|
||||||
|
|
||||||
|
# Map the volumes
|
||||||
|
VOLUME /var/infoscreens/dist
|
||||||
|
|
||||||
|
CMD npm start
|
||||||
|
|
|
@ -8,8 +8,8 @@ services:
|
||||||
infoscreens:
|
infoscreens:
|
||||||
image: cbase/infoscreens
|
image: cbase/infoscreens
|
||||||
ports:
|
ports:
|
||||||
- '8080:80'
|
- '8080:8080'
|
||||||
links:
|
links:
|
||||||
- mqtt-infoscreens
|
- mqtt-infoscreens
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/share/nginx/html
|
- ./dist:/var/infoscreens/dist
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
"pretest": "eslint index.js lib/*.js elements/*.js infodisplay/*.js events/*.js",
|
"pretest": "eslint index.js lib/*.js elements/*.js infodisplay/*.js events/*.js",
|
||||||
"start": "webpack-dev-server",
|
"dev": "webpack-dev-server",
|
||||||
|
"start": "node server",
|
||||||
"test": "npm run build"
|
"test": "npm run build"
|
||||||
},
|
},
|
||||||
"author": "Henri Bergius <henri.bergius@iki.fi>",
|
"author": "Henri Bergius <henri.bergius@iki.fi>",
|
||||||
|
@ -16,6 +17,11 @@
|
||||||
"color-interpolate": "^1.0.2",
|
"color-interpolate": "^1.0.2",
|
||||||
"d3": "^4.12.0",
|
"d3": "^4.12.0",
|
||||||
"dateformat": "^3.0.2",
|
"dateformat": "^3.0.2",
|
||||||
|
"isomorphic-fetch": "^2.2.1",
|
||||||
|
"koa": "^2.6.2",
|
||||||
|
"koa-cors": "0.0.16",
|
||||||
|
"koa-router": "^7.4.0",
|
||||||
|
"koa-static": "^5.0.0",
|
||||||
"msgflo-browser": "^0.2.0",
|
"msgflo-browser": "^0.2.0",
|
||||||
"plotly.js": "^1.31.2",
|
"plotly.js": "^1.31.2",
|
||||||
"skatejs": "^5.0.0-beta.4"
|
"skatejs": "^5.0.0-beta.4"
|
||||||
|
|
23
server/index.js
Normal file
23
server/index.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
const Koa = require('koa');
|
||||||
|
const Router = require('koa-router');
|
||||||
|
const Static = require('koa-static');
|
||||||
|
const Cors = require('koa-cors');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const route35c3 = require('./route/35c3');
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
route35c3(router);
|
||||||
|
|
||||||
|
app
|
||||||
|
.use(Cors())
|
||||||
|
.use(router.routes())
|
||||||
|
.use(router.allowedMethods())
|
||||||
|
.use(Static(path.resolve(__dirname, '../dist'), {}));
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
|
if (!module.parent) {
|
||||||
|
app.listen(8080);
|
||||||
|
}
|
10
server/route/35c3.js
Normal file
10
server/route/35c3.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
require('isomorphic-fetch');
|
||||||
|
|
||||||
|
module.exports = (router) => {
|
||||||
|
router.get('/35c3/fahrplan', async (ctx) => {
|
||||||
|
const res = await fetch('https://fahrplan.events.ccc.de/congress/2018/Fahrplan/schedule.json');
|
||||||
|
ctx.assert((res.status === 200), res.status);
|
||||||
|
const body = await res.json();
|
||||||
|
ctx.body = body;
|
||||||
|
});
|
||||||
|
};
|
|
@ -6,6 +6,7 @@ module.exports = {
|
||||||
infoscreens: './index.js',
|
infoscreens: './index.js',
|
||||||
infodisplay: './infodisplay/infodisplay.js',
|
infodisplay: './infodisplay/infodisplay.js',
|
||||||
calendar: './events/calendar.js',
|
calendar: './events/calendar.js',
|
||||||
|
'35c3-calendar': './35c3-events/calendar.js',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'dist'),
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
|
Loading…
Reference in a new issue