summaryrefslogblamecommitdiffstats
path: root/modules-available/locationinfo/templates/frontend-default.html
blob: d4e8109a3d552ea22a6d1d23b5609a6e6165f034 (plain) (tree)
1
2
3
4
5
6
7
8
9
               




         
                                                
 
         











                                                                             
                                                                                                           

   
                          


                                                                                      

                                                                                                      
 
                               

                      
                                  
                                   

                                     



                                                  

                 



                                                                                            
                      
                                               

                                                                             
                                    



                                                       

                 







                                    

                                       
                                     


                                                



                                                


                             

                                     





                                          

                                           

                                          


                                      
                                    
                                   
                                     
                                    
                                        

                                       
 






                                         


                              
                                 


                                              
                                  
                                     

                 
                              

                                         
                                          
                                      

                 


                                            

                 

                                    

                 
                                       
                                        
                                         

                 
                                       

                                          

                 


                                     
                                          
                                   
                                                  

                 

                                           

                 
                              
                                           




                                           


                                         
                                                   
                                                                 
                                               




                                    
                                               


                                 
                                                       






                                     
                                               






                                          



                                     
                               



                                              











                                               
                                           
                                    

                                    





                                                  




                                           





                                         
                                        


                                         

                                        
                                         
                 
 
                                         





                                               




                                       
                                   



                                          


                                       

                                                   

                 
                                       







                                           
                         








                                           
                                              















                                                




                                                     
                           
                                                                   







                                                  
                                         

                 

                                                 
                                                     
                                                      

                 




                                                   



                                      
                
 


                                                                                                           
                                                                                                                   
 

       



                                                      
                                                















                                                          
       
 
                               






                                   
                                     






                                                                
                                       



                                                                 
                                          















                                                                           




                                                                                            
 






                                                                                                                          
 



                                                                    
 


                                               
                                                                                  

                                          
                                                                                           

                                                     
                                                                         




                                          
 


                                                    
 



                                                               


























































                                                                                                             














                                                                                
                                                                       





















                                                                                             
                                
                              
                                 

                                                                   
                                    


                                           
                                    













                                                                     
 
                                                                             

                                                                                       
 
                                                
                                                                                                                       

                                                                                                

                                                                                                                   
 
                                                                                              
                                                            
                                                               
                                                                                                                                                           
                                                
                                             
                                                                 
 

                                                                    
                         
 






                                                     

                                                

                                               
 





                                                               
 


                                              
 

                                                                                           

                                                 

                                         

                                                 
                 


                                                                            
         
 

                             
                                                            



                                         
 


                                                                        
                                                                            

                                             

                                           
                 
 

                                                            




                                                      
                         


                                         












                                                                  
 







                                                                  
                                                                              




















                                                                      










                                                                  
                                 

                                                                        
                                                                          
                                         
                                 

                                                  

 






                                             

                                   


                                                        




                                               
                                                                                                                                                



                                                                
 


                                                                                     
                                 








                                                                                                                                          
                                 








                                                

                                                                                                                                             
                                       
                                             

                                                     


                                                       




                                                              


                                  






                                                                      

                                                  
                                        



























                                                                                                                            
                                            


















                                                                                                                            
                                 

                                                                     
                                 




                                                                           
                                 

                         




                                                    
                                    




                                                                         


































                                                                                                           
                                                                                                










                                                                                                        
                                 


                                                                                                          
                         





                                            
                                                                                                











                                                                                    


                                                                                  










                                                                                                                           





                                                                                                                                              
                                              



                                                                                                           
                                                     


                                                                                   
                                                                                                                  
                                                                                              
                                                                   
                                                                             


                                                        
                         







                                                                        




















                                                                                     













                                                                      
                                                                                                     
                                                                            

                                                                                   
                                                                             






















                                                                                                                            


                                    
                                                                     
                                      
                                       

                                                                    
                                            
                                                                    
                                             
                                                                    
                                             
                                                                    
                                             








                                                                                      
                                                                                                   



                                                                                       
                                                              


           

                                                    
                                                 

                                       
                                                                                      

                                       
                                    











                                                                                                     


                                                 
                         




                                                      
           
                               
                                  
                                                 




                                                    
                                    


                                                                     
                                                              
                                                                                      

                                                                        



                                                                                                    


                                                 
                         






                                                      
                                                                                                
                           
           
                                     
                                                                         








                                                                                          

















                                                                                                      
                                                                                            

                                             
                                                         

                                                           
                                                                               
                                                       




                                                          
                                                                           
                 
                                                            
                                           











                                                                                                       

                               
 
                                                   



                                                         
                                                                                                    




                                              
                                                                                                           











                                                                                                                  

                 

                                              
                                                                                                             
                               

                 

                                                      
                                                                                                             
                        
                                                                                                           

                 
         
 
 


                                                                  
                                             






                                                                                                          
                                 




                                                                                                                         
                         
                                                   
                                     




                                                                                                                                          
                         


                             
 

                                                                    
                         








                                                                                                         
 
 

                             
 







                                                                                                                               
                 
                                                                                                                      
 





                                              
 






                                                       
                         
 



                                                                             
                 
 
                                                         
 





                                                                
                         







                                                         
                         

                 

                                 



                                                           
 


                                        
 
         
 




                                                            
                                              
 
                                                                     


                                                                                                                               

                 
                                                    
 


                                                                                      
                 

                                                                        

                 


                                                                                                            
         
 







                                                                         
                                                                                                                                
                                                                                            




                                                                                                                                       
                                                                     
 


                                                                                                   
                                         
                                 
                         
                 







                                                                             
                            


                                                                                             



                                      






                                                                                
























                                                                   




                                                               
                                                                                                    

















                                                                                                              



                               
                                                                                                   






                                                                                                
                                 

                                                                                          
                                 
                         




                                                                                             
                                                  













                                                                                      
                                                                            
                                  
                                                               






                                                                                                                                                                                         
                         
 
                                                                                                                                            

                                       
                                            























                                                                                                       
                                                                            
                                        
                                         
                                             
                                                                              






                                                                                                          

                                                                                                   
                                   

                         
         
 



                                                                                                  
 

                                                      
 






































                                                                                       
                                                            

                                       




                                                                                       
         
                        



                                            
                 

                                        
                 
         
 




























                                                                                           
                                                       






















                                                                                                            
         
       
<!DOCTYPE html>
<!--

parameter

required:
    uuid: [integer] panel id, see in admin panel

optional:
    mode:[1,2,3,4] sets the displaying
        1: Calendar & Room
        2: only Calendar
        3: only Room
        4: Calendar & Room alternately
    daystoshow:[1,2,3,4,5,6,7] sets how many days the calendar shows
    scale:[10-90] scales the calendar and Roomplan in mode 1
    switchtime:[1-120] sets the time between switchen in mode 4  (in seconds)
    calupdate: Time the calender querys for updates,in minutes.
    roomupdate: Time the PCs in the room gets updated,in seconds.
    rotation:[0-3] rotation of the roomplan
    vertical:[true] only mode 1, sets the calendar above the roomplan
    scaledaysauto: [true] if true it finds automatically the daystoshow parameter depending on display size

-->
<html lang="{{language}}">
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8">
<head>
	<title>DoorSign</title>
	<link rel='stylesheet' type='text/css' href='{{dirprefix}}modules/js_jqueryui/style.css'/>
	<link rel='stylesheet' type='text/css' href='{{dirprefix}}modules/js_weekcalendar/style.css'/>

	<style type="text/css">

		body {
			margin: 0;
			padding: 0;
			width: 100%;
			height: 100%;
			background-color: #cacaca;
			overflow: hidden;
			position: absolute;
			display: table;
		}

		body, .wc-container {
			font-family: "Lucida Grande", Helvetica, Arial, Verdana, sans-serif;
		}

		.row {
			background-color: #444;
			box-shadow: 0 0.1875rem 0.375rem rgba(0, 0, 0, 0.25);
			margin-bottom: 4px;
			width: 100%;
			display: flex;
			flex-wrap: nowrap;
			align-items: center;
			justify-content: space-between;
		}

		.pull-left {
			float: left;
		}

		.clearfix {
			clear: both;
		}

		.col {
			padding: 0 4px;
			color: white;
			overflow: hidden;
			flex: 1 1 auto;
			text-overflow: ellipsis;
			position: relative;
			display: flex;
			flex-direction: column;
			justify-content: center;
		}

		.col-square {
			order: 1000;
			float: right;
			width: 70pt;
			width: 6vw;
			height: 70pt;
			height: 6vw;
			font-size: 56pt;
			font-size: 4.25vw;
			flex: 0 0 auto;
			text-align: center;
			padding: 0;
			overflow: visible;
		}

		.count-1 .col-square {
			width: 93pt;
			width: 8vw;
			height: 93pt;
			height: 8vw;
			font-size: 85pt;
			font-size: 6vw;
		}

		.count-3 .col-square {
			width: 46pt;
			width: 4vw;
			height: 46pt;
			height: 4vw;
			font-size: 35pt;
			font-size: 2.5vw;
		}

		.progressbar {
			width: 0;
			height: 2px;
			position: absolute;
			background-color: red;
			bottom: 0;
			z-index: 100;
		}

		.header-font {
			font-size: 25pt;
			font-size: 1.8vw;
			font-weight: bold;
			padding: 10px;
		}

		.nowrap {
			white-space: nowrap;
			overflow: hidden;
		}

		.timer {
			color: #ddd;
		}

		.count-3 .header-font {
			font-size: 16pt;
			font-size: 1.2vw;
		}

		.count-1 .header-font {
			font-size: 30pt;
			font-size: 2.25vw;
		}

		.seats-counter {
			color: white;
			margin: auto;
			font-weight: bold;
			padding: 0;
			text-shadow: #000 2px 2px;
		}

		.center {
			text-align: center;
		}

		.room-layout {
			position: relative;
			float: left;
		}

		.location-container {
			position: absolute;
			width: 100%;
			height: 100%;
			overflow: hidden;
			border: 1px solid darkgrey;
			background: linear-gradient(#ddd, white);
			box-sizing: border-box;
		}

		.calendar {
			float: left;
			padding: 0;
			box-sizing: border-box;
		}

		.free-busy-busy {
			background: rgba(0, 0, 0, .25);
		}

		.ui-widget-content {
			color: white;
		}

		.wc-header {
			background-color: #444;
			font-weight: bold;
		}

		.ui-state-default {
			text-shadow: none;
		}

		.BROKEN {
			opacity: 0.4;
		}

		.pc-container {
			position: absolute;
			left: 0;
			bottom: 0;
			display: inline-block;
			padding: 0;
			margin: 0;
			overflow: hidden;
		}

		.pc-container div {
			box-sizing: border-box;
		}

		.screen-frame {
			position: relative;
			background: black;
			border-radius: 11%;
			width: 100%;
			height: 83%;
			padding: 6%;
		}

		.screen-inner {
			width: 100%;
			height: 100%;
			transition: background 2s;
			border-radius: 5%;
			padding-top: 4px;
			overflow: hidden;
			text-align: center;
			color: #fff;
		}

		.BROKEN .screen-inner {
			background: #000;
		}

		.OFFLINE .screen-inner {
			background: #332;
		}

		.IDLE .screen-inner,
		.STANDBY .screen-inner {
			background: #250;
		}

		.OCCUPIED .screen-inner {
			background: #d23;
		}

		.OCCUPIED .screen-inner:after {
			content: '\01F464';
			font-weight: bold;
		}

		.screen-foot1 {
			margin: 0 auto;
			width: 10%;
			height: 7%;
			background: black;
		}

		.screen-foot2 {
			margin: 0 auto;
			width: 80%;
			height: 7%;
			background: black;
			border-radius: 30% 30% 0 0;
		}

		.pc-overlay-container {
			position: absolute;
			left: 0;
			bottom: 0;
			width: 100%;
			height: 100%;
			display: table;
		}

		.pc-img {
			position: absolute;
			left: 0;
			top: 0;
			width: 100%;
			height: 100%;

		}

		.overlay {
			display: inline-block;
			position: relative;
			width: 50%;
			height: 50%;
			opacity: 0.5;
			float: left;
			z-index: 5;
		}

		.overlay-rollstuhl {
			width: 25%;
			height: 50%;
			background-color: white;
			opacity: 0.5;
			float: left;
		}

		.ui-widget-content .ui-state-active {
			font-weight: bold;
			color: black;
		}

		.wc-today {
			background-color: rgba(255, 255, 255, .66);
		}

		.wc-time-header-cell {
			background-color: #eeeeee;
			border: none;
		}

		.ui-corner-all {
			border-radius: 0;
		}

		.wc-scrollable-grid {
			transition: height 500ms;
			background: rgba(0, 0, 0, 0);
			overflow-y: hidden !important;
		}

		.wc-grid-timeslot-header,
		.wc-header .wc-time-column-header {
			width: 50px;
		}

		#i18n {
			display: none;
		}

	</style>

	<script type='text/javascript' src='{{dirprefix}}script/jquery.js'></script>
	<script type='text/javascript' src='{{dirprefix}}modules/js_jqueryui/clientscript.js'></script>
	<script type='text/javascript' src="{{dirprefix}}modules/js_weekcalendar/clientscript.js"></script>
	<script type='text/javascript' src='{{dirprefix}}modules/locationinfo/frontend/frontendscript.js'></script>

</head>
<body>
<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="for">{{lang_for}}</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>

<script type="text/javascript">
	var rooms = {};
	var lastRoomUpdate = 0;
	var lastCalendarUpdate = 0;
	var lastSwitchTime = 0;
	var hasMode4 = false;
	var globalConfig = {};
	var roomIds = [];
	var panelUuid = '{{{uuid}}}';
	const IMG_FORMAT_LIST = (function() {
		if (typeof(SVGRect) !== "undefined") {
			return [".svg", ".png", ".jpg", ".gif"];
		}
		return [".png", ".jpg", ".gif"];
	})();

	$(document).ready(function () {
		if (!SetUpDate) {
			fatalError("js_weekcalendar not loaded");
			return;
		}
		applyConfig({{{config}}});
	});

	/**
	 * Display given error message and try reloading page once a minute
	 */
	function fatalError(message) {
		$('body').empty().append($('<h1>').text(message));
		window.setInterval(function () {
			$.ajax('/').done(function () {
				window.location.reload(true);
			}).fail(function () {
				$('body').append('...');
			});
		}, 60000);
	}

	function applyConfig(result) {
		if (!result.locations || result.locations.constructor !== Array) {
			fatalError("Requested panel doesn't contain locations / not array");
			return;
		}

		var fetchedRooms = result.locations.filter(function (x) {
			// filter out if no numeric id, or id already present, or already got 4 locations
			if (typeof(x.id) !== 'number' || x.id <= 0 || roomIds.indexOf(x.id) !== -1 || roomIds.length >= 4)
				return false;
			roomIds.push(x.id);
			return true;
		});

		if (roomIds.length === 0) {
			fatalError("List of location ids is empty");
			return;
		}

		var time = false;
		var p = result.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(result.time);
		}
		if (isNaN(time.getTime()) || time.getFullYear() < 2010) {
			time = new Date();
		}
		SetUpDate(time);
		delete result.time;
		delete result.locations;

		globalConfig = result;
		sanitizeGlobalConfig();
		lastRoomUpdate = MyDate().getTime();

		for (var i = 0; i < fetchedRooms.length; ++i) {
			addRoom(fetchedRooms[i]);
		}
		initRooms();
	}

	const PARAM_STRING = 1;
	const PARAM_INT = 2;
	const PARAM_BOOL = 3;

	/**
	 * Read given parameter from URL, replacing it in the config object if present.
	 * @param config object config object
	 * @param property string name of property in object, URL param of same name is being checked
	 * @param paramType int one of PARAM_STRING, PARAM_INT, PARAM_BOOL
	 * @param intScaleFactor int optional scale factor that will be applied if paramType == PARAM_INT
	 */
	function setRoomConfigFromUrl(config, property, paramType, intScaleFactor) {
		var val = getUrlParameter(property);
		if (val === true || val === false)
			return;
		if (paramType === PARAM_STRING) {
			config[property] = val;
		} else if (paramType === PARAM_INT) {
			config[property] = parseInt(val);
			if (intScaleFactor) {
				config[property] *= intScaleFactor;
			}
		} else if (paramType === PARAM_BOOL) {
			val = val.toLowerCase();
			config[property] = val.length > 0 && val !== 'false' && val !== 'off' && val !== '0';
		} else {
			console.log('Invalid paramType: ' + paramType);
		}
	}

	/**
	 * Put given numeric config property in range min..max (both inclusive),
	 * if not in range, set to default.
	 * @param config - object config object
	 * @param property - string config property
	 * @param min int - min allowed value (inclusive)
	 * @param max int - max allowed value (inclusive)
	 * @param defaultval - default value to use if out of range
	 * @param scaleFactor int - optional scale factor to apply
	 */
	function putInRange(config, property, min, max, defaultval, scaleFactor) {
		var v = config[property];
		if (!scaleFactor) {
			scaleFactor = 1;
		}
		if (!v || !isFinite(v) || isNaN(v) || v < min * scaleFactor || v > max * scaleFactor) {
			config[property] = defaultval * scaleFactor;
		}
	}

	/**
	 * gets Additional Parameters from the URL, and from the
	 * downloaded json.
	 * also makes sure parameters are in a given range
	 */
	function sanitizeGlobalConfig() {
		sanitizeConfig(globalConfig);
	}

	function sanitizeConfig(config) {
		if (config) {
			config.switchtime = config.switchtime * 1000;
			config.calupdate = config.calupdate * 60 * 1000;
			config.roomupdate = config.roomupdate * 1000;
		}

		setRoomConfigFromUrl(config, 'calupdate', PARAM_INT, 60 * 1000);
		setRoomConfigFromUrl(config, 'roomupdate', PARAM_INT, 1000);
		setRoomConfigFromUrl(config, 'daystoshow', PARAM_INT);
		setRoomConfigFromUrl(config, 'scaledaysauto', PARAM_BOOL);
		setRoomConfigFromUrl(config, 'vertical', PARAM_BOOL);
		setRoomConfigFromUrl(config, 'eco', PARAM_BOOL);
		setRoomConfigFromUrl(config, 'prettytime', PARAM_BOOL);

		setRoomConfigFromUrl(config, 'scale', PARAM_INT);
		setRoomConfigFromUrl(config, 'rotation', PARAM_INT);
		setRoomConfigFromUrl(config, 'switchtime', PARAM_INT, 1000);

		// parameter validation
		putInRange(config, 'switchtime', 5, 120, 6, 1000);
		putInRange(config, 'scale', 10, 90, 50);
		putInRange(config, 'daystoshow', 1, 7, 7);
		putInRange(config, 'roomupdate', 15, 5 * 60, 60, 1000);
		putInRange(config, 'calupdate', 1, 60, 30, 60 * 1000);
		putInRange(config, 'mode', 1, 4, 1);
		putInRange(config, 'rotation', 0, 3, 0);
	}

	/**
	 * generates the Room divs and calls the needed functions depending on the rooms mode
	 */
	function initRooms() {

		var width = "100%";
		var height = "100%";
		var columns = 1;
		var top, left;
		hasMode4 = false;
		if (roomIds.length === 2 || roomIds.length === 4) {
			width = "50%";
			columns = 2;
		}
		if (roomIds.length === 3) {
			width = "33%";
			columns = 3;
		}
		if (roomIds.length === 4) {
			height = "50%";
		}
		for (var t = 0; t < roomIds.length; t++) {
			var rid = roomIds[t];
			var room = rooms[rid];
			if (roomIds.length === 3) {
				top = 0;
				left = (t * 33) + '%';
			} else {
				top = (Math.floor(t / 2) * 50) + '%';
				left = ((t % 2) * 50) + '%';
			}

			var $loc = $("<div>").addClass('location-container');
			$loc.css({top: top, left: left, width: width, height: height});
			$("body").append($loc);

			room.$.container = $loc;
			room.$.locationName = $('<div>').addClass('col').addClass('header-font').addClass('pull-left');
			room.$.currentEvent = $("<span>").addClass('nowrap');
			room.$.currentRemain = $("<span>").addClass('nowrap').addClass('timer');
			room.$.seatsCounter = $('<span>').addClass('seats-counter');
			room.$.seatsBackground = $('<div>').addClass('col col-square').append(room.$.seatsCounter);

			var $header = $('<div>').addClass('row').addClass('count-' + columns);
			$header.append(room.$.locationName);
			$header.append(room.$.seatsBackground);
			$header.append($('<div>').addClass('col header-font center').append(room.$.currentEvent).append(' ').append(room.$.currentRemain));
			room.$.header = $header;
			$loc.append($header);
			$header.append('<div class="clearfix">');

			if (room.name !== null) {
				room.$.locationName.text(room.name);
			}

			if (room.config.mode !== 3) {
				setUpCalendar(room);
			}
			if (room.config.mode !== 2) {
				initRoomLayout(room);
			}
			if (room.config.mode === 4) {
				hasMode4 = true;
			}
			SetOpeningTimes(room);
			UpdateRoomHeader(room);

			(function (room) {
				setTimeout(function () {
					resizeIfRequired(room);
				}, 800);
			})(room);
		}

		if (hasMode4) {
			generateProgressBar();
		}

		// Manually initialize mode 2, as initRoomLayout isn't called for this mode
		if (room.config.mode === 2) {
			var date = MyDate();
			var now = date.getTime();
			queryCalendars();
			queryRooms();
			lastCalendarUpdate = now;
			lastRoomUpdate = now;
		}
		mainUpdateLoop();
		setInterval(mainUpdateLoop, 10000);
		setInterval(updateHeaders, globalConfig.eco ? 10000 : 1000);
	}

	var lastDate = false;
	/**
	 * Main Update loop, this loop runs every 10 seconds
	 */
	function mainUpdateLoop() {
		var date = MyDate();
		var now = date.getTime();

		if (lastCalendarUpdate + globalConfig.calupdate < now) {
			lastCalendarUpdate = now;
			queryCalendars();
		} else if (lastRoomUpdate + globalConfig.roomupdate < now) {
			lastRoomUpdate = now;
			queryRooms();
		} else {
			queryPanelChange();
		}

		$('.calendar').weekCalendar("scrollToHour");

		// reload site at midnight
		var today = date.getDate();
		if (lastDate !== false) {
			if (lastDate !== today) {
				location.reload(true);
			}
		} else {
			lastDate = today;
		}
	}

	/**
	 * Update all location headers.
	 * Runs ever second (normal) or every 10 seconds (eco)
	 */
	function updateHeaders() {
		for (var property in rooms) {
			if (rooms[property].state.end) {
				// Updating All room Headers
				UpdateRoomHeader(rooms[property]);
			}
		}

	}

	/**
	 *  Generates a room Object and adds it to the rooms array
	 *  @param roomData Config Json of the room
	 */
	function addRoom(roomData) {
		var mergedConfig = {};
		if (roomData.config && typeof(roomData.config) === 'object') {
			mergedConfig = roomData.config;
			sanitizeConfig(mergedConfig);
		}
		for (var k in globalConfig) {
			if (typeof mergedConfig[k] === 'undefined') {
				mergedConfig[k] = globalConfig[k];
			}
		}
		var now = MyDate().getTime();
		var room = {
			id: roomData.id,
			name: roomData.name,
			config: mergedConfig,
			timetable: null,
			currentEvent: null,
			nextEventEnd: null,
			timeTilFree: null,
			state: null,
			rawOpeningTimes: roomData.openingtime || null,
			openingTimes: null,
			openTimes: 24,
			layout: roomData.machines || null,
			freePcs: 0,
			resizeRoom: true,
			resizeCalendar: true,
			lastCalendarUpdate: now,
			lastRoomUpdate: now,
			$: {},
			getState: function () {
				if (this.state === null) {
					ComputeCurrentState(this);
					return this.state;
				}
				if (this.state.end) {
					if (this.state.end < MyDate()) {
						ComputeCurrentState(this);
					}
				}
				return this.state;
			}


		};
		rooms[roomData.id] = room;
		return room;
	}

	/**
	 *  inilizes the Calendar for an room
	 *  @param room Room Object
	 */
	function setUpCalendar(room) {
		var daysToShow = room.config.daystoshow;
		generateCalendarDiv(room);
		room.$.calendar.weekCalendar({
			timeslotsPerHour: 1,
			timeslotHeight: 30,
			daysToShow: daysToShow,
			height: function () {
				// if (room.config.mode === 1 && room.config.vertical && (!room.timetable || !room.timetable.length)) return 20;
				var height = $(window).height();
				if (roomIds.length === 4) {
					height /= 2;
				}

				height -= room.$.header.height() - 5;
				if (room.config.mode === 1 && room.config.vertical) {
					height *= (room.config.scale / 100);
				}
				return height;
			},
			eventRender: function (calEvent, $event) {
				if (calEvent.end.getTime() < MyDate().getTime()) {
					$event.css("backgroundColor", "#aaa");
					$event.find(".time").css({"backgroundColor": "#999", "border": "1px solid #888"});
				} else if (calEvent.end.getTime() > MyDate().getTime() && calEvent.start.getTime() < MyDate().getTime()) {
					$event.css("backgroundColor", "#25B002");
					$event.find(".time").css({"backgroundColor": "#25B002", "border": "1px solid #888"});
				}
			},
			date: MyDate(),
			dateFormat: "j.n",
			timeFormat: "G:i",
			scrollToHourMillis: 500,
			use24Hour: true,
			readonly: true,
			showHeader: false,
			hourLine: true,
			shortDays: [t("shortSun"), t("shortMon"), t("shortTue"), t("shortWed"), t("shortThu"), t("shortFri"), t("shortSat")],
			longDays: [t("longSun"), t("longMon"), t("longTue"), t("longWed"), t("longThu"), t("longFri"), t("longSat")],
			buttons: false,
			timeSeparator: " - ",
			startOnFirstDayOfWeek: false,
			displayFreeBusys: true,
			defaultFreeBusy: {free: false},
			allowCalEventOverlap: true,
			overlapEventsSeparate: true
		});
	}

	/**
	 * Generates the Calendar Div, depending on it's width
	 * @param room Room Object
	 */

	function generateCalendarDiv(room) {
		var width = 100;
		if (room.config.mode === 1 && !room.config.vertical) {
			width = room.config.scale;
		}
		var $cal = $('<div>').addClass('calendar');
		if (room.config.mode === 1 && room.config.vertical) {
			$cal.css('float', "none");
		}
		$cal.width(width + '%');
		room.$.container.append($cal);
		room.$.calendar = $cal;
	}

	const OT_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
	const OT_KEYS = ['HourOpen', 'HourClose', 'MinutesOpen', 'MinutesClose'];

	/**
	 * sets the opening Time in the calendar plugin and saves it in the room object
	 * @param room Room Object
	 */

	function SetOpeningTimes(room) {
		var opening = 24;
		var close = 0;
		var i;
		if (room.rawOpeningTimes && typeof(room.rawOpeningTimes) === 'object') {
			// TODO: wtf! we have three(!) formats for storing the opening times (DB, API, now this one) - WHY!?
			var parsedOpenings = room.rawOpeningTimes;
			room.state = null;
			room.openingTimesCalendar = [];
			room.openingTimes = [];
			for (i = 0; i < OT_DAYS.length; ++i) {
				room.openingTimes.push(filterOpeningTimesDay(parsedOpenings[OT_DAYS[i]]));
			}
			delete room.rawOpeningTimes;
		}
		if (!room.openingTimes) {
			scaleCalendar(room);
			return;
		}
		if (room.config.mode === 3) {
			// Calendar is not displayed, don't need to do additional work
			return;
		}
		var now = MyDate();
		for (i = 0; i < 7; i++) {
			var tmp = room.openingTimes[i];
			for (var d = 0; d < tmp.length; d++) {
				var day = getNextDayOfWeek(now, i);
				if (room.openingTimesCalendar) {
					room.openingTimesCalendar.push({
						"start": new Date(day.getFullYear(), day.getMonth(), day.getDate(),
								tmp[d]['HourOpen'], tmp[d]['MinutesOpen']),
						"end": new Date(day.getFullYear(), day.getMonth(),
								day.getDate(), tmp[d]['HourClose'], tmp[d]['MinutesClose']),
						"free": true
					});
				}
				if (tmp[d]['HourOpen'] < opening) {
					opening = tmp[d]['HourOpen'];
				}
				if (tmp[d]['HourClose'] >= close) {
					close = tmp[d]['HourClose'];
					if (tmp[d]['MinutesClose'] !== 0) {
						close++;
					}
				}
			}
		}
		if (opening === 24 && close === 0) {
			opening = 0;
			close = 24;
		}
		room.openTimes = close - opening;
		scaleCalendar(room);
		room.$.calendar.weekCalendar("option", "businessHours", {
			start: opening,
			end: close,
			limitDisplay: true
		});
	}

	/**
	 * 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;
		});
	}

	/**
	 * querys the Calendar data
	 */
	function queryCalendars() {
		if (!panelUuid) return;
		var url = "{{dirprefix}}api.php?do=locationinfo&get=calendar&uuid=" + panelUuid;
		$.ajax({
			url: url,
			dataType: 'json',
			cache: false,
			timeout: 30000,
			success: function (result) {
				if (result && result.constructor === Array) {
					var l = result.length;
					for (var i = 0; i < l; i++) {
						updateCalendar(result[i].calendar, rooms[result[i].id]);
					}
				}
			}, error: function () {
				// Retry in 5 minutes (300 seconds)
				lastCalendarUpdate = MyDate().getTime() + globalConfig.calupdate + 300000;
			}
		});
	}

	const SEVEN_DAYS = 7 * 86400 * 1000;

	/**
	 * applies new calendar data to the calendar plugin and also saves it to the room object
	 * @param {Array} json Calendar data
	 * @param room Room Object
	 */
	function updateCalendar(json, room) {
		if (!room) {
			console.log("Error: No room for calendar data");
			return;
		}
		if (!json || json.constructor !== Array) {
			console.log("Error: Calendar data was empty or malformed.");
			return;
		}
		if (json.length === 0) {
			console.log("Notice: Calendar already empty from server");
		}
		var now = MyDate().getTime();
		json = json.filter(function (el) {
			if (!el.title || !el.start || !el.end) return false;
			var s = new Date(el.start).getTime();
			var e = new Date(el.end).getTime();
			return !(isNaN(s) || isNaN(e) || Math.abs(s - now) > SEVEN_DAYS || Math.abs(e - now) > SEVEN_DAYS);
		});
		if (json.length === 0) {
			console.log('Notice: Calendar has no current events for ' + room.name);
		}
		try {
			for (var i = json.length - 1; i > 0; i--) {
				// if title, start and end are the same, "merge" two events by removing one of them
				if (json[i].title === json[i-1].title && json[i].start === json[i-1].start && json[i].end === json[i-1].end) {
					json.splice(i, 1);
				}
			}
			room.timetable = json;
			for (var property in room.timetable) {
				room.timetable[property].start = cleanDate(room.timetable[property].start);
				room.timetable[property].end = cleanDate(room.timetable[property].end);
			}
			if (room.config.mode !== 3) {
				var cal = room.$.calendar;
				cal.weekCalendar('option', 'data', {events: json});
				cal.weekCalendar("refresh");
				cal.weekCalendar("option", "defaultFreeBusy", {free: !room.openingTimesCalendar});
				cal.weekCalendar("updateFreeBusy", room.openingTimesCalendar);
				cal.weekCalendar("resizeCalendar");
				cal.weekCalendar("option", "hourLine", true);
				setTimeout(function() {
					scaleRoom(room);
				}, 550);
			}
			room.state = null;
			UpdateRoomHeader(room);
		} catch (e) {
			console.log("Error: Couldnt add calendar data");
			console.log(e);
		}
	}

	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;
	}

	/**
	 * scales calendar, called once on create and on window resize
	 * @param room Room Object
	 */
	function scaleCalendar(room) {
		if (room.config.mode === 3) {
			return;
		}
		var $cal = room.$.calendar;
		if (!$cal.is(':visible')) return;
		room.resizeCalendar = false;
		var columnWidth = $cal.find(".wc-day-1").width();

		if (room.config.scaledaysauto) {
			var result = ($cal.weekCalendar("option", "daysToShow") * columnWidth) / 130;
			result = Math.min(Math.max(Math.abs(result), 1), 7);
			if (result !== $cal.weekCalendar("option", "daysToShow")) {
				$cal.weekCalendar("option", "daysToShow", result);
				columnWidth = $cal.find(".wc-day-1").width();
			}
		}
		if (((!room.config.scaledaysauto) || $cal.weekCalendar("option", "daysToShow") === 1) && columnWidth < 85) {
			$cal.weekCalendar("option", "useShortDayNames", true);
		} else {
			$cal.weekCalendar("option", "useShortDayNames", false);
		}
		var clientHeight = $(window).height();
		if (roomIds.length === 4) {
			clientHeight = clientHeight / 2;
		}

		clientHeight = clientHeight - room.$.header.height()
				- room.$.calendar.find(".wc-time-column-header").height() - 2;

		if (room.config.mode === 1 && room.config.vertical) {

			clientHeight = clientHeight * (room.config.scale / 100);
			clientHeight -= 22;
		}
		clientHeight -= 6;
		var height = clientHeight / (room.openTimes * $cal.weekCalendar("option", "timeslotsPerHour"));

		if (height < 30) {
			height = 30;
		}
		var fontHeight = Math.min(height, columnWidth / 2.1);
		// Scale calendar font
		if (fontHeight > 120) {
			$cal.weekCalendar("option", "textSize", 28);
		}
		else if (fontHeight > 100) {
			$cal.weekCalendar("option", "textSize", 24);
		} else if (fontHeight > 80) {
			$cal.weekCalendar("option", "textSize", 22);
		} else if (fontHeight > 70) {
			$cal.weekCalendar("option", "textSize", 20);
		} else if (fontHeight > 60) {
			$cal.weekCalendar("option", "textSize", 14);
		} else {
			$cal.weekCalendar("option", "textSize", 13);
		}
		$cal.weekCalendar("option", "timeslotHeight", height);
		if (room.timetable) {
			$cal.weekCalendar("option", "data", {events: room.timetable});
			$cal.weekCalendar('refresh');
		}
		$cal.weekCalendar("option", "defaultFreeBusy", {free: !room.openingTimesCalendar});
		if (room.openingTimesCalendar) {
			$cal.weekCalendar("updateFreeBusy", room.openingTimesCalendar);
		}
		$cal.weekCalendar("resizeCalendar");
		$cal.weekCalendar("option", "hourLine", true);
	}

	/**
	 * returns next closing time of a given room
	 * @param room
	 * @returns {Date} Object of next closing
	 */
	function GetNextClosing(room) {
		if (!room.openingTimes || room.openingTimes.length === 0) return null;
		var now = MyDate();
		var day = now.getDay();
		var bestdate = null;
		for (var a = 0; a < 7; a++) {
			var tmp = room.openingTimes[(day + a) % 7];
			if (!tmp) continue;
			for (var i = 0; i < tmp.length; i++) {
				var closeDate = getNextDayOfWeek(now, (day + a) % 7);
				closeDate.setHours(tmp[i].HourClose);
				closeDate.setMinutes(tmp[i].MinutesClose);
				closeDate.setSeconds(0);
				if (closeDate > now) {
					if (!IsOpen(new Date(closeDate.getTime() + 1800000), room)) {
						if (!bestdate || bestdate > closeDate) {
							bestdate = closeDate;
						}
					}
				}
			}
			if (bestdate) return bestdate;
		}
		return null;
	}

	/**
	 * Returns next Opening
	 * @param room Room Object
	 * @returns {Date} Object of next opening
	 */
	function GetNextOpening(room) {
		if (!room.openingTimes) return null;
		var now = MyDate();
		var day = now.getDay();
		var bestdate = null;
		for (var dow = 0; dow < 7; dow++) {
			var tmp = room.openingTimes[(day + dow) % 7];
			if (!tmp) continue;
			for (var i = 0; i < tmp.length; i++) {
				var openDate = getNextDayOfWeek(now, (day + dow) % 7);
				openDate.setHours(tmp[i].HourOpen);
				openDate.setMinutes(tmp[i].MinutesOpen);
				if (openDate > now) {
					if (!IsOpen(new Date(openDate.getTime() - 1800000), room)) {
						if (!bestdate || bestdate > openDate) {
							bestdate = openDate;
						}
					}
				}
			}
			if (bestdate) return bestdate;
		}
		return null;
	}


	/**
	 * Sets the free PCs number in the right corner and updates the square color accordingly
	 * @param room Room
	 */
	function SetFreeSeats(room) {
		// if room has no allowed value, set text in the box to -
		if (!room.numPcs) {
			room.$.seatsCounter.text('\u00A0');
		} else if (!room.state.free) {
			room.$.seatsCounter.text('\u2013');
		} else {
			room.$.seatsCounter.text(room.freePcs);
		}
		if ((room.freePcs > 0 || !room.numPcs) && room.state && room.state.free) {
			room.$.seatsBackground.css('background-color', '#260');
		} else {
			room.$.seatsBackground.css('background-color', 'red');
		}
	}

	/**
	 * Updates the Header of an Room
	 * @param room Room Object
	 */
	function UpdateRoomHeader(room) {
		var tmp = room.getState();
		var same = (tmp === room.lastHeaderState);
		if (!same) {
			room.lastHeaderState = tmp;
		}
		var newText = false, newTime = false;
		var seats = room.freePcs;
		if (tmp.state === 'closed' || tmp.state === 'CalendarEvent' || tmp.state === 'Free') {
			newTime = GetTimeDiferenceAsString(tmp.end, MyDate(), globalConfig);
		}
		if (tmp.state === "closed") {
			if (!same) newText = t("closed");
		} else if (tmp.state === "CalendarEvent") {
			if (!same) newText = tmp.title;
		} else if (tmp.state === "Free" || tmp.state === "FreeNoEnd") {
			if (!same) newText = t("free");
		}
		if (newText !== false) {
			room.$.currentEvent.text(newText);
		}
		if (newTime !== false) {
			room.$.currentRemain.text(t("for") + " " +newTime);
		}
		if (room.lastFreeSeats !== seats || !same) {
			SetFreeSeats(room);
			room.lastFreeSeats = seats;
		}
	}

	/**
	 * 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;
		}

		var closing = GetNextClosing(room);
		var event = getNextEvent(room.timetable);

		// 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: ""
			};
			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;
	}

	/**
	 * Skip to next upcoming day matching the given day of week.
	 * @return {Date}
	 */
	function getNextDayOfWeek(date, dayOfWeek) {
		var resultDate = new Date(date.getTime());
		resultDate.setDate(date.getDate() + (7 + dayOfWeek - date.getDay()) % 7);
		return resultDate;
	}
	/*
    /========================================== Room Layout =============================================
    */


	const picSizeX = 3.8;
	const picSizeY = 3;

	/**
	 * Generates the RoomLayout Div
	 * @param width The width the RoomLayout should have (in percent).
	 * @param room Room Object
	 */
	function generateRoomLayoutDiv(width, room) {
		if ((room.config.vertical && room.config.mode === 1) || (room.config.mode === 3) || (room.config.mode === 4)) {
			width = 100 + "%";
		}
		var $div = $('<div>').prop('id', 'roomLayout_' + room.id).addClass("room-layout").css('width', width);

		if (room.config.mode === 4) {
			$div.hide();
		}
		room.$.container.append($div);
		room.$.layout = $div;
	}

	/**
	 * Main function for generating the Room Layout
	 * @param room Room Object
	 */
	function initRoomLayout(room) {
		var maxX = false, maxY = false;
		var minX = false, minY = false;
		var x, y;

		generateRoomLayoutDiv((100 - room.config.scale) + "%", room);
		var layout = room.layout;
		if (layout === null || !layout.length) {
			return;
		}

		rotateRoom(room.config.rotation, layout);

		for (var i = 0; i < layout.length; i++) {
			x = layout[i].x = parseInt(layout[i].x);
			y = layout[i].y = parseInt(layout[i].y);
			if (isNaN(x) || isNaN(y)) continue;
			if (minX === false || x < minX) {
				minX = x;
			}
			if (minY === false || y < minY) {
				minY = y;
			}
			if (maxX === false || x > maxX) {
				maxX = x;
			}
			if (maxY === false || y > maxY) {
				maxY = y;
			}
		}

		room.minX = minX;
		room.minY = minY;
		room.maxX = maxX + picSizeX;
		room.maxY = maxY + picSizeY;
		room.xDifference = (room.maxX - room.minX);
		room.yDifference = (room.maxY - room.minY);

		setUpRoom(room, layout);
		scaleRoom(room);
		UpdatePc(layout, room);

	}

	/**
	 * Computes offsets and scaling's for the RoomLayout
	 * @param room Room Object
	 */
	function generateOffsetAndScale(room) {
		var clientHeight, clientWidth;

		if (room.config.vertical && room.config.mode === 1) {
			clientHeight = room.$.container.height() - (room.$.calendar.position().top + room.$.calendar.height());
		} else {
			clientHeight = room.$.container.height() - (room.$.header.height() + 5);
		}

		clientWidth = room.$.layout.width();

		var scaleX = clientWidth / picSizeX, scaleY = clientHeight / picSizeY;
		if (room.xDifference > 0) {
			scaleX = (clientWidth - 20) / room.xDifference;
		}
		if (room.yDifference > 0) {
			scaleY = (clientHeight - 20) / room.yDifference;
		}

		room.scale = Math.min(scaleY, scaleX);
		room.xOffset = -room.minX * room.scale + (clientWidth - room.xDifference * room.scale) / 2;
		room.yOffset = -room.minY * room.scale + (clientHeight - room.yDifference * room.scale) / 2;
	}

	/**
	 * adds images for each pc to Room Layout
	 * @param room Room Object
	 * @param layout Layout json
	 */
	function setUpRoom(room, layout) {
		for (var i = 0; i < layout.length; i++) {
			if (!isNaN(layout[i].y) && !isNaN(layout[i].x)) {
				//var $img = $('<img>').prop('id', "pc-img_" + room.id + "_" + layout[i].id).addClass('pc-img');
				var $overlays = $('<div>').addClass('pc-overlay-container');
				layout[i].$div = $('<div>').prop('id', "pc_" + room.id + "_" + layout[i].id).addClass('pc-container');
				layout[i].$div.append($('<div>').addClass('screen-frame').append($('<div>').addClass('screen-inner')));
				layout[i].$div.append($('<div>').addClass('screen-foot1'));
				layout[i].$div.append($('<div>').addClass('screen-foot2'));
				//layout[i].$div.append($overlays).append($img);
				room.$.layout.append(layout[i].$div);

				if (layout[i].overlay && layout[i].overlay.constructor === Array) {
					for (var a = 0; a < layout[i].overlay.length; a++) {
						addOverlay($overlays, layout[i].overlay[a]);
					}
				}
			}
		}
	}

	/**
	 * Generate overlay with given image name.
	 * @param $container container to put overlay into
	 * @param overlayName name of the overlay (image name without ending)
	 */
	function addOverlay($container, overlayName) {
		var imgname;
		for (var i = 0; i < IMG_FORMAT_LIST.length; ++i) {
			if (imageExists("img/overlay/" + overlayName + IMG_FORMAT_LIST[i])) {
				imgname = "img/overlay/" + overlayName + IMG_FORMAT_LIST[i];
				break;
			}

		}
		var $overlay;
		if (!imgname) {
			$overlay = $('<div>');
		} else {
			$overlay = $("<img>").attr('src', imgname);
		}
		$overlay.addClass('overlay').addClass("overlay-" + overlayName);
		$container.append($overlay);
	}


	var imgExists = {};

	/**
	 * checks if images exists on the web server.
	 * result will be cached after fist call.
	 *
	 * @param {String} image_url URL of image to check
	 * @return {Boolean} true iff image exists
	 */
	function imageExists(image_url) {
		if (!imgExists.hasOwnProperty(image_url)) {
			var http = new XMLHttpRequest();
			http.open('HEAD', image_url, false);
			http.send();
			imgExists[image_url] = http.status === 200;
		}
		return imgExists[image_url];

	}

	/**
	 * 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=" + panelUuid,
			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 (globalConfig.ts && globalConfig.ts !== result.ts) {
					// Change
					window.location.reload(true);
				}
				globalConfig.ts = result.ts;
			}
		})
	}

	/**
	 * Queries Pc states
	 */
	function queryRooms() {
		$.ajax({
			url: "{{dirprefix}}api.php?do=locationinfo&get=machines&uuid=" + panelUuid,
			dataType: 'json',
			cache: false,
			timeout: 30000,
			success: function (result) {
				if (!result || result.constructor !== Array) {
					console.log('Warning: get=machines didnt return array');
					return;
				}
				for (var i = 0; i < result.length; i++) {
					UpdatePc(result[i].machines, rooms[result[i].id]);
				}
			}
		})
	}

	/**
	 * Updates the PC's (images) in the room layout. Also Updates how many pc's are free.
	 * @param update array of PCs for one room
	 * @param room Room object
	 */
	function UpdatePc(update, room) {
		if (!room) {
			console.log('Got room update for unknown room, ignored.');
			return;
		}
		if (!update || update.constructor !== Array) {
			console.log('Update data is not array for room ' + room.name);
			console.log(update);
			return;
		}
		var freePcs = 0;
		for (var i = 0; i < update.length; i++) {
			var $div = $("#pc_" + room.id + "_" + update[i].id);
			// Pc free
			if (room.config.roomplanner === true) {
				if ((update[i].pcState === "IDLE" || update[i].pcState === "OFFLINE" || update[i].pcState === "STANDBY") && !isNaN(update[i].x) && !isNaN(update[i].y)) {
					freePcs++;
				}
			} else {
				if ((update[i].pcState === "IDLE" || update[i].pcState === "OFFLINE" || update[i].pcState === "STANDBY")) {
					freePcs++;
				}
			}

			$div.removeClass('BROKEN OFFLINE IDLE OCCUPIED STANDBY'.replace(update[i].pcState, '')).addClass(update[i].pcState);
		}
		room.freePcs = freePcs;
		room.numPcs = update.length;
		UpdateRoomHeader(room);
	}

	/**
	 * Adjust pc coordinate depending on room rotation
	 * @param r Rotation, from 0 - 3 (int)
	 * @param layout Layout json
	 */
	function rotateRoom(r, layout) {
		for (var z = 0; z < r; z++) {
			for (var i = 0; i < layout.length; i++) {
				var x = parseInt(layout[i].x);
				var y = parseInt(layout[i].y);
				layout[i].x = y;
				layout[i].y = -x;
			}
		}
	}

	/**
	 * Positions the computer images in the roomLayout div according to their position and div size
	 * @param room Room object
	 */
	function scaleRoom(room) {
		if (!room.$.layout || !room.$.layout.is(':visible')) return;
		room.resizeRoom = false;
		if (!room.layout) return;
		generateOffsetAndScale(room);
		room.$.layout.css('font-size', Math.floor(room.scale) + 'pt');
		for (var i = 0; i < room.layout.length; i++) {
			var pcWidth = (picSizeX * room.scale) + "px";
			var pcHeight = (picSizeY * room.scale) + "px";
			if (room.layout[i].$div && !isNaN(room.layout[i].y) && !isNaN(room.layout[i].x)) {
				room.layout[i].$div.css({
					width: pcWidth,
					height: pcHeight,
					top: (room.layout[i].y * room.scale + room.yOffset) + "px",
					left: (room.layout[i].x * room.scale + room.xOffset) + "px"
				});
			}
		}
	}

	/*
    /========================================== Misc =============================================
    */
	var resizeTimeout = false;

	// called when browser window changes size
	// scales calendar and room layout accordingly

	$(window).resize(function () {
		if (resizeTimeout !== false) clearTimeout(resizeTimeout);
		resizeTimeout = setTimeout(function () {
			resizeTimeout = false;
			for (var property in rooms) {
				rooms[property].resizeCalendar = true;
				rooms[property].resizeRoom = true;
				scaleCalendar(rooms[property]);
				scaleRoom(rooms[property]);
			}
			SetProgressBarSpeed();
		}, 200);
	});


	/**
	 * returns parameter value from the url
	 *  @param sParam
	 *  @returns boolean|string for given parameter
	 */
	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('=', 2);

			if (sParameterName[0] === sParam) {
				if (sParameterName.length === 1) return true;
				return sParameterName[1];
			}
		}
		return false;
	}

	/**
	 * 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 = {};

	function resizeIfRequired(room) {
		if (room.resizeCalendar) {
			scaleCalendar(room);
		}
		if (room.resizeRoom) {
			scaleRoom(room);
		}
	}


	/**
	 * Used in Mode 4, switches given room from Timetable to room layout and vice versa
	 */
	function switchLayouts() {
		for (var roomKey in rooms) {
			var room = rooms[roomKey];
			if (room.config.mode !== 4) continue;
			if (room.$.layout.is(':visible')) {
				room.$.layout.hide();
				room.$.calendar.show();
			} else {
				room.$.layout.show();
				room.$.calendar.hide();
			}
			resizeIfRequired(room);
		}
		lastSwitchTime = MyDate().getTime();
	}

	var $pbar = false;
	var pbarTimer = false;
	const PX_PER_SEC_TARGET = 10;

	/**
	 * adds a progressbar (id) used in mode 4
	 */
	function generateProgressBar() {
		if ($pbar) return;
		$pbar = $('<div class="progressbar">');
		$('body').append($pbar);
		SetProgressBarSpeed();
	}

	function SetProgressBarSpeed() {
		if (!$pbar || !globalConfig.switchtime) return;
		if (pbarTimer) clearInterval(pbarTimer);
		var interval = 1000;
		if (!globalConfig.eco) {
			var pxPerMSec = $('body').width() / globalConfig.switchtime;
			interval = Math.max(1 / (pxPerMSec / PX_PER_SEC_TARGET), 100);
		}
		pbarTimer = setInterval(function () {
			var width = ((MyDate().getTime() - lastSwitchTime) / globalConfig.switchtime) * 100;
			if (width < 0) width = 0;
			if (width >= 100) {
				width = 100;
				switchLayouts();
			}
			$pbar.width(width + '%');
		}, interval);
	}

</script>
</html>