update appCache
[lantea.git] / js / ui.js
CommitLineData
a7393a71
RK
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
23cd2dcc 4
41e2dba2 5// Get the best-available objects for indexedDB and requestAnimationFrame.
3d99a6fd 6window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
41e2dba2 7window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
993fd081 8
41e2dba2 9var mainDB;
16e4f664 10var gAppInitDone = false;
7a549148 11var gUIHideCountdown = 0;
4b1d0915 12var gWaitCounter = 0;
026c4f46 13var gTrackUpdateInterval;
68afcd96 14var gAction, gActionLabel;
da6dad24
RK
15var gOSMAPIURL = "https://api.openstreetmap.org/";
16var gOSMOAuthData = {
17 oauth_consumer_key: "6jjWwlbhGqyYeCdlFE1lTGG6IRGOv1yKpFxkcq2z",
18 oauth_secret: "A21gUeDM6mdoQgbA9uF7zJ13sbUQrNG7QQ4oSrKA",
19 url: "https://www.openstreetmap.org",
20 landing: "auth-done.html",
21}
7a549148 22
b47b4a65 23window.onload = function() {
68afcd96
RK
24 gAction = document.getElementById("action");
25 gActionLabel = document.getElementById("actionlabel");
26
b47b4a65
RK
27 var mSel = document.getElementById("mapSelector");
28 for (var mapStyle in gMapStyles) {
29 var opt = document.createElement("option");
30 opt.value = mapStyle;
31 opt.text = gMapStyles[mapStyle].name;
32 mSel.add(opt, null);
33 }
23cd2dcc 34
ecde0af2 35 var areas = document.getElementsByClassName("overlayArea");
7a549148
RK
36 for (var i = 0; i <= areas.length - 1; i++) {
37 areas[i].addEventListener("mouseup", uiEvHandler, false);
38 areas[i].addEventListener("mousemove", uiEvHandler, false);
39 areas[i].addEventListener("mousedown", uiEvHandler, false);
40 areas[i].addEventListener("mouseout", uiEvHandler, false);
41
42 areas[i].addEventListener("touchstart", uiEvHandler, false);
43 areas[i].addEventListener("touchmove", uiEvHandler, false);
44 areas[i].addEventListener("touchend", uiEvHandler, false);
45 areas[i].addEventListener("touchcancel", uiEvHandler, false);
46 areas[i].addEventListener("touchleave", uiEvHandler, false);
47 }
48
1222624d
RK
49 document.getElementById("body").addEventListener("keydown", uiEvHandler, false);
50
8e901dce 51 if (navigator.platform.length == "") {
b91b74a7
RK
52 // For Firefox OS, don't display the "save" button.
53 // Do this by setting the debugHide class for testing in debug mode.
54 document.getElementById("saveTrackButton").classList.add("debugHide");
c4d0569c
RK
55 }
56
43255174
RK
57 // Without OAuth, the login data is useless
58 //document.getElementById("uploadSettingsArea").classList.remove("debugHide");
59 // As login data is useless for now, always enable upload button
60 document.getElementById("uploadTrackButton").disabled = false;
61
c4d0569c 62 if (gDebug) {
43255174 63 // Note that GPX upload returns an error 500 on the dev API right now.
c4d0569c 64 gOSMAPIURL = "http://api06.dev.openstreetmap.org/";
b91b74a7 65 }
7a549148 66
582d50fc
RK
67 gAction.addEventListener("dbinit-done", initMap, false);
68 gAction.addEventListener("mapinit-done", postInit, false);
69 console.log("starting DB init...");
993fd081 70 initDB();
582d50fc 71}
4b1d0915 72
582d50fc
RK
73function postInit(aEvent) {
74 gAction.removeEventListener(aEvent.type, postInit, false);
75 console.log("init done, draw map.");
76 gMapPrefsLoaded = true;
16e4f664 77 gAppInitDone = true;
14acbcf7
RK
78 //gMap.resizeAndDraw(); <-- HACK: This triggers bug 1001853, work around with a delay.
79 window.setTimeout(gMap.resizeAndDraw, 100);
582d50fc
RK
80 gActionLabel.textContent = "";
81 gAction.style.display = "none";
82 setTracking(document.getElementById("trackCheckbox"));
83 gPrefs.get(gDebug ? "osm_dev_user" : "osm_user", function(aValue) {
84 if (aValue) {
85 document.getElementById("uploadUser").value = aValue;
86 document.getElementById("uploadTrackButton").disabled = false;
4b1d0915 87 }
582d50fc
RK
88 });
89 gPrefs.get(gDebug ? "osm_dev_pwd" : "osm_pwd", function(aValue) {
90 var upwd = document.getElementById("uploadPwd");
91 if (aValue)
92 document.getElementById("uploadPwd").value = aValue;
93 });
b47b4a65
RK
94}
95
96window.onresize = function() {
ac6286bd 97 gMap.resizeAndDraw();
b47b4a65
RK
98}
99
582d50fc 100function initDB(aEvent) {
993fd081 101 // Open DB.
582d50fc
RK
102 if (aEvent)
103 gAction.removeEventListener(aEvent.type, initDB, false);
1222624d 104 var request = window.indexedDB.open("MainDB-lantea", 2);
993fd081
RK
105 request.onerror = function(event) {
106 // Errors can be handled here. Error codes explain in:
107 // https://developer.mozilla.org/en/IndexedDB/IDBDatabaseException#Constants
915d4271
RK
108 if (gDebug)
109 console.log("error opening mainDB: " + event.target.errorCode);
993fd081
RK
110 };
111 request.onsuccess = function(event) {
993fd081 112 mainDB = request.result;
582d50fc
RK
113 var throwEv = new CustomEvent("dbinit-done");
114 gAction.dispatchEvent(throwEv);
993fd081
RK
115 };
116 request.onupgradeneeded = function(event) {
117 mainDB = request.result;
a8634d37 118 var ver = mainDB.version || 0; // version is empty string for a new DB
915d4271
RK
119 if (gDebug)
120 console.log("mainDB has version " + ver + ", upgrade needed.");
121 if (!mainDB.objectStoreNames.contains("prefs")) {
a8634d37
RK
122 // Create a "prefs" objectStore.
123 var prefsStore = mainDB.createObjectStore("prefs");
915d4271
RK
124 }
125 if (!mainDB.objectStoreNames.contains("track")) {
a8634d37
RK
126 // Create a "track" objectStore.
127 var trackStore = mainDB.createObjectStore("track", {autoIncrement: true});
128 }
915d4271 129 if (!mainDB.objectStoreNames.contains("tilecache")) {
a8634d37
RK
130 // Create a "tilecache" objectStore.
131 var tilecacheStore = mainDB.createObjectStore("tilecache");
132 }
993fd081
RK
133 mainDB.onversionchange = function(event) {
134 mainDB.close();
135 mainDB = undefined;
136 initDB();
137 };
138 };
139}
140
7a549148
RK
141function showUI() {
142 if (gUIHideCountdown <= 0) {
143 var areas = document.getElementsByClassName('overlayArea');
144 for (var i = 0; i <= areas.length - 1; i++) {
145 areas[i].classList.remove("hidden");
146 }
147 setTimeout(maybeHideUI, 1000);
148 }
149 gUIHideCountdown = 5;
150}
151
152function maybeHideUI() {
153 gUIHideCountdown--;
154 if (gUIHideCountdown <= 0) {
155 var areas = document.getElementsByClassName('overlayArea');
156 for (var i = 0; i <= areas.length - 1; i++) {
157 areas[i].classList.add("hidden");
158 }
159 }
160 else {
161 setTimeout(maybeHideUI, 1000);
162 }
163}
164
026c4f46
RK
165function updateTrackInfo() {
166 document.getElementById("trackLengthNum").textContent = calcTrackLength().toFixed(1);
167 var duration = calcTrackDuration();
168 var durationM = Math.round(duration/60);
169 var durationH = Math.floor(durationM/60); durationM = durationM - durationH * 60;
170 document.getElementById("trackDurationH").style.display = durationH ? "inline" : "none";
171 document.getElementById("trackDurationHNum").textContent = durationH;
172 document.getElementById("trackDurationMNum").textContent = durationM;
173}
174
993fd081
RK
175function toggleTrackArea() {
176 var fs = document.getElementById("trackArea");
123b3142
RK
177 if (fs.classList.contains("hidden")) {
178 fs.classList.remove("hidden");
7a549148 179 showUI();
026c4f46 180 gTrackUpdateInterval = setInterval(updateTrackInfo, 1000);
993fd081
RK
181 }
182 else {
026c4f46 183 clearInterval(gTrackUpdateInterval);
123b3142 184 fs.classList.add("hidden");
993fd081
RK
185 }
186}
187
b47b4a65 188function toggleSettings() {
993fd081 189 var fs = document.getElementById("settingsArea");
123b3142
RK
190 if (fs.classList.contains("hidden")) {
191 fs.classList.remove("hidden");
7a549148 192 showUI();
b47b4a65
RK
193 }
194 else {
123b3142 195 fs.classList.add("hidden");
b47b4a65
RK
196 }
197}
99631a75 198
c5378747
RK
199function toggleFullscreen() {
200 if ((document.fullScreenElement && document.fullScreenElement !== null) ||
201 (document.mozFullScreenElement && document.mozFullScreenElement !== null) ||
202 (document.webkitFullScreenElement && document.webkitFullScreenElement !== null)) {
203 if (document.cancelFullScreen) {
204 document.cancelFullScreen();
205 } else if (document.mozCancelFullScreen) {
206 document.mozCancelFullScreen();
207 } else if (document.webkitCancelFullScreen) {
208 document.webkitCancelFullScreen();
209 }
210 }
211 else {
212 var elem = document.getElementById("body");
213 if (elem.requestFullScreen) {
214 elem.requestFullScreen();
215 } else if (elem.mozRequestFullScreen) {
216 elem.mozRequestFullScreen();
217 } else if (elem.webkitRequestFullScreen) {
218 elem.webkitRequestFullScreen();
219 }
220 }
221}
222
43255174
RK
223function showUploadDialog() {
224 var dia = document.getElementById("dialogArea");
225 var areas = dia.children;
226 for (var i = 0; i <= areas.length - 1; i++) {
227 areas[i].style.display = "none";
228 }
229 document.getElementById("uploadDialog").style.display = "block";
230 document.getElementById("uploadTrackButton").disabled = true;
231 dia.classList.remove("hidden");
232}
233
ecde0af2
RK
234function showGLWarningDialog() {
235 var dia = document.getElementById("dialogArea");
236 var areas = dia.children;
237 for (var i = 0; i <= areas.length - 1; i++) {
238 areas[i].style.display = "none";
239 }
240 document.getElementById("noGLwarning").style.display = "block";
241 dia.classList.remove("hidden");
242}
243
43255174
RK
244function cancelDialog() {
245 document.getElementById("dialogArea").classList.add("hidden");
246 document.getElementById("uploadTrackButton").disabled = false;
247}
248
7a549148
RK
249var uiEvHandler = {
250 handleEvent: function(aEvent) {
251 var touchEvent = aEvent.type.indexOf('touch') != -1;
252
253 switch (aEvent.type) {
254 case "mousedown":
255 case "touchstart":
256 case "mousemove":
257 case "touchmove":
258 case "mouseup":
259 case "touchend":
1222624d 260 case "keydown":
7a549148
RK
261 showUI();
262 break;
263 }
264 }
265};
266
8389557a
RK
267function setUploadField(aField) {
268 switch (aField.id) {
269 case "uploadUser":
c4d0569c 270 gPrefs.set(gDebug ? "osm_dev_user" : "osm_user", aField.value);
8389557a
RK
271 document.getElementById("uploadTrackButton").disabled = !aField.value.length;
272 break;
273 case "uploadPwd":
c4d0569c 274 gPrefs.set(gDebug ? "osm_dev_pwd" : "osm_pwd", aField.value);
8389557a
RK
275 break;
276 }
277}
278
99631a75
RK
279function makeISOString(aTimestamp) {
280 // ISO time format is YYYY-MM-DDTHH:mm:ssZ
281 var tsDate = new Date(aTimestamp);
362a6833 282 // Note that .getUTCMonth() returns a number between 0 and 11 (0 for January)!
99631a75 283 return tsDate.getUTCFullYear() + "-" +
362a6833 284 (tsDate.getUTCMonth() < 9 ? "0" : "") + (tsDate.getUTCMonth() + 1 ) + "-" +
99631a75
RK
285 (tsDate.getUTCDate() < 10 ? "0" : "") + tsDate.getUTCDate() + "T" +
286 (tsDate.getUTCHours() < 10 ? "0" : "") + tsDate.getUTCHours() + ":" +
287 (tsDate.getUTCMinutes() < 10 ? "0" : "") + tsDate.getUTCMinutes() + ":" +
288 (tsDate.getUTCSeconds() < 10 ? "0" : "") + tsDate.getUTCSeconds() + "Z";
289}
290
8389557a
RK
291function convertTrack(aTargetFormat) {
292 var out = "";
293 switch (aTargetFormat) {
294 case "gpx":
295 out += '<?xml version="1.0" encoding="UTF-8" ?>' + "\n\n";
296 out += '<gpx version="1.0" creator="Lantea" xmlns="http://www.topografix.com/GPX/1/0">' + "\n";
297 if (gTrack.length) {
298 out += ' <trk>' + "\n";
993fd081 299 out += ' <trkseg>' + "\n";
8389557a
RK
300 for (var i = 0; i < gTrack.length; i++) {
301 if (gTrack[i].beginSegment && i > 0) {
302 out += ' </trkseg>' + "\n";
303 out += ' <trkseg>' + "\n";
304 }
305 out += ' <trkpt lat="' + gTrack[i].coords.latitude + '" lon="' +
306 gTrack[i].coords.longitude + '">' + "\n";
307 if (gTrack[i].coords.altitude) {
308 out += ' <ele>' + gTrack[i].coords.altitude + '</ele>' + "\n";
309 }
310 out += ' <time>' + makeISOString(gTrack[i].time) + '</time>' + "\n";
311 out += ' </trkpt>' + "\n";
312 }
313 out += ' </trkseg>' + "\n";
314 out += ' </trk>' + "\n";
993fd081 315 }
8389557a
RK
316 out += '</gpx>' + "\n";
317 break;
318 case "json":
319 out = JSON.stringify(gTrack);
320 break;
321 default:
322 break;
323 }
324 return out;
325}
326
327function saveTrack() {
328 if (gTrack.length) {
329 var outDataURI = "data:application/gpx+xml," +
330 encodeURIComponent(convertTrack("gpx"));
99631a75
RK
331 window.open(outDataURI, 'GPX Track');
332 }
333}
993fd081 334
4b12da3a
RK
335function saveTrackDump() {
336 if (gTrack.length) {
8389557a
RK
337 var outDataURI = "data:application/json," +
338 encodeURIComponent(convertTrack("json"));
4b12da3a
RK
339 window.open(outDataURI, 'JSON dump');
340 }
341}
342
8389557a 343function uploadTrack() {
6ddefbf9 344 // Hide all areas in the dialog.
43255174
RK
345 var dia = document.getElementById("dialogArea");
346 var areas = dia.children;
347 for (var i = 0; i <= areas.length - 1; i++) {
348 areas[i].style.display = "none";
349 }
6ddefbf9
RK
350 // Reset all the fields in the status area.
351 document.getElementById("uploadStatusCloseButton").disabled = true;
352 document.getElementById("uploadInProgress").style.display = "block";
353 document.getElementById("uploadSuccess").style.display = "none";
d0c62ee0 354 document.getElementById("uploadFailed").style.display = "none";
6ddefbf9 355 document.getElementById("uploadError").style.display = "none";
d0c62ee0 356 document.getElementById("uploadErrorMsg").textContent = "";
6ddefbf9 357 // Now show the status area.
43255174
RK
358 document.getElementById("uploadStatus").style.display = "block";
359
8389557a 360 // See http://wiki.openstreetmap.org/wiki/Api06#Uploading_traces
43255174
RK
361 var trackBlob = new Blob([convertTrack("gpx")],
362 { "type" : "application/gpx+xml" });
8389557a
RK
363 var formData = new FormData();
364 formData.append("file", trackBlob);
43255174
RK
365 var desc = document.getElementById("uploadDesc").value;
366 formData.append("description",
367 desc.length ? desc : "Track recorded via Lantea Maps");
8389557a 368 //formData.append("tags", "");
43255174
RK
369 formData.append("visibility",
370 document.getElementById("uploadVisibility").value);
da6dad24
RK
371
372/* GPS trace upload API still only supports HTTP Basic Auth. This below would be OAuth code to try.
373 // Init OSM Auth, see https://github.com/osmlab/osm-auth
374 var auth = osmAuth({
375 oauth_consumer_key: gOSMOAuthData.oauth_consumer_key,
376 oauth_secret: gOSMOAuthData.oauth_secret,
377 url: gOSMOAuthData.url,
378 landing: gOSMOAuthData.landing,
379 auto: true // show a login form if the user is not authenticated and
380 // you try to do a call
381 });
382
383 // Do an authenticate request first, so that we actuall do the login.
384 if (!auth.authenticated) {
385 auth.authenticate(function(err, xhrresponse) {
386 if (err) {
387 reportUploadStatus(false);
388 }
389 else {
390 reportUploadStatus(true);
391 }
392 });
393 }
394 if (!auth.authenticated) {
395 reportUploadStatus(false);
396 return;
397 }
398 // Only now do the actual upload.
399 auth.xhr({
400 method: "POST",
401 path: "/api/0.6/gpx/create",
402 content: formData,
403 options: {"header": {"Content-Type": "multipart/form-data"}},
404 },
405 function(err, xhrresponse) {
406 if (err) {
407 reportUploadStatus(false);
408 }
409 else {
410 reportUploadStatus(true);
411 }
412 }
413 );
414*/
415
0494a6db
RK
416 // Do an empty POST request first, so that we don't send everything,
417 // then ask for credentials, and then send again.
418 var hXHR = new XMLHttpRequest();
419 hXHR.onreadystatechange = function() {
fdaf08db 420 if (hXHR.readyState == 4 && (hXHR.status == 200 || hXHR.status == 400)) {
0494a6db
RK
421 // 400 is Bad Request, but that's expected as this was empty.
422 // So far so good, init actual upload.
423 var XHR = new XMLHttpRequest();
424 XHR.onreadystatechange = function() {
425 if (XHR.readyState == 4 && XHR.status == 200) {
426 // Everthing looks fine.
427 reportUploadStatus(true);
428 } else if (XHR.readyState == 4 && XHR.status != 200) {
429 // Fetched the wrong page or network error...
430 reportUploadStatus(false);
431 }
432 };
433 XHR.open("POST", gOSMAPIURL + "api/0.6/gpx/create", true);
434 // Cross-Origin XHR doesn't allow username/password (HTTP Auth).
435 // So, we'll ask the user for entering credentials with rather ugly UI.
436 XHR.withCredentials = true;
437 try {
438 XHR.send(formData); // Send actual form data.
439 }
440 catch (e) {
441 reportUploadStatus(false, e);
442 }
443 } else if (hXHR.readyState == 4 && hXHR.status != 200) {
444 // Fetched the wrong page or network error...
8389557a
RK
445 reportUploadStatus(false);
446 }
447 };
0494a6db 448 hXHR.open("POST", gOSMAPIURL + "api/0.6/gpx/create", true);
43255174
RK
449 // Cross-Origin XHR doesn't allow username/password (HTTP Auth).
450 // So, we'll ask the user for entering credentials with rather ugly UI.
0494a6db 451 hXHR.withCredentials = true;
8389557a 452 try {
0494a6db 453 hXHR.send(); // Empty request, see above.
8389557a
RK
454 }
455 catch (e) {
456 reportUploadStatus(false, e);
457 }
458}
459
460function reportUploadStatus(aSuccess, aMessage) {
43255174
RK
461 document.getElementById("uploadStatusCloseButton").disabled = false;
462 document.getElementById("uploadInProgress").style.display = "none";
463 if (aSuccess) {
464 document.getElementById("uploadSuccess").style.display = "block";
465 }
466 else if (aMessage) {
467 document.getElementById("uploadErrorMsg").textContent = aMessage;
468 document.getElementById("uploadError").style.display = "block";
469 }
470 else {
471 document.getElementById("uploadFailed").style.display = "block";
472 }
8389557a
RK
473}
474
993fd081
RK
475var gPrefs = {
476 objStore: "prefs",
477
478 get: function(aKey, aCallback) {
479 if (!mainDB)
480 return;
481 var transaction = mainDB.transaction([this.objStore]);
482 var request = transaction.objectStore(this.objStore).get(aKey);
483 request.onsuccess = function(event) {
484 aCallback(request.result, event);
485 };
486 request.onerror = function(event) {
487 // Errors can be handled here.
488 aCallback(undefined, event);
489 };
490 },
491
492 set: function(aKey, aValue, aCallback) {
493 if (!mainDB)
494 return;
495 var success = false;
d4ccddb8 496 var transaction = mainDB.transaction([this.objStore], "readwrite");
993fd081 497 var objStore = transaction.objectStore(this.objStore);
3610c22d 498 var request = objStore.put(aValue, aKey);
993fd081
RK
499 request.onsuccess = function(event) {
500 success = true;
501 if (aCallback)
502 aCallback(success, event);
503 };
504 request.onerror = function(event) {
505 // Errors can be handled here.
506 if (aCallback)
507 aCallback(success, event);
508 };
509 },
510
511 unset: function(aKey, aCallback) {
512 if (!mainDB)
513 return;
514 var success = false;
d4ccddb8 515 var transaction = mainDB.transaction([this.objStore], "readwrite");
993fd081
RK
516 var request = transaction.objectStore(this.objStore).delete(aKey);
517 request.onsuccess = function(event) {
518 success = true;
519 if (aCallback)
520 aCallback(success, event);
521 };
522 request.onerror = function(event) {
523 // Errors can be handled here.
524 if (aCallback)
525 aCallback(success, event);
526 }
527 }
528};
529
530var gTrackStore = {
531 objStore: "track",
532
533 getList: function(aCallback) {
534 if (!mainDB)
535 return;
536 var transaction = mainDB.transaction([this.objStore]);
537 var objStore = transaction.objectStore(this.objStore);
538 if (objStore.getAll) { // currently Mozilla-specific
539 objStore.getAll().onsuccess = function(event) {
540 aCallback(event.target.result);
541 };
542 }
543 else { // Use cursor (standard method).
544 var tPoints = [];
545 objStore.openCursor().onsuccess = function(event) {
546 var cursor = event.target.result;
547 if (cursor) {
548 tPoints.push(cursor.value);
549 cursor.continue();
550 }
551 else {
552 aCallback(tPoints);
553 }
554 };
555 }
556 },
557
6ddefbf9
RK
558 getListStepped: function(aCallback) {
559 if (!mainDB)
560 return;
561 var transaction = mainDB.transaction([this.objStore]);
562 var objStore = transaction.objectStore(this.objStore);
563 // Use cursor in reverse direction (so we get the most recent position first)
564 objStore.openCursor(null, "prev").onsuccess = function(event) {
565 var cursor = event.target.result;
566 if (cursor) {
567 aCallback(cursor.value);
568 cursor.continue();
569 }
570 else {
571 aCallback(null);
572 }
573 };
574 },
575
993fd081
RK
576 push: function(aValue, aCallback) {
577 if (!mainDB)
578 return;
d4ccddb8 579 var transaction = mainDB.transaction([this.objStore], "readwrite");
993fd081
RK
580 var objStore = transaction.objectStore(this.objStore);
581 var request = objStore.add(aValue);
582 request.onsuccess = function(event) {
583 if (aCallback)
584 aCallback(request.result, event);
585 };
586 request.onerror = function(event) {
587 // Errors can be handled here.
588 if (aCallback)
589 aCallback(false, event);
590 };
591 },
592
593 clear: function(aCallback) {
594 if (!mainDB)
595 return;
596 var success = false;
d4ccddb8 597 var transaction = mainDB.transaction([this.objStore], "readwrite");
993fd081
RK
598 var request = transaction.objectStore(this.objStore).clear();
599 request.onsuccess = function(event) {
600 success = true;
601 if (aCallback)
602 aCallback(success, event);
603 };
604 request.onerror = function(event) {
605 // Errors can be handled here.
606 if (aCallback)
607 aCallback(success, event);
608 }
609 }
610};