Mobile view slider (#324)
* Mobile view slider --------- Co-authored-by: kvakazyambra <kvakazyambra@gmail.com>
This commit is contained in:
parent
e782ef348b
commit
ba71cbfdef
|
@ -16,7 +16,7 @@ import { slugify } from '../../utils/slugify'
|
|||
import { DropArea } from '../_shared/DropArea'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
import { ImageSwiper } from '../_shared/SolidSwiper'
|
||||
import { EditorSwiper } from '../_shared/SolidSwiper'
|
||||
import { Editor, Panel } from '../Editor'
|
||||
import { AudioUploader } from '../Editor/AudioUploader'
|
||||
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
||||
|
@ -369,8 +369,7 @@ export const EditView = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<Show when={props.shout.layout === 'image'}>
|
||||
<ImageSwiper
|
||||
editorMode={true}
|
||||
<EditorSwiper
|
||||
images={mediaItems()}
|
||||
onImageChange={handleMediaChange}
|
||||
onImageDelete={(index) => handleMediaDelete(index)}
|
||||
|
|
|
@ -36,7 +36,7 @@ export const ArticleCardSwiper = (props: Props) => {
|
|||
ref={(el) => (mainSwipeRef.current = el)}
|
||||
centered-slides={true}
|
||||
observer={true}
|
||||
space-between={20}
|
||||
space-between={10}
|
||||
breakpoints={{
|
||||
576: { spaceBetween: 20, slidesPerView: 1.5 },
|
||||
992: { spaceBetween: 52, slidesPerView: 1.5 },
|
||||
|
@ -44,13 +44,11 @@ export const ArticleCardSwiper = (props: Props) => {
|
|||
round-lengths={true}
|
||||
loop={true}
|
||||
speed={800}
|
||||
/*
|
||||
autoplay={{
|
||||
disableOnInteraction: false,
|
||||
delay: 6000,
|
||||
pauseOnMouseEnter: true
|
||||
pauseOnMouseEnter: true,
|
||||
}}
|
||||
*/
|
||||
>
|
||||
<For each={props.slides}>
|
||||
{(slide, index) => (
|
||||
|
|
326
src/components/_shared/SolidSwiper/EditorSwiper.tsx
Normal file
326
src/components/_shared/SolidSwiper/EditorSwiper.tsx
Normal file
|
@ -0,0 +1,326 @@
|
|||
import { createFileUploader } from '@solid-primitives/upload'
|
||||
import { clsx } from 'clsx'
|
||||
import { createEffect, createSignal, For, Show, on, onMount, lazy } from 'solid-js'
|
||||
import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSnackbar } from '../../../context/snackbar'
|
||||
import { MediaItem, UploadedFile } from '../../../pages/types'
|
||||
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
||||
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 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<string>()
|
||||
|
||||
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) {
|
||||
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)
|
||||
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 (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(async () => {
|
||||
const { register } = await import('swiper/element/bundle')
|
||||
register()
|
||||
SwiperCore.use([Pagination, Navigation, Manipulation])
|
||||
})
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.Swiper, styles.editorMode)}>
|
||||
<div class={styles.container}>
|
||||
<Show when={props.images.length === 0}>
|
||||
<DropArea
|
||||
fileType="image"
|
||||
isMultiply={true}
|
||||
placeholder={t('Add images')}
|
||||
onUpload={handleDropAreaUpload}
|
||||
description={
|
||||
<div>
|
||||
{t('You can upload up to 100 images in .jpg, .png format.')}
|
||||
<br />
|
||||
{t('Each image must be no larger than 5 MB.')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
<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}
|
||||
onBeforeSlideChangeStart={handleSaveBeforeSlideChange}
|
||||
space-between={20}
|
||||
>
|
||||
<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}>
|
||||
<Image src={slide.url} alt={slide.title} width={800} />
|
||||
|
||||
<Popover content={t('Delete')}>
|
||||
{(triggerRef: (el) => void) => (
|
||||
<div ref={triggerRef} onClick={() => handleDelete(index())} class={styles.action}>
|
||||
<Icon class={styles.icon} name="delete-white" />
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
)}
|
||||
</For>
|
||||
</swiper-container>
|
||||
<div
|
||||
class={clsx(styles.navigation, styles.prev, {
|
||||
[styles.disabled]: slideIndex() === 0,
|
||||
})}
|
||||
onClick={() => mainSwipeRef.current.swiper.slidePrev()}
|
||||
>
|
||||
<Icon name="swiper-l-arr" class={styles.icon} />
|
||||
</div>
|
||||
<div
|
||||
class={clsx(styles.navigation, styles.next, {
|
||||
[styles.disabled]: slideIndex() + 1 === props.images.length,
|
||||
})}
|
||||
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'}
|
||||
space-between={20}
|
||||
auto-scroll-offset={1}
|
||||
watch-overflow={true}
|
||||
watch-slides-visibility={true}
|
||||
direction={'horizontal'}
|
||||
slides-offset-after={160}
|
||||
slides-offset-before={30}
|
||||
>
|
||||
<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)}
|
||||
style={{
|
||||
'background-image': `url(${getImageUrl(slide.url, { width: 110, height: 75 })})`,
|
||||
}}
|
||||
>
|
||||
<div class={styles.thumbAction}>
|
||||
<div class={clsx(styles.action)} onClick={() => handleDelete(index())}>
|
||||
<Icon class={styles.icon} name="delete-white" />
|
||||
</div>
|
||||
<div
|
||||
class={clsx(styles.action, {
|
||||
[styles.hidden]: index() === 0,
|
||||
})}
|
||||
onClick={() => handleChangeIndex('left', index())}
|
||||
>
|
||||
<Icon
|
||||
class={styles.icon}
|
||||
name="arrow-right-white"
|
||||
style={{ transform: 'rotate(-180deg)' }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class={clsx(styles.action, {
|
||||
[styles.hidden]: index() === props.images.length - 1,
|
||||
})}
|
||||
onClick={() => handleChangeIndex('right', index())}
|
||||
>
|
||||
<Icon class={styles.icon} name="arrow-right-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<div class={styles.upload}>
|
||||
<div class={styles.inner} onClick={handleUploadThumb}>
|
||||
<Show when={!loading()} fallback={<Loading size="small" />}>
|
||||
<Icon name="swiper-plus" />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-container>
|
||||
<div
|
||||
class={clsx(styles.navigation, styles.thumbsNav, styles.prev, {
|
||||
[styles.disabled]: slideIndex() === 0,
|
||||
})}
|
||||
onClick={() => thumbSwipeRef.current.swiper.slidePrev()}
|
||||
>
|
||||
<Icon name="swiper-l-arr" class={styles.icon} />
|
||||
</div>
|
||||
<div
|
||||
class={clsx(styles.navigation, styles.thumbsNav, styles.next, {
|
||||
[styles.disabled]: slideIndex() + 1 === props.images.length,
|
||||
})}
|
||||
onClick={() => thumbSwipeRef.current.swiper.slideNext()}
|
||||
>
|
||||
<Icon name="swiper-r-arr" class={styles.icon} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={props.images.length > 0}>
|
||||
<div class={styles.description}>
|
||||
<input
|
||||
type="text"
|
||||
class={clsx(styles.input, styles.title)}
|
||||
placeholder={t('Enter image title')}
|
||||
value={props.images[slideIndex()]?.title}
|
||||
onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'title', event.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class={styles.input}
|
||||
placeholder={t('Specify the source and the name of the author')}
|
||||
value={props.images[slideIndex()]?.source}
|
||||
onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)}
|
||||
/>
|
||||
<SimplifiedEditor
|
||||
initialContent={props.images[slideIndex()]?.body}
|
||||
smallHeight={true}
|
||||
placeholder={t('Enter image description')}
|
||||
onChange={(value) => setSlideBody(value)}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,59 +1,34 @@
|
|||
import { createFileUploader } from '@solid-primitives/upload'
|
||||
import { clsx } from 'clsx'
|
||||
import { createEffect, createSignal, For, Show, on, onMount, lazy } from 'solid-js'
|
||||
import { createEffect, createSignal, For, Show, on, onMount, lazy, onCleanup } from 'solid-js'
|
||||
import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
|
||||
import { throttle } from 'throttle-debounce'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSnackbar } from '../../../context/snackbar'
|
||||
import { MediaItem, UploadedFile } from '../../../pages/types'
|
||||
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
||||
import { MediaItem } from '../../../pages/types'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
||||
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 styles from './Swiper.module.scss'
|
||||
|
||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||
|
||||
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<string>()
|
||||
const MIN_WIDTH = 540
|
||||
|
||||
export const ImageSwiper = (props: Props) => {
|
||||
const [slideIndex, setSlideIndex] = createSignal(0)
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
||||
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
||||
const swiperMainContainer: { current: HTMLDivElement } = { 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)
|
||||
|
@ -69,99 +44,41 @@ export const ImageSwiper = (props: Props) => {
|
|||
{ 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(async () => {
|
||||
const { register } = await import('swiper/element/bundle')
|
||||
register()
|
||||
SwiperCore.use([Pagination, Navigation, Manipulation])
|
||||
SwiperCore.use([Pagination, Navigation, Manipulation, ResizeObserver])
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
const updateDirection = () => {
|
||||
const width = window.innerWidth
|
||||
const direction = width > MIN_WIDTH ? 'vertical' : 'horizontal'
|
||||
if (direction === 'horizontal') {
|
||||
setIsMobileView(true)
|
||||
} else {
|
||||
setIsMobileView(false)
|
||||
}
|
||||
thumbSwipeRef.current?.swiper?.changeDirection(direction)
|
||||
}
|
||||
|
||||
updateDirection()
|
||||
|
||||
const handleResize = throttle(100, () => {
|
||||
updateDirection()
|
||||
})
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.Swiper, props.editorMode ? styles.editorMode : styles.articleMode)}>
|
||||
<div class={styles.container}>
|
||||
<Show when={props.editorMode && props.images.length === 0}>
|
||||
<DropArea
|
||||
fileType="image"
|
||||
isMultiply={true}
|
||||
placeholder={t('Add images')}
|
||||
onUpload={handleDropAreaUpload}
|
||||
description={
|
||||
<div>
|
||||
{t('You can upload up to 100 images in .jpg, .png format.')}
|
||||
<br />
|
||||
{t('Each image must be no larger than 5 MB.')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Show>
|
||||
<div class={clsx(styles.Swiper, styles.articleMode, { [styles.mobileView]: isMobileView() })}>
|
||||
<div class={styles.container} ref={(el) => (swiperMainContainer.current = el)}>
|
||||
<Show when={props.images.length > 0}>
|
||||
<div class={styles.holder}>
|
||||
<swiper-container
|
||||
|
@ -170,8 +87,7 @@ export const ImageSwiper = (props: Props) => {
|
|||
thumbs-swiper={'.thumbSwiper'}
|
||||
observer={true}
|
||||
onSlideChange={handleSlideChange}
|
||||
onBeforeSlideChangeStart={handleSaveBeforeSlideChange}
|
||||
space-between={20}
|
||||
space-between={isMobileView() ? 20 : 10}
|
||||
>
|
||||
<For each={props.images}>
|
||||
{(slide, index) => (
|
||||
|
@ -180,19 +96,6 @@ export const ImageSwiper = (props: Props) => {
|
|||
<swiper-slide lazy="true" virtual-index={index()}>
|
||||
<div class={styles.image}>
|
||||
<Image src={slide.url} alt={slide.title} width={800} />
|
||||
<Show when={props.editorMode}>
|
||||
<Popover content={t('Delete')}>
|
||||
{(triggerRef: (el) => void) => (
|
||||
<div
|
||||
ref={triggerRef}
|
||||
onClick={() => handleDelete(index())}
|
||||
class={styles.action}
|
||||
>
|
||||
<Icon class={styles.icon} name="delete-white" />
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
</Show>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
)}
|
||||
|
@ -224,13 +127,10 @@ export const ImageSwiper = (props: Props) => {
|
|||
class={'thumbSwiper'}
|
||||
ref={(el) => (thumbSwipeRef.current = el)}
|
||||
slides-per-view={'auto'}
|
||||
space-between={20}
|
||||
space-between={isMobileView() ? 20 : 10}
|
||||
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}
|
||||
>
|
||||
<For each={props.images}>
|
||||
{(slide, index) => (
|
||||
|
@ -242,47 +142,10 @@ export const ImageSwiper = (props: Props) => {
|
|||
style={{
|
||||
'background-image': `url(${getImageUrl(slide.url, { width: 110, height: 75 })})`,
|
||||
}}
|
||||
>
|
||||
<Show when={props.editorMode}>
|
||||
<div class={styles.thumbAction}>
|
||||
<div class={clsx(styles.action)} onClick={() => handleDelete(index())}>
|
||||
<Icon class={styles.icon} name="delete-white" />
|
||||
</div>
|
||||
<div
|
||||
class={clsx(styles.action, {
|
||||
[styles.hidden]: index() === 0,
|
||||
})}
|
||||
onClick={() => handleChangeIndex('left', index())}
|
||||
>
|
||||
<Icon
|
||||
class={styles.icon}
|
||||
name="arrow-right-white"
|
||||
style={{ transform: 'rotate(-180deg)' }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class={clsx(styles.action, {
|
||||
[styles.hidden]: index() === props.images.length - 1,
|
||||
})}
|
||||
onClick={() => handleChangeIndex('right', index())}
|
||||
>
|
||||
<Icon class={styles.icon} name="arrow-right-white" />
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
)}
|
||||
</For>
|
||||
<Show when={props.editorMode}>
|
||||
<div class={styles.upload}>
|
||||
<div class={styles.inner} onClick={handleUploadThumb}>
|
||||
<Show when={!loading()} fallback={<Loading size="small" />}>
|
||||
<Icon name="swiper-plus" />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</swiper-container>
|
||||
<div
|
||||
class={clsx(styles.navigation, styles.thumbsNav, styles.prev, {
|
||||
|
@ -304,9 +167,6 @@ export const ImageSwiper = (props: Props) => {
|
|||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<Show
|
||||
when={props.editorMode}
|
||||
fallback={
|
||||
<div class={styles.slideDescription}>
|
||||
<Show when={props.images[slideIndex()]?.title}>
|
||||
<div class={styles.articleTitle}>{props.images[slideIndex()].title}</div>
|
||||
|
@ -318,33 +178,6 @@ export const ImageSwiper = (props: Props) => {
|
|||
<div class={styles.body} innerHTML={props.images[slideIndex()].body} />
|
||||
</Show>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Show when={props.images.length > 0}>
|
||||
<div class={styles.description}>
|
||||
<input
|
||||
type="text"
|
||||
class={clsx(styles.input, styles.title)}
|
||||
placeholder={t('Enter image title')}
|
||||
value={props.images[slideIndex()]?.title}
|
||||
onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'title', event.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class={styles.input}
|
||||
placeholder={t('Specify the source and the name of the author')}
|
||||
value={props.images[slideIndex()]?.source}
|
||||
onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)}
|
||||
/>
|
||||
<SimplifiedEditor
|
||||
initialContent={props.images[slideIndex()]?.body}
|
||||
smallHeight={true}
|
||||
placeholder={t('Enter image description')}
|
||||
onChange={(value) => setSlideBody(value)}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
}
|
||||
|
||||
.container {
|
||||
// max-width: 800px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
padding: 24px 0;
|
||||
|
@ -48,6 +47,7 @@
|
|||
width: 100%;
|
||||
|
||||
.thumbsHolder {
|
||||
min-width: 110px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
|
@ -60,13 +60,6 @@
|
|||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
& > swiper-container {
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
bottom: 52px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.thumbsNav {
|
||||
height: 52px;
|
||||
padding: 14px 0;
|
||||
|
@ -92,6 +85,48 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
&.mobileView {
|
||||
.container {
|
||||
flex-direction: column-reverse;
|
||||
padding: 0;
|
||||
|
||||
.thumbsHolder {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.thumbs {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
padding: 0;
|
||||
|
||||
& swiper-slide {
|
||||
//bind to html element <swiper-slide/>
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
.thumbsNav {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
|
||||
.icon {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&.prev {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.next {
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.editorMode {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export { ImageSwiper } from './ImageSwiper'
|
||||
export { EditorSwiper } from './EditorSwiper'
|
||||
|
|
Loading…
Reference in New Issue
Block a user