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

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

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import CategoryCard from "$lib/components/category_card.svelte";
import TrailCard from "$lib/components/trail/trail_card.svelte";
import { pb } from "$lib/constants";
import type { Trail } from "$lib/models/trail";
import { categories } from "$lib/stores/category_store";
import { summit_logs_delete } from "$lib/stores/summit_log_store";
@@ -16,7 +16,9 @@
currentTrail: Trail,
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) {
for (const waypoint of currentTrail.expand.waypoints) {
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"
>
{#each $trails as trail}
<a href="/trail/{trail.id}">
<a href="/trail/view/{trail.id}">
<TrailCard
{trail}
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 WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
import { pb } from "$lib/constants";
import { SummitLog } from "$lib/models/summit_log";
import { Trail, trailSchema } from "$lib/models/trail";
import { Waypoint } from "$lib/models/waypoint";
import { categories } from "$lib/stores/category_store";
import { summitLog, summit_logs_create } from "$lib/stores/summit_log_store";
import { trail, trails_create, trails_update } from "$lib/stores/trail_store";
import { waypoint, waypoints_create } from "$lib/stores/waypoint_store";
import { summitLog } from "$lib/stores/summit_log_store";
import { toast } from "$lib/stores/toast_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 { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import { createForm } from "$lib/vendor/svelte-form-lib";
@@ -26,6 +30,8 @@
import "leaflet/dist/leaflet.css";
import { onMount } from "svelte";
export let data: { trail: Trail };
let L: any;
let map: Map;
@@ -35,46 +41,6 @@
let openSummitLogModal: () => void;
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 () => {
L = (await import("leaflet")).default;
@@ -86,8 +52,107 @@
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors",
}).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() {
document.getElementById("fileInput")!.click();
}
@@ -110,54 +175,7 @@
reader.onload = function (e) {
trail.set(new Trail(""));
gpxLayer?.remove();
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);
addGPXLayer(e.target?.result as string);
};
}
@@ -167,6 +185,7 @@
function handleWaypointMenuClick(
currentWaypoint: Waypoint,
index: number,
e: CustomEvent<{ text: string; value: string }>,
) {
if (e.detail.value === "edit") {
@@ -174,11 +193,8 @@
openWaypointModal();
} else if (e.detail.value === "delete") {
currentWaypoint.marker?.remove();
$form.expand.waypoints = $form.expand.waypoints.filter(
(w) =>
w.lat !== currentWaypoint.lat &&
w.lon !== currentWaypoint.lon,
);
$form.expand.waypoints.splice(index, 1);
$form.expand.waypoints = $form.expand.waypoints;
}
}
@@ -260,7 +276,7 @@
}
function beforeSummitLogModalOpen() {
summitLog.set(new SummitLog(new Date().toISOString()));
summitLog.set(new SummitLog(format(new Date(), "yyyy-MM-dd")));
openSummitLogModal();
}
@@ -344,9 +360,16 @@
<TextField
name="name"
label="Name"
on:change={handleChange}
error={$errors.name}
bind:value={$form.name}
></TextField>
<TextField
name="location"
label="Location"
error={$errors.location}
bind:value={$form.location}
></TextField>
<Textarea
name="description"
label="Describe your trail"
@@ -361,12 +384,13 @@
<hr />
<h3 class="text-xl font-semibold">Waypoints</h3>
<ul>
{#each $form.expand.waypoints as waypoint, i}
{#each $form.expand.waypoints ?? [] as waypoint, i}
<li on:mouseenter={() => openMarkerPopup(waypoint)}>
<WaypointCard
{waypoint}
mode="edit"
on:change={(e) => handleWaypointMenuClick(waypoint, e)}
on:change={(e) =>
handleWaypointMenuClick(waypoint, i, e)}
></WaypointCard>
</li>
{/each}
@@ -393,7 +417,7 @@
style="display: none;"
on:change={handlePhotoSelection}
/>
{#each $form.photos as photo, i}
{#each $form.photos ?? [] as photo, i}
<div class="shrink-0 grow-0 basis-auto">
<PhotoCard
src={photo}
@@ -407,7 +431,7 @@
<hr />
<h3 class="text-xl font-semibold">Summit Book</h3>
<ul>
{#each $form.expand.summit_logs as log, i}
{#each $form.expand.summit_logs ?? [] as log, i}
<li>
<SummitLogCard
{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 WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import { trail } from "$lib/stores/trail_store";
import { getFileURL } from "$lib/util/file_util";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import type { Icon, Marker } from "leaflet";
@@ -34,7 +33,7 @@
attribution: "© OpenStreetMap contributors",
}).addTo(map);
const gpxLayer = new L.GPX($trail.gpx!, {
const gpxLayer = new L.GPX($trail.expand.gpx_data!, {
async: true,
gpx_options: {
parseElements: ["track"] as any,
@@ -76,8 +75,8 @@
markers.push(marker);
}
lightboxDataSource = $trail.photos.map((v) => ({
src: getFileURL($trail, v),
lightboxDataSource = $trail.photos.map((p) => ({
src: p,
}));
lightbox = new PhotoSwipeLightbox({
dataSource: lightboxDataSource,
@@ -118,7 +117,7 @@
<section class="relative h-80">
<img
class="w-full h-80"
src={getFileURL($trail, $trail.thumbnail)}
src={$trail.thumbnail}
alt=""
/>
<div
@@ -182,7 +181,7 @@
{/if}
{#if activeTab == 1}
<ul>
{#each $trail.expand.waypoints as waypoint, i}
{#each $trail.expand.waypoints ?? [] as waypoint, i}
<li on:mouseenter={() => openMarkerPopup(i)}>
<WaypointCard {waypoint}></WaypointCard>
</li>
@@ -194,20 +193,20 @@
id="photo-gallery"
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-no-noninteractive-element-interactions -->
<img
class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
on:click={() => openGallery(i)}
src={getFileURL($trail, photo)}
src={photo}
alt=""
/>
{/each}
</div>
{/if}
{#if activeTab == 3}
{#each $trail.expand.summit_logs as log}
{#each $trail.expand.summit_logs ?? [] as log}
<SummitLogCard {log}></SummitLogCard>
{/each}
{/if}