refactor: migrate to Svelte 5
use runes ($props, $state, $derived, $effect, etc)
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { page, navigating } from '$app/stores'
|
||||
import { onMount } from 'svelte'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
@@ -24,34 +23,33 @@
|
||||
import NewsletterModule from '$components/organisms/NewsletterModule/NewsletterModule.svelte'
|
||||
import ShopModule from '$components/organisms/ShopModule/ShopModule.svelte'
|
||||
|
||||
export let data
|
||||
let { data } = $props()
|
||||
|
||||
let photos = $state<any[]>(data.photos)
|
||||
let totalPhotos = $state(data.totalPhotos)
|
||||
|
||||
let { photos, totalPhotos }: { photos: any[], totalPhotos: number } = data
|
||||
$: ({ photos, totalPhotos } = data)
|
||||
const { location, product = undefined }: { location: any, totalPhotos: number, product: any } = data
|
||||
const { params } = $page
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
let introEl: HTMLElement
|
||||
let photosListEl: HTMLElement
|
||||
let scrollY: number
|
||||
let introEl = $state<HTMLElement>()
|
||||
let photosListEl = $state<HTMLElement>()
|
||||
let scrollY = $state<number>()
|
||||
let observerPhotos: IntersectionObserver
|
||||
let mutationPhotos: MutationObserver
|
||||
let currentPage = 1
|
||||
let ended: boolean
|
||||
let currentPhotosAmount: number
|
||||
let heroOffsetY = 0
|
||||
let currentPage = $state(1)
|
||||
let currentPhotosAmount = $derived(photos.length)
|
||||
let heroOffsetY = $state(0)
|
||||
|
||||
$: latestPhoto = photos[0]
|
||||
$: currentPhotosAmount = photos.length
|
||||
$: ended = currentPhotosAmount === totalPhotos
|
||||
const ended = $derived(currentPhotosAmount === totalPhotos)
|
||||
const latestPhoto = $derived(photos[0])
|
||||
|
||||
|
||||
/**
|
||||
* Load photos
|
||||
*/
|
||||
// Load more photos from CTA
|
||||
/** Load more photos from CTA */
|
||||
const loadMorePhotos = async () => {
|
||||
// Append more photos from API
|
||||
const newPhotos: any = await loadPhotos(currentPage + 1)
|
||||
@@ -64,9 +62,6 @@
|
||||
// Increment the current page
|
||||
currentPage++
|
||||
}
|
||||
|
||||
// Increment the currently visible amount of photos
|
||||
currentPhotosAmount += newPhotos.length
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,12 +97,14 @@
|
||||
/**
|
||||
* Add parallax on illustration when scrolling
|
||||
*/
|
||||
$: if (scrollY && scrollY < introEl.offsetHeight) {
|
||||
heroOffsetY = scrollY * 0.1
|
||||
}
|
||||
$effect(() => {
|
||||
if (scrollY && scrollY < introEl.offsetHeight) {
|
||||
heroOffsetY = scrollY * 0.1
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
// Define location's last seen state
|
||||
$seenLocations = JSON.stringify({
|
||||
// Add existing values
|
||||
@@ -264,9 +261,7 @@
|
||||
</Button>
|
||||
|
||||
{#if location.has_poster}
|
||||
<Button size="medium" url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
|
||||
<!-- <IconEarth /> -->
|
||||
</Button>
|
||||
<Button size="medium" url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,7 +304,7 @@
|
||||
ended={ended}
|
||||
current={currentPhotosAmount}
|
||||
total={totalPhotos}
|
||||
on:click={() => !ended && loadMorePhotos()}
|
||||
onclick={() => !ended && loadMorePhotos()}
|
||||
>
|
||||
{#if !ended}
|
||||
<p class="more">See more photos</p>
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment'
|
||||
import { page, navigating } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import { onMount, tick } from 'svelte'
|
||||
import { goto, replaceState } from '$app/navigation'
|
||||
import { tick } from 'svelte'
|
||||
import { fade, scale } from 'svelte/transition'
|
||||
import { quartOut } from 'svelte/easing'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -24,70 +23,74 @@
|
||||
import IconArrow from '$components/atoms/IconArrow.svelte'
|
||||
import ButtonCircle from '$components/atoms/ButtonCircle/ButtonCircle.svelte'
|
||||
|
||||
export let data
|
||||
|
||||
let { photos, currentIndex }: { photos: any[], currentIndex: number } = data
|
||||
let { data } = $props()
|
||||
const { location, countPhotos, limit, offset }: { location: any, countPhotos: number, limit: number, offset: number } = data
|
||||
|
||||
enum directions { PREV, NEXT }
|
||||
|
||||
let innerWidth: number
|
||||
let fullscreenEl: HTMLElement
|
||||
let globalOffset = offset
|
||||
let isLoading = false
|
||||
let isFullscreen = false
|
||||
let hasNext = offset + limit < countPhotos
|
||||
let hasPrev = offset > 0
|
||||
let innerWidth = $state<number>()
|
||||
let fullscreenEl = $state<HTMLElement>()
|
||||
let photos = $state<any[]>(data.photos)
|
||||
let currentIndex = $state(data.currentIndex)
|
||||
let globalOffset = $state(offset)
|
||||
let isLoading = $state(false)
|
||||
let isFullscreen = $state(false)
|
||||
let hasNext = $state(offset + limit < countPhotos)
|
||||
let hasPrev = $state(offset > 0)
|
||||
|
||||
// Define if we can navigate depending on loading state, existing photos and index being first or last
|
||||
$: canGoPrev = !isLoading && (hasNext || currentIndex !== photos.length - 1)
|
||||
$: canGoNext = !isLoading && (hasPrev || currentIndex !== 0)
|
||||
const canGoPrev = $derived(!isLoading && (hasNext || currentIndex !== photos.length - 1))
|
||||
const canGoNext = $derived(!isLoading && (hasPrev || currentIndex !== 0))
|
||||
|
||||
// Define current photo
|
||||
$: currentPhoto = photos[currentIndex]
|
||||
$: currentPhotoIndex = globalOffset + currentIndex + 1
|
||||
const currentPhoto = $derived(photos[currentIndex])
|
||||
const currentPhotoIndex = $derived(globalOffset + currentIndex + 1)
|
||||
|
||||
// Take 7 photos in the global photos array (5 for current, 1 before first and 1 after last)
|
||||
// Start one index before the current image since the first one will be invisible
|
||||
$: sliceStart = Math.max(currentIndex - 1, 0)
|
||||
$: visiblePhotos = photos.slice(sliceStart, sliceStart + 7)
|
||||
const sliceStart = $derived(Math.max(currentIndex - 1, 0))
|
||||
const visiblePhotos = $derived(photos.slice(sliceStart, sliceStart + 7))
|
||||
|
||||
// Load previous photos
|
||||
$: if (browser && currentIndex === 0 && hasPrev) {
|
||||
loadPhotos(photos[0].id)
|
||||
}
|
||||
// Load next photos
|
||||
$: if (browser && currentIndex === photos.length - 5 && hasNext) {
|
||||
loadPhotos(photos[photos.length - 1].id, directions.NEXT)
|
||||
}
|
||||
$effect(() => {
|
||||
// Load previous photos
|
||||
if (currentIndex === 0 && hasPrev) {
|
||||
loadPhotos(photos[0].id)
|
||||
}
|
||||
|
||||
// Change URL to current photo slug
|
||||
$: if (browser && currentPhoto) {
|
||||
window.history.replaceState(null, '', $page.url.pathname.replace($page.params.photo, currentPhoto.slug))
|
||||
}
|
||||
// Load next photos
|
||||
if (currentIndex === photos.length - 5 && hasNext) {
|
||||
loadPhotos(photos[photos.length - 1].id, directions.NEXT)
|
||||
}
|
||||
|
||||
// Change URL to current photo slug
|
||||
if (currentPhoto) {
|
||||
replaceState('', $page.url.pathname.replace($page.params.photo, currentPhoto.slug))
|
||||
}
|
||||
})
|
||||
|
||||
// Define previous URL
|
||||
$: previousUrl = $previousPage ? $previousPage : `/${location.country.slug}/${location.slug}`
|
||||
const previousUrl = $derived($previousPage ? $previousPage : `/${location.country.slug}/${location.slug}`)
|
||||
|
||||
|
||||
/**
|
||||
* Photo navigation
|
||||
*/
|
||||
// Go to next photo
|
||||
/** Go to next photo */
|
||||
const goToNext = throttle(() => {
|
||||
canGoPrev && currentIndex++
|
||||
}, 200)
|
||||
|
||||
// Go to previous photo
|
||||
/** Go to previous photo */
|
||||
const goToPrevious = throttle(() => {
|
||||
canGoNext && (currentIndex = Math.max(currentIndex - 1, 0))
|
||||
}, 200)
|
||||
|
||||
// Close viewer and go to previous page
|
||||
/** Close viewer and go to previous page */
|
||||
const closeViewer = () => {
|
||||
goto(previousUrl, { replaceState: false, noScroll: true, keepFocus: true })
|
||||
}
|
||||
|
||||
// Enable navigation with keyboard
|
||||
/** Enable navigation with keyboard */
|
||||
const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => {
|
||||
if (defaultPrevented) return
|
||||
switch (key) {
|
||||
@@ -98,7 +101,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Enable swipe gestures
|
||||
/** Enable swipe gestures */
|
||||
const handleSwipe = ({ detail }: CustomEvent<string>) => {
|
||||
// Swipe up and down on mobile/small screens
|
||||
if (innerWidth < 992) {
|
||||
@@ -214,7 +217,7 @@
|
||||
}
|
||||
|
||||
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
@@ -289,14 +292,14 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth on:keydown={handleKeydown} />
|
||||
<svelte:window bind:innerWidth onkeydown={handleKeydown} />
|
||||
|
||||
{#if currentPhoto}
|
||||
<Metas
|
||||
title="{currentPhoto.title} - Houses Of {location.name}"
|
||||
description="Photo of a beautiful home from {location.name}, {location.country.name}"
|
||||
image={getAssetUrlKey(currentPhoto.image.id, 'share')}
|
||||
/>
|
||||
<Metas
|
||||
title="{currentPhoto.title} - Houses Of {location.name}"
|
||||
description="Photo of a beautiful home from {location.name}, {location.country.name}"
|
||||
image={getAssetUrlKey(currentPhoto.image.id, 'share')}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -317,10 +320,11 @@
|
||||
</ButtonCircle>
|
||||
|
||||
<div class="photo-page__carousel">
|
||||
<div class="photo-page__images"
|
||||
<div
|
||||
use:swipe
|
||||
on:swipe={handleSwipe}
|
||||
on:tap={toggleFullscreen}
|
||||
class="photo-page__images"
|
||||
onswipe={handleSwipe}
|
||||
ontap={toggleFullscreen}
|
||||
>
|
||||
{#each visiblePhotos as { id, image, title }, index (id)}
|
||||
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}">
|
||||
@@ -340,10 +344,10 @@
|
||||
{/each}
|
||||
|
||||
<div class="photo-page__controls">
|
||||
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} on:click={goToPrevious}>
|
||||
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} onclick={goToPrevious}>
|
||||
<IconArrow color="pink" flip={true} />
|
||||
</ButtonCircle>
|
||||
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} on:click={goToNext}>
|
||||
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} onclick={goToNext}>
|
||||
<IconArrow color="pink" />
|
||||
</ButtonCircle>
|
||||
</div>
|
||||
@@ -378,10 +382,13 @@
|
||||
</div>
|
||||
|
||||
{#if isFullscreen}
|
||||
<div class="photo-page__fullscreen" bind:this={fullscreenEl}
|
||||
on:click={toggleFullscreen} on:keydown
|
||||
<div
|
||||
bind:this={fullscreenEl}
|
||||
class="photo-page__fullscreen"
|
||||
onclick={toggleFullscreen}
|
||||
in:fade={{ easing: quartOut, duration: 1000 }}
|
||||
out:fade={{ easing: quartOut, duration: 1000, delay: 300 }}
|
||||
role="presentation"
|
||||
>
|
||||
<div class="inner" transition:scale={{ easing: quartOut, start: 1.1, duration: 1000 }}>
|
||||
<Image
|
||||
|
||||
Reference in New Issue
Block a user