Share Dialog with QR Code (#729)

This commit is contained in:
Marcus Jaschen 2023-05-16 11:31:32 +02:00 committed by GitHub
parent c715d2bab1
commit 3b3357c473
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 401 additions and 7 deletions

View file

@ -7,6 +7,7 @@
"settings": { "settings": {
"polyfills": [ "polyfills": [
"URL", "URL",
"URLSearchParams",
"Promise", "Promise",
"navigator", "navigator",
"Uint8Array", "Uint8Array",

View file

@ -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) [The Unlicense](https://github.com/osmlab/leaflet-osm-notes/blob/gh-pages/LICENSE)
- [Fetch polyfill](https://github.com/Github/fetch) - [Fetch polyfill](https://github.com/Github/fetch)
Copyright (c) 2014-2016 GitHub, Inc.; [MIT License](https://github.com/github/fetch/blob/master/LICENSE) 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)

View file

@ -527,6 +527,68 @@ button.deactivate-beeline-active.disabled {
margin: -6px 0 6px 20px; 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) */ /* "What's new?" (Change Log) */
#whatsnew h2 { #whatsnew h2 {
margin: 2rem 0; margin: 2rem 0;

View file

@ -68,6 +68,35 @@
<span data-i18n="navbar.export">Export</span> <span data-i18n="navbar.export">Export</span>
</a> </a>
</div> </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"> <div class="nav-item dropdown">
<a <a
class="nav-link dropdown-toggle" class="nav-link dropdown-toggle"
@ -549,6 +578,131 @@
</div> </div>
</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">&times;</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 --> <!-- Track to Route modal window -->
<div <div
class="modal fade" class="modal fade"

150
js/control/ShareRoute.js Normal file
View 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);
}
},
});

View file

@ -85,7 +85,7 @@
// https://github.com/Templarian/MaterialDesign/blob/d0b28330af6648ca4c50c14d55043d71f813b3ae/svg/vector-line.svg // 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 // Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0), https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
const svg = ` 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"> 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" /> <path d="M15,3V7.59L7.59,15H3V21H9V16.42L16.42,9H21V3M17,5H19V7H17M5,17H7V19H5" />
</svg>`; </svg>`;
@ -306,11 +306,13 @@
pois = new BR.PoiMarkers(routing); pois = new BR.PoiMarkers(routing);
exportRoute = new BR.Export(router, pois, profile); exportRoute = new BR.Export(router, pois, profile);
new BR.ShareRoute();
routing.on('routing:routeWaypointEnd routing:setWaypointsEnd routing:rerouteSegmentEnd', function (evt) { routing.on('routing:routeWaypointEnd routing:setWaypointsEnd routing:rerouteSegmentEnd', function (evt) {
search.clear(); search.clear();
onUpdate(evt && evt.err); onUpdate(evt && evt.err);
}); });
map.on('routing:draw-start', function () { map.on('routing:draw-start', function () {
drawButton.state('deactivate-draw'); drawButton.state('deactivate-draw');
beelineButton.enable(); beelineButton.enable();
@ -346,6 +348,17 @@
trackAnalysis.update(track, segments); trackAnalysis.update(track, segments);
exportRoute.update(latLngs, 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); routing.addTo(map);

View file

@ -81,10 +81,10 @@
"maplibre-gl": "2.4.0", "maplibre-gl": "2.4.0",
"overpass-layer": "3.4.0", "overpass-layer": "3.4.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"qrcodejs": "^1.0.0",
"regenerator-runtime": "0.13.11", "regenerator-runtime": "0.13.11",
"togpx": "nrenner/togpx#722d291", "togpx": "nrenner/togpx#722d291",
"tokml": "0.4.0", "tokml": "0.4.0",
"url-search-params": "1.1.0",
"whatwg-fetch": "^3.6.2" "whatwg-fetch": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
@ -299,6 +299,16 @@
"dist/overpass-layer.js" "dist/overpass-layer.js"
] ]
}, },
"qrcodejs": {
"main": [
"qrcode.js"
]
},
"url-search-params-polyfill": {
"main": [
"index.js"
]
},
"@mapbox/maki": { "@mapbox/maki": {
"main": [ "main": [
"icons/art-gallery.svg", "icons/art-gallery.svg",

View file

@ -10342,6 +10342,11 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 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: qs@^6.11.0:
version "6.11.1" version "6.11.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f" 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" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= 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: use@^3.1.0:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"