start localization

This commit is contained in:
vcoppe
2024-04-24 16:12:50 +02:00
parent 9bde53a4e2
commit 78b7612171
14 changed files with 1001 additions and 94 deletions

View File

@@ -17,6 +17,7 @@
"lucide-svelte": "^0.365.0",
"mapbox-gl": "^3.2.0",
"sortablejs": "^1.15.2",
"svelte-i18n": "^4.0.0",
"tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1"
},
@@ -332,6 +333,21 @@
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
@@ -656,6 +672,50 @@
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz",
"integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==",
"dependencies": {
"@formatjs/intl-localematcher": "0.5.4",
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz",
"integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "2.7.6",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz",
"integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.18.2",
"@formatjs/icu-skeleton-parser": "1.8.0",
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz",
"integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.18.2",
"tslib": "^2.4.0"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -2095,6 +2155,21 @@
"node": ">= 6"
}
},
"node_modules/cli-color": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz",
"integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.64",
"es6-iterator": "^2.0.3",
"memoizee": "^0.4.15",
"timers-ext": "^0.1.7"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@@ -2217,6 +2292,18 @@
"node": ">=4"
}
},
"node_modules/d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"dependencies": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2300,7 +2387,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2415,12 +2501,60 @@
"is-arrayish": "^0.2.1"
}
},
"node_modules/es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
"dev": true
},
"node_modules/es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"dependencies": {
"d": "^1.0.2",
"ext": "^1.7.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/es6-weak-map": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
"integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.46",
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1"
}
},
"node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
@@ -2459,6 +2593,22 @@
"@esbuild/win32-x64": "0.20.2"
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
@@ -2650,6 +2800,20 @@
"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==",
"dev": true
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -2717,6 +2881,15 @@
"node": ">=0.10.0"
}
},
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"dependencies": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"node_modules/eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
@@ -2730,6 +2903,14 @@
"node": ">=0.8.x"
}
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3017,8 +3198,7 @@
"node_modules/globalyzer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
"dev": true
"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
},
"node_modules/globby": {
"version": "11.1.0",
@@ -3043,8 +3223,7 @@
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
},
"node_modules/got": {
"version": "11.8.6",
@@ -3234,6 +3413,17 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/intl-messageformat": {
"version": "10.5.11",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.11.tgz",
"integrity": "sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.18.2",
"@formatjs/fast-memoize": "2.2.0",
"@formatjs/icu-messageformat-parser": "2.7.6",
"tslib": "^2.4.0"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -3313,6 +3503,11 @@
"node": ">=0.10.0"
}
},
"node_modules/is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
},
"node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
@@ -3507,6 +3702,14 @@
"node": ">=10"
}
},
"node_modules/lru-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
"integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==",
"dependencies": {
"es5-ext": "~0.10.2"
}
},
"node_modules/lucide-svelte": {
"version": "0.365.0",
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.365.0.tgz",
@@ -3575,6 +3778,21 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
},
"node_modules/memoizee": {
"version": "0.4.15",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz",
"integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.53",
"es6-weak-map": "^2.0.3",
"event-emitter": "^0.3.5",
"is-promise": "^2.2.2",
"lru-queue": "^0.1.0",
"next-tick": "^1.1.0",
"timers-ext": "^0.1.7"
}
},
"node_modules/meow": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
@@ -3725,7 +3943,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
"dev": true,
"engines": {
"node": ">=4"
}
@@ -3783,6 +4000,11 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@@ -4619,7 +4841,6 @@
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
"dev": true,
"dependencies": {
"mri": "^1.1.0"
},
@@ -5066,6 +5287,401 @@
"svelte": "^3.19.0 || ^4.0.0"
}
},
"node_modules/svelte-i18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-4.0.0.tgz",
"integrity": "sha512-4vivjKZADUMRIhTs38JuBNy3unbnh9AFRxWFLxq62P4NHic+/BaIZZlAsvqsCdnp7IdJf5EoSiH6TNdItcjA6g==",
"dependencies": {
"cli-color": "^2.0.3",
"deepmerge": "^4.2.2",
"esbuild": "^0.19.2",
"estree-walker": "^2",
"intl-messageformat": "^10.5.3",
"sade": "^1.8.1",
"tiny-glob": "^0.2.9"
},
"bin": {
"svelte-i18n": "dist/cli.js"
},
"engines": {
"node": ">= 16"
},
"peerDependencies": {
"svelte": "^3 || ^4"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/aix-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/android-arm": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/android-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/android-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/darwin-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/darwin-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/freebsd-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/freebsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-ia32": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-loong64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
"cpu": [
"loong64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-mips64el": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
"cpu": [
"mips64el"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-ppc64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-riscv64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-s390x": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/linux-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/netbsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/openbsd-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/sunos-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/win32-arm64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/win32-ia32": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/@esbuild/win32-x64": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/svelte-i18n/node_modules/esbuild": {
"version": "0.19.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.19.12",
"@esbuild/android-arm": "0.19.12",
"@esbuild/android-arm64": "0.19.12",
"@esbuild/android-x64": "0.19.12",
"@esbuild/darwin-arm64": "0.19.12",
"@esbuild/darwin-x64": "0.19.12",
"@esbuild/freebsd-arm64": "0.19.12",
"@esbuild/freebsd-x64": "0.19.12",
"@esbuild/linux-arm": "0.19.12",
"@esbuild/linux-arm64": "0.19.12",
"@esbuild/linux-ia32": "0.19.12",
"@esbuild/linux-loong64": "0.19.12",
"@esbuild/linux-mips64el": "0.19.12",
"@esbuild/linux-ppc64": "0.19.12",
"@esbuild/linux-riscv64": "0.19.12",
"@esbuild/linux-s390x": "0.19.12",
"@esbuild/linux-x64": "0.19.12",
"@esbuild/netbsd-x64": "0.19.12",
"@esbuild/openbsd-x64": "0.19.12",
"@esbuild/sunos-x64": "0.19.12",
"@esbuild/win32-arm64": "0.19.12",
"@esbuild/win32-ia32": "0.19.12",
"@esbuild/win32-x64": "0.19.12"
}
},
"node_modules/svelte-i18n/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/svelte-preprocess": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",
@@ -5278,11 +5894,19 @@
"node": ">=0.8"
}
},
"node_modules/timers-ext": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
"integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
"dependencies": {
"es5-ext": "~0.10.46",
"next-tick": "1"
}
},
"node_modules/tiny-glob": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
"integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
"dev": true,
"dependencies": {
"globalyzer": "0.1.0",
"globrex": "^0.1.2"
@@ -5351,6 +5975,11 @@
"url": "https://github.com/sponsors/cocopon"
}
},
"node_modules/type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@@ -50,6 +50,7 @@
"lucide-svelte": "^0.365.0",
"mapbox-gl": "^3.2.0",
"sortablejs": "^1.15.2",
"svelte-i18n": "^4.0.0",
"tailwind-merge": "^2.2.2",
"tailwind-variants": "^0.2.1"
}

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import Data from '$lib/components/Data.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import FileList from '$lib/components/FileList.svelte';
import GPXData from '$lib/components/GPXData.svelte';
import Map from '$lib/components/Map.svelte';
import Menu from '$lib/components/Menu.svelte';
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
</script>
<div class="flex flex-col w-screen h-screen">
<div class="grow relative">
<Menu />
<Toolbar />
<Map class="h-full" />
<LayerControl />
<Data />
<FileList />
</div>
<div class="h-60 flex flex-row gap-2 overflow-hidden border">
<GPXData />
<ElevationProfile />
</div>
</div>

View File

@@ -6,7 +6,7 @@
import Chart from 'chart.js/auto';
import mapboxgl from 'mapbox-gl';
import { map, fileCollection, fileOrder, selectedFiles } from '$lib/stores';
import { map, fileCollection, fileOrder, selectedFiles, settings } from '$lib/stores';
import { onDestroy, onMount } from 'svelte';
import {
@@ -21,6 +21,28 @@
import { GPXFiles } from 'gpx';
import { surfaceColors } from '$lib/assets/surfaces';
import { _ } from 'svelte-i18n';
import {
getCadenceUnits,
getCadenceWithUnits,
getConvertedDistance,
getConvertedElevation,
getConvertedTemperature,
getConvertedVelocity,
getDistanceUnits,
getDistanceWithUnits,
getElevationUnits,
getElevationWithUnits,
getHeartRateUnits,
getHeartRateWithUnits,
getPowerUnits,
getPowerWithUnits,
getTemperatureUnits,
getTemperatureWithUnits,
getVelocityUnits,
getVelocityWithUnits
} from '$lib/units';
let canvas: HTMLCanvasElement;
let chart: Chart;
@@ -41,7 +63,7 @@
type: 'linear',
title: {
display: true,
text: 'Distance (km)',
text: `${$_('quantities.distance')} (${getDistanceUnits()})`,
padding: 0,
align: 'end'
}
@@ -50,7 +72,7 @@
type: 'linear',
title: {
display: true,
text: 'Elevation (m)',
text: `${$_('quantities.elevation')} (${getElevationUnits()})`,
padding: 0
}
}
@@ -82,41 +104,34 @@
label: function (context: Chart.TooltipContext) {
let point = context.raw;
if (context.datasetIndex === 0) {
let elevation = point.y.toFixed(0);
if ($map && marker) {
marker.addTo($map);
marker.setLngLat(point.coordinates);
}
return `Elevation: ${elevation} m`;
return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 1) {
let speed = point.y.toFixed(2);
return `Speed: ${speed} km/h`;
return `${$settings.velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 2) {
let hr = point.y;
return `Heart Rate: ${hr} bpm`;
return `${$_('quantities.heartrate')}: ${getHeartRateWithUnits(point.y)}`;
} else if (context.datasetIndex === 3) {
let cad = point.y;
return `Cadence: ${cad} rpm`;
return `${$_('quantities.cadence')}: ${getCadenceWithUnits(point.y)}`;
} else if (context.datasetIndex === 4) {
let atemp = point.y.toFixed(1);
return `Temperature: ${atemp} °C`;
return `${$_('quantities.temperature')}: ${getTemperatureWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 5) {
let power = point.y;
return `Power: ${power} W`;
return `${$_('quantities.power')}: ${getPowerWithUnits(point.y)}`;
}
},
afterBody: function (contexts: Chart.TooltipContext[]) {
let context = contexts.filter((context) => context.datasetIndex === 0);
if (context.length === 0) return;
let point = context[0].raw;
let distance = point.x.toFixed(2);
let slope = point.slope.toFixed(1);
let surface = point.surface ? point.surface : 'unknown';
return [
` Distance: ${distance} km`,
` Slope: ${slope} %`,
` Surface: ${surface}`
` ${$_('quantities.distance')}: ${getDistanceWithUnits(point.x, false)}`,
` ${$_('quantities.slope')}: ${slope} %`,
` ${$_('quantities.surface')}: ${surface}`
];
}
}
@@ -134,28 +149,28 @@
} = {
speed: {
id: 'speed',
label: 'Speed',
units: 'km/h'
label: $_('quantities.speed'),
units: getVelocityUnits()
},
hr: {
id: 'hr',
label: 'Heart Rate',
units: 'bpm'
label: $_('quantities.heartrate'),
units: getHeartRateUnits()
},
cad: {
id: 'cad',
label: 'Cadence',
units: 'rpm'
label: $_('quantities.cadence'),
units: getCadenceUnits()
},
atemp: {
id: 'atemp',
label: 'Temperature',
units: '°C'
label: $_('quantities.temperature'),
units: getTemperatureUnits()
},
power: {
id: 'power',
label: 'Power',
units: 'W'
label: $_('quantities.power'),
units: getPowerUnits()
}
};
@@ -211,11 +226,11 @@
let trackPointsAndStatistics = gpxFiles.getTrackPointsAndStatistics();
chart.data.datasets[0] = {
label: 'Elevation',
label: $_('quantities.elevation'),
data: trackPointsAndStatistics.points.map((point, index) => {
return {
x: trackPointsAndStatistics.statistics.distance[index],
y: point.ele ? point.ele : 0,
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
y: point.ele ? getConvertedElevation(point.ele) : 0,
slope: trackPointsAndStatistics.statistics.slope[index],
surface: point.getSurface(),
coordinates: point.getCoordinates()
@@ -229,8 +244,8 @@
label: datasets.speed.label,
data: trackPointsAndStatistics.points.map((point, index) => {
return {
x: trackPointsAndStatistics.statistics.distance[index],
y: trackPointsAndStatistics.statistics.speed[index]
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
y: getConvertedVelocity(trackPointsAndStatistics.statistics.speed[index])
};
}),
normalized: true,
@@ -241,7 +256,7 @@
label: datasets.hr.label,
data: trackPointsAndStatistics.points.map((point, index) => {
return {
x: trackPointsAndStatistics.statistics.distance[index],
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
y: point.getHeartRate()
};
}),
@@ -253,7 +268,7 @@
label: datasets.cad.label,
data: trackPointsAndStatistics.points.map((point, index) => {
return {
x: trackPointsAndStatistics.statistics.distance[index],
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
y: point.getCadence()
};
}),
@@ -265,8 +280,8 @@
label: datasets.atemp.label,
data: trackPointsAndStatistics.points.map((point, index) => {
return {
x: trackPointsAndStatistics.statistics.distance[index],
y: point.getTemperature()
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
y: getConvertedTemperature(point.getTemperature())
};
}),
normalized: true,
@@ -277,7 +292,7 @@
label: datasets.power.label,
data: trackPointsAndStatistics.points.map((point, index) => {
return {
x: trackPointsAndStatistics.statistics.distance[index],
x: getConvertedDistance(trackPointsAndStatistics.statistics.distance[index]),
y: point.getPower()
};
}),
@@ -286,7 +301,8 @@
hidden: true
};
chart.options.scales.x['min'] = 0;
chart.options.scales.x['max'] = gpxFiles.statistics.distance.total;
chart.options.scales.x['max'] = getConvertedDistance(gpxFiles.statistics.distance.total);
chart.update();
}

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card';
import Tooltip from '$lib/components/Tooltip.svelte';
import WithUnits from '$lib/components/WithUnits.svelte';
import { GPXStatistics } from 'gpx';
@@ -17,17 +18,6 @@
}
});
}
function toHHMMSS(seconds: number) {
var hours = Math.floor(seconds / 3600);
var minutes = Math.floor(seconds / 60) % 60;
var seconds = Math.round(seconds % 60);
return [hours, minutes, seconds]
.map((v) => (v < 10 ? '0' + v : v))
.filter((v, i) => v !== '00' || i > 0)
.join(':');
}
</script>
<Card.Root class="h-full overflow-hidden border-none min-w-48 pl-4">
@@ -35,30 +25,32 @@
<Tooltip>
<span slot="data" class="flex flex-row items-center">
<Ruler size="18" class="mr-1" />
{gpxData.distance.total.toFixed(2)} km
<WithUnits value={gpxData.distance.total} type="distance" />
</span>
<span slot="tooltip">Distance</span>
</Tooltip>
<Tooltip>
<span slot="data" class="flex flex-row items-center">
<MoveUpRight size="18" class="mr-1" />
{gpxData.elevation.gain.toFixed(0)} m
<WithUnits value={gpxData.elevation.gain} type="elevation" />
<MoveDownRight size="18" class="mx-1" />
{gpxData.elevation.loss.toFixed(0)} m
<WithUnits value={gpxData.elevation.loss} type="elevation" />
</span>
<span slot="tooltip">Elevation</span>
</Tooltip>
<Tooltip>
<span slot="data" class="flex flex-row items-center">
<Zap size="18" class="mr-1" />
{gpxData.speed.moving.toFixed(2)} km/h
<WithUnits value={gpxData.speed.moving} type="speed" />
</span>
<span slot="tooltip">Speed</span>
</Tooltip>
<Tooltip>
<span slot="data" class="flex flex-row items-center">
<Timer size="18" class="mr-1" />
{toHHMMSS(gpxData.time.moving)} / {toHHMMSS(gpxData.time.total)}
<WithUnits value={gpxData.time.moving} type="time" />
<span class="mx-1">/</span>
<WithUnits value={gpxData.time.total} type="time" />
</span>
<span slot="tooltip">Moving time / Total time</span>
</Tooltip>

View File

@@ -23,11 +23,12 @@
removeAllFiles,
removeSelectedFiles,
triggerFileInput,
selectFiles
selectFiles,
settings
} from '$lib/stores';
let distanceUnits = 'metric';
let velocityUnits = 'speed';
import { _ } from 'svelte-i18n';
let showDistanceMarkers = false;
let showDirectionMarkers = false;
</script>
@@ -39,10 +40,12 @@
<Logo class="h-5 mt-0.5 mx-2" />
<Menubar.Root class="border-none h-fit p-0">
<Menubar.Menu>
<Menubar.Trigger>File</Menubar.Trigger>
<Menubar.Trigger>{$_('menu.file')}</Menubar.Trigger>
<Menubar.Content>
<Menubar.Item>
<Plus size="16" class="mr-1" /> New <Menubar.Shortcut>⌘N</Menubar.Shortcut>
<Plus size="16" class="mr-1" />
{$_('menu.new')}
<Menubar.Shortcut>⌘N</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item on:click={triggerFileInput}>
@@ -108,7 +111,7 @@
><Menubar.Sub>
<Menubar.SubTrigger inset>Distance units</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={distanceUnits}>
<Menubar.RadioGroup bind:value={$settings.distanceUnits}>
<Menubar.RadioItem value="metric">Metric</Menubar.RadioItem>
<Menubar.RadioItem value="imperial">Imperial</Menubar.RadioItem>
</Menubar.RadioGroup>
@@ -117,12 +120,21 @@
<Menubar.Sub>
<Menubar.SubTrigger inset>Velocity units</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={velocityUnits}>
<Menubar.RadioGroup bind:value={$settings.velocityUnits}>
<Menubar.RadioItem value="speed">Speed</Menubar.RadioItem>
<Menubar.RadioItem value="pace">Pace</Menubar.RadioItem>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Sub>
<Menubar.SubTrigger inset>Temperature units</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={$settings.temperatureUnits}>
<Menubar.RadioItem value="celsius">Celsius</Menubar.RadioItem>
<Menubar.RadioItem value="fahrenheit">Fahrenheit</Menubar.RadioItem>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.CheckboxItem bind:checked={showDistanceMarkers}>
Show distance markers

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import { settings } from '$lib/stores';
import {
celsiusToFahrenheit,
distancePerHourToSecondsPerDistance,
kilometersToMiles,
metersToFeet,
secondsToHHMMSS
} from '$lib/units';
import { _ } from 'svelte-i18n';
export let value: number;
export let type: 'distance' | 'elevation' | 'speed' | 'temperature' | 'time';
</script>
{#if type === 'distance'}
{#if $settings.distanceUnits === 'metric'}
{value.toFixed(2)} {$_('units.kilometers')}
{:else}
{kilometersToMiles(value).toFixed(2)} {$_('units.miles')}
{/if}
{:else if type === 'elevation'}
{#if $settings.distanceUnits === 'metric'}
{value.toFixed(0)} {$_('units.meters')}
{:else}
{metersToFeet(value).toFixed(0)} {$_('units.feet')}
{/if}
{:else if type === 'speed'}
{#if $settings.distanceUnits === 'metric'}
{#if $settings.velocityUnits === 'speed'}
{value.toFixed(2)} {$_('units.kilometers_per_hour')}
{:else}
{secondsToHHMMSS(distancePerHourToSecondsPerDistance(value))}
{$_('units.minutes_per_kilometer')}
{/if}
{:else if $settings.velocityUnits === 'speed'}
{kilometersToMiles(value).toFixed(2)} {$_('units.miles_per_hour')}
{:else}
{secondsToHHMMSS(distancePerHourToSecondsPerDistance(kilometersToMiles(value)))}
{$_('units.minutes_per_mile')}
{/if}
{:else if type === 'temperature'}
{#if $settings.temperatureUnits === 'celsius'}
{value} {$_('units.celsius')}
{:else}
{celsiusToFahrenheit(value)} {$_('units.fahrenheit')}
{/if}
{:else if type === 'time'}
{secondsToHHMMSS(value)}
{/if}

View File

@@ -8,6 +8,11 @@ export const fileCollection = writable<GPXFiles>(new GPXFiles([]));
export const fileOrder = writable<GPXFile[]>([]);
export const selectedFiles = writable<Set<GPXFile>>(new Set());
export const selectFiles = writable<{ [key: string]: (file?: GPXFile) => void }>({});
export const settings = writable<{ [key: string]: any }>({
distanceUnits: 'metric',
velocityUnits: 'speed',
temperatureUnits: 'celsius',
});
export function addFile(file: GPXFile) {
fileCollection.update($files => {

144
website/src/lib/units.ts Normal file
View File

@@ -0,0 +1,144 @@
import { get } from 'svelte/store';
import { settings } from './stores';
import { _ } from 'svelte-i18n';
export function kilometersToMiles(value: number) {
return value * 0.621371;
}
export function metersToFeet(value: number) {
return value * 3.28084;
}
export function celsiusToFahrenheit(value: number) {
return value * 1.8 + 32;
}
export function distancePerHourToSecondsPerDistance(value: number) {
return 3600 / value;
}
export function secondsToHHMMSS(value: number) {
var hours = Math.floor(value / 3600);
var minutes = Math.floor(value / 60) % 60;
var seconds = Math.round(value % 60);
return [hours, minutes, seconds]
.map((v) => (v < 10 ? '0' + v : v))
.filter((v, i) => v !== '00' || i > 0)
.join(':');
}
// Get a string representation of the value with units
export function getDistanceWithUnits(value: number, convert: boolean = true) {
if (convert) {
return getConvertedDistance(value).toFixed(2) + ' ' + getDistanceUnits();
} else {
return value.toFixed(2) + ' ' + getDistanceUnits();
}
}
export function getVelocityWithUnits(value: number, convert: boolean = true) {
const velocityUnits = get(settings).velocityUnits;
const distanceUnits = get(settings).distanceUnits;
if (velocityUnits === 'speed') {
if (convert) {
return getConvertedVelocity(value).toFixed(2) + ' ' + getVelocityUnits();
} else {
return value.toFixed(2) + ' ' + getVelocityUnits();
}
} else {
if (convert) {
return secondsToHHMMSS(getConvertedVelocity(value));
} else {
return secondsToHHMMSS(value);
}
}
}
export function getElevationWithUnits(value: number, convert: boolean = true) {
if (convert) {
return getConvertedElevation(value).toFixed(0) + ' ' + getElevationUnits();
} else {
return value.toFixed(0) + ' ' + getElevationUnits();
}
}
export function getHeartRateWithUnits(value: number) {
return value.toFixed(0) + ' ' + getHeartRateUnits();
}
export function getCadenceWithUnits(value: number) {
return value.toFixed(0) + ' ' + getCadenceUnits();
}
export function getPowerWithUnits(value: number) {
return value.toFixed(0) + ' ' + getPowerUnits();
}
export function getTemperatureWithUnits(value: number, convert: boolean = true) {
if (convert) {
return getConvertedTemperature(value).toFixed(0) + ' ' + getTemperatureUnits();
} else {
return value.toFixed(0) + ' ' + getTemperatureUnits();
}
}
// Get the units
export function getDistanceUnits() {
return get(settings).distanceUnits === 'metric' ? get(_)('units.kilometers') : get(_)('units.miles');
}
export function getVelocityUnits() {
const velocityUnits = get(settings).velocityUnits;
const distanceUnits = get(settings).distanceUnits;
if (velocityUnits === 'speed') {
return distanceUnits === 'metric' ? get(_)('units.kilometers_per_hour') : get(_)('units.miles_per_hour');
} else {
return distanceUnits === 'metric' ? get(_)('units.minutes_per_kilometer') : get(_)('units.minutes_per_mile');
}
}
export function getElevationUnits() {
return get(settings).distanceUnits === 'metric' ? get(_)('units.meters') : get(_)('units.feet');
}
export function getHeartRateUnits() {
return get(_)('units.heartrate');
}
export function getCadenceUnits() {
return get(_)('units.cadence');
}
export function getPowerUnits() {
return get(_)('units.power');
}
export function getTemperatureUnits() {
return get(settings).temperatureUnits === 'celsius' ? get(_)('units.celsius') : get(_)('units.fahrenheit');
}
// Convert only the value
export function getConvertedDistance(value: number) {
return get(settings).distanceUnits === 'metric' ? value : kilometersToMiles(value);
}
export function getConvertedElevation(value: number) {
return get(settings).distanceUnits === 'metric' ? value : metersToFeet(value);
}
export function getConvertedVelocity(value: number) {
const velocityUnits = get(settings).velocityUnits;
const distanceUnits = get(settings).distanceUnits;
if (velocityUnits === 'speed') {
return distanceUnits === 'metric' ? value : kilometersToMiles(value);
} else {
return distanceUnits === 'metric' ? distancePerHourToSecondsPerDistance(value) : distancePerHourToSecondsPerDistance(kilometersToMiles(value));
}
}
export function getConvertedTemperature(value: number) {
return get(settings).temperatureUnits === 'celsius' ? value : celsiusToFahrenheit(value);
}

View File

@@ -0,0 +1,33 @@
{
"menu": {
"file": "File",
"new": "New"
},
"quantities": {
"distance": "Distance",
"elevation": "Elevation",
"temperature": "Temperature",
"speed": "Speed",
"pace": "Pace",
"heartrate": "Heart rate",
"cadence": "Cadence",
"power": "Power",
"slope": "Slope",
"surface": "Surface"
},
"units": {
"meters": "m",
"feet": "ft",
"kilometers": "km",
"miles": "mi",
"celsius": "°C",
"fahrenheit": "°F",
"kilometers_per_hour": "km/h",
"miles_per_hour": "mph",
"minutes_per_kilometer": "min/km",
"minutes_per_mile": "min/mi",
"heartrate": "bpm",
"cadence": "rpm",
"power": "W"
}
}

View File

@@ -0,0 +1,10 @@
export const prerender = true;
import { register, init } from 'svelte-i18n';
register('en', () => import('../locales/en.json'));
init({
fallbackLocale: 'en',
initialLocale: 'en',
});

View File

@@ -1 +0,0 @@
export const prerender = true;

View File

@@ -1,25 +1,5 @@
<script lang="ts">
import Data from '$lib/components/Data.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import FileList from '$lib/components/FileList.svelte';
import GPXData from '$lib/components/GPXData.svelte';
import Map from '$lib/components/Map.svelte';
import Menu from '$lib/components/Menu.svelte';
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
import App from '$lib/components/App.svelte';
</script>
<div class="flex flex-col w-screen h-screen">
<div class="grow relative">
<Menu />
<Toolbar />
<Map class="h-full" />
<LayerControl />
<Data />
<FileList />
</div>
<div class="h-60 flex flex-row gap-2 overflow-hidden border">
<GPXData />
<ElevationProfile />
</div>
</div>
<App />

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import App from '$lib/components/App.svelte';
import { locale } from 'svelte-i18n';
import { page } from '$app/stores';
locale.set($page.params.language);
</script>
<App />