From 7aa2fcb93c3903f7ccfc64a9eacb01faaec2458c Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 1 Apr 2021 23:42:56 +0200 Subject: [PATCH] Download in client from Blob URL --- js/Browser.js | 1 + js/control/Export.js | 46 ++++++++----- js/util/Diff.js | 124 ----------------------------------- tests/control/Export.test.js | 1 + tests/format/Gpx.test.js | 1 + 5 files changed, 33 insertions(+), 140 deletions(-) delete mode 100644 js/util/Diff.js diff --git a/js/Browser.js b/js/Browser.js index 646cc48..f70a5de 100644 --- a/js/Browser.js +++ b/js/Browser.js @@ -22,5 +22,6 @@ touchScreen: touchScreen, touchScreenDetectable: touchScreenDetectable, touch: touch, + download: 'Blob' in window && 'createObjectURL' in URL && 'download' in document.createElement('a'), }; })(); diff --git a/js/control/Export.js b/js/control/Export.js index b19dd4c..8f94f10 100644 --- a/js/control/Export.js +++ b/js/control/Export.js @@ -15,7 +15,8 @@ BR.Export = L.Class.extend({ var trackname = (this.trackname = document.getElementById('trackname')); this.tracknameAllowedChars = BR.conf.tracknameAllowedChars; - if (this.tracknameAllowedChars) { + // a.download attribute automatically replaces invalid characters + if (!BR.Browser.download && this.tracknameAllowedChars) { this.tracknameMessage = document.getElementById('trackname-message'); var patternRegex = new RegExp('[' + this.tracknameAllowedChars + ']+'); @@ -52,19 +53,33 @@ BR.Export = L.Class.extend({ e.preventDefault(); - if (true) { - var uri = this.router.getUrl(this.latLngs, this.pois.getMarkers(), null, format, nameUri, includeWaypoints); - - // var evt = document.createEvent('MouseEvents'); - // evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - // var link = document.createElement('a'); - // link.href = uri; - // link.dispatchEvent(evt); - //} else { - + if (BR.Browser.download) { const track = this._formatTrack(format, name, includeWaypoints); - console.log('track: ', track); - BR.Diff.diff(uri, track, format); + + const mimeTypeMap = { + gpx: 'application/gpx+xml', + kml: 'application/vnd.google-earth.kml+xml', + geojson: 'application/vnd.geo+json', + csv: 'text/tab-separated-values', + }; + + const mimeType = mimeTypeMap[format]; + + const blob = new Blob([track], { + type: mimeType + ';charset=utf-8', + }); + const objectUrl = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = objectUrl; + link.download = (name || 'brouter') + '.' + format; + link.click(); + } else { + var uri = this.router.getUrl(this.latLngs, this.pois.getMarkers(), null, format, nameUri, includeWaypoints); + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + var link = document.createElement('a'); + link.href = uri; + link.dispatchEvent(evt); } }, @@ -134,7 +149,6 @@ BR.Export = L.Class.extend({ }, _generateTrackname: function () { - return; // TODO remove var trackname = this.trackname; this._getCityAtPosition( this.latLngs[0], @@ -143,7 +157,7 @@ BR.Export = L.Class.extend({ this.latLngs[this.latLngs.length - 1], L.bind(function (to) { var distance = document.getElementById('distance').innerHTML; - if (this.tracknameAllowedChars) { + if (!BR.Browser.download && this.tracknameAllowedChars) { distance = distance.replace(',', '.'); // temp. fix (#202) } if (!from || !to) { @@ -161,7 +175,7 @@ BR.Export = L.Class.extend({ }); } - if (this.tracknameAllowedChars) { + if (!BR.Browser.download && this.tracknameAllowedChars) { // temp. fix: replace and remove characters that will get removed by server quick fix (#194) trackname.value = trackname.value.replace(/[>)]/g, '').replace(/ \(/g, ' - '); this._validationMessage(); diff --git a/js/util/Diff.js b/js/util/Diff.js deleted file mode 100644 index 5cf16b6..0000000 --- a/js/util/Diff.js +++ /dev/null @@ -1,124 +0,0 @@ -BR.Diff = {}; - -// -BR.Diff.diff = function (uri, track, format) { - BR.Util.get( - uri, - ((err, text) => { - if (err) { - console.error('Error exporting "' + profileUrl + '": ' + err); - return; - } - - if (format === 'gpx') { - text = BR.Gpx.pretty(BR.Diff.adoptGpx(text)); - } else if (format === 'geojson') { - text = JSON.stringify(JSON.parse(text), null, 2); - } - var dmp = new diff_match_patch(); - var diff = dmp.diff_main(text, track); - dmp.diff_cleanupSemantic(diff); - - if (dmp.diff_levenshtein(diff) > 0) { - let i = 0; - while (i < diff.length - 2) { - if ( - diff[i][0] === 0 && - diff[i + 1][0] === -1 && - diff[i + 2][0] === 1 && - (/(rteTime|rteSpeed)>\d+\.\d{0,2}$/.test(diff[i][1]) || /time=[0-9h ]*m \d$/.test(diff[i][1])) - ) { - const del = +diff[i + 1][1]; - const ins = +diff[i + 2][1]; - if (Number.isInteger(del) && Number.isInteger(ins) && Math.abs(del - ins) <= 1) { - diff.splice(i + 1, 2); - if (i + 1 < diff.length && diff[i + 1][0] === 0) { - diff[i + 1][1] = diff[i][1] + diff[i + 1][1]; - diff.splice(i, 1); - continue; - } - } - } - i++; - } - } - - if (dmp.diff_levenshtein(diff) > 0) { - //console.log('server: ', text); - //console.log('client: ', track); - console.log(diff); - bootbox.alert(BR.Diff.diffPrettyHtml(diff)); - } else { - console.log('diff equal'); - } - }).bind(this) - ); -}; - -// diff_match_patch.prototype.diff_prettyHtml modified to only show specified number of context lines -BR.Diff.diffPrettyHtml = function (diffs, contextLen = 2) { - var html = []; - var pattern_amp = /&/g; - var pattern_lt = //g; - var pattern_para = /\n/g; - for (var x = 0; x < diffs.length; x++) { - var op = diffs[x][0]; // Operation (insert, delete, equal) - var data = diffs[x][1]; // Text of change. - var text = data - .replace(pattern_amp, '&') - .replace(pattern_lt, '<') - .replace(pattern_gt, '>') - //.replace(pattern_para, '¶
'); - .replace(pattern_para, '
'); - switch (op) { - case DIFF_INSERT: - html[x] = '' + text + ''; - break; - case DIFF_DELETE: - html[x] = '' + text + ''; - break; - case DIFF_EQUAL: - const lines = text.split('
'); - const len = lines.length; - if (len > contextLen * 2) { - text = [...lines.slice(0, contextLen), '...', ...lines.slice(-contextLen)].join('
'); - } - - html[x] = '' + text + ''; - break; - } - } - return html.join(''); -}; - -// TODO remove -// copied from Gpx.test.js -BR.Diff.adoptGpx = function (gpx, replaceCreator = true) { - const creator = 'BRouter-Web 0.15.1'; - const name = 'Track'; - const newline = '\n'; - - gpx = gpx.replace(/=\.(?=\d)/, '=0.'); - if (replaceCreator) { - gpx = gpx.replace(/creator="(?!OsmAndRouter)[^"]*"/, `creator="${creator}"`); - } - gpx = gpx.replace(/creator="([^"]*)" version="1.1"/, 'version="1.1" \n creator="$1"'); - //gpx = gpx.replace(/\n [^<]*<\/name>/, `\n ${name}`); - gpx = gpx - .split(newline) - .map((line) => line.replace(/lon="([^"]*)" lat="([^"]*)"/, 'lat="$2" lon="$1"')) - .join(newline); - gpx = gpx.replace(/(lon|lat)="([-0-9]+.[0-9]+?)0+"/g, '$1="$2"'); // remove trailing zeros - // remove trailing zeros comment-style voicehints - gpx = gpx.replace(/;\s*([-0-9]+.[0-9]+?)0+;/g, (match, p1) => `;${p1.padStart(10)};`); - gpx = gpx.replace(/>([-0-9]+?\.\d*0+)<\//g, (match, p1) => `>${+p1}\n', ''); - - // added - // trunc bc. float precision diffs - gpx = gpx.replace(/(rteTime|rteSpeed)>([^<]*)<\//g, (match, p1, p2) => `${p1}>${(+p2).toFixed(3)}\n?\s*/, ''); // ignore (invalid) double tag - - return gpx; -}; diff --git a/tests/control/Export.test.js b/tests/control/Export.test.js index 72b003d..76bfd67 100644 --- a/tests/control/Export.test.js +++ b/tests/control/Export.test.js @@ -3,6 +3,7 @@ BR.conf = {}; $ = require('jquery'); require('leaflet'); turf = require('@turf/turf'); +require('../../js/Browser.js'); require('../../js/control/Export.js'); const fs = require('fs'); diff --git a/tests/format/Gpx.test.js b/tests/format/Gpx.test.js index 2f73b3a..df9d972 100644 --- a/tests/format/Gpx.test.js +++ b/tests/format/Gpx.test.js @@ -3,6 +3,7 @@ BR.version = '1.5.1'; turf = require('@turf/turf'); togpx = require('togpx'); require('leaflet'); +require('../../js/Browser.js'); require('../../js/format/VoiceHints.js'); require('../../js/format/Xml.js'); require('../../js/format/Gpx.js');