refactor: migrate to Svelte 5

use runes ($props, $state, $derived, $effect, etc)
This commit is contained in:
2024-08-02 17:50:16 +02:00
parent 245049222b
commit 6f8a619af2
60 changed files with 1120 additions and 859 deletions

View File

@@ -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>

View File

@@ -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