main.js 7.2 KB

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