adds map_with_elevation_multiple
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import type { DropdownItem } from "$lib/components/base/dropdown.svelte";
|
||||
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||
import ListCard from "$lib/components/list/list_card.svelte";
|
||||
import ListModal from "$lib/components/list/list_modal.svelte";
|
||||
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
||||
import TrailList from "$lib/components/trail/trail_list.svelte";
|
||||
import { List } from "$lib/models/list";
|
||||
import type { Trail, TrailFilter } from "$lib/models/trail";
|
||||
import type { TrailFilter } from "$lib/models/trail";
|
||||
import {
|
||||
list,
|
||||
lists,
|
||||
@@ -16,25 +18,14 @@
|
||||
lists_update,
|
||||
} from "$lib/stores/list_store";
|
||||
import { fetchGPX } from "$lib/stores/trail_store";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||
import type {
|
||||
GPX,
|
||||
Icon,
|
||||
LatLngBoundsExpression,
|
||||
LeafletEvent,
|
||||
Map,
|
||||
Marker,
|
||||
Map
|
||||
} from "leaflet";
|
||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
import { onMount, tick } from "svelte";
|
||||
import { tick } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
let openListModal: () => void;
|
||||
@@ -43,89 +34,8 @@
|
||||
let filter: TrailFilter = $page.data.filter;
|
||||
let listToBeDeleted: List | null = null;
|
||||
|
||||
let L: any;
|
||||
let map: Map;
|
||||
let showMap: boolean = false;
|
||||
let gpxLayers: GPX[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
L = (await import("leaflet")).default;
|
||||
await import("leaflet-gpx");
|
||||
await import("leaflet.awesome-markers");
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
function addGPXLayer(trail: Trail) {
|
||||
return new Promise<GPX>(function (resolve, reject) {
|
||||
if (!trail.expand.gpx_data) {
|
||||
reject();
|
||||
}
|
||||
|
||||
const thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
const gpxLayer = new L.GPX(trail.expand.gpx_data!, {
|
||||
async: true,
|
||||
polyline_options: {
|
||||
className: "lightblue-theme elevation-polyline",
|
||||
weight: 5,
|
||||
},
|
||||
gpx_options: {
|
||||
parseElements: ["track", "route"],
|
||||
},
|
||||
marker_options: {
|
||||
startIcon: L.AwesomeMarkers.icon({
|
||||
icon: "circle-half-stroke",
|
||||
prefix: "fa",
|
||||
markerColor: "cadetblue",
|
||||
iconColor: "white",
|
||||
}) as Icon,
|
||||
startIconUrl: "",
|
||||
endIconUrl: "",
|
||||
shadowUrl: "",
|
||||
},
|
||||
})
|
||||
.on("addpoint", function (e: any) {
|
||||
if (e.point_type === "start") {
|
||||
const marker: Marker = e.point as Marker;
|
||||
marker.bindPopup(
|
||||
`<a href="map/trail/${trail.id}">
|
||||
<li class="flex items-center gap-4 cursor-pointer text-black">
|
||||
<div class="shrink-0"><img class="h-14 w-14 object-cover rounded-xl" src="${thumbnail}" alt="">
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-lg">${trail.name}</h4>
|
||||
<div class="flex gap-x-4">
|
||||
${trail.location ? `<h5><i class="fa fa-location-dot mr-2"></i>${trail.location}</h5>` : ""}
|
||||
<h5><i class="fa fa-gauge mr-2"></i>${$_(trail.difficulty as string)}</h5>
|
||||
</div>
|
||||
<div class="flex mt-2 gap-4 text-sm text-gray-500"><span class="shrink-0"><i
|
||||
class="fa fa-left-right mr-2"></i>${formatDistance(
|
||||
trail.distance,
|
||||
)}</span> <span class="shrink-0"><i class="fa fa-up-down mr-2"></i>${formatElevation(
|
||||
trail.elevation_gain,
|
||||
)}</span> <span class="shrink-0"><i class="fa fa-clock mr-2"></i>${formatTimeHHMM(
|
||||
trail.duration,
|
||||
)}</span></div>
|
||||
</div>
|
||||
</li>
|
||||
</a>`,
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("loaded", function (e: LeafletEvent) {
|
||||
resolve(gpxLayer);
|
||||
})
|
||||
.on("error", reject)
|
||||
.addTo(map);
|
||||
});
|
||||
}
|
||||
let showMap: boolean = true;
|
||||
|
||||
async function toggleMap() {
|
||||
showMap = !showMap;
|
||||
@@ -136,11 +46,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function beforeListModalOpen() {
|
||||
list.set(new List("", []));
|
||||
openListModal();
|
||||
}
|
||||
|
||||
async function saveList(e: CustomEvent<{ list: List; avatar?: File }>) {
|
||||
const result = e.detail;
|
||||
if (result.list.id) {
|
||||
@@ -158,8 +63,7 @@
|
||||
const item = e.detail;
|
||||
|
||||
if (item.value == "edit") {
|
||||
list.set(currentList);
|
||||
openListModal();
|
||||
goto("/lists/edit/" + currentList.id);
|
||||
} else if (item.value == "delete") {
|
||||
openConfirmModal();
|
||||
listToBeDeleted = currentList;
|
||||
@@ -176,36 +80,13 @@
|
||||
}
|
||||
|
||||
async function setCurrentList(item: List) {
|
||||
for (const layer of gpxLayers) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
|
||||
list.set(item);
|
||||
|
||||
if (item.expand && item.expand.trails.length > 0) {
|
||||
let minLat = item.expand.trails[0].lat!;
|
||||
let maxLat = item.expand.trails[0].lat!;
|
||||
let minLon = item.expand.trails[0].lon!;
|
||||
let maxLon = item.expand.trails[0].lon!;
|
||||
|
||||
for (const trail of item.expand.trails) {
|
||||
minLat = Math.min(minLat, trail.lat!);
|
||||
maxLat = Math.max(maxLat, trail.lat!);
|
||||
minLon = Math.min(minLon, trail.lon!);
|
||||
maxLon = Math.max(maxLon, trail.lon!);
|
||||
|
||||
const gpxData: string = await fetchGPX(trail);
|
||||
trail.expand.gpx_data = gpxData;
|
||||
gpxLayers.push(await addGPXLayer(trail));
|
||||
}
|
||||
const boundingBox: LatLngBoundsExpression = [
|
||||
[maxLat, minLon],
|
||||
[minLat, maxLon],
|
||||
];
|
||||
map.fitBounds(boundingBox);
|
||||
} else {
|
||||
map.setView([0, 0], 4);
|
||||
}
|
||||
list.set(item);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -213,16 +94,16 @@
|
||||
<title>{$_("list", { values: { n: 2 } })} | wanderer</title>
|
||||
</svelte:head>
|
||||
<main
|
||||
class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-4 lg:gap-8 max-w-7xl mx-4 md:mx-auto"
|
||||
style="min-height: calc(100vh - 124px)"
|
||||
class="grid grid-cols-1 md:grid-cols-[430px_1fr] gap-4 lg:gap-4 mx-4"
|
||||
style="height: calc(100vh - 124px)"
|
||||
>
|
||||
<ul
|
||||
class="list-list mx-2 md:mx-auto rounded-xl border border-input-border p-4 max-w-full"
|
||||
class="list-list mx-4 md:mx-auto rounded-xl border border-input-border max-w-full overflow-y-scroll"
|
||||
>
|
||||
<div class="flex gap-x-4 items-center">
|
||||
<div class="flex gap-x-4 items-center p-4 top-0 sticky bg-background z-50">
|
||||
<button
|
||||
class="flex w-full items-center gap-4 hover:bg-menu-item-background-hover transition-colors rounded-xl p-4 cursor-pointer"
|
||||
on:click={beforeListModalOpen}
|
||||
on:click={() => goto("/lists/edit/new")}
|
||||
>
|
||||
<i class="fa fa-plus text-xl aspect-square"></i>
|
||||
<h5 id="create-list-button" class="text-xl font-semibold">
|
||||
@@ -235,7 +116,7 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<hr class="border-separator my-2" />
|
||||
<hr class="border-separator mb-2" />
|
||||
{#each $lists as item, i}
|
||||
<li
|
||||
class="list-list-item"
|
||||
@@ -253,7 +134,10 @@
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div id="map" class="rounded-xl z-0" class:hidden={!showMap}></div>
|
||||
<div class:hidden={!showMap}>
|
||||
<MapWithElevationMultiple trails={$list.expand?.trails ?? []} bind:map
|
||||
></MapWithElevationMultiple>
|
||||
</div>
|
||||
<div class="min-w-0" class:hidden={showMap}>
|
||||
<TrailList
|
||||
bind:filter
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
} from "$lib/components/base/search.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import Textarea from "$lib/components/base/textarea.svelte";
|
||||
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
||||
import TrailListItem from "$lib/components/trail/trail_list_item.svelte";
|
||||
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
||||
import type { Trail } from "$lib/models/trail.js";
|
||||
import { trails_show } from "$lib/stores/trail_store";
|
||||
import { getFileURL } from "$lib/util/file_util.js";
|
||||
import {
|
||||
@@ -19,18 +19,41 @@
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import type { Trail } from "$lib/models/trail.js";
|
||||
import { lists_create, lists_update } from "$lib/stores/list_store.js";
|
||||
import { show_toast } from "$lib/stores/toast_store.js";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let data;
|
||||
|
||||
let previewURL = "";
|
||||
let searchDropdownItems: SearchItem[] = [];
|
||||
|
||||
let activeTrailIndex: number | null = null;
|
||||
|
||||
let map: MapWithElevationMultiple;
|
||||
|
||||
let loading: boolean = false;
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<List>({
|
||||
initialValues: data.list!,
|
||||
validationSchema: listSchema,
|
||||
onSubmit: async (submittedList) => {
|
||||
(document.getElementById("avatar") as HTMLInputElement).value = "";
|
||||
const avatarFile = (
|
||||
document.getElementById("avatar") as HTMLInputElement
|
||||
).files![0];
|
||||
loading = true;
|
||||
if ($form.id) {
|
||||
await lists_update($form, avatarFile);
|
||||
} else {
|
||||
await lists_create($form, avatarFile);
|
||||
}
|
||||
loading = false;
|
||||
|
||||
show_toast({
|
||||
type: "success",
|
||||
icon: "check",
|
||||
text: $_("list-saved-successfully"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -77,7 +100,7 @@
|
||||
|
||||
async function handleSearchClick(item: SearchItem) {
|
||||
const trail = await trails_show(item.value, true);
|
||||
$form.trails?.push(trail);
|
||||
$form.trails?.push(trail.id!);
|
||||
$form.expand!.trails = [...$form.expand!.trails, trail];
|
||||
}
|
||||
|
||||
@@ -155,9 +178,13 @@
|
||||
items={searchDropdownItems}
|
||||
></Search>
|
||||
{#if $form.expand?.trails.length}
|
||||
{#each $form.expand?.trails ?? [] as trail}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
{#each $form.expand?.trails ?? [] as trail, i}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="flex gap-4 p-4 rounded-xl border border-input-border cursor-pointer hover:bg-secondary-hover transition-colors items-center"
|
||||
class:border-primary={i == activeTrailIndex}
|
||||
on:click={() => map.selectTrail(i)}
|
||||
>
|
||||
<div class="shrink-0">
|
||||
<img
|
||||
@@ -213,9 +240,17 @@
|
||||
>No routes added</span
|
||||
>
|
||||
{/if}
|
||||
<Button primary={true} large={true} type="submit" extraClasses="mb-2"
|
||||
>{$_("save-list")}</Button
|
||||
<Button
|
||||
primary={true}
|
||||
large={true}
|
||||
type="submit"
|
||||
extraClasses="mb-2"
|
||||
{loading}>{$_("save-list")}</Button
|
||||
>
|
||||
</form>
|
||||
<MapWithElevation trails={$form.expand?.trails ?? []}></MapWithElevation>
|
||||
<MapWithElevationMultiple
|
||||
trails={$form.expand?.trails ?? []}
|
||||
bind:activeTrailIndex
|
||||
bind:this={map}
|
||||
></MapWithElevationMultiple>
|
||||
</main>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<main class="grid grid-cols-1 md:grid-cols-[458px_1fr] gap-x-1 gap-y-4">
|
||||
<TrailInfoPanel trail={$trail} {markers}></TrailInfoPanel>
|
||||
<div id="trail-details" class=" sticky top-[62px]">
|
||||
<MapWithElevation trails={[$trail]} bind:markers></MapWithElevation>
|
||||
<MapWithElevation trail={$trail} bind:markers></MapWithElevation>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -509,7 +509,7 @@
|
||||
>
|
||||
<div class="basis-full">
|
||||
<MapWithElevation
|
||||
trails={[$trail]}
|
||||
trail={$trail}
|
||||
options={{
|
||||
theme: "gray-theme",
|
||||
slope: false,
|
||||
|
||||
@@ -889,7 +889,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<MapWithElevation
|
||||
trails={[$form]}
|
||||
trail={$form}
|
||||
crosshair={drawingActive}
|
||||
options={{
|
||||
autofitBounds: !drawingActive,
|
||||
|
||||
Reference in New Issue
Block a user