This commit is contained in:
nicco 2017-12-25 00:50:54 +01:00
parent 1f6dffc690
commit 7ec7eb5b4b
41 changed files with 576 additions and 2 deletions

View File

@ -1,2 +1,2 @@
# dvb
DVB Departure Screen
# DVB
Serverless DVB Departure Screen

92
app/index.html Normal file
View File

@ -0,0 +1,92 @@
<!doctype html>
<html>
<head>
<link href="res/fonts/Army/import.css" rel="stylesheet" type="text/css">
<link href="res/css/main.css" rel="stylesheet" type="text/css">
<link href="res/css/layout.css" rel="stylesheet" type="text/css">
<link href="res/css/screen.css" rel="stylesheet" type="text/css">
<link href="res/css/top.css" rel="stylesheet" type="text/css">
<link href="res/css/weather.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="fill" id="app">
<div class="container fill">
<div class="top">
<div class="fill" id="datetime">
<dvb-time></dvb-time>
<dvb-date></dvb-date>
</div>
</div>
<div class="body">
<div class="fill" id="screen">
<!-- ZELLESCHER -->
<dvb-line stop-Id="33000312" line="11"></dvb-line>
<dvb-line stop-Id="33000312" line="61"></dvb-line>
<!-- STREHLI -->
<dvb-line stop-Id="33000311" line="66"></dvb-line>
<!-- Räcknitzhöhe -->
<dvb-line stop-Id="33000313" line="85"></dvb-line>
</div>
</div>
<div class="side">
<div class="fill weather-container">
<dvb-weather offset="0"></dvb-weather>
<dvb-weather offset="14400"></dvb-weather>
<dvb-weather offset="28800"></dvb-weather>
<dvb-weather offset="43200"></dvb-weather>
</div>
</div>
</div>
</div>
<!-- VUE Templates -->
<script type="text/x-template" id="tmpl-dvb-weather">
<div class="weather-item">
<span class="temperature">{{cur}}</span>
</div>
</script>
<script type="text/x-template" id="tmpl-dvb-line">
<div class="fill line">
<div class="head">
<div class="title">
<span>{{stopName}}</span>
</div>
<br>
<div class="lineNumber">{{lineNumber}}</div>
</div>
<div class="body">
<template v-for="(direction, name) in directions">
<div class="title">
<span>{{ name }}</span>
</div>
<div v-for="data in direction" v-if="data.RealTime" class="departure">
{{ new Date() | timeInterval(data.RealTime) }}
<span class="min">min</span>
</div>
</template>
</div>
</div>
</script>
<!-- VENDOR -->
<script src="res/js/vendor/vue.min.js"></script>
<!-- VUE Controller -->
<script src="res/js/vue/dvb-date.js"></script>
<script src="res/js/vue/dvb-time.js"></script>
<script src="res/js/vue/dvb-weather.js"></script>
<script src="res/js/vue/dvb-line.js"></script>
<script src="res/js/main.js"></script>
</body>
</html>

22
app/res/css/layout.css Normal file
View File

@ -0,0 +1,22 @@
/* LAYOUT */
.container {
display: grid;
grid-template-columns: 1fr calc(100vh / 4);
grid-template-rows: auto 1fr;
grid-template-areas: "top side" "body side";
background: var(--clr-light);
color: var(--clr-dark);
}
.container .top {
grid-area: top;
}
.container .side {
grid-area: side;
}
.container .body {
grid-area: body;
}

33
app/res/css/main.css Normal file
View File

@ -0,0 +1,33 @@
* {
--clr-light: hsl(85, 70%, 83%);
--clr-dark: hsl(31, 16%, 27%);
--clr-accent-1: hsl(45, 76%, 50%);
--clr-accent-2: hsl(46, 35%, 51%);
--clr-accent-3: hsl(29, 75%, 60%);
--font-xxxl: 10em;
--font-xxl: 7.5em;
--font-xl: 2em;
--font-lg: 1.5em;
--font-md: 1em;
--font-sm: .8em;
--font-xs: .6em;
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
font-family: Army;
}
/* UTILITY */
.fill {
height: 100%;
width: 100%;
}

77
app/res/css/screen.css Normal file
View File

@ -0,0 +1,77 @@
/* SCREEN */
#screen {
box-shadow: 1em -1em 8em -4em rgba(0, 0, 0, 1);
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 2em;
}
#screen .line {
flex: 1;
padding: .5em;
overflow: hidden;
height: 100%;
width: 100%;
text-align: center;
background: hsla(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
/* HEAD */
#screen .head {
flex: 0 1 auto;
}
#screen .head .title {
font-size: var(--font-xl);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: .15em 0em;
}
#screen .head .title>span {
background-color: var(--clr-accent-1);
padding: 1em 0;
}
#screen .head .lineNumber {
font-size: var(--font-xxl);
}
/* BODY */
#screen .body {
flex: 1 0 auto;
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: space-between;
}
#screen .body .departure {
font-size: var(--font-xl);
}
#screen .body .departure .min {
font-size: var(--font-xs);
}
#screen .body .title {
font-size: var(--font-xl);
padding: .15em 0em;
overflow: hidden;
margin: .5em 0;
}
#screen .body .title>span {
background-color: var(--clr-accent-3);
padding: var(--font-xl) 0;
}

26
app/res/css/top.css Normal file
View File

@ -0,0 +1,26 @@
/* DATETIME */
#datetime {
display: grid;
grid-template: 1fr auto / 1fr auto;
padding: 1em;
}
/* TIME */
#time {
grid-column: 1 / span 1;
grid-row: 1;
font-size: var(--font-xxxl);
text-align: center;
}
/* DATE */
#date {
grid-column: 2;
grid-row: 2;
font-size: var(--font-xl);
}

25
app/res/css/weather.css Normal file
View File

@ -0,0 +1,25 @@
/* WEATHER */
.weather-container {
display: grid;
grid-template-rows: repeat(4, 1fr);
grid-template-columns: 100%;
grid-gap: 1em;
padding: 1em;
}
.weather-container .weather-item {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
position: relative;
}
.weather-container .weather-item .temperature {
position: absolute;
bottom: 10%;
right: 10%;
width: 80%;
text-align: right;
font-size: var(--font-xl);
}

BIN
app/res/fonts/Army/Army.ttf Normal file

Binary file not shown.

6
app/res/fonts/Army/import.css vendored Normal file
View File

@ -0,0 +1,6 @@
@font-face {
font-family: 'Army';
font-weight: normal;
font-style: normal;
src: local('Army'), url('Army.tff');
}

BIN
app/res/img/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
app/res/img/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
app/res/img/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
app/res/img/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
app/res/img/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
app/res/img/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
app/res/img/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
app/res/img/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
app/res/img/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
app/res/img/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
app/res/img/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

18
app/res/js/main.js Normal file
View File

@ -0,0 +1,18 @@
'use strict'
const S = {
stations: [33000312, 33000311],
url: 'https://webapi.vvo-online.de/dm'
}
function parseTime(str) {
if (str === undefined)
return
if (str instanceof Date)
return str
return new Date(parseInt(str.slice(6, -2).split('+')[0]))
}
new Vue({
el: '#app'
})

60
app/res/js/vendor/fittext.js vendored Normal file
View File

@ -0,0 +1,60 @@
/*!
* FitText.js 1.0 jQuery free version
*
* Copyright 2011, Dave Rupert http://daverupert.com
* Released under the WTFPL license
* http://sam.zoy.org/wtfpl/
* Modified by Slawomir Kolodziej http://slawekk.info
*
* Date: Tue Aug 09 2011 10:45:54 GMT+0200 (CEST)
*/
(function () {
var addEvent = function (el, type, fn) {
if (el.addEventListener)
el.addEventListener(type, fn, false);
else
el.attachEvent('on' + type, fn);
};
var extend = function (obj, ext) {
for (var key in ext)
if (ext.hasOwnProperty(key))
obj[key] = ext[key];
return obj;
};
window.fitText = function (el, kompressor, options) {
var settings = extend({
'minFontSize': -1 / 0,
'maxFontSize': 1 / 0
}, options);
var fit = function (el) {
var compressor = kompressor || 1;
var resizer = function () {
el.style.fontSize = Math.max(Math.min(el.clientWidth / (compressor * 10), parseFloat(settings.maxFontSize)), parseFloat(settings.minFontSize)) + 'px';
};
// Call once to set.
resizer();
// Bind events
// If you have any js library which support Events, replace this part
// and remove addEvent function (or use original jQuery version)
addEvent(window, 'resize', resizer);
addEvent(window, 'orientationchange', resizer);
};
if (el.length)
for (var i = 0; i < el.length; i++)
fit(el[i]);
else
fit(el);
// return set of elements
return el;
};
})();

6
app/res/js/vendor/vue.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
Vue.component('dvb-date', {
template: `<div id="date">{{cur}}</div>`,
data() {
return {
hz: 3,
cur: '...',
}
},
methods: {
update() {
this.cur = new Date().toLocaleDateString('de-DE', {
weekday: 'short',
year: 'numeric',
month: 'numeric',
day: 'numeric'
})
},
startWatcher() {
this.update()
this.watcherId = setInterval(this.update, this.hz * 1000)
},
stopWatcher() {
clearInterval(this.watcherId)
},
},
mounted() {
this.startWatcher()
},
})

View File

@ -0,0 +1,86 @@
Vue.component('dvb-line', {
template: '#tmpl-dvb-line',
data() {
return {
hz: 30,
departures: null,
directions: {},
stopName: '',
lineNumber: ''
}
},
props: {
stopId: Number,
line: Number
},
filters: {
formatTime(d) {
if (d === undefined) return
d = this.parseTime(d)
let
h = d.getHours(),
m = d.getMinutes()
h = h < 10 ? `0${h}` : h
m = m < 10 ? `0${m}` : m
return `${h}:${m}`
},
timeInterval(a, b) {
a = this.parseTime(a)
b = this.parseTime(b)
return ((b - a) / 1000 / 60) | 0
}
},
methods: {
startWatcher() {
this.update()
this.watcherId = setInterval(this.update, 1000 * this.hz)
},
stopWatcher() {
clearInterval(this.watcherId)
},
update() {
console.log('Updating...')
fetch('https://webapi.vvo-online.de/dm', {
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify({
'stopid': this.stopId
})
})
.then(res => res.json())
.then(res => {
this.stopName = res.Name
this.departures = res.Departures.filter(departure => departure.LineName === this.line)
this.formatDirections()
})
},
formatDirections() {
// Reset names & data
this.directions = {}
for (var i of this.departures) {
// Initial directions if null
if (!(i.Direction in this.directions))
this.directions[i.Direction] = []
// Inset departure into array
this.directions[i.Direction].push(i)
}
this.directions = Object.keys(this.directions)
.sort().reduce((a, v) => {
a[v] = this.directions[v];
return a;
}, {});
},
},
created() {
this.lineNumber = this.line
this.stopId = this.stopId
this.startWatcher()
},
})

View File

@ -0,0 +1,31 @@
Vue.component('dvb-time', {
template: `<div id="time">{{cur}}</div>`,
data() {
return {
hz: 0.5,
cur: '...',
}
},
methods: {
update() {
this.cur = new Date().toLocaleDateString('de-DE', {
formatMatcher: 'best fit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
.split(',')[1]
.trim()
},
startWatcher() {
this.update()
this.watcherId = setInterval(this.update, this.hz * 1000)
},
stopWatcher() {
clearInterval(this.watcherId)
},
},
mounted() {
this.startWatcher()
},
})

View File

@ -0,0 +1,63 @@
Vue.component('dvb-weather', {
template: '#tmpl-dvb-weather',
props: {
offset: ['Number']
},
data() {
return {
hz: 10,
cur: '...',
images: {
'sunny': 11,
'rain': 4,
'snow': 6,
'clouds': 10,
'storm': 1,
'': 9,
},
states: {
'fog': 2,
'wind': 3,
'frosty': 8,
'wet': 12,
'cold': 7,
},
static: {
'0': {
image: 11,
temp: '23'
},
'14400': {
image: 4,
temp: '11'
},
'28800': {
image: 6,
temp: '-1'
},
'43200': {
image: 1,
temp: '7'
},
}
}
},
methods: {
startWatcher() {
this.update()
this.watcherId = setInterval(this.update, this.hz * 1000)
},
stopWatcher() {
clearInterval(this.watcherId)
},
update() {},
},
created() {
this.startWatcher()
},
mounted() {
const data = this.static[this.offset]
this.$el.style.backgroundImage = `url('res/img/${data.image}.png')`
this.cur = data.temp
},
})

BIN
design/DVB_Anzeige.pdf Normal file

Binary file not shown.

BIN
design/icons/single/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
design/icons/single/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
design/icons/single/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
design/icons/single/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
design/icons/single/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
design/icons/single/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
design/icons/single/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
design/icons/single/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
design/icons/single/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
design/icons/single/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
design/icons/single/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
design/icons/single/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
design/icons/weather.psd Normal file

Binary file not shown.