diff --git a/src/components/containers/BlurWrapper/styles.scss b/src/components/containers/BlurWrapper/styles.scss index e9fb9aec..253b89c5 100644 --- a/src/components/containers/BlurWrapper/styles.scss +++ b/src/components/containers/BlurWrapper/styles.scss @@ -1,4 +1,7 @@ .blur { filter: blur(0); transition: filter 0.25s; + max-height: 100vh; + width: 100vw; + overflow: visible auto; } diff --git a/src/components/containers/PageCover/index.tsx b/src/components/containers/PageCover/index.tsx new file mode 100644 index 00000000..c461a2a8 --- /dev/null +++ b/src/components/containers/PageCover/index.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react'; +import * as styles from './styles.scss'; +import { createPortal } from 'react-dom'; +import { selectNode } from '~/redux/node/selectors'; +import { connect } from 'react-redux'; +import pick from 'ramda/es/pick'; +import { getURL } from '~/utils/dom'; + +const mapStateToProps = state => pick(['current_cover_image'], selectNode(state)); + +type IProps = ReturnType & {}; + +const PageCoverUnconnected: FC = ({ current_cover_image }) => + current_cover_image + ? createPortal( +
, + document.body + ) + : null; + +const PageCover = connect(mapStateToProps)(PageCoverUnconnected); +export { PageCover }; diff --git a/src/components/containers/PageCover/styles.scss b/src/components/containers/PageCover/styles.scss new file mode 100644 index 00000000..4ad4866e --- /dev/null +++ b/src/components/containers/PageCover/styles.scss @@ -0,0 +1,30 @@ +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.wrap { + position: absolute; + top: 0; + left: 0; + z-index: -1; + background: 50% 50% no-repeat; + background-size: cover; + width: 100%; + height: 100%; + animation: fadeIn 2s; + + &::after { + content: ' '; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url(~/sprites/dots.svg) rgba(0, 0, 0, 0.5); + } +} diff --git a/src/containers/App.tsx b/src/containers/App.tsx index 1b988ac6..fb02ef7b 100644 --- a/src/containers/App.tsx +++ b/src/containers/App.tsx @@ -14,18 +14,22 @@ import { URLS } from '~/constants/urls'; import { Modal } from '~/containers/dialogs/Modal'; import { selectModal } from '~/redux/modal/selectors'; import { BlurWrapper } from '~/components/containers/BlurWrapper'; +import { PageCover } from '~/components/containers/PageCover'; import { NodeLayout } from './node/NodeLayout'; import { BottomContainer } from '~/containers/main/BottomContainer'; -const mapStateToProps = selectModal; +const mapStateToProps = state => ({ + modal: selectModal(state), +}); const mapDispatchToProps = {}; type IProps = typeof mapDispatchToProps & ReturnType & {}; -const Component: FC = ({ is_shown }) => ( +const Component: FC = ({ modal: { is_shown } }) => (
+ diff --git a/src/containers/node/NodeLayout/index.tsx b/src/containers/node/NodeLayout/index.tsx index 9aa29fb9..43da34de 100644 --- a/src/containers/node/NodeLayout/index.tsx +++ b/src/containers/node/NodeLayout/index.tsx @@ -26,6 +26,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = { nodeLoadNode: NODE_ACTIONS.nodeLoadNode, nodeUpdateTags: NODE_ACTIONS.nodeUpdateTags, + nodeSetCoverImage: NODE_ACTIONS.nodeSetCoverImage, nodeEdit: NODE_ACTIONS.nodeEdit, nodeLike: NODE_ACTIONS.nodeLike, }; @@ -38,13 +39,14 @@ const NodeLayoutUnconnected: FC = ({ match: { params: { id }, }, - node: { is_loading, is_loading_comments, comments = [], current: node }, + node: { is_loading, is_loading_comments, comments = [], current: node, current_cover_image }, user, - user: { is_user, role }, + user: { is_user }, nodeLoadNode, nodeUpdateTags, nodeEdit, nodeLike, + nodeSetCoverImage, }) => { const [layout, setLayout] = useState({}); @@ -71,6 +73,12 @@ const NodeLayoutUnconnected: FC = ({ const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]); const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]); + useEffect(() => { + if (!node.cover) return; + nodeSetCoverImage(node.cover); + return () => nodeSetCoverImage(null); + }, [nodeSetCoverImage, node.cover]); + return ( {block && createElement(block, { node, is_loading, updateLayout, layout })} diff --git a/src/redux/node/actions.ts b/src/redux/node/actions.ts index 4d3dd3d7..56dd72a3 100644 --- a/src/redux/node/actions.ts +++ b/src/redux/node/actions.ts @@ -1,4 +1,4 @@ -import { INode, IValidationErrors, IComment, ITag } from '../types'; +import { INode, IValidationErrors, IComment, ITag, IFile } from '../types'; import { NODE_ACTIONS, NODE_TYPES } from './constants'; import { INodeState } from './reducer'; @@ -84,3 +84,8 @@ export const nodeSetEditor = (editor: INode) => ({ type: NODE_ACTIONS.SET_EDITOR, editor, }); + +export const nodeSetCoverImage = (current_cover_image: IFile) => ({ + type: NODE_ACTIONS.SET_COVER_IMAGE, + current_cover_image, +}); diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts index e5c79811..74026ed5 100644 --- a/src/redux/node/constants.ts +++ b/src/redux/node/constants.ts @@ -35,6 +35,7 @@ export const NODE_ACTIONS = { UPDATE_TAGS: `${prefix}UPDATE_TAGS`, SET_TAGS: `${prefix}SET_TAGS`, + SET_COVER_IMAGE: `${prefix}SET_COVER_IMAGE`, }; export const EMPTY_NODE: INode = { @@ -99,8 +100,10 @@ export const NODE_EDITORS = { }; export const NODE_PANEL_COMPONENTS = { + [NODE_TYPES.TEXT]: [EditorUploadCoverButton], + [NODE_TYPES.VIDEO]: [EditorUploadCoverButton], [NODE_TYPES.IMAGE]: [EditorImageUploadButton, EditorUploadCoverButton], - [NODE_TYPES.AUDIO]: [EditorAudioUploadButton, EditorImageUploadButton], + [NODE_TYPES.AUDIO]: [EditorAudioUploadButton, EditorImageUploadButton, EditorUploadCoverButton], }; export const NODE_EDITOR_DATA: Record< diff --git a/src/redux/node/handlers.ts b/src/redux/node/handlers.ts index 3ed37d47..ef7d677c 100644 --- a/src/redux/node/handlers.ts +++ b/src/redux/node/handlers.ts @@ -10,6 +10,7 @@ import { nodeSetCommentData, nodeSetTags, nodeSetEditor, + nodeSetCoverImage, } from './actions'; import { INodeState } from './reducer'; @@ -46,6 +47,11 @@ const setTags = (state: INodeState, { tags }: ReturnType) => const setEditor = (state: INodeState, { editor }: ReturnType) => assocPath(['editor'], editor, state); +const setCoverImage = ( + state: INodeState, + { current_cover_image }: ReturnType +) => assocPath(['current_cover_image'], current_cover_image, state); + export const NODE_HANDLERS = { [NODE_ACTIONS.SET_SAVE_ERRORS]: setSaveErrors, [NODE_ACTIONS.SET_LOADING]: setLoading, @@ -56,4 +62,5 @@ export const NODE_HANDLERS = { [NODE_ACTIONS.SET_COMMENT_DATA]: setCommentData, [NODE_ACTIONS.SET_TAGS]: setTags, [NODE_ACTIONS.SET_EDITOR]: setEditor, + [NODE_ACTIONS.SET_COVER_IMAGE]: setCoverImage, }; diff --git a/src/redux/node/reducer.ts b/src/redux/node/reducer.ts index 5f05586d..fa15f16e 100644 --- a/src/redux/node/reducer.ts +++ b/src/redux/node/reducer.ts @@ -1,5 +1,5 @@ import { createReducer } from '~/utils/reducer'; -import { INode, IComment } from '../types'; +import { INode, IComment, IFile } from '../types'; import { EMPTY_NODE, EMPTY_COMMENT } from './constants'; import { NODE_HANDLERS } from './handlers'; @@ -8,6 +8,7 @@ export type INodeState = Readonly<{ current: INode; comments: IComment[]; comment_data: Record; + current_cover_image: IFile; error: string; errors: Record; @@ -27,6 +28,7 @@ const INITIAL_STATE: INodeState = { current: { ...EMPTY_NODE }, comment_data: { 0: { ...EMPTY_COMMENT } }, comments: [], + current_cover_image: null, is_loading: false, is_loading_comments: false,