@@ -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
|
||||
response.headers.set(
|
||||
'set-cookie',
|
||||
pb.authStore.exportToCookie({ httpOnly: false, secure: false })
|
||||
pb.authStore.exportToCookie({ httpOnly: false, secure: true })
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<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"
|
||||
role="presentation"
|
||||
on:mouseup|stopPropagation={() => handleItemClick(item)}
|
||||
on:mousedown|stopPropagation={() => handleItemClick(item)}
|
||||
>
|
||||
{#if item.icon}
|
||||
<i class="fa fa-{item.icon} mr-3"></i>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
export let src: string;
|
||||
export let isThumbnail: boolean = false;
|
||||
export let showThumbnailControls: boolean = true;
|
||||
export let showExifControls: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -12,6 +13,10 @@
|
||||
dispatch("thumbnail", src);
|
||||
}
|
||||
|
||||
function handleExifClick() {
|
||||
dispatch("exif", src);
|
||||
}
|
||||
|
||||
function handleDeleteClick() {
|
||||
dispatch("delete", src);
|
||||
}
|
||||
@@ -38,7 +43,15 @@
|
||||
><i class="fa fa-file-image text-primary"></i></button
|
||||
>
|
||||
{/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
|
||||
class="tooltip"
|
||||
data-title={$_("delete")}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||
|
||||
export let trail: Trail | null;
|
||||
export let markers: Marker[] = [];
|
||||
@@ -22,15 +24,22 @@
|
||||
let controlElevation: any;
|
||||
|
||||
$: gpxData = trail?.expand.gpx_data;
|
||||
$: if (gpxData && controlElevation) {
|
||||
$: if (gpxData && controlElevation) {
|
||||
controlElevation.clear();
|
||||
controlElevation.load(gpxData);
|
||||
}
|
||||
|
||||
$: if(options) {
|
||||
controlElevation?.updateOptions(options)
|
||||
$: if (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 () => {
|
||||
L = (await import("leaflet")).default;
|
||||
await import("leaflet-gpx");
|
||||
@@ -55,9 +64,39 @@
|
||||
dispatch("click", e);
|
||||
});
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
}).addTo(map);
|
||||
const baseLayer = L.tileLayer(
|
||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
{
|
||||
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 = {
|
||||
height: 200,
|
||||
@@ -84,11 +123,16 @@
|
||||
// Toggle "leaflet-almostover" integration
|
||||
almostOver: false,
|
||||
// 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
|
||||
edgeScale: false,
|
||||
// Toggle "leaflet-hotline" integration
|
||||
hotline: true,
|
||||
hotline: "altitude",
|
||||
// Display track datetimes: true || false
|
||||
timestamps: false,
|
||||
waypoints: false,
|
||||
@@ -103,7 +147,7 @@
|
||||
prefix: "fa",
|
||||
markerColor: "cadetblue",
|
||||
iconColor: "white",
|
||||
className: "awesome-marker pointer-events-none"
|
||||
className: "awesome-marker pointer-events-none",
|
||||
}),
|
||||
},
|
||||
trkEnd: {
|
||||
@@ -112,11 +156,11 @@
|
||||
prefix: "fa",
|
||||
markerColor: "cadetblue",
|
||||
iconColor: "white",
|
||||
className: "awesome-marker pointer-events-none"
|
||||
className: "awesome-marker pointer-events-none",
|
||||
}),
|
||||
},
|
||||
graticule: false,
|
||||
drawing: false
|
||||
drawing: false,
|
||||
};
|
||||
|
||||
const elevation_options = Object.assign(
|
||||
@@ -137,16 +181,37 @@
|
||||
graticule.addTo(map!);
|
||||
}
|
||||
});
|
||||
|
||||
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
|
||||
controlElevation.updateOptions({ hotline: metric, autofitBounds: false });
|
||||
controlElevation.clear();
|
||||
controlElevation.load(gpxData);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="map-container" class="flex flex-col h-full">
|
||||
<div
|
||||
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
|
||||
? "position: relative; outline-style: none;cursor: crosshair !important"
|
||||
: "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">
|
||||
<slot />
|
||||
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
export let parent: { [key: string]: any };
|
||||
export let thumbnail: number = 0;
|
||||
export let showThumbnailControls: boolean = true;
|
||||
export let showExifControls: boolean = false;
|
||||
|
||||
let photoPreviews: string[] = [];
|
||||
|
||||
@@ -15,7 +16,7 @@
|
||||
(photoFiles ?? []).map(async (f) => {
|
||||
return await readAsDataURLAsync(f);
|
||||
}),
|
||||
).then((v) => {
|
||||
).then((v) => {
|
||||
photoPreviews = v;
|
||||
});
|
||||
|
||||
@@ -59,7 +60,7 @@
|
||||
if (!photoFiles) {
|
||||
photoFiles = [];
|
||||
}
|
||||
photoFiles = [...photoFiles, file]
|
||||
photoFiles = [...photoFiles, file];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +118,9 @@
|
||||
on:delete={() => handlePhotoDelete(i)}
|
||||
isThumbnail={thumbnail === i}
|
||||
on:thumbnail={() => makePhotoThumbnail(i)}
|
||||
on:exif
|
||||
{showThumbnailControls}
|
||||
{showExifControls}
|
||||
></PhotoCard>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -35,9 +35,21 @@
|
||||
|
||||
onMount(() => {
|
||||
const storedDisplayOption = localStorage.getItem("displayOption");
|
||||
const storedSort= localStorage.getItem("sort");
|
||||
const storedSortOrder= localStorage.getItem("sort_order");
|
||||
|
||||
if (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() {
|
||||
@@ -45,6 +57,7 @@
|
||||
}
|
||||
|
||||
function setSort() {
|
||||
localStorage.setItem("sort", filter.sort)
|
||||
dispatch("update", filter);
|
||||
}
|
||||
|
||||
@@ -54,6 +67,7 @@
|
||||
} else {
|
||||
filter.sortOrder = "+";
|
||||
}
|
||||
localStorage.setItem("sort_order", filter.sortOrder)
|
||||
dispatch("update", filter);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { waypoint } from "$lib/stores/waypoint_store";
|
||||
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 { util } from "$lib/vendor/svelte-form-lib/util";
|
||||
import { _ } from "svelte-i18n";
|
||||
@@ -12,6 +13,9 @@
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import Textarea from "../base/textarea.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 closeModal: (() => void) | undefined = undefined;
|
||||
|
||||
@@ -20,9 +24,9 @@
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<Waypoint>({
|
||||
initialValues: $waypoint,
|
||||
validationSchema: waypointSchema,
|
||||
onSubmit: async (submittedWaypoint) => {
|
||||
dispatch("save", submittedWaypoint);
|
||||
|
||||
onSubmit: async (submittedWaypoint) => {
|
||||
dispatch("save", submittedWaypoint);
|
||||
|
||||
closeModal!();
|
||||
},
|
||||
});
|
||||
@@ -35,6 +39,26 @@
|
||||
.filter((i) => i.includes($form.icon ?? ""))
|
||||
.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>
|
||||
|
||||
<Modal
|
||||
@@ -102,9 +126,11 @@
|
||||
<PhotoPicker
|
||||
id="waypoint"
|
||||
parent={$form}
|
||||
on:exif={(e) => getCoordinatesFromPhoto(e.detail)}
|
||||
bind:photos={$form.photos}
|
||||
bind:photoFiles={$form._photos}
|
||||
showThumbnailControls={false}
|
||||
showExifControls={true}
|
||||
></PhotoPicker>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "Dateiformat",
|
||||
"french": "Französisch",
|
||||
"german": "Deutsch",
|
||||
"get-position-from-exif": "Koordinaten aus EXIF Daten",
|
||||
"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_1_heading": "Hier gibt es noch keine Routen",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "Keine Ergebnisse gefunden",
|
||||
"not-a-valid-email-address": "Keine gültige Email-Adresse",
|
||||
"not-completed": "Nicht abgeschlossen",
|
||||
"off": "Aus",
|
||||
"or": "oder",
|
||||
"orientation": "Ausrichtung",
|
||||
"paper-size": "Papierformat",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "In der Übersicht anzeigen",
|
||||
"show-on-map": "Auf der Karte anzeigen",
|
||||
"slogan": "Speichere deine Abenteuer!",
|
||||
"slope": "Steigung",
|
||||
"sort": "Sortieren",
|
||||
"speed": "Geschwindigkeit",
|
||||
"stop-drawing": "Zeichnen beenden",
|
||||
"summit-book": "Gipfelbuch",
|
||||
"text": "Text",
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "File format",
|
||||
"french": "French",
|
||||
"german": "German",
|
||||
"get-position-from-exif": "Get coordinates from EXIF data",
|
||||
"grid": "Grid",
|
||||
"hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!",
|
||||
"hero_section_1_heading": "It seems there are no trails here yet.",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "No results found",
|
||||
"not-a-valid-email-address": "Not a valid email address",
|
||||
"not-completed": "Not completed",
|
||||
"off": "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": "Save your adventures!",
|
||||
"slope": "Slope",
|
||||
"sort": "Sort",
|
||||
"speed": "Speed",
|
||||
"stop-drawing": "Stop drawing",
|
||||
"summit-book": "Summit Book",
|
||||
"text": "Text",
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "",
|
||||
"french": "Français",
|
||||
"german": "Allemand",
|
||||
"get-position-from-exif": "",
|
||||
"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_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",
|
||||
"not-a-valid-email-address": "Adresse email invalide",
|
||||
"not-completed": "Pas terminé",
|
||||
"off": "",
|
||||
"or": "ou",
|
||||
"orientation": "Orientation",
|
||||
"paper-size": "Taille de papier",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "Voir dans l'aperçu",
|
||||
"show-on-map": "Voir sur la carte",
|
||||
"slogan": "Sauvegarder vos aventures !",
|
||||
"slope": "",
|
||||
"sort": "Trier",
|
||||
"speed": "",
|
||||
"stop-drawing": "",
|
||||
"summit-book": "Livre du sommet",
|
||||
"text": "Texte",
|
||||
@@ -163,4 +167,4 @@
|
||||
"welcome_to": "Bienvenue à",
|
||||
"wrong-username-or-password": "Nom d'utilisateur ou mot de passe incorrect",
|
||||
"you-can": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "",
|
||||
"french": "Francia",
|
||||
"german": "Német",
|
||||
"get-position-from-exif": "",
|
||||
"grid": "",
|
||||
"hero_section_0_text": "Fedezze fel az izgalmas útvonalakat, mentse el kedvenceit, és tapasztalja meg a természet szépségét. Találja meg a következő kalandját!",
|
||||
"hero_section_1_heading": "Úgy tűnik, itt még nincsenek ösvények.",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "Nincs eredmény",
|
||||
"not-a-valid-email-address": "Érvénytelen e-mail cím",
|
||||
"not-completed": "Nem teljesített",
|
||||
"off": "",
|
||||
"or": "vagy",
|
||||
"orientation": "",
|
||||
"paper-size": "",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "Áttekintés",
|
||||
"show-on-map": "Mutatás térképen",
|
||||
"slogan": "Mentse el a kalandjait!",
|
||||
"slope": "",
|
||||
"sort": "Rendezés",
|
||||
"speed": "",
|
||||
"stop-drawing": "",
|
||||
"summit-book": "Csúcspont könyv",
|
||||
"text": "Szöveg",
|
||||
@@ -163,4 +167,4 @@
|
||||
"welcome_to": "Üdvözöljük a",
|
||||
"wrong-username-or-password": "Helytelen felhasználónév vagy jelszó",
|
||||
"you-can": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "",
|
||||
"french": "Frans",
|
||||
"german": "Duits",
|
||||
"get-position-from-exif": "",
|
||||
"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_1_heading": "Je hebt nog geen wandelroutes.",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "Er zijn geen zoekresultaten",
|
||||
"not-a-valid-email-address": "Het e-mailadres is ongeldig",
|
||||
"not-completed": "Niet voltooid",
|
||||
"off": "",
|
||||
"or": "of",
|
||||
"orientation": "Oriëntatie",
|
||||
"paper-size": "Papierformaat",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "Tonen op overzicht",
|
||||
"show-on-map": "Tonen op kaart",
|
||||
"slogan": "Bewaar je avonturen!",
|
||||
"slope": "",
|
||||
"sort": "Sorteren",
|
||||
"speed": "",
|
||||
"stop-drawing": "",
|
||||
"summit-book": "Bergtopboek",
|
||||
"text": "Tekst",
|
||||
@@ -163,4 +167,4 @@
|
||||
"welcome_to": "Welkom bij",
|
||||
"wrong-username-or-password": "Onjuiste gebruikersnaam of wachtwoord",
|
||||
"you-can": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "",
|
||||
"french": "Francuski",
|
||||
"german": "Niemiecki",
|
||||
"get-position-from-exif": "",
|
||||
"grid": "",
|
||||
"hero_section_0_text": "Eksploruj ekscytujące szlaki, zapisz swoje ulubione, doświadcz piękna naturalnego świata. Znajdź swoją następną przygodę!",
|
||||
"hero_section_1_heading": "Wygląda na to że nie ma tutaj ścieżek.",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "Brak wyników",
|
||||
"not-a-valid-email-address": "Nieprawidłowy adres email",
|
||||
"not-completed": "Nie dokończono",
|
||||
"off": "",
|
||||
"or": "lub",
|
||||
"orientation": "",
|
||||
"paper-size": "",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "Pokaż w przeglądzie",
|
||||
"show-on-map": "Pokaż na mapie",
|
||||
"slogan": "Zapisz swoją wyprawę!",
|
||||
"slope": "",
|
||||
"sort": "Sortowanie",
|
||||
"speed": "",
|
||||
"stop-drawing": "",
|
||||
"summit-book": "Logbook",
|
||||
"text": "Tekst",
|
||||
@@ -163,4 +167,4 @@
|
||||
"welcome_to": "Witaj w",
|
||||
"wrong-username-or-password": "Zła nazwa użytkownika lub hasło",
|
||||
"you-can": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "",
|
||||
"french": "Francês",
|
||||
"german": "Alemão",
|
||||
"get-position-from-exif": "",
|
||||
"grid": "",
|
||||
"hero_section_0_text": "Explore trilhas emocionantes, salve seus favoritos e experimente a beleza da natureza. Encontre sua próxima aventura!",
|
||||
"hero_section_1_heading": "Parece que ainda não há trilhas aqui.",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "Nenhum resultado encontrado",
|
||||
"not-a-valid-email-address": "Não um endereço de e-mail válido",
|
||||
"not-completed": "Não preenchido",
|
||||
"off": "",
|
||||
"or": "ou",
|
||||
"orientation": "",
|
||||
"paper-size": "",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "Mostrar na vista geral",
|
||||
"show-on-map": "Mostrar no mapa",
|
||||
"slogan": "Guarde as suas aventuras!",
|
||||
"slope": "",
|
||||
"sort": "Ordenar",
|
||||
"speed": "",
|
||||
"stop-drawing": "",
|
||||
"summit-book": "Livro da cimeira",
|
||||
"text": "Texto",
|
||||
@@ -163,4 +167,4 @@
|
||||
"welcome_to": "Bem-vindo ao",
|
||||
"wrong-username-or-password": "Nome de utilizador ou palavra-passe errados",
|
||||
"you-can": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"file-format": "",
|
||||
"french": "法语",
|
||||
"german": "德语",
|
||||
"get-position-from-exif": "",
|
||||
"grid": "",
|
||||
"hero_section_0_text": "探索激动人心的探险,收藏最爱行程,,体验美丽地大自然。找到你的下一次冒险!",
|
||||
"hero_section_1_heading": "这儿似乎还没有行程。",
|
||||
@@ -117,6 +118,7 @@
|
||||
"no-results": "没有找到结果",
|
||||
"not-a-valid-email-address": "无效电子邮箱地址",
|
||||
"not-completed": "未完成",
|
||||
"off": "",
|
||||
"or": "或",
|
||||
"orientation": "",
|
||||
"paper-size": "",
|
||||
@@ -147,7 +149,9 @@
|
||||
"show-in-overview": "详情中展示",
|
||||
"show-on-map": "地图中展示",
|
||||
"slogan": "保存你的冒险!",
|
||||
"slope": "",
|
||||
"sort": "排序",
|
||||
"speed": "",
|
||||
"stop-drawing": "",
|
||||
"summit-book": "详细日程",
|
||||
"text": "文本",
|
||||
@@ -163,4 +167,4 @@
|
||||
"welcome_to": "欢迎",
|
||||
"wrong-username-or-password": "用户名或密码无效",
|
||||
"you-can": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,13 @@ export function calculateScaleFactor(map: Map) {
|
||||
const scaleFactor = realWorlMetersPer100Pixels / screenMetersPer100Pixels
|
||||
|
||||
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
10
web/src/lib/vendor/exif-js/exif.d.ts
vendored
Normal 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
1047
web/src/lib/vendor/exif-js/exif.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -452,8 +452,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
||||
.then(() => {
|
||||
layer.eachLayer((trkseg) => {
|
||||
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(geo.toGeoJSON().features[0].geometry.coordinates, {
|
||||
let line = L.hotline(this._data.map(m => [m.x, m.y, m[prop] || 0]), {
|
||||
renderer: L.Hotline.renderer(),
|
||||
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,
|
||||
@@ -1037,7 +1036,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
||||
// check and fix missing data on last added point
|
||||
if (i > 0 && isNaN(prev[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])) {
|
||||
prev[attr] = lastValid[attr];
|
||||
} else if (!isNaN(curr[attr])) {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
// +layout.ts
|
||||
import '$lib/i18n';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import { env } from "$env/dynamic/private";
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||
return { settings: locals.settings }
|
||||
export const load: LayoutServerLoad = async ({ locals, url }) => {
|
||||
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 }
|
||||
}
|
||||
@@ -7,11 +7,14 @@
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
import { isRouteProtected } from "$lib/util/authorization_util";
|
||||
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/components.css";
|
||||
import "../css/theme.css";
|
||||
import { page } from "$app/stores";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
|
||||
beforeNavigate((n) => {
|
||||
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
||||
n.cancel();
|
||||
@@ -20,6 +23,7 @@
|
||||
});
|
||||
|
||||
let hideDemoHint = false;
|
||||
let hideWarning = false;
|
||||
</script>
|
||||
|
||||
{#if env.PUBLIC_IS_DEMO === "true" && !hideDemoHint}
|
||||
@@ -38,6 +42,19 @@
|
||||
</div>
|
||||
{/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>
|
||||
<Toast></Toast>
|
||||
<slot />
|
||||
|
||||
@@ -64,9 +64,39 @@
|
||||
map = L.map("map").setView([0, 0], 4);
|
||||
map.attributionControl.setPrefix(false);
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
}).addTo(map);
|
||||
const baseLayer = L.tileLayer(
|
||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
{
|
||||
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) => {
|
||||
const bounds = map.getBounds();
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
onMount(() => {
|
||||
if (window.innerWidth < 768) {
|
||||
filterExpanded = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleFilterUpdate() {
|
||||
async function handleFilterUpdate() {
|
||||
const response = await trails_search_filter(filter, 1);
|
||||
pagination.page = response.page;
|
||||
pagination.totalPages = response.totalPages;
|
||||
}
|
||||
|
||||
async function paginate(page: number) {
|
||||
async function paginate(page: number) {
|
||||
pagination.page = page;
|
||||
const response = await trails_search_filter(filter, page);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user