Compare commits

...

6 Commits

Author SHA1 Message Date
f8632b2e97 Remove console log 2019-12-22 12:53:25 +01:00
3bec754ae8 Add Gutenberg search 2019-12-22 12:53:17 +01:00
6cc58a70bd Add a time estimate 2019-12-22 12:17:53 +01:00
c774725270 Add a progress bar 2019-12-22 12:17:15 +01:00
b88721b4f8 Add book id to entries 2019-12-22 12:15:35 +01:00
21d9b5b802 Add packages 2019-12-20 14:40:39 +01:00
11 changed files with 1282 additions and 9 deletions

File diff suppressed because one or more lines are too long

1136
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
"start": "webpack-dev-server --mode development", "start": "webpack-dev-server --mode development",
"build": "webpack --mode production", "build": "webpack --mode production",
"build-fresh": "rm -rf build/ && npm run build", "build-fresh": "rm -rf build/ && npm run build",
"serve": "ws -d build --compress",
"test": "tests", "test": "tests",
"lint": "eslint src", "lint": "eslint src",
"format": "prettier --write src/**/*.js" "format": "prettier --write src/**/*.js"
@ -14,6 +15,7 @@
"author": "Alfred Melch (dev@melch.pro)", "author": "Alfred Melch (dev@melch.pro)",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^0.19.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"debounce": "^1.2.0", "debounce": "^1.2.0",
"react": "^16.12.0", "react": "^16.12.0",
@ -21,6 +23,7 @@
"react-icons": "^3.8.0", "react-icons": "^3.8.0",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
"redux": "^4.0.4", "redux": "^4.0.4",
"regenerator-runtime": "^0.13.3",
"reselect": "^4.0.0" "reselect": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
@ -40,6 +43,7 @@
"eslint-plugin-react": "^7.17.0", "eslint-plugin-react": "^7.17.0",
"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",
"mini-css-extract-plugin": "^0.8.0", "mini-css-extract-plugin": "^0.8.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",

View File

@ -12,11 +12,15 @@ new_data = list()
stats_for = ['language', 'rights', 'subject'] stats_for = ['language', 'rights', 'subject']
stats = {key: defaultdict(int) for key in stats_for} 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 stat_key in stats_for:
for value in entry[stat_key]: for value in entry[stat_key]:
stats[stat_key][value] += 1 stats[stat_key][value] += 1
new_data.append({key: entry[key] for key in entry if key != 'formaturi'})
for stat in stats.keys(): for stat in stats.keys():
with open('data/stats_' + stat + '.json', 'w') as f: with open('data/stats_' + stat + '.json', 'w') as f:

View File

@ -1,7 +1,13 @@
import React from 'react' import React from 'react'
import { RsvpReader } from './components/RsvpReader' import { RsvpReader } from './components/RsvpReader'
import { GutenbergSearch } from './components/GutenbergSearch'
export const App = () => { export const App = () => {
return <RsvpReader></RsvpReader> return (
<>
<RsvpReader></RsvpReader>
<GutenbergSearch></GutenbergSearch>
</>
)
} }

View 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>
)
}

View 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}
/>
)
}

View File

@ -7,7 +7,9 @@ import { Options } from './Options'
import { PlayerControl } from './PlayerControl' import { PlayerControl } from './PlayerControl'
import styles from './RsvpReader.css' 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 = () => { export const RsvpReader = () => {
return ( return (
@ -16,6 +18,7 @@ export const RsvpReader = () => {
<TextInput /> <TextInput />
</div> </div>
<div className={styles.mainItem}> <div className={styles.mainItem}>
<Progress />
<BorderMarker> <BorderMarker>
<Segment /> <Segment />
</BorderMarker> </BorderMarker>
@ -25,10 +28,9 @@ export const RsvpReader = () => {
</SegmentControl> </SegmentControl>
</div> </div>
<Options></Options> <Options></Options>
<TimeCalc />
</div> </div>
<div className={styles.item}> <div className={styles.item}>{/* <TextOutput /> */}</div>
<TextOutput />
</div>
</div> </div>
) )
} }

View 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>
)
}

View File

@ -1,3 +1,5 @@
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 'react-redux'

View File

@ -82,7 +82,6 @@ export const selectPrevWord = createSelector(
selectWords, selectWords,
selectCurrentSegmentIndex, selectCurrentSegmentIndex,
(words, curSegment) => { (words, curSegment) => {
console.log(words, curSegment, getNextSmallerNumber(curSegment, words))
return getNextSmallerNumber(curSegment, words) return getNextSmallerNumber(curSegment, words)
} }
) )