import React, { FC, useMemo, useState, useEffect, useRef, useCallback } from 'react'; import { ImageSwitcher } from '../ImageSwitcher'; import * as styles from './styles.scss'; import { INode } from '~/redux/types'; import classNames from 'classnames'; import { getImageSize } from '~/utils/dom'; import { UPLOAD_TYPES } from '~/redux/uploads/constants'; import { NODE_SETTINGS } from '~/redux/node/constants'; interface IProps { is_loading: boolean; node: INode; layout: {}; updateLayout: () => void; } const getX = event => (event.touches ? event.touches[0].clientX : event.clientX); const NodeImageSlideBlock: FC = ({ node, is_loading, updateLayout }) => { const [current, setCurrent] = useState(0); const [height, setHeight] = useState(320); const [max_height, setMaxHeight] = useState(960); const [loaded, setLoaded] = useState>({}); const refs = useRef>({}); const [heights, setHeights] = useState({}); const [initial_offset, setInitialOffset] = useState(0); const [initial_x, setInitialX] = useState(0); const [offset, setOffset] = useState(0); const [is_dragging, setIsDragging] = useState(false); const slide = useRef(); const wrap = useRef(); const images = useMemo( () => (node && node.files && node.files.filter(({ type }) => type === UPLOAD_TYPES.IMAGE)) || [], [node] ); const updateSizes = useCallback(() => { const values = Object.keys(refs.current).reduce((obj, key) => { const ref = refs.current[key]; if (!ref || !ref.getBoundingClientRect) return 0; return { ...obj, [key]: ref.getBoundingClientRect().height }; }, {}); setHeights(values); }, [refs]); const setRef = useCallback( index => el => { refs.current[index] = el; }, [refs, heights, setHeights] ); const onImageLoad = useCallback(index => () => setLoaded({ ...loaded, [index]: true }), [ setLoaded, loaded, ]); // update outside hooks useEffect(() => updateLayout(), [loaded, height]); useEffect(() => updateSizes(), [refs, current, loaded]); useEffect(() => { if (!wrap || !wrap.current) return; const { width } = wrap.current.getBoundingClientRect(); const selected = Math.abs(-offset / width); const prev = Math.max(heights[Math.floor(selected)] || 320, 320); const next = Math.max(heights[Math.ceil(selected)] || 320, 320); const now = prev - (prev - next) * (selected % 1); if (current !== Math.round(selected)) setCurrent(Math.round(selected)); setHeight(now); }, [offset, heights, max_height]); const onDrag = useCallback( event => { if ( !is_dragging || !slide.current || !wrap.current || (event.touches && event.clientY > event.clientX) ) return; const { width: slide_width } = slide.current.getBoundingClientRect(); const { width: wrap_width } = wrap.current.getBoundingClientRect(); setOffset( Math.min(Math.max(initial_offset + getX(event) - initial_x, wrap_width - slide_width), 0) ); }, [is_dragging, initial_x, setOffset, initial_offset] ); const normalizeOffset = useCallback(() => { const { width: wrap_width } = wrap.current.getBoundingClientRect(); setOffset(Math.round(offset / wrap_width) * wrap_width); }, [wrap, offset]); const updateMaxHeight = useCallback(() => { if (!wrap.current) return; const { width } = wrap.current.getBoundingClientRect(); setMaxHeight(width * NODE_SETTINGS.MAX_IMAGE_ASPECT); normalizeOffset(); }, [wrap, setMaxHeight, normalizeOffset]); const stopDragging = useCallback(() => { if (!is_dragging) return; normalizeOffset(); setIsDragging(false); }, [setIsDragging, is_dragging, normalizeOffset]); const startDragging = useCallback( event => { setIsDragging(true); setInitialX(getX(event)); setInitialOffset(offset); }, [setIsDragging, setInitialX, offset, setInitialOffset] ); useEffect(() => updateMaxHeight(), []); useEffect(() => { window.addEventListener('resize', updateSizes); window.addEventListener('resize', updateMaxHeight); window.addEventListener('mousemove', onDrag); window.addEventListener('touchmove', onDrag); window.addEventListener('mouseup', stopDragging); window.addEventListener('touchend', stopDragging); return () => { window.removeEventListener('resize', updateSizes); window.removeEventListener('resize', updateMaxHeight); window.removeEventListener('mousemove', onDrag); window.removeEventListener('touchmove', onDrag); window.removeEventListener('mouseup', stopDragging); window.removeEventListener('touchend', stopDragging); }; }, [onDrag, stopDragging, updateMaxHeight, updateSizes]); const changeCurrent = useCallback( (item: number) => { const { width } = wrap.current.getBoundingClientRect(); setOffset(-1 * item * width); }, [wrap] ); return (
{(is_loading || !loaded[0] || !images.length) &&
} {images.map((file, index) => (
))}
); }; export { NodeImageSlideBlock };