diff --git a/src/App.css b/src/App.css index 41984d0..dd35395 100644 --- a/src/App.css +++ b/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%; } diff --git a/src/App.js b/src/App.js index 0e59902..ea98029 100644 --- a/src/App.js +++ b/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() { - setLoading(true); - let result = await axios.get('https://raw.githubusercontent.com/FreeEbookFoundationBot/free-programming-books-json/main/fpb.json'); - setData(result.data); + 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) => { - const fuseOptions = { - findAllMatches: true, - shouldSort: false, - 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); - }); - }); + if(dataArray){ + const fuseOptions = { + useExtendedSearch: true, + findAllMatches: true, + shouldSort: true, + includeScore: true, + 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(

Loading...

); } - if(searchTerm && searchResults.length !== 0){ + if(error){ + return( +

Error: {error}

+ ) + } + if(searchParams.title && searchResults.length !== 0){ resultsList = searchResults && searchResults.map((entry) => { - return (
  • {entry.item.title}
  • ) + return + // return (
  • {entry.item.title}
  • ) }); } - console.log(data); return( -
    -
    -

    Free Programming Books

    - {/* */} - - - -
      - {resultsList} -
    +
    +

    Free Programming Books

    +
    + + +
    +

    Section Results

    +
    + {sectionResults} +
    +

    Top Results

    +
    + {resultsList}
    ); diff --git a/src/components/LangDropdown.js b/src/components/LangDropdown.js index df9ae20..15c40a9 100644 --- a/src/components/LangDropdown.js +++ b/src/components/LangDropdown.js @@ -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 () => { - let langArray = []; - data.children[0].children.forEach( (document) => { - langArray.push(document.language); - }); - langArray.sort((a, b) => a.name > b.name) - setLanguages(langArray); + 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] ) @@ -26,10 +32,9 @@ function LangDropdown({ data }){ return createOption(language) }); // console.log(options); - return( - - + {options} ) diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index d8c0dc0..1e79624 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -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( diff --git a/src/components/SearchResult.js b/src/components/SearchResult.js new file mode 100644 index 0000000..79f81de --- /dev/null +++ b/src/components/SearchResult.js @@ -0,0 +1,14 @@ +import React, { useState, useEffect } from 'react'; + +function SearchResult({ data }){ + return( +
    + +
    + ) +} + +export default SearchResult; \ No newline at end of file