adds trail edit

This commit is contained in:
Christian Beutel
2024-01-31 21:55:18 +01:00
parent efaf0d4e5c
commit 99f743ec94
15 changed files with 338 additions and 136 deletions

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import { toast } from "$lib/stores/toast_store";
$: getBackgroundColor = () => {
if ($toast && $toast.type == "success") {
return "green-200";
}
return "";
};
$: getBorderColor = () => {
if ($toast && $toast.type == "success") {
return "green-500";
}
return "";
};
</script>
{#if $toast}
<div
class="fixed px-8 py-4 shadow-xl rounded-xl border bottom-4 right-4 bg-{getBackgroundColor()} border-{getBorderColor()}"
style="z-index: 1000;"
>
<i class="fa fa-{$toast.icon} mr-2"></i>
<span>{$toast.text}</span>
</div>
{/if}

View File

@@ -12,7 +12,7 @@
<a class="font-semibold" href="">Favorites</a> <a class="font-semibold" href="">Favorites</a>
</menu> </menu>
<div class="flex gap-6 items-center"> <div class="flex gap-6 items-center">
<a class="btn-primary btn-large" href="/new"><i class="fa fa-plus mr-2"></i>New Trail</a> <a class="btn-primary btn-large" href="/trail/edit/new"><i class="fa fa-plus mr-2"></i>New Trail</a>
<Avatar></Avatar> <Avatar></Avatar>
</div> </div>
</nav> </nav>

View File

@@ -17,7 +17,7 @@
const { form, errors, handleChange, handleSubmit } = createForm<SummitLog>({ const { form, errors, handleChange, handleSubmit } = createForm<SummitLog>({
initialValues: $summitLog, initialValues: $summitLog,
validationSchema: summitLogSchema, validationSchema: summitLogSchema,
onSubmit: async (submittedValues) => { onSubmit: async (submittedValues) => {
dispatch("save", submittedValues); dispatch("save", submittedValues);
closeModal!(); closeModal!();
}, },

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Trail } from "$lib/models/trail"; import type { Trail } from "$lib/models/trail";
import { getFileURL } from "$lib/util/file_util";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util"; import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
import Dropdown from "../base/dropdown.svelte"; import Dropdown from "../base/dropdown.svelte";
@@ -14,7 +13,7 @@
<div class="trail-card rounded-2xl shadow-md w-72 cursor-pointer"> <div class="trail-card rounded-2xl shadow-md w-72 cursor-pointer">
<div class="w-full h-48 overflow-hidden rounded-t-2xl"> <div class="w-full h-48 overflow-hidden rounded-t-2xl">
<img src={getFileURL(trail, trail.thumbnail)} alt="" /> <img src={trail.thumbnail} alt="" />
</div> </div>
<div class="p-4"> <div class="p-4">
<div> <div>

View File

@@ -17,6 +17,7 @@ class Trail {
category?: Category; category?: Category;
waypoints: Waypoint[] waypoints: Waypoint[]
summit_logs: SummitLog[] summit_logs: SummitLog[]
gpx_data?: string
} }
tags?: string[]; tags?: string[];
description?: string; description?: string;

View File

@@ -8,4 +8,6 @@ export async function categories_index() {
const response: Category[] = (await pb.collection('categories').getFullList<Category>()) const response: Category[] = (await pb.collection('categories').getFullList<Category>())
categories.set(response); categories.set(response);
return response;
} }

View File

@@ -0,0 +1,10 @@
import { writable, type Writable } from "svelte/store";
export type Toast = {
type: "info" | "success" | "warning" | "error"
icon: string;
text: string;
}
export const toast: Writable<Toast> = writable();

View File

@@ -2,14 +2,26 @@ import { pb } from "$lib/constants";
import { Trail } from "$lib/models/trail"; import { Trail } from "$lib/models/trail";
import { getFileURL } from "$lib/util/file_util"; import { getFileURL } from "$lib/util/file_util";
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import { waypoints_create, waypoints_delete } from "./waypoint_store";
import { summit_logs_create, summit_logs_delete } from "./summit_log_store";
import type { Waypoint } from "$lib/models/waypoint";
import type { SummitLog } from "$lib/models/summit_log";
export const trails: Writable<Trail[]> = writable([]) export const trails: Writable<Trail[]> = writable([])
export const trail: Writable<Trail> = writable(new Trail("")); export const trail: Writable<Trail> = writable(new Trail(""));
export const editTrail: Writable<Trail> = writable(new Trail(""));
export async function trails_index() { export async function trails_index() {
const response: Trail[] = (await pb.collection('trails').getList<Trail>(1, 5, { expand: "category,waypoints,summit_logs" })).items const response: Trail[] = (await pb.collection('trails').getList<Trail>(1, 5, { expand: "category,waypoints,summit_logs" })).items
for (const trail of response) {
setFileURLs(trail);
}
trails.set(response); trails.set(response);
return response;
} }
export async function trails_show(id: string, loadGPX?: boolean) { export async function trails_show(id: string, loadGPX?: boolean) {
@@ -17,26 +29,119 @@ export async function trails_show(id: string, loadGPX?: boolean) {
if (loadGPX) { if (loadGPX) {
const gpxData: string = await fetchGPX(response); const gpxData: string = await fetchGPX(response);
response.gpx = gpxData; response.expand.gpx_data = gpxData;
} }
setFileURLs(response);
response.expand.waypoints = response.expand.waypoints || [];
response.expand.summit_logs = response.expand.summit_logs || [];
response._photoFiles = [];
trail.set(response); trail.set(response);
return response;
} }
export async function trails_create(bodyParams?: { [key: string]: any; } | FormData) { export async function trails_create(trail: Trail, formData: { [key: string]: any; } | FormData) {
const model = await pb formData.set("category", trail.expand.category!.id);
for (const file of trail._photoFiles) {
formData.append("photos", file);
}
for (const waypoint of trail.expand.waypoints) {
const model = await waypoints_create({
...waypoint,
marker: undefined,
});
formData.append("waypoints", model.id!);
}
for (const summitLog of trail.expand.summit_logs) {
const model = await summit_logs_create(summitLog);
formData.append("summit_logs", model.id!);
}
let model = await pb
.collection("trails") .collection("trails")
.create<Trail>(bodyParams); .create<Trail>(formData);
const thumbnailIndex = trail.photos.findIndex(
(p) => p == trail.thumbnail,
);
let thumbnail: string | undefined = "/imgs/thumbnail.jpg";
if (thumbnailIndex >= 0) {
thumbnail = model.photos.at(thumbnailIndex);
}
model = await pb
.collection("trails")
.update<Trail>(model.id!, { thumbnail: thumbnail });
return model; return model;
} }
export async function trails_update(id: string, bodyParams?: { [key: string]: any; } | FormData) { export async function trails_update(oldTrail: Trail, newTrail: Trail, formData: { [key: string]: any; } | FormData) {
const model = await pb
.collection("trails")
.update<Trail>(id, bodyParams);
return model; const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand.waypoints ?? [], newTrail.expand.waypoints ?? []);
for (const addedWaypoint of waypointUpdates.added) {
const model = await waypoints_create({
...addedWaypoint,
marker: undefined,
});
formData.append("waypoints+", model.id!);
}
for (const deletedWaypoint of waypointUpdates.deleted) {
const success = await waypoints_delete(deletedWaypoint.id!);
}
const summitLogUpdates = compareObjectArrays<SummitLog>(oldTrail.expand.summit_logs ?? [], newTrail.expand.summit_logs ?? []);
for (const summitLog of summitLogUpdates.added) {
const model = await summit_logs_create(summitLog);
formData.append("summit_logs+", model.id!);
}
for (const deletedSummitLog of summitLogUpdates.deleted) {
const success = await summit_logs_delete(deletedSummitLog.id!);
}
for (const file of newTrail._photoFiles) {
formData.append("photos", file);
}
const deletedPhotos = oldTrail.photos.filter(oldPhoto => !newTrail.photos.find(newPhoto => newPhoto === oldPhoto));
for (const deletedPhoto of deletedPhotos) {
formData.append("photos-", deletedPhoto.replace(/^.*[\\/]/, ''));
}
if (formData.get("gpx").size == 0) {
formData.delete("gpx");
}
const thumbnailIndex = newTrail.photos.findIndex(
(p) => p == newTrail.thumbnail,
);
let model = await pb
.collection("trails")
.update<Trail>(newTrail.id!, formData);
let thumbnail: string | undefined = oldTrail.thumbnail;
if (thumbnailIndex >= 0) {
thumbnail = model.photos.at(thumbnailIndex);
}
model = await pb
.collection("trails")
.update<Trail>(model.id!, { thumbnail: thumbnail });
trail.set(model);
// return model;
} }
@@ -57,4 +162,23 @@ async function fetchGPX(trail: Trail) {
const gpxData = await response.text(); const gpxData = await response.text();
return gpxData return gpxData
} }
function setFileURLs(trail: Trail) {
trail.thumbnail = getFileURL(trail, trail.thumbnail);
for (let i = 0; i < trail.photos.length; i++) {
const photo = trail.photos[i];
trail.photos[i] = getFileURL(trail, photo)
}
}
function compareObjectArrays<T extends { id?: string }>(oldArray: T[], newArray: T[]) {
const newObjects = newArray.filter(newObj => !oldArray.find(oldObj => oldObj.id === newObj.id));
const deletedObjects = oldArray.filter(oldObj => !newArray.find(newObj => newObj.id === oldObj.id));
return {
added: newObjects,
deleted: deletedObjects
};
}

View File

@@ -1,4 +1,5 @@
<script> <script>
import Toast from "$lib/components/base/toast.svelte";
import Footer from "$lib/components/footer.svelte"; import Footer from "$lib/components/footer.svelte";
import NavBar from "$lib/components/nav_bar.svelte"; import NavBar from "$lib/components/nav_bar.svelte";
import "../app.css"; import "../app.css";
@@ -7,5 +8,6 @@ import NavBar from "$lib/components/nav_bar.svelte";
</script> </script>
<NavBar></NavBar> <NavBar></NavBar>
<Toast></Toast>
<slot /> <slot />
<Footer></Footer> <Footer></Footer>

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { goto } from "$app/navigation";
import CategoryCard from "$lib/components/category_card.svelte"; import CategoryCard from "$lib/components/category_card.svelte";
import TrailCard from "$lib/components/trail/trail_card.svelte"; import TrailCard from "$lib/components/trail/trail_card.svelte";
import { pb } from "$lib/constants";
import type { Trail } from "$lib/models/trail"; import type { Trail } from "$lib/models/trail";
import { categories } from "$lib/stores/category_store"; import { categories } from "$lib/stores/category_store";
import { summit_logs_delete } from "$lib/stores/summit_log_store"; import { summit_logs_delete } from "$lib/stores/summit_log_store";
@@ -16,7 +16,9 @@
currentTrail: Trail, currentTrail: Trail,
item: { text: string; value: any }, item: { text: string; value: any },
) { ) {
if (item.value == "delete") { if (item.value == "edit") {
goto(`/trail/edit/${currentTrail.id}`);
} else if (item.value == "delete") {
if (currentTrail.expand.waypoints) { if (currentTrail.expand.waypoints) {
for (const waypoint of currentTrail.expand.waypoints) { for (const waypoint of currentTrail.expand.waypoints) {
waypoints_delete(waypoint.id!); waypoints_delete(waypoint.id!);
@@ -55,7 +57,7 @@
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 justify-items-center gap-8 py-8" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 justify-items-center gap-8 py-8"
> >
{#each $trails as trail} {#each $trails as trail}
<a href="/trail/{trail.id}"> <a href="/trail/view/{trail.id}">
<TrailCard <TrailCard
{trail} {trail}
on:change={(e) => handleDropdownClick(trail, e.detail)} on:change={(e) => handleDropdownClick(trail, e.detail)}

View File

@@ -1,8 +0,0 @@
import { Trail } from "$lib/models/trail";
import { categories_index } from "$lib/stores/category_store";
import { trail } from "$lib/stores/trail_store";
import type { Load } from "@sveltejs/kit";
export const load: Load = async ({ params }) => {
await categories_index()
};

View File

@@ -8,14 +8,18 @@
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte"; import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte"; import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte"; import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
import { pb } from "$lib/constants";
import { SummitLog } from "$lib/models/summit_log"; import { SummitLog } from "$lib/models/summit_log";
import { Trail, trailSchema } from "$lib/models/trail"; import { Trail, trailSchema } from "$lib/models/trail";
import { Waypoint } from "$lib/models/waypoint"; import { Waypoint } from "$lib/models/waypoint";
import { categories } from "$lib/stores/category_store"; import { categories } from "$lib/stores/category_store";
import { summitLog, summit_logs_create } from "$lib/stores/summit_log_store"; import { summitLog } from "$lib/stores/summit_log_store";
import { trail, trails_create, trails_update } from "$lib/stores/trail_store"; import { toast } from "$lib/stores/toast_store";
import { waypoint, waypoints_create } from "$lib/stores/waypoint_store"; import {
trail,
trails_create,
trails_update,
} from "$lib/stores/trail_store";
import { waypoint } from "$lib/stores/waypoint_store";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util"; import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util"; import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import { createForm } from "$lib/vendor/svelte-form-lib"; import { createForm } from "$lib/vendor/svelte-form-lib";
@@ -26,6 +30,8 @@
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import { onMount } from "svelte"; import { onMount } from "svelte";
export let data: { trail: Trail };
let L: any; let L: any;
let map: Map; let map: Map;
@@ -35,46 +41,6 @@
let openSummitLogModal: () => void; let openSummitLogModal: () => void;
let loading = false; let loading = false;
const { form, errors, handleChange, handleSubmit } = createForm<Trail>({
initialValues: new Trail("adsf", { category: $categories[0] }),
validationSchema: trailSchema,
onSubmit: async (trail) => {
loading = true;
try {
const form = document.getElementById(
"trail-form",
) as HTMLFormElement;
const formData = new FormData(form);
formData.set("category", trail.expand.category!.id);
for (const file of trail._photoFiles) {
formData.append("photos", file);
}
for (const waypoint of trail.expand.waypoints) {
const model = await waypoints_create({...waypoint, marker: undefined});
formData.append("waypoints", model.id!);
}
for (const summitLog of trail.expand.summit_logs) {
const model = await summit_logs_create(summitLog);
formData.append("summit_logs", model.id!);
}
const model = await trails_create(formData);
const thumbnailIndex = trail.photos.findIndex(
(p) => p == trail.thumbnail,
);
let thumbnail: string | undefined = "/imgs/thumbnail.jpg";
if (thumbnailIndex >= 0) {
thumbnail = model.photos.at(thumbnailIndex);
}
await trails_update(model.id!, { thumbnail: thumbnail });
} catch (e) {
} finally {
loading = false;
}
},
});
onMount(async () => { onMount(async () => {
L = (await import("leaflet")).default; L = (await import("leaflet")).default;
@@ -86,8 +52,107 @@
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors", attribution: "© OpenStreetMap contributors",
}).addTo(map); }).addTo(map);
if (
data.trail.expand.gpx_data &&
data.trail.expand.gpx_data.length > 0
) {
addGPXLayer(data.trail.expand.gpx_data, false);
}
if (data.trail.expand.waypoints?.length > 0) {
for (const waypoint of data.trail.expand.waypoints) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(map);
}
}
}); });
const { form, errors, handleChange, handleSubmit } = createForm<Trail>({
initialValues: data.trail,
validationSchema: trailSchema,
onSubmit: async (submittedTrail) => {
loading = true;
try {
const form = document.getElementById(
"trail-form",
) as HTMLFormElement;
const formData = new FormData(form);
if (!submittedTrail.id) {
await trails_create(submittedTrail, formData);
} else {
await trails_update($trail, submittedTrail, formData);
}
toast.set({type: "success", "icon": "check", "text": "Trail saved successfully!"})
} catch (e) {
console.error(e);
} finally {
loading = false;
}
},
});
function addGPXLayer(gpx: string, addWaypoints: boolean = true) {
gpxLayer?.remove();
gpxLayer = new L.GPX(gpx, {
async: true,
gpx_options: {
parseElements: [
"track",
"route",
...(addWaypoints ? ["waypoint"] : []),
],
},
marker_options: {
wptIcons: {
"": L.AwesomeMarkers.icon({
icon: "circle",
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon,
},
startIcon: L.AwesomeMarkers.icon({
icon: "circle-half-stroke",
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon,
endIcon: L.AwesomeMarkers.icon({
icon: "flag-checkered",
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon,
startIconUrl: "",
endIconUrl: "",
shadowUrl: "",
},
})
.on("addpoint", function (e: any) {
if (e.point_type === "start") {
e.point.setZIndexOffset(1000);
} else if (e.point_type === "waypoint") {
const waypoint = new Waypoint(
e.point._latlng.lat,
e.point._latlng.lng,
{ name: e.point.options.title, marker: e.point },
);
if (!$form.expand.waypoints) {
}
$form.expand.waypoints.push(waypoint);
}
})
.on("loaded", function (e: LeafletEvent) {
map.fitBounds(e.target.getBounds());
$form.distance = e.target.get_distance();
$form.elevation_gain = e.target.get_elevation_gain();
$form.duration = e.target.get_total_time() / 1000 / 60;
})
.addTo(map);
}
function openFileBrowser() { function openFileBrowser() {
document.getElementById("fileInput")!.click(); document.getElementById("fileInput")!.click();
} }
@@ -110,54 +175,7 @@
reader.onload = function (e) { reader.onload = function (e) {
trail.set(new Trail("")); trail.set(new Trail(""));
gpxLayer?.remove(); addGPXLayer(e.target?.result as string);
gpxLayer = new L.GPX(e.target?.result, {
async: true,
marker_options: {
wptIcons: {
"": L.AwesomeMarkers.icon({
icon: "circle",
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon,
},
startIcon: L.AwesomeMarkers.icon({
icon: "circle-half-stroke",
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon,
endIcon: L.AwesomeMarkers.icon({
icon: "flag-checkered",
prefix: "fa",
markerColor: "cadetblue",
iconColor: "white",
}) as Icon,
startIconUrl: "",
endIconUrl: "",
shadowUrl: "",
},
})
.on("addpoint", function (e: any) {
if (e.point_type === "start") {
e.point.setZIndexOffset(1000);
} else if (e.point_type === "waypoint") {
const waypoint = new Waypoint(
e.point._latlng.lat,
e.point._latlng.lng,
{ name: e.point.options.title, marker: e.point },
);
$form.expand.waypoints.push(waypoint);
}
})
.on("loaded", function (e: LeafletEvent) {
map.fitBounds(e.target.getBounds());
$form.distance = e.target.get_distance();
$form.elevation_gain = e.target.get_elevation_gain();
$form.duration = e.target.get_total_time() / 1000 / 60;
})
.addTo(map);
}; };
} }
@@ -167,6 +185,7 @@
function handleWaypointMenuClick( function handleWaypointMenuClick(
currentWaypoint: Waypoint, currentWaypoint: Waypoint,
index: number,
e: CustomEvent<{ text: string; value: string }>, e: CustomEvent<{ text: string; value: string }>,
) { ) {
if (e.detail.value === "edit") { if (e.detail.value === "edit") {
@@ -174,11 +193,8 @@
openWaypointModal(); openWaypointModal();
} else if (e.detail.value === "delete") { } else if (e.detail.value === "delete") {
currentWaypoint.marker?.remove(); currentWaypoint.marker?.remove();
$form.expand.waypoints = $form.expand.waypoints.filter( $form.expand.waypoints.splice(index, 1);
(w) => $form.expand.waypoints = $form.expand.waypoints;
w.lat !== currentWaypoint.lat &&
w.lon !== currentWaypoint.lon,
);
} }
} }
@@ -260,7 +276,7 @@
} }
function beforeSummitLogModalOpen() { function beforeSummitLogModalOpen() {
summitLog.set(new SummitLog(new Date().toISOString())); summitLog.set(new SummitLog(format(new Date(), "yyyy-MM-dd")));
openSummitLogModal(); openSummitLogModal();
} }
@@ -344,9 +360,16 @@
<TextField <TextField
name="name" name="name"
label="Name" label="Name"
on:change={handleChange}
error={$errors.name} error={$errors.name}
bind:value={$form.name} bind:value={$form.name}
></TextField> ></TextField>
<TextField
name="location"
label="Location"
error={$errors.location}
bind:value={$form.location}
></TextField>
<Textarea <Textarea
name="description" name="description"
label="Describe your trail" label="Describe your trail"
@@ -361,12 +384,13 @@
<hr /> <hr />
<h3 class="text-xl font-semibold">Waypoints</h3> <h3 class="text-xl font-semibold">Waypoints</h3>
<ul> <ul>
{#each $form.expand.waypoints as waypoint, i} {#each $form.expand.waypoints ?? [] as waypoint, i}
<li on:mouseenter={() => openMarkerPopup(waypoint)}> <li on:mouseenter={() => openMarkerPopup(waypoint)}>
<WaypointCard <WaypointCard
{waypoint} {waypoint}
mode="edit" mode="edit"
on:change={(e) => handleWaypointMenuClick(waypoint, e)} on:change={(e) =>
handleWaypointMenuClick(waypoint, i, e)}
></WaypointCard> ></WaypointCard>
</li> </li>
{/each} {/each}
@@ -393,7 +417,7 @@
style="display: none;" style="display: none;"
on:change={handlePhotoSelection} on:change={handlePhotoSelection}
/> />
{#each $form.photos as photo, i} {#each $form.photos ?? [] as photo, i}
<div class="shrink-0 grow-0 basis-auto"> <div class="shrink-0 grow-0 basis-auto">
<PhotoCard <PhotoCard
src={photo} src={photo}
@@ -407,7 +431,7 @@
<hr /> <hr />
<h3 class="text-xl font-semibold">Summit Book</h3> <h3 class="text-xl font-semibold">Summit Book</h3>
<ul> <ul>
{#each $form.expand.summit_logs as log, i} {#each $form.expand.summit_logs ?? [] as log, i}
<li> <li>
<SummitLogCard <SummitLogCard
{log} {log}

View File

@@ -0,0 +1,20 @@
import { Trail } from "$lib/models/trail";
import { categories_index } from "$lib/stores/category_store";
import { trail, trails_show } from "$lib/stores/trail_store";
import { error, type Load } from "@sveltejs/kit";
export const load: Load = async ({ params }) => {
if (!params.id) {
return error(400, "Bad Request")
}
const categories = await categories_index()
let trail: Trail;
if (params.id === "new") {
trail = new Trail("", { category: categories[0] });
} else {
trail = await trails_show(params.id, true);
}
return { trail: trail }
};

View File

@@ -3,7 +3,6 @@
import Tabs from "$lib/components/tabs.svelte"; import Tabs from "$lib/components/tabs.svelte";
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte"; import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import { trail } from "$lib/stores/trail_store"; import { trail } from "$lib/stores/trail_store";
import { getFileURL } from "$lib/util/file_util";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util"; import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util"; import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import type { Icon, Marker } from "leaflet"; import type { Icon, Marker } from "leaflet";
@@ -34,7 +33,7 @@
attribution: "© OpenStreetMap contributors", attribution: "© OpenStreetMap contributors",
}).addTo(map); }).addTo(map);
const gpxLayer = new L.GPX($trail.gpx!, { const gpxLayer = new L.GPX($trail.expand.gpx_data!, {
async: true, async: true,
gpx_options: { gpx_options: {
parseElements: ["track"] as any, parseElements: ["track"] as any,
@@ -76,8 +75,8 @@
markers.push(marker); markers.push(marker);
} }
lightboxDataSource = $trail.photos.map((v) => ({ lightboxDataSource = $trail.photos.map((p) => ({
src: getFileURL($trail, v), src: p,
})); }));
lightbox = new PhotoSwipeLightbox({ lightbox = new PhotoSwipeLightbox({
dataSource: lightboxDataSource, dataSource: lightboxDataSource,
@@ -118,7 +117,7 @@
<section class="relative h-80"> <section class="relative h-80">
<img <img
class="w-full h-80" class="w-full h-80"
src={getFileURL($trail, $trail.thumbnail)} src={$trail.thumbnail}
alt="" alt=""
/> />
<div <div
@@ -182,7 +181,7 @@
{/if} {/if}
{#if activeTab == 1} {#if activeTab == 1}
<ul> <ul>
{#each $trail.expand.waypoints as waypoint, i} {#each $trail.expand.waypoints ?? [] as waypoint, i}
<li on:mouseenter={() => openMarkerPopup(i)}> <li on:mouseenter={() => openMarkerPopup(i)}>
<WaypointCard {waypoint}></WaypointCard> <WaypointCard {waypoint}></WaypointCard>
</li> </li>
@@ -194,20 +193,20 @@
id="photo-gallery" id="photo-gallery"
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"
> >
{#each $trail.photos as photo, i} {#each $trail.photos ?? [] as photo, i}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<img <img
class="rounded-xl cursor-pointer hover:scale-105 transition-transform" class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
on:click={() => openGallery(i)} on:click={() => openGallery(i)}
src={getFileURL($trail, photo)} src={photo}
alt="" alt=""
/> />
{/each} {/each}
</div> </div>
{/if} {/if}
{#if activeTab == 3} {#if activeTab == 3}
{#each $trail.expand.summit_logs as log} {#each $trail.expand.summit_logs ?? [] as log}
<SummitLogCard {log}></SummitLogCard> <SummitLogCard {log}></SummitLogCard>
{/each} {/each}
{/if} {/if}