Compare commits
6 Commits
5870b906a0
...
f8632b2e97
Author | SHA1 | Date | |
---|---|---|---|
f8632b2e97 | |||
3bec754ae8 | |||
6cc58a70bd | |||
c774725270 | |||
b88721b4f8 | |||
21d9b5b802 |
File diff suppressed because one or more lines are too long
1136
package-lock.json
generated
1136
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@
|
||||
"start": "webpack-dev-server --mode development",
|
||||
"build": "webpack --mode production",
|
||||
"build-fresh": "rm -rf build/ && npm run build",
|
||||
"serve": "ws -d build --compress",
|
||||
"test": "tests",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src/**/*.js"
|
||||
@ -14,6 +15,7 @@
|
||||
"author": "Alfred Melch (dev@melch.pro)",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"classnames": "^2.2.6",
|
||||
"debounce": "^1.2.0",
|
||||
"react": "^16.12.0",
|
||||
@ -21,6 +23,7 @@
|
||||
"react-icons": "^3.8.0",
|
||||
"react-redux": "^7.1.3",
|
||||
"redux": "^4.0.4",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -40,6 +43,7 @@
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"html-webpack-plugin": "^4.0.0-beta.11",
|
||||
"local-web-server": "^3.0.7",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-flexbugs-fixes": "^4.1.0",
|
||||
|
@ -12,11 +12,15 @@ new_data = list()
|
||||
stats_for = ['language', 'rights', 'subject']
|
||||
stats = {key: defaultdict(int) for key in stats_for}
|
||||
|
||||
for entry in data.values():
|
||||
for book_id, entry in data.items():
|
||||
# strip formaturi from entry
|
||||
new_entry = {key: entry[key] for key in entry if key != 'formaturi'}
|
||||
new_entry['id'] = book_id
|
||||
new_data.append(new_entry)
|
||||
# add stats
|
||||
for stat_key in stats_for:
|
||||
for value in entry[stat_key]:
|
||||
stats[stat_key][value] += 1
|
||||
new_data.append({key: entry[key] for key in entry if key != 'formaturi'})
|
||||
|
||||
for stat in stats.keys():
|
||||
with open('data/stats_' + stat + '.json', 'w') as f:
|
||||
|
@ -1,7 +1,13 @@
|
||||
import React from 'react'
|
||||
|
||||
import { RsvpReader } from './components/RsvpReader'
|
||||
import { GutenbergSearch } from './components/GutenbergSearch'
|
||||
|
||||
export const App = () => {
|
||||
return <RsvpReader></RsvpReader>
|
||||
return (
|
||||
<>
|
||||
<RsvpReader></RsvpReader>
|
||||
<GutenbergSearch></GutenbergSearch>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
82
src/components/GutenbergSearch.js
Normal file
82
src/components/GutenbergSearch.js
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import axios from 'axios'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { debounce } from 'debounce'
|
||||
|
||||
import { setText } from '../store/actions.js'
|
||||
|
||||
async function search(searchTerm) {
|
||||
const regex = new RegExp(searchTerm, 'i')
|
||||
const result = []
|
||||
const data = await import('../../data/gutenberg.json').then(
|
||||
module => module.default
|
||||
)
|
||||
for (let entry of data) {
|
||||
if (regex.test(entry.title[0]) || regex.test(entry.author[0])) {
|
||||
result.push(entry)
|
||||
}
|
||||
|
||||
if (result.length >= 20) break
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const Book = ({ entry }) => {
|
||||
const dispatch = useDispatch()
|
||||
const { author, language, rights, subject, title, id } = entry
|
||||
|
||||
const handleClick = async () => {
|
||||
const url = `https://gutenberg.muperfredi.de/texts/${id}/stripped-body`
|
||||
const text = await axios.get(url).then(res => res.data.body)
|
||||
dispatch(setText(text))
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{id} {title.join(' - ')}
|
||||
</div>
|
||||
<div>{author[0]}</div>
|
||||
<div>{language.join(' - ')}</div>
|
||||
<div>
|
||||
<button onClick={handleClick}>Load</button>
|
||||
</div>
|
||||
<br></br>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const GutenbergSearch = () => {
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [result, setResult] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const debouncedSearch = useCallback(
|
||||
debounce(async term => {
|
||||
setLoading(true)
|
||||
await search(term).then(setResult)
|
||||
setLoading(false)
|
||||
}, 500),
|
||||
[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (searchTerm.length > 0) {
|
||||
debouncedSearch(searchTerm)
|
||||
}
|
||||
}, [searchTerm, debouncedSearch])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Search for books in the Gutenberg Project</h2>
|
||||
<input
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
></input>
|
||||
{loading && 'loading...'}
|
||||
{result.map(entry => (
|
||||
<Book key={entry.id} entry={entry} />
|
||||
))}
|
||||
{result.length === 0 && <div>'no results to display'</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
15
src/components/Progress.js
Normal file
15
src/components/Progress.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { selectCurrentSegmentIndex, selectSegments } from '../store/selectors'
|
||||
|
||||
export const Progress = () => {
|
||||
const curIdx = useSelector(selectCurrentSegmentIndex)
|
||||
const segments = useSelector(selectSegments)
|
||||
return (
|
||||
<progress
|
||||
style={{ width: '100%' }}
|
||||
value={curIdx}
|
||||
max={segments.length - 1}
|
||||
/>
|
||||
)
|
||||
}
|
@ -7,7 +7,9 @@ import { Options } from './Options'
|
||||
import { PlayerControl } from './PlayerControl'
|
||||
|
||||
import styles from './RsvpReader.css'
|
||||
import { PipeMarker, BorderMarker } from './PivotMarker'
|
||||
import { BorderMarker } from './PivotMarker'
|
||||
import { Progress } from './Progress'
|
||||
import { TimeCalc } from './TimeCalc'
|
||||
|
||||
export const RsvpReader = () => {
|
||||
return (
|
||||
@ -16,6 +18,7 @@ export const RsvpReader = () => {
|
||||
<TextInput />
|
||||
</div>
|
||||
<div className={styles.mainItem}>
|
||||
<Progress />
|
||||
<BorderMarker>
|
||||
<Segment />
|
||||
</BorderMarker>
|
||||
@ -25,10 +28,9 @@ export const RsvpReader = () => {
|
||||
</SegmentControl>
|
||||
</div>
|
||||
<Options></Options>
|
||||
<TimeCalc />
|
||||
</div>
|
||||
<div className={styles.item}>
|
||||
<TextOutput />
|
||||
</div>
|
||||
<div className={styles.item}>{/* <TextOutput /> */}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
23
src/components/TimeCalc.js
Normal file
23
src/components/TimeCalc.js
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { selectSegments, selectWpm } from '../store/selectors'
|
||||
|
||||
function formatTime(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = Math.floor(totalSeconds % 60)
|
||||
const pad = num => String(num).padStart(2, '0')
|
||||
return `${hours}:${pad(minutes)}:${pad(seconds)}`
|
||||
}
|
||||
|
||||
export const TimeCalc = () => {
|
||||
const wpm = useSelector(selectWpm)
|
||||
const segments = useSelector(selectSegments)
|
||||
const minutesNeeded = segments.length / wpm
|
||||
return (
|
||||
<div>
|
||||
<h2>Time needed</h2>
|
||||
Time needed for Text: {formatTime(minutesNeeded * 60)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import 'regenerator-runtime/runtime'
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
|
@ -82,7 +82,6 @@ export const selectPrevWord = createSelector(
|
||||
selectWords,
|
||||
selectCurrentSegmentIndex,
|
||||
(words, curSegment) => {
|
||||
console.log(words, curSegment, getNextSmallerNumber(curSegment, words))
|
||||
return getNextSmallerNumber(curSegment, words)
|
||||
}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user