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

View File

@@ -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>

View File

@@ -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")}

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>