Use new store

This commit is contained in:
Alfred Melch 2020-02-01 18:13:16 +01:00
parent 653627033a
commit 16cdc1acf7
16 changed files with 140 additions and 61 deletions

View File

@ -1,17 +1,15 @@
import React from 'react' import React from 'react'
import { useDispatch } from 'react-redux'
import { setText, setLang } from '../store/actions'
import { getBook } from '../lib/gutenberg' import { getBook } from '../lib/gutenberg'
import styles from './Book.css' import styles from './Book.css'
import { useStore } from '../store/RSVPStore'
export const Book = ({ author, language, rights, subject, title, id }) => { export const Book = ({ author, language, title, id }) => {
const dispatch = useDispatch() const [_, { setText, setLang }] = useStore()
const handleClick = async () => { const handleClick = async () => {
dispatch(setText(await getBook(id))) setText(await getBook(id))
dispatch(setLang(language[0])) setLang(language[0])
} }
return ( return (
<div className={styles.book} onClick={handleClick}> <div className={styles.book} onClick={handleClick}>

View File

@ -1,5 +1,10 @@
import React from 'react' import React from 'react'
import { useCounter, CounterProvider } from './CounterStore' import {
useCounter,
CounterProvider,
selectors,
useCounterSelector
} from './CounterStore'
export const Counter = () => { export const Counter = () => {
return ( return (
@ -16,12 +21,12 @@ export const Counter = () => {
const Logger = () => { const Logger = () => {
const store = useCounter() const store = useCounter()
console.log(store[0])
return <></> return <></>
} }
const MyCounter = () => { const MyCounter = () => {
const [{ counter }, { increment, decrement }] = useCounter() const [_, { increment, decrement }] = useCounter()
const counter = useCounterSelector(selectors.$count)
return ( return (
<div> <div>
{counter} {counter}

View File

@ -0,0 +1,9 @@
export const $count = state => {
return state.counter
}
export const lala = 'lalal'
export default {
$count,
lala
}

View File

@ -1,4 +1,5 @@
import { createStore } from 'context-store' import { createStore } from 'potent-reducer'
import selectors from './CounterSelector'
const initialState = { const initialState = {
counter: 0, counter: 0,
@ -17,6 +18,8 @@ const reducer = {
SET_TEXT: (state, { text }) => ({ ...state, text }) SET_TEXT: (state, { text }) => ({ ...state, text })
} }
const store = createStore({ reducer, thunks, initialState }) const store = createStore({ reducer, thunks, initialState, logging: true })
export const CounterProvider = store.Provider export const CounterProvider = store.Provider
export const useCounter = store.useStore export const useCounter = store.useStore
export const useCounterSelector = store.useSelector
export { selectors }

View File

@ -1,22 +1,20 @@
import React from 'react' import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { debounce } from 'debounce' import { debounce } from 'debounce'
import { setMaxLength, setWpm, setOffset, setLang } from '../store/actions'
import { Slider } from './Slider' import { Slider } from './Slider'
import { selectOffset, selectLang } from '../store/selectors' import { selectOffset, selectLang } from '../store/selectors'
import { useSelector, useDispatch } from '../store/RSVPStore'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const availableLanguages = ['en', 'de'] const availableLanguages = ['en', 'de']
export const Options = () => { export const Options = () => {
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useDispatch()
const maxLength = useSelector(state => state.maxLength) const maxLength = useSelector(state => state.maxLength)
const wpm = useSelector(state => state.wpm) const wpm = useSelector(state => state.wpm)
const offset = useSelector(selectOffset) const offset = useSelector(selectOffset)
const lang = useSelector(selectLang) const lang = useSelector(selectLang)
const { setMaxLength, setWpm, setOffset, setLang } = useDispatch()
return ( return (
<div> <div>
<h2>{t('options.title')}</h2> <h2>{t('options.title')}</h2>
@ -25,25 +23,25 @@ export const Options = () => {
min={3} min={3}
max={15} max={15}
value={maxLength} value={maxLength}
onChange={debounce(val => dispatch(setMaxLength(val)), 100)} onChange={debounce(val => setMaxLength(val), 100)}
/> />
<Slider <Slider
title={t('options.wpm')} title={t('options.wpm')}
min={100} min={100}
max={1000} max={1000}
value={wpm} value={wpm}
onChange={debounce(val => dispatch(setWpm(val)), 50)} onChange={debounce(val => setWpm(val), 50)}
/> />
<Slider <Slider
title={t('options.offset')} title={t('options.offset')}
min={-50} min={-50}
max={50} max={50}
value={offset} value={offset}
onChange={val => dispatch(setOffset(val))} onChange={val => setOffset(val)}
/> />
<label> <label>
Language: Language:
<select value={lang} onChange={e => dispatch(setLang(e.target.value))}> <select value={lang} onChange={e => setLang(e.target.value)}>
{availableLanguages.map(l => ( {availableLanguages.map(l => (
<option key={l}>{l}</option> <option key={l}>{l}</option>
))} ))}

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from '../store/RSVPStore'
import styles from './PivotMarker.css' import styles from './PivotMarker.css'
import { selectOffset } from '../store/selectors' import { selectOffset } from '../store/selectors'

View File

@ -1,36 +1,37 @@
import React from 'react' import React from 'react'
import { useSelector, useDispatch } from 'react-redux' import { useDispatch, useSelector } from '../store/RSVPStore'
import { import {
selectHasNextSegment, selectHasNextSegment,
selectRunning, selectRunning,
selectInterval selectInterval
} from '../store/selectors' } from '../store/selectors'
import { incrementSegment, stop, pause, start } from '../store/actions'
import { FiPlay, FiPause, FiSquare } from 'react-icons/fi' import { FiPlay, FiPause, FiSquare } from 'react-icons/fi'
import { useInterval } from './generics/useInterval' import { useInterval } from './generics/useInterval'
import { IconButton } from '../styles/IconButton' import { IconButton } from '../styles/IconButton'
export const PlayerControl = () => { export const PlayerControl = () => {
const dispatch = useDispatch()
const running = useSelector(selectRunning) const running = useSelector(selectRunning)
const hasNext = useSelector(selectHasNextSegment) const hasNext = useSelector(selectHasNextSegment)
const interval = useSelector(selectInterval) const interval = useSelector(selectInterval)
const { pause, start, stop, incSegment } = useDispatch()
useInterval( useInterval(
() => { () => {
if (!hasNext) dispatch(pause()) if (!hasNext) pause()
else dispatch(incrementSegment()) else incSegment()
}, },
running ? interval : null running ? interval : null
) )
return ( return (
<> <>
<IconButton Icon={FiSquare} onClick={() => dispatch(stop())} /> <IconButton Icon={FiSquare} onClick={() => stop()} />
<IconButton <IconButton
Icon={running ? FiPause : FiPlay} Icon={running ? FiPause : FiPlay}
onClick={() => dispatch(running ? pause() : start())} onClick={() => (running ? pause() : start())}
disabled={!hasNext} disabled={!hasNext}
/> />
</> </>

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from '../store/RSVPStore'
import { selectCurrentSegmentIndex, selectSegments } from '../store/selectors' import { selectCurrentSegmentIndex, selectSegments } from '../store/selectors'
export const Progress = () => { export const Progress = () => {

View File

@ -2,7 +2,8 @@ import React, { useState, useEffect, useCallback } from 'react'
import { debounce } from 'debounce' import { debounce } from 'debounce'
import classNames from 'classnames' import classNames from 'classnames'
import { FiSearch } from 'react-icons/fi' import { FiSearch } from 'react-icons/fi'
import { useDispatch } from 'react-redux' import { useDispatch } from '../store/RSVPStore'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import axios from 'axios' import axios from 'axios'
@ -14,16 +15,15 @@ import { Book } from './Book'
import styles from './SearchBar.css' import styles from './SearchBar.css'
import { CursorList } from './generics/CursorList' import { CursorList } from './generics/CursorList'
import { setText, setLang } from '../store/actions.js'
export const SearchBar = () => { export const SearchBar = () => {
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useDispatch()
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [results, setResults] = useState([]) const [results, setResults] = useState([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [isFocused, setFocus] = useState(false) const [isFocused, setFocus] = useState(false)
const { setText, setLang } = useDispatch()
const debouncedSearch = useCallback( const debouncedSearch = useCallback(
debounce(async term => { debounce(async term => {
await search(term, 100).then(setResults) await search(term, 100).then(setResults)
@ -35,9 +35,9 @@ export const SearchBar = () => {
const handleClick = async idx => { const handleClick = async idx => {
const { id, language } = results[idx] const { id, language } = results[idx]
const url = `https://gutenberg.muperfredi.de/texts/${id}/stripped-body` const url = `https://gutenberg.muperfredi.de/texts/${id}/stripped-body`
const text = await axios.get(url).then(res => res.data.body) const text = await axios.get(url).then(res => res.data.body)(setText(text))(
dispatch(setText(text)) setLang(language[0])
dispatch(setLang(language[0])) )
} }
useEffect(() => { useEffect(() => {

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from '../store/RSVPStore'
import { selectPivotizedSegment, selectOffset } from '../store/selectors' import { selectPivotizedSegment, selectOffset } from '../store/selectors'
import styles from './Segment.css' import styles from './Segment.css'

View File

@ -1,12 +1,11 @@
import React from 'react' import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { import {
FiSkipBack, FiSkipBack,
FiSkipForward, FiSkipForward,
FiRewind, FiRewind,
FiFastForward FiFastForward
} from 'react-icons/fi' } from 'react-icons/fi'
import { useDispatch, useSelector } from '../store/RSVPStore'
import { IconButton } from '../styles/IconButton' import { IconButton } from '../styles/IconButton'
import { import {
@ -16,45 +15,39 @@ import {
selectHasNextWord selectHasNextWord
} from '../store/selectors' } from '../store/selectors'
import {
incrementSentence,
incrementWord,
decrementSentence,
decrementWord
} from '../store/actions'
export const SegmentControl = ({ children }) => { export const SegmentControl = ({ children }) => {
const dispatch = useDispatch()
const hasPrevSentence = useSelector(selectHasPrevSentence) const hasPrevSentence = useSelector(selectHasPrevSentence)
const hasNextSentence = useSelector(selectHasNextSentence) const hasNextSentence = useSelector(selectHasNextSentence)
const hasPrevWord = useSelector(selectHasPrevWord) const hasPrevWord = useSelector(selectHasPrevWord)
const hasNextWord = useSelector(selectHasNextWord) const hasNextWord = useSelector(selectHasNextWord)
const { decSentence, decWord } = useDispatch()
const { incSentence, incWord } = useDispatch()
return ( return (
<> <>
<IconButton <IconButton
title="Previous Sentence" title="Previous Sentence"
onClick={() => dispatch(decrementSentence())} onClick={() => decSentence()}
Icon={FiRewind} Icon={FiRewind}
disabled={!hasPrevSentence} disabled={!hasPrevSentence}
/> />
<IconButton <IconButton
Icon={FiSkipBack} Icon={FiSkipBack}
title="Previous Word" title="Previous Word"
onClick={() => dispatch(decrementWord())} onClick={() => decWord()}
disabled={!hasPrevWord} disabled={!hasPrevWord}
/> />
{children} {children}
<IconButton <IconButton
Icon={FiSkipForward} Icon={FiSkipForward}
title="Next Word" title="Next Word"
onClick={() => dispatch(incrementWord())} onClick={() => incWord()}
disabled={!hasNextWord} disabled={!hasNextWord}
/> />
<IconButton <IconButton
Icon={FiFastForward} Icon={FiFastForward}
title="Next Sentence" title="Next Sentence"
onClick={() => dispatch(incrementSentence())} onClick={() => incSentence()}
disabled={!hasNextSentence} disabled={!hasNextSentence}
/> />
</> </>

View File

@ -1,7 +1,5 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from '../store/RSVPStore'
import { setText } from '../store/actions.js'
import styles from './TextInput.css' import styles from './TextInput.css'
@ -10,7 +8,7 @@ const lorem =
export const TextInput = () => { export const TextInput = () => {
const [text, setTextState] = useState(lorem) const [text, setTextState] = useState(lorem)
const dispatch = useDispatch() const { setText } = useDispatch()
return ( return (
<div> <div>
<textarea <textarea
@ -18,7 +16,7 @@ export const TextInput = () => {
defaultValue={text} defaultValue={text}
onInput={e => setTextState(e.target.value)} onInput={e => setTextState(e.target.value)}
></textarea> ></textarea>
<button className={styles.load} onClick={() => dispatch(setText(text))}> <button className={styles.load} onClick={() => setText(text)}>
Load Load
</button> </button>
</div> </div>

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from '../store/RSVPStore'
import classNames from 'classnames' import classNames from 'classnames'
import { getNextSmallerNumber } from '../lib/array-util.js' import { getNextSmallerNumber } from '../lib/array-util.js'

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from '../store/RSVPStore'
import { selectTotalTime } from '../store/selectors' import { selectTotalTime } from '../store/selectors'
function formatTime(totalSeconds) { function formatTime(totalSeconds) {

View File

@ -2,10 +2,9 @@ import 'regenerator-runtime/runtime'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Provider } from 'react-redux' import { Provider } from './store/RSVPStore'
import { App } from './App' import { App } from './App'
import { store } from './store/index.js'
import './i18n' import './i18n'
@ -17,7 +16,7 @@ function createRootElement() {
return root return root
} }
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider>
<App /> <App />
</Provider>, </Provider>,
createRootElement() createRootElement()

75
src/store/RSVPStore.js Normal file
View File

@ -0,0 +1,75 @@
import { createStore } from 'potent-reducer'
import {
safeSelectNextWord,
safeSelectNextSentence,
safeSelectPrevWord,
safeSelectPrevSentence,
safeSelectNextSegment,
safeSelectPrevSegment
} from './selectors'
const initialState = {
originalText: 'Sample Text',
curIdx: 0,
maxLength: 10,
isPlaying: false,
wpm: 300,
offset: -15,
running: false,
lang: 'en',
textDisplay: {
mode: 'pagination',
options: {
pagination: { pageLength: 50 },
segments: { prev: 9, next: 40 },
sentences: { prev: 1, next: 8 }
}
}
}
const thunks = {
setText: text => dispatch => dispatch({ text }),
resetSegment: () => dispatch => dispatch(),
incSegment: () => dispatch => dispatch(),
decSegment: () => dispatch => dispatch(),
incWord: () => dispatch => dispatch(),
decWord: () => dispatch => dispatch(),
incSentence: () => dispatch => dispatch(),
decSentence: () => dispatch => dispatch(),
setMaxLength: length => dispatch => dispatch({ length }),
setWpm: wpm => dispatch => dispatch({ wpm }),
setOffset: offset => dispatch => dispatch({ offset }),
start: () => dispatch => dispatch(),
stop: () => dispatch => dispatch(),
pause: () => dispatch => dispatch(),
setLang: lang => dispatch => dispatch({ lang })
}
const reducer = {
SET_TEXT: (state, { text }) => ({ ...state, originalText: text, curIdx: 0 }),
RESET_SEGMENT: state => ({ ...state, curIdx: 0 }),
INC_SEGMENT: state => ({ ...state, curIdx: safeSelectNextSegment(state) }),
DEC_SEGMENT: state => ({ ...state, curIdx: safeSelectPrevSegment(state) }),
INC_WORD: state => ({ ...state, curIdx: safeSelectNextWord(state) }),
DEC_WORD: state => ({ ...state, curIdx: safeSelectPrevWord(state) }),
INC_SENTENCE: state => ({ ...state, curIdx: safeSelectNextSentence(state) }),
DEC_SENTENCE: state => ({ ...state, curIdx: safeSelectPrevSentence(state) }),
SET_MAX_LENGTH: (state, { maxLength }) => ({
...state,
maxLength,
running: false,
curIdx: 0
}),
SET_WPM: (state, { wpm }) => ({ ...state, wpm }),
SET_OFFSET: (state, { offset }) => ({ ...state, offset }),
START: state => ({ ...state, running: true }),
STOP: state => ({ ...state, running: false, curIdx: 0 }),
PAUSE: state => ({ ...state, running: false }),
SET_LANG: (state, { lang }) => ({ ...state, lang })
}
const store = createStore({ reducer, thunks, initialState, logging: true })
export const Provider = store.Provider
export const useStore = store.useStore
export const useSelector = store.useSelector
export const useDispatch = store.useDispatch