Share Dialog with QR Code (#729)
This commit is contained in:
parent
c715d2bab1
commit
3b3357c473
8 changed files with 401 additions and 7 deletions
|
|
@ -7,6 +7,7 @@
|
|||
"settings": {
|
||||
"polyfills": [
|
||||
"URL",
|
||||
"URLSearchParams",
|
||||
"Promise",
|
||||
"navigator",
|
||||
"Uint8Array",
|
||||
|
|
|
|||
|
|
@ -144,3 +144,7 @@ Copyright (c) 2018 Norbert Renner and [contributors](https://github.com/nrenner/
|
|||
[The Unlicense](https://github.com/osmlab/leaflet-osm-notes/blob/gh-pages/LICENSE)
|
||||
- [Fetch polyfill](https://github.com/Github/fetch)
|
||||
Copyright (c) 2014-2016 GitHub, Inc.; [MIT License](https://github.com/github/fetch/blob/master/LICENSE)
|
||||
- [qrcodejs](https://github.com/llyys/qrcodejs)
|
||||
Copyright (c) 2012 davidshimjs [The MIT License](https://github.com/llyys/qrcodejs/blob/master/LICENSE)
|
||||
- [Bootstrap Icons](https://github.com/twbs/icons)
|
||||
Copyright (c) 2019-2023 The Bootstrap Authors [The MIT License](https://github.com/twbs/icons/blob/main/LICENSE)
|
||||
|
|
|
|||
|
|
@ -527,6 +527,68 @@ button.deactivate-beeline-active.disabled {
|
|||
margin: -6px 0 6px 20px;
|
||||
}
|
||||
|
||||
/* Share Dialog */
|
||||
|
||||
#share-qrcode-img img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.btn-group-xs > .btn,
|
||||
.btn-xs {
|
||||
padding: 0.1rem 0.4rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.btn-facebook {
|
||||
background-color: #3b5998;
|
||||
border-color: #3b5998;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-facebook:hover {
|
||||
background-color: #2d4373;
|
||||
border-color: #2d4373;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-whatsapp {
|
||||
background-color: #25d366;
|
||||
border-color: #25d366;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-whatsapp:hover {
|
||||
background-color: #1da851;
|
||||
border-color: #1da851;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-email {
|
||||
background-color: #e74c3c;
|
||||
border-color: #e74c3c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-email:hover {
|
||||
background-color: #c0392b;
|
||||
border-color: #c0392b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-mastodon {
|
||||
background-color: #2b90d9;
|
||||
border-color: #2b90d9;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-mastodon:hover {
|
||||
background-color: #1d5bbf;
|
||||
border-color: #1d5bbf;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* "What's new?" (Change Log) */
|
||||
#whatsnew h2 {
|
||||
margin: 2rem 0;
|
||||
|
|
|
|||
154
index.html
154
index.html
|
|
@ -68,6 +68,35 @@
|
|||
<span data-i18n="navbar.export">Export</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
data-toggle="modal"
|
||||
data-target="#share-dialog"
|
||||
id="shareButton"
|
||||
href="#"
|
||||
role="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
data-i18n="[title]keyboard.generic-shortcut"
|
||||
data-i18n-options='{ "action": "$t(navbar.share-tooltip)", "key": "A" }'
|
||||
title="Share Route"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.499 2.499 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
<span data-i18n="navbar.share">Share</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item dropdown">
|
||||
<a
|
||||
class="nav-link dropdown-toggle"
|
||||
|
|
@ -549,6 +578,131 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share modal window -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="share-dialog"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="Share route window"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" data-i18n="share.title">Share Route</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p data-i18n="share.introduction">
|
||||
Share this route using a QR Code or by copying the link to the clipboard. The QR Code opens
|
||||
the route on your smartphone browser for viewing, changing or exporting.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button class="share-copy-link btn btn-sm btn-primary">
|
||||
<i class="fa fa-copy"></i>
|
||||
<span data-i18n="share.copy-link">Copy Link to this Route</span>
|
||||
</button>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u={url}"
|
||||
class="share-service share-service-facebook btn btn-sm btn-facebook"
|
||||
target="_blank"
|
||||
data-i18n="[title]share.tooltip-facebook"
|
||||
title="Share on Facebook"
|
||||
><i class="fa fa-facebook"></i
|
||||
></a>
|
||||
<a
|
||||
href="whatsapp://send?text={url}"
|
||||
class="share-service share-service-whatsapp btn btn-sm btn-whatsapp"
|
||||
data-i18n="[title]share.tooltip-whatsapp"
|
||||
title="Share on WhatsApp"
|
||||
target="_blank"
|
||||
><i class="fa fa-whatsapp"></i
|
||||
></a>
|
||||
<a
|
||||
href="mailto:?subject=Planned Route&body={url}"
|
||||
class="share-service share-service-email btn btn-sm btn-email"
|
||||
data-i18n="[title]share.tooltip-email"
|
||||
title="Share by email"
|
||||
><i class="fa fa-envelope"></i
|
||||
></a>
|
||||
<button
|
||||
class="share-service-mastodon btn btn-sm btn-mastodon"
|
||||
data-i18n="[title]share.tooltip-mastodon"
|
||||
title="Share on Mastodon"
|
||||
hidden
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15"
|
||||
height="15"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p id="share-qrcode-img" style="margin-right: 1em"></p>
|
||||
|
||||
<p class="alert alert-danger hide" id="qrcode-msg-too-long" data-i18n="qrcode.msg-too-long">
|
||||
Cannot create QR Code: Route definition is too long. Removing some waypoints may help fit
|
||||
all data into the QRCode.
|
||||
</p>
|
||||
<p
|
||||
class="alert alert-danger hide"
|
||||
id="qrcode-msg-unknown-error"
|
||||
data-i18n="qrcode.msg-unknown-error"
|
||||
>
|
||||
Cannot create QR Code: An unknown error occurred. See the browser console for details. Maybe
|
||||
the route definition is too long. Removing some waypoints may help fit all data into the
|
||||
QRCode.
|
||||
</p>
|
||||
<div>
|
||||
<div class="pull-left mr-2"><small>QR Code Size:</small></div>
|
||||
<div
|
||||
id="qrcode-buttons"
|
||||
class="btn-group btn-group-xs"
|
||||
role="group"
|
||||
aria-label="Change size of QR Code"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary qrcode-size-button"
|
||||
id="qrcode-create-small"
|
||||
data-qrcode-size="256"
|
||||
data-i18n="qrcode.small"
|
||||
>
|
||||
Small
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary qrcode-size-button"
|
||||
id="qrcode-create-medium"
|
||||
data-qrcode-size="384"
|
||||
data-i18n="qrcode.medium"
|
||||
>
|
||||
Medium
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary qrcode-size-button"
|
||||
id="qrcode-create-large"
|
||||
data-qrcode-size="512"
|
||||
data-i18n="qrcode.large"
|
||||
>
|
||||
Large
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Track to Route modal window -->
|
||||
<div
|
||||
class="modal fade"
|
||||
|
|
|
|||
150
js/control/ShareRoute.js
Normal file
150
js/control/ShareRoute.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
BR.ShareRoute = L.Class.extend({
|
||||
/**
|
||||
* Sharing via Mastodon is currently disabled by default, because
|
||||
* the share intent fails when the current route URL is longer
|
||||
* than the post character limit for that instance.
|
||||
*/
|
||||
options: {
|
||||
services: {
|
||||
mastodon: false,
|
||||
},
|
||||
shortcut: {
|
||||
share_action: 65, // char code for 'a' ("action")
|
||||
},
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
L.DomUtil.get('shareButton').onclick = L.bind(this.share, this);
|
||||
L.DomEvent.addListener(document, 'keydown', this._keydownListener, this);
|
||||
},
|
||||
|
||||
share: function (event) {
|
||||
event.preventDefault();
|
||||
this.services();
|
||||
this.qrcode();
|
||||
},
|
||||
|
||||
services: function () {
|
||||
const self = this;
|
||||
|
||||
$('.share-copy-link').on('click', function () {
|
||||
navigator.clipboard.writeText(self.getShareUrl());
|
||||
});
|
||||
|
||||
$('a.share-service').each(function () {
|
||||
$(this).attr('href', $(this).attr('href').replace('{url}', encodeURIComponent(self.getShareUrl())));
|
||||
});
|
||||
|
||||
if (this.options.services.mastodon === true) {
|
||||
$('.share-service-mastodon')
|
||||
.removeAttr('hidden')
|
||||
.on('click', function () {
|
||||
let mastodonServer = window.prompt(
|
||||
i18next.t('share.mastodon-enter-server-name'),
|
||||
'mastodon.social'
|
||||
);
|
||||
if (mastodonServer.indexOf('http') !== 0) {
|
||||
mastodonServer = 'https://' + mastodonServer;
|
||||
}
|
||||
window.open(mastodonServer + '/share?text=' + encodeURIComponent(self.getShareUrl()), '_blank');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders QR Code for the current route:
|
||||
*
|
||||
* - add query parameter `?export=dialog` to the current URL
|
||||
* - displays QR Code for that URL in the dialog, size is automatically adjusted
|
||||
* to the length of the URL
|
||||
* - displays buttons to change the size of the QR Code (small, medium, large)
|
||||
*/
|
||||
qrcode: function () {
|
||||
const exportUrl = this.createQrCodeUrl();
|
||||
|
||||
this.renderQrCode('share-qrcode-img', exportUrl, this.getQrCodeSizeForUrl(exportUrl));
|
||||
|
||||
$('.qrcode-size-button').on('click', { shareRoute: this, url: exportUrl }, function (event) {
|
||||
event.data.shareRoute.renderQrCode('share-qrcode-img', event.data.url, $(this).data('qrcodeSize'));
|
||||
});
|
||||
},
|
||||
|
||||
getQrCodeSizeForUrl: function (url) {
|
||||
if (url.length < 500) {
|
||||
return 256;
|
||||
}
|
||||
|
||||
if (url.length < 1700) {
|
||||
return 384;
|
||||
}
|
||||
|
||||
return 512;
|
||||
},
|
||||
|
||||
renderQrCode: function (elementId, url, size) {
|
||||
$('#share-qrcode-img').empty();
|
||||
$('#qrcode-buttons').show();
|
||||
$('#qrcode-msg-unknown-error').hide();
|
||||
$('#qrcode-msg-too-long').hide();
|
||||
|
||||
try {
|
||||
new QRCode(document.getElementById(elementId), {
|
||||
text: url,
|
||||
width: size,
|
||||
height: size,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M,
|
||||
});
|
||||
} catch (exception) {
|
||||
$('#share-qrcode-img').empty();
|
||||
$('#qrcode-buttons').hide();
|
||||
if (exception.message === 'Too long data') {
|
||||
$('#qrcode-msg-too-long').show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Cannot create QR Code', exception);
|
||||
$('#qrcode-msg-unknown-error').show();
|
||||
}
|
||||
},
|
||||
|
||||
createQrCodeUrl: function () {
|
||||
// We work on a copy of the current location instance to avoid
|
||||
// reloading the page (which will happen when the `export` query
|
||||
// parameter is added to the actual location object):
|
||||
const exportLocation = new URL(document.location.href);
|
||||
const searchParams = new URLSearchParams(exportLocation.search);
|
||||
|
||||
// We've to provide a value to the query parameter here, because
|
||||
// URLSearchParams uses a list of tuples internally where the
|
||||
// value part of such a tuple isn't optional. For now the value
|
||||
// is 'dialog', but it's possible to add support for other values
|
||||
// later, e.g. for starting a download directly after opening the link:
|
||||
searchParams.set('export', 'dialog');
|
||||
exportLocation.search = searchParams.toString();
|
||||
|
||||
return exportLocation.toString();
|
||||
},
|
||||
|
||||
getShareUrl: function () {
|
||||
const exportLocation = new URL(document.location.href);
|
||||
const searchParams = new URLSearchParams(exportLocation.search);
|
||||
searchParams.delete('export');
|
||||
exportLocation.search = searchParams.toString();
|
||||
|
||||
return exportLocation.toString();
|
||||
},
|
||||
|
||||
_keydownListener: function (event) {
|
||||
if (
|
||||
BR.Util.keyboardShortcutsAllowed(event) &&
|
||||
event.keyCode === this.options.shortcut.share_action &&
|
||||
!$('#shareButton').hasClass('disabled')
|
||||
) {
|
||||
$('#share-dialog').modal('show');
|
||||
this.share(event);
|
||||
}
|
||||
},
|
||||
});
|
||||
15
js/index.js
15
js/index.js
|
|
@ -85,7 +85,7 @@
|
|||
// https://github.com/Templarian/MaterialDesign/blob/d0b28330af6648ca4c50c14d55043d71f813b3ae/svg/vector-line.svg
|
||||
// Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0), https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
|
||||
const svg = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
||||
id="mdi-vector-line" width="24" height="24" viewBox="0 0 24 24" class="mdi active">
|
||||
<path d="M15,3V7.59L7.59,15H3V21H9V16.42L16.42,9H21V3M17,5H19V7H17M5,17H7V19H5" />
|
||||
</svg>`;
|
||||
|
|
@ -306,11 +306,13 @@
|
|||
pois = new BR.PoiMarkers(routing);
|
||||
|
||||
exportRoute = new BR.Export(router, pois, profile);
|
||||
new BR.ShareRoute();
|
||||
|
||||
routing.on('routing:routeWaypointEnd routing:setWaypointsEnd routing:rerouteSegmentEnd', function (evt) {
|
||||
search.clear();
|
||||
onUpdate(evt && evt.err);
|
||||
});
|
||||
|
||||
map.on('routing:draw-start', function () {
|
||||
drawButton.state('deactivate-draw');
|
||||
beelineButton.enable();
|
||||
|
|
@ -346,6 +348,17 @@
|
|||
trackAnalysis.update(track, segments);
|
||||
|
||||
exportRoute.update(latLngs, segments);
|
||||
|
||||
// Open export dialog immediately when the `export` query parameter is set;
|
||||
// this is used for the QR code export:
|
||||
var searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.has('export') && searchParams.get('export') === 'dialog') {
|
||||
routing.once('routing:setWaypointsEnd', () => {
|
||||
if (latLngs.length > 1) {
|
||||
$('#exportButton').trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
routing.addTo(map);
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -81,10 +81,10 @@
|
|||
"maplibre-gl": "2.4.0",
|
||||
"overpass-layer": "3.4.0",
|
||||
"popper.js": "1.16.1",
|
||||
"qrcodejs": "^1.0.0",
|
||||
"regenerator-runtime": "0.13.11",
|
||||
"togpx": "nrenner/togpx#722d291",
|
||||
"tokml": "0.4.0",
|
||||
"url-search-params": "1.1.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -299,6 +299,16 @@
|
|||
"dist/overpass-layer.js"
|
||||
]
|
||||
},
|
||||
"qrcodejs": {
|
||||
"main": [
|
||||
"qrcode.js"
|
||||
]
|
||||
},
|
||||
"url-search-params-polyfill": {
|
||||
"main": [
|
||||
"index.js"
|
||||
]
|
||||
},
|
||||
"@mapbox/maki": {
|
||||
"main": [
|
||||
"icons/art-gallery.svg",
|
||||
|
|
|
|||
10
yarn.lock
10
yarn.lock
|
|
@ -10342,6 +10342,11 @@ punycode@^2.1.0, punycode@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qrcodejs@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/qrcodejs/-/qrcodejs-1.0.0.tgz#afab5e9e858521f859ae336d2ed0f9fd2e76cca7"
|
||||
integrity sha512-67rj3mMBhSBepaD57qENnltO+r8rSYlqM7HGThks/BiyDAkc86sLvkKqjkqPS5v13f7tvnt6dbEf3qt7zq+BCg==
|
||||
|
||||
qs@^6.11.0:
|
||||
version "6.11.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f"
|
||||
|
|
@ -12411,11 +12416,6 @@ urix@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||
|
||||
url-search-params@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/url-search-params/-/url-search-params-1.1.0.tgz#865669a6e4e9e5543f86fc972b27c91485375326"
|
||||
integrity sha512-XiO5GLAxmlZgdLob/RmKZRc2INHrssMbpwD6O46JkB1aEJO4fkV3x3mR6+CDX01ijfEUwvfwCiUQZrKqfm1ILw==
|
||||
|
||||
use@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue