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