Use new store
This commit is contained in:
parent
653627033a
commit
16cdc1acf7
@ -1,17 +1,15 @@
|
||||
import React from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import { setText, setLang } from '../store/actions'
|
||||
import { getBook } from '../lib/gutenberg'
|
||||
|
||||
import styles from './Book.css'
|
||||
import { useStore } from '../store/RSVPStore'
|
||||
|
||||
export const Book = ({ author, language, rights, subject, title, id }) => {
|
||||
const dispatch = useDispatch()
|
||||
export const Book = ({ author, language, title, id }) => {
|
||||
const [_, { setText, setLang }] = useStore()
|
||||
|
||||
const handleClick = async () => {
|
||||
dispatch(setText(await getBook(id)))
|
||||
dispatch(setLang(language[0]))
|
||||
setText(await getBook(id))
|
||||
setLang(language[0])
|
||||
}
|
||||
return (
|
||||
<div className={styles.book} onClick={handleClick}>
|
||||
|
@ -1,5 +1,10 @@
|
||||
import React from 'react'
|
||||
import { useCounter, CounterProvider } from './CounterStore'
|
||||
import {
|
||||
useCounter,
|
||||
CounterProvider,
|
||||
selectors,
|
||||
useCounterSelector
|
||||
} from './CounterStore'
|
||||
|
||||
export const Counter = () => {
|
||||
return (
|
||||
@ -16,12 +21,12 @@ export const Counter = () => {
|
||||
|
||||
const Logger = () => {
|
||||
const store = useCounter()
|
||||
console.log(store[0])
|
||||
return <></>
|
||||
}
|
||||
|
||||
const MyCounter = () => {
|
||||
const [{ counter }, { increment, decrement }] = useCounter()
|
||||
const [_, { increment, decrement }] = useCounter()
|
||||
const counter = useCounterSelector(selectors.$count)
|
||||
return (
|
||||
<div>
|
||||
{counter}
|
||||
|
9
src/components/CounterSelector.js
Normal file
9
src/components/CounterSelector.js
Normal file
@ -0,0 +1,9 @@
|
||||
export const $count = state => {
|
||||
return state.counter
|
||||
}
|
||||
export const lala = 'lalal'
|
||||
|
||||
export default {
|
||||
$count,
|
||||
lala
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { createStore } from 'context-store'
|
||||
import { createStore } from 'potent-reducer'
|
||||
import selectors from './CounterSelector'
|
||||
|
||||
const initialState = {
|
||||
counter: 0,
|
||||
@ -17,6 +18,8 @@ const reducer = {
|
||||
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 useCounter = store.useStore
|
||||
export const useCounterSelector = store.useSelector
|
||||
export { selectors }
|
||||
|
@ -1,22 +1,20 @@
|
||||
import React from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { debounce } from 'debounce'
|
||||
|
||||
import { setMaxLength, setWpm, setOffset, setLang } from '../store/actions'
|
||||
import { Slider } from './Slider'
|
||||
import { selectOffset, selectLang } from '../store/selectors'
|
||||
import { useSelector, useDispatch } from '../store/RSVPStore'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const availableLanguages = ['en', 'de']
|
||||
|
||||
export const Options = () => {
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useDispatch()
|
||||
const maxLength = useSelector(state => state.maxLength)
|
||||
const wpm = useSelector(state => state.wpm)
|
||||
const offset = useSelector(selectOffset)
|
||||
const lang = useSelector(selectLang)
|
||||
|
||||
const { setMaxLength, setWpm, setOffset, setLang } = useDispatch()
|
||||
return (
|
||||
<div>
|
||||
<h2>{t('options.title')}</h2>
|
||||
@ -25,25 +23,25 @@ export const Options = () => {
|
||||
min={3}
|
||||
max={15}
|
||||
value={maxLength}
|
||||
onChange={debounce(val => dispatch(setMaxLength(val)), 100)}
|
||||
onChange={debounce(val => setMaxLength(val), 100)}
|
||||
/>
|
||||
<Slider
|
||||
title={t('options.wpm')}
|
||||
min={100}
|
||||
max={1000}
|
||||
value={wpm}
|
||||
onChange={debounce(val => dispatch(setWpm(val)), 50)}
|
||||
onChange={debounce(val => setWpm(val), 50)}
|
||||
/>
|
||||
<Slider
|
||||
title={t('options.offset')}
|
||||
min={-50}
|
||||
max={50}
|
||||
value={offset}
|
||||
onChange={val => dispatch(setOffset(val))}
|
||||
onChange={val => setOffset(val)}
|
||||
/>
|
||||
<label>
|
||||
Language:
|
||||
<select value={lang} onChange={e => dispatch(setLang(e.target.value))}>
|
||||
<select value={lang} onChange={e => setLang(e.target.value)}>
|
||||
{availableLanguages.map(l => (
|
||||
<option key={l}>{l}</option>
|
||||
))}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useSelector } from '../store/RSVPStore'
|
||||
|
||||
import styles from './PivotMarker.css'
|
||||
import { selectOffset } from '../store/selectors'
|
||||
|
@ -1,36 +1,37 @@
|
||||
import React from 'react'
|
||||
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { useDispatch, useSelector } from '../store/RSVPStore'
|
||||
|
||||
import {
|
||||
selectHasNextSegment,
|
||||
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 PlayerControl = () => {
|
||||
const dispatch = useDispatch()
|
||||
const running = useSelector(selectRunning)
|
||||
const hasNext = useSelector(selectHasNextSegment)
|
||||
const interval = useSelector(selectInterval)
|
||||
|
||||
const { pause, start, stop, incSegment } = useDispatch()
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
if (!hasNext) dispatch(pause())
|
||||
else dispatch(incrementSegment())
|
||||
if (!hasNext) pause()
|
||||
else incSegment()
|
||||
},
|
||||
running ? interval : null
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton Icon={FiSquare} onClick={() => dispatch(stop())} />
|
||||
<IconButton Icon={FiSquare} onClick={() => stop()} />
|
||||
<IconButton
|
||||
Icon={running ? FiPause : FiPlay}
|
||||
onClick={() => dispatch(running ? pause() : start())}
|
||||
onClick={() => (running ? pause() : start())}
|
||||
disabled={!hasNext}
|
||||
/>
|
||||
</>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useSelector } from '../store/RSVPStore'
|
||||
import { selectCurrentSegmentIndex, selectSegments } from '../store/selectors'
|
||||
|
||||
export const Progress = () => {
|
||||
|
@ -2,7 +2,8 @@ import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { debounce } from 'debounce'
|
||||
import classNames from 'classnames'
|
||||
import { FiSearch } from 'react-icons/fi'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useDispatch } from '../store/RSVPStore'
|
||||
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import axios from 'axios'
|
||||
|
||||
@ -14,16 +15,15 @@ import { Book } from './Book'
|
||||
import styles from './SearchBar.css'
|
||||
import { CursorList } from './generics/CursorList'
|
||||
|
||||
import { setText, setLang } from '../store/actions.js'
|
||||
|
||||
export const SearchBar = () => {
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useDispatch()
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [results, setResults] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isFocused, setFocus] = useState(false)
|
||||
|
||||
const { setText, setLang } = useDispatch()
|
||||
|
||||
const debouncedSearch = useCallback(
|
||||
debounce(async term => {
|
||||
await search(term, 100).then(setResults)
|
||||
@ -35,9 +35,9 @@ export const SearchBar = () => {
|
||||
const handleClick = async idx => {
|
||||
const { id, language } = results[idx]
|
||||
const url = `https://gutenberg.muperfredi.de/texts/${id}/stripped-body`
|
||||
const text = await axios.get(url).then(res => res.data.body)
|
||||
dispatch(setText(text))
|
||||
dispatch(setLang(language[0]))
|
||||
const text = await axios.get(url).then(res => res.data.body)(setText(text))(
|
||||
setLang(language[0])
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useSelector } from '../store/RSVPStore'
|
||||
import { selectPivotizedSegment, selectOffset } from '../store/selectors'
|
||||
|
||||
import styles from './Segment.css'
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import {
|
||||
FiSkipBack,
|
||||
FiSkipForward,
|
||||
FiRewind,
|
||||
FiFastForward
|
||||
} from 'react-icons/fi'
|
||||
|
||||
import { useDispatch, useSelector } from '../store/RSVPStore'
|
||||
import { IconButton } from '../styles/IconButton'
|
||||
|
||||
import {
|
||||
@ -16,45 +15,39 @@ import {
|
||||
selectHasNextWord
|
||||
} from '../store/selectors'
|
||||
|
||||
import {
|
||||
incrementSentence,
|
||||
incrementWord,
|
||||
decrementSentence,
|
||||
decrementWord
|
||||
} from '../store/actions'
|
||||
|
||||
export const SegmentControl = ({ children }) => {
|
||||
const dispatch = useDispatch()
|
||||
const hasPrevSentence = useSelector(selectHasPrevSentence)
|
||||
const hasNextSentence = useSelector(selectHasNextSentence)
|
||||
const hasPrevWord = useSelector(selectHasPrevWord)
|
||||
const hasNextWord = useSelector(selectHasNextWord)
|
||||
|
||||
const { decSentence, decWord } = useDispatch()
|
||||
const { incSentence, incWord } = useDispatch()
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
title="Previous Sentence"
|
||||
onClick={() => dispatch(decrementSentence())}
|
||||
onClick={() => decSentence()}
|
||||
Icon={FiRewind}
|
||||
disabled={!hasPrevSentence}
|
||||
/>
|
||||
<IconButton
|
||||
Icon={FiSkipBack}
|
||||
title="Previous Word"
|
||||
onClick={() => dispatch(decrementWord())}
|
||||
onClick={() => decWord()}
|
||||
disabled={!hasPrevWord}
|
||||
/>
|
||||
{children}
|
||||
<IconButton
|
||||
Icon={FiSkipForward}
|
||||
title="Next Word"
|
||||
onClick={() => dispatch(incrementWord())}
|
||||
onClick={() => incWord()}
|
||||
disabled={!hasNextWord}
|
||||
/>
|
||||
<IconButton
|
||||
Icon={FiFastForward}
|
||||
title="Next Sentence"
|
||||
onClick={() => dispatch(incrementSentence())}
|
||||
onClick={() => incSentence()}
|
||||
disabled={!hasNextSentence}
|
||||
/>
|
||||
</>
|
||||
|
@ -1,7 +1,5 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import { setText } from '../store/actions.js'
|
||||
import { useDispatch } from '../store/RSVPStore'
|
||||
|
||||
import styles from './TextInput.css'
|
||||
|
||||
@ -10,7 +8,7 @@ const lorem =
|
||||
|
||||
export const TextInput = () => {
|
||||
const [text, setTextState] = useState(lorem)
|
||||
const dispatch = useDispatch()
|
||||
const { setText } = useDispatch()
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
@ -18,7 +16,7 @@ export const TextInput = () => {
|
||||
defaultValue={text}
|
||||
onInput={e => setTextState(e.target.value)}
|
||||
></textarea>
|
||||
<button className={styles.load} onClick={() => dispatch(setText(text))}>
|
||||
<button className={styles.load} onClick={() => setText(text)}>
|
||||
Load
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useSelector } from '../store/RSVPStore'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { getNextSmallerNumber } from '../lib/array-util.js'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useSelector } from '../store/RSVPStore'
|
||||
import { selectTotalTime } from '../store/selectors'
|
||||
|
||||
function formatTime(totalSeconds) {
|
||||
|
@ -2,10 +2,9 @@ import 'regenerator-runtime/runtime'
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import { Provider } from './store/RSVPStore'
|
||||
|
||||
import { App } from './App'
|
||||
import { store } from './store/index.js'
|
||||
|
||||
import './i18n'
|
||||
|
||||
@ -17,7 +16,7 @@ function createRootElement() {
|
||||
return root
|
||||
}
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<Provider>
|
||||
<App />
|
||||
</Provider>,
|
||||
createRootElement()
|
||||
|
75
src/store/RSVPStore.js
Normal file
75
src/store/RSVPStore.js
Normal 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
|
Loading…
Reference in New Issue
Block a user