diff --git a/webUI/react/package.json b/webUI/react/package.json index b316510..9810cc9 100644 --- a/webUI/react/package.json +++ b/webUI/react/package.json @@ -8,6 +8,7 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "axios": "^0.21.1", "fontsource-roboto": "^4.0.0", "react": "^17.0.1", "react-beautiful-dnd": "^13.0.0", diff --git a/webUI/react/src/screens/Extensions.tsx b/webUI/react/src/screens/Extensions.tsx index 74cb7c2..7584450 100644 --- a/webUI/react/src/screens/Extensions.tsx +++ b/webUI/react/src/screens/Extensions.tsx @@ -18,7 +18,7 @@ export default function Extensions() { }, []); if (extensions.length === 0) { - return

wait

; + return

loading...

; } return <>{extensions.map((it) => )}; } diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/Library.tsx index 6bd298e..c632dfc 100644 --- a/webUI/react/src/screens/Library.tsx +++ b/webUI/react/src/screens/Library.tsx @@ -6,10 +6,12 @@ import { Tab, Tabs } from '@material-ui/core'; import React, { useContext, useEffect, useState } from 'react'; import MangaGrid from '../components/MangaGrid'; import NavBarTitle from '../context/NavbarTitle'; +import client from '../util/client'; interface IMangaCategory { category: ICategory mangas: IManga[] + isFetched: boolean } interface TabPanelProps { @@ -46,67 +48,63 @@ export default function Library() { setTitle('Library'); }, []); - // eslint-disable-next-line @typescript-eslint/no-shadow - const fetchAndSetMangas = (tabs: IMangaCategory[], tab: IMangaCategory, index: number) => { - fetch(`http://127.0.0.1:4567/api/v1/category/${tab.category.id}`) - .then((response) => response.json()) - .then((data: IManga[]) => { - const tabsClone = JSON.parse(JSON.stringify(tabs)); - tabsClone[index].mangas = data; - setTabs(tabsClone); // clone the object - }); - }; - const handleTabChange = (newTab: number) => { setTabNum(newTab); - tabs.forEach((tab, index) => { - if (tab.category.order === newTab && tab.mangas.length === 0) { - // mangas are empty, fetch the mangas - fetchAndSetMangas(tabs, tab, index); - } - }); }; useEffect(() => { - fetch('http://127.0.0.1:4567/api/v1/library') - .then((response) => response.json()) - .then((data: IManga[]) => { - // if some manga with no category exist, they will be added under a virtual category - if (data.length > 0) { - return [ - { - category: { - name: 'Default', isLanding: true, order: 0, id: -1, - }, - mangas: data, - }, - ]; // will set state on the next fetch - } - - // no default category so the first tab is 1 - setTabNum(1); - return []; - }) + Promise.all([ + client.get('/api/v1/library').then((response) => response.data), + client.get('/api/v1/category').then((response) => response.data), + ]) .then( - (newTabs: IMangaCategory[]) => { - fetch('http://127.0.0.1:4567/api/v1/category') - .then((response) => response.json()) - .then((data: ICategory[]) => { - const mangaCategories = data.map((category) => ({ - category, - mangas: [] as IManga[], - })); - const newNewTabs = [...newTabs, ...mangaCategories]; - setTabs(newNewTabs); + ([libraryMangas, categories]) => { + const categoryTabs = categories.map((category) => ({ + category, + mangas: [] as IManga[], + isFetched: false, + })); - // if no default category, we must fetch the first tab now... - // eslint-disable-next-line max-len - if (newTabs.length === 0) { fetchAndSetMangas(newNewTabs, newNewTabs[0], 0); } - }); + if (libraryMangas.length > 0 || categoryTabs.length === 0) { + const defaultCategoryTab = { + category: { + name: 'Default', + isLanding: true, + order: 0, + id: -1, + }, + mangas: libraryMangas, + isFetched: true, + }; + setTabs( + [defaultCategoryTab, ...categoryTabs], + ); + } else { + setTabs(categoryTabs); + setTabNum(1); + } }, ); }, []); + // fetch the current tab + useEffect(() => { + tabs.forEach((tab, index) => { + if (tab.category.order === tabNum && !tab.isFetched) { + // eslint-disable-next-line @typescript-eslint/no-shadow + fetch(`http://127.0.0.1:4567/api/v1/category/${tab.category.id}`) + .then((response) => response.json()) + .then((data: IManga[]) => { + const tabsClone = JSON.parse(JSON.stringify(tabs)); + tabsClone[index].mangas = data; + tabsClone[index].isFetched = true; + + setTabs(tabsClone); // clone the object + }); + } + }); + }, [tabNum]); + let toRender; if (tabs.length > 1) { // eslint-disable-next-line max-len @@ -119,6 +117,7 @@ export default function Library() { hasNextPage={false} lastPageNum={lastPageNum} setLastPageNum={setLastPageNum} + message={tab.isFetched ? 'Category is Empty' : 'Loading...'} /> )); @@ -149,6 +148,7 @@ export default function Library() { hasNextPage={false} lastPageNum={lastPageNum} setLastPageNum={setLastPageNum} + message={tabs.length > 0 ? 'Library is Empty' : undefined} /> ); } diff --git a/webUI/react/src/screens/Sources.tsx b/webUI/react/src/screens/Sources.tsx index 0a73e82..c7b2658 100644 --- a/webUI/react/src/screens/Sources.tsx +++ b/webUI/react/src/screens/Sources.tsx @@ -10,15 +10,17 @@ export default function Sources() { const { setTitle } = useContext(NavBarTitle); setTitle('Sources'); const [sources, setSources] = useState([]); + const [fetched, setFetched] = useState(false); useEffect(() => { fetch('http://127.0.0.1:4567/api/v1/source/list') .then((response) => response.json()) - .then((data) => setSources(data)); + .then((data) => { setSources(data); setFetched(true); }); }, []); if (sources.length === 0) { - return (

wait

); + if (fetched) return (

No sources found. Install Some Extensions first.

); + return (

loading...

); } return <>{sources.map((it) => )}; } diff --git a/webUI/react/src/util/client.tsx b/webUI/react/src/util/client.tsx new file mode 100644 index 0000000..ce4884c --- /dev/null +++ b/webUI/react/src/util/client.tsx @@ -0,0 +1,10 @@ +import axios from 'axios'; +import storage from './storage'; + +const clientMaker = () => axios.create({ + baseURL: storage.getItem('baseURL', 'http://127.0.0.1:4567'), +}); + +const client = clientMaker(); + +export default client; diff --git a/webUI/react/src/util/storage.tsx b/webUI/react/src/util/storage.tsx new file mode 100644 index 0000000..2a6bf7c --- /dev/null +++ b/webUI/react/src/util/storage.tsx @@ -0,0 +1,22 @@ +function getItem(key: string, defaultValue: T) : T { + try { + const item = window.localStorage.getItem(key); + + if (item !== null) { return JSON.parse(item); } + + window.localStorage.setItem(key, JSON.stringify(defaultValue)); + } finally { + // eslint-disable-next-line no-unsafe-finally + return defaultValue; + } +} + +function setItem(key: string, value: T): void { + try { + window.localStorage.setItem(key, JSON.stringify(value)); + + // eslint-disable-next-line no-empty + } catch (error) { } +} + +export default { getItem, setItem }; diff --git a/webUI/react/yarn.lock b/webUI/react/yarn.lock index 5ed1461..844fc22 100644 --- a/webUI/react/yarn.lock +++ b/webUI/react/yarn.lock @@ -2625,6 +2625,13 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf" integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -5233,6 +5240,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + fontsource-roboto@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fontsource-roboto/-/fontsource-roboto-4.0.0.tgz#35eacd4fb8d90199053c0eec9b34a57fb79cd820"