-
-
Notifications
You must be signed in to change notification settings - Fork 9
/
main.js
371 lines (344 loc) · 11.2 KB
/
main.js
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
// Written in ES5 so that it'll work in older browsers without transpilation.
// Probably unnecessary but whatever, don't @ me
(function() {
var timezone = getTimezone();
// Frequency in minutes
var eventFrequency = {
freeRoam: 45,
role: 90
};
// Select DOM elements
var elements = {
freeRoam: {
container: document.getElementById('free-roam-schedule'),
nextEventName: document.getElementById('next-fr-event-name'),
nextEventTime: document.getElementById('next-fr-event-time'),
nextEventEta: document.getElementById('next-fr-event-eta')
},
role: {
container: document.getElementById('role-schedule'),
nextEventName: document.getElementById('next-role-event-name'),
nextEventTime: document.getElementById('next-role-event-time'),
nextEventEta: document.getElementById('next-role-event-eta')
},
locale: document.getElementById('locale')
};
/**
* Update the list of event times
* @param {array} data The list of event times
* @param {string} key Property key (either freeRoam/role)
*/
function updateList(data, key) {
var el = elements[key];
var frequency = minutesToMilliseconds(eventFrequency[key]);
var list = document.createElement('ul');
data.map(function(t, i) {
return calculateEventTimes(t, i + 1, frequency);
})
.sort(function(a, b) {
// Start the list at midnight regardless of the user's timezone
return (a.timeNumber - b.timeNumber);
})
.forEach(function(event) {
var li = document.createElement('li');
if (event.isNext) {
li.classList.add('next-event');
el.nextEventName.innerHTML = event.name;
el.nextEventTime.innerHTML = event.timeString;
el.nextEventEta.innerHTML = event.etaText;
}
li.append(getAnchor(key.toLowerCase() + event.id));
var text = document.createElement('span');
text.innerText = event.timeString + ' - ' + event.name;
li.append(text);
li.append(getFormLink(event, key, event.id));
list.append(li);
});
el.container.innerHTML = list.outerHTML;
}
/**
* Create an anchor link
* @param {Object} event Event datum
* @param {string} key Property key (either freeRoam/role)
* @param {string} id Unique identifier
* @return {Node} anchor link element
*/
function getFormLink(event, key, id) {
var anchor = document.createElement('a');
anchor.setAttribute('target', '_blank');
var eventType = {
freeRoam: 'Free-roam+event',
role: 'Role+event'
};
var qsValues = {
'entry.1897203079': eventType[key],
'entry.1753454597': timezone,
'entry.1235834234': event.timeString,
'entry.1278810820': event.utcTimeString,
'entry.698549775': event.name,
'entry.988863521': String(id)
};
var queryString = Object.keys(qsValues)
.map(function(qsKey) {
return [qsKey, qsValues[qsKey].replace(/\s/g, '+')].join('=');
})
.join('&');
var url =
'https://docs.google.com/forms/d/e/1FAIpQLSeaEdri09zJXnLksx4icLAY70tWGGDqyuPvaQZQMnc4R9R9ag/viewform?usp=pp_url&' +
queryString;
anchor.setAttribute('href', url);
anchor.className = 'form-link';
anchor.innerText = 'Submit correction';
anchor.setAttribute('title', 'Incorrect time? Send me correct details and I\'ll update it.');
return anchor;
}
/**
* Create an anchor link
* @param {string} id Unique identifier
* @return {Node} anchor link element
*/
function getAnchor(id) {
var anchor = document.createElement('a');
anchor.setAttribute('id', id);
anchor.className = 'anchor';
anchor.setAttribute('href', '#' + id);
anchor.innerText = '#';
return anchor;
}
/**
* Convert minutes to milliseconds
* @param {number} t Time in minutes
* @return {number} Time in milliseconds
*/
function minutesToMilliseconds(t) {
return t * 60 * 1000;
}
/**
* Format the event datum and perform time-zone calculations
* @param {Array} d Event datum containing time and name
* @return {Object} Formatted event datum
*/
function calculateEventTimes(d, id, frequency) {
var eventTime = d[0];
var now = Date.now();
var oneDay = minutesToMilliseconds(24 * 60);
var dateTime = getDateTime(now, eventTime);
var eta = dateTime - now;
// Ensure that event dates are not in the past or too far
// in the future, where timezone is not UTC
if (eta > frequency) {
dateTime = getDateTime(now - oneDay, eventTime);
eta = dateTime - now;
}
if (eta <= 0) {
dateTime = getDateTime(now + oneDay, eventTime);
eta = dateTime - now;
}
return {
id: id,
dateTime: dateTime,
name: d[1],
eta: eta,
etaText: getEtaText(eta),
isNext: eta > 0 && eta <= frequency,
timeString: dateTime.toLocaleTimeString('default', {
hour: '2-digit',
minute: '2-digit'
}),
timeNumber: (dateTime.getHours() * 60 + dateTime.getMinutes()),
utcTimeString: eventTime
};
}
/**
* Get the Date object for an event
* @param {number} date Timestamp, e.g. Date.now()
*/
function getDateTime(date, eventTime) {
return new Date(
[new Date(date).toDateString(), eventTime, 'UTC'].join(' ')
);
}
/**
* Display time remaining in minutes or seconds
* @param {number} t Time in milliseconds
* @return {string} e.g. 1 minute, 35 minutes, 1 second, 40 seconds, etc.
*/
function getEtaText(t) {
t = t / 1000; // convert to seconds
function s(t) {
return Math.abs(t) === 1 ? '' : 's';
}
if (Math.abs(t) < 60) {
return Math.round(t) + ' second' + s(t);
}
t = Math.round(t / 60); // convert to minutes
return t + ' minute' + s(t);
}
/**
* Get the name or abbreviation for the user's time zone
* @return {string} e.g. GMT or Greenwich Mean Time or UTC+5
*/
function getTimezone() {
var date = new Date();
// Generate long time-zone name
var longTimeZone = date.toString().match(/\((.+)\)/);
if (longTimeZone) {
return longTimeZone[1];
}
// Fall back to short code if that one is null
var shortTimeZone = date
.toLocaleTimeString('en-us', { timeZoneName: 'short' })
.split(' ');
if (shortTimeZone) {
return shortTimeZone[2];
}
// Finally, fall back to time-zone offset
var timeZoneOffset = date.getTimezoneOffset() / -60;
if (timeZoneOffset > 0) {
timeZoneOffset = '+' + timeZoneOffset;
}
return 'GMT' + timeZoneOffset;
}
/**
* Update the user's time zone in intro paragraph
*/
function showTimeZone() {
elements.locale.innerText = ' (' + timezone + ')';
}
/**
* Format API data
*/
function formatAPIData(data) {
var keys = {
dd_intro: "Dispatch Rider",
fmarch_name: "Master Archer",
fme_at_name: "Wild Animal Tagging",
fme_ce_name: "Condor Egg",
fme_esc_name: "Day of Reckoning",
fme_kotc_variation_ffa_name: "King of the Castle",
fme_kotr_name: "Railroad Baron",
fme_pla_name: "Protect Legendary Animal",
fme_ru_name: "Manhunt",
fme_st_name: "Trade Route",
fme_wk_name: "Salvage",
fme_wp_name: "Wildlife Photographer",
fmechal_variation_bow_name: "Bow Kills Challenge",
fmechal_variation_fishing_lake_name: "Lake Fishing Challenge",
fmechal_variation_fishing_river_name: "River Fishing Challenge",
fmechal_variation_fishing_swamp_name: "Swamp Fishing Challenge",
fmechal_variation_flying_bird_name: "Bird Shooting Contest",
fmechal_variation_headshot_name: "Headshot Kills Challenge",
fmechal_variation_herbalist_name: "Herb Picking Contest",
fmechal_variation_horseback_name: "Horseback Kills Challenge",
fmechal_variation_hunting_name: "Wild Animal Kills Challenge",
fmechal_variation_longarm_name: "Longarm Kills Challenge",
fmechal_variation_sidearm_name: "Sidearm Kills Challenge",
gh_name: "Fool's Gold",
hp_intro: "Cold Dead Hands",
};
return Object.keys(data).map(function(time) {
return [
time,
keys[data[time].text] ||
(data[time].alt || data[time].id)
.replace(/_/g,' ')
.replace(/\b\w/g , function(m){
return m.toUpperCase();
})
]
});
}
/**
* Update both lists
*/
function update(data) {
updateList(data.freeRoam, 'freeRoam');
updateList(data.role, 'role');
}
function initialise(data) {
update(data);
// Update event list every 10 seconds
window.setInterval(update.bind(this, data), 10000);
}
/**
* Fetch API data, and fall back to hard-coded data on error
*/
function requestAPIData() {
fetch('https://api.rdo.gg/events/')
.then(function(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.then(function(data) {
// Modify API data into expected schema
initialise({
freeRoam: formatAPIData(data.standard),
role: formatAPIData(data.themed)
})
})
.catch(function(error) {
console.error(error);
// Fall back to hard-coded list if fetch has been unsuccessful
initialise({
freeRoam: window.rdoEvents.freeRoam,
role: window.rdoEvents.role
})
})
}
// Initialise
showTimeZone();
requestAPIData();
})();
//--- Theme switcher ---//
(function() {
var localStorageKey = 'rdr2-event-schedule-theme';
var THEMES = ['dark', 'light'];
var themeButton = document.querySelector('#theme');
// Detect localstorage value and use that if it exists
var currentTheme = localStorage.getItem(localStorageKey) || THEMES[0];
updateTheme(currentTheme);
function updateTheme(theme, updateStorage) {
THEMES.forEach(function(d) {
document.body.classList.remove(d);
});
document.body.classList.add(theme);
if (updateStorage) {
localStorage.setItem(localStorageKey, theme);
}
currentTheme = theme;
}
// Toggle theme on button click
themeButton.addEventListener('click', function() {
var newTheme = THEMES[0] === currentTheme ? THEMES[1] : THEMES[0];
updateTheme(newTheme, true);
});
})();
//--- Toggle news ---//
(function() {
var localStorageKey = 'rdr2-event-schedule-news';
var newsContainer = document.querySelector('#news');
var newsButton = newsContainer.querySelector('#toggle-news');
var newsLi = newsContainer.querySelectorAll('li:not(:first-child)');
// Detect localstorage value and use that if it exists
var showNews = getLocalStorageValue();
toggleNews();
function getLocalStorageValue() {
var value = localStorage.getItem(localStorageKey);
return value && value === 'true' ? true : false;
}
function toggleNews() {
newsLi.forEach(function(node) {
node.classList.toggle('hide', !showNews);
});
newsButton.innerText = showNews ? 'hide' : 'show all';
}
// Toggle news visibility on button click
newsButton.addEventListener('click', function(event) {
showNews = !showNews;
toggleNews();
localStorage.setItem(localStorageKey, showNews);
event.preventDefault();
});
})();