mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-02 19:55:18 +02:00
commit
3508b3b04f
24 changed files with 206 additions and 127 deletions
|
@ -18,8 +18,8 @@
|
|||
import CollectionLink from './CollectionLink.svelte';
|
||||
import DotsHorizontal from '~icons/mdi/dots-horizontal';
|
||||
import DeleteWarning from './DeleteWarning.svelte';
|
||||
import ImageDisplayModal from './ImageDisplayModal.svelte';
|
||||
import { isAdventureVisited, typeToString } from '$lib';
|
||||
import CardCarousel from './CardCarousel.svelte';
|
||||
|
||||
export let type: string;
|
||||
export let user: User | null;
|
||||
|
@ -28,7 +28,6 @@
|
|||
let isCollectionModalOpen: boolean = false;
|
||||
let isWarningModalOpen: boolean = false;
|
||||
|
||||
let image_url: string | null = null;
|
||||
export let adventure: Adventure;
|
||||
|
||||
let activityTypes: string[] = [];
|
||||
|
@ -120,12 +119,6 @@
|
|||
dispatch('edit', adventure);
|
||||
}
|
||||
|
||||
let currentSlide = 0;
|
||||
|
||||
function goToSlide(index: number) {
|
||||
currentSlide = index;
|
||||
}
|
||||
|
||||
function link() {
|
||||
dispatch('link', adventure);
|
||||
}
|
||||
|
@ -146,48 +139,10 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
{#if image_url}
|
||||
<ImageDisplayModal image={image_url} on:close={() => (image_url = null)} {adventure} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl"
|
||||
>
|
||||
<figure>
|
||||
{#if adventure.images && adventure.images.length > 0}
|
||||
<div class="carousel w-full">
|
||||
{#each adventure.images as image, i}
|
||||
<div
|
||||
class="carousel-item w-full"
|
||||
style="display: {i === currentSlide ? 'block' : 'none'}"
|
||||
>
|
||||
<!-- svelte-ignore a11y-invalid-attribute -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a on:click={() => (image_url = image.image)}
|
||||
><img src={image.image} class="w-full h-48 object-cover" alt={adventure.name} /></a
|
||||
>
|
||||
<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}
|
||||
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||
<img
|
||||
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
|
||||
alt="No image available"
|
||||
class="w-full h-48 object-cover"
|
||||
/>
|
||||
{/if}
|
||||
</figure>
|
||||
<CardCarousel adventures={[adventure]} />
|
||||
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between">
|
||||
|
|
87
frontend/src/lib/components/CardCarousel.svelte
Normal file
87
frontend/src/lib/components/CardCarousel.svelte
Normal file
|
@ -0,0 +1,87 @@
|
|||
<script lang="ts">
|
||||
import type { Adventure } from '$lib/types';
|
||||
import ImageDisplayModal from './ImageDisplayModal.svelte';
|
||||
|
||||
export let adventures: Adventure[] = [];
|
||||
|
||||
let currentSlide = 0;
|
||||
let image_url: string | null = null;
|
||||
|
||||
$: adventure_images = adventures.flatMap((adventure) =>
|
||||
adventure.images.map((image) => ({ image: image.image, adventure: adventure }))
|
||||
);
|
||||
|
||||
$: {
|
||||
if (adventure_images.length > 0) {
|
||||
currentSlide = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function changeSlide(direction: string) {
|
||||
if (direction === 'next' && currentSlide < adventure_images.length - 1) {
|
||||
currentSlide = currentSlide + 1;
|
||||
} else if (direction === 'prev' && currentSlide > 0) {
|
||||
currentSlide = currentSlide - 1;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if image_url}
|
||||
<ImageDisplayModal
|
||||
adventure={adventure_images[currentSlide].adventure}
|
||||
image={image_url}
|
||||
on:close={() => (image_url = null)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<figure>
|
||||
{#if adventure_images && adventure_images.length > 0}
|
||||
<div class="carousel w-full relative">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="carousel-item w-full block">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a
|
||||
on:click|stopPropagation={() => (image_url = adventure_images[currentSlide].image)}
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src={adventure_images[currentSlide].image}
|
||||
class="w-full h-48 object-cover"
|
||||
alt={adventure_images[currentSlide].adventure.name}
|
||||
/>
|
||||
</a>
|
||||
|
||||
{#if adventure_images.length > 1}
|
||||
<div class="absolute inset-0 flex items-center justify-between pointer-events-none">
|
||||
{#if currentSlide > 0}
|
||||
<button
|
||||
on:click|stopPropagation={() => changeSlide('prev')}
|
||||
class="btn btn-circle btn-sm ml-2 pointer-events-auto">❮</button
|
||||
>
|
||||
{:else}
|
||||
<div class="w-12"></div>
|
||||
{/if}
|
||||
|
||||
{#if currentSlide < adventure_images.length - 1}
|
||||
<button
|
||||
on:click|stopPropagation={() => changeSlide('next')}
|
||||
class="btn btn-circle mr-2 btn-sm pointer-events-auto">❯</button
|
||||
>
|
||||
{:else}
|
||||
<div class="w-12"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||
<img
|
||||
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
|
||||
alt="No image available"
|
||||
class="w-full h-48 object-cover"
|
||||
/>
|
||||
{/if}
|
||||
</figure>
|
|
@ -9,7 +9,7 @@
|
|||
import ArchiveArrowUp from '~icons/mdi/archive-arrow-up';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Collection } from '$lib/types';
|
||||
import type { Adventure, Collection } from '$lib/types';
|
||||
import { addToast } from '$lib/toasts';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
|
@ -17,14 +17,14 @@
|
|||
import TrashCan from '~icons/mdi/trashcan';
|
||||
import DeleteWarning from './DeleteWarning.svelte';
|
||||
import ShareModal from './ShareModal.svelte';
|
||||
import CardCarousel from './CardCarousel.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let type: String | undefined | null;
|
||||
export let adventures: Adventure[] = [];
|
||||
let isShareModalOpen: boolean = false;
|
||||
|
||||
// export let type: String;
|
||||
|
||||
function editAdventure() {
|
||||
dispatch('edit', collection);
|
||||
}
|
||||
|
@ -86,6 +86,7 @@
|
|||
<div
|
||||
class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral text-neutral-content shadow-xl"
|
||||
>
|
||||
<CardCarousel {adventures} />
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between">
|
||||
<button
|
||||
|
|
|
@ -11,16 +11,8 @@
|
|||
|
||||
let originalName = collectionToEdit.name;
|
||||
|
||||
let isPointModalOpen: boolean = false;
|
||||
|
||||
import MapMarker from '~icons/mdi/map-marker';
|
||||
import Calendar from '~icons/mdi/calendar';
|
||||
import Notebook from '~icons/mdi/notebook';
|
||||
import ClipboardList from '~icons/mdi/clipboard-list';
|
||||
import Image from '~icons/mdi/image';
|
||||
import Star from '~icons/mdi/star';
|
||||
import Attachment from '~icons/mdi/attachment';
|
||||
import PointSelectionModal from './PointSelectionModal.svelte';
|
||||
import Earth from '~icons/mdi/earth';
|
||||
|
||||
onMount(async () => {
|
||||
|
@ -156,6 +148,16 @@
|
|||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="end_date">Link </label><br />
|
||||
<input
|
||||
type="url"
|
||||
id="link"
|
||||
name="link"
|
||||
bind:value={collectionToEdit.link}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="is_public">Public <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import type { Adventure } from '$lib/types';
|
||||
|
||||
export let image: string;
|
||||
export let adventure: Adventure;
|
||||
export let adventure: Adventure | null = null;
|
||||
|
||||
onMount(() => {
|
||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
||||
|
@ -42,34 +42,36 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box w-11/12 max-w-5xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<div class="modal-header flex justify-between items-center mb-4">
|
||||
<h3 class="font-bold text-2xl">{adventure.name}</h3>
|
||||
<button class="btn btn-circle btn-neutral" on:click={close}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-center items-center"
|
||||
style="display: flex; justify-content: center; align-items: center;"
|
||||
>
|
||||
<img
|
||||
src={image}
|
||||
alt={adventure.name}
|
||||
style="max-width: 100%; max-height: 75vh; object-fit: contain;"
|
||||
/>
|
||||
</div>
|
||||
{#if adventure}
|
||||
<div class="modal-header flex justify-between items-center mb-4">
|
||||
<h3 class="font-bold text-2xl">{adventure.name}</h3>
|
||||
<button class="btn btn-circle btn-neutral" on:click={close}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-center items-center"
|
||||
style="display: flex; justify-content: center; align-items: center;"
|
||||
>
|
||||
<img
|
||||
src={image}
|
||||
alt={adventure.name}
|
||||
style="max-width: 100%; max-height: 75vh; object-fit: contain;"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
name: '',
|
||||
description: '',
|
||||
adventures: [] as Adventure[],
|
||||
is_public: false
|
||||
is_public: false,
|
||||
shared_with: [],
|
||||
link: ''
|
||||
};
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
@ -151,6 +153,16 @@
|
|||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="end_date">Link </label><br />
|
||||
<input
|
||||
type="url"
|
||||
id="link"
|
||||
name="link"
|
||||
bind:value={newCollection.link}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">Create</button>
|
||||
<button type="button" class="btn mt-4" on:click={close}>Close</button>
|
||||
|
|
|
@ -57,11 +57,7 @@
|
|||
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl overflow-hidden"
|
||||
>
|
||||
<div class="card-body">
|
||||
{#if region.name_en && region.name !== region.name_en}
|
||||
<h2 class="card-title overflow-ellipsis">{region.name} ({region.name_en})</h2>
|
||||
{:else}
|
||||
<h2 class="card-title overflow-ellipsis">{region.name}</h2>
|
||||
{/if}
|
||||
<h2 class="card-title overflow-ellipsis">{region.name}</h2>
|
||||
<p>{region.id}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
|
||||
|
|
|
@ -87,6 +87,7 @@ export type Collection = {
|
|||
checklists?: Checklist[];
|
||||
is_archived?: boolean;
|
||||
shared_with: string[];
|
||||
link?: string | null;
|
||||
};
|
||||
|
||||
export type OpenStreetMapPlace = {
|
||||
|
|
|
@ -53,6 +53,11 @@ export const actions: Actions = {
|
|||
const description = formData.get('description') as string | null;
|
||||
const start_date = formData.get('start_date') as string | null;
|
||||
const end_date = formData.get('end_date') as string | null;
|
||||
let link = formData.get('link') as string | null;
|
||||
|
||||
if (link) {
|
||||
link = checkLink(link);
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
return {
|
||||
|
@ -66,6 +71,7 @@ export const actions: Actions = {
|
|||
formDataToSend.append('description', description || '');
|
||||
formDataToSend.append('start_date', start_date || '');
|
||||
formDataToSend.append('end_date', end_date || '');
|
||||
formDataToSend.append('link', link || '');
|
||||
let auth = event.cookies.get('auth');
|
||||
|
||||
if (!auth) {
|
||||
|
@ -142,6 +148,7 @@ export const actions: Actions = {
|
|||
let is_public = formData.get('is_public') as string | null | boolean;
|
||||
const start_date = formData.get('start_date') as string | null;
|
||||
const end_date = formData.get('end_date') as string | null;
|
||||
let link = formData.get('link') as string | null;
|
||||
|
||||
if (is_public) {
|
||||
is_public = true;
|
||||
|
@ -149,6 +156,10 @@ export const actions: Actions = {
|
|||
is_public = false;
|
||||
}
|
||||
|
||||
if (link) {
|
||||
link = checkLink(link);
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
return {
|
||||
status: 400,
|
||||
|
@ -162,6 +173,7 @@ export const actions: Actions = {
|
|||
formDataToSend.append('is_public', is_public.toString());
|
||||
formDataToSend.append('start_date', start_date || '');
|
||||
formDataToSend.append('end_date', end_date || '');
|
||||
formDataToSend.append('link', link || '');
|
||||
|
||||
let auth = event.cookies.get('auth');
|
||||
|
||||
|
|
|
@ -180,6 +180,7 @@
|
|||
{collection}
|
||||
on:delete={deleteCollection}
|
||||
on:edit={editCollection}
|
||||
adventures={collection.adventures}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
groupAdventuresByDate,
|
||||
groupNotesByDate,
|
||||
groupTransportationsByDate,
|
||||
groupChecklistsByDate
|
||||
groupChecklistsByDate,
|
||||
isAdventureVisited
|
||||
} from '$lib';
|
||||
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
|
||||
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
|
||||
|
@ -43,13 +44,12 @@
|
|||
let numberOfDays: number = NaN;
|
||||
|
||||
$: {
|
||||
numAdventures = adventures.filter((a) => a.type === 'visited' || a.type === 'planned').length;
|
||||
numVisited = adventures.filter((a) => a.type === 'visited').length;
|
||||
numAdventures = adventures.length;
|
||||
numVisited = adventures.filter(isAdventureVisited).length;
|
||||
}
|
||||
|
||||
let notFound: boolean = false;
|
||||
let isShowingLinkModal: boolean = false;
|
||||
let isShowingCreateModal: boolean = false;
|
||||
let isShowingTransportationModal: boolean = false;
|
||||
let isShowingChecklistModal: boolean = false;
|
||||
|
||||
|
@ -371,6 +371,14 @@
|
|||
{#if collection.name}
|
||||
<h1 class="text-center font-extrabold text-4xl mb-2">{collection.name}</h1>
|
||||
{/if}
|
||||
{#if collection.link}
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<a href={collection.link} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
||||
Visit Link
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if collection.description}
|
||||
<p class="text-center text-lg mb-2">{collection.description}</p>
|
||||
{/if}
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
let stats: {
|
||||
country_count: number;
|
||||
planned_count: number;
|
||||
total_regions: number;
|
||||
trips_count: number;
|
||||
visited_count: number;
|
||||
adventure_count: number;
|
||||
visited_region_count: number;
|
||||
total_countries: number;
|
||||
} | null;
|
||||
|
@ -19,16 +18,6 @@
|
|||
console.log(stats);
|
||||
</script>
|
||||
|
||||
<!--
|
||||
// v0 by Vercel.
|
||||
// https://v0.dev/t/EtPnDdQYcbn
|
||||
-->
|
||||
|
||||
<!--
|
||||
// v0 by Vercel.
|
||||
// https://v0.dev/t/DYwTru570WN
|
||||
-->
|
||||
|
||||
{#if data.user.profile_pic}
|
||||
<div class="avatar flex items-center justify-center">
|
||||
<div class="w-24 rounded">
|
||||
|
@ -65,17 +54,10 @@
|
|||
<div class="flex justify-center items-center">
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Completed Adventures</div>
|
||||
<div class="stat-value text-center">{stats.visited_count}</div>
|
||||
<div class="stat-title">Adventures</div>
|
||||
<div class="stat-value text-center">{stats.adventure_count}</div>
|
||||
<!-- <div class="stat-desc">Jan 1st - Feb 1st</div> -->
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Planned Adventures</div>
|
||||
<div class="stat-value text-center">{stats.planned_count}</div>
|
||||
<!-- <div class="stat-desc">↗︎ 400 (22%)</div> -->
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Collections</div>
|
||||
<div class="stat-value text-center">{stats.trips_count}</div>
|
||||
|
|
|
@ -25,3 +25,8 @@
|
|||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<svelte:head>
|
||||
<title>Shared Collections</title>
|
||||
<meta name="description" content="Collections shared with you by other users." />
|
||||
</svelte:head>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue