mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
photoswipe element
This commit is contained in:
parent
55c806ce21
commit
19aeb8a334
16 changed files with 260 additions and 58 deletions
|
@ -85,6 +85,7 @@
|
||||||
"less-middleware": "~2.2.1",
|
"less-middleware": "~2.2.1",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.11.0",
|
||||||
|
"photoswipe": "^4.1.3",
|
||||||
"raleway-cyrillic": "^4.0.2",
|
"raleway-cyrillic": "^4.0.2",
|
||||||
"ramda": "^0.26.1",
|
"ramda": "^0.26.1",
|
||||||
"react": "16.13.0",
|
"react": "16.13.0",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { PRESETS } from '~/constants/urls';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
|
modalShowPhotoswipe: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeAudioImageBlock: FC<IProps> = ({ node }) => {
|
const NodeAudioImageBlock: FC<IProps> = ({ node }) => {
|
||||||
|
|
|
@ -1,27 +1,22 @@
|
||||||
import React, {
|
import React, { FC, useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
||||||
FC,
|
import { ImageSwitcher } from '../ImageSwitcher';
|
||||||
useMemo,
|
import * as styles from './styles.scss';
|
||||||
useState,
|
import { INode } from '~/redux/types';
|
||||||
useEffect,
|
import classNames from 'classnames';
|
||||||
useRef,
|
import { getURL } from '~/utils/dom';
|
||||||
useCallback
|
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||||
} from "react";
|
import { PRESETS } from '~/constants/urls';
|
||||||
import { ImageSwitcher } from "../ImageSwitcher";
|
import * as MODAL_ACTIONS from '~/redux/modal/actions';
|
||||||
import * as styles from "./styles.scss";
|
|
||||||
import { INode } from "~/redux/types";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { getURL } from "~/utils/dom";
|
|
||||||
import { UPLOAD_TYPES } from "~/redux/uploads/constants";
|
|
||||||
import { PRESETS } from "~/constants/urls";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_loading: boolean;
|
is_loading: boolean;
|
||||||
node: INode;
|
node: INode;
|
||||||
layout: {};
|
layout: {};
|
||||||
updateLayout: () => void;
|
updateLayout: () => void;
|
||||||
|
modalShowPhotoswipe: typeof MODAL_ACTIONS.modalShowPhotoswipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeImageBlock: FC<IProps> = ({ node, is_loading, updateLayout }) => {
|
const NodeImageBlock: FC<IProps> = ({ node, is_loading, updateLayout, modalShowPhotoswipe }) => {
|
||||||
const [is_animated, setIsAnimated] = useState(false);
|
const [is_animated, setIsAnimated] = useState(false);
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const [height, setHeight] = useState(320);
|
const [height, setHeight] = useState(320);
|
||||||
|
@ -30,36 +25,35 @@ const NodeImageBlock: FC<IProps> = ({ node, is_loading, updateLayout }) => {
|
||||||
|
|
||||||
const images = useMemo(
|
const images = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(node &&
|
(node && node.files && node.files.filter(({ type }) => type === UPLOAD_TYPES.IMAGE)) || [],
|
||||||
node.files &&
|
|
||||||
node.files.filter(({ type }) => type === UPLOAD_TYPES.IMAGE)) ||
|
|
||||||
[],
|
|
||||||
[node]
|
[node]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setRef = useCallback(index => el => (refs.current[index] = el), [refs]);
|
const setRef = useCallback(index => el => (refs.current[index] = el), [refs]);
|
||||||
const onImageLoad = useCallback(
|
const onImageLoad = useCallback(index => () => setLoaded({ ...loaded, [index]: true }), [
|
||||||
index => () => setLoaded({ ...loaded, [index]: true }),
|
setLoaded,
|
||||||
[setLoaded, loaded]
|
loaded,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onOpenPhotoSwipe = useCallback(
|
||||||
|
(index: number) => () => modalShowPhotoswipe(images, index),
|
||||||
|
[modalShowPhotoswipe, images]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => updateLayout(), [loaded]);
|
useEffect(() => updateLayout(), [loaded]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!refs || !refs.current[current] || !loaded[current])
|
if (!refs || !refs.current[current] || !loaded[current]) return setHeight(320);
|
||||||
return setHeight(320);
|
|
||||||
|
|
||||||
const el = refs.current[current];
|
const el = refs.current[current];
|
||||||
|
|
||||||
const element_height =
|
const element_height = el.getBoundingClientRect && el.getBoundingClientRect().height;
|
||||||
el.getBoundingClientRect && el.getBoundingClientRect().height;
|
|
||||||
|
|
||||||
setHeight(element_height);
|
setHeight(element_height);
|
||||||
}, [refs, current, loaded]);
|
}, [refs, current, loaded]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => setIsAnimated(true), 250);
|
const timer = setTimeout(() => setIsAnimated(true), 250);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -74,21 +68,19 @@ const NodeImageBlock: FC<IProps> = ({ node, is_loading, updateLayout }) => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.image_container} style={{ height }}>
|
<div className={styles.image_container} style={{ height }}>
|
||||||
{(is_loading || !loaded[0] || !images.length) && (
|
{(is_loading || !loaded[0] || !images.length) && <div className={styles.placeholder} />}
|
||||||
<div className={styles.placeholder} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{images.map((file, index) => (
|
{images.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.image_wrap, {
|
className={classNames(styles.image_wrap, {
|
||||||
is_active: index === current && loaded[index]
|
is_active: index === current && loaded[index],
|
||||||
})}
|
})}
|
||||||
ref={setRef(index)}
|
ref={setRef(index)}
|
||||||
key={file.id}
|
key={file.id}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
src={getURL(file, PRESETS["1600"])}
|
src={getURL(file, PRESETS['1600'])}
|
||||||
alt=""
|
alt=""
|
||||||
key={file.id}
|
key={file.id}
|
||||||
onLoad={onImageLoad(index)}
|
onLoad={onImageLoad(index)}
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import React, {
|
import React, { FC, useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
||||||
FC,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useCallback,
|
|
||||||
useLayoutEffect,
|
|
||||||
} from 'react';
|
|
||||||
import { ImageSwitcher } from '../ImageSwitcher';
|
import { ImageSwitcher } from '../ImageSwitcher';
|
||||||
import * as styles from './styles.scss';
|
import * as styles from './styles.scss';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
|
@ -17,17 +9,24 @@ import { getURL } from '~/utils/dom';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
import { throttle } from 'throttle-debounce';
|
import { throttle } from 'throttle-debounce';
|
||||||
|
import * as MODAL_ACTIONS from '~/redux/modal/actions';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_loading: boolean;
|
is_loading: boolean;
|
||||||
node: INode;
|
node: INode;
|
||||||
layout: {};
|
layout: {};
|
||||||
updateLayout: () => void;
|
updateLayout: () => void;
|
||||||
|
modalShowPhotoswipe: typeof MODAL_ACTIONS.modalShowPhotoswipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getX = event => (event.touches ? event.touches[0].clientX : event.clientX);
|
const getX = event => (event.touches ? event.touches[0].clientX : event.clientX);
|
||||||
|
|
||||||
const NodeImageSlideBlock: FC<IProps> = ({ node, is_loading, updateLayout }) => {
|
const NodeImageSlideBlock: FC<IProps> = ({
|
||||||
|
node,
|
||||||
|
is_loading,
|
||||||
|
updateLayout,
|
||||||
|
modalShowPhotoswipe,
|
||||||
|
}) => {
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const [height, setHeight] = useState(320);
|
const [height, setHeight] = useState(320);
|
||||||
const [max_height, setMaxHeight] = useState(960);
|
const [max_height, setMaxHeight] = useState(960);
|
||||||
|
@ -39,6 +38,8 @@ const NodeImageSlideBlock: FC<IProps> = ({ node, is_loading, updateLayout }) =>
|
||||||
const [initial_x, setInitialX] = useState(0);
|
const [initial_x, setInitialX] = useState(0);
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
const [is_dragging, setIsDragging] = useState(false);
|
const [is_dragging, setIsDragging] = useState(false);
|
||||||
|
const [drag_start_time, setDragStartTime] = useState(0);
|
||||||
|
|
||||||
const slide = useRef<HTMLDivElement>();
|
const slide = useRef<HTMLDivElement>();
|
||||||
const wrap = useRef<HTMLDivElement>();
|
const wrap = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
@ -166,12 +167,25 @@ const NodeImageSlideBlock: FC<IProps> = ({ node, is_loading, updateLayout }) =>
|
||||||
normalizeOffset();
|
normalizeOffset();
|
||||||
}, [wrap, setMaxHeight, normalizeOffset]);
|
}, [wrap, setMaxHeight, normalizeOffset]);
|
||||||
|
|
||||||
const stopDragging = useCallback(() => {
|
const onOpenPhotoSwipe = useCallback(() => modalShowPhotoswipe(images, current), [
|
||||||
|
modalShowPhotoswipe,
|
||||||
|
images,
|
||||||
|
current,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stopDragging = useCallback(
|
||||||
|
event => {
|
||||||
if (!is_dragging) return;
|
if (!is_dragging) return;
|
||||||
|
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
normalizeOffset();
|
normalizeOffset();
|
||||||
}, [setIsDragging, is_dragging, normalizeOffset]);
|
|
||||||
|
if (initial_x - event.clientX < 10) {
|
||||||
|
onOpenPhotoSwipe();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setIsDragging, is_dragging, normalizeOffset, onOpenPhotoSwipe]
|
||||||
|
);
|
||||||
|
|
||||||
const startDragging = useCallback(
|
const startDragging = useCallback(
|
||||||
event => {
|
event => {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { ProfileDialog } from '~/containers/dialogs/ProfileDialog';
|
||||||
import { RestoreRequestDialog } from '~/containers/dialogs/RestoreRequestDialog';
|
import { RestoreRequestDialog } from '~/containers/dialogs/RestoreRequestDialog';
|
||||||
import { RestorePasswordDialog } from '~/containers/dialogs/RestorePasswordDialog';
|
import { RestorePasswordDialog } from '~/containers/dialogs/RestorePasswordDialog';
|
||||||
import { DIALOGS } from '~/redux/modal/constants';
|
import { DIALOGS } from '~/redux/modal/constants';
|
||||||
|
import { PhotoSwipe } from '~/containers/dialogs/PhotoSwipe';
|
||||||
|
|
||||||
export const DIALOG_CONTENT = {
|
export const DIALOG_CONTENT = {
|
||||||
[DIALOGS.EDITOR_IMAGE]: EditorDialogImage,
|
[DIALOGS.EDITOR_IMAGE]: EditorDialogImage,
|
||||||
|
@ -22,6 +23,7 @@ export const DIALOG_CONTENT = {
|
||||||
[DIALOGS.PROFILE]: ProfileDialog,
|
[DIALOGS.PROFILE]: ProfileDialog,
|
||||||
[DIALOGS.RESTORE_REQUEST]: RestoreRequestDialog,
|
[DIALOGS.RESTORE_REQUEST]: RestoreRequestDialog,
|
||||||
[DIALOGS.RESTORE_PASSWORD]: RestorePasswordDialog,
|
[DIALOGS.RESTORE_PASSWORD]: RestorePasswordDialog,
|
||||||
|
[DIALOGS.PHOTOSWIPE]: PhotoSwipe,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NODE_EDITOR_DIALOGS = {
|
export const NODE_EDITOR_DIALOGS = {
|
||||||
|
|
121
src/containers/dialogs/PhotoSwipe/index.tsx
Normal file
121
src/containers/dialogs/PhotoSwipe/index.tsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import React, { FC, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import PhotoSwipeJs from 'photoswipe/dist/photoswipe.js';
|
||||||
|
import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default.js';
|
||||||
|
import 'photoswipe/dist/photoswipe.css';
|
||||||
|
import 'photoswipe/dist/default-skin/default-skin.css';
|
||||||
|
import { IState } from '~/redux/store';
|
||||||
|
import { selectModal } from '~/redux/modal/selectors';
|
||||||
|
import { getURL } from '~/utils/dom';
|
||||||
|
import { PRESETS } from '~/constants/urls';
|
||||||
|
import * as MODAL_ACTIONS from '~/redux/modal/actions';
|
||||||
|
import styles from './styles.scss';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: IState) => ({
|
||||||
|
photoswipe: selectModal(state).photoswipe,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
modalSetShown: MODAL_ACTIONS.modalSetShown,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||||
|
|
||||||
|
const PhotoSwipeUnconnected: FC<Props> = ({ photoswipe, modalSetShown }) => {
|
||||||
|
let ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const items = useMemo(
|
||||||
|
() =>
|
||||||
|
photoswipe.images.map(image => ({
|
||||||
|
src: getURL(image, window.innerWidth < 768 ? PRESETS[900] : ''),
|
||||||
|
})),
|
||||||
|
[photoswipe.images]
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeModal = useCallback(() => modalSetShown(false), [modalSetShown]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
new Promise(async resolve => {
|
||||||
|
const images = await Promise.all(
|
||||||
|
items.map(
|
||||||
|
item =>
|
||||||
|
new Promise(resolveImage => {
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
resolveImage({
|
||||||
|
src: item.src,
|
||||||
|
h: img.naturalHeight,
|
||||||
|
w: img.naturalWidth,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
resolveImage({});
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = item.src;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve(images);
|
||||||
|
}).then(images => {
|
||||||
|
const ps = new PhotoSwipeJs(ref.current, PhotoSwipeUI_Default, images, {
|
||||||
|
index: photoswipe.index || 0,
|
||||||
|
closeOnScroll: false,
|
||||||
|
history: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
ps.init();
|
||||||
|
ps.listen('destroy', closeModal);
|
||||||
|
ps.listen('close', closeModal);
|
||||||
|
});
|
||||||
|
}, [items, photoswipe.index]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pswp" tabIndex={-1} role="dialog" aria-hidden="true" ref={ref}>
|
||||||
|
<div className={classNames('pswp__bg', styles.bg)} />
|
||||||
|
<div className="pswp__scroll-wrap">
|
||||||
|
<div className="pswp__container">
|
||||||
|
<div className="pswp__item" />
|
||||||
|
<div className="pswp__item" />
|
||||||
|
<div className="pswp__item" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pswp__ui pswp__ui--hidden">
|
||||||
|
<div className={classNames('pswp__top-bar', styles.bar)}>
|
||||||
|
<div className="pswp__counter" />
|
||||||
|
<button className="pswp__button pswp__button--close" title="Close (Esc)" />
|
||||||
|
<button className="pswp__button pswp__button--fs" title="Toggle fullscreen" />
|
||||||
|
<button className="pswp__button pswp__button--zoom" title="Zoom in/out" />
|
||||||
|
<div className="pswp__preloader">
|
||||||
|
<div className="pswp__preloader__icn">
|
||||||
|
<div className="pswp__preloader__cut">
|
||||||
|
<div className="pswp__preloader__donut" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
|
||||||
|
<div className="pswp__share-tooltip" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="pswp__button pswp__button--arrow--left"
|
||||||
|
title="Previous (arrow left)"
|
||||||
|
/>
|
||||||
|
<button className="pswp__button pswp__button--arrow--right" title="Next (arrow right)" />
|
||||||
|
<div className="pswp__caption">
|
||||||
|
<div className="pswp__caption__center" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PhotoSwipe = connect(mapStateToProps, mapDispatchToProps)(PhotoSwipeUnconnected);
|
||||||
|
|
||||||
|
export { PhotoSwipe };
|
7
src/containers/dialogs/PhotoSwipe/styles.scss
Normal file
7
src/containers/dialogs/PhotoSwipe/styles.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.bg {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
|
@ -10,11 +10,9 @@ import { Group } from '~/components/containers/Group';
|
||||||
import { Padder } from '~/components/containers/Padder';
|
import { Padder } from '~/components/containers/Padder';
|
||||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
import { NodeRelated } from '~/components/node/NodeRelated';
|
import { NodeRelated } from '~/components/node/NodeRelated';
|
||||||
import * as styles from './styles.scss';
|
|
||||||
import { NodeComments } from '~/components/node/NodeComments';
|
import { NodeComments } from '~/components/node/NodeComments';
|
||||||
import { NodeTags } from '~/components/node/NodeTags';
|
import { NodeTags } from '~/components/node/NodeTags';
|
||||||
import { NODE_COMPONENTS, NODE_INLINES } from '~/redux/node/constants';
|
import { NODE_COMPONENTS, NODE_INLINES } from '~/redux/node/constants';
|
||||||
import * as NODE_ACTIONS from '~/redux/node/actions';
|
|
||||||
import { selectUser } from '~/redux/auth/selectors';
|
import { selectUser } from '~/redux/auth/selectors';
|
||||||
import pick from 'ramda/es/pick';
|
import pick from 'ramda/es/pick';
|
||||||
import { NodeRelatedPlaceholder } from '~/components/node/NodeRelated/placeholder';
|
import { NodeRelatedPlaceholder } from '~/components/node/NodeRelated/placeholder';
|
||||||
|
@ -23,7 +21,12 @@ import { NodeCommentForm } from '~/components/node/NodeCommentForm';
|
||||||
import { Sticky } from '~/components/containers/Sticky';
|
import { Sticky } from '~/components/containers/Sticky';
|
||||||
import { Footer } from '~/components/main/Footer';
|
import { Footer } from '~/components/main/Footer';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
import * as styles from './styles.scss';
|
||||||
|
import * as NODE_ACTIONS from '~/redux/node/actions';
|
||||||
|
import * as MODAL_ACTIONS from '~/redux/modal/actions';
|
||||||
|
import { IState } from '~/redux/store';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: IState) => ({
|
||||||
node: selectNode(state),
|
node: selectNode(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
@ -39,6 +42,7 @@ const mapDispatchToProps = {
|
||||||
nodeLockComment: NODE_ACTIONS.nodeLockComment,
|
nodeLockComment: NODE_ACTIONS.nodeLockComment,
|
||||||
nodeEditComment: NODE_ACTIONS.nodeEditComment,
|
nodeEditComment: NODE_ACTIONS.nodeEditComment,
|
||||||
nodeLoadMoreComments: NODE_ACTIONS.nodeLoadMoreComments,
|
nodeLoadMoreComments: NODE_ACTIONS.nodeLoadMoreComments,
|
||||||
|
modalShowPhotoswipe: MODAL_ACTIONS.modalShowPhotoswipe,
|
||||||
};
|
};
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> &
|
type IProps = ReturnType<typeof mapStateToProps> &
|
||||||
|
@ -71,6 +75,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
nodeLockComment,
|
nodeLockComment,
|
||||||
nodeEditComment,
|
nodeEditComment,
|
||||||
nodeLoadMoreComments,
|
nodeLoadMoreComments,
|
||||||
|
modalShowPhotoswipe,
|
||||||
}) => {
|
}) => {
|
||||||
const [layout, setLayout] = useState({});
|
const [layout, setLayout] = useState({});
|
||||||
|
|
||||||
|
@ -108,7 +113,8 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={styles.node} seamless>
|
<Card className={styles.node} seamless>
|
||||||
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
{block &&
|
||||||
|
createElement(block, { node, is_loading, updateLayout, layout, modalShowPhotoswipe })}
|
||||||
|
|
||||||
<NodePanel
|
<NodePanel
|
||||||
node={pick(['title', 'user', 'is_liked', 'is_heroic', 'deleted_at', 'created_at'], node)}
|
node={pick(['title', 'user', 'is_liked', 'is_heroic', 'deleted_at', 'created_at'], node)}
|
||||||
|
@ -137,6 +143,7 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
||||||
is_loading,
|
is_loading,
|
||||||
updateLayout,
|
updateLayout,
|
||||||
layout,
|
layout,
|
||||||
|
modalShowPhotoswipe,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { IModalState } from '~/redux/modal/reducer';
|
import { IModalState } from '~/redux/modal/reducer';
|
||||||
import { MODAL_ACTIONS } from '~/redux/modal/constants';
|
import { MODAL_ACTIONS } from '~/redux/modal/constants';
|
||||||
|
import { IFile } from '../types';
|
||||||
|
|
||||||
|
export const modalSet = (modal: Partial<IModalState>) => ({
|
||||||
|
type: MODAL_ACTIONS.SET,
|
||||||
|
modal,
|
||||||
|
});
|
||||||
|
|
||||||
export const modalSetShown = (is_shown: IModalState['is_shown']) => ({
|
export const modalSetShown = (is_shown: IModalState['is_shown']) => ({
|
||||||
is_shown,
|
is_shown,
|
||||||
|
@ -15,3 +21,9 @@ export const modalShowDialog = (dialog: IModalState['dialog']) => ({
|
||||||
dialog,
|
dialog,
|
||||||
type: MODAL_ACTIONS.SHOW_DIALOG,
|
type: MODAL_ACTIONS.SHOW_DIALOG,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const modalShowPhotoswipe = (images: IFile[], index: number) => ({
|
||||||
|
type: MODAL_ACTIONS.SHOW_PHOTOSWIPE,
|
||||||
|
images,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
|
|
@ -11,12 +11,17 @@ export const DIALOGS = {
|
||||||
RESTORE_REQUEST: 'RESTORE_REQUEST',
|
RESTORE_REQUEST: 'RESTORE_REQUEST',
|
||||||
RESTORE_PASSWORD: 'RESTORE_PASSWORD',
|
RESTORE_PASSWORD: 'RESTORE_PASSWORD',
|
||||||
TEST: 'TEST',
|
TEST: 'TEST',
|
||||||
|
PHOTOSWIPE: 'PHOTOSWIPE',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const prefix = 'MODAL.';
|
||||||
|
|
||||||
export const MODAL_ACTIONS = {
|
export const MODAL_ACTIONS = {
|
||||||
SET_SHOWN: 'MODAL.SET_SHOWN',
|
SET: `${prefix}SET`,
|
||||||
SET_DIALOG: 'SET_DIALOG',
|
SET_SHOWN: `${prefix}MODAL.SET_SHOWN`,
|
||||||
SHOW_DIALOG: 'SHOW_DIALOG',
|
SET_DIALOG: `${prefix}SET_DIALOG`,
|
||||||
|
SHOW_DIALOG: `${prefix}SHOW_DIALOG`,
|
||||||
|
SHOW_PHOTOSWIPE: `${prefix}SHOW_PHOTOSWIPE`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IDialogProps {
|
export interface IDialogProps {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { MODAL_ACTIONS } from '~/redux/modal/constants';
|
import { MODAL_ACTIONS } from '~/redux/modal/constants';
|
||||||
|
import { modalSet } from './actions';
|
||||||
|
|
||||||
|
const setState = (state, { modal }: ReturnType<typeof modalSet>) => ({ ...state, ...modal });
|
||||||
const setShown = (state, { is_shown }) => ({ ...state, is_shown });
|
const setShown = (state, { is_shown }) => ({ ...state, is_shown });
|
||||||
const showDialog = (state, { dialog }) => ({ ...state, dialog, is_shown: true });
|
const showDialog = (state, { dialog }) => ({ ...state, dialog, is_shown: true });
|
||||||
const setDialog = (state, { dialog }) => ({ ...state, dialog });
|
const setDialog = (state, { dialog }) => ({ ...state, dialog });
|
||||||
|
|
||||||
export const MODAL_HANDLERS = {
|
export const MODAL_HANDLERS = {
|
||||||
|
[MODAL_ACTIONS.SET]: setState,
|
||||||
[MODAL_ACTIONS.SET_SHOWN]: setShown,
|
[MODAL_ACTIONS.SET_SHOWN]: setShown,
|
||||||
[MODAL_ACTIONS.SHOW_DIALOG]: showDialog,
|
[MODAL_ACTIONS.SHOW_DIALOG]: showDialog,
|
||||||
[MODAL_ACTIONS.SET_DIALOG]: setDialog,
|
[MODAL_ACTIONS.SET_DIALOG]: setDialog,
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
import { MODAL_HANDLERS } from '~/redux/modal/handlers';
|
import { MODAL_HANDLERS } from '~/redux/modal/handlers';
|
||||||
import { createReducer } from '~/utils/reducer';
|
import { createReducer } from '~/utils/reducer';
|
||||||
import { DIALOGS } from '~/redux/modal/constants';
|
import { DIALOGS } from '~/redux/modal/constants';
|
||||||
import { ValueOf } from '~/redux/types';
|
import { ValueOf, IFile } from '~/redux/types';
|
||||||
|
|
||||||
export interface IModalState {
|
export interface IModalState {
|
||||||
is_shown: boolean;
|
is_shown: boolean;
|
||||||
dialog: ValueOf<typeof DIALOGS>;
|
dialog: ValueOf<typeof DIALOGS>;
|
||||||
|
photoswipe: {
|
||||||
|
images: IFile[];
|
||||||
|
index: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_STATE: IModalState = {
|
const INITIAL_STATE: IModalState = {
|
||||||
is_shown: false,
|
is_shown: false,
|
||||||
dialog: null,
|
dialog: null,
|
||||||
|
photoswipe: {
|
||||||
|
images: [],
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(INITIAL_STATE, MODAL_HANDLERS);
|
export default createReducer(INITIAL_STATE, MODAL_HANDLERS);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { takeEvery, put } from 'redux-saga/effects';
|
import { takeEvery, put } from 'redux-saga/effects';
|
||||||
import { LocationChangeAction, LOCATION_CHANGE } from 'connected-react-router';
|
import { LocationChangeAction, LOCATION_CHANGE } from 'connected-react-router';
|
||||||
import { authOpenProfile, authShowRestoreModal } from '../auth/actions';
|
import { authOpenProfile, authShowRestoreModal } from '../auth/actions';
|
||||||
|
import { MODAL_ACTIONS, DIALOGS } from './constants';
|
||||||
|
import { modalShowPhotoswipe, modalSet } from './actions';
|
||||||
|
|
||||||
function* onPathChange({
|
function* onPathChange({
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -18,6 +20,21 @@ function* onPathChange({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* onShowPhotoswipe({ images, index }: ReturnType<typeof modalShowPhotoswipe>) {
|
||||||
|
console.log({ images, index });
|
||||||
|
yield put(
|
||||||
|
modalSet({
|
||||||
|
dialog: DIALOGS.PHOTOSWIPE,
|
||||||
|
is_shown: true,
|
||||||
|
photoswipe: {
|
||||||
|
images,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function* modalSaga() {
|
export function* modalSaga() {
|
||||||
yield takeEvery(LOCATION_CHANGE, onPathChange);
|
yield takeEvery(LOCATION_CHANGE, onPathChange);
|
||||||
|
yield takeEvery(MODAL_ACTIONS.SHOW_PHOTOSWIPE, onShowPhotoswipe);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { EditorImageUploadButton } from '~/components/editors/EditorImageUploadB
|
||||||
import { EditorAudioUploadButton } from '~/components/editors/EditorAudioUploadButton';
|
import { EditorAudioUploadButton } from '~/components/editors/EditorAudioUploadButton';
|
||||||
import { EditorUploadCoverButton } from '~/components/editors/EditorUploadCoverButton';
|
import { EditorUploadCoverButton } from '~/components/editors/EditorUploadCoverButton';
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from '~/components/containers/Filler';
|
||||||
|
import { modalShowPhotoswipe } from '../modal/actions';
|
||||||
|
|
||||||
const prefix = 'NODE.';
|
const prefix = 'NODE.';
|
||||||
export const NODE_ACTIONS = {
|
export const NODE_ACTIONS = {
|
||||||
|
@ -77,7 +78,13 @@ export const NODE_TYPES = {
|
||||||
|
|
||||||
type INodeComponents = Record<
|
type INodeComponents = Record<
|
||||||
ValueOf<typeof NODE_TYPES>,
|
ValueOf<typeof NODE_TYPES>,
|
||||||
FC<{ node: INode; is_loading: boolean; layout: {}; updateLayout: () => void }>
|
FC<{
|
||||||
|
node: INode;
|
||||||
|
is_loading: boolean;
|
||||||
|
layout: {};
|
||||||
|
updateLayout: () => void;
|
||||||
|
modalShowPhotoswipe: typeof modalShowPhotoswipe;
|
||||||
|
}>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const NODE_COMPONENTS: INodeComponents = {
|
export const NODE_COMPONENTS: INodeComponents = {
|
||||||
|
|
|
@ -122,7 +122,7 @@ module.exports = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|svg)$/,
|
test: /\.(png|svg|gif)$/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
options: {},
|
options: {},
|
||||||
|
|
|
@ -7179,6 +7179,11 @@ performance-now@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||||
|
|
||||||
|
photoswipe@^4.1.3:
|
||||||
|
version "4.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-4.1.3.tgz#59f49494eeb9ddab5888d03392926a19bc197550"
|
||||||
|
integrity sha512-89Z43IRUyw7ycTolo+AaiDn3W1EEIfox54hERmm9bI12IB9cvRfHSHez3XhAyU8XW2EAFrC+2sKMhh7SJwn0bA==
|
||||||
|
|
||||||
pify@^2.0.0:
|
pify@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue