[wip] Implement photos loading and make everything dynamic

- Handle what happens when we click on Load more photos (detect if can load next or if reached the end)
- Show the current and total amount of photos (WIP as it apparently can't be filtered for aggregated)
- See filtered photos from URL when coming directly
- Disable button when all photos has been seen
- Show message if location doesn't exist
This commit is contained in:
2021-10-09 18:59:38 +02:00
parent 5e790ad68c
commit fd150e252c
4 changed files with 180 additions and 53 deletions

View File

@@ -1,8 +1,10 @@
# Options # Options
VITE_LIMIT_TIME=2 * 7 * 24 * 60 * 60 * 1000 VITE_LIMIT_TIME=2 * 7 * 24 * 60 * 60 * 1000
VITE_PREVIEW_COUNT=4 VITE_PREVIEW_COUNT=4
VITE_GRID_AMOUNT=21 VITE_GRID_AMOUNT=22
VITE_GRID_INCREMENT=21 VITE_GRID_INCREMENT=22
VITE_LIST_AMOUNT=10
VITE_LIST_INCREMENT=10
# API related # API related
# VITE_API_URL_DEV="http://192.168.1.19:8055" # VITE_API_URL_DEV="http://192.168.1.19:8055"

View File

@@ -2,6 +2,7 @@
export let text: string export let text: string
export let tag: string = 'a' export let tag: string = 'a'
export let url: string = undefined export let url: string = undefined
export let disabled: boolean = undefined
const classes = [ const classes = [
'button', 'button',
@@ -10,7 +11,7 @@
</script> </script>
{#if tag === 'button'} {#if tag === 'button'}
<button class={classes} on:click> <button class={classes} on:click disabled={disabled}>
<slot /> <slot />
{text} {text}
</button> </button>

View File

@@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/env'
import { page } from '$app/stores' import { page } from '$app/stores'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import { getContext } from 'svelte' import { getContext } from 'svelte'
import { fly } from 'svelte/transition' import { fly } from 'svelte/transition'
import { quartOut } from 'svelte/easing' import { quartOut } from 'svelte/easing'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
// Components // Components
import Metas from '$components/Metas.svelte' import Metas from '$components/Metas.svelte'
import IconEarth from '$components/atoms/IconEarth.svelte' import IconEarth from '$components/atoms/IconEarth.svelte'
@@ -17,6 +18,11 @@
import Newsletter from '$components/organisms/Newsletter.svelte' import Newsletter from '$components/organisms/Newsletter.svelte'
export let photos: any export let photos: any
export let lastUpdated: string
export let totalPhotos: number
export let filteredCountryExists: boolean
dayjs.extend(relativeTime)
const { country: countries }: any = getContext('global') const { country: countries }: any = getContext('global')
@@ -27,16 +33,20 @@
const defaultCountry = 'all' const defaultCountry = 'all'
const defaultSort = 'latest' const defaultSort = 'latest'
let filtered: boolean = false let filtered: boolean = false
let filterCountry: any = defaultCountry let filterCountry: any = $page.query.get('country') || defaultCountry
let filterSort: string = defaultSort let filterSort: string = $page.query.get('sort') || defaultSort
let countryFlagId: string let countryFlagId: string
// Define URL params // Pages related informations
let urlFiltersParams: URLSearchParams let currentPage: number = 1
if (browser) { let currentPhotosAmount: number = photos.length
urlFiltersParams = new URLSearchParams(window.location.pathname) let ended: boolean
} $: ended = currentPhotosAmount === totalPhotos
/**
* Define URL params
*/
$: { $: {
// Define country flag from selection // Define country flag from selection
if (filterCountry !== defaultCountry) { if (filterCountry !== defaultCountry) {
@@ -46,17 +56,17 @@
} }
// Update URL filtering params from filter values // Update URL filtering params from filter values
// if (filtered && filterCountry && filterSort) { if (filtered && filterCountry && filterSort) {
// urlFiltersParams.set('country', filterCountry) // urlFiltersParams.set('country', filterCountry)
// urlFiltersParams.set('sort', filterSort) // urlFiltersParams.set('sort', filterSort)
// goto(`${$page.path}?${urlFiltersParams.toString()}`, { noscroll: true }) // goto(`${$page.path}?${urlFiltersParams.toString()}`, { keepfocus:true, replaceState:true, noscroll:true })
// // TODO: Request photos from the country of choice // TODO: Request photos from the country of choice
// // loadPhotos() // loadPhotos()
// console.log('load photos') console.log('load photos')
// } }
console.log({ filtered, filterCountry, filterSort }) // console.log({ filtered, filterCountry, filterSort })
} }
@@ -93,25 +103,59 @@
* Load photos * Load photos
*/ */
// Load more photos from CTA // Load more photos from CTA
const loadMorePhotos = () => { const loadMorePhotos = async () => {
console.log('load more photos') // Append more photos from API including options and page
// TODO: Append more photos from API including options and page const newPhotos: any = await loadPhotos(currentPage + 1)
console.log(newPhotos)
if (newPhotos) {
photos = [...photos, ...newPhotos]
// Define actions if the number of new photos is the expected ones
if (newPhotos.length === Number(import.meta.env.VITE_GRID_INCREMENT)) {
// Increment the current page
currentPage++
} }
// Load photos // Increment the currently visible amount of photos
currentPhotosAmount += newPhotos.length
}
}
// [function] Load photos helper
const loadPhotos = async (page?: number, params?: string) => { const loadPhotos = async (page?: number, params?: string) => {
const res = fetchAPI(` const res = fetchAPI(`
query { query {
photo (limit: 11, sort: ["-date_created"]) { photos: photo (
filter: {
${filterCountry !== 'all' ? `location: { country: { slug: { _eq: "${filterCountry}" }} },` : ''}
status: { _eq: "published" },
},
sort: "${filterSort === 'latest' ? '-' : ''}date_created",
limit: ${import.meta.env.VITE_GRID_INCREMENT},
page: ${page},
) {
title
slug
image { image {
id id
title title
} }
location {
slug
country { slug }
}
} }
} }
`) `)
const { data } = await res const { data: { photos }} = await res
console.log(data)
if (photos) {
// Return new photos
return photos
} else {
throw new Error('Error while loading new photos')
}
} }
</script> </script>
@@ -136,10 +180,15 @@
<Select <Select
name="country" id="filter_country" name="country" id="filter_country"
options={[ options={[
{ value: 'all', name: 'Worldwide', default: true }, {
value: 'all',
name: 'Worldwide',
default: filterCountry === defaultCountry
},
...countries.map(({ slug, name }) => ({ ...countries.map(({ slug, name }) => ({
value: slug, value: slug,
name, name,
default: filterCountry === slug,
})) }))
]} ]}
on:change={handleCountryChange} on:change={handleCountryChange}
@@ -156,8 +205,8 @@
<Select <Select
name="sort" id="filter_sort" name="sort" id="filter_sort"
options={[ options={[
{ value: 'latest', name: 'Latest photos', default: true }, { value: 'latest', name: 'Latest photos', default: filterSort === defaultSort },
{ value: 'oldest', name: 'Oldest photos' }, { value: 'oldest', name: 'Oldest photos', default: filterSort === 'oldest' },
]} ]}
on:change={handleSortChange} on:change={handleSortChange}
value={filterSort} value={filterSort}
@@ -190,11 +239,12 @@
<section class="photos__content"> <section class="photos__content">
<div class="grid container"> <div class="grid container">
{#if photos}
<div class="photos__grid"> <div class="photos__grid">
{#each Array(22 * 2) as _} {#each photos as { image, slug, location }}
<div class="photo shadow-photo"> <div class="photo shadow-photo">
<a href="/country/location/photo-slug"> <a href="/{location.country.slug}/{location.slug}/{slug}">
<Image id="5a45ba18-7b78-48e0-a299-302b1ea9b77b" width={1200} height={800} alt="image" /> <Image id={image.id} width={1200} height={800} alt={image.title} />
<PostCard /> <PostCard />
</a> </a>
</div> </div>
@@ -202,14 +252,30 @@
</div> </div>
<div class="controls grid"> <div class="controls grid">
<p class="controls__date">Last updated: 4 days ago</p> <p class="controls__date" title={dayjs(lastUpdated).format('DD/MM/YYYY, hh:mm')}>
<Button text="See more photos" tag="button" on:click={loadMorePhotos} /> Last updated: <time datetime={dayjs(lastUpdated).format('YYYY-MM-DD')}>{dayjs().to(dayjs(lastUpdated))}</time>
</p>
<Button
text={!ended ? 'See more photos' : "You've seen it all!"}
tag="button"
on:click={loadMorePhotos}
disabled={ended}
/>
<div class="controls__count"> <div class="controls__count">
<span class="current">21</span> <span class="current">{currentPhotosAmount}</span>
<span>/</span> <span>/</span>
<span class="total">129</span> <span class="total">{totalPhotos}</span>
</div> </div>
</div> </div>
{:else if !filteredCountryExists}
<div class="photos__message">
<p>
<strong>{$page.query.get('country').replace(/(^|\s)\S/g, letter => letter.toUpperCase())}</strong> is not available… yet 👀
</p>
</div>
{/if}
<div class="grid-modules"> <div class="grid-modules">
<div class="wrap"> <div class="wrap">
@@ -226,14 +292,48 @@
import { fetchAPI } from '$utils/api' import { fetchAPI } from '$utils/api'
export async function load ({ page, session, fetch, context }) { export async function load ({ page, session, fetch, context }) {
// TODO: Implement query parameters from URL (country, sort) // Query parameters
const queryCountry = page.query.get('country')
const querySort = page.query.get('sort')
const res = await fetchAPI(` const res = await fetchAPI(`
query { query {
photo (limit: 11, sort: ["-date_created"]) { photos: photo (
filter: {
${queryCountry ? `location: { country: { slug: { _eq: "${queryCountry}" }} },` : ''}
status: { _eq: "published" },
},
sort: "${querySort === 'latest' ? '-' : ''}date_created",
limit: ${import.meta.env.VITE_GRID_AMOUNT},
page: 1,
) {
title
slug
image { image {
id id
title title
} }
location {
slug
country { slug }
}
}
total_published: photo_aggregated {
count { id }
}
country: country (
filter: {
slug: { _eq: "${queryCountry}" },
status: { _eq: "published" },
},
) {
slug
}
lastUpdated: photo (limit: 1, sort: "-date_created", filter: { status: { _eq: "published" }}) {
date_created
} }
} }
`) `)
@@ -242,7 +342,10 @@
return { return {
props: { props: {
photos: data.photo, photos: data.photos,
filteredCountryExists: data.country.length > 0,
totalPhotos: data.total_published[0].count.id,
lastUpdated: data.lastUpdated[0].date_created,
} }
} }
} }

View File

@@ -324,6 +324,20 @@
} }
} }
// Message
&__message {
// grid-column: ;
text-align: center;
padding: clamp(48px, 10vw, 160px) 0;
color: $color-text;
@include bp (sm) {
grid-column: 3 / span 20;
}
}
/** /**
* Controls * Controls
*/ */
@@ -361,12 +375,14 @@
// See More Photos // See More Photos
.button { .button {
$color-button: #F2D3B8;
grid-column: span var(--columns); grid-column: span var(--columns);
grid-row: 1; grid-row: 1;
margin: 0 auto; margin: 0 auto;
height: 56px; height: 56px;
background-color: #F2D3B8; background-color: $color-button;
font-size: rem(16px); font-size: rem(16px);
border: none;
@include bp (sm) { @include bp (sm) {
grid-column: 6 / span 12; grid-column: 6 / span 12;
@@ -374,6 +390,11 @@
padding: 0 40px; padding: 0 40px;
font-size: rem(18px); font-size: rem(18px);
} }
&[disabled] {
background: none;
border: 2px solid darken($color-button, 2);
}
} }
// Photo Count // Photo Count