{"version":3,"sources":["QueryBooks.js","serviceWorker.js","index.js"],"names":["BookListing","props","className","href","canonicalVolumeLink","width","height","src","thumbnail","alt","title","author","description","QueryBooks","state","results","searchForm","document","getElementById","searchInput","addEventListener","event","preventDefault","this","runQuery","value","bind","query","apiRequest","fetch","then","response","json","JSONResponse","error","errorList","errors","errorString","i","length","JSON","stringify","message","reason","Error","parseJSONResponse","catch","alert","items","forEach","item","push","key","id","volumeInfo","authors","join","imageLinks","setState","React","Component","Boolean","window","location","hostname","match","navBar","querySelector","heroBox","func","limit","lastFunc","lastRan","context","args","arguments","clearTimeout","setTimeout","Date","now","apply","throttle","getBoundingClientRect","top","bottom","classList","add","remove","ReactDOM","render","StrictMode","navigator","serviceWorker","ready","registration","unregister","console"],"mappings":"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","file":"static/js/main.0cb28486.chunk.js","sourcesContent":["import React from 'react';\n\n\nfunction BookListing(props) {\n\t//TODO: Make the whole card clickable but also selectable (for copy/paste)\n\t//TODO: Truncate long card-text gracefully\n\treturn (\n\t\t
  • \n\t\t\t \n\t\t\t\t
    \n\t\t\t\t\t
    \n\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
    \n\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t

    {props.title}

    \n\t\t\t\t\t\t\t\t

    by {props.author}

    \n\t\t\t\t\t\t\t\t

    {props.description}

    \n\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t
    \n\t\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t
    \n\t\t
  • \n\t);\n}\n\nclass QueryBooks extends React.Component {\n constructor(props) {\n\t\tsuper(props);\n\t\t\n this.state = {\n\t\t\tresults: []\n\t\t}\n\t}\n\t\n\tcomponentDidMount() {\n\t\tlet searchForm = document.getElementById(\"search-form\");\n\t\tlet searchInput = document.getElementById('search-input');\n\t\tif(searchForm && searchInput){\n\t\t\tsearchForm.addEventListener(\"submit\", function(event) {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.runQuery(searchInput.value);\n \t}.bind(this), false);\n\t\t}\n\t}\n\t\n\trunQuery(query) {\n\t\tlet apiRequest = `https://www.googleapis.com/books/v1/volumes?maxResults=12&q=${query}`;\n\n\t\tfetch(apiRequest)\n\t\t\t.then(response => response.json())\n\t\t\t.then(JSONResponse => {\n\t\t\t\tif (JSONResponse.error) {\n\t\t\t\t\tlet errorList = JSONResponse.error.errors;\n\t\t\t\t\tlet errorString = \"Google Books volume search returned ERRORS:\";\n\t\t\t\t\tfor(let i=0; i< errorList.length; i++)\n\t\t\t\t\t\terrorString += `\\n\\t${JSON.stringify(errorList[i].message)}, ${JSON.stringify(errorList[i].reason)}`;\n\t\t\t\t\tthrow(new Error(errorString));\n\t\t\t\t}\n\t\t\t\treturn JSONResponse;\n\t\t\t})\n\t\t\t.then(JSONResponse => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.parseJSONResponse(JSONResponse);\n\t\t\t\t}\n\t\t\t\tcatch(error) {\n\t\t\t\t\tthrow(error);\n\t\t\t\t}\n\n\t\t\t\treturn JSONResponse;\n\t\t\t})\n\t\t\t.catch(error => {\n\t\t\t\tif (error.message === 'Failed to fetch')\n\t\t\t\t\talert(`Error fetching URL from ${apiRequest}`);\n\t\t\t\telse\n\t\t\t\t\talert(error);\n\t\t\t});\n\t}\n\n\tparseJSONResponse(json) {\n\t\tlet results = [];\n\n\t\ttry {\n\t\t\tjson.items.forEach(item => {\t\t\n\t\t\t\tresults.push();\n\t\t\t});\n\t\t\t\n\t\t\tthis.setState({results: results});\n\t\t}\n\t\tcatch(error) {\n\t\t\tthrow error;\n\t\t}\n\t} \n\n render() {\n return this.state.results;\n }\n}\n\n\nexport default QueryBooks;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.0/8 are considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl, {\n headers: { 'Service-Worker': 'script' },\n })\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready\n .then(registration => {\n registration.unregister();\n })\n .catch(error => {\n console.error(error.message);\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport \"bootstrap\";\nimport './index.scss';\nimport QueryBooks from './QueryBooks';\nimport * as serviceWorker from './serviceWorker';\n\n\n// change navBar to opaque when heroBox is scrolled up above its bottom edge\nlet navBar = document.querySelector(\"nav\");\nlet heroBox = document.querySelector(\"#hero-box\");\nif (navBar && heroBox) {\n\tdocument.addEventListener(\n\t\t\"scroll\",\n\t\tthrottle(() => {\n\t\t\tif (\n\t\t\t\theroBox.getBoundingClientRect().top < navBar.getBoundingClientRect().bottom\n\t\t\t) {\n\t\t\t\tnavBar.classList.add(\"nav-opaque\");\n\t\t\t} else {\n\t\t\t\tnavBar.classList.remove(\"nav-opaque\");\n\t\t\t}\n\t\t}, 300)\n\t);\n}\n\nfunction throttle(func, limit) {\n\tlet lastFunc;\n\tlet lastRan;\n\treturn function () {\n\t\tconst context = this;\n\t\tconst args = arguments;\n\t\tif (!lastRan) {\n\t\t\tfunc.apply(context, args);\n\t\t\tlastRan = Date.now();\n\t\t} else {\n\t\t\tclearTimeout(lastFunc);\n\t\t\tlastFunc = setTimeout(function () {\n\t\t\t\tif (Date.now() - lastRan >= limit) {\n\t\t\t\t\tfunc.apply(context, args);\n\t\t\t\t\tlastRan = Date.now();\n\t\t\t\t}\n\t\t\t}, limit - (Date.now() - lastRan));\n\t\t}\n\t};\n}\n\n\nReactDOM.render(\n \n \n ,\n document.getElementById('results-root')\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();"],"sourceRoot":""}