1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-22 14:29:36 +02:00

feat: add primary image functionality to AdventureImage model and update related components

This commit is contained in:
Sean Morley 2025-01-02 23:25:58 -05:00
parent 991efa8d08
commit c6fa603a93
8 changed files with 112 additions and 5 deletions

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2025-01-03 04:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0016_alter_adventureimage_image'),
]
operations = [
migrations.AddField(
model_name='adventureimage',
name='is_primary',
field=models.BooleanField(default=False),
),
]

View file

@ -280,6 +280,7 @@ class AdventureImage(models.Model):
upload_to=PathAndRename('images/') # Use the callable class here
)
adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE)
is_primary = models.BooleanField(default=False)
def __str__(self):
return self.image.url

View file

@ -8,7 +8,7 @@ from main.utils import CustomModelSerializer
class AdventureImageSerializer(CustomModelSerializer):
class Meta:
model = AdventureImage
fields = ['id', 'image', 'adventure']
fields = ['id', 'image', 'adventure', 'is_primary']
read_only_fields = ['id']
def to_representation(self, instance):

View file

@ -1033,6 +1033,28 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
def image_delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
@action(detail=True, methods=['post'])
def toggle_primary(self, request, *args, **kwargs):
# Makes the image the primary image for the adventure, if there is already a primary image linked to the adventure, it is set to false and the new image is set to true. make sure that the permission is set to the owner of the adventure
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)
# Check if the image is already the primary image
if instance.is_primary:
return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST)
# Set the current primary image to false
AdventureImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False)
# Set the new image to true
instance.is_primary = True
instance.save()
return Response({"success": "Image set as primary image"})
def create(self, request, *args, **kwargs):
if not request.user.is_authenticated:

View file

@ -21,7 +21,7 @@
let query: string = '';
let places: OpenStreetMapPlace[] = [];
let images: { id: string; image: string }[] = [];
let images: { id: string; image: string; is_primary: boolean }[] = [];
let warningMessage: string = '';
let constrainDates: boolean = false;
@ -34,6 +34,9 @@
import MarkdownEditor from './MarkdownEditor.svelte';
import ImmichSelect from './ImmichSelect.svelte';
import Star from '~icons/mdi/star';
import Crown from '~icons/mdi/crown';
let wikiError: string = '';
let noPlaces: boolean = false;
@ -179,6 +182,25 @@
}
}
async function makePrimaryImage(image_id: string) {
let res = await fetch(`/api/images/${image_id}/toggle_primary`, {
method: 'POST'
});
if (res.ok) {
images = images.map((image) => {
if (image.id === image_id) {
image.is_primary = true;
} else {
image.is_primary = false;
}
return image;
});
adventure.images = images;
} else {
console.error('Error in makePrimaryImage:', res);
}
}
async function fetchImage() {
try {
let res = await fetch(url);
@ -208,7 +230,8 @@
// Assuming the first object in the array is the new image
let newImage = {
id: rawData[1],
image: rawData[2] // This is the URL for the image
image: rawData[2], // This is the URL for the image
is_primary: false
};
console.log('New Image:', newImage);
@ -249,7 +272,7 @@
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 };
let newImage = { id: newData.data.id, image: newData.data.image, is_primary: false };
console.log(newImage);
images = [...images, newImage];
adventure.images = images;
@ -1038,6 +1061,21 @@ it would also work to just use on:click on the MapLibre component itself. -->
>
</button>
{#if !image.is_primary}
<button
type="button"
class="absolute top-1 left-1 btn btn-success btn-xs z-10"
on:click={() => makePrimaryImage(image.id)}
>
<Star class="h-4 w-4" />
</button>
{:else}
<!-- crown icon -->
<div class="absolute top-1 left-1 bg-warning text-white rounded-full p-1 z-10">
<Crown class="h-4 w-4" />
</div>
{/if}
<img
src={image.image}
alt={image.id}

View file

@ -9,7 +9,11 @@
let image_url: string | null = null;
$: adventure_images = adventures.flatMap((adventure) =>
adventure.images.map((image) => ({ image: image.image, adventure: adventure }))
adventure.images.map((image) => ({
image: image.image,
adventure: adventure,
is_primary: image.is_primary
}))
);
$: {
@ -18,6 +22,19 @@
}
}
$: {
// sort so that any image in adventure_images .is_primary is first
adventure_images.sort((a, b) => {
if (a.is_primary && !b.is_primary) {
return -1;
} else if (!a.is_primary && b.is_primary) {
return 1;
} else {
return 0;
}
});
}
function changeSlide(direction: string) {
if (direction === 'next' && currentSlide < adventure_images.length - 1) {
currentSlide = currentSlide + 1;

View file

@ -24,6 +24,7 @@ export type Adventure = {
images: {
id: string;
image: string;
is_primary: boolean;
}[];
visits: {
id: string;

View file

@ -34,6 +34,16 @@
onMount(() => {
if (data.props.adventure) {
adventure = data.props.adventure;
// sort so that any image in adventure_images .is_primary is first
adventure.images.sort((a, b) => {
if (a.is_primary && !b.is_primary) {
return -1;
} else if (!a.is_primary && b.is_primary) {
return 1;
} else {
return 0;
}
});
} else {
notFound = true;
}