filter by language

pull/6/head
nrfq 2021-12-02 16:55:12 -05:00
parent c3cbc18814
commit 0524b18250
5 changed files with 159 additions and 99 deletions

View File

@ -1,43 +1,32 @@
.App {
text-align: center; body{
box-sizing: border-box;
background-color: #222222;
} }
.App-logo { #root {
height: 40vmin; box-sizing: border-box;
pointer-events: none;
} }
@media (prefers-reduced-motion: no-preference) { .frontPage {
.App-logo { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
animation: App-logo-spin infinite 20s linear; 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
} sans-serif;
} -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
#frontPage {
color: blue;
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; height: 100;
justify-content: center; color: whitesmoke;
font-size: calc(10px + 2vmin); text-align: center;
color: white;
} }
.App-link { .search-results{
color: #61dafb; display: flex;
} flex-direction: row;
flex-wrap: wrap;
@keyframes App-logo-spin { box-sizing: border-box;
from { justify-content: space-around;
transform: rotate(0deg); padding-left: 10%;
} padding-right: 10%;
to {
transform: rotate(360deg);
}
} }

View File

@ -1,9 +1,12 @@
import React, { useState, useEffect, Component } from 'react'; import React, { useState, useEffect, Component } from 'react';
import LangDropdown from './components/LangDropdown'; import LangDropdown from './components/LangDropdown';
import SearchBar from './components/SearchBar'; import SearchBar from './components/SearchBar';
import SearchResult from './components/SearchResult';
import axios from 'axios'; import axios from 'axios';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
const fpb = require('./fpb.json');
function makeBook(author, hLang, cLang, title, url) function makeBook(author, hLang, cLang, title, url)
{ {
//returns a struct with basic book info (author, human language, computer language, book title, url) //returns a struct with basic book info (author, human language, computer language, book title, url)
@ -24,9 +27,9 @@ function forEachBook(func, json) //Runs func on each section, entry, and book in
for (const hLang in json) //for each human language for (const hLang in json) //for each human language
{ {
if (Array.isArray(json[hLang].sections)) //check if sections is an array if (Array.isArray(hLang.sections)) //check if sections is an array
{ {
json[hLang].sections.forEach( hLang.sections.forEach(
(cLang) => //for each computer lanuage (cLang) => //for each computer lanuage
{if (Array.isArray(cLang.entries)) //verify is entries is an array {if (Array.isArray(cLang.entries)) //verify is entries is an array
{ {
@ -97,24 +100,68 @@ function sortByScore(results){
return results; return results;
} }
function App() {
const [ data, setData ] = useState(undefined);
const [ loading, setLoading ] = useState(true); //Determines whether to show spinner
const [ searchTerm, setSearchTerm ] = useState('');
const [ searchResults, setSearchResults ] = useState([]);
let resultsList = null; // the html string containing the search results
const setSearch = (term) => { // Lets a child set the value of the search term function jsonToArray(json){
setSearchTerm(term); let arr = [];
let sections = [];
json.children[0].children.forEach(
(document) => {
document.sections.forEach(
(section) => {
if(!sections.includes(section.section))
sections.push(section.section);
section.entries.forEach(
(entry) => {
arr.push({author: entry.author, title: entry.title, url: entry.url, lang: document.language, section: section.section});
}
)
section.subsections.forEach(
(subsection) => {
subsection.entries.forEach(
(entry) => {
arr.push({author: entry.author, title: entry.title, url: entry.url, lang: document.language, section: section.section, subsection: subsection.section});
}
)
}
)
}
)
}
)
return {arr: arr, sections: sections};
}
function App() {
const [ data, setData ] = useState(undefined); // keeps the state of the json
const [ dataArray, setDataArray ] = useState([]); // put everything into one array. uses more memory, but search is faster and less complex
const [ index, setIndex ] = useState([]);
const [ loading, setLoading ] = useState(true); //Determines whether to show spinner
const [ searchParams, setSearchParams ] = useState({title: ''});
const [ searchResults, setSearchResults ] = useState([]);
const [ error, setError ] = useState('');
let resultsList = null; // the html string containing the search results
let sectionResults = null;
const changeParameter = (param, value) => { // Lets a child set the value of the search term
setSearchParams({...searchParams, [param]: value});
}; };
// fetches data the first time the page renders // fetches data the first time the page renders
useEffect( useEffect(
() => { () => {
async function fetchData() { async function fetchData() {
try{
setLoading(true); setLoading(true);
let result = await axios.get('https://raw.githubusercontent.com/FreeEbookFoundationBot/free-programming-books-json/main/fpb.json'); let result = await axios.get('https://raw.githubusercontent.com/FreeEbookFoundationBot/free-programming-books-json/main/fpb.json');
setData(result.data); setData(result.data);
let { arr, sections } = jsonToArray(result.data);
setDataArray(arr);
setIndex(sections);
}
catch(e){
setError("Couldn't get data. Please try again later")
}
setLoading(false); setLoading(false);
} }
fetchData(); fetchData();
@ -126,62 +173,67 @@ function App() {
// THIS IS THE MAIN SEARCH FUNCTION CURRENTLY // THIS IS THE MAIN SEARCH FUNCTION CURRENTLY
useEffect( useEffect(
() => { () => {
if(data){ if(dataArray){
let result = [];
data.children[0].children.forEach( (document) => {
document.sections.forEach( (section) => {
const fuseOptions = { const fuseOptions = {
useExtendedSearch: true,
findAllMatches: true, findAllMatches: true,
shouldSort: false, shouldSort: true,
includeScore: true, includeScore: true,
threshold: 0.3, threshold: 0.2,
keys: ['title'] keys: ['title', 'lang.code']
}; }
let fuse = new Fuse(section.entries, fuseOptions);
let fuseResult = fuse.search(searchTerm); let fuse = new Fuse(dataArray, fuseOptions);
result = result.concat(fuseResult); let query = [];
section.subsections.forEach( (subsection) => { for (const [key, value] of Object.entries(searchParams)) {
let fuse = new Fuse(subsection.entries, fuseOptions); if(value == null || value == '') continue;
let fuseResult = fuse.search(searchTerm); if(key == 'lang.code'){
result = result.concat(fuseResult); query.push({'lang.code': `^${value}`});
continue
}
query.push({[key]: value});
}
let result = fuse.search({
$and: query
}); });
}); setSearchResults(result.slice(0, 40));
});
result = sortByScore(result);
setSearchResults(result);
} }
}, },
[ searchTerm ] [ searchParams ]
) )
const buildList = () => {
};
if(loading){ // if still fetching resource if(loading){ // if still fetching resource
return( return(
<h1>Loading...</h1> <h1>Loading...</h1>
); );
} }
if(searchTerm && searchResults.length !== 0){ if(error){
return(
<h1>Error: {error}</h1>
)
}
if(searchParams.title && searchResults.length !== 0){
resultsList = resultsList =
searchResults && searchResults &&
searchResults.map((entry) => { searchResults.map((entry) => {
return (<li><a href={entry.item.url}>{entry.item.title}</a></li>) return <SearchResult data={entry.item}/>
// return (<li><a href={entry.item.url}>{entry.item.title}</a></li>)
}); });
} }
console.log(data);
return( return(
<div> <div className="frontPage">
<div id="frontPage">
<h1>Free Programming Books</h1> <h1>Free Programming Books</h1>
{/* <input type="text"></input> */} <div>
<SearchBar setSearch={setSearch}/> <SearchBar changeParameter={changeParameter}/>
<LangDropdown data={data}/> <LangDropdown changeParameter={changeParameter} data={data}/>
<SubmitButton/> </div>
<ol> <h2>Section Results</h2>
<div className="search-results">
{sectionResults}
</div>
<h2>Top Results</h2>
<div className="search-results">
{resultsList} {resultsList}
</ol>
</div> </div>
</div> </div>
); );

View File

@ -1,17 +1,23 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
function LangDropdown({ data }){ function LangDropdown({ changeParameter, data }){
const [ languages, setLanguages ] = useState([]); const [ languages, setLanguages ] = useState([]);
let options = null; let options = null;
const handleChange = (e) => {
changeParameter('lang.code', e.target.value);
}
useEffect( // run whenever data changes useEffect( // run whenever data changes
() => { () => {
if(data){
let langArray = []; let langArray = [];
data.children[0].children.forEach( (document) => { data.children[0].children.forEach( (document) => {
langArray.push(document.language); langArray.push(document.language);
}); });
langArray.sort((a, b) => a.name > b.name) langArray.sort((a, b) => a.name > b.name)
setLanguages(langArray); setLanguages(langArray);
}
}, },
[data] [data]
) )
@ -27,9 +33,8 @@ function LangDropdown({ data }){
}); });
// console.log(options); // console.log(options);
return( return(
<select onChange={handleChange} name="languages" id="languages">
<select name="languages" id="languages"> <option key="allLangs" value="">All Languages</option>
<option key="allLangs" value="allLangs">All Languages</option>
{options} {options}
</select> </select>
) )

View File

@ -2,7 +2,7 @@ import React from 'react';
function SearchBar(props){ function SearchBar(props){
const handleChange = (e) => { const handleChange = (e) => {
props.setSearch(e.target.value); props.changeParameter('title', e.target.value);
} }
return( return(

View File

@ -0,0 +1,14 @@
import React, { useState, useEffect } from 'react';
function SearchResult({ data }){
return(
<div>
<button>
<h3>{data.title} by {data.author}</h3>
<p>({data.lang.code})</p>
</button>
</div>
)
}
export default SearchResult;