Collection Speed Improvements (#874)
* Add UltraSlimCollectionSerializer and update CollectionViewSet for optimized listing - Introduced UltraSlimCollectionSerializer for efficient data representation. - Updated CollectionViewSet to use the new serializer for list actions. - Enhanced queryset optimizations with prefetching for related images. - Modified frontend components to support SlimCollection type for better performance. * Optimize rendering of collection cards by adding a unique key to the each block
This commit is contained in:
@@ -656,3 +656,48 @@ class CollectionInviteSerializer(serializers.ModelSerializer):
|
|||||||
model = CollectionInvite
|
model = CollectionInvite
|
||||||
fields = ['id', 'collection', 'created_at', 'name', 'collection_owner_username', 'collection_user_first_name', 'collection_user_last_name']
|
fields = ['id', 'collection', 'created_at', 'name', 'collection_owner_username', 'collection_user_first_name', 'collection_user_last_name']
|
||||||
read_only_fields = ['id', 'created_at']
|
read_only_fields = ['id', 'created_at']
|
||||||
|
|
||||||
|
class UltraSlimCollectionSerializer(serializers.ModelSerializer):
|
||||||
|
location_images = serializers.SerializerMethodField()
|
||||||
|
location_count = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Collection
|
||||||
|
fields = [
|
||||||
|
'id', 'user', 'name', 'description', 'is_public', 'start_date', 'end_date',
|
||||||
|
'is_archived', 'link', 'created_at', 'updated_at', 'location_images',
|
||||||
|
'location_count', 'shared_with'
|
||||||
|
]
|
||||||
|
read_only_fields = fields # All fields are read-only for listing
|
||||||
|
|
||||||
|
def get_location_images(self, obj):
|
||||||
|
"""Get primary images from locations in this collection, optimized with select_related"""
|
||||||
|
# Filter first, then slice (removed slicing)
|
||||||
|
images = ContentImage.objects.filter(
|
||||||
|
location__collections=obj
|
||||||
|
).select_related('user').prefetch_related('location')
|
||||||
|
|
||||||
|
return ContentImageSerializer(
|
||||||
|
images,
|
||||||
|
many=True,
|
||||||
|
context={'request': self.context.get('request')}
|
||||||
|
).data
|
||||||
|
|
||||||
|
def get_location_count(self, obj):
|
||||||
|
"""Get count of locations in this collection"""
|
||||||
|
# This uses the cached count if available, or does a simple count query
|
||||||
|
return obj.locations.count()
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
representation = super().to_representation(instance)
|
||||||
|
|
||||||
|
# make it show the uuid instead of the pk for the user
|
||||||
|
representation['user'] = str(instance.user.uuid)
|
||||||
|
|
||||||
|
# Make it display the user uuid for the shared users instead of the PK
|
||||||
|
shared_uuids = []
|
||||||
|
for user in instance.shared_with.all():
|
||||||
|
shared_uuids.append(str(user.uuid))
|
||||||
|
representation['shared_with'] = shared_uuids
|
||||||
|
return representation
|
||||||
|
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
from django.db.models import Q
|
from django.db.models import Q, Prefetch
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from adventures.models import Collection, Location, Transportation, Note, Checklist, CollectionInvite
|
from adventures.models import Collection, Location, Transportation, Note, Checklist, CollectionInvite, ContentImage
|
||||||
from adventures.permissions import CollectionShared
|
from adventures.permissions import CollectionShared
|
||||||
from adventures.serializers import CollectionSerializer, CollectionInviteSerializer
|
from adventures.serializers import CollectionSerializer, CollectionInviteSerializer, UltraSlimCollectionSerializer
|
||||||
from users.models import CustomUser as User
|
from users.models import CustomUser as User
|
||||||
from adventures.utils import pagination
|
from adventures.utils import pagination
|
||||||
from users.serializers import CustomUserDetailsSerializer as UserSerializer
|
from users.serializers import CustomUserDetailsSerializer as UserSerializer
|
||||||
|
|
||||||
|
|
||||||
class CollectionViewSet(viewsets.ModelViewSet):
|
class CollectionViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = CollectionSerializer
|
serializer_class = CollectionSerializer
|
||||||
permission_classes = [CollectionShared]
|
permission_classes = [CollectionShared]
|
||||||
pagination_class = pagination.StandardResultsSetPagination
|
pagination_class = pagination.StandardResultsSetPagination
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
"""Return different serializers based on the action"""
|
||||||
|
if self.action in ['list', 'all', 'archived', 'shared']:
|
||||||
|
return UltraSlimCollectionSerializer
|
||||||
|
return CollectionSerializer
|
||||||
|
|
||||||
def apply_sorting(self, queryset):
|
def apply_sorting(self, queryset):
|
||||||
order_by = self.request.query_params.get('order_by', 'name')
|
order_by = self.request.query_params.get('order_by', 'name')
|
||||||
order_direction = self.request.query_params.get('order_direction', 'asc')
|
order_direction = self.request.query_params.get('order_direction', 'asc')
|
||||||
@@ -51,30 +58,89 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
"""Override to add nested and exclusion contexts based on query parameters"""
|
"""Override to add nested and exclusion contexts based on query parameters"""
|
||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
|
|
||||||
# Handle nested parameter
|
# Handle nested parameter (only for full serializer actions)
|
||||||
is_nested = self.request.query_params.get('nested', 'false').lower() == 'true'
|
if self.action not in ['list', 'all', 'archived', 'shared']:
|
||||||
if is_nested:
|
is_nested = self.request.query_params.get('nested', 'false').lower() == 'true'
|
||||||
context['nested'] = True
|
if is_nested:
|
||||||
|
context['nested'] = True
|
||||||
|
|
||||||
# Handle individual exclusion parameters (if using granular approach)
|
# Handle individual exclusion parameters (if using granular approach)
|
||||||
exclude_params = [
|
exclude_params = [
|
||||||
'exclude_transportations',
|
'exclude_transportations',
|
||||||
'exclude_notes',
|
'exclude_notes',
|
||||||
'exclude_checklists',
|
'exclude_checklists',
|
||||||
'exclude_lodging'
|
'exclude_lodging'
|
||||||
]
|
]
|
||||||
|
|
||||||
for param in exclude_params:
|
for param in exclude_params:
|
||||||
if self.request.query_params.get(param, 'false').lower() == 'true':
|
if self.request.query_params.get(param, 'false').lower() == 'true':
|
||||||
context[param] = True
|
context[param] = True
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get_optimized_queryset_for_listing(self):
|
||||||
|
"""Get optimized queryset for list actions with prefetching"""
|
||||||
|
return self.get_base_queryset().select_related('user').prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'locations__images',
|
||||||
|
queryset=ContentImage.objects.filter(is_primary=True).select_related('user'),
|
||||||
|
to_attr='primary_images'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_base_queryset(self):
|
||||||
|
"""Base queryset logic extracted for reuse"""
|
||||||
|
if self.action == 'destroy':
|
||||||
|
return Collection.objects.filter(user=self.request.user.id)
|
||||||
|
|
||||||
|
if self.action in ['update', 'partial_update']:
|
||||||
|
return Collection.objects.filter(
|
||||||
|
Q(user=self.request.user.id) | Q(shared_with=self.request.user)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
# Allow access to collections with pending invites for accept/decline actions
|
||||||
|
if self.action in ['accept_invite', 'decline_invite']:
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return Collection.objects.none()
|
||||||
|
return Collection.objects.filter(
|
||||||
|
Q(user=self.request.user.id) |
|
||||||
|
Q(shared_with=self.request.user) |
|
||||||
|
Q(invites__invited_user=self.request.user)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
if self.action == 'retrieve':
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return Collection.objects.filter(is_public=True)
|
||||||
|
return Collection.objects.filter(
|
||||||
|
Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
# For list action, include collections owned by the user or shared with the user, that are not archived
|
||||||
|
return Collection.objects.filter(
|
||||||
|
(Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""Get queryset with optimizations for list actions"""
|
||||||
|
if self.action in ['list', 'all', 'archived', 'shared']:
|
||||||
|
return self.get_optimized_queryset_for_listing()
|
||||||
|
return self.get_base_queryset()
|
||||||
|
|
||||||
def list(self, request):
|
def list(self, request):
|
||||||
# make sure the user is authenticated
|
# make sure the user is authenticated
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
queryset = Collection.objects.filter(user=request.user, is_archived=False)
|
|
||||||
|
queryset = Collection.objects.filter(
|
||||||
|
(Q(user=request.user.id) | Q(shared_with=request.user)) & Q(is_archived=False)
|
||||||
|
).distinct().select_related('user').prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'locations__images',
|
||||||
|
queryset=ContentImage.objects.filter(is_primary=True).select_related('user'),
|
||||||
|
to_attr='primary_images'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
return self.paginate_and_respond(queryset, request)
|
return self.paginate_and_respond(queryset, request)
|
||||||
|
|
||||||
@@ -85,6 +151,12 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
queryset = Collection.objects.filter(
|
queryset = Collection.objects.filter(
|
||||||
Q(user=request.user)
|
Q(user=request.user)
|
||||||
|
).select_related('user').prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'locations__images',
|
||||||
|
queryset=ContentImage.objects.filter(is_primary=True).select_related('user'),
|
||||||
|
to_attr='primary_images'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
@@ -99,6 +171,12 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
queryset = Collection.objects.filter(
|
queryset = Collection.objects.filter(
|
||||||
Q(user=request.user.id) & Q(is_archived=True)
|
Q(user=request.user.id) & Q(is_archived=True)
|
||||||
|
).select_related('user').prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'locations__images',
|
||||||
|
queryset=ContentImage.objects.filter(is_primary=True).select_related('user'),
|
||||||
|
to_attr='primary_images'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
@@ -173,9 +251,17 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
def shared(self, request):
|
def shared(self, request):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return Response({"error": "User is not authenticated"}, status=400)
|
return Response({"error": "User is not authenticated"}, status=400)
|
||||||
|
|
||||||
queryset = Collection.objects.filter(
|
queryset = Collection.objects.filter(
|
||||||
shared_with=request.user
|
shared_with=request.user
|
||||||
|
).select_related('user').prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
'locations__images',
|
||||||
|
queryset=ContentImage.objects.filter(is_primary=True).select_related('user'),
|
||||||
|
to_attr='primary_images'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
@@ -222,8 +308,6 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
# Add these methods to your CollectionViewSet class
|
|
||||||
|
|
||||||
@action(detail=True, methods=['post'], url_path='revoke-invite/(?P<uuid>[^/.]+)')
|
@action(detail=True, methods=['post'], url_path='revoke-invite/(?P<uuid>[^/.]+)')
|
||||||
def revoke_invite(self, request, pk=None, uuid=None):
|
def revoke_invite(self, request, pk=None, uuid=None):
|
||||||
"""Revoke a pending invite for a collection"""
|
"""Revoke a pending invite for a collection"""
|
||||||
@@ -393,37 +477,6 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
return Response({"success": success_message})
|
return Response({"success": success_message})
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
if self.action == 'destroy':
|
|
||||||
return Collection.objects.filter(user=self.request.user.id)
|
|
||||||
|
|
||||||
if self.action in ['update', 'partial_update']:
|
|
||||||
return Collection.objects.filter(
|
|
||||||
Q(user=self.request.user.id) | Q(shared_with=self.request.user)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
# Allow access to collections with pending invites for accept/decline actions
|
|
||||||
if self.action in ['accept_invite', 'decline_invite']:
|
|
||||||
if not self.request.user.is_authenticated:
|
|
||||||
return Collection.objects.none()
|
|
||||||
return Collection.objects.filter(
|
|
||||||
Q(user=self.request.user.id) |
|
|
||||||
Q(shared_with=self.request.user) |
|
|
||||||
Q(invites__invited_user=self.request.user)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
if self.action == 'retrieve':
|
|
||||||
if not self.request.user.is_authenticated:
|
|
||||||
return Collection.objects.filter(is_public=True)
|
|
||||||
return Collection.objects.filter(
|
|
||||||
Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
# For list action, include collections owned by the user or shared with the user, that are not archived
|
|
||||||
return Collection.objects.filter(
|
|
||||||
(Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
# This is ok because you cannot share a collection when creating it
|
# This is ok because you cannot share a collection when creating it
|
||||||
serializer.save(user=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
@@ -436,12 +489,3 @@ class CollectionViewSet(viewsets.ModelViewSet):
|
|||||||
return paginator.get_paginated_response(serializer.data)
|
return paginator.get_paginated_response(serializer.data)
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
|
||||||
# Add nested=True to serializer context for GET list requests
|
|
||||||
context = self.get_serializer_context()
|
|
||||||
# If this is a list action, make sure nested=True in context
|
|
||||||
if self.action == 'list':
|
|
||||||
context['nested'] = True
|
|
||||||
kwargs['context'] = context
|
|
||||||
return super().get_serializer(*args, **kwargs)
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import ShareVariant from '~icons/mdi/share-variant';
|
import ShareVariant from '~icons/mdi/share-variant';
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import type { Location, Collection, User } from '$lib/types';
|
import type { Location, Collection, User, SlimCollection, ContentImage } from '$lib/types';
|
||||||
import { addToast } from '$lib/toasts';
|
import { addToast } from '$lib/toasts';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
@@ -55,7 +55,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let collection: Collection;
|
export let collection: Collection | SlimCollection;
|
||||||
|
|
||||||
|
let location_images: ContentImage[] = [];
|
||||||
|
if ('location_images' in collection) {
|
||||||
|
location_images = collection.location_images;
|
||||||
|
} else {
|
||||||
|
location_images = collection.locations.flatMap((location: Location) => location.images);
|
||||||
|
}
|
||||||
|
|
||||||
|
let locationLength: number = 0;
|
||||||
|
if ('location_count' in collection) {
|
||||||
|
locationLength = collection.location_count;
|
||||||
|
} else {
|
||||||
|
locationLength = collection.locations.length;
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteCollection() {
|
async function deleteCollection() {
|
||||||
let res = await fetch(`/api/collections/${collection.id}`, {
|
let res = await fetch(`/api/collections/${collection.id}`, {
|
||||||
@@ -92,11 +106,7 @@
|
|||||||
>
|
>
|
||||||
<!-- Image Carousel -->
|
<!-- Image Carousel -->
|
||||||
<div class="relative overflow-hidden rounded-t-2xl">
|
<div class="relative overflow-hidden rounded-t-2xl">
|
||||||
<CardCarousel
|
<CardCarousel images={location_images} name={collection.name} icon="📚" />
|
||||||
images={collection.locations.flatMap((location) => location.images)}
|
|
||||||
name={collection.name}
|
|
||||||
icon="📚"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Badge Overlay -->
|
<!-- Badge Overlay -->
|
||||||
<div class="absolute top-4 left-4 flex flex-col gap-2">
|
<div class="absolute top-4 left-4 flex flex-col gap-2">
|
||||||
@@ -124,7 +134,7 @@
|
|||||||
|
|
||||||
<!-- Adventure Count -->
|
<!-- Adventure Count -->
|
||||||
<p class="text-sm text-base-content/70">
|
<p class="text-sm text-base-content/70">
|
||||||
{collection.locations.length}
|
{locationLength}
|
||||||
{$t('locations.locations')}
|
{$t('locations.locations')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Collection, User } from '$lib/types';
|
import type { Collection, SlimCollection, User } from '$lib/types';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
import Share from '~icons/mdi/share';
|
import Share from '~icons/mdi/share';
|
||||||
import Clear from '~icons/mdi/close';
|
import Clear from '~icons/mdi/close';
|
||||||
|
|
||||||
export let collection: Collection;
|
export let collection: SlimCollection | Collection;
|
||||||
|
|
||||||
// Extended user interface to include status
|
// Extended user interface to include status
|
||||||
interface UserWithStatus extends User {
|
interface UserWithStatus extends User {
|
||||||
@@ -160,6 +160,7 @@
|
|||||||
|
|
||||||
<dialog id="my_modal_1" class="modal">
|
<dialog id="my_modal_1" class="modal">
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
<div
|
<div
|
||||||
class="modal-box w-11/12 max-w-5xl p-6 space-y-6"
|
class="modal-box w-11/12 max-w-5xl p-6 space-y-6"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
|
|||||||
@@ -137,6 +137,23 @@ export type Collection = {
|
|||||||
link?: string | null;
|
link?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SlimCollection = {
|
||||||
|
id: string;
|
||||||
|
user: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
is_public: boolean;
|
||||||
|
start_date: string | null;
|
||||||
|
end_date: string | null;
|
||||||
|
is_archived: boolean;
|
||||||
|
link: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
location_images: ContentImage[];
|
||||||
|
location_count: number;
|
||||||
|
shared_with: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type GeocodeSearchResult = {
|
export type GeocodeSearchResult = {
|
||||||
lat?: string;
|
lat?: string;
|
||||||
lon?: string;
|
lon?: string;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
||||||
import type { Location, Collection } from '$lib/types';
|
import type { Location, Collection, SlimCollection } from '$lib/types';
|
||||||
|
|
||||||
import type { Actions } from '@sveltejs/kit';
|
import type { Actions } from '@sveltejs/kit';
|
||||||
import { fetchCSRFToken } from '$lib/index.server';
|
import { fetchCSRFToken } from '$lib/index.server';
|
||||||
@@ -62,11 +62,11 @@ export const load = (async (event) => {
|
|||||||
next: collectionsData.next,
|
next: collectionsData.next,
|
||||||
previous: collectionsData.previous,
|
previous: collectionsData.previous,
|
||||||
count: collectionsData.count,
|
count: collectionsData.count,
|
||||||
sharedCollections: sharedData as Collection[],
|
sharedCollections: sharedData as SlimCollection[],
|
||||||
currentPage,
|
currentPage,
|
||||||
order_by,
|
order_by,
|
||||||
order_direction,
|
order_direction,
|
||||||
archivedCollections: archivedData as Collection[],
|
archivedCollections: archivedData as SlimCollection[],
|
||||||
invites: invitesData
|
invites: invitesData
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import CollectionLink from '$lib/components/CollectionLink.svelte';
|
import CollectionLink from '$lib/components/CollectionLink.svelte';
|
||||||
import CollectionModal from '$lib/components/CollectionModal.svelte';
|
import CollectionModal from '$lib/components/CollectionModal.svelte';
|
||||||
import NotFound from '$lib/components/NotFound.svelte';
|
import NotFound from '$lib/components/NotFound.svelte';
|
||||||
import type { Collection, CollectionInvite } from '$lib/types';
|
import type { Collection, CollectionInvite, SlimCollection } from '$lib/types';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
import Plus from '~icons/mdi/plus';
|
import Plus from '~icons/mdi/plus';
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
export let data: any;
|
export let data: any;
|
||||||
console.log('Collections page data:', data);
|
console.log('Collections page data:', data);
|
||||||
|
|
||||||
let collections: Collection[] = data.props.adventures || [];
|
let collections: SlimCollection[] = data.props.adventures || [];
|
||||||
let sharedCollections: Collection[] = data.props.sharedCollections || [];
|
let sharedCollections: SlimCollection[] = data.props.sharedCollections || [];
|
||||||
let archivedCollections: Collection[] = data.props.archivedCollections || [];
|
let archivedCollections: SlimCollection[] = data.props.archivedCollections || [];
|
||||||
|
|
||||||
let newType: string = '';
|
let newType: string = '';
|
||||||
let resultsPerPage: number = 25;
|
let resultsPerPage: number = 25;
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveOrCreate(event: CustomEvent<Collection>) {
|
function saveOrCreate(event: CustomEvent<SlimCollection>) {
|
||||||
if (collections.find((collection) => collection.id === event.detail.id)) {
|
if (collections.find((collection) => collection.id === event.detail.id)) {
|
||||||
collections = collections.map((collection) => {
|
collections = collections.map((collection) => {
|
||||||
if (collection.id === event.detail.id) {
|
if (collection.id === event.detail.id) {
|
||||||
@@ -156,8 +156,8 @@
|
|||||||
isShowingCollectionModal = false;
|
isShowingCollectionModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function editCollection(event: CustomEvent<Collection>) {
|
function editCollection(event: CustomEvent<SlimCollection>) {
|
||||||
collectionToEdit = event.detail;
|
collectionToEdit = event.detail as unknown as Collection;
|
||||||
isShowingCollectionModal = true;
|
isShowingCollectionModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveEdit(event: CustomEvent<Collection>) {
|
function saveEdit(event: CustomEvent<SlimCollection>) {
|
||||||
collections = collections.map((adventure) => {
|
collections = collections.map((adventure) => {
|
||||||
if (adventure.id === event.detail.id) {
|
if (adventure.id === event.detail.id) {
|
||||||
return event.detail;
|
return event.detail;
|
||||||
@@ -490,7 +490,7 @@
|
|||||||
<div
|
<div
|
||||||
class="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6"
|
class="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6"
|
||||||
>
|
>
|
||||||
{#each currentCollections as collection}
|
{#each currentCollections as collection (collection.id)}
|
||||||
<CollectionCard
|
<CollectionCard
|
||||||
type=""
|
type=""
|
||||||
{collection}
|
{collection}
|
||||||
|
|||||||
Reference in New Issue
Block a user