From e44ba65bd91cb8652a42ca9b714e014586212f2e Mon Sep 17 00:00:00 2001
From: Christian Beutel <>
Date: Wed, 10 Apr 2024 02:05:13 +0200
Subject: [PATCH] more work on print view
---
web/package-lock.json | 309 +++++++++++++++++-
web/package.json | 1 +
.../trail/map_with_elevation.svelte | 43 ++-
web/src/lib/util/leaflet_util.ts | 41 ++-
web/src/lib/util/svg_util.ts | 29 ++
.../vendor/leaflet-elevation/src/control.js | 1 +
.../vendor/leaflet-elevation/src/index.css | 11 +
.../lib/vendor/leaflet-elevation/src/utils.js | 3 +-
.../lib/vendor/leaflet-image/leaflet-image.js | 261 +++++++++++++++
web/src/lib/vendor/leaflet-image/queue.js | 120 +++++++
web/src/routes/map/trail/[id]/+page.svelte | 4 +-
.../routes/map/trail/[id]/print/+page.svelte | 262 +++++++++++++++
web/src/routes/map/trail/[id]/print/+page.ts | 19 ++
web/static/vendor/leaflet-image/marker.png | Bin 0 -> 4658 bytes
14 files changed, 1077 insertions(+), 27 deletions(-)
create mode 100644 web/src/lib/util/svg_util.ts
create mode 100644 web/src/lib/vendor/leaflet-image/leaflet-image.js
create mode 100644 web/src/lib/vendor/leaflet-image/queue.js
create mode 100644 web/src/routes/map/trail/[id]/print/+page.svelte
create mode 100644 web/src/routes/map/trail/[id]/print/+page.ts
create mode 100644 web/static/vendor/leaflet-image/marker.png
diff --git a/web/package-lock.json b/web/package-lock.json
index 47d7c63d..d76a50d0 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -23,6 +23,7 @@
"nouislider": "^15.7.1",
"photoswipe": "^5.4.3",
"pocketbase": "^0.21.0",
+ "qrcode-with-logos": "^1.0.5",
"svelte-i18n": "^4.0.0",
"three": "^0.161.0",
"yup": "^1.3.3"
@@ -1463,6 +1464,14 @@
"node": ">=6"
}
},
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -1564,6 +1573,80 @@
"node": ">=0.10"
}
},
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/code-red": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
@@ -1580,7 +1663,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -1591,8 +1673,7 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -1772,6 +1853,14 @@
}
}
},
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/decimal.js": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
@@ -1842,6 +1931,11 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+ },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -1866,6 +1960,11 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
+ "node_modules/encode-utf8": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+ "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
+ },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -2093,6 +2192,18 @@
"node": ">=8"
}
},
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -2161,6 +2272,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-func-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
@@ -2396,7 +2515,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -2625,6 +2743,17 @@
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
},
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/loupe": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
@@ -3012,6 +3141,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3035,6 +3197,14 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -3182,6 +3352,14 @@
"node": ">=16"
}
},
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/pocketbase": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.21.0.tgz",
@@ -3364,6 +3542,31 @@
"node": ">=6"
}
},
+ "node_modules/qrcode": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
+ "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "encode-utf8": "^1.0.3",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/qrcode-with-logos": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/qrcode-with-logos/-/qrcode-with-logos-1.0.5.tgz",
+ "integrity": "sha512-ZHynBWdidxv57T5Lw8KBGpM6c/bwrL9buITPOrtX0PaoV0AgKkqHifdO+rKV7jMSiR/EcQWpvHI9ZarZonDq6g==",
+ "dependencies": {
+ "qrcode": "^1.4.4"
+ }
+ },
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
@@ -3416,6 +3619,14 @@
"node": ">=8.10.0"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -3424,6 +3635,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+ },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -3574,6 +3790,11 @@
"node": ">=v12.22.7"
}
},
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+ },
"node_modules/set-cookie-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
@@ -4577,6 +4798,11 @@
"node": ">= 8"
}
},
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
+ },
"node_modules/why-is-node-running": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
@@ -4734,6 +4960,11 @@
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
},
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
+ },
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
@@ -4743,6 +4974,76 @@
"node": ">= 14"
}
},
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yocto-queue": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
diff --git a/web/package.json b/web/package.json
index f1cd6735..62d13783 100644
--- a/web/package.json
+++ b/web/package.json
@@ -48,6 +48,7 @@
"nouislider": "^15.7.1",
"photoswipe": "^5.4.3",
"pocketbase": "^0.21.0",
+ "qrcode-with-logos": "^1.0.5",
"svelte-i18n": "^4.0.0",
"three": "^0.161.0",
"yup": "^1.3.3"
diff --git a/web/src/lib/components/trail/map_with_elevation.svelte b/web/src/lib/components/trail/map_with_elevation.svelte
index 41eb05dd..f6d7d588 100644
--- a/web/src/lib/components/trail/map_with_elevation.svelte
+++ b/web/src/lib/components/trail/map_with_elevation.svelte
@@ -3,13 +3,16 @@
import { currentUser } from "$lib/stores/user_store";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import type { Map, Marker } from "leaflet";
- import { onMount } from "svelte";
+ import { createEventDispatcher, onMount } from "svelte";
export let trail: Trail;
export let markers: Marker[] = [];
+ export let map: Map | null = null;
+ export let options = {};
+
+ const dispatch = createEventDispatcher();
let L: any;
- let map: Map;
let controlElevation: any;
$: if (trail.expand.gpx_data && controlElevation) {
@@ -23,14 +26,21 @@
//@ts-ignore
await import("$lib/vendor/leaflet-elevation/src/index.js");
- map = L.map("map").setView([trail.lat ?? 0, trail.lon ?? 0], 14);
- map.attributionControl.setPrefix(false)
+ map = L.map("map", { preferCanvas: true }).setView(
+ [trail.lat ?? 0, trail.lon ?? 0],
+ 14,
+ );
+ map!.attributionControl.setPrefix(false);
+ map!.on("zoomend", function () {
+ dispatch("zoomend", map);
+ });
+
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors",
}).addTo(map);
- const elevation_options = {
+ const default_elevation_options = {
height: 200,
theme: "lightblue-theme",
@@ -84,11 +94,16 @@
},
};
+ const elevation_options = Object.assign(
+ default_elevation_options,
+ options,
+ );
+
controlElevation = L.control.elevation(elevation_options).addTo(map);
for (const waypoint of trail.expand.waypoints) {
const marker = createMarkerFromWaypoint(L, waypoint);
- marker.addTo(map);
+ marker.addTo(map!);
markers.push(marker);
}
});
@@ -96,16 +111,8 @@
-
-
diff --git a/web/src/lib/util/leaflet_util.ts b/web/src/lib/util/leaflet_util.ts
index 3b927112..a687b5d8 100644
--- a/web/src/lib/util/leaflet_util.ts
+++ b/web/src/lib/util/leaflet_util.ts
@@ -1,5 +1,5 @@
import type { Waypoint } from "$lib/models/waypoint";
-import type { Icon, LeafletEvent, Marker } from "leaflet";
+import type { Icon, LeafletEvent, Map, Marker } from "leaflet";
export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?: (event: LeafletEvent) => void): Marker {
const fontAwesomeIcon = L.AwesomeMarkers.icon({
@@ -27,4 +27,43 @@ export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?:
}
return marker;
+}
+
+export function calculatePixelPerMeter(map: Map, meters: number) {
+ const y = map.getSize().y;
+ const x = map.getSize().x;
+ const maxMeters = map.containerPointToLatLng([0, y]).distanceTo(map.containerPointToLatLng([x, y]));
+ const pixelPerMeter = x / maxMeters;
+
+ return pixelPerMeter * meters
+}
+
+export function calculateScaleFactor(map: Map) {
+ function _pxTOmm() {
+ let heightRef = document.createElement('div');
+ heightRef.style.height = '1mm';
+ heightRef.style.position = "absolute";
+ heightRef.id = 'heightRef';
+ document.body.appendChild(heightRef);
+
+ const pxPermm = heightRef.getBoundingClientRect().height;
+
+ document.body.removeChild(heightRef);
+
+ return function pxTOmm(px: number) {
+ return px / pxPermm;
+ }
+ }
+ var centerOfMap = map.getSize().y / 2;
+
+ var realWorlMetersPer100Pixels = map.distance(
+ map.containerPointToLatLng([0, centerOfMap]),
+ map.containerPointToLatLng([100, centerOfMap])
+ );
+
+ const screenMetersPer100Pixels = _pxTOmm()(100) / 1000;
+
+ const scaleFactor = realWorlMetersPer100Pixels / screenMetersPer100Pixels
+
+ return scaleFactor
}
\ No newline at end of file
diff --git a/web/src/lib/util/svg_util.ts b/web/src/lib/util/svg_util.ts
new file mode 100644
index 00000000..22fae97f
--- /dev/null
+++ b/web/src/lib/util/svg_util.ts
@@ -0,0 +1,29 @@
+export function createRect(
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ fill: string,
+) {
+ const svgns = "http://www.w3.org/2000/svg";
+
+ const rect = document.createElementNS(svgns, "rect");
+ rect.setAttribute("x", x.toString());
+ rect.setAttribute("y", y.toString());
+ rect.setAttribute("width", width.toString());
+ rect.setAttribute("height", height.toString());
+ rect.setAttribute("fill", fill);
+ return rect;
+}
+
+export function createText(content: string, x: number,
+ y: number, fontSize?: string) {
+ const svgns = "http://www.w3.org/2000/svg";
+
+ const text = document.createElementNS(svgns, "text");
+ text.setAttribute("x", x.toString());
+ text.setAttribute("y", y.toString());
+ text.style.fontSize = fontSize ?? ""
+ text.textContent = content;
+ return text;
+}
\ No newline at end of file
diff --git a/web/src/lib/vendor/leaflet-elevation/src/control.js b/web/src/lib/vendor/leaflet-elevation/src/control.js
index 0d24c873..d99bd394 100644
--- a/web/src/lib/vendor/leaflet-elevation/src/control.js
+++ b/web/src/lib/vendor/leaflet-elevation/src/control.js
@@ -773,6 +773,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
this.track_info.name = geojson.name;
}
+ style.color = "#ff0000"
let layer = L.geoJson(geojson, {
distanceMarkers: distanceMarkers,
style: style,
diff --git a/web/src/lib/vendor/leaflet-elevation/src/index.css b/web/src/lib/vendor/leaflet-elevation/src/index.css
index 3cc79cfb..1c23aca7 100644
--- a/web/src/lib/vendor/leaflet-elevation/src/index.css
+++ b/web/src/lib/vendor/leaflet-elevation/src/index.css
@@ -319,6 +319,17 @@
--ele-grid: rgba(var(--input-border));
}
+/* GRAY THEME //////////////////////////////////////////////// */
+.gray-theme {
+ --ele-bg: #ffffff;
+ --ele-area: #242734;
+ --ele-alpha: 0.45;
+ --ele-stroke: #7b7b7b;
+ --ele-circle: #fff;
+ --ele-line: #000;
+ --ele-grid: rgba(var(--input-border));
+}
+
.elevation-detached.lightblue-theme .area {
stroke: #3366CC;
}
diff --git a/web/src/lib/vendor/leaflet-elevation/src/utils.js b/web/src/lib/vendor/leaflet-elevation/src/utils.js
index 6366757c..5aaad7df 100644
--- a/web/src/lib/vendor/leaflet-elevation/src/utils.js
+++ b/web/src/lib/vendor/leaflet-elevation/src/utils.js
@@ -8,7 +8,8 @@ export const Colors = {
'purple' : { area: '#732C7B' },
'steelblue': { area: '#4682B4' },
'red' : { area: '#F00' },
- 'lime' : { area: '#9CC222', line: '#566B13' }
+ 'lime' : { area: '#9CC222', line: '#566B13' },
+ 'gray': { area: '#000000', line: '#242734', alpha: 0.00001, stroke: '#000000' }
};
const SEC = 1000;
diff --git a/web/src/lib/vendor/leaflet-image/leaflet-image.js b/web/src/lib/vendor/leaflet-image/leaflet-image.js
new file mode 100644
index 00000000..e3cbbfd8
--- /dev/null
+++ b/web/src/lib/vendor/leaflet-image/leaflet-image.js
@@ -0,0 +1,261 @@
+import queue from "./queue";
+
+var cacheBusterDate = +new Date();
+
+// leaflet-image
+export default function leafletImage(map, callback) {
+
+ var dimensions = map.getSize(),
+ layerQueue = new queue(1);
+
+ var canvas = document.createElement('canvas');
+ canvas.width = dimensions.x;
+ canvas.height = dimensions.y;
+ var ctx = canvas.getContext('2d');
+
+ // dummy canvas image when loadTile get 404 error
+ // and layer don't have errorTileUrl
+ var dummycanvas = document.createElement('canvas');
+ dummycanvas.width = 1;
+ dummycanvas.height = 1;
+ var dummyctx = dummycanvas.getContext('2d');
+ dummyctx.fillStyle = 'rgba(0,0,0,0)';
+ dummyctx.fillRect(0, 0, 1, 1);
+
+ // layers are drawn in the same order as they are composed in the DOM:
+ // tiles, paths, and then markers
+ map.eachLayer(drawTileLayer);
+ map.eachLayer(drawEsriDynamicLayer);
+
+ if (map._pathRoot) {
+ layerQueue.defer(handlePathRoot, map._pathRoot);
+ } else if (map._panes) {
+ var firstCanvas = map._panes.overlayPane.getElementsByTagName('canvas').item(0);
+ if (firstCanvas) {
+ layerQueue.defer(handlePathRoot, firstCanvas);
+ }
+ }
+ map.eachLayer(drawMarkerLayer);
+ layerQueue.awaitAll(layersDone);
+
+ function drawTileLayer(l) {
+ if (l instanceof L.TileLayer) layerQueue.defer(handleTileLayer, l);
+ else if (l._heat) layerQueue.defer(handlePathRoot, l._canvas);
+ }
+
+ function drawMarkerLayer(l) {
+ if (l instanceof L.Marker && l.options.icon instanceof L.Icon) {
+ layerQueue.defer(handleMarkerLayer, l);
+ }
+ }
+
+ function drawEsriDynamicLayer(l) {
+ if (!L.esri) return;
+
+ if (l instanceof L.esri.DynamicMapLayer) {
+ layerQueue.defer(handleEsriDymamicLayer, l);
+ }
+ }
+
+ function done() {
+ callback(null, canvas);
+ }
+
+ function layersDone(err, layers) {
+ if (err) throw err;
+ layers.forEach(function (layer) {
+ if (layer && layer.canvas) {
+ ctx.drawImage(layer.canvas, 0, 0);
+ }
+ });
+ done();
+ }
+
+ function handleTileLayer(layer, callback) {
+ // `L.TileLayer.Canvas` was removed in leaflet 1.0
+ var isCanvasLayer = (L.TileLayer.Canvas && layer instanceof L.TileLayer.Canvas),
+ canvas = document.createElement('canvas');
+
+ canvas.width = dimensions.x;
+ canvas.height = dimensions.y;
+
+ var ctx = canvas.getContext('2d'),
+ bounds = map.getPixelBounds(),
+ zoom = map.getZoom(),
+ tileSize = layer.options.tileSize;
+
+ if (zoom > layer.options.maxZoom ||
+ zoom < layer.options.minZoom
+ ) {
+ return callback();
+ }
+
+ var tileBounds = L.bounds(
+ bounds.min.divideBy(tileSize)._floor(),
+ bounds.max.divideBy(tileSize)._floor()),
+ tiles = [],
+ j, i,
+ tileQueue = new queue(1);
+
+ for (j = tileBounds.min.y; j <= tileBounds.max.y; j++) {
+ for (i = tileBounds.min.x; i <= tileBounds.max.x; i++) {
+ tiles.push(new L.Point(i, j));
+ }
+ }
+
+ tiles.forEach(function (tilePoint) {
+ var originalTilePoint = tilePoint.clone();
+
+ if (layer._adjustTilePoint) {
+ layer._adjustTilePoint(tilePoint);
+ }
+
+ var tilePos = originalTilePoint
+ .scaleBy(new L.Point(tileSize, tileSize))
+ .subtract(bounds.min);
+
+ if (tilePoint.y >= 0) {
+ if (isCanvasLayer) {
+ var tile = layer._tiles[tilePoint.x + ':' + tilePoint.y];
+ tileQueue.defer(canvasTile, tile, tilePos, tileSize);
+ } else {
+ var url = addCacheString(layer.getTileUrl(tilePoint));
+ tileQueue.defer(loadTile, url, tilePos, tileSize);
+ }
+ }
+ });
+
+ tileQueue.awaitAll(tileQueueFinish);
+
+ function canvasTile(tile, tilePos, tileSize, callback) {
+ callback(null, {
+ img: tile,
+ pos: tilePos,
+ size: tileSize
+ });
+ }
+
+ function loadTile(url, tilePos, tileSize, callback) {
+ var im = new Image();
+ im.crossOrigin = '';
+ im.onload = function () {
+ callback(null, {
+ img: this,
+ pos: tilePos,
+ size: tileSize
+ });
+ };
+ im.onerror = function (e) {
+ // use canvas instead of errorTileUrl if errorTileUrl get 404
+ if (layer.options.errorTileUrl != '' && e.target.errorCheck === undefined) {
+ e.target.errorCheck = true;
+ e.target.src = layer.options.errorTileUrl;
+ } else {
+ callback(null, {
+ img: dummycanvas,
+ pos: tilePos,
+ size: tileSize
+ });
+ }
+ };
+ im.src = url;
+ }
+
+ function tileQueueFinish(err, data) {
+ data.forEach(drawTile);
+ callback(null, { canvas: canvas });
+ }
+
+ function drawTile(d) {
+ ctx.drawImage(d.img, Math.floor(d.pos.x), Math.floor(d.pos.y),
+ d.size, d.size);
+ }
+ }
+
+ function handlePathRoot(root, callback) {
+ var bounds = map.getPixelBounds(),
+ origin = map.getPixelOrigin(),
+ canvas = document.createElement('canvas');
+ canvas.width = dimensions.x;
+ canvas.height = dimensions.y;
+ var ctx = canvas.getContext('2d');
+ var pos = L.DomUtil.getPosition(root).subtract(bounds.min).add(origin);
+ try {
+ ctx.drawImage(root, pos.x, pos.y, canvas.width - (pos.x * 2), canvas.height - (pos.y * 2));
+ callback(null, {
+ canvas: canvas
+ });
+ } catch (e) {
+ console.error('Element could not be drawn on canvas', root); // eslint-disable-line no-console
+ }
+ }
+
+ function handleMarkerLayer(marker, callback) {
+ var canvas = document.createElement('canvas'),
+ ctx = canvas.getContext('2d'),
+ pixelBounds = map.getPixelBounds(),
+ minPoint = new L.Point(pixelBounds.min.x, pixelBounds.min.y),
+ pixelPoint = map.project(marker.getLatLng()),
+ url = "/vendor/leaflet-image/marker.png",
+ icon = getComputedStyle(marker._icon.children[0], ':before').getPropertyValue('content').substring(1, 2) ?? "\uf111",
+ im = new Image(),
+ options = marker.options.icon.options,
+ size = options.iconSize,
+ pos = pixelPoint.subtract(minPoint),
+ anchor = L.point(options.iconAnchor || size && size.divideBy(2, true));
+
+ if (size instanceof L.Point) size = [size.x, size.y];
+
+ var x = Math.round(pos.x - size[0] + anchor.x),
+ y = Math.round(pos.y - anchor.y);
+
+ canvas.width = dimensions.x;
+ canvas.height = dimensions.y;
+ im.crossOrigin = '';
+
+ im.onload = function () {
+ 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);
+ callback(null, {
+ canvas: canvas
+ });
+ };
+
+ im.src = url;
+ }
+
+ function handleEsriDymamicLayer(dynamicLayer, callback) {
+ var canvas = document.createElement('canvas');
+ canvas.width = dimensions.x;
+ canvas.height = dimensions.y;
+
+ var ctx = canvas.getContext('2d');
+
+ var im = new Image();
+ im.crossOrigin = '';
+ im.src = addCacheString(dynamicLayer._currentImage._image.src);
+
+ im.onload = function () {
+ ctx.drawImage(im, 0, 0);
+ callback(null, {
+ canvas: canvas
+ });
+ };
+ }
+
+ function addCacheString(url) {
+ // If it's a data URL we don't want to touch this.
+ if (isDataURL(url)) {
+ return url;
+ }
+ return url + ((url.match(/\?/)) ? '&' : '?') + 'cache=' + cacheBusterDate;
+ }
+
+ function isDataURL(url) {
+ var dataURLRegex = /^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i;
+ return !!url.match(dataURLRegex);
+ }
+
+};
diff --git a/web/src/lib/vendor/leaflet-image/queue.js b/web/src/lib/vendor/leaflet-image/queue.js
new file mode 100644
index 00000000..253ffd08
--- /dev/null
+++ b/web/src/lib/vendor/leaflet-image/queue.js
@@ -0,0 +1,120 @@
+var noabort = {};
+const slice = [].slice;
+
+function Queue(size) {
+ this._size = size;
+ this._call =
+ this._error = null;
+ this._tasks = [];
+ this._data = [];
+ this._waiting =
+ this._active =
+ this._ended =
+ this._start = 0; // inside a synchronous task callback?
+}
+
+Queue.prototype = queue.prototype = {
+ constructor: Queue,
+ defer: function(callback) {
+ if (typeof callback !== "function") throw new Error("invalid callback");
+ if (this._call) throw new Error("defer after await");
+ if (this._error != null) return this;
+ var t = slice.call(arguments, 1);
+ t.push(callback);
+ ++this._waiting, this._tasks.push(t);
+ poke(this);
+ return this;
+ },
+ abort: function() {
+ if (this._error == null) abort(this, new Error("abort"));
+ return this;
+ },
+ await: function(callback) {
+ if (typeof callback !== "function") throw new Error("invalid callback");
+ if (this._call) throw new Error("multiple await");
+ this._call = function(error, results) { callback.apply(null, [error].concat(results)); };
+ maybeNotify(this);
+ return this;
+ },
+ awaitAll: function(callback) {
+ if (typeof callback !== "function") throw new Error("invalid callback");
+ if (this._call) throw new Error("multiple await");
+ this._call = callback;
+ maybeNotify(this);
+ return this;
+ }
+};
+
+function poke(q) {
+ if (!q._start) {
+ try { start(q); } // let the current task complete
+ catch (e) {
+ if (q._tasks[q._ended + q._active - 1]) abort(q, e); // task errored synchronously
+ else if (!q._data) throw e; // await callback errored synchronously
+ }
+ }
+}
+
+function start(q) {
+ while (q._start = q._waiting && q._active < q._size) {
+ var i = q._ended + q._active,
+ t = q._tasks[i],
+ j = t.length - 1,
+ c = t[j];
+ t[j] = end(q, i);
+ --q._waiting, ++q._active;
+ t = c.apply(null, t);
+ if (!q._tasks[i]) continue; // task finished synchronously
+ q._tasks[i] = t || noabort;
+ }
+}
+
+function end(q, i) {
+ return function(e, r) {
+ if (!q._tasks[i]) return; // ignore multiple callbacks
+ --q._active, ++q._ended;
+ q._tasks[i] = null;
+ if (q._error != null) return; // ignore secondary errors
+ if (e != null) {
+ abort(q, e);
+ } else {
+ q._data[i] = r;
+ if (q._waiting) poke(q);
+ else maybeNotify(q);
+ }
+ };
+}
+
+function abort(q, e) {
+ var i = q._tasks.length, t;
+ q._error = e; // ignore active callbacks
+ q._data = undefined; // allow gc
+ q._waiting = NaN; // prevent starting
+
+ while (--i >= 0) {
+ if (t = q._tasks[i]) {
+ q._tasks[i] = null;
+ if (t.abort) {
+ try { t.abort(); }
+ catch (e) { /* ignore */ }
+ }
+ }
+ }
+
+ q._active = NaN; // allow notification
+ maybeNotify(q);
+}
+
+function maybeNotify(q) {
+ if (!q._active && q._call) {
+ var d = q._data;
+ q._data = undefined; // allow gc
+ q._call(q._error, d);
+ }
+}
+
+export default function queue(concurrency) {
+ if (concurrency == null) concurrency = Infinity;
+ else if (!((concurrency = +concurrency) >= 1)) throw new Error("invalid concurrency");
+ return new Queue(concurrency);
+}
\ No newline at end of file
diff --git a/web/src/routes/map/trail/[id]/+page.svelte b/web/src/routes/map/trail/[id]/+page.svelte
index 6df874c4..112727ae 100644
--- a/web/src/routes/map/trail/[id]/+page.svelte
+++ b/web/src/routes/map/trail/[id]/+page.svelte
@@ -16,9 +16,7 @@
{$trail.name} | {$_("map")} | wanderer
-
+
diff --git a/web/src/routes/map/trail/[id]/print/+page.svelte b/web/src/routes/map/trail/[id]/print/+page.svelte
new file mode 100644
index 00000000..2df02add
--- /dev/null
+++ b/web/src/routes/map/trail/[id]/print/+page.svelte
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
diff --git a/web/src/routes/map/trail/[id]/print/+page.ts b/web/src/routes/map/trail/[id]/print/+page.ts
new file mode 100644
index 00000000..b7aa33b1
--- /dev/null
+++ b/web/src/routes/map/trail/[id]/print/+page.ts
@@ -0,0 +1,19 @@
+import { lists_index } from "$lib/stores/list_store";
+import { trails_show } from "$lib/stores/trail_store";
+import { error, type Load } from "@sveltejs/kit";
+import { ClientResponseError } from "pocketbase";
+
+export const load: Load = async ({ params, fetch }) => {
+ try {
+ await trails_show(params.id!, true, fetch)
+ } catch (e) {
+ if (e instanceof ClientResponseError && e.status == 404) {
+ error(404, {
+ message: 'Not found'
+ });
+ }
+ console.log(e);
+
+ }
+ await lists_index(undefined, fetch);
+};
\ No newline at end of file
diff --git a/web/static/vendor/leaflet-image/marker.png b/web/static/vendor/leaflet-image/marker.png
new file mode 100644
index 0000000000000000000000000000000000000000..f0716727ac0af4d27535a6a00c85158483fe94fd
GIT binary patch
literal 4658
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
literal 0
HcmV?d00001