2023-11-14 15:10:00 +00:00
|
|
|
import { clsx } from 'clsx'
|
2023-12-15 13:45:34 +00:00
|
|
|
import { createEffect, createSignal, For, Show, on, onMount, onCleanup } from 'solid-js'
|
2023-07-02 05:08:42 +00:00
|
|
|
import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
|
2023-12-04 10:05:29 +00:00
|
|
|
import { throttle } from 'throttle-debounce'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
import { MediaItem } from '../../../pages/types'
|
2023-10-27 18:50:13 +00:00
|
|
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Icon } from '../Icon'
|
2023-10-27 18:50:13 +00:00
|
|
|
import { Image } from '../Image'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
|
|
|
import { SwiperRef } from './swiper'
|
|
|
|
|
|
|
|
import styles from './Swiper.module.scss'
|
2023-07-02 05:08:42 +00:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
images: MediaItem[]
|
|
|
|
onImagesAdd?: (value: MediaItem[]) => void
|
|
|
|
onImagesSorted?: (value: MediaItem[]) => void
|
|
|
|
onImageDelete?: (mediaItemIndex: number) => void
|
|
|
|
onImageChange?: (index: number, value: MediaItem) => void
|
|
|
|
}
|
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
const MIN_WIDTH = 540
|
|
|
|
|
2023-11-02 10:34:38 +00:00
|
|
|
export const ImageSwiper = (props: Props) => {
|
2023-07-02 05:08:42 +00:00
|
|
|
const [slideIndex, setSlideIndex] = createSignal(0)
|
2023-12-04 10:05:29 +00:00
|
|
|
const [isMobileView, setIsMobileView] = createSignal(false)
|
2023-07-02 05:08:42 +00:00
|
|
|
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
|
|
|
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
2023-12-04 10:05:29 +00:00
|
|
|
const swiperMainContainer: { current: HTMLDivElement } = { current: null }
|
2023-07-02 05:08:42 +00:00
|
|
|
|
|
|
|
const handleSlideChange = () => {
|
|
|
|
thumbSwipeRef.current.swiper.slideTo(mainSwipeRef.current.swiper.activeIndex)
|
|
|
|
setSlideIndex(mainSwipeRef.current.swiper.activeIndex)
|
|
|
|
}
|
|
|
|
|
|
|
|
createEffect(
|
|
|
|
on(
|
|
|
|
() => props.images.length,
|
|
|
|
() => {
|
|
|
|
mainSwipeRef.current?.swiper.update()
|
|
|
|
thumbSwipeRef.current?.swiper.update()
|
2023-09-01 14:28:50 +00:00
|
|
|
},
|
2023-11-14 15:10:00 +00:00
|
|
|
{ defer: true },
|
|
|
|
),
|
2023-07-02 05:08:42 +00:00
|
|
|
)
|
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
onMount(async () => {
|
|
|
|
const { register } = await import('swiper/element/bundle')
|
|
|
|
register()
|
2023-12-04 10:34:37 +00:00
|
|
|
SwiperCore.use([Pagination, Navigation, Manipulation])
|
2023-07-02 05:08:42 +00:00
|
|
|
})
|
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
onMount(() => {
|
|
|
|
const updateDirection = () => {
|
|
|
|
const width = window.innerWidth
|
|
|
|
const direction = width > MIN_WIDTH ? 'vertical' : 'horizontal'
|
|
|
|
if (direction === 'horizontal') {
|
|
|
|
setIsMobileView(true)
|
|
|
|
} else {
|
|
|
|
setIsMobileView(false)
|
2023-07-02 05:08:42 +00:00
|
|
|
}
|
2023-12-04 10:05:29 +00:00
|
|
|
thumbSwipeRef.current?.swiper?.changeDirection(direction)
|
2023-07-02 05:08:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
updateDirection()
|
2023-07-02 05:08:42 +00:00
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
const handleResize = throttle(100, () => {
|
|
|
|
updateDirection()
|
|
|
|
})
|
2023-08-17 10:36:27 +00:00
|
|
|
|
2023-12-04 10:05:29 +00:00
|
|
|
window.addEventListener('resize', handleResize)
|
|
|
|
|
|
|
|
onCleanup(() => {
|
|
|
|
window.removeEventListener('resize', handleResize)
|
|
|
|
})
|
2023-11-13 15:35:07 +00:00
|
|
|
})
|
|
|
|
|
2023-07-02 05:08:42 +00:00
|
|
|
return (
|
2023-12-04 10:05:29 +00:00
|
|
|
<div class={clsx(styles.Swiper, styles.articleMode, { [styles.mobileView]: isMobileView() })}>
|
|
|
|
<div class={styles.container} ref={(el) => (swiperMainContainer.current = el)}>
|
2023-07-02 05:08:42 +00:00
|
|
|
<Show when={props.images.length > 0}>
|
|
|
|
<div class={styles.holder}>
|
|
|
|
<swiper-container
|
|
|
|
ref={(el) => (mainSwipeRef.current = el)}
|
|
|
|
slides-per-view={1}
|
|
|
|
thumbs-swiper={'.thumbSwiper'}
|
|
|
|
observer={true}
|
|
|
|
onSlideChange={handleSlideChange}
|
2023-12-04 10:05:29 +00:00
|
|
|
space-between={isMobileView() ? 20 : 10}
|
2023-07-02 05:08:42 +00:00
|
|
|
>
|
|
|
|
<For each={props.images}>
|
|
|
|
{(slide, index) => (
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
// @ts-ignore
|
|
|
|
<swiper-slide lazy="true" virtual-index={index()}>
|
|
|
|
<div class={styles.image}>
|
2023-11-18 14:10:02 +00:00
|
|
|
<Image src={slide.url} alt={slide.title} width={800} />
|
2023-07-02 05:08:42 +00:00
|
|
|
</div>
|
|
|
|
</swiper-slide>
|
|
|
|
)}
|
|
|
|
</For>
|
|
|
|
</swiper-container>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.navigation, styles.prev, {
|
2023-11-14 15:10:00 +00:00
|
|
|
[styles.disabled]: slideIndex() === 0,
|
2023-07-02 05:08:42 +00:00
|
|
|
})}
|
|
|
|
onClick={() => mainSwipeRef.current.swiper.slidePrev()}
|
|
|
|
>
|
|
|
|
<Icon name="swiper-l-arr" class={styles.icon} />
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.navigation, styles.next, {
|
2023-11-14 15:10:00 +00:00
|
|
|
[styles.disabled]: slideIndex() + 1 === props.images.length,
|
2023-07-02 05:08:42 +00:00
|
|
|
})}
|
|
|
|
onClick={() => mainSwipeRef.current.swiper.slideNext()}
|
|
|
|
>
|
|
|
|
<Icon name="swiper-r-arr" class={styles.icon} />
|
|
|
|
</div>
|
|
|
|
<div class={styles.counter}>
|
|
|
|
{slideIndex() + 1} / {props.images.length}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class={clsx(styles.holder, styles.thumbsHolder)}>
|
|
|
|
<div class={styles.thumbs}>
|
|
|
|
<swiper-container
|
|
|
|
class={'thumbSwiper'}
|
|
|
|
ref={(el) => (thumbSwipeRef.current = el)}
|
|
|
|
slides-per-view={'auto'}
|
2023-12-04 10:05:29 +00:00
|
|
|
space-between={isMobileView() ? 20 : 10}
|
2023-07-02 05:08:42 +00:00
|
|
|
auto-scroll-offset={1}
|
|
|
|
watch-overflow={true}
|
|
|
|
watch-slides-visibility={true}
|
|
|
|
>
|
|
|
|
<For each={props.images}>
|
|
|
|
{(slide, index) => (
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
// @ts-ignore
|
|
|
|
<swiper-slide virtual-index={index()} style={{ width: 'auto', height: 'auto' }}>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.imageThumb)}
|
2023-10-27 18:50:13 +00:00
|
|
|
style={{
|
2023-11-14 15:10:00 +00:00
|
|
|
'background-image': `url(${getImageUrl(slide.url, { width: 110, height: 75 })})`,
|
2023-10-27 18:50:13 +00:00
|
|
|
}}
|
2023-12-04 10:05:29 +00:00
|
|
|
/>
|
2023-07-02 05:08:42 +00:00
|
|
|
</swiper-slide>
|
|
|
|
)}
|
|
|
|
</For>
|
|
|
|
</swiper-container>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.navigation, styles.thumbsNav, styles.prev, {
|
2023-11-14 15:10:00 +00:00
|
|
|
[styles.disabled]: slideIndex() === 0,
|
2023-07-02 05:08:42 +00:00
|
|
|
})}
|
|
|
|
onClick={() => thumbSwipeRef.current.swiper.slidePrev()}
|
|
|
|
>
|
2023-07-17 22:24:37 +00:00
|
|
|
<Icon name="swiper-l-arr" class={styles.icon} />
|
2023-07-02 05:08:42 +00:00
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.navigation, styles.thumbsNav, styles.next, {
|
2023-11-14 15:10:00 +00:00
|
|
|
[styles.disabled]: slideIndex() + 1 === props.images.length,
|
2023-07-02 05:08:42 +00:00
|
|
|
})}
|
|
|
|
onClick={() => thumbSwipeRef.current.swiper.slideNext()}
|
|
|
|
>
|
2023-07-17 22:24:37 +00:00
|
|
|
<Icon name="swiper-r-arr" class={styles.icon} />
|
2023-07-02 05:08:42 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
2023-12-04 10:05:29 +00:00
|
|
|
<div class={styles.slideDescription}>
|
|
|
|
<Show when={props.images[slideIndex()]?.title}>
|
|
|
|
<div class={styles.articleTitle}>{props.images[slideIndex()].title}</div>
|
|
|
|
</Show>
|
|
|
|
<Show when={props.images[slideIndex()]?.source}>
|
|
|
|
<div class={styles.source}>{props.images[slideIndex()].source}</div>
|
2023-07-28 19:53:21 +00:00
|
|
|
</Show>
|
2023-12-04 10:05:29 +00:00
|
|
|
<Show when={props.images[slideIndex()]?.body}>
|
|
|
|
<div class={styles.body} innerHTML={props.images[slideIndex()].body} />
|
|
|
|
</Show>
|
|
|
|
</div>
|
2023-07-02 05:08:42 +00:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|