diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py
index 0029499..7ac6842 100644
--- a/backend/server/adventures/admin.py
+++ b/backend/server/adventures/admin.py
@@ -1,7 +1,7 @@
import os
from django.contrib import admin
from django.utils.html import mark_safe
-from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note
+from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage
from worldtravel.models import Country, Region, VisitedRegion
@@ -57,6 +57,20 @@ class CustomUserAdmin(UserAdmin):
else:
return
+class AdventureImageAdmin(admin.ModelAdmin):
+ list_display = ('user_id', 'image_display')
+
+ def image_display(self, obj):
+ if obj.image: # Ensure this field matches your model's image field
+ public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/')
+ public_url = public_url.replace("'", "")
+ return mark_safe(f'
+ {#if adventure.images && adventure.images.length > 0}
+
+
+ import { createEventDispatcher } from 'svelte';
+ import type { Adventure, OpenStreetMapPlace, Point } from '$lib/types';
+ import { onMount } from 'svelte';
+ import { enhance } from '$app/forms';
+ import { addToast } from '$lib/toasts';
+ import { deserialize } from '$app/forms';
+
+ export let longitude: number | null = null;
+ export let latitude: number | null = null;
+ export let collection_id: string | null = null;
+
+ import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
+ let markers: Point[] = [];
+ let query: string = '';
+ let places: OpenStreetMapPlace[] = [];
+ let images: { id: string; image: string }[] = [];
+
+ import Earth from '~icons/mdi/earth';
+ import ActivityComplete from './ActivityComplete.svelte';
+ import { appVersion } from '$lib/config';
+
+ export let startDate: string | null = null;
+ export let endDate: string | null = null;
+
+ let noPlaces: boolean = false;
+
+ export let adventureToEdit: Adventure | null = null;
+
+ let adventure: Adventure = {
+ id: adventureToEdit?.id || '',
+ name: adventureToEdit?.name || '',
+ type: adventureToEdit?.type || 'visited',
+ date: adventureToEdit?.date || null,
+ link: adventureToEdit?.link || null,
+ description: adventureToEdit?.description || null,
+ activity_types: adventureToEdit?.activity_types || [],
+ rating: adventureToEdit?.rating || NaN,
+ is_public: adventureToEdit?.is_public || false,
+ latitude: adventureToEdit?.latitude || NaN,
+ longitude: adventureToEdit?.longitude || NaN,
+ location: adventureToEdit?.location || null,
+ images: adventureToEdit?.images || [],
+ user_id: adventureToEdit?.user_id || null,
+ collection: adventureToEdit?.collection || collection_id || null
+ };
+
+ let url: string = '';
+ let imageError: string = '';
+ let wikiImageError: string = '';
+
+ images = adventure.images || [];
+
+ if (adventure.longitude && adventure.latitude) {
+ markers = [
+ {
+ lngLat: { lng: adventure.longitude, lat: adventure.latitude },
+ location: adventure.location || '',
+ name: adventure.name,
+ activity_type: ''
+ }
+ ];
+ }
+
+ if (longitude && latitude) {
+ adventure.latitude = latitude;
+ adventure.longitude = longitude;
+ reverseGeocode();
+ }
+
+ $: {
+ if (!adventure.rating) {
+ adventure.rating = NaN;
+ }
+ }
+
+ let imageSearch: string = adventure.name || '';
+
+ async function removeImage(id: string) {
+ let res = await fetch(`/api/images/${id}/image_delete`, {
+ method: 'POST'
+ });
+ if (res.status === 204) {
+ images = images.filter((image) => image.id !== id);
+ adventure.images = images;
+ console.log(images);
+ addToast('success', 'Image removed');
+ } else {
+ addToast('error', 'Failed to remove image');
+ }
+ }
+
+ let isDetails: boolean = true;
+
+ function saveAndClose() {
+ dispatch('save', adventure);
+ close();
+ }
+
+ $: if (markers.length > 0) {
+ adventure.latitude = Math.round(markers[0].lngLat.lat * 1e6) / 1e6;
+ adventure.longitude = Math.round(markers[0].lngLat.lng * 1e6) / 1e6;
+ if (!adventure.location) {
+ adventure.location = markers[0].location;
+ }
+ if (!adventure.name) {
+ adventure.name = markers[0].name;
+ }
+ }
+
+ async function fetchImage() {
+ let res = await fetch(url);
+ let data = await res.blob();
+ if (!data) {
+ imageError = 'No image found at that URL.';
+ return;
+ }
+ let file = new File([data], 'image.jpg', { type: 'image/jpeg' });
+ let formData = new FormData();
+ formData.append('image', file);
+ formData.append('adventure', adventure.id);
+ let res2 = await fetch(`/adventures?/image`, {
+ method: 'POST',
+ body: formData
+ });
+ let data2 = await res2.json();
+ console.log(data2);
+ if (data2.type === 'success') {
+ images = [...images, data2];
+ adventure.images = images;
+ addToast('success', 'Image uploaded');
+ } else {
+ addToast('error', 'Failed to upload image');
+ }
+ }
+
+ async function fetchWikiImage() {
+ let res = await fetch(`/api/generate/img/?name=${imageSearch}`);
+ let data = await res.json();
+ if (!res.ok) {
+ wikiImageError = 'Failed to fetch image';
+ return;
+ }
+ if (data.source) {
+ let imageUrl = data.source;
+ let res = await fetch(imageUrl);
+ let blob = await res.blob();
+ let file = new File([blob], `${imageSearch}.jpg`, { type: 'image/jpeg' });
+ let formData = new FormData();
+ formData.append('image', file);
+ formData.append('adventure', adventure.id);
+ let res2 = await fetch(`/adventures?/image`, {
+ method: 'POST',
+ body: formData
+ });
+ if (res2.ok) {
+ let newData = deserialize(await res2.text()) as { data: { id: string; image: string } };
+ console.log(newData);
+ let newImage = { id: newData.data.id, image: newData.data.image };
+ console.log(newImage);
+ images = [...images, newImage];
+ adventure.images = images;
+ addToast('success', 'Image uploaded');
+ } else {
+ addToast('error', 'Failed to upload image');
+ wikiImageError = 'Failed to upload image';
+ }
+ }
+ }
+ async function geocode(e: Event | null) {
+ if (e) {
+ e.preventDefault();
+ }
+ if (!query) {
+ alert('Please enter a location');
+ return;
+ }
+ let res = await fetch(`https://nominatim.openstreetmap.org/search?q=${query}&format=jsonv2`, {
+ headers: {
+ 'User-Agent': `AdventureLog / ${appVersion} `
+ }
+ });
+ console.log(res);
+ let data = (await res.json()) as OpenStreetMapPlace[];
+ places = data;
+ if (data.length === 0) {
+ noPlaces = true;
+ } else {
+ noPlaces = false;
+ }
+ }
+
+ async function reverseGeocode() {
+ let res = await fetch(
+ `https://nominatim.openstreetmap.org/search?q=${adventure.latitude},${adventure.longitude}&format=jsonv2`,
+ {
+ headers: {
+ 'User-Agent': `AdventureLog / ${appVersion} `
+ }
+ }
+ );
+ let data = (await res.json()) as OpenStreetMapPlace[];
+ if (data.length > 0) {
+ adventure.name = data[0]?.name || '';
+ adventure.activity_types?.push(data[0]?.type || '');
+ adventure.location = data[0]?.display_name || '';
+ if (longitude && latitude) {
+ markers = [
+ {
+ lngLat: { lng: longitude, lat: latitude },
+ location: data[0]?.display_name || '',
+ name: data[0]?.name || '',
+ activity_type: data[0]?.type || ''
+ }
+ ];
+ }
+ }
+ console.log(data);
+ }
+
+ let fileInput: HTMLInputElement;
+
+ const dispatch = createEventDispatcher();
+ let modal: HTMLDialogElement;
+
+ onMount(async () => {
+ modal = document.getElementById('my_modal_1') as HTMLDialogElement;
+ if (modal) {
+ modal.showModal();
+ }
+ });
+
+ function close() {
+ dispatch('close');
+ }
+
+ function handleKeydown(event: KeyboardEvent) {
+ if (event.key === 'Escape') {
+ close();
+ }
+ }
+
+ // async function generateDesc() {
+ // let res = await fetch(`/api/generate/desc/?name=${adventureToEdit.name}`);
+ // let data = await res.json();
+ // if (data.extract) {
+ // adventureToEdit.description = data.extract;
+ // }
+ // }
+
+ function addMarker(e: CustomEvent