import { UploadFile, createFileUploader } from '@solid-primitives/upload' import { clsx } from 'clsx' import { For, Show, createEffect, createSignal, lazy, on, onMount } from 'solid-js' import SwiperCore from 'swiper' import { Manipulation, Navigation, Pagination } from 'swiper/modules' import { useLocalize } from '~/context/localize' import { useSnackbar } from '~/context/ui' import { getImageUrl } from '~/lib/getImageUrl' import { handleImageUpload } from '~/lib/handleImageUpload' import { composeMediaItems } from '~/utils/composeMediaItems' import { validateFiles } from '~/utils/validateFile' import { DropArea } from '../DropArea' import { Icon } from '../Icon' import { Image } from '../Image' import { Loading } from '../Loading' import { Popover } from '../Popover' import { SwiperRef } from './swiper' import { useSession } from '~/context/session' import { MediaItem } from '~/types/mediaitem' import { UploadedFile } from '~/types/upload' import styles from './Swiper.module.scss' const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) type Props = { images: MediaItem[] onImagesAdd?: (value: MediaItem[]) => void onImagesSorted?: (value: MediaItem[]) => void onImageDelete?: (mediaItemIndex: number) => void onImageChange?: (index: number, value: MediaItem) => void } export const EditorSwiper = (props: Props) => { const { t } = useLocalize() const [loading, setLoading] = createSignal(false) const [slideIndex, setSlideIndex] = createSignal(0) const [slideBody, setSlideBody] = createSignal() const { session } = useSession() let mainSwipeRef: SwiperRef | null let thumbSwipeRef: SwiperRef | null const { showSnackbar } = useSnackbar() const handleSlideDescriptionChange = (index: number, field: string, value: string | undefined) => { if (props.onImageChange) { props.onImageChange(index, { ...props.images[index], [field]: value }) } } const swipeToUploaded = () => { setTimeout(() => { mainSwipeRef?.swiper.slideTo(props.images.length - 1) }, 0) } const handleSlideChange = () => { thumbSwipeRef?.swiper.slideTo(mainSwipeRef?.swiper.activeIndex || 0) setSlideIndex(mainSwipeRef?.swiper.activeIndex || 0) } createEffect( on( () => props.images.length, (_) => { mainSwipeRef?.swiper.update() thumbSwipeRef?.swiper.update() }, { defer: true } ) ) const handleDropAreaUpload = (value: UploadedFile[]) => { props.onImagesAdd?.(composeMediaItems(value)) swipeToUploaded() } const handleDelete = (index: number) => { props.onImageDelete?.(index) if (index === 0) { mainSwipeRef?.swiper.update() } else { mainSwipeRef?.swiper.slideTo(index - 1) } } const { selectFiles } = createFileUploader({ multiple: true, accept: 'image/*' }) const initUpload = async (selectedFiles: UploadFile[]) => { const isValid = validateFiles('image', selectedFiles) if (!isValid) { await showSnackbar({ type: 'error', body: t('Invalid file type') }) setLoading(false) return } try { setLoading(true) const results: UploadedFile[] = [] for (const file of selectedFiles) { const result = await handleImageUpload(file, session()?.access_token || '') results.push(result) } props.onImagesAdd?.(composeMediaItems(results)) setLoading(false) swipeToUploaded() } catch (error) { console.error('[runUpload]', error) showSnackbar({ type: 'error', body: t('Error') }) setLoading(false) } } const handleUploadThumb = async () => { selectFiles((selectedFiles) => { initUpload(selectedFiles) }) } const handleChangeIndex = (direction: 'left' | 'right', index: number) => { const images = [...props.images] if (images?.length > 0) { 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?.swiper.slideTo(direction === 'left' ? index - 1 : index + 1) }, 0) } } const handleSaveBeforeSlideChange = () => { handleSlideDescriptionChange(slideIndex(), 'body', slideBody()) } onMount(async () => { const { register } = await import('swiper/element/bundle') 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 = 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: HTMLElement) => void) => (
handleDelete(index())} class={styles.action}>
)}
)}
mainSwipeRef?.swiper.slidePrev()} >
mainSwipeRef?.swiper.slideNext()} >
{slideIndex() + 1} / {props.images.length}
(thumbSwipeRef = el)} slides-per-view={'auto'} space-between={20} auto-scroll-offset={1} watch-overflow={true} watch-slides-visibility={true} direction={'horizontal'} slides-offset-after={160} slides-offset-before={30} > {(slide, index) => ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore
handleDelete(index())}>
handleChangeIndex('left', index())} >
handleChangeIndex('right', index())} >
)}
}>
thumbSwipeRef?.swiper.slidePrev()} >
thumbSwipeRef?.swiper.slideNext()} >
0}>
handleSlideDescriptionChange(slideIndex(), 'title', event.target.value)} /> handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)} /> setSlideBody(value)} />
) }