main.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. "use strict";
  2. let puzzeltocht = undefined;
  3. window.addEventListener("DOMContentLoaded", () => {
  4. puzzeltocht = new Puzzeltocht();
  5. });
  6. class Puzzeltocht {
  7. constructor() {
  8. this.errorCount = 0;
  9. this.joinScreen = new JoinScreen();
  10. this.joinScreen.setJoinListener((e, t) => this.start(e, t));
  11. this.missionScreen = new MissionScreen();
  12. this.missionScreen.setAnswerListener(e => this.answer(e));
  13. this.location = new LocationApi();
  14. console.log("puzzeltocht initialised");
  15. }
  16. start(eventId, teamId) {
  17. this.eventId = this.joinScreen.eventId;
  18. this.teamId = teamId;
  19. document.getElementById("join").style.display = "none";
  20. document.getElementById("mission").style.display = "block";
  21. this.location.enable(p => this.updateLocation(p))
  22. }
  23. answer(a) {
  24. this.lastUpdate = Date.now();
  25. Api.sendUpdate(this.eventId, this.teamId, this.position, a)
  26. .then(m => {
  27. this.missionScreen.update(m);
  28. this.errorCount = 0;
  29. })
  30. .catch(e => this.onError(e));
  31. }
  32. updateLocation(position) {
  33. let l = document.getElementById("location");
  34. if (position.accuracy() > 20 && window.location.hostname !== "localhost") {
  35. l.innerText = "Onbetrouwbare locatie: " + position.string();
  36. return;
  37. }
  38. this.position = position;
  39. l.innerText = position.string();
  40. if (this.lastUpdate === undefined || Date.now() - this.lastUpdate > 5000) {
  41. Api.sendUpdate(this.eventId, this.teamId, position)
  42. .then(m => {
  43. this.missionScreen.update(m);
  44. this.lastUpdate = Date.now();
  45. this.errorCount = 0;
  46. })
  47. .catch(e => this.onError(e));
  48. }
  49. }
  50. onError(e) {
  51. this.errorCount++;
  52. if (this.errorCount > 10) {
  53. this.location.disable();
  54. let l = document.getElementById("location");
  55. l.innerText = "Error!";
  56. }
  57. console.log(e);
  58. }
  59. }
  60. class MissionScreen {
  61. setAnswerListener(callback) {
  62. document.getElementById("answerForm").addEventListener("submit", (e) => MissionScreen.answer(e, callback));
  63. }
  64. static answer(submitEvent, callback) {
  65. submitEvent.preventDefault();
  66. let form = document.getElementById("answerForm");
  67. let answer = form.elements["questionAnwser"].value;
  68. form.elements["questionAnwser"].value = "";
  69. document.getElementById("answerButton").disabled = true;
  70. callback(answer);
  71. }
  72. update(m) {
  73. this.mission = m;
  74. document.getElementById("missionTitle").innerText = m.title + " (" + m.distanceToTarget + "m)";
  75. document.getElementById("missionDescription").innerText = m.description;
  76. if (m.type === "QUESTION") {
  77. document.getElementById("questionMission").style.display = "block";
  78. document.getElementById("answerButton").disabled = false;
  79. } else {
  80. document.getElementById("questionMission").style.display = "none";
  81. }
  82. }
  83. }
  84. class JoinScreen {
  85. constructor() {
  86. Api.fetchEvents()
  87. .then(JoinScreen.showEvents)
  88. .catch(console.log);
  89. }
  90. setJoinListener(callback) {
  91. document.getElementById("joinForm").addEventListener("submit", (e) => this.joinEvent(e, callback));
  92. }
  93. joinEvent(submitEvent, callback) {
  94. submitEvent.preventDefault();
  95. let form = document.getElementById("joinForm");
  96. let teamName = form.elements["teamName"].value;
  97. this.eventId = form.elements["eventId"].value;
  98. if (this.eventId === "" || teamName === "") {
  99. console.log("required form field empty");
  100. return;
  101. }
  102. document.getElementById("joinButton").disabled = true;
  103. Api.join(this.eventId, teamName)
  104. .then(teamId => callback(this.eventId, teamId))
  105. .catch(r => {
  106. console.log(r);
  107. document.getElementById("joinButton").disabled = false;
  108. });
  109. }
  110. static showEvents(eventJson) {
  111. let events = document.getElementById("events");
  112. events.innerHTML = "";
  113. eventJson.forEach((e) => {
  114. events.innerHTML += '<label><input name="eventId" value="' + e.id + '" type="radio"><span>' + e.title + '</span></label>';
  115. });
  116. document.getElementById("joinButton").disabled = false;
  117. }
  118. }
  119. class Api {
  120. static fetchEvents() {
  121. return FetchJson.get("/api/event")
  122. }
  123. static join(eventId, teamName) {
  124. let url = "/api/event/" + encodeURIComponent(eventId) + "/team";
  125. return FetchJson.post(url, teamName);
  126. }
  127. static sendUpdate(eventId, teamId, position, answer = null) {
  128. let body = {
  129. location: {latitude: position.lat(), longitude: position.lon()},
  130. answer: answer,
  131. };
  132. let url = "/api/event/" + encodeURIComponent(eventId) + "/team/" + encodeURIComponent(teamId);
  133. return FetchJson.put(url, body);
  134. }
  135. }
  136. class FetchJson {
  137. static get(url) {
  138. return fetch(url, {method: "GET"})
  139. .then(FetchJson.responseBody)
  140. }
  141. static post(url, body) {
  142. let options = {
  143. method: "POST",
  144. headers: {"Content-Type": "application/json"},
  145. body: JSON.stringify(body)
  146. };
  147. return fetch(url, options)
  148. .then(FetchJson.responseBody)
  149. }
  150. static put(url, body) {
  151. let options = {
  152. method: "PUT",
  153. headers: {"Content-Type": "application/json"},
  154. body: JSON.stringify(body)
  155. };
  156. return fetch(url, options)
  157. .then(FetchJson.responseBody)
  158. }
  159. static responseBody(r) {
  160. if (!r.ok) {
  161. throw Error("HTTP " + r.status + " " + r.statusText + " " + r.url);
  162. }
  163. return r.json();
  164. }
  165. }
  166. class Location {
  167. constructor(pos) {
  168. this.pos = pos;
  169. }
  170. accuracy() {
  171. return this.pos.coords.accuracy.toFixed(1)
  172. }
  173. age() {
  174. return Date.now() - this.pos.timestamp;
  175. }
  176. lat() {
  177. return this.pos.coords.latitude.toFixed(5);
  178. }
  179. lon() {
  180. return this.pos.coords.longitude.toFixed(5);
  181. }
  182. string() {
  183. return "(" + this.lat() + ", " + this.lon() + ")"
  184. + " +-" + this.accuracy() + "m, " + this.age() + "s geleden"
  185. }
  186. }
  187. class LocationApi {
  188. constructor() {
  189. if ("geolocation" in navigator) {
  190. console.log("Geolocation API available");
  191. } else {
  192. console.log("Geolocation API not available");
  193. document.getElementById("unsupported").style.display = "block";
  194. }
  195. if (window.isSecureContext) {
  196. console.log("Secure context available");
  197. } else {
  198. console.log("Secure context not available");
  199. document.getElementById("unsupported").style.display = "block";
  200. }
  201. }
  202. disable() {
  203. console.log("disabling geolocation watch " + this.watchId);
  204. navigator.geolocation.clearWatch(this.watchId);
  205. }
  206. enable(callback) {
  207. const geoOpts = {
  208. enableHighAccuracy: true,
  209. maximumAge: 10000,
  210. timeout: 9500
  211. };
  212. this.watchId = navigator.geolocation.watchPosition(
  213. (pos) => callback(new Location(pos)),
  214. (err) => console.log(err),
  215. geoOpts);
  216. }
  217. }