- Easier to read and write - Also fixes fullscreen when leaving the viewer, my bad
214 lines
7.2 KiB
Svelte
214 lines
7.2 KiB
Svelte
<script>
|
|
import { onMount, createEventDispatcher } from 'svelte'
|
|
import { stores } from '@sapper/app'
|
|
import { currentLocation, fullscreen } from 'utils/store'
|
|
import { getThumbnail, formatDate } from 'utils/functions'
|
|
|
|
// Dependencies
|
|
import SwipeListener from 'swipe-listener'
|
|
|
|
// Animations
|
|
import { animateIn } from 'animations/Carousel'
|
|
|
|
// Components
|
|
import IconArrow from 'atoms/IconArrow'
|
|
import Counter from 'atoms/Counter'
|
|
import PaginationDots from 'molecules/PaginationDots'
|
|
|
|
// Props
|
|
export let photos
|
|
export let viewer = false
|
|
|
|
// Variables
|
|
const dispatch = createEventDispatcher()
|
|
const { page } = stores()
|
|
let scope
|
|
let swiped
|
|
let currentIndex = 0
|
|
|
|
// Reactive variables from currentIndex
|
|
$: currentPhoto = photos[currentIndex] || null
|
|
$: prevPhoto = photos[currentIndex - 1] || photos[photos.length - 1]
|
|
$: nextPhoto = photos[currentIndex + 1] || photos[0]
|
|
|
|
|
|
/*
|
|
** Navigate to a photo
|
|
*/
|
|
// Change current index from param
|
|
const goToPhoto = to => {
|
|
if (to === 'prev') {
|
|
currentIndex--
|
|
currentIndex = (currentIndex < 0) ? photos.length - 1 : currentIndex
|
|
} else if (to === 'next') {
|
|
currentIndex++
|
|
currentIndex = (currentIndex >= photos.length) ? 0 : currentIndex
|
|
} else {
|
|
currentIndex = to
|
|
}
|
|
|
|
// Dispatch current photo
|
|
dispatch('photoChange', photos[currentIndex])
|
|
// Reset fullscreen value if open
|
|
if ($fullscreen) fullscreen.set()
|
|
}
|
|
|
|
// Hover on controls
|
|
const hoverPhoto = event => {
|
|
const button = event.currentTarget.querySelector('button')
|
|
const photoActive = scope.querySelector('.gallery__photo--active')
|
|
|
|
let photoToHover
|
|
if (event.currentTarget.dataset.to === 'prev') {
|
|
photoToHover = (photoActive.previousSibling != null) ? photoActive.previousSibling : photoActive.parentNode.lastChild
|
|
} else {
|
|
photoToHover = (photoActive.nextSibling != null) ? photoActive.nextSibling : photoActive.parentNode.firstChild
|
|
}
|
|
|
|
// Toggle class and focus of the button
|
|
if (event.type === 'mouseenter') {
|
|
photoToHover.classList.add('hover')
|
|
button.classList.add('hover')
|
|
} else if (event.type === 'mouseleave') {
|
|
photoToHover.classList.remove('hover')
|
|
button.classList.remove('hover')
|
|
}
|
|
}
|
|
|
|
// Open fullscreen
|
|
const openFullscreen = event => {
|
|
if (!swiped) {
|
|
fullscreen.set(currentPhoto)
|
|
}
|
|
// Reset swiped event if fired
|
|
swiped = false
|
|
}
|
|
|
|
|
|
/*
|
|
** Navigation
|
|
*/
|
|
// Drag and swipe
|
|
const swipe = directions => {
|
|
swiped = true
|
|
|
|
if (directions.right) {
|
|
goToPhoto('prev')
|
|
} else if (directions.left) {
|
|
goToPhoto('next')
|
|
}
|
|
}
|
|
|
|
// Keyboard navigation
|
|
const keyboardNav = event => {
|
|
if ([37,80,74].includes(event.keyCode)) {
|
|
goToPhoto('prev')
|
|
} else if ([39,78,75].includes(event.keyCode)) {
|
|
goToPhoto('next')
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** Run code when mounted
|
|
*/
|
|
onMount(() => {
|
|
// Entering transition
|
|
if (photos.length) {
|
|
animateIn(scope)
|
|
}
|
|
|
|
// Enable gestures
|
|
const touch = SwipeListener(scope)
|
|
|
|
// Viewer: Navigate to photo on init and URL change
|
|
if (viewer) {
|
|
page.subscribe(page => {
|
|
if (page.path.includes('/viewer/')) {
|
|
goToPhoto(photos.findIndex(photo => photo.slug === page.params.photo))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<svelte:window on:keydown={keyboardNav} />
|
|
|
|
{#if photos.length}
|
|
<div class="carousel"
|
|
bind:this={scope}
|
|
on:swipe={event => swipe(event.detail.directions)}
|
|
>
|
|
<div class="wrap">
|
|
<div class="gallery">
|
|
<div class="gallery__images">
|
|
{#each photos as { id, name, location, image }, index}
|
|
<picture class="gallery__photo"
|
|
class:gallery__photo--prev={id === prevPhoto.id}
|
|
class:gallery__photo--active={id === currentPhoto.id}
|
|
class:gallery__photo--next={id === nextPhoto.id}
|
|
on:click={openFullscreen}
|
|
>
|
|
<source media="(min-width: 968px)" srcset={getThumbnail(image.private_hash, 1400)}>
|
|
<source media="(min-width: 800px)" srcset={getThumbnail(image.private_hash, 900)}>
|
|
<source media="(min-width: 500px)" srcset={getThumbnail(image.private_hash, 600)}>
|
|
<source media="(min-width: 300px)" srcset={getThumbnail(image.private_hash, 400)}>
|
|
<img src="{getThumbnail(image.private_hash, 900)}" alt="{name}, {location.name}, {location.country.name}">
|
|
</picture>
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="carousel__controls">
|
|
<div class="carousel__area carousel__area--prev" data-to="prev" rel="prev"
|
|
on:mouseenter={hoverPhoto} on:mouseleave={hoverPhoto}
|
|
on:click={() => goToPhoto('prev')}
|
|
>
|
|
<button class="button-control button-control--white dir-left" aria-label="Previous">
|
|
<IconArrow direction="left" color="#ff6c89" class="icon" />
|
|
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
|
</button>
|
|
</div>
|
|
|
|
<div class="carousel__area carousel__area--next" data-to="next" rel="next"
|
|
on:mouseenter={hoverPhoto} on:mouseleave={hoverPhoto}
|
|
on:click={() => goToPhoto('next')}
|
|
>
|
|
<button class="button-control button-control--white dir-right" aria-label="Next">
|
|
<IconArrow direction="right" color="#ff6c89" class="icon" />
|
|
<IconArrow direction="right" color="#fff" class="icon" hidden="true" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{#if viewer}
|
|
<Counter {currentIndex} className="carousel__number" />
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="carousel__infos">
|
|
<div class="carousel__locations">
|
|
{#each photos as { id, name, location }, index}
|
|
<div class="carousel__location style-location"
|
|
class:carousel__location--prev={id === prevPhoto.id}
|
|
class:carousel__location--active={id === currentPhoto.id}
|
|
class:carousel__location--next={id === nextPhoto.id}
|
|
>
|
|
<p class="street">{name}</p>
|
|
<p class="state style-caps style-caps--transparent">
|
|
{location.name}, {location.country.name}
|
|
</p>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{#if viewer}
|
|
<p class="carousel__date">{formatDate(currentPhoto.date, 'FULL')}</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<PaginationDots className="carousel__dots" {photos} {currentIndex}
|
|
on:goToIndex={event => currentIndex = event.detail}
|
|
/>
|
|
</div>
|
|
{/if}
|