main.js 5.9 KB

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