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 {
|
#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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
142
src/App.js
142
src/App.js
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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