Feature/streamline backend requests (#297)
* Introduce "RequestManager"
* Use "RequestManager" - Simple replacements
- Get rid of all "util/client" imports
- replace old requests with new "RequestManager"
- remove "fetcher" from global SWR config
instead of using the SWR hooks by themselves, the "requestManager" is supposed to be used
* Use "RequestManager" - Do requests via SWR hooks
Use SWR hooks at places where it's easily usable
* Prevent trailing slashes in the "baseUrl"
This commit is contained in:
@@ -16,7 +16,6 @@ import NavbarContext from 'components/context/NavbarContext';
|
||||
import EmptyView from 'components/util/EmptyView';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import client from 'util/client';
|
||||
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Box } from '@mui/system';
|
||||
@@ -28,6 +27,7 @@ import { BACK } from 'util/useBackTo';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IChapter, IQueue } from 'typings';
|
||||
import makeToast from 'components/util/Toast';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const initialQueue = {
|
||||
status: 'Stopped',
|
||||
@@ -37,16 +37,16 @@ const initialQueue = {
|
||||
const DownloadQueue: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data: queueState } = useSubscription<IQueue>('/api/v1/downloads');
|
||||
const { data: queueState } = useSubscription<IQueue>('downloads');
|
||||
const { queue, status } = queueState ?? initialQueue;
|
||||
|
||||
const { setTitle, setAction } = useContext(NavbarContext);
|
||||
|
||||
const toggleQueueStatus = () => {
|
||||
if (status === 'Stopped') {
|
||||
client.get('/api/v1/downloads/start');
|
||||
requestManager.startDownloads();
|
||||
} else {
|
||||
client.get('/api/v1/downloads/stop');
|
||||
requestManager.stopDownloads();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,15 +67,15 @@ const DownloadQueue: React.FC = () => {
|
||||
try {
|
||||
if (isRunning) {
|
||||
// required to stop before deleting otherwise the download kept going. Server issue?
|
||||
await client.get('/api/v1/downloads/stop');
|
||||
await requestManager.stopDownloads();
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
// remove from download queue
|
||||
client.delete(`/api/v1/download/${chapter.mangaId}/chapter/${chapter.index}`),
|
||||
requestManager.removeChapterFromDownloadQueue(chapter.mangaId, chapter.index),
|
||||
// delete partial download, should be handle server side?
|
||||
// bug: The folder and the last image downloaded are not deleted
|
||||
client.delete(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`),
|
||||
requestManager.deleteDownloadedChapter(chapter.mangaId, chapter.index),
|
||||
]);
|
||||
} catch (error) {
|
||||
makeToast(t('download.queue.error.label.failed_to_remove'), 'error');
|
||||
@@ -85,7 +85,7 @@ const DownloadQueue: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
client.get('/api/v1/downloads/start').catch(() => {});
|
||||
requestManager.startDownloads().catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,7 +12,6 @@ import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import ExtensionCard from 'components/ExtensionCard';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import client, { useQuery } from 'util/client';
|
||||
import useLocalStorage from 'util/useLocalStorage';
|
||||
import LangSelect from 'components/navbar/action/LangSelect';
|
||||
import { extensionDefaultLangs, DefaultLanguage, langSortCmp } from 'util/language';
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
isExtensionStateOrLanguage,
|
||||
translateExtensionLanguage,
|
||||
} from 'screens/util/Extensions';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const LANGUAGE = 0;
|
||||
const EXTENSIONS = 1;
|
||||
@@ -102,7 +102,7 @@ export default function MangaExtensions() {
|
||||
);
|
||||
}, [t, shownLangs]);
|
||||
|
||||
const { data: allExtensions, mutate, isLoading } = useQuery<IExtension[]>('/api/v1/extension/list');
|
||||
const { data: allExtensions, mutate, isLoading } = requestManager.useGetExtensionList();
|
||||
|
||||
const filteredExtensions = useMemo(
|
||||
() =>
|
||||
@@ -128,18 +128,13 @@ export default function MangaExtensions() {
|
||||
|
||||
const submitExternalExtension = (file: File) => {
|
||||
if (file.name.toLowerCase().endsWith('apk')) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = '';
|
||||
}
|
||||
|
||||
makeToast(t('extension.label.installing_file'), 'info');
|
||||
client
|
||||
.post('/api/v1/extension/install', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
requestManager
|
||||
.installExtension(file)
|
||||
.then(() => {
|
||||
makeToast(t('extension.label.installed_successfully'), 'success');
|
||||
mutate();
|
||||
|
||||
@@ -16,12 +16,11 @@ import LibraryToolbarMenu from 'components/library/LibraryToolbarMenu';
|
||||
import LibraryMangaGrid from 'components/library/LibraryMangaGrid';
|
||||
import AppbarSearch from 'components/util/AppbarSearch';
|
||||
import { useQueryParam, NumberParam } from 'use-query-params';
|
||||
import { useQuery } from 'util/client';
|
||||
import UpdateChecker from 'components/library/UpdateChecker';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ICategory, IManga } from 'typings';
|
||||
import { styled } from '@mui/system';
|
||||
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const TitleWithSizeTag = styled('span')({
|
||||
display: 'flex',
|
||||
@@ -37,7 +36,7 @@ export default function Library() {
|
||||
|
||||
const { options } = useLibraryOptionsContext();
|
||||
const [lastLibraryUpdate, setLastLibraryUpdate] = useState(Date.now());
|
||||
const { data: tabsData, error: tabsError, isLoading } = useQuery<ICategory[]>('/api/v1/category');
|
||||
const { data: tabsData, error: tabsError, isLoading } = requestManager.useGetCategories();
|
||||
const tabs = tabsData ?? [];
|
||||
const librarySize = useMemo(() => tabs.map((tab) => tab.size).reduce((prev, curr) => prev + curr, 0), [tabs]);
|
||||
|
||||
@@ -48,7 +47,7 @@ export default function Library() {
|
||||
data: mangaData,
|
||||
error: mangaError,
|
||||
isLoading: mangaLoading,
|
||||
} = useQuery<IManga[]>(activeTab ? `/api/v1/category/${activeTab?.id}` : null);
|
||||
} = requestManager.useGetCategoryMangas(activeTab?.id, { skipRequest: !activeTab });
|
||||
const mangas = mangaData ?? [];
|
||||
|
||||
const { setTitle, setAction } = useContext(NavbarContext);
|
||||
|
||||
@@ -20,8 +20,7 @@ import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useQuery } from 'util/client';
|
||||
import { IManga } from 'typings';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const AUTOFETCH_AGE = 60 * 60 * 24; // 24 hours
|
||||
|
||||
@@ -32,13 +31,7 @@ const Manga: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const autofetchedRef = useRef(false);
|
||||
|
||||
const {
|
||||
data: manga,
|
||||
error,
|
||||
isLoading,
|
||||
isValidating,
|
||||
mutate,
|
||||
} = useQuery<IManga>(`/api/v1/manga/${id}/?onlineFetch=false`);
|
||||
const { data: manga, error, isLoading, isValidating, mutate } = requestManager.useGetManga(id);
|
||||
|
||||
const [refresh, { loading: refreshing }] = useRefreshManga(id);
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ import DoublePagedPager from 'components/reader/pager/DoublePagedPager';
|
||||
import VerticalPager from 'components/reader/pager/VerticalPager';
|
||||
import ReaderNavBar from 'components/navbar/ReaderNavBar';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import client from 'util/client';
|
||||
import useLocalStorage from 'util/useLocalStorage';
|
||||
import { Box } from '@mui/system';
|
||||
import { requestUpdateMangaMetadata } from 'util/metadata';
|
||||
import {
|
||||
@@ -26,21 +24,16 @@ import {
|
||||
useDefaultReaderSettings,
|
||||
} from 'util/readerSettings';
|
||||
import makeToast from 'components/util/Toast';
|
||||
import {
|
||||
ChapterOffset,
|
||||
IChapter,
|
||||
IManga,
|
||||
IMangaCard,
|
||||
IPartialChapter,
|
||||
IReaderSettings,
|
||||
ReaderType,
|
||||
TranslationKey,
|
||||
} from 'typings';
|
||||
import { ChapterOffset, IChapter, IManga, IMangaCard, IReaderSettings, ReaderType, TranslationKey } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const isDupChapter = async (chapterIndex: number, currentChapter: IChapter) => {
|
||||
const nextChapter = (await client.get<IChapter>(`/api/v1/manga/${currentChapter.mangaId}/chapter/${chapterIndex}`))
|
||||
.data;
|
||||
const nextChapter = (
|
||||
await requestManager
|
||||
.getClient()
|
||||
.get<IChapter>(`/api/v1/manga/${currentChapter.mangaId}/chapter/${chapterIndex}`)
|
||||
).data;
|
||||
|
||||
return nextChapter.chapterNumber === currentChapter.chapterNumber;
|
||||
};
|
||||
@@ -102,18 +95,22 @@ export default function Reader() {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||
|
||||
const { chapterIndex, mangaId } = useParams<{ chapterIndex: string; mangaId: string }>();
|
||||
const [manga, setManga] = useState<IMangaCard | IManga>({
|
||||
id: +mangaId,
|
||||
title: '',
|
||||
thumbnailUrl: '',
|
||||
genre: [],
|
||||
inLibraryAt: 0,
|
||||
lastReadAt: 0,
|
||||
});
|
||||
const [chapter, setChapter] = useState<IChapter | IPartialChapter>(initialChapter());
|
||||
const {
|
||||
data: manga = {
|
||||
id: +mangaId,
|
||||
title: '',
|
||||
thumbnailUrl: '',
|
||||
genre: [],
|
||||
inLibraryAt: 0,
|
||||
lastReadAt: 0,
|
||||
} as IMangaCard | IManga,
|
||||
isLoading: isMangaLoading,
|
||||
} = requestManager.useGetManga(mangaId);
|
||||
const { data: chapter = initialChapter(), isLoading: isChapterLoading } = requestManager.useGetChapter(
|
||||
mangaId,
|
||||
chapterIndex,
|
||||
);
|
||||
const [curPage, setCurPage] = useState<number>(0);
|
||||
const [pageToScrollTo, setPageToScrollTo] = useState<number | undefined>(undefined);
|
||||
const { setOverride, setTitle } = useContext(NavbarContext);
|
||||
@@ -121,7 +118,6 @@ export default function Reader() {
|
||||
|
||||
const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings();
|
||||
const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings));
|
||||
const [isMangaLoading, setIsMangaLoading] = useState(true);
|
||||
|
||||
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
|
||||
setSettings({ ...settings, [key]: value });
|
||||
@@ -156,6 +152,17 @@ export default function Reader() {
|
||||
[chapter, settings],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isChapterLoading || !chapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chapter.lastPageRead === chapter.pageCount - 1) {
|
||||
// last page, also probably read = true, we will load the first page.
|
||||
setCurPage(0);
|
||||
} else setCurPage(chapter.lastPageRead);
|
||||
}, [chapter, isChapterLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!manga?.title || (chapter as IChapter)?.name === t('global.label.loading')) {
|
||||
setTitle(t('reader.title'));
|
||||
@@ -193,52 +200,22 @@ export default function Reader() {
|
||||
return () => setOverride({ status: false, value: <div /> });
|
||||
}, [manga, chapter, settings, curPage, chapterIndex, retrievingNextChapter]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMangaLoading(true);
|
||||
client
|
||||
.get(`/api/v1/manga/${mangaId}/`)
|
||||
.then((response) => response.data)
|
||||
.then((data: IManga) => {
|
||||
setManga(data);
|
||||
setIsMangaLoading(false);
|
||||
});
|
||||
}, [mangaId]);
|
||||
|
||||
useEffect(() => {
|
||||
setChapter(initialChapter);
|
||||
client
|
||||
.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`)
|
||||
.then((response) => response.data)
|
||||
.then((data: IChapter) => {
|
||||
setChapter(data);
|
||||
|
||||
if (data.lastPageRead === data.pageCount - 1) {
|
||||
// last page, also probably read = true, we will load the first page.
|
||||
setCurPage(0);
|
||||
} else setCurPage(data.lastPageRead);
|
||||
});
|
||||
}, [chapterIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (curPage !== -1) {
|
||||
const formData = new FormData();
|
||||
formData.append('lastPageRead', curPage.toString());
|
||||
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData);
|
||||
requestManager.updateChapter(manga.id, chapter.index, { lastPageRead: curPage });
|
||||
}
|
||||
|
||||
if (curPage === chapter.pageCount - 1) {
|
||||
const formDataRead = new FormData();
|
||||
formDataRead.append('read', 'true');
|
||||
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formDataRead);
|
||||
requestManager.updateChapter(manga.id, chapter.index, { read: true });
|
||||
}
|
||||
}, [curPage]);
|
||||
|
||||
const nextChapter = useCallback(() => {
|
||||
if (chapter.index < chapter.chapterCount) {
|
||||
const formData = new FormData();
|
||||
formData.append('lastPageRead', `${chapter.pageCount - 1}`);
|
||||
formData.append('read', 'true');
|
||||
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData);
|
||||
requestManager.updateChapter(manga.id, chapter.index, {
|
||||
lastPageRead: chapter.pageCount - 1,
|
||||
read: true,
|
||||
});
|
||||
|
||||
openNextChapter(ChapterOffset.NEXT, (nextChapterIndex) =>
|
||||
history.replace({
|
||||
@@ -278,7 +255,7 @@ export default function Reader() {
|
||||
|
||||
const pages = range(chapter.pageCount).map((index) => ({
|
||||
index,
|
||||
src: `${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterIndex}/page/${index}`,
|
||||
src: requestManager.getChapterPageUrl(mangaId, chapterIndex, index),
|
||||
}));
|
||||
|
||||
const ReaderComponent = getReaderComponent(settings.readerType);
|
||||
|
||||
@@ -12,15 +12,15 @@ import MangaGrid from 'components/MangaGrid';
|
||||
import LangSelect from 'components/navbar/action/LangSelect';
|
||||
import AppbarSearch from 'components/util/AppbarSearch';
|
||||
import PQueue from 'p-queue';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { StringParam, useQueryParam } from 'use-query-params';
|
||||
import client from 'util/client';
|
||||
import { langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from 'util/language';
|
||||
import useLocalStorage from 'util/useLocalStorage';
|
||||
import { ISource } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { translateExtensionLanguage } from 'screens/util/Extensions';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
function sourceToLangList(sources: ISource[]) {
|
||||
const result: string[] = [];
|
||||
@@ -46,9 +46,22 @@ const SearchAll: React.FC = () => {
|
||||
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
|
||||
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||
|
||||
const [sources, setSources] = useState<ISource[]>([]);
|
||||
const { data: unsortedSources = [], isLoading: FetchedSources } = requestManager.useGetSourceList();
|
||||
const sources = useMemo(
|
||||
() =>
|
||||
unsortedSources.sort((a: { displayName: string }, b: { displayName: string }) => {
|
||||
if (a.displayName < b.displayName) {
|
||||
return -1;
|
||||
}
|
||||
if (a.displayName > b.displayName) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}),
|
||||
[unsortedSources],
|
||||
);
|
||||
|
||||
const [fetched, setFetched] = useState<any>({});
|
||||
const [FetchedSources, setFetchedSources] = useState<any>({});
|
||||
|
||||
const [lastPageNum, setLastPageNum] = useState<number>(1);
|
||||
|
||||
@@ -61,32 +74,12 @@ const SearchAll: React.FC = () => {
|
||||
setAction(<AppbarSearch />);
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
client
|
||||
.get('/api/v1/source/list')
|
||||
.then((response) => response.data)
|
||||
.then((data) => {
|
||||
setSources(
|
||||
data.sort((a: { displayName: string }, b: { displayName: string }) => {
|
||||
if (a.displayName < b.displayName) {
|
||||
return -1;
|
||||
}
|
||||
if (a.displayName > b.displayName) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}),
|
||||
);
|
||||
setFetchedSources(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
async function doIT(elem: any[]) {
|
||||
elem.map((ele) =>
|
||||
limit.add(async () => {
|
||||
const response = await client.get(
|
||||
`/api/v1/source/${ele.id}/search?searchTerm=${query || ''}&pageNum=1`,
|
||||
);
|
||||
const response = await requestManager
|
||||
.getClient()
|
||||
.get(`/api/v1/source/${ele.id}/search?searchTerm=${query || ''}&pageNum=1`);
|
||||
const data = await response.data;
|
||||
const tmp = mangas;
|
||||
tmp[ele.id] = data.mangaList;
|
||||
|
||||
@@ -41,6 +41,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import LanguageIcon from '@mui/icons-material/Language';
|
||||
import { langCodeToName } from 'util/language';
|
||||
import CollectionsOutlinedBookmarkIcon from '@mui/icons-material/CollectionsBookmarkOutlined';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
export default function Settings() {
|
||||
const { t, i18n } = useTranslation();
|
||||
@@ -52,7 +53,7 @@ export default function Settings() {
|
||||
}, [t]);
|
||||
|
||||
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
|
||||
const [serverAddress, setServerAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||
const [serverAddress, setServerAddress] = useLocalStorage<string>('serverBaseURL', '');
|
||||
const [showNsfw, setShowNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||
const [useCache, setUseCache] = useLocalStorage<boolean>('useCache', true);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
@@ -73,7 +74,9 @@ export default function Settings() {
|
||||
|
||||
const handleDialogSubmit = () => {
|
||||
setDialogOpen(false);
|
||||
setServerAddress(dialogValue);
|
||||
const serverBaseUrl = dialogValue.replaceAll(/(\/)+$/g, '');
|
||||
setServerAddress(serverBaseUrl);
|
||||
requestManager.updateClient({ baseURL: serverBaseUrl });
|
||||
};
|
||||
|
||||
const handleDialogOpenItemWidth = () => {
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import client, { useQuery } from 'util/client';
|
||||
import { SwitchPreferenceCompat, CheckBoxPreference } from 'components/sourceConfiguration/TwoStatePreference';
|
||||
import ListPreference from 'components/sourceConfiguration/ListPreference';
|
||||
import EditTextPreference from 'components/sourceConfiguration/EditTextPreference';
|
||||
import MultiSelectListPreference from 'components/sourceConfiguration/MultiSelectListPreference';
|
||||
import List from '@mui/material/List';
|
||||
import cloneObject from 'util/cloneObject';
|
||||
import { SourcePreferences } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
function getPrefComponent(type: string) {
|
||||
switch (type) {
|
||||
@@ -46,9 +45,7 @@ export default function SourceConfigure() {
|
||||
}, [t]);
|
||||
|
||||
const { sourceId } = useParams<{ sourceId: string }>();
|
||||
const { data: sourcePreferences = [], mutate } = useQuery<SourcePreferences[]>(
|
||||
`/api/v1/source/${sourceId}/preferences`,
|
||||
);
|
||||
const { data: sourcePreferences = [], mutate } = requestManager.useGetSourcePreferences(sourceId);
|
||||
|
||||
const convertToString = (position: number, value: any): string => {
|
||||
switch (sourcePreferences[position].props.defaultValueType) {
|
||||
@@ -60,12 +57,7 @@ export default function SourceConfigure() {
|
||||
};
|
||||
|
||||
const updateValue = (position: number) => (value: any) => {
|
||||
client
|
||||
.post(
|
||||
`/api/v1/source/${sourceId}/preferences`,
|
||||
JSON.stringify({ position, value: convertToString(position, value) }),
|
||||
)
|
||||
.then(() => mutate());
|
||||
requestManager.setSourcePreferences(sourceId, position, convertToString(position, value)).then(() => mutate());
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,16 +11,16 @@ import { useParams, useHistory } from 'react-router-dom';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import SourceMangaGrid from 'components/source/SourceMangaGrid';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import client from 'util/client';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import SourceOptions from 'components/source/SourceOptions';
|
||||
import AppbarSearch from 'components/util/AppbarSearch';
|
||||
import { useQueryParam, StringParam } from 'use-query-params';
|
||||
import SourceGridLayout from 'components/source/GridLayouts';
|
||||
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
|
||||
import { IManga, IMangaCard, ISource, ISourceFilters } from 'typings';
|
||||
import { IManga, IMangaCard, ISourceFilters } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Link from '@mui/material/Link';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
interface IPos {
|
||||
position: number;
|
||||
@@ -34,6 +34,9 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
|
||||
const history = useHistory();
|
||||
|
||||
const { sourceId } = useParams<{ sourceId: string }>();
|
||||
|
||||
const { data: source } = requestManager.useGetSource(sourceId);
|
||||
|
||||
const [isConfigurable, setIsConfigurable] = useState<boolean>(false);
|
||||
const [mangas, setMangas] = useState<IMangaCard[]>([]);
|
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false);
|
||||
@@ -53,7 +56,8 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
|
||||
const { options } = useLibraryOptionsContext();
|
||||
|
||||
function makeFilters() {
|
||||
client
|
||||
requestManager
|
||||
.getClient()
|
||||
.get(`/api/v1/source/${sourceId}/filters`)
|
||||
.then((response) => response.data)
|
||||
.then((data: ISourceFilters[]) => {
|
||||
@@ -66,14 +70,13 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
client
|
||||
.get(`/api/v1/source/${sourceId}`)
|
||||
.then((response) => response.data)
|
||||
.then((data: ISource) => {
|
||||
setTitle(data.displayName);
|
||||
setIsConfigurable(data.isConfigurable);
|
||||
});
|
||||
}, []);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTitle(source.displayName);
|
||||
setIsConfigurable(source.isConfigurable);
|
||||
}, [source]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerUpdate === 2) {
|
||||
@@ -86,9 +89,9 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
|
||||
if (update.length > 0) {
|
||||
const rep = update;
|
||||
setUpdate([]);
|
||||
client
|
||||
.post(
|
||||
`/api/v1/source/${sourceId}/filters`,
|
||||
requestManager
|
||||
.setSourceFilters(
|
||||
sourceId,
|
||||
rep.map((e: IPos) => {
|
||||
const { position, state, group }: IPos = e;
|
||||
return group === undefined
|
||||
@@ -125,7 +128,7 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
|
||||
setNoreset(undefined);
|
||||
setReset(1);
|
||||
} else if (Noreset === undefined) {
|
||||
client.get(`/api/v1/source/${sourceId}/filters?reset=true`).then(() => {
|
||||
requestManager.resetSourceFilters(sourceId).then(() => {
|
||||
makeFilters();
|
||||
setSearch(false);
|
||||
if (reset === 1) {
|
||||
@@ -188,7 +191,8 @@ export default function SourceMangas({ popular }: { popular: boolean }) {
|
||||
useEffect(() => {
|
||||
if (lastPageNum !== 0) {
|
||||
const sourceType = popular ? 'popular' : 'latest';
|
||||
client
|
||||
requestManager
|
||||
.getClient()
|
||||
.get(
|
||||
`/api/v1/source/${sourceId}/${
|
||||
query !== undefined || Search || Noreset === null ? 'search' : sourceType
|
||||
|
||||
@@ -16,10 +16,10 @@ import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||
import { IconButton } from '@mui/material';
|
||||
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useQuery } from 'util/client';
|
||||
import { ISource } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { translateExtensionLanguage } from 'screens/util/Extensions';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
function sourceToLangList(sources: ISource[]) {
|
||||
const result: string[] = [];
|
||||
@@ -53,7 +53,7 @@ export default function Sources() {
|
||||
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
|
||||
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||
|
||||
const { data: sources, isLoading } = useQuery<ISource[]>('/api/v1/source/list');
|
||||
const { data: sources, isLoading } = requestManager.useGetSourceList();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ import EmptyView from 'components/util/EmptyView';
|
||||
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import client from 'util/client';
|
||||
import useLocalStorage from 'util/useLocalStorage';
|
||||
import { IChapter, IMangaChapter, IQueue, PaginatedList } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { t as translate } from 'i18next';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
function epochToDate(epoch: number) {
|
||||
const date = new Date(0); // The 0 there is the key, which sets the date to the epoch
|
||||
@@ -66,7 +65,6 @@ function groupByDate(updates: IMangaChapter[]): [string, { item: IMangaChapter;
|
||||
return Object.keys(groups).map((key) => [key, groups[key]]);
|
||||
}
|
||||
|
||||
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
|
||||
const initialQueue = {
|
||||
status: 'Stopped',
|
||||
queue: [],
|
||||
@@ -82,14 +80,11 @@ const Updates: React.FC = () => {
|
||||
const [fetched, setFetched] = useState(false);
|
||||
const [lastPageNum, setLastPageNum] = useState(0);
|
||||
|
||||
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||
const [useCache] = useLocalStorage<boolean>('useCache', true);
|
||||
|
||||
const [, setWsClient] = useState<WebSocket>();
|
||||
const [{ queue }, setQueueState] = useState<IQueue>(initialQueue);
|
||||
|
||||
useEffect(() => {
|
||||
const wsc = new WebSocket(`${baseWebsocketUrl}/api/v1/downloads`);
|
||||
const wsc = requestManager.getDownloadWebSocket();
|
||||
wsc.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data) as IQueue;
|
||||
setQueueState(data);
|
||||
@@ -108,7 +103,8 @@ const Updates: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (hasNextPage) {
|
||||
client
|
||||
requestManager
|
||||
.getClient()
|
||||
.get(`/api/v1/update/recentChapters/${lastPageNum}`)
|
||||
.then((response) => response.data)
|
||||
.then(({ hasNextPage: fetchedHasNextPage, page }: PaginatedList<IMangaChapter>) => {
|
||||
@@ -149,7 +145,7 @@ const Updates: React.FC = () => {
|
||||
};
|
||||
|
||||
const downloadChapter = (chapter: IChapter) => {
|
||||
client.get(`/api/v1/download/${chapter.mangaId}/chapter/${chapter.index}`);
|
||||
requestManager.addChapterToDownloadQueue(chapter.mangaId, chapter.index);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -200,7 +196,7 @@ const Updates: React.FC = () => {
|
||||
marginRight: 2,
|
||||
imageRendering: 'pixelated',
|
||||
}}
|
||||
src={`${serverAddress}${manga.thumbnailUrl}?useCache=${useCache}`}
|
||||
src={requestManager.getValidImgUrlFor(manga.thumbnailUrl)}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant="h5" component="h2">
|
||||
|
||||
@@ -13,9 +13,8 @@ import ListItemText from '@mui/material/ListItemText';
|
||||
import ListItemLink from 'components/util/ListItemLink';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||
import { useQuery } from 'util/client';
|
||||
import { IAbout } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
export default function About() {
|
||||
const { t } = useTranslation();
|
||||
@@ -26,7 +25,7 @@ export default function About() {
|
||||
setAction(null);
|
||||
}, [t]);
|
||||
|
||||
const { data: about } = useQuery<IAbout>('/api/v1/settings/about');
|
||||
const { data: about } = requestManager.useGetAbout();
|
||||
|
||||
if (about === undefined) {
|
||||
return <LoadingPlaceholder />;
|
||||
|
||||
@@ -11,11 +11,11 @@ import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { fromEvent } from 'file-selector';
|
||||
import client from 'util/client';
|
||||
import makeToast from 'components/util/Toast';
|
||||
import ListItemLink from 'components/util/ListItemLink';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
export default function Backup() {
|
||||
const { t } = useTranslation();
|
||||
@@ -25,18 +25,11 @@ export default function Backup() {
|
||||
setAction(null);
|
||||
}, [t]);
|
||||
|
||||
const { baseURL } = client.defaults;
|
||||
|
||||
const submitBackup = (file: File) => {
|
||||
if (file.name.toLowerCase().endsWith('proto.gz')) {
|
||||
const formData = new FormData();
|
||||
formData.append('backup.proto.gz', file);
|
||||
|
||||
makeToast(t('settings.backup.label.restoring_backup'), 'info');
|
||||
client
|
||||
.post('/api/v1/backup/import/file', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
requestManager
|
||||
.restoreBackupFile(file)
|
||||
.then(() => makeToast(t('settings.backup.label.restored_backup'), 'success'))
|
||||
.catch(() => makeToast(t('settings.backup.label.backup_restore_failed'), 'error'));
|
||||
} else if (file.name.toLowerCase().endsWith('json')) {
|
||||
@@ -76,7 +69,7 @@ export default function Backup() {
|
||||
return (
|
||||
<>
|
||||
<List sx={{ padding: 0 }}>
|
||||
<ListItemLink to={`${baseURL}/api/v1/backup/export/file`} directLink>
|
||||
<ListItemLink to={requestManager.getExportBackupUrl()} directLink>
|
||||
<ListItemText
|
||||
primary={t('settings.backup.label.create_backup')}
|
||||
secondary={t('settings.backup.label.create_backup_info')}
|
||||
|
||||
@@ -31,10 +31,10 @@ import DialogTitle from '@mui/material/DialogTitle';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import client, { useQuery } from 'util/client';
|
||||
import { ICategory } from 'typings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DEFAULT_FULL_FAB_HEIGHT } from 'components/util/StyledFab';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const getItemStyle = (
|
||||
isDragging: boolean,
|
||||
@@ -58,7 +58,7 @@ export default function Categories() {
|
||||
setAction(null);
|
||||
}, [t]);
|
||||
|
||||
const { data, mutate } = useQuery<ICategory[]>('/api/v1/category/');
|
||||
const { data, mutate } = requestManager.useGetCategories();
|
||||
const categories = useMemo(() => {
|
||||
const res = [...(data ?? [])];
|
||||
if (res.length > 0 && res[0].name === 'Default') {
|
||||
@@ -79,10 +79,7 @@ export default function Categories() {
|
||||
newData.splice(to, 0, removed);
|
||||
mutate(newData, { revalidate: false });
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('from', `${from + 1}`);
|
||||
formData.append('to', `${to + 1}`);
|
||||
client.patch('/api/v1/category/reorder', formData).finally(() => mutate());
|
||||
requestManager.reorderCategory(from + 1, to + 1).finally(() => mutate());
|
||||
};
|
||||
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
@@ -119,21 +116,19 @@ export default function Categories() {
|
||||
const handleDialogSubmit = () => {
|
||||
setDialogOpen(false);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', dialogName);
|
||||
formData.append('default', dialogDefault.toString());
|
||||
|
||||
if (categoryToEdit === -1) {
|
||||
client.post('/api/v1/category/', formData).finally(() => mutate());
|
||||
requestManager.createCategory(dialogName).finally(() => mutate());
|
||||
} else {
|
||||
const category = categories[categoryToEdit];
|
||||
client.patch(`/api/v1/category/${category.id}`, formData).finally(() => mutate());
|
||||
requestManager
|
||||
.updateCategory(category.id, { name: dialogName, default: dialogDefault })
|
||||
.finally(() => mutate());
|
||||
}
|
||||
};
|
||||
|
||||
const deleteCategory = (index: number) => {
|
||||
const category = categories[index];
|
||||
client.delete(`/api/v1/category/${category.id}`).finally(() => mutate());
|
||||
requestManager.deleteCategory(category.id).finally(() => mutate());
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,6 @@ import List from '@mui/material/List';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import NavbarContext from 'components/context/NavbarContext';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import client, { useQuery } from 'util/client';
|
||||
import { ICategory, IncludeInGlobalUpdate } from 'typings';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
@@ -25,6 +24,7 @@ import { styled } from '@mui/system';
|
||||
import makeToast from 'components/util/Toast';
|
||||
import { t as translate } from 'i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import requestManager from 'lib/RequestManager';
|
||||
|
||||
const CategoriesDiv = styled('div')({
|
||||
display: 'flex',
|
||||
@@ -77,7 +77,7 @@ export default function LibrarySettings() {
|
||||
setAction(null);
|
||||
}, [t]);
|
||||
|
||||
const { data: categories, isLoading, error: requestError, mutate } = useQuery<ICategory[]>('/api/v1/category/');
|
||||
const { data: categories, isLoading, error: requestError, mutate } = requestManager.useGetCategories();
|
||||
|
||||
const [currentCategories, setCurrentCategories] = useState<ICategory[]>(categories ?? []); // categories to check if response categories changed
|
||||
const [dialogCategories, setDialogCategories] = useState<ICategory[]>(categories ?? []); // categories that are shown and updated in the dialog
|
||||
@@ -110,12 +110,8 @@ export default function LibrarySettings() {
|
||||
requestError,
|
||||
);
|
||||
|
||||
const updateCategory = (category: ICategory) => {
|
||||
const formData = new FormData();
|
||||
formData.append('includeInUpdate', `${category.includeInUpdate}`);
|
||||
|
||||
return client.patch(`/api/v1/category/${category.id}`, formData);
|
||||
};
|
||||
const updateCategory = (category: ICategory) =>
|
||||
requestManager.updateCategory(category.id, { includeInUpdate: category.includeInUpdate });
|
||||
|
||||
const updateCategories = async () => {
|
||||
const categoriesToUpdate = dialogCategories.filter((category) => {
|
||||
|
||||
Reference in New Issue
Block a user