This commit is contained in:
Christian Beutel
2024-06-15 18:13:46 +02:00
parent 48c192376c
commit 369e63972c
27 changed files with 1321 additions and 41 deletions

9
web/package-lock.json generated
View File

@@ -16,6 +16,7 @@
"@types/three": "^0.161.2", "@types/three": "^0.161.2",
"canvg": "^4.0.1", "canvg": "^4.0.1",
"crypto-random-string": "^5.0.0", "crypto-random-string": "^5.0.0",
"instead": "^1.0.3",
"isomorphic-xml2js": "^0.1.3", "isomorphic-xml2js": "^0.1.3",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",
@@ -2929,6 +2930,14 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"node_modules/instead": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/instead/-/instead-1.0.3.tgz",
"integrity": "sha512-7EUe8+ZmvERjdLwrEc1/CdErN8n8XYdF/uI+n8aLbs6Uf/wyqUXyFrCQ8HYADBFaz4+IqGiPVSTInpkMwqrXuw==",
"engines": {
"node": ">=10"
}
},
"node_modules/internal-slot": { "node_modules/internal-slot": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",

View File

@@ -40,6 +40,7 @@
"@types/three": "^0.161.2", "@types/three": "^0.161.2",
"canvg": "^4.0.1", "canvg": "^4.0.1",
"crypto-random-string": "^5.0.0", "crypto-random-string": "^5.0.0",
"instead": "^1.0.3",
"isomorphic-xml2js": "^0.1.3", "isomorphic-xml2js": "^0.1.3",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",

View File

@@ -65,7 +65,7 @@ export const handle: Handle = async ({ event, resolve }) => {
// send back the default 'pb_auth' cookie to the client with the latest store state // send back the default 'pb_auth' cookie to the client with the latest store state
response.headers.set( response.headers.set(
'set-cookie', 'set-cookie',
pb.authStore.exportToCookie({ httpOnly: false, secure: false }) pb.authStore.exportToCookie({ httpOnly: false, secure: true })
) )
return response return response

View File

@@ -68,7 +68,7 @@
<li <li
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors" class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
role="presentation" role="presentation"
on:mouseup|stopPropagation={() => handleItemClick(item)} on:mousedown|stopPropagation={() => handleItemClick(item)}
> >
{#if item.icon} {#if item.icon}
<i class="fa fa-{item.icon} mr-3"></i> <i class="fa fa-{item.icon} mr-3"></i>

View File

@@ -5,6 +5,7 @@
export let src: string; export let src: string;
export let isThumbnail: boolean = false; export let isThumbnail: boolean = false;
export let showThumbnailControls: boolean = true; export let showThumbnailControls: boolean = true;
export let showExifControls: boolean = false;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@@ -12,6 +13,10 @@
dispatch("thumbnail", src); dispatch("thumbnail", src);
} }
function handleExifClick() {
dispatch("exif", src);
}
function handleDeleteClick() { function handleDeleteClick() {
dispatch("delete", src); dispatch("delete", src);
} }
@@ -38,7 +43,15 @@
><i class="fa fa-file-image text-primary"></i></button ><i class="fa fa-file-image text-primary"></i></button
> >
{/if} {/if}
{#if showExifControls}
<button
type="button"
class="tooltip"
data-title={$_("get-position-from-exif")}
on:click={handleExifClick}
><i class="fa fa-magnifying-glass-location text-primary"></i></button
>
{/if}
<button <button
class="tooltip" class="tooltip"
data-title={$_("delete")} data-title={$_("delete")}

View File

@@ -8,6 +8,8 @@
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css"; import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
import { _ } from "svelte-i18n";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
export let trail: Trail | null; export let trail: Trail | null;
export let markers: Marker[] = []; export let markers: Marker[] = [];
@@ -27,10 +29,17 @@
controlElevation.load(gpxData); controlElevation.load(gpxData);
} }
$: if(options) { $: if (options) {
controlElevation?.updateOptions(options) controlElevation?.updateOptions(options);
} }
const hotlineSwitcherItems: DropdownItem[] = [
{ text: $_("altitude"), value: "altitude" },
{ text: $_("slope"), value: "slope" },
{ text: $_("speed"), value: "speed" },
{ text: $_("off"), value: false },
];
onMount(async () => { onMount(async () => {
L = (await import("leaflet")).default; L = (await import("leaflet")).default;
await import("leaflet-gpx"); await import("leaflet-gpx");
@@ -55,9 +64,39 @@
dispatch("click", e); dispatch("click", e);
}); });
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { const baseLayer = L.tileLayer(
attribution: "© OpenStreetMap contributors", "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
}).addTo(map); {
attribution: "© OpenStreetMap contributors",
},
);
const topoLayer = L.tileLayer(
"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png ",
{
attribution: "© OpenStreetMap contributors",
},
);
const baseMaps = {
OpenStreetMaps: baseLayer,
OpenTopoMaps: topoLayer,
};
L.control.layers(baseMaps).addTo(map);
switch (localStorage.getItem("layer")) {
case "OpenTopoMaps":
topoLayer.addTo(map);
break;
default:
baseLayer.addTo(map);
break;
}
map!.on("baselayerchange", function (e) {
localStorage.setItem("layer", e.name);
});
const default_elevation_options = { const default_elevation_options = {
height: 200, height: 200,
@@ -84,11 +123,16 @@
// Toggle "leaflet-almostover" integration // Toggle "leaflet-almostover" integration
almostOver: false, almostOver: false,
// Toggle "leaflet-distance-markers" integration // Toggle "leaflet-distance-markers" integration
distanceMarkers: { lazy: false, distance: false, direction: true, offset: 500 }, distanceMarkers: {
lazy: false,
distance: false,
direction: true,
offset: 500,
},
// Toggle "leaflet-edgescale" integration // Toggle "leaflet-edgescale" integration
edgeScale: false, edgeScale: false,
// Toggle "leaflet-hotline" integration // Toggle "leaflet-hotline" integration
hotline: true, hotline: "altitude",
// Display track datetimes: true || false // Display track datetimes: true || false
timestamps: false, timestamps: false,
waypoints: false, waypoints: false,
@@ -103,7 +147,7 @@
prefix: "fa", prefix: "fa",
markerColor: "cadetblue", markerColor: "cadetblue",
iconColor: "white", iconColor: "white",
className: "awesome-marker pointer-events-none" className: "awesome-marker pointer-events-none",
}), }),
}, },
trkEnd: { trkEnd: {
@@ -112,11 +156,11 @@
prefix: "fa", prefix: "fa",
markerColor: "cadetblue", markerColor: "cadetblue",
iconColor: "white", iconColor: "white",
className: "awesome-marker pointer-events-none" className: "awesome-marker pointer-events-none",
}), }),
}, },
graticule: false, graticule: false,
drawing: false drawing: false,
}; };
const elevation_options = Object.assign( const elevation_options = Object.assign(
@@ -137,16 +181,37 @@
graticule.addTo(map!); graticule.addTo(map!);
} }
}); });
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
controlElevation.updateOptions({ hotline: metric, autofitBounds: false });
controlElevation.clear();
controlElevation.load(gpxData);
}
</script> </script>
<div id="map-container" class="flex flex-col h-full"> <div id="map-container" class="flex flex-col h-full">
<div <div
id="map" id="map"
class="rounded-xl z-0 basis-full min-h-72" class="rounded-xl z-0 basis-full min-h-96 md:min-h-0"
style={crosshair style={crosshair
? "position: relative; outline-style: none;cursor: crosshair !important" ? "position: relative; outline-style: none;cursor: crosshair !important"
: "position: relative; outline-style: none;"} : "position: relative; outline-style: none;"}
></div> >
<div class="absolute top-20 right-3 text-sm" style="z-index: 500">
<Dropdown
items={hotlineSwitcherItems}
on:change={(e) => switchHotline(e.detail.value)}
let:toggleMenu={openDropdown}
>
<button
class="rounded-sm bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-11 w-11"
on:click={openDropdown}
>
<i class="fa fa-route text-lg"></i>
</button>
</Dropdown>
</div>
</div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<slot /> <slot />
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div> <div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>

View File

@@ -8,6 +8,7 @@
export let parent: { [key: string]: any }; export let parent: { [key: string]: any };
export let thumbnail: number = 0; export let thumbnail: number = 0;
export let showThumbnailControls: boolean = true; export let showThumbnailControls: boolean = true;
export let showExifControls: boolean = false;
let photoPreviews: string[] = []; let photoPreviews: string[] = [];
@@ -59,7 +60,7 @@
if (!photoFiles) { if (!photoFiles) {
photoFiles = []; photoFiles = [];
} }
photoFiles = [...photoFiles, file] photoFiles = [...photoFiles, file];
} }
} }
@@ -117,7 +118,9 @@
on:delete={() => handlePhotoDelete(i)} on:delete={() => handlePhotoDelete(i)}
isThumbnail={thumbnail === i} isThumbnail={thumbnail === i}
on:thumbnail={() => makePhotoThumbnail(i)} on:thumbnail={() => makePhotoThumbnail(i)}
on:exif
{showThumbnailControls} {showThumbnailControls}
{showExifControls}
></PhotoCard> ></PhotoCard>
</div> </div>
{/each} {/each}

View File

@@ -35,9 +35,21 @@
onMount(() => { onMount(() => {
const storedDisplayOption = localStorage.getItem("displayOption"); const storedDisplayOption = localStorage.getItem("displayOption");
const storedSort= localStorage.getItem("sort");
const storedSortOrder= localStorage.getItem("sort_order");
if (storedDisplayOption) { if (storedDisplayOption) {
selectedDisplayOption = storedDisplayOption; selectedDisplayOption = storedDisplayOption;
} }
if(storedSort) {
filter.sort = storedSort as typeof filter.sort
}
if(storedSortOrder) {
filter.sortOrder = storedSortOrder as typeof filter.sortOrder
}
if(storedSort || storedSortOrder) {
dispatch("update", filter);
}
}); });
function setDisplayOption() { function setDisplayOption() {
@@ -45,6 +57,7 @@
} }
function setSort() { function setSort() {
localStorage.setItem("sort", filter.sort)
dispatch("update", filter); dispatch("update", filter);
} }
@@ -54,6 +67,7 @@
} else { } else {
filter.sortOrder = "+"; filter.sortOrder = "+";
} }
localStorage.setItem("sort_order", filter.sortOrder)
dispatch("update", filter); dispatch("update", filter);
} }
</script> </script>

View File

@@ -4,6 +4,7 @@
import { waypoint } from "$lib/stores/waypoint_store"; import { waypoint } from "$lib/stores/waypoint_store";
import { icons } from "$lib/util/icon_util"; import { icons } from "$lib/util/icon_util";
import EXIF from "$lib/vendor/exif-js/exif";
import { createForm } from "$lib/vendor/svelte-form-lib/index"; import { createForm } from "$lib/vendor/svelte-form-lib/index";
import { util } from "$lib/vendor/svelte-form-lib/util"; import { util } from "$lib/vendor/svelte-form-lib/util";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
@@ -12,6 +13,9 @@
import TextField from "../base/text_field.svelte"; import TextField from "../base/text_field.svelte";
import Textarea from "../base/textarea.svelte"; import Textarea from "../base/textarea.svelte";
import PhotoPicker from "../trail/photo_picker.svelte"; import PhotoPicker from "../trail/photo_picker.svelte";
import { convertDMSToDD } from "$lib/util/leaflet_util";
import { show_toast } from "$lib/stores/toast_store";
export let openModal: (() => void) | undefined = undefined; export let openModal: (() => void) | undefined = undefined;
export let closeModal: (() => void) | undefined = undefined; export let closeModal: (() => void) | undefined = undefined;
@@ -35,6 +39,26 @@
.filter((i) => i.includes($form.icon ?? "")) .filter((i) => i.includes($form.icon ?? ""))
.map((i) => ({ text: i, value: i, icon: i })) .map((i) => ({ text: i, value: i, icon: i }))
: []; : [];
function getCoordinatesFromPhoto(src: string) {
EXIF.getData({ src: src }, function (p) {
const lat = EXIF.getTag(p, "GPSLatitude");
const latDir = EXIF.getTag(p, "GPSLatitudeRef");
const lon = EXIF.getTag(p, "GPSLongitude");
const lonDir = EXIF.getTag(p, "GPSLongitudeRef");
if (lat && lon) {
$form.lat = convertDMSToDD(lat, latDir);
$form.lon = convertDMSToDD(lon, lonDir);
} else {
show_toast({
text: "No GPS data in image",
icon: "close",
type: "error",
});
}
});
}
</script> </script>
<Modal <Modal
@@ -102,9 +126,11 @@
<PhotoPicker <PhotoPicker
id="waypoint" id="waypoint"
parent={$form} parent={$form}
on:exif={(e) => getCoordinatesFromPhoto(e.detail)}
bind:photos={$form.photos} bind:photos={$form.photos}
bind:photoFiles={$form._photos} bind:photoFiles={$form._photos}
showThumbnailControls={false} showThumbnailControls={false}
showExifControls={true}
></PhotoPicker> ></PhotoPicker>
</div> </div>
</form> </form>

View File

@@ -74,6 +74,7 @@
"file-format": "Dateiformat", "file-format": "Dateiformat",
"french": "Französisch", "french": "Französisch",
"german": "Deutsch", "german": "Deutsch",
"get-position-from-exif": "Koordinaten aus EXIF Daten",
"grid": "Gitter", "grid": "Gitter",
"hero_section_0_text": "Entdecke spannende Routen, speichere deine Favoriten und erlebe die Schönheit der Natur. Finde dein nächstes Abenteuer!", "hero_section_0_text": "Entdecke spannende Routen, speichere deine Favoriten und erlebe die Schönheit der Natur. Finde dein nächstes Abenteuer!",
"hero_section_1_heading": "Hier gibt es noch keine Routen", "hero_section_1_heading": "Hier gibt es noch keine Routen",
@@ -117,6 +118,7 @@
"no-results": "Keine Ergebnisse gefunden", "no-results": "Keine Ergebnisse gefunden",
"not-a-valid-email-address": "Keine gültige Email-Adresse", "not-a-valid-email-address": "Keine gültige Email-Adresse",
"not-completed": "Nicht abgeschlossen", "not-completed": "Nicht abgeschlossen",
"off": "Aus",
"or": "oder", "or": "oder",
"orientation": "Ausrichtung", "orientation": "Ausrichtung",
"paper-size": "Papierformat", "paper-size": "Papierformat",
@@ -147,7 +149,9 @@
"show-in-overview": "In der Übersicht anzeigen", "show-in-overview": "In der Übersicht anzeigen",
"show-on-map": "Auf der Karte anzeigen", "show-on-map": "Auf der Karte anzeigen",
"slogan": "Speichere deine Abenteuer!", "slogan": "Speichere deine Abenteuer!",
"slope": "Steigung",
"sort": "Sortieren", "sort": "Sortieren",
"speed": "Geschwindigkeit",
"stop-drawing": "Zeichnen beenden", "stop-drawing": "Zeichnen beenden",
"summit-book": "Gipfelbuch", "summit-book": "Gipfelbuch",
"text": "Text", "text": "Text",

View File

@@ -74,6 +74,7 @@
"file-format": "File format", "file-format": "File format",
"french": "French", "french": "French",
"german": "German", "german": "German",
"get-position-from-exif": "Get coordinates from EXIF data",
"grid": "Grid", "grid": "Grid",
"hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!", "hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!",
"hero_section_1_heading": "It seems there are no trails here yet.", "hero_section_1_heading": "It seems there are no trails here yet.",
@@ -117,6 +118,7 @@
"no-results": "No results found", "no-results": "No results found",
"not-a-valid-email-address": "Not a valid email address", "not-a-valid-email-address": "Not a valid email address",
"not-completed": "Not completed", "not-completed": "Not completed",
"off": "Off",
"or": "or", "or": "or",
"orientation": "Orientation", "orientation": "Orientation",
"paper-size": "Paper size", "paper-size": "Paper size",
@@ -147,7 +149,9 @@
"show-in-overview": "Show in overview", "show-in-overview": "Show in overview",
"show-on-map": "Show on map", "show-on-map": "Show on map",
"slogan": "Save your adventures!", "slogan": "Save your adventures!",
"slope": "Slope",
"sort": "Sort", "sort": "Sort",
"speed": "Speed",
"stop-drawing": "Stop drawing", "stop-drawing": "Stop drawing",
"summit-book": "Summit Book", "summit-book": "Summit Book",
"text": "Text", "text": "Text",

View File

@@ -74,6 +74,7 @@
"file-format": "", "file-format": "",
"french": "Français", "french": "Français",
"german": "Allemand", "german": "Allemand",
"get-position-from-exif": "",
"grid": "Grille", "grid": "Grille",
"hero_section_0_text": "Explorez des itinéraires passionnants, sauvegardez vos favoris et découvrez la beauté de la nature. Trouvez votre prochaine aventure !", "hero_section_0_text": "Explorez des itinéraires passionnants, sauvegardez vos favoris et découvrez la beauté de la nature. Trouvez votre prochaine aventure !",
"hero_section_1_heading": "Il semble qu'il n'y ait pas encore d'itinéraire ici.", "hero_section_1_heading": "Il semble qu'il n'y ait pas encore d'itinéraire ici.",
@@ -117,6 +118,7 @@
"no-results": "Pas de résultat", "no-results": "Pas de résultat",
"not-a-valid-email-address": "Adresse email invalide", "not-a-valid-email-address": "Adresse email invalide",
"not-completed": "Pas terminé", "not-completed": "Pas terminé",
"off": "",
"or": "ou", "or": "ou",
"orientation": "Orientation", "orientation": "Orientation",
"paper-size": "Taille de papier", "paper-size": "Taille de papier",
@@ -147,7 +149,9 @@
"show-in-overview": "Voir dans l'aperçu", "show-in-overview": "Voir dans l'aperçu",
"show-on-map": "Voir sur la carte", "show-on-map": "Voir sur la carte",
"slogan": "Sauvegarder vos aventures !", "slogan": "Sauvegarder vos aventures !",
"slope": "",
"sort": "Trier", "sort": "Trier",
"speed": "",
"stop-drawing": "", "stop-drawing": "",
"summit-book": "Livre du sommet", "summit-book": "Livre du sommet",
"text": "Texte", "text": "Texte",

View File

@@ -74,6 +74,7 @@
"file-format": "", "file-format": "",
"french": "Francia", "french": "Francia",
"german": "Német", "german": "Német",
"get-position-from-exif": "",
"grid": "", "grid": "",
"hero_section_0_text": "Fedezze fel az izgalmas útvonalakat, mentse el kedvenceit, és tapasztalja meg a természet szépségét. Találja meg a következő kalandját!", "hero_section_0_text": "Fedezze fel az izgalmas útvonalakat, mentse el kedvenceit, és tapasztalja meg a természet szépségét. Találja meg a következő kalandját!",
"hero_section_1_heading": "Úgy tűnik, itt még nincsenek ösvények.", "hero_section_1_heading": "Úgy tűnik, itt még nincsenek ösvények.",
@@ -117,6 +118,7 @@
"no-results": "Nincs eredmény", "no-results": "Nincs eredmény",
"not-a-valid-email-address": "Érvénytelen e-mail cím", "not-a-valid-email-address": "Érvénytelen e-mail cím",
"not-completed": "Nem teljesített", "not-completed": "Nem teljesített",
"off": "",
"or": "vagy", "or": "vagy",
"orientation": "", "orientation": "",
"paper-size": "", "paper-size": "",
@@ -147,7 +149,9 @@
"show-in-overview": "Áttekintés", "show-in-overview": "Áttekintés",
"show-on-map": "Mutatás térképen", "show-on-map": "Mutatás térképen",
"slogan": "Mentse el a kalandjait!", "slogan": "Mentse el a kalandjait!",
"slope": "",
"sort": "Rendezés", "sort": "Rendezés",
"speed": "",
"stop-drawing": "", "stop-drawing": "",
"summit-book": "Csúcspont könyv", "summit-book": "Csúcspont könyv",
"text": "Szöveg", "text": "Szöveg",

View File

@@ -74,6 +74,7 @@
"file-format": "", "file-format": "",
"french": "Frans", "french": "Frans",
"german": "Duits", "german": "Duits",
"get-position-from-exif": "",
"grid": "Rooster", "grid": "Rooster",
"hero_section_0_text": "Verken uitdagende wandelroutes, bewaar je favoriete en ervaar de pracht van de natuur. Op naar het volgende avontuur!", "hero_section_0_text": "Verken uitdagende wandelroutes, bewaar je favoriete en ervaar de pracht van de natuur. Op naar het volgende avontuur!",
"hero_section_1_heading": "Je hebt nog geen wandelroutes.", "hero_section_1_heading": "Je hebt nog geen wandelroutes.",
@@ -117,6 +118,7 @@
"no-results": "Er zijn geen zoekresultaten", "no-results": "Er zijn geen zoekresultaten",
"not-a-valid-email-address": "Het e-mailadres is ongeldig", "not-a-valid-email-address": "Het e-mailadres is ongeldig",
"not-completed": "Niet voltooid", "not-completed": "Niet voltooid",
"off": "",
"or": "of", "or": "of",
"orientation": "Oriëntatie", "orientation": "Oriëntatie",
"paper-size": "Papierformaat", "paper-size": "Papierformaat",
@@ -147,7 +149,9 @@
"show-in-overview": "Tonen op overzicht", "show-in-overview": "Tonen op overzicht",
"show-on-map": "Tonen op kaart", "show-on-map": "Tonen op kaart",
"slogan": "Bewaar je avonturen!", "slogan": "Bewaar je avonturen!",
"slope": "",
"sort": "Sorteren", "sort": "Sorteren",
"speed": "",
"stop-drawing": "", "stop-drawing": "",
"summit-book": "Bergtopboek", "summit-book": "Bergtopboek",
"text": "Tekst", "text": "Tekst",

View File

@@ -74,6 +74,7 @@
"file-format": "", "file-format": "",
"french": "Francuski", "french": "Francuski",
"german": "Niemiecki", "german": "Niemiecki",
"get-position-from-exif": "",
"grid": "", "grid": "",
"hero_section_0_text": "Eksploruj ekscytujące szlaki, zapisz swoje ulubione, doświadcz piękna naturalnego świata. Znajdź swoją następną przygodę!", "hero_section_0_text": "Eksploruj ekscytujące szlaki, zapisz swoje ulubione, doświadcz piękna naturalnego świata. Znajdź swoją następną przygodę!",
"hero_section_1_heading": "Wygląda na to że nie ma tutaj ścieżek.", "hero_section_1_heading": "Wygląda na to że nie ma tutaj ścieżek.",
@@ -117,6 +118,7 @@
"no-results": "Brak wyników", "no-results": "Brak wyników",
"not-a-valid-email-address": "Nieprawidłowy adres email", "not-a-valid-email-address": "Nieprawidłowy adres email",
"not-completed": "Nie dokończono", "not-completed": "Nie dokończono",
"off": "",
"or": "lub", "or": "lub",
"orientation": "", "orientation": "",
"paper-size": "", "paper-size": "",
@@ -147,7 +149,9 @@
"show-in-overview": "Pokaż w przeglądzie", "show-in-overview": "Pokaż w przeglądzie",
"show-on-map": "Pokaż na mapie", "show-on-map": "Pokaż na mapie",
"slogan": "Zapisz swoją wyprawę!", "slogan": "Zapisz swoją wyprawę!",
"slope": "",
"sort": "Sortowanie", "sort": "Sortowanie",
"speed": "",
"stop-drawing": "", "stop-drawing": "",
"summit-book": "Logbook", "summit-book": "Logbook",
"text": "Tekst", "text": "Tekst",

View File

@@ -74,6 +74,7 @@
"file-format": "", "file-format": "",
"french": "Francês", "french": "Francês",
"german": "Alemão", "german": "Alemão",
"get-position-from-exif": "",
"grid": "", "grid": "",
"hero_section_0_text": "Explore trilhas emocionantes, salve seus favoritos e experimente a beleza da natureza. Encontre sua próxima aventura!", "hero_section_0_text": "Explore trilhas emocionantes, salve seus favoritos e experimente a beleza da natureza. Encontre sua próxima aventura!",
"hero_section_1_heading": "Parece que ainda não há trilhas aqui.", "hero_section_1_heading": "Parece que ainda não há trilhas aqui.",
@@ -117,6 +118,7 @@
"no-results": "Nenhum resultado encontrado", "no-results": "Nenhum resultado encontrado",
"not-a-valid-email-address": "Não um endereço de e-mail válido", "not-a-valid-email-address": "Não um endereço de e-mail válido",
"not-completed": "Não preenchido", "not-completed": "Não preenchido",
"off": "",
"or": "ou", "or": "ou",
"orientation": "", "orientation": "",
"paper-size": "", "paper-size": "",
@@ -147,7 +149,9 @@
"show-in-overview": "Mostrar na vista geral", "show-in-overview": "Mostrar na vista geral",
"show-on-map": "Mostrar no mapa", "show-on-map": "Mostrar no mapa",
"slogan": "Guarde as suas aventuras!", "slogan": "Guarde as suas aventuras!",
"slope": "",
"sort": "Ordenar", "sort": "Ordenar",
"speed": "",
"stop-drawing": "", "stop-drawing": "",
"summit-book": "Livro da cimeira", "summit-book": "Livro da cimeira",
"text": "Texto", "text": "Texto",

View File

@@ -74,6 +74,7 @@
"file-format": "", "file-format": "",
"french": "法语", "french": "法语",
"german": "德语", "german": "德语",
"get-position-from-exif": "",
"grid": "", "grid": "",
"hero_section_0_text": "探索激动人心的探险,收藏最爱行程,,体验美丽地大自然。找到你的下一次冒险!", "hero_section_0_text": "探索激动人心的探险,收藏最爱行程,,体验美丽地大自然。找到你的下一次冒险!",
"hero_section_1_heading": "这儿似乎还没有行程。", "hero_section_1_heading": "这儿似乎还没有行程。",
@@ -117,6 +118,7 @@
"no-results": "没有找到结果", "no-results": "没有找到结果",
"not-a-valid-email-address": "无效电子邮箱地址", "not-a-valid-email-address": "无效电子邮箱地址",
"not-completed": "未完成", "not-completed": "未完成",
"off": "",
"or": "或", "or": "或",
"orientation": "", "orientation": "",
"paper-size": "", "paper-size": "",
@@ -147,7 +149,9 @@
"show-in-overview": "详情中展示", "show-in-overview": "详情中展示",
"show-on-map": "地图中展示", "show-on-map": "地图中展示",
"slogan": "保存你的冒险!", "slogan": "保存你的冒险!",
"slope": "",
"sort": "排序", "sort": "排序",
"speed": "",
"stop-drawing": "", "stop-drawing": "",
"summit-book": "详细日程", "summit-book": "详细日程",
"text": "文本", "text": "文本",

View File

@@ -95,3 +95,12 @@ export function calculateScaleFactor(map: Map) {
return scaleFactor return scaleFactor
} }
export function convertDMSToDD(dms: Number[], direction: "N" | "O" | "S" | "W") {
var dd = dms[0].valueOf() + dms[1].valueOf() / 60 + dms[2].valueOf() / (60 * 60);
if (direction == "S" || direction == "W") {
dd = dd * -1;
}
return dd;
}

10
web/src/lib/vendor/exif-js/exif.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
interface EXIFStatic {
getData(url: { src: string } | HTMLImageElement | File, callback: (photo: any) => void): any;
getTag(img: { src: string } | HTMLImageElement | File, tag: any): any;
getAllTags(img: string | File): any;
pretty(img: any): string;
readFromBinaryFile(file: any): any;
}
declare var EXIF: EXIFStatic;
export = EXIF;

1047
web/src/lib/vendor/exif-js/exif.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -452,8 +452,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
.then(() => { .then(() => {
layer.eachLayer((trkseg) => { layer.eachLayer((trkseg) => {
if (trkseg.feature.geometry.type != "Point") { if (trkseg.feature.geometry.type != "Point") {
let geo = L.geoJson(trkseg.toGeoJSON(), { coordsToLatLng: (coords) => L.latLng(coords[0], coords[1], (coords[2] * (this.options.altitudeFactor || 1)) || 0) }); let line = L.hotline(this._data.map(m => [m.x, m.y, m[prop] || 0]), {
let line = L.hotline(geo.toGeoJSON().features[0].geometry.coordinates, {
renderer: L.Hotline.renderer(), renderer: L.Hotline.renderer(),
min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0, min: isFinite(this.track_info[prop + '_min']) ? this.track_info[prop + '_min'] : 0,
max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1, max: isFinite(this.track_info[prop + '_max']) ? this.track_info[prop + '_max'] : 1,
@@ -1037,7 +1036,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
// check and fix missing data on last added point // check and fix missing data on last added point
if (i > 0 && isNaN(prev[attr])) { if (i > 0 && isNaN(prev[attr])) {
if (!isNaN(lastValid[attr]) && !isNaN(curr[attr])) { if (!isNaN(lastValid[attr]) && !isNaN(curr[attr])) {
prev[attr] = (lastValid[attr] + curr[attr]) / 2; prev[attr] = _.round((lastValid[attr] + curr[attr]) / 2, props.decimals);
} else if (!isNaN(lastValid[attr])) { } else if (!isNaN(lastValid[attr])) {
prev[attr] = lastValid[attr]; prev[attr] = lastValid[attr];
} else if (!isNaN(curr[attr])) { } else if (!isNaN(curr[attr])) {

View File

@@ -1,7 +1,12 @@
// +layout.ts // +layout.ts
import '$lib/i18n'; import '$lib/i18n';
import type { LayoutServerLoad } from './$types'; import type { LayoutServerLoad } from './$types';
import { env } from "$env/dynamic/private";
export const load: LayoutServerLoad = async ({ locals }) => { export const load: LayoutServerLoad = async ({ locals, url }) => {
return { settings: locals.settings } const warnings = []
if (env.ORIGIN != url.origin) {
warnings.push(`You are accessing wanderer from <span class="font-mono bg-gray-100">${url.origin}</span> but your ORIGIN environment variable is set to <span class="font-mono bg-gray-100">${env.ORIGIN}</span>. This may cause errors.`)
}
return { settings: locals.settings, warnings: warnings }
} }

View File

@@ -7,10 +7,13 @@
import { currentUser } from "$lib/stores/user_store"; import { currentUser } from "$lib/stores/user_store";
import { isRouteProtected } from "$lib/util/authorization_util"; import { isRouteProtected } from "$lib/util/authorization_util";
import "@fortawesome/fontawesome-free/css/all.min.css"; import "@fortawesome/fontawesome-free/css/all.min.css";
import { fade, slide } from "svelte/transition"; import { slide } from "svelte/transition";
import "../css/app.css"; import "../css/app.css";
import "../css/components.css"; import "../css/components.css";
import "../css/theme.css"; import "../css/theme.css";
import { page } from "$app/stores";
import { onMount } from "svelte";
beforeNavigate((n) => { beforeNavigate((n) => {
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) { if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
@@ -20,6 +23,7 @@
}); });
let hideDemoHint = false; let hideDemoHint = false;
let hideWarning = false;
</script> </script>
{#if env.PUBLIC_IS_DEMO === "true" && !hideDemoHint} {#if env.PUBLIC_IS_DEMO === "true" && !hideDemoHint}
@@ -38,6 +42,19 @@
</div> </div>
{/if} {/if}
{#if $page.data.warnings?.length && !hideWarning}
<div
class="flex items-center justify-between bg-red-200 text-center p-4 text-sm"
out:slide
>
<div></div>
<span>{@html $page.data.warnings[0]} </span>
<button class="btn-icon self-end" on:click={() => (hideWarning = true)}
><i class="fa fa-close"></i></button
>
</div>
{/if}
<NavBar></NavBar> <NavBar></NavBar>
<Toast></Toast> <Toast></Toast>
<slot /> <slot />

View File

@@ -64,9 +64,39 @@
map = L.map("map").setView([0, 0], 4); map = L.map("map").setView([0, 0], 4);
map.attributionControl.setPrefix(false); map.attributionControl.setPrefix(false);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { const baseLayer = L.tileLayer(
attribution: "© OpenStreetMap contributors", "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
}).addTo(map); {
attribution: "© OpenStreetMap contributors",
},
);
const topoLayer = L.tileLayer(
"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png ",
{
attribution: "© OpenStreetMap contributors",
},
);
const baseMaps = {
OpenStreetMaps: baseLayer,
OpenTopoMaps: topoLayer,
};
L.control.layers(baseMaps).addTo(map);
switch (localStorage.getItem("layer")) {
case "OpenTopoMaps":
topoLayer.addTo(map);
break;
default:
baseLayer.addTo(map);
break;
}
map.on("baselayerchange", function (e) {
localStorage.setItem("layer", e.name);
});
map.on("moveend", async (e) => { map.on("moveend", async (e) => {
const bounds = map.getBounds(); const bounds = map.getBounds();

View File

@@ -195,7 +195,7 @@ https://github.com/iosphere/Leaflet.hotline/
* @returns {Array.<number>} The RGB values as an array [r, g, b] * @returns {Array.<number>} The RGB values as an array [r, g, b]
*/ */
getRGBForValue: function (value) { getRGBForValue: function (value) {
var valueRelative = Math.min(Math.max((value - this._min) / (this._max - this._min), 0), 0.999); var valueRelative = Math.min(Math.max((value - this._min) / (this._max - this._min), 0), 0.999) || 0;
var paletteIndex = Math.floor(valueRelative * 256) * 4; var paletteIndex = Math.floor(valueRelative * 256) * 4;
return [ return [

View File

@@ -16,7 +16,7 @@ export function Altitude() {
required: this.options.slope, required: this.options.slope,
meta: 'z', meta: 'z',
unit: altitude.label, unit: altitude.label,
statsName: 'elevation', statsName: 'altitude',
deltaMax: this.options.altitudeDeltaMax, deltaMax: this.options.altitudeDeltaMax,
clampRange: this.options.altitudeRange, clampRange: this.options.altitudeRange,
// init: ({point}) => { // init: ({point}) => {