1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

photoswipe element

This commit is contained in:
Fedor Katurov 2020-04-20 18:04:09 +07:00
parent 55c806ce21
commit 19aeb8a334
16 changed files with 260 additions and 58 deletions

View file

@ -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",

View file

@ -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 }) => {

View file

@ -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)}

View file

@ -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), [
if (!is_dragging) return; modalShowPhotoswipe,
images,
current,
]);
setIsDragging(false); const stopDragging = useCallback(
normalizeOffset(); event => {
}, [setIsDragging, is_dragging, normalizeOffset]); if (!is_dragging) return;
setIsDragging(false);
normalizeOffset();
if (initial_x - event.clientX < 10) {
onOpenPhotoSwipe();
}
},
[setIsDragging, is_dragging, normalizeOffset, onOpenPhotoSwipe]
);
const startDragging = useCallback( const startDragging = useCallback(
event => { event => {

View file

@ -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 = {

View 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 };

View file

@ -0,0 +1,7 @@
.bg {
background-color: transparent;
}
.bar {
background-color: transparent !important;
}

View file

@ -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>
)} )}

View file

@ -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,
});

View file

@ -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 {

View file

@ -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,

View file

@ -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);

View file

@ -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);
} }

View file

@ -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 = {

View file

@ -122,7 +122,7 @@ module.exports = () => {
}, },
}, },
{ {
test: /\.(png|svg)$/, test: /\.(png|svg|gif)$/,
use: { use: {
loader: 'file-loader', loader: 'file-loader',
options: {}, options: {},

View file

@ -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"