1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-21 13:59:36 +02:00

Merge pull request #239 from seanmorley15/new_images

New images
This commit is contained in:
Sean Morley 2024-08-17 22:43:40 -04:00 committed by GitHub
commit cfe14b3708
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1117 additions and 197 deletions

View file

@ -1,7 +1,7 @@
import os import os
from django.contrib import admin from django.contrib import admin
from django.utils.html import mark_safe 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 from worldtravel.models import Country, Region, VisitedRegion
@ -57,6 +57,20 @@ class CustomUserAdmin(UserAdmin):
else: else:
return 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'<img src="{public_url}/media/{obj.image.name}" width="100px" height="100px"')
else:
return
image_display.short_description = 'Image Preview'
class CollectionAdmin(admin.ModelAdmin): class CollectionAdmin(admin.ModelAdmin):
def adventure_count(self, obj): def adventure_count(self, obj):
return obj.adventure_set.count() return obj.adventure_set.count()
@ -78,6 +92,7 @@ admin.site.register(Transportation)
admin.site.register(Note) admin.site.register(Note)
admin.site.register(Checklist) admin.site.register(Checklist)
admin.site.register(ChecklistItem) admin.site.register(ChecklistItem)
admin.site.register(AdventureImage, AdventureImageAdmin)
admin.site.site_header = 'AdventureLog Admin' admin.site.site_header = 'AdventureLog Admin'
admin.site.site_title = 'AdventureLog Admin Site' admin.site.site_title = 'AdventureLog Admin Site'

View file

@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2024-08-15 23:20
import django_resized.forms
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('adventures', 'migrate_images'),
]
operations = [
migrations.AddField(
model_name='adventure',
name='image',
field=django_resized.forms.ResizedImageField(blank=True, crop=None, force_format='WEBP', keep_meta=True, null=True, quality=75, scale=None, size=[1920, 1080], upload_to='images/'),
),
]

View file

@ -0,0 +1,27 @@
# Generated by Django 5.0.8 on 2024-08-15 23:17
import django.db.models.deletion
import django_resized.forms
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='AdventureImage',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('image', django_resized.forms.ResizedImageField(crop=None, force_format='WEBP', keep_meta=True, quality=75, scale=None, size=[1920, 1080], upload_to='images/')),
('adventure', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='adventures.adventure')),
('user_id', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2024-08-15 23:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0001_adventure_image'),
]
operations = [
migrations.AlterField(
model_name='adventureimage',
name='adventure',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='adventures.adventure'),
),
]

View file

@ -0,0 +1,29 @@
from django.db import migrations
def move_images_to_new_model(apps, schema_editor):
Adventure = apps.get_model('adventures', 'Adventure')
AdventureImage = apps.get_model('adventures', 'AdventureImage')
for adventure in Adventure.objects.all():
if adventure.image:
AdventureImage.objects.create(
adventure=adventure,
image=adventure.image,
user_id=adventure.user_id,
)
class Migration(migrations.Migration):
dependencies = [
('adventures', '0001_initial'),
('adventures', '0002_adventureimage'),
]
operations = [
migrations.RunPython(move_images_to_new_model),
migrations.RemoveField(
model_name='Adventure',
name='image',
),
]

View file

@ -182,3 +182,13 @@ class ChecklistItem(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
class AdventureImage(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id)
image = ResizedImageField(force_format="WEBP", quality=75, upload_to='images/')
adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE)
def __str__(self):
return self.image.url

View file

@ -1,29 +1,39 @@
import os import os
from .models import Adventure, ChecklistItem, Collection, Note, Transportation, Checklist from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist
from rest_framework import serializers from rest_framework import serializers
class AdventureSerializer(serializers.ModelSerializer): class AdventureImageSerializer(serializers.ModelSerializer):
class Meta:
model = AdventureImage
fields = ['id', 'image', 'adventure']
read_only_fields = ['id']
def to_representation(self, instance):
representation = super().to_representation(instance)
# Build the full URL for the image
request = self.context.get('request')
if request and instance.image:
public_url = request.build_absolute_uri(instance.image.url)
else:
public_url = f"{os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/')}/media/{instance.image.name}"
representation['image'] = public_url
return representation
class AdventureSerializer(serializers.ModelSerializer):
images = AdventureImageSerializer(many=True, read_only=True)
class Meta: class Meta:
model = Adventure model = Adventure
fields = '__all__' fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude']
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)
if instance.image:
public_url = os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/')
#print(public_url)
# remove any ' from the url
public_url = public_url.replace("'", "")
representation['image'] = f"{public_url}/media/{instance.image.name}"
return representation return representation
def validate_activity_types(self, value):
if value:
return [activity.lower() for activity in value]
return value
class TransportationSerializer(serializers.ModelSerializer): class TransportationSerializer(serializers.ModelSerializer):
class Meta: class Meta:

View file

@ -1,6 +1,6 @@
from django.urls import include, path from django.urls import include, path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet
router = DefaultRouter() router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures') router.register(r'adventures', AdventureViewSet, basename='adventures')
@ -11,6 +11,7 @@ router.register(r'activity-types', ActivityTypesView, basename='activity-types')
router.register(r'transportations', TransportationViewSet, basename='transportations') router.register(r'transportations', TransportationViewSet, basename='transportations')
router.register(r'notes', NoteViewSet, basename='notes') router.register(r'notes', NoteViewSet, basename='notes')
router.register(r'checklists', ChecklistViewSet, basename='checklists') router.register(r'checklists', ChecklistViewSet, basename='checklists')
router.register(r'images', AdventureImageViewSet, basename='images')
urlpatterns = [ urlpatterns = [

View file

@ -1,12 +1,13 @@
import uuid
import requests import requests
from django.db import transaction from django.db import transaction
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework import viewsets from rest_framework import viewsets
from django.db.models.functions import Lower from django.db.models.functions import Lower
from rest_framework.response import Response from rest_framework.response import Response
from .models import Adventure, Checklist, Collection, Transportation, Note from .models import Adventure, Checklist, Collection, Transportation, Note, AdventureImage
from worldtravel.models import VisitedRegion, Region, Country from worldtravel.models import VisitedRegion, Region, Country
from .serializers import AdventureSerializer, CollectionSerializer, NoteSerializer, TransportationSerializer, ChecklistSerializer from .serializers import AdventureImageSerializer, AdventureSerializer, CollectionSerializer, NoteSerializer, TransportationSerializer, ChecklistSerializer
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch from django.db.models import Q, Prefetch
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
@ -530,3 +531,95 @@ class ChecklistViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(user_id=self.request.user) serializer.save(user_id=self.request.user)
class AdventureImageViewSet(viewsets.ModelViewSet):
serializer_class = AdventureImageSerializer
permission_classes = [IsAuthenticated]
def dispatch(self, request, *args, **kwargs):
print(f"Method: {request.method}")
return super().dispatch(request, *args, **kwargs)
@action(detail=True, methods=['post'])
def image_delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure')
try:
adventure = Adventure.objects.get(id=adventure_id)
except Adventure.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().create(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure')
try:
adventure = Adventure.objects.get(id=adventure_id)
except Adventure.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().update(request, *args, **kwargs)
def perform_destroy(self, instance):
print("perform_destroy")
return super().perform_destroy(instance)
def destroy(self, request, *args, **kwargs):
print("destroy")
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object()
adventure = instance.adventure
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object()
adventure = instance.adventure
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return super().partial_update(request, *args, **kwargs)
@action(detail=False, methods=['GET'], url_path='(?P<adventure_id>[0-9a-f-]+)')
def adventure_images(self, request, adventure_id=None, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
try:
adventure_uuid = uuid.UUID(adventure_id)
except ValueError:
return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST)
queryset = AdventureImage.objects.filter(
Q(adventure__id=adventure_uuid) & Q(user_id=request.user)
)
serializer = self.get_serializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def get_queryset(self):
return AdventureImage.objects.filter(user_id=self.request.user)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)

View file

@ -129,6 +129,12 @@
dispatch('edit', adventure); dispatch('edit', adventure);
} }
let currentSlide = 0;
function goToSlide(index: number) {
currentSlide = index;
}
function link() { function link() {
dispatch('link', adventure); dispatch('link', adventure);
} }
@ -153,10 +159,27 @@
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-primary-content shadow-xl text-base-content" class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-primary-content shadow-xl text-base-content"
> >
<figure> <figure>
<!-- svelte-ignore a11y-img-redundant-alt --> {#if adventure.images && adventure.images.length > 0}
{#if adventure.image && adventure.image !== ''} <div class="carousel w-full">
<img src={adventure.image} alt="Adventure Image" class="w-full h-48 object-cover" /> {#each adventure.images as image, i}
<div
class="carousel-item w-full"
style="display: {i === currentSlide ? 'block' : 'none'}"
>
<img src={image.image} class="w-full h-48 object-cover" alt={adventure.name} />
<div class="flex justify-center w-full py-2 gap-2">
{#each adventure.images as _, i}
<button
on:click={() => goToSlide(i)}
class="btn btn-xs {i === currentSlide ? 'btn-active' : ''}">{i + 1}</button
>
{/each}
</div>
</div>
{/each}
</div>
{:else} {:else}
<!-- svelte-ignore a11y-img-redundant-alt -->
<img <img
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'} src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
alt="No image available" alt="No image available"

View file

@ -0,0 +1,667 @@
<script lang="ts">
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<any>) {
markers = [];
markers = [...markers, { lngLat: e.detail.lngLat, name: '', location: '', activity_type: '' }];
console.log(markers);
}
function imageSubmit() {
return async ({ result }: any) => {
if (result.type === 'success') {
if (result.data.id && result.data.image) {
adventure.images = [...adventure.images, result.data];
images = [...images, result.data];
addToast('success', 'Image uploaded');
fileInput.value = '';
console.log(adventure);
} else {
addToast('error', result.data.error || 'Failed to upload image');
}
}
};
}
async function handleSubmit(event: Event) {
event.preventDefault();
console.log(adventure);
if (adventure.id === '') {
let res = await fetch('/api/adventures', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(adventure)
});
let data = await res.json();
if (data.id) {
adventure = data as Adventure;
isDetails = false;
addToast('success', 'Adventure created');
} else {
addToast('error', 'Failed to create adventure');
}
} else {
let res = await fetch(`/api/adventures/${adventure.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(adventure)
});
let data = await res.json();
if (data.id) {
adventure = data as Adventure;
isDetails = false;
addToast('success', 'Adventure updated');
} else {
addToast('error', 'Failed to update adventure');
}
}
}
</script>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<dialog id="my_modal_1" class="modal">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<div class="modal-box w-11/12 max-w-2xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
<h3 class="font-bold text-lg">Edit {adventure.type} Adventure</h3>
{#if adventure.id === '' || isDetails}
<div class="modal-action items-center">
<form method="post" style="width: 100%;" on:submit={handleSubmit}>
<!-- Grid layout for form fields -->
<h2 class="text-2xl font-semibold mb-2">Basic Information</h2>
<!-- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3"> -->
<div>
<label for="name">Name</label><br />
<input
type="text"
id="name"
name="name"
bind:value={adventure.name}
class="input input-bordered w-full"
required
/>
</div>
<div>
<div class="form-control">
<label class="label cursor-pointer">
<span class="label-text">Visited</span>
<input
type="radio"
name="radio-10"
class="radio checked:bg-red-500"
on:click={() => (adventure.type = 'visited')}
checked={adventure.type == 'visited'}
/>
</label>
</div>
<div class="form-control">
<label class="label cursor-pointer">
<span class="label-text">Planned</span>
<input
type="radio"
name="radio-10"
class="radio checked:bg-blue-500"
on:click={() => (adventure.type = 'planned')}
checked={adventure.type == 'planned'}
/>
</label>
</div>
</div>
<div>
<label for="date">Date</label><br />
<input
type="date"
id="date"
name="date"
min={startDate || ''}
max={endDate || ''}
bind:value={adventure.date}
class="input input-bordered w-full"
/>
</div>
<div>
<!-- link -->
<div>
<label for="link">Link</label><br />
<input
type="text"
id="link"
name="link"
bind:value={adventure.link}
class="input input-bordered w-full"
/>
</div>
</div>
<div>
<label for="description">Description</label><br />
<textarea
id="description"
name="description"
bind:value={adventure.description}
class="textarea textarea-bordered w-full h-32"
></textarea>
</div>
<div>
<label for="activity_types">Activity Types</label><br />
<input
type="text"
id="activity_types"
name="activity_types"
hidden
bind:value={adventure.activity_types}
class="input input-bordered w-full"
/>
<ActivityComplete bind:activities={adventure.activity_types} />
</div>
<div>
<label for="rating"
>Rating <iconify-icon icon="mdi:star" class="text-xl -mb-1"></iconify-icon></label
><br />
<input
type="number"
min="0"
max="5"
hidden
bind:value={adventure.rating}
id="rating"
name="rating"
class="input input-bordered w-full max-w-xs mt-1"
/>
<div class="rating -ml-3 mt-1">
<input
type="radio"
name="rating-2"
class="rating-hidden"
checked={Number.isNaN(adventure.rating)}
/>
<input
type="radio"
name="rating-2"
class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 1)}
checked={adventure.rating === 1}
/>
<input
type="radio"
name="rating-2"
class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 2)}
checked={adventure.rating === 2}
/>
<input
type="radio"
name="rating-2"
class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 3)}
checked={adventure.rating === 3}
/>
<input
type="radio"
name="rating-2"
class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 4)}
checked={adventure.rating === 4}
/>
<input
type="radio"
name="rating-2"
class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 5)}
checked={adventure.rating === 5}
/>
{#if adventure.rating}
<button
type="button"
class="btn btn-sm btn-error ml-2"
on:click={() => (adventure.rating = NaN)}
>
Remove
</button>
{/if}
</div>
<div>
<div class="mt-2">
<div>
<label for="is_public"
>Public <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label
><br />
<input
type="checkbox"
class="toggle toggle-primary"
id="is_public"
name="is_public"
bind:checked={adventure.is_public}
/>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<h2 class="text-2xl font-semibold mb-2 mt-2">Location Information</h2>
<!-- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"> -->
<div>
<label for="latitude">Location</label><br />
<input
type="text"
id="location"
name="location"
bind:value={adventure.location}
class="input input-bordered w-full"
/>
</div>
<div>
<form on:submit={geocode} class="mt-2">
<input
type="text"
placeholder="Seach for a location"
class="input input-bordered w-full max-w-xs mb-2"
id="search"
name="search"
bind:value={query}
/>
<button class="btn btn-neutral -mt-1" type="submit">Search</button>
</form>
</div>
{#if places.length > 0}
<div class="mt-4 max-w-full">
<h3 class="font-bold text-lg mb-4">Search Results</h3>
<div class="flex flex-wrap">
{#each places as place}
<button
type="button"
class="btn btn-neutral mb-2 mr-2 max-w-full break-words whitespace-normal text-left"
on:click={() => {
markers = [
{
lngLat: { lng: Number(place.lon), lat: Number(place.lat) },
location: place.display_name,
name: place.name,
activity_type: place.type
}
];
}}
>
{place.display_name}
</button>
{/each}
</div>
</div>
{:else if noPlaces}
<p class="text-error text-lg">No results found</p>
{/if}
<!-- </div> -->
<div>
<MapLibre
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
standardControls
>
<!-- MapEvents gives you access to map events even from other components inside the map,
where you might not have access to the top-level `MapLibre` component. In this case
it would also work to just use on:click on the MapLibre component itself. -->
<MapEvents on:click={addMarker} />
{#each markers as marker}
<DefaultMarker lngLat={marker.lngLat} />
{/each}
</MapLibre>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">Save & Next</button>
<button type="button" class="btn" on:click={close}>Close</button>
</div>
{#if adventure.is_public}
<div class="bg-neutral p-4 mt-2 rounded-md shadow-sm">
<p class=" font-semibold">Share this Adventure!</p>
<div class="flex items-center justify-between">
<p class="text-card-foreground font-mono">
{window.location.origin}/adventures/{adventure.id}
</p>
<button
type="button"
on:click={() => {
navigator.clipboard.writeText(
`${window.location.origin}/adventures/${adventure.id}`
);
}}
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
>
Copy Link
</button>
</div>
</div>
{/if}
</form>
</div>
{:else}
<p>Upload images here</p>
<!-- <p>{adventureToEdit.id}</p> -->
<div class="mb-2">
<label for="image">Image </label><br />
<div class="flex">
<form
method="POST"
action="/adventures?/image"
use:enhance={imageSubmit}
enctype="multipart/form-data"
>
<input
type="file"
name="image"
class="file-input file-input-bordered w-full max-w-xs"
bind:this={fileInput}
accept="image/*"
id="image"
/>
<input type="hidden" name="adventure" value={adventure.id} id="adventure" />
<button class="btn btn-neutral mt-2 mb-2" type="submit">Upload Image</button>
</form>
</div>
<div class="mt-2">
<label for="url">URL</label><br />
<input
type="text"
id="url"
name="url"
bind:value={url}
class="input input-bordered w-full"
/>
<button class="btn btn-neutral mt-2" type="button" on:click={fetchImage}
>Fetch Image</button
>
</div>
<div class="mt-2">
<label for="name">Wikipedia</label><br />
<input
type="text"
id="name"
name="name"
bind:value={imageSearch}
class="input input-bordered w-full"
/>
<button class="btn btn-neutral mt-2" type="button" on:click={fetchWikiImage}
>Fetch Image</button
>
</div>
<div class="divider"></div>
{#if images.length > 0}
<h1 class="font-semibold text-xl">My Images</h1>
{:else}
<h1 class="font-semibold text-xl">No Images</h1>
{/if}
<div class="flex flex-wrap gap-2 mt-2">
{#each images as image}
<div class="relative h-32 w-32">
<button
type="button"
class="absolute top-0 left-0 btn btn-error btn-sm z-10"
on:click={() => removeImage(image.id)}
>
X
</button>
<img src={image.image} alt={image.id} class="w-full h-full object-cover" />
</div>
{/each}
</div>
</div>
<div class="mt-4">
<button type="button" class="btn btn-primary" on:click={saveAndClose}>Close</button>
</div>
{/if}
</div>
</dialog>

View file

@ -111,6 +111,11 @@
</p>{/if} </p>{/if}
<div class="card-actions justify-end"> <div class="card-actions justify-end">
{#if type == 'link'}
<button class="btn btn-primary" on:click={() => dispatch('link', collection.id)}>
<Plus class="w-5 h-5 mr-1" />
</button>
{:else}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-neutral"> <div tabindex="0" role="button" class="btn btn-neutral">
<DotsHorizontal class="w-6 h-6" /> <DotsHorizontal class="w-6 h-6" />
@ -148,13 +153,9 @@
><TrashCan class="w-6 h-6" />Delete</button ><TrashCan class="w-6 h-6" />Delete</button
> >
{/if} {/if}
{#if type == 'link'}
<button class="btn btn-primary" on:click={() => dispatch('link', collection.id)}>
<Plus class="w-5 h-5 mr-1" />
</button>
{/if}
</ul> </ul>
</div> </div>
{/if}
</div> </div>
</div> </div>
</div> </div>

View file

@ -11,7 +11,7 @@ export type User = {
export type Adventure = { export type Adventure = {
id: string; id: string;
user_id: number; user_id: number | null;
type: string; type: string;
name: string; name: string;
location?: string | null; location?: string | null;
@ -19,7 +19,10 @@ export type Adventure = {
description?: string | null; description?: string | null;
rating?: number | null; rating?: number | null;
link?: string | null; link?: string | null;
image?: string | null; images: {
id: string;
image: string;
}[];
date?: string | null; // Assuming date is a string in 'YYYY-MM-DD' format date?: string | null; // Assuming date is a string in 'YYYY-MM-DD' format
collection?: string | null; collection?: string | null;
latitude: number | null; latitude: number | null;
@ -55,6 +58,8 @@ export type Point = {
lng: number; lng: number;
}; };
name: string; name: string;
location: string;
activity_type: string;
}; };
export type Collection = { export type Collection = {

View file

@ -159,7 +159,7 @@ export const actions: Actions = {
} }
formDataToSend.append('rating', rating ? rating.toString() : ''); formDataToSend.append('rating', rating ? rating.toString() : '');
formDataToSend.append('link', link || ''); formDataToSend.append('link', link || '');
formDataToSend.append('image', image); // formDataToSend.append('image', image);
// log each key-value pair in the FormData // log each key-value pair in the FormData
for (let pair of formDataToSend.entries()) { for (let pair of formDataToSend.entries()) {
@ -233,6 +233,21 @@ export const actions: Actions = {
let image_url = new_id.image; let image_url = new_id.image;
let link_url = new_id.link; let link_url = new_id.link;
if (image && image.size > 0) {
let imageForm = new FormData();
imageForm.append('image', image);
imageForm.append('adventure', id);
let imageRes = await fetch(`${serverEndpoint}/api/images/`, {
method: 'POST',
headers: {
Cookie: `${event.cookies.get('auth')}`
},
body: imageForm
});
let data = await imageRes.json();
console.log(data);
}
return { id, user_id, image_url, link }; return { id, user_id, image_url, link };
}, },
edit: async (event) => { edit: async (event) => {
@ -410,5 +425,17 @@ export const actions: Actions = {
let image_url = adventure.image; let image_url = adventure.image;
let link_url = adventure.link; let link_url = adventure.link;
return { image_url, link_url }; return { image_url, link_url };
},
image: async (event) => {
let formData = await event.request.formData();
let res = await fetch(`${serverEndpoint}/api/images/`, {
method: 'POST',
headers: {
Cookie: `${event.cookies.get('auth')}`
},
body: formData
});
let data = await res.json();
return data;
} }
}; };

View file

@ -3,8 +3,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import AdventureCard from '$lib/components/AdventureCard.svelte'; import AdventureCard from '$lib/components/AdventureCard.svelte';
import EditAdventure from '$lib/components/EditAdventure.svelte'; import AdventureModal from '$lib/components/AdventureModal.svelte';
import NewAdventure from '$lib/components/NewAdventure.svelte';
import NotFound from '$lib/components/NotFound.svelte'; import NotFound from '$lib/components/NotFound.svelte';
import type { Adventure } from '$lib/types'; import type { Adventure } from '$lib/types';
@ -95,31 +94,31 @@
} }
} }
let adventureToEdit: Adventure; let adventureToEdit: Adventure | null = null;
let isEditModalOpen: boolean = false; let isAdventureModalOpen: boolean = false;
function deleteAdventure(event: CustomEvent<string>) { function deleteAdventure(event: CustomEvent<string>) {
adventures = adventures.filter((adventure) => adventure.id !== event.detail); adventures = adventures.filter((adventure) => adventure.id !== event.detail);
} }
function createAdventure(event: CustomEvent<Adventure>) { // function that save changes to an existing adventure or creates a new one if it doesn't exist
adventures = [event.detail, ...adventures]; function saveOrCreate(event: CustomEvent<Adventure>) {
isShowingCreateModal = false; if (adventures.find((adventure) => adventure.id === event.detail.id)) {
}
function editAdventure(event: CustomEvent<Adventure>) {
adventureToEdit = event.detail;
isEditModalOpen = true;
}
function saveEdit(event: CustomEvent<Adventure>) {
adventures = adventures.map((adventure) => { adventures = adventures.map((adventure) => {
if (adventure.id === event.detail.id) { if (adventure.id === event.detail.id) {
return event.detail; return event.detail;
} }
return adventure; return adventure;
}); });
isEditModalOpen = false; } else {
adventures = [event.detail, ...adventures];
}
isAdventureModalOpen = false;
}
function editAdventure(event: CustomEvent<Adventure>) {
adventureToEdit = event.detail;
isAdventureModalOpen = true;
} }
let sidebarOpen = false; let sidebarOpen = false;
@ -129,19 +128,11 @@
} }
</script> </script>
{#if isShowingCreateModal} {#if isAdventureModalOpen}
<NewAdventure <AdventureModal
type={newType}
on:create={createAdventure}
on:close={() => (isShowingCreateModal = false)}
/>
{/if}
{#if isEditModalOpen}
<EditAdventure
{adventureToEdit} {adventureToEdit}
on:close={() => (isEditModalOpen = false)} on:close={() => (isAdventureModalOpen = false)}
on:saveEdit={saveEdit} on:save={saveOrCreate}
/> />
{/if} {/if}
@ -160,21 +151,14 @@
<button <button
class="btn btn-primary" class="btn btn-primary"
on:click={() => { on:click={() => {
isShowingCreateModal = true; isAdventureModalOpen = true;
newType = 'visited'; newType = 'visited';
adventureToEdit = null;
}} }}
> >
Visited Adventure</button Adventure</button
>
<button
class="btn btn-primary"
on:click={() => {
isShowingCreateModal = true;
newType = 'planned';
}}
>
Planned Adventure</button
> >
<!-- <button <!-- <button
class="btn btn-primary" class="btn btn-primary"
on:click={() => (isShowingNewTrip = true)}>Trip Planner</button on:click={() => (isShowingNewTrip = true)}>Trip Planner</button

View file

@ -26,11 +26,18 @@
let adventure: Adventure; let adventure: Adventure;
let currentSlide = 0;
function goToSlide(index: number) {
currentSlide = index;
}
let notFound: boolean = false; let notFound: boolean = false;
let isEditModalOpen: boolean = false; let isEditModalOpen: boolean = false;
import ClipboardList from '~icons/mdi/clipboard-list'; import ClipboardList from '~icons/mdi/clipboard-list';
import EditAdventure from '$lib/components/EditAdventure.svelte'; import EditAdventure from '$lib/components/AdventureModal.svelte';
import AdventureModal from '$lib/components/AdventureModal.svelte';
onMount(() => { onMount(() => {
if (data.props.adventure) { if (data.props.adventure) {
@ -69,10 +76,10 @@
{/if} {/if}
{#if isEditModalOpen} {#if isEditModalOpen}
<EditAdventure <AdventureModal
adventureToEdit={adventure} adventureToEdit={adventure}
on:close={() => (isEditModalOpen = false)} on:close={() => (isEditModalOpen = false)}
on:saveEdit={saveEdit} on:save={saveEdit}
/> />
{/if} {/if}
@ -92,16 +99,31 @@
<main class="flex-1"> <main class="flex-1">
<div class="max-w-5xl mx-auto p-4 md:p-6 lg:p-8"> <div class="max-w-5xl mx-auto p-4 md:p-6 lg:p-8">
<div class="grid gap-8"> <div class="grid gap-8">
{#if adventure.image} {#if adventure.images && adventure.images.length > 0}
<div> <div class="carousel w-full">
{#each adventure.images as image, i}
<div
class="carousel-item w-full"
style="display: {i === currentSlide ? 'block' : 'none'}"
>
<img <img
src={adventure.image} src={image.image}
alt={adventure.name}
width="1200" width="1200"
height="600" height="600"
class="w-full h-auto object-cover rounded-lg" class="w-full h-auto object-cover rounded-lg"
style="aspect-ratio: 1200 / 600; object-fit: cover;" style="aspect-ratio: 1200 / 600; object-fit: cover;"
alt={adventure.name}
/> />
<div class="flex justify-center w-full py-2 gap-2">
{#each adventure.images as _, i}
<button
on:click={() => goToSlide(i)}
class="btn btn-xs {i === currentSlide ? 'btn-active' : ''}">{i + 1}</button
>
{/each}
</div>
</div>
{/each}
</div> </div>
{/if} {/if}
<div class="grid gap-4"> <div class="grid gap-4">
@ -109,7 +131,6 @@
<div> <div>
<h1 class="text-4xl mt-2 font-bold">{adventure.name}</h1> <h1 class="text-4xl mt-2 font-bold">{adventure.name}</h1>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
{#if adventure.rating !== undefined && adventure.rating !== null} {#if adventure.rating !== undefined && adventure.rating !== null}
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">

View file

@ -1,14 +1,11 @@
<script lang="ts"> <script lang="ts">
import { enhance, deserialize } from '$app/forms'; import { enhance } from '$app/forms';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import AdventureCard from '$lib/components/AdventureCard.svelte';
import CollectionCard from '$lib/components/CollectionCard.svelte'; import CollectionCard from '$lib/components/CollectionCard.svelte';
import EditAdventure from '$lib/components/EditAdventure.svelte';
import EditCollection from '$lib/components/EditCollection.svelte'; import EditCollection from '$lib/components/EditCollection.svelte';
import NewAdventure from '$lib/components/NewAdventure.svelte';
import NewCollection from '$lib/components/NewCollection.svelte'; import NewCollection from '$lib/components/NewCollection.svelte';
import NotFound from '$lib/components/NotFound.svelte'; import NotFound from '$lib/components/NotFound.svelte';
import type { Adventure, Collection } from '$lib/types'; import type { Collection } from '$lib/types';
import Plus from '~icons/mdi/plus'; import Plus from '~icons/mdi/plus';

View file

@ -8,9 +8,7 @@
import Plus from '~icons/mdi/plus'; import Plus from '~icons/mdi/plus';
import AdventureCard from '$lib/components/AdventureCard.svelte'; import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureLink from '$lib/components/AdventureLink.svelte'; import AdventureLink from '$lib/components/AdventureLink.svelte';
import EditAdventure from '$lib/components/EditAdventure.svelte';
import NotFound from '$lib/components/NotFound.svelte'; import NotFound from '$lib/components/NotFound.svelte';
import NewAdventure from '$lib/components/NewAdventure.svelte';
import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre'; import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre';
import TransportationCard from '$lib/components/TransportationCard.svelte'; import TransportationCard from '$lib/components/TransportationCard.svelte';
import EditTransportation from '$lib/components/EditTransportation.svelte'; import EditTransportation from '$lib/components/EditTransportation.svelte';
@ -26,6 +24,7 @@
} from '$lib'; } from '$lib';
import ChecklistCard from '$lib/components/ChecklistCard.svelte'; import ChecklistCard from '$lib/components/ChecklistCard.svelte';
import ChecklistModal from '$lib/components/ChecklistModal.svelte'; import ChecklistModal from '$lib/components/ChecklistModal.svelte';
import AdventureModal from '$lib/components/AdventureModal.svelte';
export let data: PageData; export let data: PageData;
console.log(data); console.log(data);
@ -83,11 +82,6 @@
adventures = adventures.filter((a) => a.id !== event.detail); adventures = adventures.filter((a) => a.id !== event.detail);
} }
function createAdventure(event: CustomEvent<Adventure>) {
adventures = [event.detail, ...adventures];
isShowingCreateModal = false;
}
async function addAdventure(event: CustomEvent<Adventure>) { async function addAdventure(event: CustomEvent<Adventure>) {
console.log(event.detail); console.log(event.detail);
if (adventures.find((a) => a.id === event.detail.id)) { if (adventures.find((a) => a.id === event.detail.id)) {
@ -124,9 +118,9 @@
}); });
} }
let adventureToEdit: Adventure; let adventureToEdit: Adventure | null = null;
let transportationToEdit: Transportation; let transportationToEdit: Transportation;
let isEditModalOpen: boolean = false; let isAdventureModalOpen: boolean = false;
let isTransportationEditModalOpen: boolean = false; let isTransportationEditModalOpen: boolean = false;
let isNoteModalOpen: boolean = false; let isNoteModalOpen: boolean = false;
let noteToEdit: Note | null; let noteToEdit: Note | null;
@ -136,7 +130,7 @@
function editAdventure(event: CustomEvent<Adventure>) { function editAdventure(event: CustomEvent<Adventure>) {
adventureToEdit = event.detail; adventureToEdit = event.detail;
isEditModalOpen = true; isAdventureModalOpen = true;
} }
function saveNewTransportation(event: CustomEvent<Transportation>) { function saveNewTransportation(event: CustomEvent<Transportation>) {
@ -149,14 +143,18 @@
isTransportationEditModalOpen = false; isTransportationEditModalOpen = false;
} }
function saveEdit(event: CustomEvent<Adventure>) { function saveOrCreate(event: CustomEvent<Adventure>) {
if (adventures.find((adventure) => adventure.id === event.detail.id)) {
adventures = adventures.map((adventure) => { adventures = adventures.map((adventure) => {
if (adventure.id === event.detail.id) { if (adventure.id === event.detail.id) {
return event.detail; return event.detail;
} }
return adventure; return adventure;
}); });
isEditModalOpen = false; } else {
adventures = [event.detail, ...adventures];
}
isAdventureModalOpen = false;
} }
</script> </script>
@ -180,13 +178,12 @@
/> />
{/if} {/if}
{#if isEditModalOpen} {#if isAdventureModalOpen}
<EditAdventure <AdventureModal
{adventureToEdit} {adventureToEdit}
on:close={() => (isEditModalOpen = false)} on:close={() => (isAdventureModalOpen = false)}
on:saveEdit={saveEdit} on:save={saveOrCreate}
startDate={collection.start_date} collection_id={collection.id}
endDate={collection.end_date}
/> />
{/if} {/if}
@ -235,17 +232,6 @@
/> />
{/if} {/if}
{#if isShowingCreateModal}
<NewAdventure
type={newType}
collection_id={collection.id}
on:create={createAdventure}
on:close={() => (isShowingCreateModal = false)}
startDate={collection.start_date}
endDate={collection.end_date}
/>
{/if}
{#if isShowingTransportationModal} {#if isShowingTransportationModal}
<NewTransportation <NewTransportation
on:close={() => (isShowingTransportationModal = false)} on:close={() => (isShowingTransportationModal = false)}
@ -312,21 +298,13 @@
<button <button
class="btn btn-primary" class="btn btn-primary"
on:click={() => { on:click={() => {
isShowingCreateModal = true; isAdventureModalOpen = true;
newType = 'visited'; adventureToEdit = null;
}} }}
> >
Visited Adventure</button Adventure</button
>
<button
class="btn btn-primary"
on:click={() => {
isShowingCreateModal = true;
newType = 'planned';
}}
>
Planned Adventure</button
> >
<button <button
class="btn btn-primary" class="btn btn-primary"
on:click={() => { on:click={() => {

View file

@ -1,22 +1,14 @@
<script lang="ts"> <script lang="ts">
import { enhance, deserialize } from '$app/forms';
import AdventureCard from '$lib/components/AdventureCard.svelte';
import CollectionCard from '$lib/components/CollectionCard.svelte'; import CollectionCard from '$lib/components/CollectionCard.svelte';
import EditAdventure from '$lib/components/EditAdventure.svelte';
import EditCollection from '$lib/components/EditCollection.svelte';
import NewAdventure from '$lib/components/NewAdventure.svelte';
import NewCollection from '$lib/components/NewCollection.svelte';
import NotFound from '$lib/components/NotFound.svelte'; import NotFound from '$lib/components/NotFound.svelte';
import type { Adventure, Collection } from '$lib/types'; import type { Collection } from '$lib/types';
import Plus from '~icons/mdi/plus';
export let data: any; export let data: any;
console.log(data); console.log(data);
let collections: Collection[] = data.props.adventures || []; let collections: Collection[] = data.props.adventures || [];
function deleteCollection(event: CustomEvent<number>) { function deleteCollection(event: CustomEvent<string>) {
collections = collections.filter((collection) => collection.id !== event.detail); collections = collections.filter((collection) => collection.id !== event.detail);
} }
</script> </script>

View file

@ -1,7 +1,6 @@
<script> <script>
// @ts-nocheck // @ts-nocheck
import AdventureModal from '$lib/components/AdventureModal.svelte';
import NewAdventure from '$lib/components/NewAdventure.svelte';
import { import {
DefaultMarker, DefaultMarker,
MapEvents, MapEvents,
@ -142,11 +141,11 @@
</div> </div>
{#if createModalOpen} {#if createModalOpen}
<NewAdventure <AdventureModal
on:close={() => (createModalOpen = false)} on:close={() => (createModalOpen = false)}
longitude={newLongitude} on:save={createNewAdventure}
latitude={newLatitude} latitude={newLatitude}
on:create={createNewAdventure} longitude={newLongitude}
/> />
{/if} {/if}

View file

@ -4,9 +4,10 @@
import type { Adventure, OpenStreetMapPlace } from '$lib/types'; import type { Adventure, OpenStreetMapPlace } from '$lib/types';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import EditAdventure from '$lib/components/EditAdventure.svelte'; import EditAdventure from '$lib/components/AdventureModal.svelte';
import { appVersion } from '$lib/config'; import { appVersion } from '$lib/config';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import AdventureModal from '$lib/components/AdventureModal.svelte';
export let data: PageData; export let data: PageData;
@ -59,29 +60,31 @@
} }
let adventureToEdit: Adventure; let adventureToEdit: Adventure;
let isEditModalOpen: boolean = false; let isAdventureModalOpen: boolean = false;
function editAdventure(event: CustomEvent<Adventure>) { function editAdventure(event: CustomEvent<Adventure>) {
adventureToEdit = event.detail; adventureToEdit = event.detail;
isEditModalOpen = true; isAdventureModalOpen = true;
} }
function saveEdit(event: CustomEvent<Adventure>) { function saveEdit(event: CustomEvent<Adventure>) {
console.log(event.detail);
myAdventures = myAdventures.map((adventure) => { myAdventures = myAdventures.map((adventure) => {
if (adventure.id === event.detail.id) { if (adventure.id === event.detail.id) {
return event.detail; return event.detail;
} }
return adventure; return adventure;
}); });
isEditModalOpen = false; isAdventureModalOpen = false;
console.log(myAdventures);
} }
</script> </script>
{#if isEditModalOpen} {#if isAdventureModalOpen}
<EditAdventure <AdventureModal
{adventureToEdit} {adventureToEdit}
on:close={() => (isEditModalOpen = false)} on:close={() => (isAdventureModalOpen = false)}
on:saveEdit={saveEdit} on:save={filterByProperty}
/> />
{/if} {/if}