[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:
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the currently visible amount of photos
|
||||||
|
currentPhotosAmount += newPhotos.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load photos
|
// [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,26 +239,43 @@
|
|||||||
|
|
||||||
<section class="photos__content">
|
<section class="photos__content">
|
||||||
<div class="grid container">
|
<div class="grid container">
|
||||||
<div class="photos__grid">
|
{#if photos}
|
||||||
{#each Array(22 * 2) as _}
|
<div class="photos__grid">
|
||||||
<div class="photo shadow-photo">
|
{#each photos as { image, slug, location }}
|
||||||
<a href="/country/location/photo-slug">
|
<div class="photo shadow-photo">
|
||||||
<Image id="5a45ba18-7b78-48e0-a299-302b1ea9b77b" width={1200} height={800} alt="image" />
|
<a href="/{location.country.slug}/{location.slug}/{slug}">
|
||||||
<PostCard />
|
<Image id={image.id} width={1200} height={800} alt={image.title} />
|
||||||
</a>
|
<PostCard />
|
||||||
</div>
|
</a>
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
|
|
||||||
<div class="controls grid">
|
|
||||||
<p class="controls__date">Last updated: 4 days ago</p>
|
|
||||||
<Button text="See more photos" tag="button" on:click={loadMorePhotos} />
|
|
||||||
<div class="controls__count">
|
|
||||||
<span class="current">21</span>
|
|
||||||
<span>/</span>
|
|
||||||
<span class="total">129</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="controls grid">
|
||||||
|
<p class="controls__date" title={dayjs(lastUpdated).format('DD/MM/YYYY, hh:mm')}>
|
||||||
|
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">
|
||||||
|
<span class="current">{currentPhotosAmount}</span>
|
||||||
|
<span>/</span>
|
||||||
|
<span class="total">{totalPhotos}</span>
|
||||||
|
</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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user