diff --git a/src/components/containers/Markdown/index.tsx b/src/components/containers/Markdown/index.tsx new file mode 100644 index 00000000..3ff0397f --- /dev/null +++ b/src/components/containers/Markdown/index.tsx @@ -0,0 +1,11 @@ +import React, { ButtonHTMLAttributes, DetailedHTMLProps, FC, HTMLAttributes } from 'react'; +import styles from '~/styles/common/markdown.module.scss'; +import classNames from 'classnames'; + +interface IProps extends DetailedHTMLProps, HTMLDivElement> {} + +const Markdown: FC = ({ className, ...props }) => ( +
+); + +export { Markdown }; diff --git a/src/components/lab/LabBottomPanel/index.tsx b/src/components/lab/LabBottomPanel/index.tsx new file mode 100644 index 00000000..16024efd --- /dev/null +++ b/src/components/lab/LabBottomPanel/index.tsx @@ -0,0 +1,17 @@ +import React, { FC } from 'react'; +import { Group } from '~/components/containers/Group'; +import { Icon } from '~/components/input/Icon'; +import { INodeComponentProps } from '~/redux/node/constants'; +import { Filler } from '~/components/containers/Filler'; +import styles from './styles.module.scss'; +import { getPrettyDate } from '~/utils/dom'; + +const LabBottomPanel: FC = ({ node }) => ( + +
{getPrettyDate(node.created_at)}
+ + +
+); + +export { LabBottomPanel }; diff --git a/src/components/lab/LabBottomPanel/styles.module.scss b/src/components/lab/LabBottomPanel/styles.module.scss new file mode 100644 index 00000000..9a2f5871 --- /dev/null +++ b/src/components/lab/LabBottomPanel/styles.module.scss @@ -0,0 +1,10 @@ +@import "~/styles/variables.scss"; + +.wrap { + padding: 0 $gap $gap; +} + +.timestamp { + font: $font_12_regular; + color: darken(white, 40%); +} diff --git a/src/components/lab/LabImage/index.tsx b/src/components/lab/LabImage/index.tsx new file mode 100644 index 00000000..e0383657 --- /dev/null +++ b/src/components/lab/LabImage/index.tsx @@ -0,0 +1,102 @@ +import React, { FC, useCallback, useEffect, useState } from 'react'; +import { INodeComponentProps } from '~/redux/node/constants'; +import SwiperCore, { A11y, Pagination, Navigation, SwiperOptions, Keyboard } from 'swiper'; +import { Swiper, SwiperSlide } from 'swiper/react'; + +import 'swiper/swiper.scss'; +import 'swiper/components/pagination/pagination.scss'; +import 'swiper/components/scrollbar/scrollbar.scss'; +import 'swiper/components/zoom/zoom.scss'; +import 'swiper/components/navigation/navigation.scss'; + +import styles from './styles.module.scss'; +import { useNodeImages } from '~/utils/hooks/node/useNodeImages'; +import { getURL } from '~/utils/dom'; +import { PRESETS, URLS } from '~/constants/urls'; +import SwiperClass from 'swiper/types/swiper-class'; +import { modalShowPhotoswipe } from '~/redux/modal/actions'; +import { useDispatch } from 'react-redux'; +import { useHistory } from 'react-router'; + +SwiperCore.use([Navigation, Pagination, A11y]); + +interface IProps extends INodeComponentProps {} + +const breakpoints: SwiperOptions['breakpoints'] = { + 599: { + spaceBetween: 20, + navigation: true, + }, +}; + +const LabImage: FC = ({ node }) => { + const dispatch = useDispatch(); + const history = useHistory(); + + const [controlledSwiper, setControlledSwiper] = useState(undefined); + + const images = useNodeImages(node); + + const updateSwiper = useCallback(() => { + if (!controlledSwiper) return; + + controlledSwiper.updateSlides(); + controlledSwiper.updateSize(); + controlledSwiper.update(); + }, [controlledSwiper]); + + const resetSwiper = useCallback(() => { + if (!controlledSwiper) return; + controlledSwiper.slideTo(0, 0); + setTimeout(() => controlledSwiper.slideTo(0, 0), 300); + }, [controlledSwiper]); + + useEffect(() => { + updateSwiper(); + resetSwiper(); + }, [images, updateSwiper, resetSwiper]); + + const onClick = useCallback(() => history.push(URLS.NODE_URL(node.id)), [history, node.id]); + + if (!images?.length) { + return null; + } + + return ( +
+ 1 ? 1.1 : 1} + onSwiper={setControlledSwiper} + spaceBetween={10} + grabCursor + autoHeight + breakpoints={breakpoints} + observeSlideChildren + observeParents + resizeObserver + watchOverflow + updateOnImagesReady + onInit={resetSwiper} + keyboard={{ + enabled: true, + onlyInViewport: false, + }} + > + {images.map(file => ( + + {node.title} + + ))} + +
+ ); +}; + +export { LabImage }; diff --git a/src/components/lab/LabImage/styles.module.scss b/src/components/lab/LabImage/styles.module.scss new file mode 100644 index 00000000..06e20d84 --- /dev/null +++ b/src/components/lab/LabImage/styles.module.scss @@ -0,0 +1,70 @@ +@import "~/styles/variables.scss"; + +.wrapper { + border-radius: $radius; + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + + :global(.swiper-container) { + width: 100%; + } + + :global(.swiper-button-next), + :global(.swiper-button-prev) { + color: white; + font-size: 10px; + + &::after { + font-size: 32px; + } + } + +} + +.slide { + text-align: center; + text-transform: uppercase; + font: $font_32_bold; + display: flex; + border-radius: $radius; + align-items: center; + justify-content: center; + width: auto; + max-width: 100%; + opacity: 1; + filter: brightness(50%) saturate(0.5); + transition: opacity 0.5s, filter 0.5s, transform 0.5s; + + &:global(.swiper-slide-active) { + opacity: 1; + filter: brightness(100%); + } + + @include tablet { + padding-bottom: 0; + padding-top: 0; + } +} + +.image { + max-height: calc(100vh - 70px - 70px); + max-width: 100%; + border-radius: $radius; + transition: box-shadow 1s; + box-shadow: transparentize(black, 0.7) 0 3px 5px; + + :global(.swiper-slide-active) & { + box-shadow: transparentize(black, 0.9) 0 10px 5px 4px, + transparentize(black, 0.7) 0 5px 5px, + transparentize(white, 0.95) 0 -1px 2px, + transparentize(white, 0.95) 0 -1px; + } + + @include tablet { + padding-bottom: 0; + max-height: 100vh; + border-radius: 0; + } +} diff --git a/src/components/lab/LabNode/index.tsx b/src/components/lab/LabNode/index.tsx index 5f64c55f..e4207484 100644 --- a/src/components/lab/LabNode/index.tsx +++ b/src/components/lab/LabNode/index.tsx @@ -4,28 +4,17 @@ import { NodePanelInner } from '~/components/node/NodePanelInner'; import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks'; import styles from './styles.module.scss'; import { Card } from '~/components/containers/Card'; -import { NodePanelLab } from '~/components/node/NodePanelLab'; +import { LabNodeTitle } from '~/components/lab/LabNodeTitle'; +import { Grid } from '~/components/containers/Grid'; interface IProps { node: INode; } const LabNode: FC = ({ node }) => { - const { inline, block, head } = useNodeBlocks(node, false); + const { lab } = useNodeBlocks(node, false); - console.log(node.id, { inline, block, head }); - - return ( - -
- -
- - {head} - {block} - {inline} -
- ); + return
{lab}
; }; export { LabNode }; diff --git a/src/components/lab/LabNode/styles.module.scss b/src/components/lab/LabNode/styles.module.scss index 0e8e59ab..cfc41356 100644 --- a/src/components/lab/LabNode/styles.module.scss +++ b/src/components/lab/LabNode/styles.module.scss @@ -1,11 +1,11 @@ @import "~/styles/variables.scss"; .wrap { + box-shadow: transparentize(black, 0.5) 0 0 0 1px, inset transparentize(white, 0.9) 0 1px, lighten(black, 10%) 0 4px; + background-color: $content_bg; + cursor: pointer; + min-width: 0; -} - -.head { - background-color: transparentize(black, 0.9); - border-radius: $radius $radius 0 0; + border-radius: $radius; } diff --git a/src/components/lab/LabNodeTitle/index.tsx b/src/components/lab/LabNodeTitle/index.tsx new file mode 100644 index 00000000..058223c9 --- /dev/null +++ b/src/components/lab/LabNodeTitle/index.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import { INode } from '~/redux/types'; +import styles from './styles.module.scss'; +import { URLS } from '~/constants/urls'; +import { Link } from 'react-router-dom'; + +interface IProps { + node: INode; +} + +const LabNodeTitle: FC = ({ node }) => { + if (!node.title) return null; + + return ( +
+
+ {node.title || '...'} +
+
+ ); +}; + +export { LabNodeTitle }; diff --git a/src/components/node/NodePanelLab/styles.module.scss b/src/components/lab/LabNodeTitle/styles.module.scss similarity index 94% rename from src/components/node/NodePanelLab/styles.module.scss rename to src/components/lab/LabNodeTitle/styles.module.scss index 095dafe5..378f00c7 100644 --- a/src/components/node/NodePanelLab/styles.module.scss +++ b/src/components/lab/LabNodeTitle/styles.module.scss @@ -1,7 +1,7 @@ @import "~/styles/variables.scss"; .wrap { - padding: $gap; + padding: 0 $gap; } .title { diff --git a/src/components/lab/LabPad/index.tsx b/src/components/lab/LabPad/index.tsx new file mode 100644 index 00000000..5432ad99 --- /dev/null +++ b/src/components/lab/LabPad/index.tsx @@ -0,0 +1,8 @@ +import React, { FC } from 'react'; +import styles from './styles.module.scss'; + +interface IProps {} + +const LabPad: FC = () =>
; + +export { LabPad }; diff --git a/src/components/lab/LabPad/styles.module.scss b/src/components/lab/LabPad/styles.module.scss new file mode 100644 index 00000000..7869ed41 --- /dev/null +++ b/src/components/lab/LabPad/styles.module.scss @@ -0,0 +1,5 @@ +@import "~/styles/variables.scss"; + +.pad { + height: $gap; +} diff --git a/src/components/lab/LabText/index.tsx b/src/components/lab/LabText/index.tsx new file mode 100644 index 00000000..ec388b13 --- /dev/null +++ b/src/components/lab/LabText/index.tsx @@ -0,0 +1,28 @@ +import React, { FC, useCallback, useMemo } from 'react'; +import { Markdown } from '~/components/containers/Markdown'; +import { INodeComponentProps } from '~/redux/node/constants'; +import { formatTextParagraphs } from '~/utils/dom'; +import { path } from 'ramda'; +import styles from './styles.module.scss'; +import { useHistory } from 'react-router'; +import { URLS } from '~/constants/urls'; + +const LabText: FC = ({ node }) => { + const content = useMemo(() => formatTextParagraphs(path(['blocks', 0, 'text'], node) || ''), [ + node.blocks, + ]); + + const history = useHistory(); + + const onClick = useCallback(() => history.push(URLS.NODE_URL(node.id)), [node.id]); + + return ( + + ); +}; + +export { LabText }; diff --git a/src/components/lab/LabText/styles.module.scss b/src/components/lab/LabText/styles.module.scss new file mode 100644 index 00000000..c06fe398 --- /dev/null +++ b/src/components/lab/LabText/styles.module.scss @@ -0,0 +1,21 @@ +@import "~/styles/variables.scss"; + +.wrap { + padding: 0 $gap; + + @include tablet { + position: relative; + max-height: 50vh; + overflow: hidden; + + &::after { + content: ' '; + position: absolute; + background: linear-gradient(transparentize($content_bg, 1), $content_bg 80%); + bottom: 0; + left: auto; + width: 100%; + height: 100px; + } + } +} diff --git a/src/components/node/NodePanelLab/index.tsx b/src/components/node/NodePanelLab/index.tsx deleted file mode 100644 index c33714d4..00000000 --- a/src/components/node/NodePanelLab/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { FC } from 'react'; -import { INode } from '~/redux/types'; -import styles from './styles.module.scss'; -import { URLS } from '~/constants/urls'; -import { Link } from 'react-router-dom'; - -interface IProps { - node: INode; -} - -const NodePanelLab: FC = ({ node }) => ( -
-
- {node.title || '...'} -
-
-); - -export { NodePanelLab }; diff --git a/src/containers/lab/LabGrid/styles.module.scss b/src/containers/lab/LabGrid/styles.module.scss index 3f42c360..e58bb06d 100644 --- a/src/containers/lab/LabGrid/styles.module.scss +++ b/src/containers/lab/LabGrid/styles.module.scss @@ -4,5 +4,5 @@ display: grid; grid-auto-flow: row; grid-auto-rows: auto; - grid-row-gap: $gap; + grid-row-gap: $gap * 2; } diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts index 8cc79869..59a7050f 100644 --- a/src/redux/node/constants.ts +++ b/src/redux/node/constants.ts @@ -15,6 +15,11 @@ import { IEditorComponentProps, NodeEditorProps } from '~/redux/node/types'; import { EditorFiller } from '~/components/editors/EditorFiller'; import { EditorPublicSwitch } from '~/components/editors/EditorPublicSwitch'; import { NodeImageSwiperBlock } from '~/components/node/NodeImageSwiperBlock'; +import { LabNodeTitle } from '~/components/lab/LabNodeTitle'; +import { LabText } from '~/components/lab/LabText'; +import { LabImage } from '~/components/lab/LabImage'; +import { LabBottomPanel } from '~/components/lab/LabBottomPanel'; +import { LabPad } from '~/components/lab/LabPad'; const prefix = 'NODE.'; export const NODE_ACTIONS = { @@ -83,6 +88,13 @@ export type INodeComponentProps = { export type INodeComponents = Record, FC>; +export const LAB_PREVIEW_LAYOUT: Record[]> = { + [NODE_TYPES.IMAGE]: [LabImage, LabPad, LabNodeTitle, LabBottomPanel], + [NODE_TYPES.VIDEO]: [NodeVideoBlock, LabPad, LabNodeTitle, LabBottomPanel], + [NODE_TYPES.AUDIO]: [LabPad, LabNodeTitle, NodeAudioImageBlock, NodeAudioBlock, LabBottomPanel], + [NODE_TYPES.TEXT]: [LabPad, LabNodeTitle, LabText, LabBottomPanel], +}; + export const NODE_HEADS: INodeComponents = { [NODE_TYPES.IMAGE]: NodeImageSwiperBlock, }; diff --git a/src/utils/hooks/node/useNodeBlocks.ts b/src/utils/hooks/node/useNodeBlocks.ts index 823e522c..df0be6be 100644 --- a/src/utils/hooks/node/useNodeBlocks.ts +++ b/src/utils/hooks/node/useNodeBlocks.ts @@ -3,6 +3,7 @@ import { createElement, FC, useCallback, useMemo } from 'react'; import { isNil, prop } from 'ramda'; import { INodeComponentProps, + LAB_PREVIEW_LAYOUT, NODE_COMPONENTS, NODE_HEADS, NODE_INLINES, @@ -11,11 +12,12 @@ import { // useNodeBlocks returns head, block and inline blocks of node export const useNodeBlocks = (node: INode, isLoading: boolean) => { const createNodeBlock = useCallback( - (block?: FC) => + (block?: FC, key = 0) => !isNil(block) && createElement(block, { node, isLoading, + key, }), [node, isLoading] ); @@ -35,5 +37,13 @@ export const useNodeBlocks = (node: INode, isLoading: boolean) => { [node, createNodeBlock] ); - return { head, block, inline }; + const lab = useMemo( + () => + node?.type && prop(node.type, LAB_PREVIEW_LAYOUT) + ? prop(node.type, LAB_PREVIEW_LAYOUT).map((comp, i) => createNodeBlock(comp, i)) + : undefined, + [node, createNodeBlock] + ); + + return { head, block, inline, lab }; };