diff --git a/search/Dockerfile b/search/Dockerfile index 10e4ed0f..fec03b36 100644 --- a/search/Dockerfile +++ b/search/Dockerfile @@ -1,4 +1,4 @@ -FROM getmeili/meilisearch:v1.6.2 +FROM getmeili/meilisearch:v1.7 COPY ./migrations/migration.dump /meili_data/dumps/migration.dump COPY ./entrypoint.sh /entrypoint.sh diff --git a/search/migrations/migration.dump b/search/migrations/migration.dump index 056edab7..682158c6 100644 Binary files a/search/migrations/migration.dump and b/search/migrations/migration.dump differ diff --git a/web/src/lib/components/trail/map_with_elevation.svelte b/web/src/lib/components/trail/map_with_elevation.svelte index 197a4afa..41eb05dd 100644 --- a/web/src/lib/components/trail/map_with_elevation.svelte +++ b/web/src/lib/components/trail/map_with_elevation.svelte @@ -43,7 +43,7 @@ reverseCoords: false, acceleration: false, slope: true, - speed: false, + speed: true, altitude: true, time: true, distance: true, diff --git a/web/src/lib/components/trail/trail_filter_panel.svelte b/web/src/lib/components/trail/trail_filter_panel.svelte index f1e0c9d5..87de566e 100644 --- a/web/src/lib/components/trail/trail_filter_panel.svelte +++ b/web/src/lib/components/trail/trail_filter_panel.svelte @@ -54,10 +54,12 @@ update(); } - function setDifficultyFilter(difficulty: "easy" | "moderate" | "difficult") { + function setDifficultyFilter( + difficulty: "easy" | "moderate" | "difficult", + ) { const difficultyIndex = filter.difficulty.findIndex( (d) => d == difficulty, - ); + ); if (difficultyIndex !== -1) { filter.difficulty.splice(difficultyIndex, 1); } else { @@ -102,8 +104,9 @@ searchDropdownItems = result.hits.map((h: Record) => ({ text: h.name, - description: - country_codes[h["country code"] as keyof typeof country_codes], + description: `${h.division} | ${ + country_codes[h["country code"] as keyof typeof country_codes] + }`, value: h, icon: "city", })); @@ -208,7 +211,12 @@ >
{formatDistance(filter.distanceMin)} - {formatDistance(filter.distanceMax)}{filter.distanceMax == filter.distanceLimit ? '+' : ''} + {formatDistance(filter.distanceMax)}{filter.distanceMax == + filter.distanceLimit + ? "+" + : ""}

{$_("elevation-gain")}

@@ -221,7 +229,13 @@ >
{formatElevation(filter.elevationGainMin)} - {formatElevation(filter.elevationGainMax)}{filter.elevationGainMax == filter.elevationGainLimit ? '+' : ''} + {formatElevation( + filter.elevationGainMax, + )}{filter.elevationGainMax == filter.elevationGainLimit + ? "+" + : ""}

{$_("completed")}

diff --git a/web/src/lib/stores/trail_store.ts b/web/src/lib/stores/trail_store.ts index 705043fa..9b638e0a 100644 --- a/web/src/lib/stores/trail_store.ts +++ b/web/src/lib/stores/trail_store.ts @@ -349,7 +349,7 @@ export async function trails_delete(trail: Trail) { } } -async function fetchGPX(trail: Trail, f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch) { +export async function fetchGPX(trail: Trail, f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch) { if (!trail.gpx) { return ""; } diff --git a/web/src/lib/vendor/leaflet-elevation/src/index.css b/web/src/lib/vendor/leaflet-elevation/src/index.css index ae1048af..3cc79cfb 100644 --- a/web/src/lib/vendor/leaflet-elevation/src/index.css +++ b/web/src/lib/vendor/leaflet-elevation/src/index.css @@ -331,4 +331,9 @@ text-align: center; color: #000; background: #fff; +} + +.leaflet-popup-content { + width: max-content !important; + max-width: 100%; } \ No newline at end of file diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index cc5e1df6..693256f4 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -47,7 +47,7 @@ const cityItems = response.results[1].hits.map( (c: Record) => ({ text: c.name, - description: `City | ${ + description: `City | ${c.division} | ${ country_codes[ c["country code"] as keyof typeof country_codes ] diff --git a/web/src/routes/api/v1/list/+server.ts b/web/src/routes/api/v1/list/+server.ts index 05d32acc..9b9b17d1 100644 --- a/web/src/routes/api/v1/list/+server.ts +++ b/web/src/routes/api/v1/list/+server.ts @@ -6,7 +6,7 @@ export async function GET(event: RequestEvent) { const sort = event.url.searchParams.get('sort') ?? "" try { const r: List[] = await pb.collection('lists').getFullList({ - expand: "trails", + expand: "trails,trails.waypoints,trails.category", sort: sort, }) return json(r) diff --git a/web/src/routes/lists/+page.svelte b/web/src/routes/lists/+page.svelte index efeeaf74..df7ac74a 100644 --- a/web/src/routes/lists/+page.svelte +++ b/web/src/routes/lists/+page.svelte @@ -6,7 +6,7 @@ import ListModal from "$lib/components/list/list_modal.svelte"; import TrailList from "$lib/components/trail/trail_list.svelte"; import { List } from "$lib/models/list"; - import type { TrailFilter } from "$lib/models/trail"; + import type { Trail, TrailFilter } from "$lib/models/trail"; import { list, lists, @@ -15,15 +15,129 @@ lists_index, 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, + } from "leaflet"; + import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css"; + import "leaflet/dist/leaflet.css"; + + import { onMount, tick } from "svelte"; import { _ } from "svelte-i18n"; let openListModal: () => void; let openConfirmModal: () => void; 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(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"], + }, + 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) { + console.log(e); + + if (e.point_type === "start") { + const marker: Marker = e.point as Marker; + marker.bindPopup( + ` +
  • +
    +
    +
    +

    ${trail.name}

    +
    + ${trail.location ? `
    ${trail.location}
    ` : ""} +
    ${$_(trail.difficulty as string)}
    +
    +
    ${formatDistance( + trail.distance, + )} ${formatElevation( + trail.elevation_gain, + )} ${formatTimeHHMM( + trail.duration, + )}
    +
    +
  • +
    `, + ); + } + }) + .on("loaded", function (e: LeafletEvent) { + resolve(gpxLayer); + }) + .on("error", reject) + .addTo(map); + }); + } + + async function toggleMap() { + showMap = !showMap; + if (showMap) { + setCurrentList($list); + await tick(); + map.invalidateSize(); + } + } + function beforeListModalOpen() { list.set(new List("", [])); openListModal(); @@ -62,6 +176,37 @@ await lists_index(); listToBeDeleted = null; } + + 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); + } + } @@ -72,20 +217,31 @@ style="min-height: calc(100vh - 124px)" >
      - +
      + + +
      +
      {#each $lists as item, i} -
    • list.set(item)} role="presentation"> +
    • setCurrentList(item)} + role="presentation" + > handleDropdownClick(e, item)} @@ -97,11 +253,15 @@
    • {/each}
    - await lists_index()} - > +
    +
    + await lists_index()} + > +
    + + + diff --git a/web/src/routes/map/+page.svelte b/web/src/routes/map/+page.svelte index da4a9fe2..b987e603 100644 --- a/web/src/routes/map/+page.svelte +++ b/web/src/routes/map/+page.svelte @@ -142,7 +142,7 @@ const cityItems = response.results[1].hits.map( (c: Record) => ({ text: c.name, - description: `City | ${ + description: `City | ${c.division} | ${ country_codes[ c["country code"] as keyof typeof country_codes ] @@ -341,8 +341,4 @@ height: calc(100vh - 124px); } } - :global(.leaflet-popup-content) { - width: max-content !important; - max-width: 100%; - } diff --git a/web/src/routes/profile/+page.svelte b/web/src/routes/profile/+page.svelte index ba8f7349..fb8ca886 100644 --- a/web/src/routes/profile/+page.svelte +++ b/web/src/routes/profile/+page.svelte @@ -51,8 +51,9 @@ const result = await r.json(); searchDropdownItems = result.hits.map((h: Record) => ({ text: h.name, - description: - country_codes[h["country code"] as keyof typeof country_codes], + description: `${h.division} | ${ + country_codes[h["country code"] as keyof typeof country_codes] + }`, value: h, icon: "city", })); diff --git a/web/src/routes/trail/edit/[id]/+page.svelte b/web/src/routes/trail/edit/[id]/+page.svelte index 6bd1a487..5bfe7273 100644 --- a/web/src/routes/trail/edit/[id]/+page.svelte +++ b/web/src/routes/trail/edit/[id]/+page.svelte @@ -186,7 +186,6 @@ function addGPXLayer(gpx: string, addWaypoints: boolean = true) { return new Promise(function (resolve, reject) { gpxLayer?.remove(); - let startCoordinates: LatLng; gpxLayer = new L.GPX(gpx, { async: true, polyline_options: { @@ -230,19 +229,8 @@ .on("addpoint", function (e: any) { if (e.point_type === "start") { e.point.setZIndexOffset(1000); - startCoordinates = e.point._latlng; - } else if (e.point_type == "end") { - if (startCoordinates) { - $form.lat = - (startCoordinates.lat + e.point._latlng.lat) / - 2; - $form.lon = - (startCoordinates.lng + e.point._latlng.lng) / - 2; - } else { - $form.lat = e.point._latlng.lat; - $form.lon = e.point._latlng.lng; - } + $form.lat = e.point._latlng.lat; + $form.lon = e.point._latlng.lng; } else if (e.point_type === "waypoint") { const waypoint = new Waypoint( e.point._latlng.lat,