finishes trail printing

This commit is contained in:
Christian Beutel
2024-04-12 00:25:26 +02:00
parent 71d548b9d6
commit f9f8998617
20 changed files with 643 additions and 39 deletions

216
web/package-lock.json generated
View File

@@ -14,8 +14,10 @@
"@threlte/extras": "^8.7.5",
"@types/leaflet.awesome-markers": "^2.0.28",
"@types/three": "^0.161.2",
"canvg": "^4.0.1",
"crypto-random-string": "^5.0.0",
"jsdom": "^24.0.0",
"jspdf": "^2.5.1",
"leaflet": "^1.9.4",
"leaflet-gpx": "^1.7.0",
"leaflet.awesome-markers": "^2.0.5",
@@ -73,6 +75,17 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
@@ -1105,12 +1118,22 @@
"undici-types": "~5.26.4"
}
},
"node_modules/@types/offscreencanvas": {
"version": "2019.7.3",
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="
},
"node_modules/@types/pug": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
"dev": true
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -1329,6 +1352,17 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/autoprefixer": {
"version": "10.4.17",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz",
@@ -1393,6 +1427,15 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"optional": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1491,6 +1534,17 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -1584,6 +1638,22 @@
}
]
},
"node_modules/canvg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-4.0.1.tgz",
"integrity": "sha512-5gD/d6SiCCT7baLnVr0hokYe93DfcHW2rSqdKOuOQD84YMlyfttnZ8iQsThTdX6koYam+PROz/FuQTo500zqGw==",
"dependencies": {
"@types/offscreencanvas": "^2019.6.4",
"@types/raf": "^3.4.0",
"raf": "^3.4.1",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/chai": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
@@ -1805,6 +1875,17 @@
"node": ">= 0.6"
}
},
"node_modules/core-js": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
"integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
"hasInstallScript": true,
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-fetch": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
@@ -1846,6 +1927,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@@ -2106,6 +2196,12 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"node_modules/dompurify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.0.tgz",
"integrity": "sha512-5RXhAXSCrKTqt9pSbobT9PVRX+oPpENplTZqCiK1l0ya+ZOzwo9kqsGLbYRsAhzIiLCwKEy99XKSSrqnRTLVcw==",
"optional": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -2689,6 +2785,19 @@
"node": ">=18"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -3225,6 +3334,53 @@
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
"dev": true
},
"node_modules/jspdf": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
"integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
"dependencies": {
"@babel/runtime": "^7.14.0",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"fflate": "^0.4.8"
},
"optionalDependencies": {
"canvg": "^3.0.6",
"core-js": "^3.6.0",
"dompurify": "^2.2.0",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jspdf/node_modules/canvg": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/jspdf/node_modules/fflate": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
},
"node_modules/jspdf/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -3882,6 +4038,11 @@
"png-js": "^1.0.0"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
},
"node_modules/periscopic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
@@ -4228,6 +4389,14 @@
}
]
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -4255,6 +4424,11 @@
"node": ">=8.10.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
@@ -4338,6 +4512,14 @@
"node": ">=0.10.0"
}
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -4586,6 +4768,14 @@
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/std-env": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
@@ -4962,6 +5152,14 @@
}
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -5016,6 +5214,15 @@
"node": ">=10.13.0"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -5305,6 +5512,15 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/vite": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",

View File

@@ -39,8 +39,10 @@
"@threlte/extras": "^8.7.5",
"@types/leaflet.awesome-markers": "^2.0.28",
"@types/three": "^0.161.2",
"canvg": "^4.0.1",
"crypto-random-string": "^5.0.0",
"jsdom": "^24.0.0",
"jspdf": "^2.5.1",
"leaflet": "^1.9.4",
"leaflet-gpx": "^1.7.0",
"leaflet.awesome-markers": "^2.0.5",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@
import type { Trail } from "$lib/models/trail";
import { currentUser } from "$lib/stores/user_store";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
import type { Map, Marker } from "leaflet";
import { createEventDispatcher, onMount } from "svelte";
@@ -9,6 +10,7 @@
export let markers: Marker[] = [];
export let map: Map | null = null;
export let options = {};
export let graticule: AutoGraticule | null = null;
const dispatch = createEventDispatcher();
@@ -110,7 +112,8 @@
}
if(elevation_options.graticule) {
new AutoGraticule().addTo(map!);
graticule = new AutoGraticule();
graticule.addTo(map!);
}
});
@@ -120,6 +123,6 @@
<div id="map" class="rounded-xl z-0 basis-full"></div>
<div class="flex items-center justify-between">
<slot />
<div class="basis-full min-w-[300px]" id="elevation"></div>
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>
</div>
</div>

View File

@@ -40,6 +40,7 @@
},
]
: []),
{ text: $_("print"), value: "print", icon: "print" },
{ text: $_("add-to-list"), value: "list", icon: "bookmark" },
{ text: $_("edit"), value: "edit", icon: "pen" },
{ text: $_("delete"), value: "delete", icon: "trash" },
@@ -61,6 +62,8 @@
"_blank",
)
?.focus();
} else if (item.value == "print") {
goto(`/map/trail/${trail.id}/print`);
} else if (item.value == "download") {
downloadURI(getFileURL(trail, trail.gpx), trail.gpx!);
} else if (item.value == "edit") {

View File

@@ -52,6 +52,7 @@
"entry": "Eintrag",
"error-creating-user": "Fehler beim Erstellen des Nutzers",
"error-during-login": "Fehler beim Login",
"error-printing-map": "",
"error-reading-file": "Fehler beim Lesen der Datei",
"est-duration": "Gesch. Dauer",
"explore": "Erkunden",
@@ -59,6 +60,7 @@
"features": "Features",
"french": "Französisch",
"german": "Deutsch",
"grid": "",
"hero_section_0_text": "Entdecke spannende Routen, speichere deine Favoriten und erlebe die Schönheit der Natur. Finde dein nächstes Abenteuer!",
"hero_section_1_heading": "Hier gibt es noch keine Routen",
"hero_section_1_text": "Hier sind einige Routen, die dir gefallen könnten. Oder du wirfst einen Blick auf die vollständige Liste.",
@@ -67,6 +69,7 @@
"hungarian": "Ungarisch",
"icon": "Icon",
"imperial": "Amerikanisch",
"include-description": "",
"invalid-date": "Ungültiges Datum",
"invalid-username": "Ungültiger Nutzername",
"language": "Sprache",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Keine gültige Email-Adresse",
"not-completed": "Nicht abgeschlossen",
"or": "oder",
"orientation": "",
"paper-size": "",
"password": "Passwort",
"photos": "Fotos",
"pick-a-trail": "Route auswählen",

View File

@@ -52,6 +52,7 @@
"entry": "Entry",
"error-creating-user": "Error creating user",
"error-during-login": "Error during login",
"error-printing-map": "Error printing map",
"error-reading-file": "Error reading file",
"est-duration": "Est. duration",
"explore": "Explore",
@@ -59,6 +60,7 @@
"features": "Features",
"french": "French",
"german": "German",
"grid": "Grid",
"hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!",
"hero_section_1_heading": "It seems there are no trails here yet.",
"hero_section_1_text": "Here are some trails you might like. Or you can just go to the full list right now.",
@@ -67,6 +69,7 @@
"hungarian": "Hungarian",
"icon": "Icon",
"imperial": "Imperial",
"include-description": "Include description",
"invalid-date": "Invalid Date",
"invalid-username": "Invalid username",
"language": "Language",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Not a valid email address",
"not-completed": "Not completed",
"or": "or",
"orientation": "Orientation",
"paper-size": "Paper size",
"password": "Password",
"photos": "Photos",
"pick-a-trail": "Pick a trail",

View File

@@ -52,6 +52,7 @@
"entry": "Entrée",
"error-creating-user": "Erreur durant la création de l'utilisateur",
"error-during-login": "Erreur durant la connexion",
"error-printing-map": "",
"error-reading-file": "Erreur de lecture du fichier",
"est-duration": "Temps estimé",
"explore": "Explorer",
@@ -59,6 +60,7 @@
"features": "Fonctionnalités",
"french": "Français",
"german": "Allemand",
"grid": "",
"hero_section_0_text": "Explorez des itinéraires passionnants, sauvegardez vos favoris et découvrez la beauté de la nature. Trouvez votre prochaine aventure !",
"hero_section_1_heading": "Il semble qu'il n'y ait pas encore d'itinéraire ici.",
"hero_section_1_text": "Voici quelques itinéraires qui pourraient vous plaire. Vous pouvez également consulter la liste complète dès maintenant.",
@@ -67,6 +69,7 @@
"hungarian": "Hongrois",
"icon": "Icône",
"imperial": "Impérial",
"include-description": "",
"invalid-date": "Date invalide",
"invalid-username": "Nom d'utilisateur invalide",
"language": "Langue",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Adresse email invalide",
"not-completed": "Pas terminé",
"or": "ou",
"orientation": "",
"paper-size": "",
"password": "Mot de passe",
"photos": "Photos",
"pick-a-trail": "Choisir un itinéraire",

View File

@@ -52,6 +52,7 @@
"entry": "Bejegyzés",
"error-creating-user": "Hiba felhasználó hozzáadása közben",
"error-during-login": "Hiba bejelentkezés közben",
"error-printing-map": "",
"error-reading-file": "Hiba a fájl olvasása közben",
"est-duration": "Becsült időtartam",
"explore": "Felfedezés",
@@ -59,6 +60,7 @@
"features": "Jellemzők",
"french": "Francia",
"german": "Német",
"grid": "",
"hero_section_0_text": "Fedezze fel az izgalmas útvonalakat, mentse el kedvenceit, és tapasztalja meg a természet szépségét. Találja meg a következő kalandját!",
"hero_section_1_heading": "Úgy tűnik, itt még nincsenek ösvények.",
"hero_section_1_text": "Íme néhány nyomvonal, amely tetszeni fog. Vagy csak menj a teljes listára most rögtön.",
@@ -67,6 +69,7 @@
"hungarian": "Magyar",
"icon": "Ikon",
"imperial": "Angolszász",
"include-description": "",
"invalid-date": "Érvénytelen dátum",
"invalid-username": "Érvénytelen felhasználó",
"language": "Nyelf",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Érvénytelen e-mail cím",
"not-completed": "Nem teljesített",
"or": "vagy",
"orientation": "",
"paper-size": "",
"password": "Jelszó",
"photos": "Képek",
"pick-a-trail": "Válasszon útvonalat",

View File

@@ -52,6 +52,7 @@
"entry": "Item",
"error-creating-user": "Het account kan niet worden aangemaakt",
"error-during-login": "Het inloggen is mislukt",
"error-printing-map": "",
"error-reading-file": "Het bestand kan niet worden ingelezen",
"est-duration": "Geschatte duur",
"explore": "Verkennen",
@@ -59,6 +60,7 @@
"features": "Kenmerken",
"french": "Frans",
"german": "Duits",
"grid": "",
"hero_section_0_text": "Verken uitdagende wandelroutes, bewaar je favoriete en ervaar de pracht van de natuur. Op naar het volgende avontuur!",
"hero_section_1_heading": "Je hebt nog geen wandelroutes.",
"hero_section_1_text": "Wellicht vind je deze routes interessant. Ook kun je de volledige lijst bekijken.",
@@ -67,6 +69,7 @@
"hungarian": "Hongaars",
"icon": "Pictogram",
"imperial": "Imperiaal",
"include-description": "",
"invalid-date": "Ongeldige datum",
"invalid-username": "Ongeldige gebruikersnaam",
"language": "Taal",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Het e-mailadres is ongeldig",
"not-completed": "Niet voltooid",
"or": "of",
"orientation": "",
"paper-size": "",
"password": "Wachtwoord",
"photos": "Foto's",
"pick-a-trail": "Kies een wandelroute",

View File

@@ -52,6 +52,7 @@
"entry": "Pozycja",
"error-creating-user": "Błąd tworzenia użytkownika",
"error-during-login": "Błąd podczas logowania",
"error-printing-map": "",
"error-reading-file": "Błąd wczytywania pliku",
"est-duration": "Szacowany czas",
"explore": "Eksploruj",
@@ -59,6 +60,7 @@
"features": "Funkcje",
"french": "Francuski",
"german": "Niemiecki",
"grid": "",
"hero_section_0_text": "Eksploruj ekscytujące szlaki, zapisz swoje ulubione, doświadcz piękna naturalnego świata. Znajdź swoją następną przygodę!",
"hero_section_1_heading": "Wygląda na to że nie ma tutaj ścieżek.",
"hero_section_1_text": "Oto kila ścieżek które możesz zobaczyć lub możesz przejść do pełnej listy.",
@@ -67,6 +69,7 @@
"hungarian": "Język węgierski",
"icon": "Ikona",
"imperial": "Anglosaskie",
"include-description": "",
"invalid-date": "Nieprawidłowa data",
"invalid-username": "Błędna nazwa użytkownika",
"language": "Język",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Nieprawidłowy adres email",
"not-completed": "Nie dokończono",
"or": "lub",
"orientation": "",
"paper-size": "",
"password": "Hasło",
"photos": "Zdjęcia",
"pick-a-trail": "Wybierz ścieżkę",

View File

@@ -52,6 +52,7 @@
"entry": "Entrada",
"error-creating-user": "Erro ao criar usuário",
"error-during-login": "Erro durante o login",
"error-printing-map": "",
"error-reading-file": "Erro ao ler o arquivo",
"est-duration": "Duração prevista",
"explore": "Explorar",
@@ -59,6 +60,7 @@
"features": "Características",
"french": "Francês",
"german": "Alemão",
"grid": "",
"hero_section_0_text": "Explore trilhas emocionantes, salve seus favoritos e experimente a beleza da natureza. Encontre sua próxima aventura!",
"hero_section_1_heading": "Parece que ainda não há trilhas aqui.",
"hero_section_1_text": "Aqui estão algumas trilhas que você pode gostar. Ou podes ir à lista completa agora.",
@@ -67,6 +69,7 @@
"hungarian": "Húngaro",
"icon": "Ícone",
"imperial": "Imperial",
"include-description": "",
"invalid-date": "Data inválida",
"invalid-username": "Nome de usuário inválido",
"language": "Língua",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "Não um endereço de e-mail válido",
"not-completed": "Não preenchido",
"or": "ou",
"orientation": "",
"paper-size": "",
"password": "Senha",
"photos": "Fotos",
"pick-a-trail": "Escolha uma trilha",

View File

@@ -52,6 +52,7 @@
"entry": "日程",
"error-creating-user": "创建用户错误",
"error-during-login": "登录错误",
"error-printing-map": "",
"error-reading-file": "读取文件错误",
"est-duration": "预计时长",
"explore": "探索",
@@ -59,6 +60,7 @@
"features": "特性",
"french": "法语",
"german": "德语",
"grid": "",
"hero_section_0_text": "探索激动人心的探险,收藏最爱行程,,体验美丽地大自然。找到你的下一次冒险!",
"hero_section_1_heading": "这儿似乎还没有行程。",
"hero_section_1_text": "以下有你可能喜欢的线路,或者立刻浏览全部列表。",
@@ -67,6 +69,7 @@
"hungarian": "匈牙利语",
"icon": "图标",
"imperial": "英制",
"include-description": "",
"invalid-date": "无效日期",
"invalid-username": "无效用户名",
"language": "语言",
@@ -99,6 +102,8 @@
"not-a-valid-email-address": "无效电子邮箱地址",
"not-completed": "未完成",
"or": "或",
"orientation": "",
"paper-size": "",
"password": "密码",
"photos": "图片",
"pick-a-trail": "选择一个路线",

View File

@@ -4,6 +4,7 @@ export function createRect(
width: number,
height: number,
fill: string,
border?: string,
) {
const svgns = "http://www.w3.org/2000/svg";
@@ -12,7 +13,7 @@ export function createRect(
rect.setAttribute("y", y.toString());
rect.setAttribute("width", width.toString());
rect.setAttribute("height", height.toString());
rect.setAttribute("fill", fill);
rect.setAttribute("style", `fill:${fill};stroke-width:1;stroke:${border ?? ''}`);
return rect;
}

View File

@@ -327,7 +327,7 @@
--ele-stroke: #7b7b7b;
--ele-circle: #fff;
--ele-line: #000;
--ele-grid: rgba(var(--input-border));
--ele-grid: #d1d5db;
}
.elevation-detached.lightblue-theme .area {

View File

@@ -215,7 +215,7 @@ export default function leafletImage(map, callback) {
im.onload = function () {
if (marker._icon.classList.contains("leaflet-grid-label")) {
const label = marker._icon.children[0].textContent;
ctx.font = '600 8px "Font Awesome 6 Free"';
ctx.font = '600 10px "IBMPlexSans"';
ctx.fillStyle = "#000";
ctx.fillText(label, x + 4, y + 12);
} else {
@@ -224,14 +224,16 @@ export default function leafletImage(map, callback) {
ctx.drawImage(this, x, y, size[0], size[1]);
ctx.font = '600 14px "Font Awesome 6 Free"';
ctx.fillStyle = "#ffffff";
ctx.fillText(icon, x + 10.2, y + 24);
const iconWidth = ctx.measureText(icon).width
ctx.fillText(icon, x + size[0] / 2 - iconWidth / 2, y + 24);
if (waypointName) {
ctx.fillStyle = "#313131"
ctx.roundRect(x - 8, y - 18, waypointName.length * 7, 20, 5)
ctx.fill();
ctx.fillStyle = "rgba(49, 49, 49, 0.5)"
ctx.font = '600 12px "IBMPlexSans"';
const textWidth = ctx.measureText(waypointName).width
ctx.roundRect(size[0] / 2 + x - (textWidth + 16) / 2, y - 18, textWidth + 16, 20, 5)
ctx.fill();
ctx.fillStyle = "#ffffff";
ctx.fillText(waypointName, x, y - 4);
ctx.fillText(waypointName, size[0] / 2 + x - (textWidth + 16) / 2 + 8, y - 4);
}
}

View File

@@ -1,7 +1,12 @@
<script lang="ts">
import { page } from "$app/stores";
import "$lib/assets/fonts/IBMPlexSans-Regular-normal";
import "$lib/assets/fonts/IBMPlexSans-SemiBold-bold";
import "$lib/assets/fonts/fa-solid-900-normal";
import Button from "$lib/components/base/button.svelte";
import Select from "$lib/components/base/select.svelte";
import Select, {
type SelectItem,
} from "$lib/components/base/select.svelte";
import LogoText from "$lib/components/logo/logo_text.svelte";
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
import { trail } from "$lib/stores/trail_store";
@@ -18,14 +23,19 @@
import { createRect, createText } from "$lib/util/svg_util";
import "$lib/vendor/leaflet-elevation/src/index.css";
import leafletImage from "$lib/vendor/leaflet-image/leaflet-image.js";
import { jsPDF } from "jspdf";
import type { Map } from "leaflet";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import QrCodeWithLogo from "qrcode-with-logos";
import { onMount, tick } from "svelte";
import { _ } from "svelte-i18n";
import { Canvg } from "canvg";
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
import { show_toast } from "$lib/stores/toast_store";
let map: Map;
let graticule: AutoGraticule;
const paperSizes: { text: string; value: keyof typeof paperDimensions }[] =
[
@@ -34,13 +44,25 @@
];
const paperDimensions = {
a4: {
css: {
width: "21cm",
height: "29.7cm",
},
pdf: {
width: 210,
height: 297,
},
},
letter: {
css: {
width: "21.59cm",
height: "27.94cm",
},
pdf: {
width: 215.9,
height: 279.4,
},
},
};
let selectedPaperSize = paperSizes[0].value;
@@ -50,7 +72,17 @@
];
let selectedOrientation: "portrait" | "landscape" = orientations[0].value;
let scale = 1;
const gridOptions = [
{ text: "Degrees", value: "degree" },
{ text: "No Grid", value: "off" },
];
let selectedGrid = gridOptions[0].value;
let scale: number = 1;
let printLoading: boolean = false;
let includeDescription: boolean = false;
onMount(() => {
let qrcode = new QrCodeWithLogo({
@@ -63,16 +95,262 @@
});
function print() {
leafletImage(map, function (err: string, canvas: HTMLCanvasElement) {
printLoading = true;
const doc = new jsPDF(selectedOrientation, "mm", [
paperDimensions[selectedPaperSize].pdf[
selectedOrientation == "portrait" ? "width" : "height"
],
paperDimensions[selectedPaperSize].pdf[
selectedOrientation == "portrait" ? "height" : "width"
],
]);
doc.setFont("IBMPlexSans-Regular", "normal");
var width = doc.internal.pageSize.getWidth();
var height = doc.internal.pageSize.getHeight();
let currentHeight = 0;
const rulerElement = document.getElementById("ruler") as HTMLElement;
const rulerSegments = rulerElement.getElementsByTagName("rect");
const rulerLabels = rulerElement.getElementsByTagName("text");
const pageWidth = document
.getElementById("paper")
?.getBoundingClientRect().width;
try {
leafletImage(
map,
async function (err: string, canvas: HTMLCanvasElement) {
// Map
var img = document.createElement("img");
var dimensions = map.getSize();
img.width = dimensions.x;
img.height = dimensions.y;
const ratio = img.height / img.width;
img.src = canvas.toDataURL();
doc.addImage(img.src, "png", 8, 8, 0, (width - 16) * ratio);
currentHeight += (width - 16) * ratio + 11;
const elevationProfileHeight = currentHeight - 4;
document.getElementById("images")!.innerHTML = "";
document.getElementById("images")!.appendChild(img);
// QR Code
doc.addImage(
document.getElementById("qrcode") as HTMLImageElement,
"png",
8,
currentHeight,
24,
24,
);
// Distance
currentHeight += 5;
let currentWidth = 8 + 24 + 4;
doc.setFontSize(9);
doc.setTextColor(107, 114, 128);
doc.setFont("fa-solid-900", "normal");
doc.text("\uf337", currentWidth, currentHeight);
currentWidth += doc.getTextWidth("\uf337") + 3;
doc.setFont("IBMPlexSans-Regular", "normal");
doc.text(
formatDistance($trail.distance),
currentWidth,
currentHeight,
);
currentWidth +=
doc.getTextWidth(formatDistance($trail.distance)) + 4;
// Elevation gain
doc.setFont("fa-solid-900", "normal");
doc.text("\uf338", currentWidth, currentHeight);
currentWidth += doc.getTextWidth("\uf338") + 3;
doc.setFont("IBMPlexSans-Regular", "normal");
doc.text(
formatElevation($trail.elevation_gain),
currentWidth,
currentHeight,
);
currentWidth +=
doc.getTextWidth(
formatDistance($trail.elevation_gain),
) + 4;
// Duration
doc.setFont("fa-solid-900", "normal");
doc.text("\uf017", currentWidth, currentHeight);
currentWidth += doc.getTextWidth("\uf337") + 3;
doc.setFont("IBMPlexSans-Regular", "normal");
doc.text(
formatTimeHHMM($trail.duration),
currentWidth,
currentHeight,
);
// Ruler
currentHeight += 6;
currentWidth = 8 + 24 + 4;
doc.setTextColor(0, 0, 0);
for (let i = 0; i < rulerSegments.length; i++) {
const segment = rulerSegments[i];
const label = rulerLabels[i].textContent ?? "";
const segmentWidth =
segment.getBoundingClientRect().width;
const segmentWidthPercent =
segmentWidth / (pageWidth ?? 0);
const segmentPDFWidth = width * segmentWidthPercent;
let textWidth = doc.getTextWidth(label);
if (i % 2 == 0) {
doc.setFillColor(255, 255, 255);
} else {
doc.setFillColor(93, 93, 93);
}
doc.rect(
currentWidth,
currentHeight,
segmentPDFWidth,
2,
"FD",
);
doc.text(
label,
currentWidth - textWidth / 2,
currentHeight + 6,
);
currentWidth += segmentPDFWidth;
if (i == rulerSegments.length - 1) {
const endLabel =
rulerLabels[i + 1].textContent ?? "";
textWidth = doc.getTextWidth(endLabel);
doc.text(
endLabel,
currentWidth - textWidth / 2,
currentHeight + 6,
);
textWidth = doc.getTextWidth("KM");
doc.text(
"KM",
currentWidth - textWidth,
currentHeight - 2,
);
}
}
// Scale
currentHeight += 12;
doc.setFillColor(255, 255, 255);
doc.text(
`Scale: 1 : ${Math.round(scale)}`,
8 + 24 + 4,
currentHeight,
);
// Elevation profile
const gridLines = document.querySelectorAll(
"#elevation .grid line",
);
gridLines.forEach((l) =>
l.setAttribute("stroke", "#d1d5db"),
);
const gridBorders = document.querySelectorAll(
"#elevation .grid path",
);
gridBorders.forEach((l) =>
l.setAttribute("stroke", "#fff"),
);
const tooltip = document.querySelector(
"#elevation .tooltip",
) as HTMLElement;
tooltip.style.display = "none";
const units = document.querySelectorAll(
"#elevation .axis text",
);
units.forEach((l) => l.setAttribute("fill", "#000"));
const elevationCanvas = document.createElement("canvas");
const ctx = elevationCanvas.getContext("2d")!;
const v = await Canvg.from(
ctx,
document.querySelector("#elevation svg")!.outerHTML,
);
v.start();
const profileRatio =
elevationCanvas.height / elevationCanvas.width;
doc.addImage(
elevationCanvas.toDataURL("image/png"),
width - 79.3 - 8,
elevationProfileHeight,
79.3,
79.3 * profileRatio,
);
const elevationPlot = (
document.querySelector(
".canvas-plot",
) as HTMLCanvasElement
).toDataURL("image/png");
doc.addImage(
elevationPlot,
width - 77.3,
elevationProfileHeight + 8,
0,
0,
);
tooltip.style.display = "block";
// Separator
currentHeight += 5;
doc.setDrawColor(232, 234, 237);
doc.line(8, currentHeight, width - 8, currentHeight);
currentHeight += 6;
// Trail name & location
doc.setTextColor(0, 0, 0);
doc.setFont("IBMPlexSans-SemiBold", "bold");
doc.text($trail.name, 8, currentHeight);
doc.setFont("fa-solid-900", "normal");
currentHeight += 5;
doc.text("\uf3c5", 8, currentHeight);
doc.setFont("IBMPlexSans-Regular", "normal");
doc.text($trail.location || "-", 12, currentHeight);
// Logo
const logo = new Image();
logo.src = "/imgs/logo_text_dark.png";
const logoRatio = 64 / 212;
const logoWidth = 32;
const logoHeight = logoWidth * logoRatio;
doc.addImage(
logo,
width - 8 - logoWidth,
height - 4 - logoHeight,
logoWidth,
logoHeight,
);
if (includeDescription && $trail.description) {
doc.addPage();
doc.text($trail.description, 16, 16, {
maxWidth: width - 32,
});
doc.addImage(
logo,
width - 8 - logoWidth,
height - 4 - logoHeight,
logoWidth,
logoHeight,
);
}
doc.save($trail.name + ".pdf");
printLoading = false;
},
);
} catch (e) {
show_toast({
icon: "close",
type: "error",
text: $_("error-printing-map"),
});
printLoading = false;
}
}
async function updateMapSize() {
@@ -107,8 +385,7 @@
label: string,
fill: string,
) {
const bar = createRect(x, height / 2, width - 4, 6, fill);
const tick = createRect(x + width - 4, height / 2, 4, 6, "#000");
const bar = createRect(x, height / 2, width, 6, fill, "#000");
const text = createText(
label,
x + width - 5 * label.length,
@@ -116,7 +393,6 @@
"0.75rem",
);
svg.appendChild(bar);
svg.appendChild(tick);
svg.append(text);
}
let multiplier = 1;
@@ -148,7 +424,7 @@
oneUnitInPixels * i * multiplier,
oneUnitInPixels * multiplier,
`${multiplier < 1 ? ((i + 1) * multiplier).toFixed(1) : (i + 1) * multiplier}`,
i % 2 == 0 ? "#7c7c7c" : "#5d5d5d",
i % 2 == 0 ? "#fff" : "#5d5d5d",
);
}
@@ -162,40 +438,72 @@
scale = calculateScaleFactor(map);
}
function toggleGrid() {
if (selectedGrid == "off") {
graticule?.remove();
} else {
graticule.addTo(map);
}
}
</script>
<svelte:head>
<title>{$_("print")} | wanderer</title>
</svelte:head>
<main class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-x-1 gap-y-4 items-start">
<div
class="print-details px-6 space-y-4 pb-4 border border-input-border rounded-3xl"
<main
class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-x-1 gap-y-4 items-start"
>
<div
class="print-details md:sticky w-80 md:top-8 mx-auto md:ml-8 p-6 flex flex-col gap-y-4 border border-input-border rounded-3xl"
>
<div id="images"></div>
<h1 class="text-4xl font-bold">Print Trail</h1>
<Select
bind:value={selectedPaperSize}
items={paperSizes}
label="Paper size"
label={$_("paper-size")}
on:change={updateMapSize}
></Select>
<Select
bind:value={selectedOrientation}
items={orientations}
label="Orientation"
label={$_("orientation")}
on:change={updateMapSize}
></Select>
<Select
bind:value={selectedGrid}
items={gridOptions}
label={$_("grid")}
on:change={toggleGrid}
></Select>
<div>
<input
id="description-checkbox"
type="checkbox"
bind:value={includeDescription}
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
/>
<label for="description-checkbox" class="ms-2 text-sm"
>{$_("include-description")}</label
>
</div>
<Button primary={true} on:click={print}>Print!</Button>
<Button
loading={printLoading}
extraClasses="mt-2"
primary={true}
on:click={print}>Print!</Button
>
</div>
<div class="paper-container overflow-scroll">
<div
class="paper flex flex-col mx-auto shadow-xl mb-8 p-8 bg-white"
style="width: {paperDimensions[selectedPaperSize][
id="paper"
class="flex flex-col mx-auto shadow-xl mb-8 p-8 bg-white"
style="width: {paperDimensions[selectedPaperSize].css[
selectedOrientation == 'portrait' ? 'width' : 'height'
]}; height: {paperDimensions[selectedPaperSize][
]}; height: {paperDimensions[selectedPaperSize].css[
selectedOrientation == 'portrait' ? 'height' : 'width'
]}"
>
@@ -214,6 +522,7 @@
}}
on:zoomend={(e) => updateScale(e.detail)}
bind:map
bind:graticule
>
<img
class="w-[100px] h-[100px]"
@@ -239,7 +548,14 @@
></i>{formatTimeHHMM($trail.duration)}</span
>
</div>
<svg width="100%" height="50" id="ruler"> </svg>
<svg
width="100%"
height="50"
id="ruler"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
</svg>
<span class="text-sm"
>Scale: &nbsp 1 : {Math.round(scale)}</span
>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB