From 14ce3b1450ce91f4ac0807b42ca93bec7577eec2 Mon Sep 17 00:00:00 2001 From: Alfred Melch Date: Fri, 13 Dec 2019 19:28:21 +0100 Subject: [PATCH] Move player logic back in store --- src/components/RsvpPlayer.js | 39 ++++++++++++++++--------- src/components/{ => generics}/Player.js | 1 + src/components/generics/useInterval.js | 22 ++++++++++++++ src/store/actions.js | 3 ++ src/store/reducer.js | 8 +++-- src/store/selectors.js | 4 +++ src/styles/IconButton.js | 13 +++++++-- 7 files changed, 73 insertions(+), 17 deletions(-) rename src/components/{ => generics}/Player.js (98%) create mode 100644 src/components/generics/useInterval.js diff --git a/src/components/RsvpPlayer.js b/src/components/RsvpPlayer.js index 7171080..8bc0a26 100644 --- a/src/components/RsvpPlayer.js +++ b/src/components/RsvpPlayer.js @@ -1,27 +1,40 @@ import React from 'react' import { useSelector, useDispatch } from 'react-redux' -import { hasNextSegment } from '../store/selectors' -import { incrementSegment, resetSegment } from '../store/actions' -import { Player } from './Player' +import { + hasNextSegment, + selectRunning, + selectInterval +} from '../store/selectors' +import { incrementSegment, stop, pause, start } from '../store/actions' +import { FiPlay, FiPause, FiSquare } from 'react-icons/fi' +import { useInterval } from './generics/useInterval' +import { IconButton } from '../styles/IconButton' export const RsvpPlayer = () => { const dispatch = useDispatch() + const running = useSelector(selectRunning) const hasNext = useSelector(hasNextSegment) - const wpm = useSelector(state => state.wpm) + const interval = useSelector(selectInterval) - const onTick = stop => { - if (!hasNext) return stop() - dispatch(incrementSegment()) - } + useInterval( + () => { + if (!hasNext) dispatch(pause()) + else dispatch(incrementSegment()) + }, + running ? interval : null + ) return (
- dispatch(resetSegment())} - /> +
+ dispatch(stop())} /> + dispatch(running ? pause() : start())} + disabled={!hasNext} + /> +
) } diff --git a/src/components/Player.js b/src/components/generics/Player.js similarity index 98% rename from src/components/Player.js rename to src/components/generics/Player.js index ec62da0..0c913b5 100644 --- a/src/components/Player.js +++ b/src/components/generics/Player.js @@ -12,6 +12,7 @@ export const Player = ({ onTick, onStart, onStop, onPause, delay }) => { const handleStart = () => { safeCall(onStart) start() + console.log('yay') } const handleStop = () => { diff --git a/src/components/generics/useInterval.js b/src/components/generics/useInterval.js new file mode 100644 index 0000000..2523dad --- /dev/null +++ b/src/components/generics/useInterval.js @@ -0,0 +1,22 @@ +import { useRef, useEffect } from 'react' + +// from: https://overreacted.io/making-setinterval-declarative-with-react-hooks/ +export function useInterval(callback, delay) { + const savedCallback = useRef() + const x = 'asdf' + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback + }, [callback]) + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current() + } + if (delay !== null) { + let id = setInterval(tick, delay) + return () => clearInterval(id) + } + }, [delay]) +} diff --git a/src/store/actions.js b/src/store/actions.js index f72dbfe..92e4711 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -11,3 +11,6 @@ export const setMaxLength = length => ({ payload: length }) export const setWpm = wpm => ({ type: 'SET_WPM', payload: wpm }) +export const start = () => ({ type: 'START' }) +export const stop = () => ({ type: 'STOP' }) +export const pause = () => ({ type: 'PAUSE' }) diff --git a/src/store/reducer.js b/src/store/reducer.js index bdd471c..344a4bf 100644 --- a/src/store/reducer.js +++ b/src/store/reducer.js @@ -10,7 +10,8 @@ export const initialState = { curIdx: 0, maxLength: 5, isPlaying: false, - wpm: 300 + wpm: 300, + running: false } const reducer = { @@ -27,7 +28,10 @@ const reducer = { INC_SENTENCE: state => ({ ...state, curIdx: selectNextSentence(state) }), DEC_SENTENCE: state => ({ ...state, curIdx: selectPrevSentence(state) }), SET_MAX_LENGTH: (state, payload) => ({ ...state, maxLength: payload }), - SET_WPM: (state, payload) => ({ ...state, wpm: payload }) + SET_WPM: (state, payload) => ({ ...state, wpm: payload }), + START: state => ({ ...state, running: true }), + STOP: state => ({ ...state, running: false, curIdx: 0 }), + PAUSE: state => ({ ...state, running: false }) } export const reducerFn = (state, { type, payload }) => { diff --git a/src/store/selectors.js b/src/store/selectors.js index 85aaa7f..d423a59 100644 --- a/src/store/selectors.js +++ b/src/store/selectors.js @@ -77,3 +77,7 @@ export const selectPrevSentence = createSelector( export const selectHasNextSentence = createSelector(selectNextSentence, Boolean) export const selectHasPrevSentence = createSelector(selectPrevSentence, Boolean) + +export const selectRunning = state => state.running +export const selectWpm = state => state.wpm +export const selectInterval = createSelector(selectWpm, wpm => 60000 / wpm) diff --git a/src/styles/IconButton.js b/src/styles/IconButton.js index ca4a45a..8cfe2fc 100644 --- a/src/styles/IconButton.js +++ b/src/styles/IconButton.js @@ -30,6 +30,15 @@ const SvgButton = styled.button` &::-moz-focus-inner { border: 0; } + + &[disabled] { + color: '#c7c7c7'; + svg { + transform: scale(1.5); + + color: '#c7c7c7'; + } + } ` const AccessLabel = styled.span` @@ -40,9 +49,9 @@ const AccessLabel = styled.span` white-space: nowrap; ` -export const IconButton = ({ title, onClick, Icon }) => { +export const IconButton = ({ Icon, title, onClick, disabled }) => { return ( - + {Icon &&