From e643362011f4fca09be576faf8c1ef7c2c8bf309 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Tue, 16 Jul 2024 14:50:30 -0400 Subject: [PATCH 1/3] new adventure card --- .../src/lib/components/AdventureCard.svelte | 91 +++++++++---------- .../src/routes/collections/[id]/+page.svelte | 16 ++++ 2 files changed, 61 insertions(+), 46 deletions(-) diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index b48b7cf..aefb15c 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -16,6 +16,7 @@ import LinkVariantRemove from '~icons/mdi/link-variant-remove'; import Plus from '~icons/mdi/plus'; import CollectionLink from './CollectionLink.svelte'; + import DotsHorizontal from '~icons/mdi/dots-horizontal'; export let type: string; @@ -108,7 +109,7 @@ {/if}
@@ -157,52 +158,50 @@ {/if}
- {#if type == 'visited'} - +
diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index 4dc9a4e..6e04b18 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -15,6 +15,7 @@ let collection: Collection; let adventures: Adventure[] = []; + let numVisited: number = adventures.filter((a) => a.type == 'visited').length; let notFound: boolean = false; let isShowingCreateModal: boolean = false; @@ -151,6 +152,21 @@ {#if collection.name}

{collection.name}

{/if} + {#if adventures.length > 0} +
+
+
+
Region Stats
+
{numVisited}/{adventures.length} Visited
+ {#if numVisited === adventures.length} +
You've completed this collection! 🎉!
+ {:else} +
Keep exploring!
+ {/if} +
+
+
+ {/if}

Linked Adventures

{#each adventures as adventure} From b39ac34b689f56755a6541c47c3946e27c138a2e Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Tue, 16 Jul 2024 15:38:07 -0400 Subject: [PATCH 2/3] universal image fetcher --- .../src/lib/components/EditAdventure.svelte | 52 +++++++++-- .../src/lib/components/ImageFetcher.svelte | 90 +++++++++++++++++++ .../src/lib/components/NewAdventure.svelte | 50 ++++++++--- frontend/src/routes/api/[...path]/+server.ts | 23 ++++- 4 files changed, 193 insertions(+), 22 deletions(-) create mode 100644 frontend/src/lib/components/ImageFetcher.svelte diff --git a/frontend/src/lib/components/EditAdventure.svelte b/frontend/src/lib/components/EditAdventure.svelte index 099d269..d6d62ee 100644 --- a/frontend/src/lib/components/EditAdventure.svelte +++ b/frontend/src/lib/components/EditAdventure.svelte @@ -12,17 +12,21 @@ let originalName = adventureToEdit.name; let isPointModalOpen: boolean = false; + let isImageFetcherOpen: boolean = false; + + let fileInput: HTMLInputElement; + let image: File; 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'; import Wikipedia from '~icons/mdi/wikipedia'; + import ImageFetcher from './ImageFetcher.svelte'; onMount(async () => { modal = document.getElementById('my_modal_1') as HTMLDialogElement; @@ -88,6 +92,23 @@ } } } + + function handleImageFetch(event: CustomEvent) { + const file = event.detail.file; + if (file && fileInput) { + // Create a DataTransfer object and add the file + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + + // Set the files property of the file input + fileInput.files = dataTransfer.files; + + // Update the adventureToEdit object + adventureToEdit.image = file; + } + isImageFetcherOpen = false; + } + function setLongLat(event: CustomEvent<[number, number]>) { console.log(event.detail); adventureToEdit.latitude = event.detail[1]; @@ -105,6 +126,10 @@ /> {/if} +{#if isImageFetcherOpen} + (isImageFetcherOpen = false)} /> +{/if} + @@ -193,14 +218,23 @@ />
-
- +
+
+ + +

+ import { addToast } from '$lib/toasts'; + import { createEventDispatcher } from 'svelte'; + const dispatch = createEventDispatcher(); + import { onMount } from 'svelte'; + let modal: HTMLDialogElement; + + let url: string = ''; + let query: string = ''; + + let error = ''; + + onMount(() => { + modal = document.getElementById('my_modal_1') as HTMLDialogElement; + if (modal) { + modal.showModal(); + } + }); + + async function fetchImage() { + let res = await fetch(url); + let data = await res.blob(); + if (!data) { + error = 'No image found at that URL.'; + return; + } + let file = new File([data], 'image.jpg', { type: 'image/jpeg' }); + close(); + dispatch('image', { file }); + } + + async function fetchWikiImage() { + let res = await fetch(`/api/generate/img/?name=${query}`); + let data = await res.json(); + if (data.source) { + let imageUrl = data.source; + let res = await fetch(imageUrl); + let blob = await res.blob(); + let file = new File([blob], `${query}.jpg`, { type: 'image/jpeg' }); + close(); + dispatch('image', { file }); + } else { + error = 'No image found for that Wikipedia article.'; + } + } + + function close() { + dispatch('close'); + } + + function handleKeydown(event: KeyboardEvent) { + if (event.key === 'Escape') { + dispatch('close'); + } + } + + + + + + + diff --git a/frontend/src/lib/components/NewAdventure.svelte b/frontend/src/lib/components/NewAdventure.svelte index edf23cb..b1f15bc 100644 --- a/frontend/src/lib/components/NewAdventure.svelte +++ b/frontend/src/lib/components/NewAdventure.svelte @@ -5,6 +5,7 @@ import { enhance } from '$app/forms'; import { addToast } from '$lib/toasts'; import PointSelectionModal from './PointSelectionModal.svelte'; + import ImageFetcher from './ImageFetcher.svelte'; export let type: string = 'visited'; @@ -28,8 +29,10 @@ }; let image: File; + let fileInput: HTMLInputElement; let isPointModalOpen: boolean = false; + let isImageFetcherOpen: boolean = false; const dispatch = createEventDispatcher(); let modal: HTMLDialogElement; @@ -102,6 +105,22 @@ } } + function handleImageFetch(event: CustomEvent) { + const file = event.detail.file; + if (file && fileInput) { + // Create a DataTransfer object and add the file + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + + // Set the files property of the file input + fileInput.files = dataTransfer.files; + + // Update the adventureToEdit object + newAdventure.image = file; + } + isImageFetcherOpen = false; + } + function setLongLat(event: CustomEvent<[number, number]>) { console.log(event.detail); newAdventure.latitude = event.detail[1]; @@ -114,6 +133,10 @@ (isPointModalOpen = false)} on:submit={setLongLat} /> {/if} +{#if isImageFetcherOpen} + (isImageFetcherOpen = false)} /> +{/if} + @@ -234,16 +257,23 @@ />
-
- +
+
+ + +
Date: Tue, 16 Jul 2024 15:44:37 -0400 Subject: [PATCH 3/3] fixed card layout --- README.md | 31 ++++++++++++++++++- .../src/lib/components/AdventureCard.svelte | 22 +++++++------ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 704cda8..a4c61ed 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ _**⚠️ AdventureLog is in early development and is not recommended for produc # Docker 🐋 -Docker is the perffered way to run AdventureLog on your local machine. It is a lightweight containerization technology that allows you to run applications in isolated environments called containers. +Docker is the preferred way to run AdventureLog on your local machine. It is a lightweight containerization technology that allows you to run applications in isolated environments called containers. **Note**: This guide mainly focuses on installation with a linux based host machine, but the steps are similar for other operating systems. ## Prerequisites @@ -53,6 +53,35 @@ Here is a summary of the configuration options available in the `docker-compose. | `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 | | `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. | +### Proxy Container (nginx) Configuration + +In order to use media files in a production environment, you need to configure the `nginx` container to serve the media files. The container is already in the docker compose file but you need to do a few things to make it work. + +1. Create a directory called `proxy` in the same directory as the `docker-compose.yml` file. +2. Create a file called `nginx.conf` in the `proxy` directory. +3. Add the following configuration to the `nginx.conf` file: + +```nginx +server { + listen 80; + server_name localhost; + + location /media/ { + alias /app/media/; + } +} +``` + +## Running the Containers + +To start the containers, run the following command: + +```bash +docker compose up -d +``` + +Enjoy AdventureLog! 🎉 + # About AdventureLog AdventureLog is a Svelte Kit and Django application that utilizes a PostgreSQL database. Users can log the adventures they have experienced, as well as plan future ones. Key features include: diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index aefb15c..8535a0c 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -125,16 +125,18 @@
-

- {adventure.name} -

-
- {#if adventure.type == 'visited'} -
Visited
- {:else} -
Planned
- {/if} -
{adventure.is_public ? 'Public' : 'Private'}
+
+

+ {adventure.name} +

+
+ {#if adventure.type == 'visited'} +
Visited
+ {:else} +
Planned
+ {/if} +
{adventure.is_public ? 'Public' : 'Private'}
+
{#if adventure.location && adventure.location !== ''}