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 @@ + + +
+ + +
+
+
+ updateScale(e.detail)} + bind:map + > + QR Code +
+ + Scale:   1 : {Math.round(scale)} +
+
+
+
+
+ +

{$trail.name}

+
+ {$trail.location || + "-"} +
+
+ {formatDistance( + $trail.distance, + )} + {formatElevation( + $trail.elevation_gain, + )} + {formatTimeHHMM( + $trail.duration, + )} +
+
+
+
+
+ + 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 00000000..f0716727 Binary files /dev/null and b/web/static/vendor/leaflet-image/marker.png differ