Merge pull request #31 from MichMich/pr/30

Merge of Pr/30
This commit is contained in:
Michael Teeuw 2015-10-30 12:38:59 +01:00
commit 77c9c5d006
12 changed files with 747 additions and 371 deletions

View File

@ -1,10 +1,47 @@
MagicMirror MagicMirror
=========== ===========
##Introduction
The super magic interface of my personal Magic Mirror. More information about this project can be found on my [blog](http://michaelteeuw.nl/tagged/magicmirror). The super magic interface of my personal Magic Mirror. More information about this project can be found on my [blog](http://michaelteeuw.nl/tagged/magicmirror).
Runs as a php script on a web server with basically no external dependencies. Can use socket.io for XBEE integration, but isn't required for basic functionality. Runs as a php script on a web server with basically no external dependencies. Can use socket.io for XBEE integration, but isn't required for basic functionality.
##Configuration
Modify js/config.js to change some general variables (language, wather location, compliments, news feed RSS) and calendar.php to add your own ICS calendar Modify js/config.js to change some general variables (language, wather location, compliments, news feed RSS) and calendar.php to add your own ICS calendar
To use the OpenWeatherMap API, you'll need a free API key. Checkout [this blogpost](http://michaelteeuw.nl/post/131504229357/what-happened-to-the-weather) for more information. To use the OpenWeatherMap API, you'll need a free API key. Checkout [this blogpost](http://michaelteeuw.nl/post/131504229357/what-happened-to-the-weather) for more information.
##Code
###[main.js](js/main.js)
This file initiates the separate pieces of functionality that will appear in the view. It also includes various utility functions that are used to update what is visible.
###[Calendar](js/calendar)
Parsing functionality for the Calendar that retrieves and updates the calendar based on the interval set at the top of the [calendar.js](js/calendar/calendar.js) file. This was actually a straight pull from the original main.js file but the parsing code may deserve an upgrade.
###[Compliments](js/compliments)
Functionality related to inserting compliments into the view and rotating them based on a specific interval set at the top of the [compliments.js](js/compliments/compliments.js) file.
###[News](js/news)
Takes an array of news feeds (or a single string) from the config file and retrieves each one so that it can be displayed in a loop based on the interval set at the top of the [news.js](js/news/news.js) file.
###[Time](js/time)
Updates the time on the screen on one second interval.
###[Version](js/version)
Checks the git version and refreshes if a new version has been pulled.
###[Weather](js/weather)
Takes the user's inserted location, language, unit type, and OpenWeatherMap API key and grabs the five day weather forecast from OpenWeatherMap. You need to set the API key in the config for this to work. (See *configuration*.)

View File

@ -24,9 +24,15 @@
<script src="js/jquery.js"></script> <script src="js/jquery.js"></script>
<script src="js/jquery.feedToJSON.js"></script> <script src="js/jquery.feedToJSON.js"></script>
<script src="js/ical_parser.js"></script> <script src="js/ical_parser.js"></script>
<script src="js/moment-with-langs.min.js"></script> <script src="js/moment-with-locales.min.js"></script>
<script src="js/config.js"></script> <script src="js/config.js"></script>
<script src="js/rrule.js"></script> <script src="js/rrule.js"></script>
<script src="js/version/version.js" type="text/javascript"></script>
<script src="js/calendar/calendar.js" type="text/javascript"></script>
<script src="js/compliments/compliments.js" type="text/javascript"></script>
<script src="js/weather/weather.js" type="text/javascript"></script>
<script src="js/time/time.js" type="text/javascript"></script>
<script src="js/news/news.js" type="text/javascript"></script>
<script src="js/main.js?nocache=<?php echo md5(microtime()) ?>"></script> <script src="js/main.js?nocache=<?php echo md5(microtime()) ?>"></script>
<!-- <script src="js/socket.io.min.js"></script> --> <!-- <script src="js/socket.io.min.js"></script> -->

132
js/calendar/calendar.js Normal file
View File

@ -0,0 +1,132 @@
var calendar = {
eventList: [],
calendarLocation: '.calendar',
updateInterval: 1000,
updateDataInterval: 60000,
fadeInterval: 1000,
intervalId: null,
dataIntervalId: null
}
calendar.updateData = function (callback) {
new ical_parser("calendar.php", function(cal) {
var events = cal.getEvents();
this.eventList = [];
for (var i in events) {
var e = events[i];
for (var key in e) {
var value = e[key];
var seperator = key.search(';');
if (seperator >= 0) {
var mainKey = key.substring(0,seperator);
var subKey = key.substring(seperator+1);
var dt;
if (subKey == 'VALUE=DATE') {
//date
dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8));
} else {
//time
dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8), value.substring(9,11), value.substring(11,13), value.substring(13,15));
}
if (mainKey == 'DTSTART') e.startDate = dt;
if (mainKey == 'DTEND') e.endDate = dt;
}
}
if (e.startDate == undefined){
//some old events in Gmail Calendar is "start_date"
//FIXME: problems with Gmail's TimeZone
var days = moment(e.DTSTART).diff(moment(), 'days');
var seconds = moment(e.DTSTART).diff(moment(), 'seconds');
var startDate = moment(e.DTSTART);
} else {
var days = moment(e.startDate).diff(moment(), 'days');
var seconds = moment(e.startDate).diff(moment(), 'seconds');
var startDate = moment(e.startDate);
}
//only add fututre events, days doesn't work, we need to check seconds
if (seconds >= 0) {
if (seconds <= 60*60*5 || seconds >= 60*60*24*2) {
var time_string = moment(startDate).fromNow();
}else {
var time_string = moment(startDate).calendar()
}
if (!e.RRULE) {
this.eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string});
}
e.seconds = seconds;
}
// Special handling for rrule events
if (e.RRULE) {
var options = new RRule.parseString(e.RRULE);
options.dtstart = e.startDate;
var rule = new RRule(options);
// TODO: don't use fixed end date here, use something like now() + 1 year
var dates = rule.between(new Date(), new Date(2016,11,31), true, function (date, i){return i < 10});
for (date in dates) {
var dt = new Date(dates[date]);
var days = moment(dt).diff(moment(), 'days');
var seconds = moment(dt).diff(moment(), 'seconds');
var startDate = moment(dt);
if (seconds >= 0) {
if (seconds <= 60*60*5 || seconds >= 60*60*24*2) {
var time_string = moment(dt).fromNow();
} else {
var time_string = moment(dt).calendar()
}
this.eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string});
}
}
}
};
this.eventList = this.eventList.sort(function(a,b){return a.seconds-b.seconds});
if (callback !== undefined && Object.prototype.toString.call(callback) === '[object Function]') {
callback(this.eventList);
}
}.bind(this));
}
calendar.updateCalendar = function (eventList) {
table = $('<table/>').addClass('xsmall').addClass('calendar-table');
opacity = 1;
for (var i in eventList) {
var e = eventList[i];
var row = $('<tr/>').css('opacity',opacity);
row.append($('<td/>').html(e.description).addClass('description'));
row.append($('<td/>').html(e.days).addClass('days dimmed'));
table.append(row);
opacity -= 1 / eventList.length;
}
$(this.calendarLocation).updateWithText(table, this.fadeInterval);
}
calendar.init = function () {
this.updateData(this.updateCalendar.bind(this));
this.intervalId = setInterval(function () {
this.updateCalendar(this.eventList)
}.bind(this), this.updateInterval);
this.dataIntervalId = setInterval(function () {
this.updateData(this.updateCalendar.bind(this));
}.bind(this), this.updateDataInterval);
}

View File

@ -0,0 +1,66 @@
var compliments = {
complimentLocation: '.compliment',
currentCompliment: '',
complimentList: {
'morning': config.compliments.morning,
'afternoon': config.compliments.afternoon,
'evening': config.compliments.evening
},
updateInterval: config.compliments.interval || 30000,
fadeInterval: config.compliments.fadeInterval || 4000,
intervalId: null
};
/**
* Changes the compliment visible on the screen
*/
compliments.updateCompliment = function () {
var _list = [];
var hour = moment().hour();
if (hour >= 3 && hour < 12) {
// Morning compliments
_list = compliments.complimentList['morning'];
} else if (hour >= 12 && hour < 17) {
// Afternoon compliments
_list = compliments.complimentList['afternoon'];
} else if (hour >= 17 || hour < 3) {
// Evening compliments
_list = compliments.complimentList['evening'];
} else {
// Edge case in case something weird happens
// This will select a compliment from all times of day
Object.keys(compliments.complimentList).forEach(function (_curr) {
_list = _list.concat(compliments.complimentList[_curr]);
});
}
// Search for the location of the current compliment in the list
var _spliceIndex = _list.indexOf(compliments.currentCompliment);
// If it exists, remove it so we don't see it again
if (_spliceIndex !== -1) {
_list = _list.slice(_spliceIndex, 1);
}
// Randomly select a location
var _location = Math.floor(Math.random() * _list.length);
compliments.currentCompliment = _list[_location];
$('.compliment').updateWithText(compliments.currentCompliment, compliments.fadeInterval);
}
compliments.init = function () {
this.updateCompliment();
this.intervalId = setInterval(function () {
this.updateCompliment();
}.bind(this), this.updateInterval)
}

View File

@ -1,37 +1,39 @@
// for navigator language var config = {
// var lang = window.navigator.language; lang: 'nl',
// you can change the language time: {
var lang = 'nl'; timeFormat: 12
},
weather: {
//change weather params here: //change weather params here:
//units: metric or imperial //units: metric or imperial
var weatherParams = { params: {
'q':'Baarn,Netherlands', q: 'Baarn,Netherlands',
'units':'metric', units: 'metric',
'lang':lang, // if you want a different lang for the weather that what is set above, change it here
'APPID':'YOUR_FREE_OPENWEATHER_API_KEY' lang: 'nl',
}; APPID: 'YOUR_FREE_OPENWEATHER_API_KEY'
}
var feed = 'http://feeds.nos.nl/nosjournaal?format=rss'; },
//var feed = 'http://www.nu.nl/feeds/rss/achterklap.rss'; compliments: {
//var feed = 'http://www.nu.nl/feeds/rss/opmerkelijk.rss'; interval: 2000,
//var feed = 'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'; fadeInterval: 4000,
morning: [
// compliments:
var morning = [
'Good morning, handsome!', 'Good morning, handsome!',
'Enjoy your day!', 'Enjoy your day!',
'How was your sleep?' 'How was your sleep?'
]; ],
afternoon: [
var afternoon = [
'Hello, beauty!', 'Hello, beauty!',
'You look sexy!', 'You look sexy!',
'Looking good today!' 'Looking good today!'
]; ],
evening: [
var evening = [
'Wow, you look hot!', 'Wow, you look hot!',
'You look nice!', 'You look nice!',
'Hi, sexy!' 'Hi, sexy!'
]; ]
},
news: {
feed: 'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
}
}

View File

@ -24,29 +24,14 @@ function roundVal(temp)
return Math.round(temp * 10) / 10; return Math.round(temp * 10) / 10;
} }
function kmh2beaufort(kmh)
{
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
}
jQuery(document).ready(function($) { jQuery(document).ready(function($) {
var news = [];
var newsIndex = 0;
var eventList = []; var eventList = [];
var lastCompliment; var lastCompliment;
var compliment; var compliment;
moment.lang(lang); moment.locale(config.lang);
//connect do Xbee monitor //connect do Xbee monitor
// var socket = io.connect('http://rpi-alarm.local:8082'); // var socket = io.connect('http://rpi-alarm.local:8082');
@ -60,327 +45,16 @@ jQuery(document).ready(function($) {
// } // }
// }); // });
version.init();
(function checkVersion() time.init();
{
$.getJSON('githash.php', {}, function(json, textStatus) {
if (json) {
if (json.gitHash != gitHash) {
window.location.reload();
window.location.href=window.location.href;
}
}
});
setTimeout(function() {
checkVersion();
}, 3000);
})();
(function updateTime() calendar.init();
{
var now = moment();
var date = now.format('LLLL').split(' ',4);
date = date[0] + ' ' + date[1] + ' ' + date[2] + ' ' + date[3];
$('.date').html(date); compliments.init();
$('.time').html(now.format('HH') + ':' + now.format('mm') + '<span class="sec">'+now.format('ss')+'</span>');
setTimeout(function() { weather.init();
updateTime();
}, 1000);
})();
(function updateCalendarData() news.init();
{
new ical_parser("calendar.php", function(cal){
events = cal.getEvents();
eventList = [];
for (var i in events) {
var e = events[i];
for (var key in e) {
var value = e[key];
var seperator = key.search(';');
if (seperator >= 0) {
var mainKey = key.substring(0,seperator);
var subKey = key.substring(seperator+1);
var dt;
if (subKey == 'VALUE=DATE') {
//date
dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8));
} else {
//time
dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8), value.substring(9,11), value.substring(11,13), value.substring(13,15));
}
if (mainKey == 'DTSTART') e.startDate = dt;
if (mainKey == 'DTEND') e.endDate = dt;
}
}
if (e.startDate == undefined){
//some old events in Gmail Calendar is "start_date"
//FIXME: problems with Gmail's TimeZone
var days = moment(e.DTSTART).diff(moment(), 'days');
var seconds = moment(e.DTSTART).diff(moment(), 'seconds');
var startDate = moment(e.DTSTART);
} else {
var days = moment(e.startDate).diff(moment(), 'days');
var seconds = moment(e.startDate).diff(moment(), 'seconds');
var startDate = moment(e.startDate);
}
//only add fututre events, days doesn't work, we need to check seconds
if (seconds >= 0) {
if (seconds <= 60*60*5 || seconds >= 60*60*24*2) {
var time_string = moment(startDate).fromNow();
}else {
var time_string = moment(startDate).calendar()
}
if (!e.RRULE) {
eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string});
}
e.seconds = seconds;
}
// Special handling for rrule events
if (e.RRULE) {
var options = new RRule.parseString(e.RRULE);
options.dtstart = e.startDate;
var rule = new RRule(options);
// TODO: don't use fixed end date here, use something like now() + 1 year
var dates = rule.between(new Date(), new Date(2016,11,31), true, function (date, i){return i < 10});
for (date in dates) {
var dt = new Date(dates[date]);
var days = moment(dt).diff(moment(), 'days');
var seconds = moment(dt).diff(moment(), 'seconds');
var startDate = moment(dt);
if (seconds >= 0) {
if (seconds <= 60*60*5 || seconds >= 60*60*24*2) {
var time_string = moment(dt).fromNow();
} else {
var time_string = moment(dt).calendar()
}
eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string});
}
}
}
};
eventList.sort(function(a,b){return a.seconds-b.seconds});
setTimeout(function() {
updateCalendarData();
}, 60000);
});
})();
(function updateCalendar()
{
table = $('<table/>').addClass('xsmall').addClass('calendar-table');
opacity = 1;
for (var i in eventList) {
var e = eventList[i];
var row = $('<tr/>').css('opacity',opacity);
row.append($('<td/>').html(e.description).addClass('description'));
row.append($('<td/>').html(e.days).addClass('days dimmed'));
table.append(row);
opacity -= 1 / eventList.length;
}
$('.calendar').updateWithText(table,1000);
setTimeout(function() {
updateCalendar();
}, 1000);
})();
(function updateCompliment()
{
//see compliments.js
while (compliment == lastCompliment) {
//Check for current time
var compliments;
var date = new Date();
var hour = date.getHours();
//set compliments to use
if (hour >= 3 && hour < 12) compliments = morning;
if (hour >= 12 && hour < 17) compliments = afternoon;
if (hour >= 17 || hour < 3) compliments = evening;
compliment = Math.floor(Math.random()*compliments.length);
}
$('.compliment').updateWithText(compliments[compliment], 4000);
lastCompliment = compliment;
setTimeout(function() {
updateCompliment(true);
}, 30000);
})();
(function updateCurrentWeather()
{
var iconTable = {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
}
$.getJSON('http://api.openweathermap.org/data/2.5/weather', weatherParams, function(json, textStatus) {
var temp = roundVal(json.main.temp);
var temp_min = roundVal(json.main.temp_min);
var temp_max = roundVal(json.main.temp_max);
var wind = roundVal(json.wind.speed);
var iconClass = iconTable[json.weather[0].icon];
var icon = $('<span/>').addClass('icon').addClass('dimmed').addClass('wi').addClass(iconClass);
$('.temp').updateWithText(icon.outerHTML()+temp+'&deg;', 1000);
// var forecast = 'Min: '+temp_min+'&deg;, Max: '+temp_max+'&deg;';
// $('.forecast').updateWithText(forecast, 1000);
var now = new Date();
var sunrise = new Date(json.sys.sunrise*1000).toTimeString().substring(0,5);
var sunset = new Date(json.sys.sunset*1000).toTimeString().substring(0,5);
var windString = '<span class="wi wi-strong-wind xdimmed"></span> ' + kmh2beaufort(wind) ;
var sunString = '<span class="wi wi-sunrise xdimmed"></span> ' + sunrise;
if (json.sys.sunrise*1000 < now && json.sys.sunset*1000 > now) {
sunString = '<span class="wi wi-sunset xdimmed"></span> ' + sunset;
}
$('.windsun').updateWithText(windString+' '+sunString, 1000);
});
setTimeout(function() {
updateCurrentWeather();
}, 60000);
})();
(function updateWeatherForecast()
{
var iconTable = {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
}
$.getJSON('http://api.openweathermap.org/data/2.5/forecast', weatherParams, function(json, textStatus) {
var forecastData = {};
for (var i in json.list) {
var forecast = json.list[i];
var dateKey = forecast.dt_txt.substring(0, 10);
if (forecastData[dateKey] == undefined) {
forecastData[dateKey] = {
'timestamp':forecast.dt * 1000,
'icon':forecast.weather[0].icon,
'temp_min':forecast.main.temp,
'temp_max':forecast.main.temp
};
} else {
forecastData[dateKey]['icon'] = forecast.weather[0].icon;
forecastData[dateKey]['temp_min'] = (forecast.main.temp < forecastData[dateKey]['temp_min']) ? forecast.main.temp : forecastData[dateKey]['temp_min'];
forecastData[dateKey]['temp_max'] = (forecast.main.temp > forecastData[dateKey]['temp_max']) ? forecast.main.temp : forecastData[dateKey]['temp_max'];
}
}
var forecastTable = $('<table />').addClass('forecast-table');
var opacity = 1;
for (var i in forecastData) {
var forecast = forecastData[i];
var iconClass = iconTable[forecast.icon];
var dt = new Date(forecast.timestamp);
var row = $('<tr />').css('opacity', opacity);
row.append($('<td/>').addClass('day').html(moment.weekdaysShort(dt.getDay())));
row.append($('<td/>').addClass('icon-small').addClass(iconClass));
row.append($('<td/>').addClass('temp-max').html(roundVal(forecast.temp_max)));
row.append($('<td/>').addClass('temp-min').html(roundVal(forecast.temp_min)));
forecastTable.append(row);
opacity -= 0.155;
}
$('.forecast').updateWithText(forecastTable, 1000);
});
setTimeout(function() {
updateWeatherForecast();
}, 60000);
})();
(function fetchNews() {
$.feedToJson({
feed: feed,
success: function(data){
news = [];
for (var i in data.item) {
var item = data.item[i];
news.push(item.title);
}
}
});
setTimeout(function() {
fetchNews();
}, 60000);
})();
(function showNews() {
var newsItem = news[newsIndex];
$('.news').updateWithText(newsItem,2000);
newsIndex--;
if (newsIndex < 0) newsIndex = news.length - 1;
setTimeout(function() {
showNews();
}, 5500);
})();
}); });

File diff suppressed because one or more lines are too long

80
js/moment-with-locales.min.js vendored Normal file

File diff suppressed because one or more lines are too long

152
js/news/news.js Normal file
View File

@ -0,0 +1,152 @@
// A lot of this code is from the original feedToJson function that was included with this project
// The new code allows for multiple feeds to be used but a bunch of variables and such have literally been copied and pasted into this code and some help from here: http://jsfiddle.net/BDK46/
// The original version can be found here: http://airshp.com/2011/jquery-plugin-feed-to-json/
var news = {
feed: config.news.feed || null,
newsLocation: '.news',
newsItems: [],
seenNewsItem: [],
_yqURL: 'http://query.yahooapis.com/v1/public/yql',
_yqlQS: '?format=json&q=select%20*%20from%20rss%20where%20url%3D',
_cacheBuster: Math.floor((new Date().getTime()) / 1200 / 1000),
_failedAttempts: 0,
fetchInterval: config.news.fetchInterval || 60000,
updateInterval: config.news.interval || 5500,
fadeInterval: 2000,
intervalId: null,
fetchNewsIntervalId: null
}
/**
* Creates the query string that will be used to grab a converted RSS feed into a JSON object via Yahoo
* @param {string} feed The original location of the RSS feed
* @return {string} The new location of the RSS feed provided by Yahoo
*/
news.buildQueryString = function (feed) {
return this._yqURL + this._yqlQS + '\'' + encodeURIComponent(feed) + '\'';
}
/**
* Fetches the news for each feed provided in the config file
*/
news.fetchNews = function () {
// Reset the news feed
this.newsItems = [];
this.feed.forEach(function (_curr) {
var _yqUrlString = this.buildQueryString(_curr);
this.fetchFeed(_yqUrlString);
}.bind(this));
}
/**
* Runs a GET request to Yahoo's service
* @param {string} yqUrl The URL being used to grab the RSS feed (in JSON format)
*/
news.fetchFeed = function (yqUrl) {
$.ajax({
type: 'GET',
datatype:'jsonp',
url: yqUrl,
success: function (data) {
if (data.query.count > 0) {
this.parseFeed(data.query.results.item);
} else {
console.error('No feed results for: ' + yqUrl);
}
}.bind(this),
error: function () {
// non-specific error message that should be updated
console.error('No feed results for: ' + yqUrl);
}
});
}
/**
* Parses each item in a single news feed
* @param {Object} data The news feed that was returned by Yahoo
* @return {boolean} Confirms that the feed was parsed correctly
*/
news.parseFeed = function (data) {
var _rssItems = [];
for (var i = 0, count = data.length; i < count; i++) {
_rssItems.push(data[i].title);
}
this.newsItems = this.newsItems.concat(_rssItems);
return true;
}
/**
* Loops through each available and unseen news feed after it has been retrieved from Yahoo and shows it on the screen
* When all news titles have been exhausted, the list resets and randomly chooses from the original set of items
* @return {boolean} Confirms that there is a list of news items to loop through and that one has been shown on the screen
*/
news.showNews = function () {
// If all items have been seen, swap seen to unseen
if (this.newsItems.length === 0 && this.seenNewsItem.length !== 0) {
if (this._failedAttempts === 20) {
console.error('Failed to show a news story 20 times, stopping any attempts');
return false;
}
this._failedAttempts++;
setTimeout(function () {
this.showNews();
}.bind(this), 3000);
} else if (this.newsItems.length === 0 && this.seenNewsItem.length !== 0) {
this.newsItems = this.seenNewsItem.splice(0);
}
var _location = Math.floor(Math.random() * this.newsItems.length);
var _item = news.newsItems.splice(_location, 1)[0];
this.seenNewsItem.push(_item);
$(this.newsLocation).updateWithText(_item, this.fadeInterval);
return true;
}
news.init = function () {
if (this.feed === null || (this.feed instanceof Array === false && typeof this.feed !== 'string')) {
return false;
} else if (typeof this.feed === 'string') {
this.feed = [this.feed];
}
this.fetchNews();
this.showNews();
this.fetchNewsIntervalId = setInterval(function () {
this.fetchNews()
}.bind(this), this.fetchInterval)
this.intervalId = setInterval(function () {
this.showNews();
}.bind(this), this.updateInterval);
}

34
js/time/time.js Normal file
View File

@ -0,0 +1,34 @@
var time = {
timeFormat: config.time.timeFormat || 24,
dateLocation: '.date',
timeLocation: '.time',
updateInterval: 1000,
intervalId: null
};
/**
* Updates the time that is shown on the screen
*/
time.updateTime = function () {
var _now = moment(),
_date = _now.format('dddd, LL');
$(this.dateLocation).html(_date);
$(this.timeLocation).html(_now.format(this._timeFormat+':mm[<span class="sec">]ss[</span>]'));
}
time.init = function () {
if (parseInt(time.timeFormat) === 12) {
time._timeFormat = 'hh'
} else {
time._timeFormat = 'HH';
}
this.intervalId = setInterval(function () {
this.updateTime();
}.bind(this), 1000);
}

34
js/version/version.js Normal file
View File

@ -0,0 +1,34 @@
var version = {
updateInterval: 600000,
intervalId: null
}
/**
* Checks the version and refreshes the page if a new version has been pulled
*/
version.checkVersion = function () {
$.ajax({
type: 'GET',
url: 'githash.php',
success: function (data) {
// The githash variable is located in index.php
if (data && data.gitHash !== gitHash) {
window.location.reload();
window.location.href = window.location.href;
}
},
error: function () {
}
});
}
version.init = function () {
this.intervalId = setInterval(function () {
this.checkVersion();
}.bind(this), this.updateInterval);
}

168
js/weather/weather.js Normal file
View File

@ -0,0 +1,168 @@
var weather = {
// Default language is Dutch because that is what the original author used
lang: config.lang || 'nl',
params: config.weather.params || null,
iconTable: {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
},
temperatureLocation: '.temp',
windSunLocation: '.windsun',
forecastLocation: '.forecast',
apiVersion: '2.5',
apiBase: 'http://api.openweathermap.org/data/',
weatherEndpoint: 'weather',
forecastEndpoint: 'forecast/daily',
updateInterval: config.weather.interval || 6000,
fadeInterval: config.weather.fadeInterval || 1000,
intervalId: null
}
/**
* Rounds a float to one decimal place
* @param {float} temperature The temperature to be rounded
* @return {float} The new floating point value
*/
weather.roundValue = function (temperature) {
return parseFloat(temperature).toFixed(1);
}
/**
* Converts the wind speed (km/h) into the values given by the Beaufort Wind Scale
* @see http://www.spc.noaa.gov/faq/tornado/beaufort.html
* @param {int} kmh The wind speed in Kilometers Per Hour
* @return {int} The wind speed converted into its corresponding Beaufort number
*/
weather.kmh2Beaufort = function(kmh) {
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
}
/**
* Retrieves the current temperature and weather patter from the OpenWeatherMap API
*/
weather.updateCurrentWeather = function () {
$.ajax({
type: 'GET',
url: weather.apiBase + '/' + weather.apiVersion + '/' + weather.weatherEndpoint,
dataType: 'json',
data: weather.params,
success: function (data) {
var _temperature = this.roundValue(data.main.temp),
_temperatureMin = this.roundValue(data.main.temp_min),
_temperatureMax = this.roundValue(data.main.temp_max),
_wind = this.roundValue(data.wind.speed),
_iconClass = this.iconTable[data.weather[0].icon];
var _icon = '<span class="icon ' + _iconClass + ' dimmed wi"></span>';
var _newTempHtml = _icon + '' + _temperature + '&deg;';
$(this.temperatureLocation).updateWithText(_newTempHtml, this.fadeInterval);
var _now = moment(),
_sunrise = moment(data.sys.sunrise*1000).format('HH:mm'),
_sunset = moment(data.sys.sunset*1000).format('HH:mm');
var _newWindHtml = '<span class="wi wi-strong-wind xdimmed"></span> ' + this.kmh2Beaufort(_wind),
_newSunHtml = '<span class="wi wi-sunrise xdimmed"></span> ' + _sunrise;
if (_sunrise < _now && _sunset > now) {
_newSunHtml = '<span class="wi wi-sunset xdimmed"></span> ' + _sunset;
}
$(this.windSunLocation).updateWithText(_newWindHtml + ' ' + _newSunHtml, this.fadeInterval);
}.bind(this),
error: function () {
}
});
}
/**
* Updates the 5 Day Forecast from the OpenWeatherMap API
*/
weather.updateWeatherForecast = function () {
$.ajax({
type: 'GET',
url: weather.apiBase + '/' + weather.apiVersion + '/' + weather.forecastEndpoint,
data: weather.params,
success: function (data) {
var _opacity = 1,
_forecastHtml = '';
_forecastHtml += '<table class="forecast-table">';
for (var i = 0, count = data.list.length; i < count; i++) {
var _forecast = data.list[i];
_forecastHtml += '<tr style="opacity:' + _opacity + '">';
_forecastHtml += '<td class="day">' + moment(_forecast.dt, 'X').format('ddd') + '</td>';
_forecastHtml += '<td class="icon-small ' + this.iconTable[_forecast.weather[0].icon] + '"></td>';
_forecastHtml += '<td class="temp-max">' + this.roundValue(_forecast.temp.max) + '</td>';
_forecastHtml += '<td class="temp-min">' + this.roundValue(_forecast.temp.min) + '</td>';
_forecastHtml += '</tr>';
_opacity -= 0.155;
}
_forecastHtml += '</table>';
$(this.forecastLocation).updateWithText(_forecastHtml, this.fadeInterval);
}.bind(this),
error: function () {
}
});
}
weather.init = function () {
if (this.params.lang === undefined) {
this.params.lang = this.lang;
}
if (this.params.cnt === undefined) {
this.params.cnt = 5;
}
this.intervalId = setInterval(function () {
this.updateCurrentWeather();
this.updateWeatherForecast();
}.bind(this), this.updateInterval);
}