Compare commits
13 Commits
16cdc1acf7
...
4656f5b280
Author | SHA1 | Date | |
---|---|---|---|
4656f5b280 | |||
d69487b048 | |||
5afe62bdcc | |||
12fd1e3981 | |||
1ba18b7341 | |||
5267561d66 | |||
ecc9e90bec | |||
2de1c47ed6 | |||
cbbe821fde | |||
60443f481e | |||
5a32f3de0c | |||
366c338528 | |||
444ef30587 |
@ -2,7 +2,10 @@ module.exports = api => {
|
|||||||
// caching the babel config
|
// caching the babel config
|
||||||
api.cache.using(() => process.env.NODE_ENV)
|
api.cache.using(() => process.env.NODE_ENV)
|
||||||
return {
|
return {
|
||||||
presets: ['@babel/preset-env', '@babel/preset-react']
|
presets: [
|
||||||
|
require.resolve('@babel/preset-env'),
|
||||||
|
require.resolve('@babel/preset-react')
|
||||||
|
]
|
||||||
// plugins: [api.env('development') && 'react-refresh/babel'].filter(Boolean)
|
// plugins: [api.env('development') && 'react-refresh/babel'].filter(Boolean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
data/jsconfig.json
Normal file
6
data/jsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src"
|
||||||
|
},
|
||||||
|
"include": ["src", "lib"]
|
||||||
|
}
|
@ -1,83 +0,0 @@
|
|||||||
import React, { createContext, useContext, useMemo, useReducer } from 'react'
|
|
||||||
|
|
||||||
/** Takes options that fully define a store and turns it into a more potent reducer */
|
|
||||||
export const usePotentReducer = options => {
|
|
||||||
const { reducer = {}, thunks = {}, initialState } = options
|
|
||||||
const { onUpdate, logging } = options
|
|
||||||
const reducerFn = useMemo(
|
|
||||||
() => makeReducerFn(reducer, { onUpdate, logging }),
|
|
||||||
[reducer, onUpdate, logging]
|
|
||||||
)
|
|
||||||
const [state, dispatch] = useReducer(reducerFn, initialState)
|
|
||||||
const _thunks = useMemo(() => bindThunks(thunks, dispatch), [
|
|
||||||
thunks,
|
|
||||||
dispatch
|
|
||||||
])
|
|
||||||
return [state, _thunks]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A ReducerStore Factory that uses React.context
|
|
||||||
* Used to make a Store accessible through multiple components
|
|
||||||
*/
|
|
||||||
export const createStore = options => {
|
|
||||||
const context = createContext(null)
|
|
||||||
const { Consumer } = context
|
|
||||||
const Provider = ({ children }) => {
|
|
||||||
const store = usePotentReducer(options)
|
|
||||||
return React.createElement(context.Provider, { value: store }, children)
|
|
||||||
}
|
|
||||||
const useStore = () => useContext(context)
|
|
||||||
const useSelector = selector => selector(useContext(context)[0])
|
|
||||||
const useDispatch = () => useContext(context)[1]
|
|
||||||
return { Provider, Consumer, useStore, useSelector, useDispatch }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Turn a reducer definition object to a function
|
|
||||||
* @param {Object} reducer: Object definition of a reducer: {<type>: (state, payload) => <newState>}
|
|
||||||
* @returns {Function}: Proper reducer
|
|
||||||
*/
|
|
||||||
function makeReducerFn(reducer, { onUpdate, logging }) {
|
|
||||||
const noop = state => state
|
|
||||||
return (prevState, action) => {
|
|
||||||
const { type } = action
|
|
||||||
const reducerBranch = reducer[type] || reducer['default'] || noop
|
|
||||||
const nextState = reducerBranch(prevState, action)
|
|
||||||
const stats = { prevState, nextState, action }
|
|
||||||
typeof onUpdate === 'function' && onUpdate(stats)
|
|
||||||
if (logging) logger(stats)
|
|
||||||
return nextState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a set of actions that are bound to the store
|
|
||||||
* @param {Object} actions: {<name>: payload => ({dispatch, type}) => }
|
|
||||||
*/
|
|
||||||
function bindThunks(thunks, dispatch) {
|
|
||||||
const _thunks = {}
|
|
||||||
for (let name of Object.keys(thunks)) {
|
|
||||||
const defaultType = camelToSnakeCase(name).toUpperCase()
|
|
||||||
_thunks[name] = (...args) =>
|
|
||||||
thunks[name](...args)(patchDispatch(dispatch, defaultType))
|
|
||||||
}
|
|
||||||
return _thunks
|
|
||||||
}
|
|
||||||
|
|
||||||
const camelToSnakeCase = str =>
|
|
||||||
str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)
|
|
||||||
|
|
||||||
/** Returns a dispatch function that fills in a default type if none is given */
|
|
||||||
function patchDispatch(dispatch, type) {
|
|
||||||
return action => {
|
|
||||||
dispatch({ type, ...action })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Log action inspired by redux-logger */
|
|
||||||
const logger = ({ prevState, action, nextState }) => {
|
|
||||||
const css = color => `color: ${color}; font-weight: bold;`
|
|
||||||
console.groupCollapsed(`%c action`, 'color: #9E9E9E', action.type)
|
|
||||||
console.log('%c prevState ', css('#9E9E9E'), prevState)
|
|
||||||
console.log('%c action ', css('#03A9F4'), action)
|
|
||||||
console.log('%c nextState ', css('#4CAF50'), nextState)
|
|
||||||
console.groupEnd()
|
|
||||||
}
|
|
3823
package-lock.json
generated
3823
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@ -10,56 +10,57 @@
|
|||||||
"serve": "ws -d build --compress --hostname 0.0.0.0",
|
"serve": "ws -d build --compress --hostname 0.0.0.0",
|
||||||
"test": "tests",
|
"test": "tests",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"format": "prettier --write src/**/*.js"
|
"format": "prettier --write src/**/*.js",
|
||||||
|
"graph": "mkdir -p build && madge src --exclude '.css$' --image build/dep-graph.svg"
|
||||||
},
|
},
|
||||||
"author": "Alfred Melch (dev@melch.pro)",
|
"author": "Alfred Melch (dev@melch.pro)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"debounce": "^1.2.0",
|
"debounce": "^1.2.0",
|
||||||
"i18next": "^19.1.0",
|
"i18next": "^19.1.0",
|
||||||
|
"potent-reducer": "^0.1.0",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-i18next": "^11.3.1",
|
"react-i18next": "^11.3.1",
|
||||||
"react-icons": "^3.8.0",
|
"react-icons": "^3.9.0",
|
||||||
"react-redux": "^7.1.3",
|
|
||||||
"redux": "^4.0.4",
|
|
||||||
"regenerator-runtime": "^0.13.3",
|
"regenerator-runtime": "^0.13.3",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"reselect-tools": "0.0.7"
|
"reselect-tools": "0.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.7.5",
|
"@babel/core": "^7.8.4",
|
||||||
"@babel/preset-env": "^7.7.6",
|
"@babel/preset-env": "^7.8.4",
|
||||||
"@babel/preset-react": "^7.7.4",
|
"@babel/preset-react": "^7.8.3",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.1.1",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.1.3",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"css-loader": "^3.3.2",
|
"css-loader": "^3.4.2",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.10.0",
|
||||||
"eslint-config-react-app": "^5.1.0",
|
"eslint-config-react-app": "^5.2.0",
|
||||||
"eslint-plugin-flowtype": "^4.5.2",
|
"eslint-plugin-flowtype": "^4.6.0",
|
||||||
"eslint-plugin-import": "^2.19.1",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||||
"eslint-plugin-react": "^7.17.0",
|
"eslint-plugin-react": "^7.18.2",
|
||||||
"eslint-plugin-react-hooks": "^2.3.0",
|
"eslint-plugin-react-hooks": "^2.3.0",
|
||||||
"html-webpack-plugin": "^4.0.0-beta.11",
|
"html-webpack-plugin": "^4.0.0-beta.11",
|
||||||
"local-web-server": "^3.0.7",
|
"local-web-server": "^3.0.7",
|
||||||
"mini-css-extract-plugin": "^0.8.0",
|
"madge": "^3.7.0",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||||
"postcss-flexbugs-fixes": "^4.1.0",
|
"postcss-flexbugs-fixes": "^4.1.0",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-normalize": "^8.0.1",
|
"postcss-normalize": "^8.0.1",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"react-refresh": "^0.7.0",
|
"react-refresh": "^0.7.2",
|
||||||
"style-loader": "^1.0.1",
|
"style-loader": "^1.1.3",
|
||||||
"webpack": "^4.41.2",
|
"webpack": "^4.41.5",
|
||||||
"webpack-bundle-analyzer": "^3.6.0",
|
"webpack-bundle-analyzer": "^3.6.0",
|
||||||
"webpack-cli": "^3.3.10",
|
"webpack-cli": "^3.3.10",
|
||||||
"webpack-dev-server": "^3.9.0",
|
"webpack-dev-server": "^3.10.2",
|
||||||
"webpackbar": "^4.0.0"
|
"webpackbar": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import { SearchBar } from './components/SearchBar'
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
import { LangSelect } from './components/LangSelect'
|
import { LangSelect } from './components/LangSelect'
|
||||||
|
|
||||||
import { Counter } from 'components/Counter'
|
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
@ -22,7 +20,6 @@ export const App = () => {
|
|||||||
<RsvpReader></RsvpReader>
|
<RsvpReader></RsvpReader>
|
||||||
</main>
|
</main>
|
||||||
<footer>Made by Alfred Melch</footer>
|
<footer>Made by Alfred Melch</footer>
|
||||||
<Counter />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
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, title, id }) => {
|
export const Book = ({ author, language, title }) => {
|
||||||
const [_, { setText, setLang }] = useStore()
|
|
||||||
|
|
||||||
const handleClick = async () => {
|
|
||||||
setText(await getBook(id))
|
|
||||||
setLang(language[0])
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.book} onClick={handleClick}>
|
<div className={styles.book}>
|
||||||
<div className={styles.title}>{title.join(' – ')}</div>
|
<div className={styles.title}>{title.join(' – ')}</div>
|
||||||
<div className={styles.author}>{author[0]}</div>
|
<div className={styles.author}>{author[0]}</div>
|
||||||
<div className={styles.language}>{language.join(' – ')}</div>
|
<div className={styles.language}>{language.join(' – ')}</div>
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
useCounter,
|
|
||||||
CounterProvider,
|
|
||||||
selectors,
|
|
||||||
useCounterSelector
|
|
||||||
} from './CounterStore'
|
|
||||||
|
|
||||||
export const Counter = () => {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<CounterProvider>
|
|
||||||
<MyCounter />
|
|
||||||
<Text />
|
|
||||||
<TextSetter />
|
|
||||||
<Logger />
|
|
||||||
</CounterProvider>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Logger = () => {
|
|
||||||
const store = useCounter()
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
const MyCounter = () => {
|
|
||||||
const [_, { increment, decrement }] = useCounter()
|
|
||||||
const counter = useCounterSelector(selectors.$count)
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{counter}
|
|
||||||
<button onClick={increment}>+</button>
|
|
||||||
<button onClick={decrement}>-</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Text = () => {
|
|
||||||
const [{ text }] = useCounter()
|
|
||||||
return <span>{text}</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
const TextSetter = () => {
|
|
||||||
const [{ text }, { setText }] = useCounter()
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={text}
|
|
||||||
onChange={evt => setText(evt.target.value)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
export const $count = state => {
|
|
||||||
return state.counter
|
|
||||||
}
|
|
||||||
export const lala = 'lalal'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
$count,
|
|
||||||
lala
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { createStore } from 'potent-reducer'
|
|
||||||
import selectors from './CounterSelector'
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
counter: 0,
|
|
||||||
text: 'initial'
|
|
||||||
}
|
|
||||||
|
|
||||||
const thunks = {
|
|
||||||
increment: () => dispatch => dispatch(),
|
|
||||||
decrement: () => dispatch => dispatch(),
|
|
||||||
setText: text => dispatch => dispatch({ text })
|
|
||||||
}
|
|
||||||
|
|
||||||
const reducer = {
|
|
||||||
INCREMENT: state => ({ ...state, counter: state.counter + 1 }),
|
|
||||||
DECREMENT: state => ({ ...state, counter: state.counter - 1 }),
|
|
||||||
SET_TEXT: (state, { text }) => ({ ...state, text })
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }
|
|
@ -3,7 +3,7 @@ import { debounce } from 'debounce'
|
|||||||
|
|
||||||
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 { useSelector, useDispatch } from '../store'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const availableLanguages = ['en', 'de']
|
const availableLanguages = ['en', 'de']
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from '../store/RSVPStore'
|
import { useSelector } from '../store'
|
||||||
|
|
||||||
import styles from './PivotMarker.css'
|
import styles from './PivotMarker.css'
|
||||||
import { selectOffset } from '../store/selectors'
|
import { selectOffset } from '../store/selectors'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { useDispatch, useSelector } from '../store/RSVPStore'
|
import { useDispatch, useSelector } from '../store'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectHasNextSegment,
|
selectHasNextSegment,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from '../store/RSVPStore'
|
import { useSelector } from '../store'
|
||||||
import { selectCurrentSegmentIndex, selectSegments } from '../store/selectors'
|
import { selectCurrentSegmentIndex, selectSegments } from '../store/selectors'
|
||||||
|
|
||||||
export const Progress = () => {
|
export const Progress = () => {
|
||||||
|
@ -2,12 +2,11 @@ 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 '../store/RSVPStore'
|
import { useDispatch } from '../store'
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import { search } from '../lib/gutenberg'
|
import { search, getBook } from '../lib/gutenberg'
|
||||||
import { Spinner } from '../styles/Spinner'
|
import { Spinner } from '../styles/Spinner'
|
||||||
import { Dropdown } from './generics/Dropdown'
|
import { Dropdown } from './generics/Dropdown'
|
||||||
import { Book } from './Book'
|
import { Book } from './Book'
|
||||||
@ -32,12 +31,10 @@ export const SearchBar = () => {
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleClick = async idx => {
|
const handleSelect = async idx => {
|
||||||
const { id, language } = results[idx]
|
const { id, language } = results[idx]
|
||||||
const url = `https://gutenberg.muperfredi.de/texts/${id}/stripped-body`
|
setText(await getBook(id))
|
||||||
const text = await axios.get(url).then(res => res.data.body)(setText(text))(
|
setLang(language[0])
|
||||||
setLang(language[0])
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -72,7 +69,7 @@ export const SearchBar = () => {
|
|||||||
items={results.map(entry => (
|
items={results.map(entry => (
|
||||||
<Book {...entry} />
|
<Book {...entry} />
|
||||||
))}
|
))}
|
||||||
onSelect={handleClick}
|
onSelect={handleSelect}
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from '../store/RSVPStore'
|
import { useSelector } from '../store'
|
||||||
import { selectPivotizedSegment, selectOffset } from '../store/selectors'
|
import { selectPivotizedSegment, selectOffset } from '../store/selectors'
|
||||||
|
|
||||||
import styles from './Segment.css'
|
import styles from './Segment.css'
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
FiRewind,
|
FiRewind,
|
||||||
FiFastForward
|
FiFastForward
|
||||||
} from 'react-icons/fi'
|
} from 'react-icons/fi'
|
||||||
import { useDispatch, useSelector } from '../store/RSVPStore'
|
import { useDispatch, useSelector } from '../store'
|
||||||
import { IconButton } from '../styles/IconButton'
|
import { IconButton } from '../styles/IconButton'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useDispatch } from '../store/RSVPStore'
|
import { useDispatch } from '../store'
|
||||||
|
|
||||||
import styles from './TextInput.css'
|
import styles from './TextInput.css'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from '../store/RSVPStore'
|
import { useSelector } from '../store'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import { getNextSmallerNumber } from '../lib/array-util.js'
|
import { getNextSmallerNumber } from '../lib/array-util.js'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useSelector } from '../store/RSVPStore'
|
import { useSelector } from '../store'
|
||||||
import { selectTotalTime } from '../store/selectors'
|
import { selectTotalTime } from '../store/selectors'
|
||||||
|
|
||||||
function formatTime(totalSeconds) {
|
function formatTime(totalSeconds) {
|
||||||
|
@ -38,7 +38,6 @@ export const CursorList = ({ items, onSelect = noop }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedRef.current) {
|
if (selectedRef.current) {
|
||||||
selectedRef.current.scrollIntoView({
|
selectedRef.current.scrollIntoView({
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'nearest'
|
block: 'nearest'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ 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 './store/RSVPStore'
|
import { Provider } from './store'
|
||||||
|
|
||||||
import { App } from './App'
|
import { App } from './App'
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ function createRootElement() {
|
|||||||
body.appendChild(root)
|
body.appendChild(root)
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider>
|
<Provider>
|
||||||
<App />
|
<App />
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
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
|
|
@ -1,18 +0,0 @@
|
|||||||
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' })
|
|
||||||
export const decrementWord = () => ({ type: 'DEC_WORD' })
|
|
||||||
export const incrementSentence = () => ({ type: 'INC_SENTENCE' })
|
|
||||||
export const decrementSentence = () => ({ type: 'DEC_SENTENCE' })
|
|
||||||
export const setMaxLength = length => ({
|
|
||||||
type: 'SET_MAX_LENGTH',
|
|
||||||
payload: length
|
|
||||||
})
|
|
||||||
export const setWpm = wpm => ({ type: 'SET_WPM', payload: wpm })
|
|
||||||
export const setOffset = offset => ({ type: 'SET_OFFSET', payload: offset })
|
|
||||||
export const start = () => ({ type: 'START' })
|
|
||||||
export const stop = () => ({ type: 'STOP' })
|
|
||||||
export const pause = () => ({ type: 'PAUSE' })
|
|
||||||
export const setLang = lang => ({ type: 'SET_LANG', payload: lang })
|
|
@ -1,14 +1,81 @@
|
|||||||
import { createStore } from 'redux'
|
import { createStore } from 'potent-reducer'
|
||||||
import { registerSelectors, getStateWith } from 'reselect-tools'
|
import {
|
||||||
|
safeSelectNextWord,
|
||||||
|
safeSelectNextSentence,
|
||||||
|
safeSelectPrevWord,
|
||||||
|
safeSelectPrevSentence,
|
||||||
|
safeSelectNextSegment,
|
||||||
|
safeSelectPrevSegment
|
||||||
|
} from './selectors'
|
||||||
|
|
||||||
import { reducerFn, initialState } from './reducer'
|
const initialState = {
|
||||||
import * as selectors from './selectors'
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const store = createStore(
|
const thunks = {
|
||||||
reducerFn,
|
setText: text => ({ text }),
|
||||||
|
resetSegment: () => {},
|
||||||
|
incSegment: () => {},
|
||||||
|
decSegment: () => {},
|
||||||
|
incWord: () => {},
|
||||||
|
decWord: () => {},
|
||||||
|
incSentence: () => {},
|
||||||
|
decSentence: () => {},
|
||||||
|
setMaxLength: length => ({ length }),
|
||||||
|
setWpm: wpm => ({ wpm }),
|
||||||
|
setOffset: offset => ({ offset }),
|
||||||
|
start: () => {},
|
||||||
|
stop: () => {},
|
||||||
|
pause: () => {},
|
||||||
|
setLang: lang => ({ 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,
|
initialState,
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
|
logging: true,
|
||||||
)
|
warnOnUndefinedSelect: true
|
||||||
|
})
|
||||||
registerSelectors(selectors)
|
export const Provider = store.Provider
|
||||||
getStateWith(() => store.getState())
|
export const useStore = store.useStore
|
||||||
|
export const useSelector = store.useSelector
|
||||||
|
export const useDispatch = store.useDispatch
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import {
|
|
||||||
safeSelectNextWord,
|
|
||||||
safeSelectNextSentence,
|
|
||||||
safeSelectPrevWord,
|
|
||||||
safeSelectPrevSentence,
|
|
||||||
safeSelectNextSegment,
|
|
||||||
safeSelectPrevSegment
|
|
||||||
} from './selectors'
|
|
||||||
|
|
||||||
export 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 reducer = {
|
|
||||||
SET_TEXT: (state, payload) => ({
|
|
||||||
...state,
|
|
||||||
originalText: payload,
|
|
||||||
curIdx: 0
|
|
||||||
}),
|
|
||||||
SET_CURRENT_SEGMENT: (state, payload) => ({ ...state, curIdx: payload }),
|
|
||||||
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, payload) => ({
|
|
||||||
...state,
|
|
||||||
maxLength: payload,
|
|
||||||
running: false,
|
|
||||||
curIdx: 0
|
|
||||||
}),
|
|
||||||
SET_WPM: (state, payload) => ({ ...state, wpm: payload }),
|
|
||||||
SET_OFFSET: (state, payload) => ({ ...state, offset: payload }),
|
|
||||||
START: state => ({ ...state, running: true }),
|
|
||||||
STOP: state => ({ ...state, running: false, curIdx: 0 }),
|
|
||||||
PAUSE: state => ({ ...state, running: false }),
|
|
||||||
SET_LANG: (state, payload) => ({ ...state, lang: payload })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const reducerFn = (state, { type, payload }) => {
|
|
||||||
return reducer[type] ? reducer[type](state, payload) : state
|
|
||||||
}
|
|
@ -25,16 +25,16 @@ module.exports = (env, argv) => {
|
|||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
loader: 'babel-loader'
|
loader: require.resolve('babel-loader')
|
||||||
},
|
},
|
||||||
// process css. css modules are enabled.
|
// process css. css modules are enabled.
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: [
|
use: [
|
||||||
isEnvDevelopment && 'style-loader',
|
isEnvDevelopment && require.resolve('style-loader'),
|
||||||
isEnvProduction && MiniCssExtractPlugin.loader,
|
isEnvProduction && MiniCssExtractPlugin.loader,
|
||||||
{
|
{
|
||||||
loader: 'css-loader',
|
loader: require.resolve('css-loader'),
|
||||||
options: {
|
options: {
|
||||||
modules: {
|
modules: {
|
||||||
localIdentName: isEnvProduction
|
localIdentName: isEnvProduction
|
||||||
@ -43,7 +43,7 @@ module.exports = (env, argv) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'postcss-loader'
|
require.resolve('postcss-loader')
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user