import { createEffect, createSignal, For, Show, on, onMount } from 'solid-js' import { MediaItem, UploadedFile } from '../../../pages/types' import { Icon } from '../Icon' import { Popover } from '../Popover' import { useLocalize } from '../../../context/localize' import { register } from 'swiper/element/bundle' import { DropArea } from '../DropArea' import { createFileUploader } from '@solid-primitives/upload' import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' import { SwiperRef } from './swiper' import { validateFiles } from '../../../utils/validateFile' import { useSnackbar } from '../../../context/snackbar' import { Loading } from '../Loading' import { clsx } from 'clsx' import styles from './Swiper.module.scss' import { composeMediaItems } from '../../../utils/composeMediaItems' import SimplifiedEditor from '../../Editor/SimplifiedEditor' import { handleImageUpload } from '../../../utils/handleImageUpload' import { getImageUrl } from '../../../utils/getImageUrl' import { Image } from '../Image' type Props = { images: MediaItem[] editorMode?: boolean onImagesAdd?: (value: MediaItem[]) => void onImagesSorted?: (value: MediaItem[]) => void onImageDelete?: (mediaItemIndex: number) => void onImageChange?: (index: number, value: MediaItem) => void } export const ImageSwiper = (props: Props) => { const { t } = useLocalize() const [loading, setLoading] = createSignal(false) const [slideIndex, setSlideIndex] = createSignal(0) const [slideBody, setSlideBody] = createSignal() const mainSwipeRef: { current: SwiperRef } = { current: null } const thumbSwipeRef: { current: SwiperRef } = { current: null } const { actions: { showSnackbar } } = useSnackbar() const handleSlideDescriptionChange = (index: number, field: string, value) => { if (props.onImageChange) { props.onImageChange(index, { ...props.images[index], [field]: value }) } } const swipeToUploaded = () => { setTimeout(() => { mainSwipeRef.current.swiper.slideTo(props.images.length - 1) }, 0) } 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() }, { defer: true } ) ) const handleDropAreaUpload = (value: UploadedFile[]) => { props.onImagesAdd(composeMediaItems(value)) swipeToUploaded() } const handleDelete = (index: number) => { props.onImageDelete(index) if (index === 0) { mainSwipeRef.current.swiper.update() } else { mainSwipeRef.current.swiper.slideTo(index - 1) } } const { selectFiles } = createFileUploader({ multiple: true, accept: `image/*` }) const initUpload = async (selectedFiles) => { const isValid = validateFiles('image', selectedFiles) if (isValid) { try { setLoading(true) const results: UploadedFile[] = [] for (const file of selectedFiles) { const result = await handleImageUpload(file) results.push(result) } props.onImagesAdd(composeMediaItems(results)) setLoading(false) swipeToUploaded() } catch (error) { await showSnackbar({ type: 'error', body: t('Error') }) console.error('[runUpload]', error) setLoading(false) } } else { await showSnackbar({ type: 'error', body: t('Invalid file type') }) setLoading(false) return false } } const handleUploadThumb = async () => { selectFiles((selectedFiles) => { initUpload(selectedFiles) }) } const handleChangeIndex = (direction: 'left' | 'right', index: number) => { const images = [...props.images] if (direction === 'left' && index > 0) { const copy = images.splice(index, 1)[0] images.splice(index - 1, 0, copy) } else if (direction === 'right' && index < images.length - 1) { const copy = images.splice(index, 1)[0] images.splice(index + 1, 0, copy) } props.onImagesSorted(images) setTimeout(() => { mainSwipeRef.current.swiper.slideTo(direction === 'left' ? index - 1 : index + 1) }, 0) } const handleSaveBeforeSlideChange = () => { handleSlideDescriptionChange(slideIndex(), 'body', slideBody()) } onMount(() => { register() SwiperCore.use([Pagination, Navigation, Manipulation]) }) return (
{t('You can upload up to 100 images in .jpg, .png format.')}
{t('Each image must be no larger than 5 MB.')}
} /> 0}>
(mainSwipeRef.current = el)} slides-per-view={1} thumbs-swiper={'.thumbSwiper'} observer={true} onSlideChange={handleSlideChange} onBeforeSlideChangeStart={handleSaveBeforeSlideChange} space-between={20} > {(slide, index) => ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore
{slide.title} {(triggerRef: (el) => void) => (
handleDelete(index())} class={styles.action} >
)}
)}
mainSwipeRef.current.swiper.slidePrev()} >
mainSwipeRef.current.swiper.slideNext()} >
{slideIndex() + 1} / {props.images.length}
(thumbSwipeRef.current = el)} slides-per-view={'auto'} space-between={20} auto-scroll-offset={1} watch-overflow={true} watch-slides-visibility={true} direction={props.editorMode ? 'horizontal' : 'vertical'} slides-offset-after={props.editorMode && 160} slides-offset-before={props.editorMode && 30} > {(slide, index) => ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore
handleDelete(index())}>
handleChangeIndex('left', index())} >
handleChangeIndex('right', index())} >
)}
}>
thumbSwipeRef.current.swiper.slidePrev()} >
thumbSwipeRef.current.swiper.slideNext()} >
{props.images[slideIndex()].title}
{props.images[slideIndex()].source}
} > 0}>
handleSlideDescriptionChange(slideIndex(), 'title', event.target.value)} /> handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)} /> setSlideBody(value)} />
) }