adds summit log table
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
export let id: string;
|
||||
export let title: string;
|
||||
export let size: string = "max-w-2xl";
|
||||
|
||||
export function openModal() {
|
||||
document.body.style.top = `-${window.scrollY}px`;
|
||||
document.body.style.position = "fixed";
|
||||
document.body.style.width = "100%";
|
||||
document.body.style.top = `-${window.scrollY}px`;
|
||||
|
||||
(document.getElementById(id) as HTMLDialogElement).showModal();
|
||||
}
|
||||
@@ -25,10 +26,10 @@
|
||||
{id}
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
class="relative w-full {size} max-h-full rounded-xl text-content"
|
||||
class="w-full {size} max-h-full rounded-xl text-content"
|
||||
>
|
||||
<!-- Modal content -->
|
||||
<div class="relative bg-background shadow rounded-xl">
|
||||
<div class="bg-background shadow rounded-xl">
|
||||
<!-- Modal header -->
|
||||
<div
|
||||
class="flex items-center justify-between p-4 md:p-5 border-b border-separator rounded-t"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
initialValues: $summitLog,
|
||||
validationSchema: summitLogSchema,
|
||||
onSubmit: async (submittedValues) => {
|
||||
if(!$form._gpx) {
|
||||
if(!$form.expand.gpx_data) {
|
||||
$form.gpx = "";
|
||||
}
|
||||
dispatch("save", submittedValues);
|
||||
|
||||
125
web/src/lib/components/summit_log/summit_log_table.svelte
Normal file
125
web/src/lib/components/summit_log/summit_log_table.svelte
Normal file
@@ -0,0 +1,125 @@
|
||||
<script lang="ts">
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { onMount, tick } from "svelte";
|
||||
import Modal from "../base/modal.svelte";
|
||||
import SummitLogTableRow from "./summit_log_table_row.svelte";
|
||||
|
||||
import type { Map } from "leaflet";
|
||||
import { gpx } from "$lib/vendor/toGeoJSON/toGeoJSON";
|
||||
import { endIcon, startIcon } from "$lib/util/leaflet_util";
|
||||
export let summitLogs: SummitLog[];
|
||||
|
||||
let openModal: () => void;
|
||||
let closeModal: () => void;
|
||||
|
||||
let map: Map;
|
||||
let L: any;
|
||||
let layerGroup: any;
|
||||
|
||||
onMount(async () => {
|
||||
L = (await import("leaflet")).default;
|
||||
|
||||
map = L.map("summit-log-table-map");
|
||||
map.attributionControl.setPrefix(false);
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
}).addTo(map);
|
||||
|
||||
layerGroup = L.layerGroup();
|
||||
|
||||
layerGroup.addTo(map);
|
||||
});
|
||||
|
||||
async function openMap(log: SummitLog) {
|
||||
if (!log.expand.gpx_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
layerGroup.clearLayers();
|
||||
const geoJson = gpx(
|
||||
new DOMParser().parseFromString(log.expand.gpx_data, "text/xml"),
|
||||
);
|
||||
const layer = L.geoJson(geoJson, {
|
||||
onEachFeature: (feature: any, layer: any) => {
|
||||
let startCoords, endCoords;
|
||||
|
||||
if (geoJson.features && geoJson.features.length > 0) {
|
||||
const geometry = geoJson.features[0].geometry;
|
||||
if (geometry.type === "LineString") {
|
||||
startCoords = geometry.coordinates[0];
|
||||
endCoords =
|
||||
geometry.coordinates[
|
||||
geometry.coordinates.length - 1
|
||||
];
|
||||
} else if (geometry.type === "MultiLineString") {
|
||||
startCoords = (geometry as any).coordinates[0][0];
|
||||
endCoords = (geometry as any).coordinates[
|
||||
geometry.coordinates.length - 1
|
||||
][
|
||||
(geometry as any).coordinates[
|
||||
geometry.coordinates.length - 1
|
||||
].length - 1
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (startCoords && endCoords) {
|
||||
const startMarker = L.marker(
|
||||
[startCoords[1], startCoords[0]],
|
||||
{
|
||||
icon: startIcon(),
|
||||
},
|
||||
);
|
||||
|
||||
const endMarker = L.marker([endCoords[1], endCoords[0]], {
|
||||
icon: endIcon(),
|
||||
});
|
||||
|
||||
layerGroup.addLayer(startMarker);
|
||||
layerGroup.addLayer(endMarker)
|
||||
}
|
||||
},
|
||||
filter: (feature: any, layer: any) => {
|
||||
return feature.geometry.type !== "Point";
|
||||
},
|
||||
}).addTo(map);
|
||||
|
||||
layerGroup.addLayer(layer);
|
||||
|
||||
openModal();
|
||||
await tick();
|
||||
map.fitBounds(layer.getBounds());
|
||||
map.invalidateSize();
|
||||
}
|
||||
</script>
|
||||
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="text-sm">
|
||||
<th class="w-24"></th>
|
||||
<th>Date</th>
|
||||
<th>Distance</th>
|
||||
<th class="whitespace-nowrap">Elevation gain</th>
|
||||
<th>Duration</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each summitLogs as log, i}
|
||||
<SummitLogTableRow index={i} {log} on:open={() => openMap(log)}
|
||||
></SummitLogTableRow>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Modal id="summit-log-table-modal" title="" bind:openModal bind:closeModal>
|
||||
<div slot="content" id="summit-log-table-map" class="h-96"></div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
th {
|
||||
padding: 0rem 0.75rem;
|
||||
}
|
||||
</style>
|
||||
118
web/src/lib/components/summit_log/summit_log_table_row.svelte
Normal file
118
web/src/lib/components/summit_log/summit_log_table_row.svelte
Normal file
@@ -0,0 +1,118 @@
|
||||
<script lang="ts">
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
import GPX from "$lib/models/gpx/gpx";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import { gpx } from "$lib/vendor/toGeoJSON/toGeoJSON";
|
||||
import type { Map } from "leaflet";
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
|
||||
export let index: number;
|
||||
export let log: SummitLog;
|
||||
|
||||
let totals: {
|
||||
distance: number;
|
||||
elevationGain: number;
|
||||
duration: number;
|
||||
} | null = null;
|
||||
|
||||
let map: Map;
|
||||
|
||||
let showText: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
onMount(async () => {
|
||||
if (log.expand.gpx_data) {
|
||||
await initMap();
|
||||
}
|
||||
});
|
||||
|
||||
async function initMap() {
|
||||
const L = (await import("leaflet")).default;
|
||||
|
||||
map = L.map("mini-map-" + index, {
|
||||
zoomControl: false,
|
||||
scrollWheelZoom: false,
|
||||
dragging: false,
|
||||
});
|
||||
map.attributionControl.setPrefix(false);
|
||||
|
||||
if (!log.expand.gpx_data || !map) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gpxObject = await GPX.parse(log.expand.gpx_data);
|
||||
if (gpxObject instanceof Error) {
|
||||
throw gpxObject;
|
||||
}
|
||||
|
||||
totals = gpxObject.getTotals();
|
||||
|
||||
const geoJson = gpx(
|
||||
new DOMParser().parseFromString(log.expand.gpx_data, "text/xml"),
|
||||
);
|
||||
const layer = (L as any)
|
||||
.geoJson(geoJson, {
|
||||
filter: (feature: any, layer: any) => {
|
||||
return feature.geometry.type !== "Point";
|
||||
},
|
||||
})
|
||||
.addTo(map);
|
||||
map.fitBounds(layer.getBounds());
|
||||
map.invalidateSize();
|
||||
}
|
||||
|
||||
function openMap() {
|
||||
dispatch("open", log);
|
||||
}
|
||||
</script>
|
||||
|
||||
<tr class="h-24 text-center">
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
on:click={openMap}
|
||||
class="h-24 aspect-square shrink-0 rounded-xl !bg-background"
|
||||
class:hidden={!log.expand.gpx_data}
|
||||
id="mini-map-{index}"
|
||||
></button>
|
||||
</td>
|
||||
<td
|
||||
>{new Date(log.date).toLocaleDateString(undefined, {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
})}</td
|
||||
>
|
||||
<td>
|
||||
{formatDistance(totals?.distance)}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{formatElevation(totals?.elevationGain)}
|
||||
</td><td>
|
||||
{formatTimeHHMM(totals?.duration)}
|
||||
</td>
|
||||
<td>
|
||||
{#if log.text}
|
||||
<button on:click={() => (showText = !showText)} class="btn-icon"
|
||||
><i class="fa{showText ? '' : '-regular'} fa-message"
|
||||
></i></button
|
||||
>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{#if showText}
|
||||
<tr
|
||||
><td class="text-left text-sm whitespace-pre-wrap" colspan="6"
|
||||
>{log.text}</td
|
||||
></tr
|
||||
>
|
||||
{/if}
|
||||
@@ -38,6 +38,7 @@
|
||||
import CommentCard from "../comment/comment_card.svelte";
|
||||
import PhotoGallery from "../photo_gallery.svelte";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
import SummitLogTable from "../summit_log/summit_log_table.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
export let mode: "overview" | "map" | "list" = "map";
|
||||
@@ -335,11 +336,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if activeTab == 3}
|
||||
<ul>
|
||||
{#each trail.expand.summit_logs ?? [] as log, i}
|
||||
<li><SummitLogCard {log} index={i}></SummitLogCard></li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="overflow-x-scroll">
|
||||
<SummitLogTable summitLogs={trail.expand.summit_logs}
|
||||
></SummitLogTable>
|
||||
</div>
|
||||
{/if}
|
||||
{#if activeTab == 4}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user