1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
/** Next time we need to query the calendar */
var nextCalendarUpdate = 0;
/** Next time we need to redraw the list because the active item changed */
var nextRender = 0;
var calendars = {};
var locations = {};
var DATENOW, NOW;
var globalTimer;
$(document).ready(function () {
if (!globalConfig || globalConfig.constructor !== Object) {
fatalError("Embedded panel config is not valid JSON");
return;
}
initializePanel();
});
function initializePanel() {
if (!globalConfig.locations || globalConfig.locations.constructor !== Array) {
fatalError("Requested panel doesn't contain locations / not array");
return;
}
var locs = 0;
globalConfig.locations.forEach(function (x) {
// filter out if no numeric id, or id already present
if (typeof(x.id) !== 'number' || x.id <= 0 || locations[x.id])
return;
locs++;
locations[x.id] = x;
});
if (locs === 0) {
fatalError("List of location ids is empty");
return;
}
if (globalConfig.time) {
SetUpDate(globalConfig.time);
}
delete globalConfig.time;
delete globalConfig.locations;
sanitizeAndOverrideConfig(globalConfig);
if (!globalTimer) {
mainUpdateLoop();
if (globalConfig.qrcode) {
makeQrCode();
}
}
}
/**
* gets Additional Parameters from the URL, and from the
* downloaded json.
* also makes sure parameters are in a given range
*/
function sanitizeAndOverrideConfig(config) {
sanitizeConfigParam(config, 'calupdate', PARAM_INT, 30, 1, 60, 60 * 1000);
sanitizeConfigParam(config, 'qrcode', PARAM_BOOL, false);
sanitizeConfigParam(config, 'language', PARAM_STRING, 'en');
}
function updateNow() {
DATENOW = MyDate();
NOW = DATENOW.getTime();
}
/**
* queries the Calendar data
*/
function queryCalendars() {
console.log('Querying current calendar data');
if (!PANEL_UUID) return;
var url = API + "get=calendar&uuid=" + PANEL_UUID;
$.ajax({
url: url,
dataType: 'json',
cache: false,
timeout: 30000,
success: queryCalendarsCallback,
error: function () {
// Retry in 5 minutes (300 seconds)
updateNow();
nextCalendarUpdate = NOW + 300000;
}
});
}
var color;
function queryCalendarsCallback(result) {
if (result && result.constructor === Array) {
var l = result.length;
var timeTable = {};
color = 'rgb(' + Math.round(Math.random() * 128 + 127) + ', ' + Math.round(Math.random() * 128 + 127) + ', ' + Math.round(Math.random() * 128 + 127) + ')';
updateNow();
for (var i = 0; i < l; i++) {
timeTable[result[i].id] = processSingleCalendar(result[i].calendar);
}
calendars = mergeCalendars(timeTable);
renderUnifiedEvents();
}
}
const SEVEN_DAYS = 7 * 86400 * 1000;
/**
* Calendar data will be filtered and sorted.
* Expired events will be removed, remaining events sorted by start time, up to 7 days in advance.
*
* @param {Array} json - The new event data in JSON format to update the calendar.
*/
function processSingleCalendar(json) {
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");
}
json = json.filter(function (el) {
if (!el.title || !el.start || !el.end) return false;
el.s = new Date(el.start).getTime();
el.e = new Date(el.end).getTime();
return !(isNaN(el.s) || isNaN(el.e) || Math.abs(el.s - NOW) > SEVEN_DAYS || Math.abs(el.e) < NOW);
});
if (json.length === 0) {
console.log('Notice: Calendar has no current events');
}
json.sort(function (a, b) { return a.s - b.s; });
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);
}
}
return json;
}
/**
* Takes one or more calendars and merges the individual eventsfrom them, sorted ascending
* by start time. The inputcalendars are assumed to be sorted in ascending order already.
* If multiple calendars contain an event with matching start/end times and title, it is
* assumed to be the same event spread across multiple locations, so the event will be
* merged, and the location names concatenated.
* @param {Array} calendars - An array of calendars to be merged.
* @returns {Array} - Returns a new array containing the merged calendars.
*/
function mergeCalendars(calendars) {
var k, tt, merged = [];
var nextRefresh = null;
do {
var smallest = false;
for (k in calendars) {
if (!calendars.hasOwnProperty(k))
continue;
tt = calendars[k];
if (!tt || tt.length === 0 || tt[0].e < NOW)
continue;
if (smallest === false || tt[0].s < calendars[smallest][0].s) {
smallest = k;
}
}
if (smallest) {
var v = calendars[smallest].shift();
v.location = locations[smallest] ? locations[smallest].name : '???';
if (merged.length !== 0) {
var cur = merged.pop();
if (cur.s === v.s && cur.e === v.e && cur.title === v.title) {
v.location += ', ' + cur.location;
} else {
merged.push(cur);
}
}
merged.push(v);
}
} while (smallest);
return merged;
}
function pad(number, digits) {
var s = "00" + number;
return s.substr(s.length - digits);
}
function formatTime(dateObj) {
return pad(dateObj.getHours(), 2) + ":" + pad(dateObj.getMinutes(), 2);
}
function makeQrCode() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'modules/locationinfo/frontend/qrcode.min.js';
console.log("MakeQR");
var callback = function() {
console.log("Script cb");
var $code = $('<div id="qrcode">');
$('body').append($code);
setTimeout(function() {
new QRCode($code[0], {
text: window.location.href.replace(/([#&])qrcode=\d+/, '$1').replace(/[&#]+$/, ''),
width: $code.width() * 4,
height: $code.height() * 4,
correctLevel : QRCode.CorrectLevel.L
});
}, 1);
}
script.onload = function () {
this.onload = null;
this.onreadystatechange = null;
callback();
};
script.onreadystatechange = function () {
if (this.readyState === 'loaded' || this.readyState === 'complete') {
this.onload = null;
this.onreadystatechange = null;
callback();
}
};
document.getElementsByTagName('head')[0].appendChild(script);
}
//
/**
* Main Update loop, this loop runs every 10 seconds
*/
function mainUpdateLoop() {
updateNow();
if (nextCalendarUpdate < NOW) {
nextCalendarUpdate = NOW + globalConfig.calupdate;
queryCalendars();
} else if (nextRender < NOW) {
renderUnifiedEvents();
} else {
queryPanelChange(API, PANEL_UUID, globalConfig);
}
var nx = Math.min(nextCalendarUpdate, nextRender) - NOW;
nx = Math.max(1000, nx);
nx = Math.min(15000, nx);
$('#time .date-today').text(t('long' + DATENOW.getDay()) + ', ' + DATENOW.toLocaleDateString(LANGUAGE));
$('#time .time-today').text(formatTime(DATENOW));
globalTimer = setTimeout(mainUpdateLoop, nx);
}
|