adds photo upload for summit logs

This commit is contained in:
Christian Beutel
2024-11-30 18:34:48 +01:00
parent 8f8d28ef7a
commit 1ad7fb3820
20 changed files with 314 additions and 366 deletions

View File

@@ -1,25 +1,31 @@
<script lang="ts">
import type { SummitLog } from "$lib/models/summit_log";
import "leaflet/dist/leaflet.css";
import { _ } from "svelte-i18n";
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
import { browser } from "$app/environment";
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 { onMount } from "svelte";
import { getFileURL, readAsDataURLAsync } from "$lib/util/file_util";
let map: Map;
let L: any;
let layer: any;
let thumbnail: string = "/imgs/default_thumbnail.webp";
$: Promise.all(
(log._photos ?? []).map(async (f) => {
return await readAsDataURLAsync(f);
}),
).then((v) => {
if (log.photos.length) {
thumbnail = getFileURL(log, log.photos[0]);
} else if (v.length) {
thumbnail = v[0];
} else {
thumbnail = "/imgs/default_thumbnail.webp";
}
});
export let index: number = 0;
export let log: SummitLog;
export let mode: "show" | "edit" = "show";
@@ -27,64 +33,18 @@
{ text: $_("edit"), value: "edit" },
{ text: $_("delete"), value: "delete" },
];
onMount(async () => {
if (!map) {
await initMap();
}
if (log.expand.gpx_data) {
showTrailOnMap();
}
});
async function initMap() {
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) {
showTrailOnMap();
} else {
removeTrailFromMap();
}
async function showTrailOnMap() {
if (!log.expand.gpx_data || !browser || !map) {
return;
}
const geoJson = gpx(
new DOMParser().parseFromString(log.expand.gpx_data, "text/xml"),
);
layer = L.geoJson(geoJson, {
filter: (feature: any, layer: any) => {
return feature.geometry.type !== "Point";
},
}).addTo(map);
map.fitBounds(layer.getBounds());
map.invalidateSize();
}
function removeTrailFromMap() {
if (layer) {
map?.removeLayer(layer);
}
}
</script>
<div class="p-4 my-2 border border-input-border rounded-xl">
<div class="flex items-center gap-x-4">
<div
class="h-24 aspect-square shrink-0 rounded-xl !bg-background"
class:hidden={!log.expand.gpx_data}
id="mini-map-{index}"
></div>
<div class="h-24 aspect-square shrink-0 rounded-xl overflow-hidden">
<img
id="header-img"
class="object-cover h-full"
src={thumbnail}
alt=""
/>
</div>
<div class="basis-full">
<div
class="flex justify-between items-center"

View File

@@ -12,6 +12,7 @@
import Textarea from "../base/textarea.svelte";
import TrailPicker from "../trail/trail_picker.svelte";
import GPX from "$lib/models/gpx/gpx";
import PhotoPicker from "../trail/photo_picker.svelte";
export let openModal: (() => void) | undefined = undefined;
export let closeModal: (() => void) | undefined = undefined;
@@ -30,6 +31,22 @@
if (!$form.expand.gpx_data) {
$form.gpx = "";
}
if (
!$form._photos?.length &&
!$form.photos?.length &&
$form.expand?.gpx_data
) {
const canvas = document.querySelector(
"#trail-picker-map .maplibregl-canvas",
) as HTMLCanvasElement;
const dataURL = canvas.toDataURL();
const response = await fetch(dataURL);
const blob = await response.blob();
$form._photos = [new File([blob], "route")];
}
dispatch("save", submittedValues);
closeModal!();
},
@@ -85,6 +102,18 @@
on:change={handleChange}
></Datepicker>
</div>
<div>
<label for="summitlog-photo-input" class="text-sm font-medium pb-1">
{$_("photos")}
</label>
<PhotoPicker
id="summitlog-photo-input"
parent={$form}
bind:photos={$form.photos}
bind:photoFiles={$form._photos}
showThumbnailControls={false}
></PhotoPicker>
</div>
<div class="flex gap-4">
<TrailPicker
bind:trailFile={$form._gpx}

View File

@@ -79,7 +79,6 @@
<tbody>
{#each summitLogs as log, i}
<SummitLogTableRow
index={i}
{log}
on:open={(e) => openMap(e.detail)}
on:text={(e) => openText(e.detail)}

View File

@@ -1,66 +1,35 @@
<script lang="ts">
import type { SummitLog } from "$lib/models/summit_log";
import "leaflet/dist/leaflet.css";
import { getFileURL } from "$lib/util/file_util";
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";
import { createEventDispatcher } from "svelte";
import { _ } from "svelte-i18n";
import { getFileURL } from "$lib/util/file_util";
import PhotoGallery from "../photo_gallery.svelte";
export let index: number;
export let log: SummitLog;
export let showCategory: boolean = false;
export let showTrail: boolean = false;
export let showAuthor: boolean = false;
let map: Map;
let openGallery: (idx?: number) => void;
let imgSrc: string[] = [];
$: if (log.photos?.length) {
imgSrc = log.photos
.filter((_, i) => i < 3)
.reverse()
.map((p) => getFileURL(log, p));
} else {
imgSrc = [];
}
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 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);
}
function openText() {
dispatch("text", log);
}
@@ -75,13 +44,27 @@
<tr class="text-center">
<td>
<button
type="button"
on:click={openMap}
class="h-20 aspect-square shrink-0 rounded-xl !bg-background hover:!bg-secondary-hover transition-colors"
class:hidden={!log.expand?.gpx_data}
id="mini-map-{index}"
></button>
{#if imgSrc.length}
<PhotoGallery
photos={log.photos.map((p) => getFileURL(log, p))}
bind:open={openGallery}
></PhotoGallery>
<button
class="relative w-16 aspect-square ml-2 mb-3 shrink-0"
type="button"
on:click={() => openGallery()}
>
{#each imgSrc as img, i}
<img
class="absolute h-full rounded-xl object-cover"
style="top: {6 * i}px; right: {6 *
i}px; transform: rotate(-{i * 5}deg)"
src={img}
alt="waypoint"
/>
{/each}
</button>
{/if}
</td>
<td class:py-4={!log.expand?.gpx_data}
>{new Date(log.date).toLocaleDateString(undefined, {