From 004b2002f436eda4b5610724c90b193d2f0438f0 Mon Sep 17 00:00:00 2001 From: Alfred Melch Date: Fri, 13 Dec 2019 08:28:28 +0100 Subject: [PATCH] Add Player --- src/App.js | 6 ++- src/components/Player.js | 82 ++++++++++++++++++++++++++------- src/components/PlayerControl.js | 40 ---------------- src/components/RsvpPlayer.js | 27 +++++++++++ src/components/RsvpReader.js | 4 +- src/store/actions.js | 4 +- src/store/reducer.js | 5 +- src/store/selectors.js | 4 -- 8 files changed, 100 insertions(+), 72 deletions(-) delete mode 100644 src/components/PlayerControl.js create mode 100644 src/components/RsvpPlayer.js diff --git a/src/App.js b/src/App.js index 12592c5..4367219 100644 --- a/src/App.js +++ b/src/App.js @@ -4,10 +4,12 @@ import { TextInput } from './components/TextInput' import { TextOutput } from './components/TextOutput' import { RsvpReader } from './components/RsvpReader' +import { IconContext } from 'react-icons' + export function App() { return ( -
+ -
+ ) } diff --git a/src/components/Player.js b/src/components/Player.js index f63326d..762f1de 100644 --- a/src/components/Player.js +++ b/src/components/Player.js @@ -1,26 +1,74 @@ -import React, { useEffect, useState, useCallback } from 'react' +import React, { useState, useEffect, useRef } from 'react' -export const PlayerControl = ({ interval, onTick, onStart, onStop }) => { - const dispatch = useDispatch() - const [isPlaying, setPlaying] = useState(false) +import { FaPlay, FaPause, FaStop } from 'react-icons/fa' - const [handle, setHandle] = useState(null) +function safeCall(fn) { + typeof fn === 'function' && fn() +} - useEffect(() => { - console.log('effect fired', isPlaying) - clearInterval(handle) - if (isPlaying) { - setHandle(setInterval(() => dispatch(incrementSegment()), 500)) - } - }, [handle, isPlaying]) +export const Player = ({ onTick, onStart, onStop, onPause, delay }) => { + const [isRunning, start, stop] = usePlayer(stop => onTick(stop), delay) + + const handleStart = () => { + safeCall(onStart) + start() + } + + const handleStop = () => { + safeCall(onStop) + stop() + } + + const handlePause = () => { + safeCall(onPause) + stop() + } return (
- - - -
playing: {isPlaying.toString()}
-
interval: {handle}
+ + {isRunning && ( + + )}{' '} + {!isRunning && ( + + )}
) } + +function usePlayer(onTick, delay) { + const [isRunning, setRunning] = useState(false) + const start = () => setRunning(true) + const stop = () => setRunning(false) + + useInterval(() => onTick(stop), isRunning ? delay : null) + return [isRunning, start, stop] +} + +// from: https://overreacted.io/making-setinterval-declarative-with-react-hooks/ +function useInterval(callback, delay) { + const savedCallback = useRef() + + // 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/components/PlayerControl.js b/src/components/PlayerControl.js deleted file mode 100644 index 31ac7ad..0000000 --- a/src/components/PlayerControl.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, { useEffect, useState, useCallback } from 'react' - -import { useSelector, useDispatch } from 'react-redux' -import { selectPlaying, hasNextSegment } from '../store/selectors' -import { start, stop, pause, incrementSegment } from '../store/actions' - -export const PlayerControl = ({ onTick }) => { - const dispatch = useDispatch() - const isPlaying = useSelector(selectPlaying) - const hasNext = useSelector(hasNextSegment) - const wpm = useSelector(state => state.wpm) - - const [handle, setHandle] = useState(null) - - useEffect(() => { - clearInterval(handle) - if (isPlaying) { - setHandle(setInterval(() => dispatch(incrementSegment()), 60000 / wpm)) - } - }, [isPlaying, wpm]) - - useEffect(() => { - if (!hasNext) dispatch(pause()) - }, [hasNext, dispatch]) - - return ( -
- - {isPlaying && ( - - )}{' '} - {!isPlaying && ( - - )} -
playing: {isPlaying.toString()}
-
interval: {handle}
-
has next: {hasNext.toString()}
-
- ) -} diff --git a/src/components/RsvpPlayer.js b/src/components/RsvpPlayer.js new file mode 100644 index 0000000..7171080 --- /dev/null +++ b/src/components/RsvpPlayer.js @@ -0,0 +1,27 @@ +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' + +export const RsvpPlayer = () => { + const dispatch = useDispatch() + const hasNext = useSelector(hasNextSegment) + const wpm = useSelector(state => state.wpm) + + const onTick = stop => { + if (!hasNext) return stop() + dispatch(incrementSegment()) + } + + return ( +
+ dispatch(resetSegment())} + /> +
+ ) +} diff --git a/src/components/RsvpReader.js b/src/components/RsvpReader.js index 42095eb..76977af 100644 --- a/src/components/RsvpReader.js +++ b/src/components/RsvpReader.js @@ -5,7 +5,7 @@ import { TextOutput } from './TextOutput' import { MainControl } from './MainControl' import { RsvpSegment } from './RsvpSegment' import { RsvpOptions } from './RsvpOptions' -import { PlayerControl } from './PlayerControl' +import { RsvpPlayer } from './RsvpPlayer' const FlexRow = styled.div` display: flex; @@ -26,7 +26,7 @@ export const RsvpReader = () => { - + diff --git a/src/store/actions.js b/src/store/actions.js index bc082be..f72dbfe 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -1,4 +1,5 @@ export const setText = text => ({ type: 'SET_TEXT', payload: text }) +export const resetSegment = () => ({ type: 'SET_CURRENT_SEGMENT', payload: 0 }) export const incrementSegment = () => ({ type: 'INC_SEGMENT' }) export const decrementSegment = () => ({ type: 'DEC_SEGMENT' }) export const incrementWord = () => ({ type: 'INC_WORD' }) @@ -10,6 +11,3 @@ export const setMaxLength = length => ({ payload: length }) export const setWpm = wpm => ({ type: 'SET_WPM', payload: wpm }) -export const start = () => ({ type: 'START' }) -export const pause = () => ({ type: 'PAUSE' }) -export const stop = () => ({ type: 'STOP' }) diff --git a/src/store/reducer.js b/src/store/reducer.js index 0cdabad..bdd471c 100644 --- a/src/store/reducer.js +++ b/src/store/reducer.js @@ -27,10 +27,7 @@ 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 }), - START: state => ({ ...state, isPlaying: true }), - PAUSE: state => ({ ...state, isPlaying: false }), - STOP: state => ({ ...state, isPlaying: false, curIdx: 0 }) + SET_WPM: (state, payload) => ({ ...state, wpm: payload }) } export const reducerFn = (state, { type, payload }) => { diff --git a/src/store/selectors.js b/src/store/selectors.js index fc5aef3..85aaa7f 100644 --- a/src/store/selectors.js +++ b/src/store/selectors.js @@ -77,7 +77,3 @@ export const selectPrevSentence = createSelector( export const selectHasNextSentence = createSelector(selectNextSentence, Boolean) export const selectHasPrevSentence = createSelector(selectPrevSentence, Boolean) - -// player - -export const selectPlaying = state => state.isPlaying