From 95ec0f2e0e5a1748a35be353c1ab2a4cb3f0daa8 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Thu, 7 Oct 2021 18:03:16 +0700 Subject: [PATCH] made better flow cells --- .env.development | 4 +- package.json | 1 + src/components/flow/CellShade/index.tsx | 13 +++- .../flow/CellShade/styles.module.scss | 15 ++-- src/components/flow/FlowCell/index.tsx | 45 +++++++++++ .../flow/FlowCell/styles.module.scss | 77 +++++++++++++++++++ src/components/flow/FlowCellImage/index.tsx | 18 +++++ .../flow/FlowCellImage/styles.module.scss | 15 ++++ src/components/flow/FlowCellText/index.tsx | 23 ++++++ .../flow/FlowCellText/styles.module.scss | 12 +++ src/components/flow/FlowGrid/index.tsx | 31 ++++---- .../flow/FlowGrid/styles.module.scss | 19 +++++ .../node/NodeImageSwiperBlock/index.tsx | 3 +- src/redux/types.ts | 3 +- src/styles/_global.scss | 4 + src/styles/common/markdown.module.scss | 2 +- src/styles/variables.scss | 6 +- src/utils/color.ts | 18 +++-- src/utils/dom.ts | 6 +- src/utils/types.ts | 5 ++ yarn.lock | 5 ++ 21 files changed, 282 insertions(+), 43 deletions(-) create mode 100644 src/components/flow/FlowCell/index.tsx create mode 100644 src/components/flow/FlowCell/styles.module.scss create mode 100644 src/components/flow/FlowCellImage/index.tsx create mode 100644 src/components/flow/FlowCellImage/styles.module.scss create mode 100644 src/components/flow/FlowCellText/index.tsx create mode 100644 src/components/flow/FlowCellText/styles.module.scss diff --git a/.env.development b/.env.development index cf60c666..589f28a1 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,3 @@ #REACT_APP_API_HOST=http://localhost:3334/ -REACT_APP_API_HOST=https://pig.staging.vault48.org/ -REACT_APP_REMOTE_CURRENT=https://pig.staging.vault48.org/static/ +REACT_APP_API_HOST=https://pig.vault48.org/ +REACT_APP_REMOTE_CURRENT=https://pig.vault48.org/static/ diff --git a/package.json b/package.json index 1b5a380e..2d802faa 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-dropzone": "^11.4.2", + "react-lazyload": "^3.2.0", "react-masonry-css": "^1.0.16", "react-popper": "^2.2.3", "react-redux": "^7.2.2", diff --git a/src/components/flow/CellShade/index.tsx b/src/components/flow/CellShade/index.tsx index 7ba39f25..b97a8471 100644 --- a/src/components/flow/CellShade/index.tsx +++ b/src/components/flow/CellShade/index.tsx @@ -1,21 +1,26 @@ import React, { FC, useMemo } from 'react'; import styles from './styles.module.scss'; import { DEFAULT_DOMINANT_COLOR } from '~/constants/node'; -import { convertHexToRGBA } from '~/utils/color'; import { DivProps } from '~/utils/types'; import classNames from 'classnames'; +import { transparentize } from 'color2k'; +import { normalizeBrightColor } from '~/utils/color'; interface Props extends DivProps { color?: string; + size?: number; } -const CellShade: FC = ({ color, ...rest }) => { +const CellShade: FC = ({ color, size = 50, ...rest }) => { const background = useMemo(() => { - if (!color || color === DEFAULT_DOMINANT_COLOR) { + const normalized = normalizeBrightColor(color); + + if (!color || color === DEFAULT_DOMINANT_COLOR || !normalized) { return undefined; } - return `linear-gradient(7deg, ${color} 50px, ${convertHexToRGBA(color, 0.3)} 250px)`; + return `linear-gradient(7deg, ${normalized} ${size}px, ${transparentize(normalized, 1)} ${size * + 5}px)`; }, [color]); return ( diff --git a/src/components/flow/CellShade/styles.module.scss b/src/components/flow/CellShade/styles.module.scss index ce9f167d..2a37fb11 100644 --- a/src/components/flow/CellShade/styles.module.scss +++ b/src/components/flow/CellShade/styles.module.scss @@ -1,15 +1,20 @@ @import "~/styles/variables"; .shade { - position: absolute; - bottom: 0; - left: 0; - right: 0; - top: 0; background: linear-gradient(7deg, transparentize($content_bg, 0.05) 30px, transparentize($content_bg, 1) 250px); pointer-events: none; touch-action: none; + &.black::after { + content: ' '; + position: absolute; + top: 10px; + right: 10px; + width: 10px; + height: 10px; + background-color: blue; + } + @include tablet { opacity: 0.7; } diff --git a/src/components/flow/FlowCell/index.tsx b/src/components/flow/FlowCell/index.tsx new file mode 100644 index 00000000..ac442e0e --- /dev/null +++ b/src/components/flow/FlowCell/index.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react'; +import styles from './styles.module.scss'; +import { NavLink } from 'react-router-dom'; +import { CellShade } from '~/components/flow/CellShade'; +import { FlowCellImage } from '~/components/flow/FlowCellImage'; +import { FlowDisplayVariant } from '~/redux/types'; +import { FlowCellText } from '~/components/flow/FlowCellText'; +import classNames from 'classnames'; + +interface Props { + to: string; + title: string; + image?: string; + color?: string; + text?: string; + display?: FlowDisplayVariant; +} + +const FlowCell: FC = ({ color, to, image, display = 'single', text, title }) => { + const withText = ((!!display && display !== 'single') || !image) && !!text; + + return ( + + {withText && ( + + {text!} + + )} + + {image && ( + + )} + + + {!withText &&

{title}

} +
+ ); +}; + +export { FlowCell }; diff --git a/src/components/flow/FlowCell/styles.module.scss b/src/components/flow/FlowCell/styles.module.scss new file mode 100644 index 00000000..af45437b --- /dev/null +++ b/src/components/flow/FlowCell/styles.module.scss @@ -0,0 +1,77 @@ +@import "~/styles/variables"; + +.cell { + @include inner_shadow; + + position: relative; + overflow: hidden; + border-radius: $radius; + display: flex; + width: 100%; + height: 100%; + background: $content_bg; + flex-direction: row; + color: inherit; + text-decoration: inherit; + font: inherit; + line-height: inherit; + + &.vertical { + flex-direction: column-reverse; + } +} + +.thumb { + @include outer_shadow; + + border-radius: $radius; + overflow: hidden; + position: relative; + z-index: 0; +} + +.shade { + @include outer_shadow; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2; +} + +.text { + position: absolute; + bottom: 10px; + left: 10px; + z-index: 1; + overflow: hidden; + border-radius: $radius; + max-height: calc(100% - 20px); + max-width: calc(100% - 20px); + box-sizing: border-box; + + + .quadro &, + .horizontal & { + max-width: calc(50% - 15px); + @include blur(transparentize($content_bg, 0), 10px, 0.5) + } + + .quadro &, + .vertical & { + max-height: calc(50% - 15px); + @include blur(transparentize($content_bg, 0), 10px, 0.5) + } +} + +.title { + font: $font_cell_title; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + z-index: 10; + padding: $gap; + text-transform: uppercase; +} diff --git a/src/components/flow/FlowCellImage/index.tsx b/src/components/flow/FlowCellImage/index.tsx new file mode 100644 index 00000000..b1b07334 --- /dev/null +++ b/src/components/flow/FlowCellImage/index.tsx @@ -0,0 +1,18 @@ +import React, { FC } from 'react'; +import LazyLoad from 'react-lazyload'; +import { IMGProps } from '~/utils/types'; +import styles from './styles.module.scss'; +import classNames from 'classnames'; + +interface Props extends IMGProps { + height?: number; +} + +const FlowCellImage: FC = ({ className, children, ...rest }) => ( + + + {children} + +); + +export { FlowCellImage }; diff --git a/src/components/flow/FlowCellImage/styles.module.scss b/src/components/flow/FlowCellImage/styles.module.scss new file mode 100644 index 00000000..61ac2651 --- /dev/null +++ b/src/components/flow/FlowCellImage/styles.module.scss @@ -0,0 +1,15 @@ +.wrapper { + width: 100%; + height: 100%; + position: relative; + + img { + position: absolute; + top: 50%; + left: 50%; + min-width: 100%; + min-height: 100%; + transform: translate(-50%, -50%); + object-fit: cover; + } +} diff --git a/src/components/flow/FlowCellText/index.tsx b/src/components/flow/FlowCellText/index.tsx new file mode 100644 index 00000000..695ba4be --- /dev/null +++ b/src/components/flow/FlowCellText/index.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import { Markdown } from '~/components/containers/Markdown'; +import { DivProps } from '~/utils/types'; +import classNames from 'classnames'; +import styles from './styles.module.scss'; +import { formatText } from '~/utils/dom'; + +interface Props extends DivProps { + children: string; + title: string; +} + +const FlowCellText: FC = ({ children, title, ...rest }) => ( +
+ {title &&

{title}

} + +
+); + +export { FlowCellText }; diff --git a/src/components/flow/FlowCellText/styles.module.scss b/src/components/flow/FlowCellText/styles.module.scss new file mode 100644 index 00000000..8cfc9a8c --- /dev/null +++ b/src/components/flow/FlowCellText/styles.module.scss @@ -0,0 +1,12 @@ +@import "~/styles/variables"; + +.text { + padding: $gap; + line-height: 1.3em; +} + +.title { + font: $font_cell_title; + margin-bottom: $gap; + text-transform: uppercase; +} diff --git a/src/components/flow/FlowGrid/index.tsx b/src/components/flow/FlowGrid/index.tsx index 17e2c700..5eaf4be9 100644 --- a/src/components/flow/FlowGrid/index.tsx +++ b/src/components/flow/FlowGrid/index.tsx @@ -1,12 +1,13 @@ -import React, { FC, Fragment, useCallback } from 'react'; -import { Cell } from '~/components/flow/Cell'; +import React, { FC, Fragment } from 'react'; import { IFlowState } from '~/redux/flow/reducer'; import { INode } from '~/redux/types'; -import { canEditNode } from '~/utils/node'; import { IUser } from '~/redux/auth/types'; -import { useHistory } from 'react-router'; -import { URLS } from '~/constants/urls'; +import { PRESETS, URLS } from '~/constants/urls'; +import { FlowCell } from '~/components/flow/FlowCell'; +import classNames from 'classnames'; +import styles from './styles.module.scss'; +import { getURLFromString } from '~/utils/dom'; type IProps = Partial & { user: Partial; @@ -14,9 +15,6 @@ type IProps = Partial & { }; export const FlowGrid: FC = ({ user, nodes, onChangeCellView }) => { - const history = useHistory(); - const onSelect = useCallback((id: INode['id']) => history.push(URLS.NODE_URL(id)), [history]); - if (!nodes) { return null; } @@ -24,13 +22,16 @@ export const FlowGrid: FC = ({ user, nodes, onChangeCellView }) => { return ( {nodes.map(node => ( - +
+ +
))}
); diff --git a/src/components/flow/FlowGrid/styles.module.scss b/src/components/flow/FlowGrid/styles.module.scss index e69de29b..c3a20fe0 100644 --- a/src/components/flow/FlowGrid/styles.module.scss +++ b/src/components/flow/FlowGrid/styles.module.scss @@ -0,0 +1,19 @@ +@import "~/styles/variables"; + +@mixin mobile { + @media (max-width: $cell * 2) { + @content; + } +} + +.cell { + &.horizontal, + &.quadro { + grid-column-end: span 2; + } + + &.vertical, + &.quadro { + grid-row-end: span 2; + } +} diff --git a/src/components/node/NodeImageSwiperBlock/index.tsx b/src/components/node/NodeImageSwiperBlock/index.tsx index 4fb4580e..ec54c8b9 100644 --- a/src/components/node/NodeImageSwiperBlock/index.tsx +++ b/src/components/node/NodeImageSwiperBlock/index.tsx @@ -15,6 +15,7 @@ import SwiperClass from 'swiper/types/swiper-class'; import { modalShowPhotoswipe } from '~/redux/modal/actions'; import { useDispatch } from 'react-redux'; import { ImagePreloader } from '~/components/media/ImagePreloader'; +import { normalizeBrightColor } from '~/utils/color'; SwiperCore.use([Navigation, Pagination, Keyboard]); @@ -97,7 +98,7 @@ const NodeImageSwiperBlock: FC = ({ node }) => { onLoad={updateSwiper} onClick={() => onOpenPhotoSwipe(i)} className={styles.image} - color={file?.metadata?.dominant_color} + color={normalizeBrightColor(file?.metadata?.dominant_color)} /> ))} diff --git a/src/redux/types.ts b/src/redux/types.ts index 9c45d24a..0d4306fa 100644 --- a/src/redux/types.ts +++ b/src/redux/types.ts @@ -111,6 +111,7 @@ export interface IBlockEmbed { } export type IBlock = IBlockText | IBlockEmbed; +export type FlowDisplayVariant = 'single' | 'vertical' | 'horizontal' | 'quadro'; export interface INode { id?: number; @@ -132,7 +133,7 @@ export interface INode { like_count?: number; flow: { - display: 'single' | 'vertical' | 'horizontal' | 'quadro'; + display: FlowDisplayVariant; show_description: boolean; dominant_color?: string; }; diff --git a/src/styles/_global.scss b/src/styles/_global.scss index ae283a3b..3223e55c 100644 --- a/src/styles/_global.scss +++ b/src/styles/_global.scss @@ -27,6 +27,10 @@ body { background-size: 600px 600px; pointer-events: none; } + + * { + box-sizing: border-box; + } } #app { diff --git a/src/styles/common/markdown.module.scss b/src/styles/common/markdown.module.scss index 82cb22a5..875b3de3 100644 --- a/src/styles/common/markdown.module.scss +++ b/src/styles/common/markdown.module.scss @@ -112,7 +112,7 @@ $margin: 1em; } :global(.grey) { - color: #555555; + color: #666666; white-space: pre-line; } } diff --git a/src/styles/variables.scss b/src/styles/variables.scss index baf86e5e..5b895b79 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -194,13 +194,13 @@ $sidebar_border: transparentize(white, 0.95); } } -@mixin blur($color: $content_bg, $radius: 15px) { - background: transparentize($color, 0.1); +@mixin blur($color: $content_bg, $radius: 15px, $opacity: 0.5) { + background: transparentize($color, $opacity / 2); @include can_backdrop { backdrop-filter: blur($radius); -webkit-backdrop-filter: blur($radius); - background: transparentize($color, 0.5); + background: transparentize($color, $opacity); } } diff --git a/src/utils/color.ts b/src/utils/color.ts index 07b136e0..45826e31 100644 --- a/src/utils/color.ts +++ b/src/utils/color.ts @@ -1,13 +1,15 @@ -export const convertHexToRGBA = (hexCode, opacity) => { - let hex = hexCode.replace('#', ''); +import { darken, desaturate, parseToHsla } from 'color2k'; +import { DEFAULT_DOMINANT_COLOR } from '~/constants/node'; - if (hex.length === 3) { - hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`; +export const normalizeBrightColor = (color?: string) => { + if (!color) { + return undefined; } - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); + const hsla = parseToHsla(color || DEFAULT_DOMINANT_COLOR); + const saturation = hsla[1]; + const lightness = hsla[2]; - return `rgba(${r},${g},${b},${opacity})`; + const desaturated = saturation > 0.3 ? desaturate(color, Math.min(saturation, 0.4)) : color; + return lightness > 0.3 ? darken(desaturated, Math.min(lightness, 0.2)) : desaturated; }; diff --git a/src/utils/dom.ts b/src/utils/dom.ts index eae9655f..3b7aedc4 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -76,17 +76,17 @@ export const describeArc = ( }; export const getURLFromString = ( - url: string, + url?: string, size?: typeof PRESETS[keyof typeof PRESETS] ): string => { if (size) { - return url.replace( + return (url || '').replace( 'REMOTE_CURRENT://', `${process.env.REACT_APP_REMOTE_CURRENT}cache/${size}/` ); } - return url.replace('REMOTE_CURRENT://', process.env.REACT_APP_REMOTE_CURRENT); + return (url || '').replace('REMOTE_CURRENT://', process.env.REACT_APP_REMOTE_CURRENT); }; export const getURL = ( diff --git a/src/utils/types.ts b/src/utils/types.ts index a7d786e1..4698ec0e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -6,3 +6,8 @@ export type DivProps = React.DetailedHTMLProps< >; export type SVGProps = React.SVGProps; + +export type IMGProps = React.DetailedHTMLProps< + React.ImgHTMLAttributes, + HTMLImageElement +>; diff --git a/yarn.lock b/yarn.lock index 31dac5f2..1caf0701 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9417,6 +9417,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-lazyload@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-lazyload/-/react-lazyload-3.2.0.tgz#497bd06a6dbd7015e3376e1137a67dc47d2dd021" + integrity sha512-zJlrG8QyVZz4+xkYZH5v1w3YaP5wEFaYSUWC4CT9UXfK75IfRAIEdnyIUF+dXr3kX2MOtL1lUaZmaQZqrETwgw== + react-masonry-css@^1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"