adds waypoint images
This commit is contained in:
137
web/src/lib/components/trail/photo_picker.svelte
Normal file
137
web/src/lib/components/trail/photo_picker.svelte
Normal file
@@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import { getFileURL, readAsDataURLAsync } from "$lib/util/file_util";
|
||||
import PhotoCard from "../photo_card.svelte";
|
||||
|
||||
export let id: string;
|
||||
export let photos: string[];
|
||||
export let photoFiles: File[] | undefined;
|
||||
export let parent: { [key: string]: any };
|
||||
export let thumbnail: number = 0;
|
||||
export let showThumbnailControls: boolean = true;
|
||||
|
||||
let photoPreviews: string[] = [];
|
||||
|
||||
$: Promise.all(
|
||||
(photoFiles ?? []).map(async (f) => {
|
||||
return await readAsDataURLAsync(f);
|
||||
}),
|
||||
).then((v) => {
|
||||
photoPreviews = v;
|
||||
});
|
||||
|
||||
let offerUpload: boolean = false;
|
||||
|
||||
function handlePhotoDragOver(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
offerUpload = true;
|
||||
}
|
||||
|
||||
function handlePhotoDragLeave() {
|
||||
offerUpload = false;
|
||||
}
|
||||
|
||||
function handlePhotoDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
offerUpload = false;
|
||||
handlePhotoSelection(e.dataTransfer?.files);
|
||||
}
|
||||
|
||||
function openPhotoBrowser() {
|
||||
document.getElementById(`${id}-photo-input`)!.click();
|
||||
}
|
||||
|
||||
function handlePhotoSelection(files?: FileList | null) {
|
||||
if (!files) {
|
||||
files = (
|
||||
document.getElementById(`${id}-photo-input`) as HTMLInputElement
|
||||
).files;
|
||||
}
|
||||
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.type.startsWith("image")) {
|
||||
continue;
|
||||
}
|
||||
if (!photoFiles) {
|
||||
photoFiles = [];
|
||||
}
|
||||
photoFiles.push(file);
|
||||
|
||||
// (function (file) {
|
||||
// var reader = new FileReader();
|
||||
// reader.onload = function (e) {
|
||||
// if (e.target?.result) {
|
||||
// photoPreviews = [
|
||||
// ...photoPreviews,
|
||||
// e.target.result as string,
|
||||
// ];
|
||||
// }
|
||||
// };
|
||||
// reader.readAsDataURL(file);
|
||||
// })(file);
|
||||
}
|
||||
}
|
||||
|
||||
function makePhotoThumbnail(index: number) {
|
||||
thumbnail = index;
|
||||
}
|
||||
|
||||
function handlePhotoDelete(index: number) {
|
||||
if (thumbnail == index) {
|
||||
thumbnail = 0;
|
||||
}
|
||||
|
||||
if (index >= photos.length) {
|
||||
if (!photoFiles) {
|
||||
photoFiles = [];
|
||||
}
|
||||
const adjustedIndex = index - photos.length;
|
||||
photoFiles.splice(adjustedIndex, 1);
|
||||
photoPreviews.splice(adjustedIndex, 1);
|
||||
|
||||
photoPreviews = [...photoPreviews];
|
||||
} else {
|
||||
photos.splice(index, 1);
|
||||
photos = [...photos];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex gap-x-4 max-w-full overflow-x-auto shrink-0 rounded-xl {offerUpload
|
||||
? 'outline-dashed outline-input-border'
|
||||
: ''}"
|
||||
role="dialog"
|
||||
on:dragover={handlePhotoDragOver}
|
||||
on:dragleave={handlePhotoDragLeave}
|
||||
on:drop={handlePhotoDrop}
|
||||
>
|
||||
<button
|
||||
class="btn-secondary h-32 w-32 m-2 shrink-0 grow-0 basis-auto"
|
||||
type="button"
|
||||
on:click={openPhotoBrowser}><i class="fa fa-plus"></i></button
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
id="{id}-photo-input"
|
||||
accept="image/*"
|
||||
multiple={true}
|
||||
style="display: none;"
|
||||
on:change={() => handlePhotoSelection()}
|
||||
/>
|
||||
{#each (photos ?? []).concat(photoPreviews) as photo, i}
|
||||
<div class="shrink-0 grow-0 basis-auto m-2">
|
||||
<PhotoCard
|
||||
src={i >= photos.length ? photo : getFileURL(parent, photo)}
|
||||
on:delete={() => handlePhotoDelete(i)}
|
||||
isThumbnail={thumbnail === i}
|
||||
on:thumbnail={() => makePhotoThumbnail(i)}
|
||||
{showThumbnailControls}
|
||||
></PhotoCard>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
|
||||
import Tabs from "$lib/components/tabs.svelte";
|
||||
import Tabs from "$lib/components/base/tabs.svelte";
|
||||
import TrailDropdown from "$lib/components/trail/trail_dropdown.svelte";
|
||||
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
@@ -17,11 +17,10 @@
|
||||
import type { Icon, Map, Marker } from "leaflet";
|
||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import type { DataSource } from "photoswipe";
|
||||
import PhotoSwipeLightbox from "photoswipe/lightbox";
|
||||
import "photoswipe/style.css";
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import PhotoGallery from "../photo_gallery.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
export let mode: "overview" | "map" = "map";
|
||||
@@ -38,13 +37,12 @@
|
||||
|
||||
let activeTab = 0;
|
||||
|
||||
let lightbox: PhotoSwipeLightbox;
|
||||
let lightboxDataSource: DataSource;
|
||||
|
||||
let thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
|
||||
let openGallery: (idx?: number) => void;
|
||||
|
||||
onMount(async () => {
|
||||
if (mode == "overview") {
|
||||
const L = (await import("leaflet")).default;
|
||||
@@ -91,42 +89,12 @@
|
||||
markers.push(marker);
|
||||
}
|
||||
}
|
||||
|
||||
lightboxDataSource = trail.photos.map((p) => ({
|
||||
src: getFileURL(trail, p),
|
||||
}));
|
||||
lightbox = new PhotoSwipeLightbox({
|
||||
dataSource: lightboxDataSource,
|
||||
pswpModule: async () => await import("photoswipe"),
|
||||
});
|
||||
lightbox.init();
|
||||
|
||||
lightbox.on("beforeOpen", () => {
|
||||
const pswp = lightbox.pswp;
|
||||
const ds = pswp?.options?.dataSource;
|
||||
if (Array.isArray(ds)) {
|
||||
for (let idx = 0, len = ds.length; idx < len; idx++) {
|
||||
const item = ds[idx];
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
item.width = img.naturalWidth;
|
||||
item.height = img.naturalHeight;
|
||||
pswp?.refreshSlideContent(idx);
|
||||
};
|
||||
img.src = item.src as string;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function openMarkerPopup(i: number) {
|
||||
markers[i].openPopup();
|
||||
}
|
||||
|
||||
function openGallery(idx: number) {
|
||||
lightbox?.loadAndOpen(idx, lightboxDataSource);
|
||||
}
|
||||
|
||||
async function toggleMapFullScreen() {
|
||||
goto(`/map/trail/${trail.id!}`);
|
||||
}
|
||||
@@ -162,8 +130,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if $currentUser && $currentUser.id == trail.author}
|
||||
<TrailDropdown {trail} {mode}
|
||||
></TrailDropdown>
|
||||
<TrailDropdown {trail} {mode}></TrailDropdown>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
@@ -234,10 +201,14 @@
|
||||
{#if activeTab == 2}
|
||||
<div
|
||||
id="photo-gallery"
|
||||
class="grid grid-cols-1 {mode == "overview"
|
||||
class="grid grid-cols-1 {mode == 'overview'
|
||||
? 'sm:grid-cols-2 md:grid-cols-3'
|
||||
: ''} gap-4"
|
||||
>
|
||||
<PhotoGallery
|
||||
photos={trail.photos.map((p) => getFileURL(trail, p))}
|
||||
bind:open={openGallery}
|
||||
></PhotoGallery>
|
||||
{#each trail.photos ?? [] as photo, i}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
|
||||
Reference in New Issue
Block a user