mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-05 13:15:18 +02:00
commit
58a40f1d09
19 changed files with 171 additions and 68 deletions
|
@ -57,15 +57,23 @@ class CustomUserAdmin(UserAdmin):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
class CollectionAdmin(admin.ModelAdmin):
|
||||||
|
def adventure_count(self, obj):
|
||||||
|
return obj.adventure_set.count()
|
||||||
|
|
||||||
|
adventure_count.short_description = 'Adventure Count'
|
||||||
|
|
||||||
|
list_display = ('name', 'user_id', 'adventure_count', 'is_public')
|
||||||
|
|
||||||
admin.site.register(CustomUser, CustomUserAdmin)
|
admin.site.register(CustomUser, CustomUserAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Adventure, AdventureAdmin)
|
admin.site.register(Adventure, AdventureAdmin)
|
||||||
|
admin.site.register(Collection, CollectionAdmin)
|
||||||
admin.site.register(Country, CountryAdmin)
|
admin.site.register(Country, CountryAdmin)
|
||||||
admin.site.register(Region, RegionAdmin)
|
admin.site.register(Region, RegionAdmin)
|
||||||
admin.site.register(VisitedRegion)
|
admin.site.register(VisitedRegion)
|
||||||
admin.site.register(Collection)
|
|
||||||
|
|
||||||
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'
|
||||||
|
|
|
@ -157,8 +157,8 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
(Q(user_id=request.user.id) | Q(is_public=True))
|
(Q(user_id=request.user.id) | Q(is_public=True))
|
||||||
)
|
)
|
||||||
queryset = self.apply_sorting(queryset)
|
queryset = self.apply_sorting(queryset)
|
||||||
adventures = self.paginate_and_respond(queryset, request)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return adventures
|
return Response(serializer.data)
|
||||||
|
|
||||||
def paginate_and_respond(self, queryset, request):
|
def paginate_and_respond(self, queryset, request):
|
||||||
paginator = self.pagination_class()
|
paginator = self.pagination_class()
|
||||||
|
|
|
@ -77,7 +77,7 @@ MIDDLEWARE = (
|
||||||
# For backwards compatibility for Django 1.8
|
# For backwards compatibility for Django 1.8
|
||||||
MIDDLEWARE_CLASSES = MIDDLEWARE
|
MIDDLEWARE_CLASSES = MIDDLEWARE
|
||||||
|
|
||||||
ROOT_URLCONF = 'demo.urls'
|
ROOT_URLCONF = 'main.urls'
|
||||||
|
|
||||||
# WSGI_APPLICATION = 'demo.wsgi.application'
|
# WSGI_APPLICATION = 'demo.wsgi.application'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
|
||||||
|
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
console.log('Adventure type changed');
|
console.log('Adventure type changed');
|
||||||
|
dispatch('typeChange', adventure.id);
|
||||||
addToast('info', 'Adventure type changed successfully!');
|
addToast('info', 'Adventure type changed successfully!');
|
||||||
adventure.type = newType;
|
adventure.type = newType;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -83,6 +83,17 @@
|
||||||
<li>
|
<li>
|
||||||
<button on:click={() => goto('/map')}>Map</button>
|
<button on:click={() => goto('/map')}>Map</button>
|
||||||
</li>
|
</li>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !data.user}
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<form class="flex gap-2">
|
<form class="flex gap-2">
|
||||||
<label class="input input-bordered flex items-center gap-2">
|
<label class="input input-bordered flex items-center gap-2">
|
||||||
<input type="text" bind:value={query} class="grow" placeholder="Search" />
|
<input type="text" bind:value={query} class="grow" placeholder="Search" />
|
||||||
|
@ -102,16 +113,6 @@
|
||||||
</label>
|
</label>
|
||||||
<button on:click={searchGo} type="submit" class="btn btn-neutral">Search</button>
|
<button on:click={searchGo} type="submit" class="btn btn-neutral">Search</button>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !data.user}
|
|
||||||
<li>
|
|
||||||
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-ghost text-xl" href="/"
|
<a class="btn btn-ghost text-xl" href="/"
|
||||||
|
@ -134,6 +135,17 @@
|
||||||
<li>
|
<li>
|
||||||
<button class="btn btn-neutral" on:click={() => goto('/map')}>Map</button>
|
<button class="btn btn-neutral" on:click={() => goto('/map')}>Map</button>
|
||||||
</li>
|
</li>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !data.user}
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<form class="flex gap-2">
|
<form class="flex gap-2">
|
||||||
<label class="input input-bordered flex items-center gap-2">
|
<label class="input input-bordered flex items-center gap-2">
|
||||||
<input type="text" bind:value={query} class="grow" placeholder="Search" />
|
<input type="text" bind:value={query} class="grow" placeholder="Search" />
|
||||||
|
@ -153,16 +165,6 @@
|
||||||
</label>
|
</label>
|
||||||
<button on:click={searchGo} type="submit" class="btn btn-neutral">Search</button>
|
<button on:click={searchGo} type="submit" class="btn btn-neutral">Search</button>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !data.user}
|
|
||||||
<li>
|
|
||||||
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
|
|
|
@ -27,7 +27,8 @@
|
||||||
user_id: NaN,
|
user_id: NaN,
|
||||||
latitude: null,
|
latitude: null,
|
||||||
longitude: null,
|
longitude: null,
|
||||||
is_public: false
|
is_public: false,
|
||||||
|
collection: null
|
||||||
};
|
};
|
||||||
|
|
||||||
let image: File;
|
let image: File;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export let appVersion = 'Web v0.4.0';
|
export let appVersion = 'Web v0.4.1';
|
||||||
export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.4.0';
|
export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.4.1';
|
||||||
export let appTitle = 'AdventureLog';
|
export let appTitle = 'AdventureLog';
|
||||||
export let copyrightYear = '2024';
|
export let copyrightYear = '2024';
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
|
|
||||||
let resultsPerPage: number = 10;
|
let resultsPerPage: number = 10;
|
||||||
|
|
||||||
let currentView: string = 'cards';
|
|
||||||
|
|
||||||
let next: string | null = data.props.next || null;
|
let next: string | null = data.props.next || null;
|
||||||
let previous: string | null = data.props.previous || null;
|
let previous: string | null = data.props.previous || null;
|
||||||
let count = data.props.count || 0;
|
let count = data.props.count || 0;
|
||||||
|
|
|
@ -9,13 +9,18 @@
|
||||||
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 EditAdventure from '$lib/components/EditAdventure.svelte';
|
||||||
|
import NotFound from '$lib/components/NotFound.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let collection: Collection;
|
let collection: Collection;
|
||||||
|
|
||||||
let adventures: Adventure[] = [];
|
let adventures: Adventure[] = [];
|
||||||
let numVisited: number = adventures.filter((a) => a.type == 'visited').length;
|
let numVisited: number = 0;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
numVisited = adventures.filter((a) => a.type === 'visited').length;
|
||||||
|
}
|
||||||
|
|
||||||
let notFound: boolean = false;
|
let notFound: boolean = false;
|
||||||
let isShowingCreateModal: boolean = false;
|
let isShowingCreateModal: boolean = false;
|
||||||
|
@ -56,6 +61,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeType(event: CustomEvent<number>) {
|
||||||
|
adventures = adventures.map((adventure) => {
|
||||||
|
if (adventure.id == event.detail) {
|
||||||
|
if (adventure.type == 'visited') {
|
||||||
|
adventure.type = 'planned';
|
||||||
|
} else {
|
||||||
|
adventure.type = 'visited';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return adventure;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let adventureToEdit: Adventure;
|
let adventureToEdit: Adventure;
|
||||||
let isEditModalOpen: boolean = false;
|
let isEditModalOpen: boolean = false;
|
||||||
|
|
||||||
|
@ -157,7 +175,7 @@
|
||||||
<div class="flex items-center justify-center mb-4">
|
<div class="flex items-center justify-center mb-4">
|
||||||
<div class="stats shadow bg-base-300">
|
<div class="stats shadow bg-base-300">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Region Stats</div>
|
<div class="stat-title">Collection Stats</div>
|
||||||
<div class="stat-value">{numVisited}/{adventures.length} Visited</div>
|
<div class="stat-value">{numVisited}/{adventures.length} Visited</div>
|
||||||
{#if numVisited === adventures.length}
|
{#if numVisited === adventures.length}
|
||||||
<div class="stat-desc">You've completed this collection! 🎉!</div>
|
<div class="stat-desc">You've completed this collection! 🎉!</div>
|
||||||
|
@ -169,6 +187,9 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<h1 class="text-center font-semibold text-2xl mt-4 mb-2">Linked Adventures</h1>
|
<h1 class="text-center font-semibold text-2xl mt-4 mb-2">Linked Adventures</h1>
|
||||||
|
{#if adventures.length == 0}
|
||||||
|
<NotFound error={undefined} />
|
||||||
|
{/if}
|
||||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||||
{#each adventures as adventure}
|
{#each adventures as adventure}
|
||||||
<AdventureCard
|
<AdventureCard
|
||||||
|
@ -177,6 +198,7 @@
|
||||||
on:delete={deleteAdventure}
|
on:delete={deleteAdventure}
|
||||||
type={adventure.type}
|
type={adventure.type}
|
||||||
{adventure}
|
{adventure}
|
||||||
|
on:typeChange={changeType}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,8 @@ export const load = (async (event) => {
|
||||||
.map((adventure) => {
|
.map((adventure) => {
|
||||||
return {
|
return {
|
||||||
lngLat: [adventure.longitude, adventure.latitude] as [number, number],
|
lngLat: [adventure.longitude, adventure.latitude] as [number, number],
|
||||||
name: adventure.name
|
name: adventure.name,
|
||||||
|
type: adventure.type
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { DefaultMarker, MapEvents, MapLibre, Popup } from 'svelte-maplibre';
|
import { DefaultMarker, MapEvents, MapLibre, Popup, Marker } from 'svelte-maplibre';
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
|
let clickedName = '';
|
||||||
|
|
||||||
let markers = data.props.markers;
|
let markers = data.props.markers;
|
||||||
console.log(markers);
|
console.log(markers);
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,14 +15,50 @@
|
||||||
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
||||||
standardControls
|
standardControls
|
||||||
>
|
>
|
||||||
{#each data.props.markers as { lngLat, name }}
|
{#each data.props.markers as { lngLat, name, type }}
|
||||||
<!-- Unlike the custom marker example, default markers do not have mouse events,
|
{#if type == 'visited'}
|
||||||
and popups only support the default openOn="click" behavior -->
|
<Marker
|
||||||
<DefaultMarker {lngLat}>
|
{lngLat}
|
||||||
<Popup offset={[0, -10]}>
|
on:click={() => (clickedName = name)}
|
||||||
|
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-red-300 text-black shadow-2xl focus:outline-2 focus:outline-black"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="red" stroke-width="2" fill="red" />
|
||||||
|
</svg>
|
||||||
|
<Popup openOn="click" offset={[0, -10]}>
|
||||||
<div class="text-lg font-bold">{name}</div>
|
<div class="text-lg font-bold">{name}</div>
|
||||||
|
<p class="font-semibold text-md">Visited</p>
|
||||||
</Popup>
|
</Popup>
|
||||||
</DefaultMarker>
|
</Marker>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if type == 'planned'}
|
||||||
|
<Marker
|
||||||
|
{lngLat}
|
||||||
|
on:click={() => (clickedName = name)}
|
||||||
|
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-blue-300 text-black shadow-2xl focus:outline-2 focus:outline-black"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="blue" stroke-width="2" fill="blue" />
|
||||||
|
</svg>
|
||||||
|
<Popup openOn="click" offset={[0, -10]}>
|
||||||
|
<div class="text-lg font-bold">{name}</div>
|
||||||
|
<p class="font-semibold text-md">Planned</p>
|
||||||
|
</Popup>
|
||||||
|
</Marker>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</MapLibre>
|
</MapLibre>
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,7 @@ export const load = (async (event) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
adventures: data.results as Adventure[],
|
adventures: data,
|
||||||
nextPage: data.next,
|
|
||||||
prevPage: data.previous,
|
|
||||||
total: data.count,
|
|
||||||
query
|
query
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
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 { page } from '$app/stores';
|
import EditAdventure from '$lib/components/EditAdventure.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let osmResults: OpenStreetMapPlace[] = [];
|
let osmResults: OpenStreetMapPlace[] = [];
|
||||||
|
let adventures: Adventure[] = [];
|
||||||
|
|
||||||
let query: string | null = '';
|
let query: string | null = '';
|
||||||
|
|
||||||
|
@ -36,15 +37,44 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
let adventures: Adventure[] = [];
|
|
||||||
if (data.props) {
|
if (data.props) {
|
||||||
adventures = data.props.adventures;
|
adventures = data.props.adventures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let adventureToEdit: Adventure;
|
||||||
|
let isEditModalOpen: boolean = false;
|
||||||
|
let isShowingCreateModal: boolean = false;
|
||||||
|
|
||||||
|
function editAdventure(event: CustomEvent<Adventure>) {
|
||||||
|
adventureToEdit = event.detail;
|
||||||
|
isEditModalOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEdit(event: CustomEvent<Adventure>) {
|
||||||
|
adventures = adventures.map((adventure) => {
|
||||||
|
if (adventure.id === event.detail.id) {
|
||||||
|
return event.detail;
|
||||||
|
}
|
||||||
|
return adventure;
|
||||||
|
});
|
||||||
|
isEditModalOpen = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if isEditModalOpen}
|
||||||
|
<EditAdventure
|
||||||
|
{adventureToEdit}
|
||||||
|
on:close={() => (isEditModalOpen = false)}
|
||||||
|
on:saveEdit={saveEdit}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if adventures.length === 0 && osmResults.length === 0}
|
{#if adventures.length === 0 && osmResults.length === 0}
|
||||||
<NotFound error={data.error} />
|
<NotFound error={data.error} />
|
||||||
{:else}
|
{/if}
|
||||||
|
|
||||||
|
{#if adventures.length > 0}
|
||||||
<h2 class="text-center font-bold text-2xl mb-4">AdventureLog Results</h2>
|
<h2 class="text-center font-bold text-2xl mb-4">AdventureLog Results</h2>
|
||||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||||
{#each adventures as adventure}
|
{#each adventures as adventure}
|
||||||
|
@ -53,10 +83,15 @@
|
||||||
type={adventure.type}
|
type={adventure.type}
|
||||||
{adventure}
|
{adventure}
|
||||||
on:delete={deleteAdventure}
|
on:delete={deleteAdventure}
|
||||||
|
on:edit={editAdventure}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if adventures.length > 0 && osmResults.length > 0}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
{/if}
|
||||||
|
{#if osmResults.length > 0}
|
||||||
<h2 class="text-center font-bold text-2xl mb-4">Online Results</h2>
|
<h2 class="text-center font-bold text-2xl mb-4">Online Results</h2>
|
||||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||||
{#each osmResults as result}
|
{#each osmResults as result}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue