filter by language
parent
c3cbc18814
commit
0524b18250
57
src/App.css
57
src/App.css
|
@ -1,43 +1,32 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
|
||||
body{
|
||||
box-sizing: border-box;
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
#root {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
#frontPage {
|
||||
color: blue;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
.frontPage {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
height: 100;
|
||||
color: whitesmoke;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.search-results{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-around;
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
}
|
||||
|
|
142
src/App.js
142
src/App.js
|
@ -1,9 +1,12 @@
|
|||
import React, { useState, useEffect, Component } from 'react';
|
||||
import LangDropdown from './components/LangDropdown';
|
||||
import SearchBar from './components/SearchBar';
|
||||
import SearchResult from './components/SearchResult';
|
||||
import axios from 'axios';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
const fpb = require('./fpb.json');
|
||||
|
||||
function makeBook(author, hLang, cLang, 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
|
||||
{
|
||||
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
|
||||
{if (Array.isArray(cLang.entries)) //verify is entries is an array
|
||||
{
|
||||
|
@ -97,24 +100,68 @@ function sortByScore(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
|
||||
setSearchTerm(term);
|
||||
function jsonToArray(json){
|
||||
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
|
||||
useEffect(
|
||||
() => {
|
||||
async function fetchData() {
|
||||
try{
|
||||
setLoading(true);
|
||||
let result = await axios.get('https://raw.githubusercontent.com/FreeEbookFoundationBot/free-programming-books-json/main/fpb.json');
|
||||
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);
|
||||
}
|
||||
fetchData();
|
||||
|
@ -126,62 +173,67 @@ function App() {
|
|||
// THIS IS THE MAIN SEARCH FUNCTION CURRENTLY
|
||||
useEffect(
|
||||
() => {
|
||||
if(data){
|
||||
let result = [];
|
||||
data.children[0].children.forEach( (document) => {
|
||||
document.sections.forEach( (section) => {
|
||||
if(dataArray){
|
||||
const fuseOptions = {
|
||||
useExtendedSearch: true,
|
||||
findAllMatches: true,
|
||||
shouldSort: false,
|
||||
shouldSort: true,
|
||||
includeScore: true,
|
||||
threshold: 0.3,
|
||||
keys: ['title']
|
||||
};
|
||||
let fuse = new Fuse(section.entries, fuseOptions);
|
||||
let fuseResult = fuse.search(searchTerm);
|
||||
result = result.concat(fuseResult);
|
||||
section.subsections.forEach( (subsection) => {
|
||||
let fuse = new Fuse(subsection.entries, fuseOptions);
|
||||
let fuseResult = fuse.search(searchTerm);
|
||||
result = result.concat(fuseResult);
|
||||
threshold: 0.2,
|
||||
keys: ['title', 'lang.code']
|
||||
}
|
||||
|
||||
let fuse = new Fuse(dataArray, fuseOptions);
|
||||
let query = [];
|
||||
for (const [key, value] of Object.entries(searchParams)) {
|
||||
if(value == null || value == '') continue;
|
||||
if(key == 'lang.code'){
|
||||
query.push({'lang.code': `^${value}`});
|
||||
continue
|
||||
}
|
||||
query.push({[key]: value});
|
||||
}
|
||||
let result = fuse.search({
|
||||
$and: query
|
||||
});
|
||||
});
|
||||
});
|
||||
result = sortByScore(result);
|
||||
setSearchResults(result);
|
||||
setSearchResults(result.slice(0, 40));
|
||||
}
|
||||
},
|
||||
[ searchTerm ]
|
||||
[ searchParams ]
|
||||
)
|
||||
|
||||
const buildList = () => {
|
||||
|
||||
};
|
||||
|
||||
if(loading){ // if still fetching resource
|
||||
return(
|
||||
<h1>Loading...</h1>
|
||||
);
|
||||
}
|
||||
if(searchTerm && searchResults.length !== 0){
|
||||
if(error){
|
||||
return(
|
||||
<h1>Error: {error}</h1>
|
||||
)
|
||||
}
|
||||
if(searchParams.title && searchResults.length !== 0){
|
||||
resultsList =
|
||||
searchResults &&
|
||||
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(
|
||||
<div>
|
||||
<div id="frontPage">
|
||||
<div className="frontPage">
|
||||
<h1>Free Programming Books</h1>
|
||||
{/* <input type="text"></input> */}
|
||||
<SearchBar setSearch={setSearch}/>
|
||||
<LangDropdown data={data}/>
|
||||
<SubmitButton/>
|
||||
<ol>
|
||||
<div>
|
||||
<SearchBar changeParameter={changeParameter}/>
|
||||
<LangDropdown changeParameter={changeParameter} data={data}/>
|
||||
</div>
|
||||
<h2>Section Results</h2>
|
||||
<div className="search-results">
|
||||
{sectionResults}
|
||||
</div>
|
||||
<h2>Top Results</h2>
|
||||
<div className="search-results">
|
||||
{resultsList}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
function LangDropdown({ data }){
|
||||
function LangDropdown({ changeParameter, data }){
|
||||
const [ languages, setLanguages ] = useState([]);
|
||||
let options = null;
|
||||
|
||||
const handleChange = (e) => {
|
||||
changeParameter('lang.code', e.target.value);
|
||||
}
|
||||
|
||||
useEffect( // run whenever data changes
|
||||
() => {
|
||||
if(data){
|
||||
let langArray = [];
|
||||
data.children[0].children.forEach( (document) => {
|
||||
langArray.push(document.language);
|
||||
});
|
||||
langArray.sort((a, b) => a.name > b.name)
|
||||
setLanguages(langArray);
|
||||
}
|
||||
},
|
||||
[data]
|
||||
)
|
||||
|
@ -27,9 +33,8 @@ function LangDropdown({ data }){
|
|||
});
|
||||
// console.log(options);
|
||||
return(
|
||||
|
||||
<select name="languages" id="languages">
|
||||
<option key="allLangs" value="allLangs">All Languages</option>
|
||||
<select onChange={handleChange} name="languages" id="languages">
|
||||
<option key="allLangs" value="">All Languages</option>
|
||||
{options}
|
||||
</select>
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
|
||||
function SearchBar(props){
|
||||
const handleChange = (e) => {
|
||||
props.setSearch(e.target.value);
|
||||
props.changeParameter('title', e.target.value);
|
||||
}
|
||||
|
||||
return(
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue