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"