diff --git a/src/components/flow/FlowGrid/index.tsx b/src/components/flow/FlowGrid/index.tsx index 23bbe28b..cb2aecdd 100644 --- a/src/components/flow/FlowGrid/index.tsx +++ b/src/components/flow/FlowGrid/index.tsx @@ -7,6 +7,7 @@ import { INode } from '~/redux/types'; import { canEditNode } from '~/utils/node'; import { IUser } from '~/redux/auth/types'; import { flowSetCellView } from '~/redux/flow/actions'; +import { FlowHero } from '../FlowHero'; type IProps = Partial & { user: Partial; @@ -14,10 +15,12 @@ type IProps = Partial & { onChangeCellView: typeof flowSetCellView; }; -export const FlowGrid: FC = ({ user, nodes, onSelect, onChangeCellView }) => ( +export const FlowGrid: FC = ({ user, nodes, heroes, onSelect, onChangeCellView }) => (
-
HERO
+
+ +
STAMP
{nodes.map(node => ( diff --git a/src/components/flow/FlowGrid/styles.scss b/src/components/flow/FlowGrid/styles.scss index 5532c25b..39f7062a 100644 --- a/src/components/flow/FlowGrid/styles.scss +++ b/src/components/flow/FlowGrid/styles.scss @@ -8,7 +8,7 @@ $cols: $content_width / $cell; .grid_test { display: grid; grid-template-columns: repeat(auto-fit, minmax($cell, 1fr)); - grid-template-rows: 40vh $cell; + grid-template-rows: 50vh $cell; grid-auto-rows: $cell; grid-auto-flow: row dense; grid-column-gap: $grid_line; @@ -20,7 +20,7 @@ $cols: $content_width / $cell; @media (max-width: $cell * 6) { grid-template-columns: repeat(5, 1fr); - grid-template-rows: 40vh 20vw; + grid-template-rows: 50vh 20vw; grid-auto-rows: 20vw; } diff --git a/src/components/flow/FlowHero/index.tsx b/src/components/flow/FlowHero/index.tsx new file mode 100644 index 00000000..bdfc99a8 --- /dev/null +++ b/src/components/flow/FlowHero/index.tsx @@ -0,0 +1,109 @@ +import React, { FC, useState, useCallback, useEffect, useRef } from 'react'; +import { IFlowState } from '~/redux/flow/reducer'; +import classNames from 'classnames'; + +import * as styles from './styles.scss'; +import { getURL } from '~/utils/dom'; +import { withRouter, RouteComponentProps } from 'react-router'; +import { URLS } from '~/constants/urls'; +import { Icon } from '~/components/input/Icon'; +import { Filler } from '~/components/containers/Filler'; + +type IProps = RouteComponentProps & { + heroes: IFlowState['heroes']; +}; + +const FlowHeroUnconnected: FC = ({ heroes, history }) => { + const [limit, setLimit] = useState(Math.max(heroes.length, 10)); + const [current, setCurrent] = useState(0); + const [loaded, setLoaded] = useState([]); + const timer = useRef(null); + + const onLoad = useCallback(id => () => setLoaded([...loaded, id]), [setLoaded, loaded]); + + const onNext = useCallback(() => { + clearTimeout(timer.current); + + if (loaded.length <= 1) return; + + const index = loaded.findIndex(el => el === current); + + setCurrent(index > loaded.length - 2 ? loaded[0] : loaded[index + 1]); + }, [loaded, current, setCurrent, timer]); + + const onPrevious = useCallback(() => { + clearTimeout(timer.current); + + if (loaded.length <= 1) return; + + const index = loaded.findIndex(el => el === current); + + setCurrent(index > 0 ? loaded[index - 1] : loaded[loaded.length - 1]); + }, [loaded, current, setCurrent, timer]); + + useEffect(() => { + timer.current = setTimeout(onNext, 3000); + + return () => clearTimeout(timer.current); + }, [current]); + + useEffect(() => { + if (current === 0 && loaded.length > 0) setCurrent(loaded[0]); + }, [loaded]); + + useEffect(() => { + setLimit(Math.max(heroes.length, limit)); + }, [heroes, limit]); + + const stopSliding = useCallback(() => { + clearTimeout(timer.current); + timer.current = setTimeout(onNext, 3000); + }, [timer]); + + const onClick = useCallback(() => { + if (!current) return; + + history.push(URLS.NODE_URL(current)); + }, [current]); + + return ( +
+
+
+
TITLE!
+
+ +
+
+ +
+
+ +
+
+
+ + {heroes.slice(0, limit).map(hero => ( +
+ {hero.thumbnail} +
+ ))} +
+ ); +}; + +const FlowHero = withRouter(FlowHeroUnconnected); + +export { FlowHero }; diff --git a/src/components/flow/FlowHero/styles.scss b/src/components/flow/FlowHero/styles.scss new file mode 100644 index 00000000..cc900bda --- /dev/null +++ b/src/components/flow/FlowHero/styles.scss @@ -0,0 +1,106 @@ +.wrap { + width: 100%; + height: 100%; + position: relative; + background: $content_bg; + border-radius: $cell_radius; +} + +.hero { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: none; + transition: opacity 1s; + background: 50% 50% no-repeat; + background-size: cover; + border-radius: $cell_radius; + z-index: 2; + opacity: 0; + cursor: pointer; + + &::after { + content: ' '; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url('~/sprites/dots.svg') rgba(0, 0, 0, 0.3); + } + + img { + width: 0; + height: 0; + opacity: 0; + pointer-events: none; + touch-action: none; + } + + &.is_visible { + display: block; + } + + &.is_active { + opacity: 1; + z-index: 3; + } +} + +.info { + display: flex; + position: absolute; + bottom: 0; + right: 0; + width: 100%; + padding: $gap; + box-sizing: border-box; + z-index: 5; + flex-direction: row; +} + +.title_wrap { + flex: 1; + white-space: nowrap; + display: flex; + margin-right: $gap; + overflow: hidden; +} + +.title { + flex: 0; + height: 48px; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + padding: 0 $gap; + border-radius: $radius; + font: $font_hero_title; +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; + height: 48px; + background: rgba(0, 0, 0, 0.7); + flex-direction: row; + width: 96px; + border-radius: $radius; + + .button { + cursor: pointer; + flex: 0 0 48px; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 40px; + height: 40px; + } + } +} diff --git a/src/containers/flow/FlowLayout/index.tsx b/src/containers/flow/FlowLayout/index.tsx index a87ccb44..3a655b16 100644 --- a/src/containers/flow/FlowLayout/index.tsx +++ b/src/containers/flow/FlowLayout/index.tsx @@ -8,7 +8,7 @@ import pick from 'ramda/es/pick'; import { selectUser } from '~/redux/auth/selectors'; const mapStateToProps = state => ({ - flow: pick(['nodes'], selectFlow(state)), + flow: pick(['nodes', 'heroes'], selectFlow(state)), user: pick(['role', 'id'], selectUser(state)), }); @@ -20,12 +20,18 @@ const mapDispatchToProps = { type IProps = ReturnType & typeof mapDispatchToProps & {}; const FlowLayoutUnconnected: FC = ({ - flow: { nodes }, + flow: { nodes, heroes }, user, nodeLoadNode, flowSetCellView, }) => ( - + ); const FlowLayout = connect( diff --git a/src/redux/flow/actions.ts b/src/redux/flow/actions.ts index d4b4b1e1..11b3b26b 100644 --- a/src/redux/flow/actions.ts +++ b/src/redux/flow/actions.ts @@ -7,6 +7,11 @@ export const flowSetNodes = (nodes: IFlowState['nodes']) => ({ type: FLOW_ACTIONS.SET_NODES, }); +export const flowSetHeroes = (heroes: IFlowState['heroes']) => ({ + heroes, + type: FLOW_ACTIONS.SET_HEROES, +}); + export const flowSetCellView = (id: INode['id'], flow: INode['flow']) => ({ type: FLOW_ACTIONS.SET_CELL_VIEW, id, diff --git a/src/redux/flow/constants.ts b/src/redux/flow/constants.ts index a7389f18..b74224b2 100644 --- a/src/redux/flow/constants.ts +++ b/src/redux/flow/constants.ts @@ -3,5 +3,6 @@ const prefix = 'FLOW.'; export const FLOW_ACTIONS = { GET_FLOW: `${prefix}GET_FLOW`, SET_NODES: `${prefix}SET_NODES`, + SET_HEROES: `${prefix}SET_HEROES`, SET_CELL_VIEW: `${prefix}SET_CELL_VIEW`, }; diff --git a/src/redux/flow/handlers.ts b/src/redux/flow/handlers.ts index 80f3b1ca..d44ec537 100644 --- a/src/redux/flow/handlers.ts +++ b/src/redux/flow/handlers.ts @@ -1,11 +1,15 @@ import assocPath from 'ramda/es/assocPath'; import { FLOW_ACTIONS } from './constants'; -import { flowSetNodes } from './actions'; +import { flowSetNodes, flowSetHeroes } from './actions'; import { IFlowState } from './reducer'; const setNodes = (state: IFlowState, { nodes }: ReturnType) => assocPath(['nodes'], nodes, state); +const setHeroes = (state: IFlowState, { heroes }: ReturnType) => + assocPath(['heroes'], heroes, state); + export const FLOW_HANDLERS = { [FLOW_ACTIONS.SET_NODES]: setNodes, + [FLOW_ACTIONS.SET_HEROES]: setHeroes, }; diff --git a/src/redux/flow/reducer.ts b/src/redux/flow/reducer.ts index f828c6c1..6786076d 100644 --- a/src/redux/flow/reducer.ts +++ b/src/redux/flow/reducer.ts @@ -5,11 +5,13 @@ import { FLOW_HANDLERS } from './handlers'; export type IFlowState = Readonly<{ is_loading: boolean; nodes: INode[]; + heroes: Partial[]; error: IError; }>; const INITIAL_STATE: IFlowState = { nodes: [], + heroes: [], is_loading: false, error: null, }; diff --git a/src/redux/flow/sagas.ts b/src/redux/flow/sagas.ts index 9c8aa710..56776a8f 100644 --- a/src/redux/flow/sagas.ts +++ b/src/redux/flow/sagas.ts @@ -2,23 +2,29 @@ import { takeLatest, call, put, select } from 'redux-saga/effects'; import { REHYDRATE } from 'redux-persist'; import { FLOW_ACTIONS } from './constants'; import { getNodes } from '../node/api'; -import { flowSetNodes, flowSetCellView } from './actions'; +import { flowSetNodes, flowSetCellView, flowSetHeroes } from './actions'; import { IResultWithStatus, INode } from '../types'; import { selectFlowNodes } from './selectors'; import { reqWrapper } from '../auth/sagas'; import { postCellView } from './api'; +import { IFlowState } from './reducer'; function* onGetFlow() { const { - data: { nodes = null }, - }: IResultWithStatus<{ nodes: INode[] }> = yield call(getNodes, {}); + data: { nodes = null, heroes = null }, + }: IResultWithStatus<{ nodes: IFlowState['nodes']; heroes: IFlowState['heroes'] }> = yield call( + getNodes, + {} + ); if (!nodes || !nodes.length) { yield put(flowSetNodes([])); + yield put(flowSetHeroes([])); return; } yield put(flowSetNodes(nodes)); + yield put(flowSetHeroes(heroes)); } function* onSetCellView({ id, flow }: ReturnType) { diff --git a/src/sprites/Sprites.tsx b/src/sprites/Sprites.tsx index 5384d0d6..39856ab4 100644 --- a/src/sprites/Sprites.tsx +++ b/src/sprites/Sprites.tsx @@ -149,6 +149,16 @@ const Sprites: FC<{}> = () => ( + + + + + + + + + + ); diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 68c73473..a7a75fb5 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -2,14 +2,14 @@ $cell: 280px; $gap: 10px; -$grid_line: 8px; +$grid_line: 5px; $content_width: $cell * 5 + $grid_line * 4; $spc: $gap * 2; $comment_height: 72px; $bar_height: 64px; $radius: 8px; -$cell_radius: 3px; +$cell_radius: $radius; $panel_radius: $radius; $input_radius: $radius; @@ -55,7 +55,7 @@ $font_8_regular: $regular 8px $font; $font_8_semibold: $semibold 8px $font; $font_cell_title: $bold 30px $font; -$font_hero_title: $font_48_semibold; +$font_hero_title: $bold 40px $font; $shadow_depth_1: transparentize(black, 0.8) 0 1px, inset transparentize(white, 0.98) 0 1px; $shadow_depth_2: transparentize(black, 0.8) 0 2px, inset transparentize(white, 0.98) 0 1px;