<!DOCTYPE html>
<html lang="{{language}}">
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8">
<head>
<script type='text/javascript' src='{{dirprefix}}script/jquery.js'></script>
<script type='text/javascript' src='{{dirprefix}}modules/locationinfo/frontend/frontendscript.js'></script>
<style type='text/css'>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: lightgrey;
color: black;
}
#main {
display: flex;
flex-wrap: wrap;
}
.outermost {
font-size: 16pt;
}
.parent, .child {
padding: 5px;
float: left;
background-color: white;
font-size: 90%;
min-height: 7em;
flex-grow: 1;
align-items: stretch;
}
.parent .parent, .parent .child {
min-height: 5em;
min-width: 90px;
}
.border {
flex-grow: 1;
display: inline-flex;
align-items: stretch;
padding: 5px;
}
.courseFont {
padding: 2px;
font-size: 90%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: bold;
overflow: hidden;
}
.headerFont {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: bold;
border: 0px;
border-bottom: 1px;
margin-bottom: 1px;
border-color: grey;
border-style: solid;
}
.pc-idle, .pc-occupied, .pc-offline, .pc-broken, .pc-standby {
padding: 2px 1px;
text-align: center;
font-size: 90%;
font-weight: 800;
overflow: hidden;
transition: width 2s;
width: 25%;
}
.pc-idle {
background-color: green;
}
.pc-occupied {
background-color: red;
border-radius: 3px 0px 0px 3px;
}
.pc-offline {
background-color: black;
color: white;
}
.pc-standby {
background-color: darkgreen;
}
.pc-broken {
background-color: darkgrey;
border-radius: 0px 3px 3px 0px;
}
.pc-state-wrapper {
display: flex;
}
.paperEffect {
margin: 0 auto;
background-color: #fff;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 10px rgba(0, 0, 0, 0.1) inset;
box-shadow: 0 0 0.2vmin rgba(0, 0, 0, 0.4), 0 0 1vmin rgba(0, 0, 0, 0.1) inset;
border-radius: 1px;
}
#i18n {
display: none;
}
</style>
<script type='text/javascript'>
var rooms = {};
var startdate;
var roomidsString = "";
var config = {{{config}}};
var lastPanelUpdate = 0;
$(document).ready(function () {
init();
});
function init() {
var time = false;
if (config.time) {
var p = config.time.split('-');
if (p.length === 6) {
time = new Date(p[0], (p[1] - 1), p[2], p[3], p[4], p[5]);
console.log(time);
}
if (time === false || isNaN(time.getTime()) || time.getFullYear() < 2010) {
time = new Date(config.time);
}
if (!time || isNaN(time.getTime()) || time.getFullYear() < 2010) {
time = new Date();
}
}
SetUpDate(time);
generateLayout(config.tree);
update();
setInterval(update, 10000);
}
function SetUpDate(d) {
startdate = d.getTime() - new Date().getTime();
}
function MyDate() {
return new Date(startdate + new Date().getTime());
}
function generateLayout(json) {
for (var i = 0; i < json.length; i++) {
console.log('Outermost for ' + json[i].locationid);
var el = generateObject(json[i], ($("#main")), true);
}
}
/**
* generates the divs, decides if parent or child
* @param json Room tree json
* @param myParent parent div
* @param outermost if the object is a root node
* @returns generated div
*/
function generateObject(json, myParent, outermost) {
var obj;
if (!json.children || json.children.length === 0) {
obj = generateChild(myParent, json.locationid, json.locationname, outermost);
} else {
obj = generateParent(myParent, json.locationid, json.locationname, outermost);
for (var i = 0; i < json.children.length; i++) {
generateObject(json.children[i], $("#parent_" + json.locationid), false);
}
}
return obj;
}
/**
* Main Update loop, this loop runs every 10 seconds
*/
function update() {
var date = MyDate();
var now = date.getTime();
if (lastPanelUpdate + (config.panelupdate * 1000) < now) {
// Set Roomupdate Interval has passed, update.
queryRooms();
queryCalendars();
lastPanelUpdate = now;
for (var property in rooms) {
rooms[property].lastCalendarUpdate = now;
rooms[property].lastRoomUpdate = now;
}
} else {
// Set Roomupdate Interval has NOT passed, check if panel was changed since last call and reload if true.
queryPanelChange();
}
}
function cleanDate(d) {
if (typeof d === 'string') {
// if is numeric
if (!isNaN(Number(d))) {
return cleanDate(parseInt(d, 10));
}
// this is a human readable date
if (d[d.length - 1] !== 'Z') d += 'Z';
var o = new Date(d);
o.setTime(o.getTime() + (o.getTimezoneOffset() * 60 * 1000));
return o;
}
if (typeof d === 'number') {
return new Date(d);
}
return d;
}
function UpdateTimeTables(json) {
var l = json.length;
for (var i = 0; i < l; i++) {
if (rooms[json[i].id] == null) {
continue;
}
rooms[json[i].id].timetable = json[i].calendar;
for (var property in rooms[json[i].id].timetable) {
rooms[json[i].id].timetable[property].start = cleanDate(rooms[json[i].id].timetable[property].start);
rooms[json[i].id].timetable[property].end = cleanDate(rooms[json[i].id].timetable[property].end);
}
ComputeCurrentState(rooms[json[i].id]);
}
for (property in rooms) {
upDateRoomState(rooms[property]);
}
}
/**
* Querys Pc states
* Room are queried with the {{uuid}} of the panel.
*/
function queryRooms() {
$.ajax({
url: "{{dirprefix}}api.php?do=locationinfo&get=pcstates&uuid={{uuid}}",
dataType: 'json',
cache: false,
timeout: 30000,
success: function (result) {
if (result[0] == null) {
console.log("Error: Backend reported null back for RoomUpdate, this might happend if the room isn't" +
"configurated.");
return;
}
updatePcStates(result);
}, error: function () {
}
})
}
/**
* Updates a room visualy
* @param room A room to update
*/
function upDateRoomState(room) {
if (room === undefined || room.lastRoomUpdate === null) {
return;
}
var state = room.getState();
if (state.state == "CalendarEvent") {
updateCourseText(room.id, state.title);
updateCoursTimer(room.id, GetTimeDiferenceAsString(state.end, MyDate()));
} else if (state.state == "Free") {
updateCourseText(room.id, t("free"));
updateCoursTimer(room.id, GetTimeDiferenceAsString(state.end, MyDate()));
} else if (state.state == "FreeNoEnd") {
updateCourseText(room.id, t("free"));
updateCoursTimer(room.id, "");
} else if (state.state == "closed") {
updateCourseText(room.id, t("closed"));
updateCoursTimer(room.id, "");
}
}
/**
* Updates for all rooms the PC's states
* @param json Json with information about the PC's states
*/
function updatePcStates(json) {
var l = json.length;
for (var i = 0; i < l; i++) {
updateRoomUsage(json[i].id, json[i].idle, json[i].occupied, json[i].offline, json[i].broken, json[i].standby);
}
}
const OT_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const OT_KEYS = ['HourOpen', 'HourClose', 'MinutesOpen', 'MinutesClose'];
/**
* Generates a room Object and adds it to the rooms array
* @param id ID of the room
* @param name Name of the room
* @param config Config Json of the room
*/
function addRoom(id, name) {
var ot = [];
if (config && config.locations) {
for (var i = 0; i < config.locations.length; ++i) {
if (config.locations[i].id == id) {
// TODO: Messed up transformation from default panel
if (config.locations[i].openingtime) {
var raw_ot = config.locations[i].openingtime;
for (var j = 0; j < OT_DAYS.length; ++j) {
ot.push(filterOpeningTimesDay(raw_ot[OT_DAYS[j]]));
}
}
}
}
}
var room = {
id: id,
name: name,
timetable: null,
currentEvent: null,
nextEventEnd: null,
timeTilFree: null,
state: null,
openingTimes: ot,
lastCalendarUpdate: null,
lastRoomUpdate: null,
getState: function () {
if (!this.state) {
ComputeCurrentState(this);
return this.state;
}
if (this.state.end != "") {
if (this.state.end < new MyDate()) {
ComputeCurrentState(this);
}
}
return this.state;
}
};
rooms[id] = room;
if (roomidsString == "") {
roomidsString = id;
} else {
roomidsString = roomidsString + "," + id;
}
}
/**
* Filter out invalid opening time entries from given array,
* also make sure all the values are of type number (int)
*
* @param {Array} arr
* @return {Array} list of valid opening times
*/
function filterOpeningTimesDay(arr) {
if (!arr || arr.constructor !== Array) return [];
return arr.map(function (el) {
if (!el || typeof el !== 'object') return null;
for (var i = 0; i < OT_KEYS.length; ++i) {
el[OT_KEYS[i]] = toInt(el[OT_KEYS[i]]);
if (isNaN(el[OT_KEYS[i]])) return null;
}
return el;
}).filter(function (el) {
if (!el) return false;
if (el.HourOpen < 0 || el.HourOpen > 23) return false;
if (el.HourClose < 0 || el.HourClose > 23) return false;
if (el.HourClose < el.HourOpen) return false;
if (el.MinutesOpen < 0 || el.MinutesOpen > 59) return false;
if (el.MinutesClose < 0 || el.MinutesClose > 59) return false;
if (el.HourOpen === el.HourClose && el.MinutesClose < el.MinutesOpen) return false;
return true;
});
}
/**
* computes state of a room, states are:
* closed, FreeNoEnd, Free, CalendarEvent.
* @param room Object
*/
function ComputeCurrentState(room) {
if (!IsOpen(MyDate(), room)) {
room.state = {state: "closed", end: GetNextOpening(room), title: "", next: ""};
return;
}
console.log("Timetable", room.id, room.timetable);
var closing = GetNextClosing(room);
var event = getNextEvent(room.timetable);
console.log("Event", room.id, event);
// no event and no closing
if (!closing && !event) {
room.state = {state: "FreeNoEnd", end: "", title: "", next: "", free: true};
return;
}
// no event so closing is next
if (!event) {
room.state = {state: "Free", end: closing, title: "", next: "closing", free: true};
return;
}
// event is at the moment
if ((!closing || event.start.getTime() < closing.getTime()) && event.start.getTime() < MyDate()) {
room.state = {
state: "CalendarEvent",
end: event.end,
title: event.title,
next: ""
};
console.log("CalendarEvent", room.id, room.state);
return;
}
// no closing so event is next
if (!closing) {
room.state = {state: "Free", end: event.start, title: "", next: "event", free: true};
return;
}
// event sooner then closing
if (event.start.getTime() < closing) {
room.state = {state: "Free", end: event.start, title: "", next: "event", free: true};
} else {
room.state = {state: "Free", end: closing, title: "", next: "closing", free: true};
}
}
/**
* returns next event from a given json of events
* @param calEvents Json which contains the calendar data.
* @returns event next Calendar Event
*/
function getNextEvent(calEvents) {
if (!calEvents) return null;
if (calEvents.constructor !== Array) {
console.log('getNextEvent called with something not array: ' + typeof(calEvents));
return null;
}
var event = null;
var now = MyDate();
for (var i = 0; i < calEvents.length; i++) {
//event is now active
if (calEvents[i].start.getTime() < now.getTime() && calEvents[i].end.getTime() > now.getTime()) {
return calEvents[i];
}
//first element to consider
if (!event) {
if (calEvents[i].start.getTime() > now.getTime()) {
event = calEvents[i];
}
} else if (calEvents[i].start.getTime() > now.getTime() && event.start.getTime() > calEvents[i].start.getTime()) {
event = calEvents[i];
}
}
return event;
}
/**
* Retruns next Opening
* @param room Room Object
* @returns bestdate Date Object of next opening
*/
function GetNextOpening(room) {
var now = new MyDate();
var day = now.getDay();
var offset = 0;
var bestdate;
for (var a = 0; a < 7; a++) {
if (room.openingTimes == null) {
return null;
}
var tmp = room.openingTimes[day];
if (tmp != null) {
for (var i = 0; i < tmp.length; i++) {
var openDate = new MyDate();
openDate.setDate(now.getDate() + offset);
openDate.setHours(tmp[i].HourOpen);
openDate.setMinutes(tmp[i].MinutesOpen);
if (openDate > now) {
if (!IsOpen(new Date(openDate.getTime() - 60000), room)) {
if (bestdate == null || bestdate > openDate) {
bestdate = openDate;
}
}
}
}
}
offset++;
day++;
if (day > 6) {
day = 0;
}
}
return bestdate;
}
/**
* returns next closing time of a given room
* @param room
* @returns Date Object of next closing
*/
function GetNextClosing(room) {
var now = new MyDate();
var day = now.getDay();
var offset = 0;
var bestdate;
for (var a = 0; a < 7; a++) {
//Test
if (room.openingTimes === null) {
return null;
}
var tmp = room.openingTimes[day];
if (tmp != null) {
for (var i = 0; i < tmp.length; i++) {
var closeDate = new MyDate();
closeDate.setDate(now.getDate() + offset);
closeDate.setHours(tmp[i].HourClose);
closeDate.setMinutes(tmp[i].MinutesClose);
if (closeDate > now) {
if (!IsOpen(new Date(closeDate.getTime() + 60000), room)) {
if (bestdate == null || bestdate > closeDate) {
bestdate = closeDate;
}
}
}
}
}
offset++;
day++;
if (day > 6) {
day = 0;
}
}
return bestdate;
}
/**
* Updates the Course Text of a child
* @param id of the child
* @param idle PC's on
* @param occupied PC's used
* @param offline PC's that are off
* @param broken PC's that are broken
* @param standby PCs in standby mode
*/
function updateRoomUsage(id, idle, occupied, offline, broken, standby) {
/* TODO Broken
if (idle === 0 && occupied === 0 && offline === 0 && broken === 0 && standby === 0) {
$('#parent_' + id).parent().hide();
return;
}
$('#parent_' + id).parent().show();
*/
var total = parseInt(idle) + parseInt(occupied) + parseInt(offline) + parseInt(broken) + parseInt(standby);
$("#pc_Idle_" + id).text(idle).width((idle / total) * 100 + '%');
$("#pc_Occupied_" + id).text(occupied).width((occupied / total) * 100 + '%');
$("#pc_Offline_" + id).text(offline).width((offline / total) * 100 + '%');
$("#pc_Broken_" + id).text(broken).width((broken / total) * 100 + '%');
$("#pc_Standby_" + id).text(standby).width((standby / total) * 100 + '%');
}
/**
* Updates the Course Text of a child
* @param id of the child
* @param text Text
*/
function updateCourseText(id, text) {
$("#div_course" + id).text(text);
}
/**
* Updates the Course time of a child
* @param id of the child
* @param time Time value
*/
function updateCoursTimer(id, time) {
// TODO: Add seconds again with a better update rate.
var time_split = time.split(":");
if (time != "") {
$("#div_Time_" + id).text(time_split[0] + ":" + time_split[1]);
} else {
$("#div_Time_" + id).text(time);
}
}
/**
* generates a Div, used for a child node
* @param target Div it should be inserted
* @param id ID of the Object it represents
* @param name Name of the Object it represents
* @param outermost if the object is a root node
* @returns generated div
*/
function generateChild(target, id, name, outermost) {
var c = "";
if (outermost) {
c = "outermost";
}
var text = "<div class='border " + c + "'>" +
"<div class='child paperEffect' id='parent_" + id + "'>" +
"<div class='headerFont'>" + name + "</div>" +
"<div class='pc-state-wrapper'>" +
"<div id = 'pc_Occupied_" + id + "' class='pc-occupied'>?</div>" +
"<div id = 'pc_Idle_" + id + "' class='pc-idle'>?</div>" +
"<div id = 'pc_Standby_" + id + "' class='pc-standby'>?</div>" +
"<div id = 'pc_Offline_" + id + "' class='pc-offline'>?</div>" +
"<div id = 'pc_Broken_" + id + "' class='pc-broken'>?</div>" +
"</div>" +
"<div class='aroundCourse'>" +
"<div id = 'div_course" + id + "'class='courseFont'>?</div>" +
"<div id = 'div_Time_" + id + "'class='courseFont'></div></div></div></div>";
var obj = $(target).append(text);
addRoom(id, name);
return obj;
}
/**
* generates a Div, used for a parent node
* @param target Div it should be inserted
* @param id ID of the Object it represents
* @param name Name of the Object it represents
* @param outermost if the object is a root node
* @returns generated div
*/
function generateParent(target, id, name, outermost) {
var c = "";
if (outermost) {
c = "outermost";
}
var text = "<div class='border " + c + "'>" +
"<div class='parent paperEffect'>" +
"<div class='headerFont'>" + name + "</div>" +
"<div id='parent_" + id + "'></div>" +
"</div></div>";
return $(target).append(text);
}
/**
* returns parameter value from the url
* @param sParam
* @returns value for given parameter
*/
var getUrlParameter = function getUrlParameter(sParam) {
var sPageURL = decodeURIComponent(window.location.search.substring(1)),
sURLVariables = sPageURL.split('&'),
sParameterName,
i;
for (i = 0; i < sURLVariables.length; i++) {
sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) {
return sParameterName[1] === undefined ? true : sParameterName[1];
}
}
};
/**
* querys the Calendar data
* Calender is queried with the {{uuid}} of the panel.
* api.inc.php / page.inc.php is getting the ids with the panel uuid.
*/
function queryCalendars() {
var url = "{{dirprefix}}api.php?do=locationinfo&get=calendar&uuid={{uuid}}";
// Todo reimplement Frontend methode if needed
/*
if(!(room.config.calendarqueryurl === undefined)) {
url = room.config.calendarqueryurl;
}
*/
$.ajax({
url: url,
dataType: 'json',
cache: false,
timeout: 30000,
success: function (result) {
UpdateTimeTables(result);
}, error: function () {
}
});
}
/**
* Checks whether the panel has been edited and reloads
* the entire page if so.
*/
function queryPanelChange() {
$.ajax({
url: "{{dirprefix}}api.php?do=locationinfo&get=timestamp&uuid={{uuid}}",
dataType: 'json',
cache: false,
timeout: 5000,
success: function (result) {
if (!result || !result.ts) {
console.log('Warning: get=timestamp didnt return json with ts field');
return;
}
if (config.ts && config.ts !== result.ts) {
// Change
window.location.reload(true);
}
config.ts = result.ts;
}
})
}
/**
* Function for translation
* @param toTranslate key which we want to translate
* @returns r translated string
*/
function t(toTranslate) {
if (tCache[toTranslate])
return tCache[toTranslate];
var r = $('#i18n').find('[data-tag="' + toTranslate + '"]');
return tCache[toTranslate] = (r.length === 0 ? toTranslate : r.text());
}
var tCache = {};
</script>
</head>
<body>
<div id="main"></div>
<div id="i18n">
<span data-tag="room">{{lang_room}}</span>
<span data-tag="closed">{{lang_closed}}</span>
<span data-tag="free">{{lang_free}}</span>
<span data-tag="shortSun">{{lang_shortSun}}</span>
<span data-tag="shortMon">{{lang_shortMon}}</span>
<span data-tag="shortTue">{{lang_shortTue}}</span>
<span data-tag="shortWed">{{lang_shortWed}}</span>
<span data-tag="shortThu">{{lang_shortThu}}</span>
<span data-tag="shortFri">{{lang_shortFri}}</span>
<span data-tag="shortSat">{{lang_shortSat}}</span>
<span data-tag="longSun">{{lang_longSun}}</span>
<span data-tag="longMon">{{lang_longMon}}</span>
<span data-tag="longTue">{{lang_longTue}}</span>
<span data-tag="longWed">{{lang_longWed}}</span>
<span data-tag="longThu">{{lang_longThu}}</span>
<span data-tag="longFri">{{lang_longFri}}</span>
<span data-tag="longSat">{{lang_longSat}}</span>
<span data-tag="to">{{lang_to}}</span>
</div>
</body>
</html>