mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 11:24:35 -05:00
migrate to axios, front-end part of configurable ServerAddress
This commit is contained in:
@@ -12,6 +12,7 @@ import Dialog from '@material-ui/core/Dialog';
|
|||||||
import Checkbox from '@material-ui/core/Checkbox';
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
import FormGroup from '@material-ui/core/FormGroup';
|
import FormGroup from '@material-ui/core/FormGroup';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => createStyles({
|
const useStyles = makeStyles(() => createStyles({
|
||||||
paper: {
|
paper: {
|
||||||
@@ -41,14 +42,14 @@ export default function CategorySelect(props: IProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let tmpCategoryInfos: ICategoryInfo[] = [];
|
let tmpCategoryInfos: ICategoryInfo[] = [];
|
||||||
fetch('http://127.0.0.1:4567/api/v1/category/')
|
client.get('/api/v1/category/')
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: ICategory[]) => {
|
.then((data: ICategory[]) => {
|
||||||
tmpCategoryInfos = data.map((category) => ({ category, selected: false }));
|
tmpCategoryInfos = data.map((category) => ({ category, selected: false }));
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${mangaId}/category/`)
|
client.get(`/api/v1/manga/${mangaId}/category/`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: ICategory[]) => {
|
.then((data: ICategory[]) => {
|
||||||
data.forEach((category) => {
|
data.forEach((category) => {
|
||||||
tmpCategoryInfos[category.order - 1].selected = true;
|
tmpCategoryInfos[category.order - 1].selected = true;
|
||||||
@@ -69,9 +70,9 @@ export default function CategorySelect(props: IProps) {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>, categoryId: number) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>, categoryId: number) => {
|
||||||
const { checked } = event.target as HTMLInputElement;
|
const { checked } = event.target as HTMLInputElement;
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${mangaId}/category/${categoryId}`, {
|
|
||||||
method: checked ? 'GET' : 'DELETE', mode: 'cors',
|
const method = checked ? client.get : client.delete;
|
||||||
})
|
method(`/api/v1/manga/${mangaId}/category/${categoryId}`)
|
||||||
.then(() => triggerUpdate());
|
.then(() => triggerUpdate());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import CardContent from '@material-ui/core/CardContent';
|
|||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -53,14 +54,16 @@ export default function ExtensionCard(props: IProps) {
|
|||||||
|
|
||||||
function install() {
|
function install() {
|
||||||
setInstalledState('installing');
|
setInstalledState('installing');
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/extension/install/${apkName}`).then(() => {
|
client.get(`/api/v1/extension/install/${apkName}`)
|
||||||
|
.then(() => {
|
||||||
setInstalledState('uninstall');
|
setInstalledState('uninstall');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function uninstall() {
|
function uninstall() {
|
||||||
setInstalledState('uninstalling');
|
setInstalledState('uninstalling');
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/extension/uninstall/${apkName}`).then(() => {
|
client.get(`/api/v1/extension/uninstall/${apkName}`)
|
||||||
|
.then(() => {
|
||||||
setInstalledState('install');
|
setInstalledState('install');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { Button, createStyles, makeStyles } from '@material-ui/core';
|
import { Button, createStyles, makeStyles } from '@material-ui/core';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import client from '../util/client';
|
||||||
import CategorySelect from './CategorySelect';
|
import CategorySelect from './CategorySelect';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => createStyles({
|
const useStyles = makeStyles(() => createStyles({
|
||||||
@@ -30,14 +31,14 @@ export default function MangaDetails(props: IProps) {
|
|||||||
|
|
||||||
function addToLibrary() {
|
function addToLibrary() {
|
||||||
setInLibrary('adding');
|
setInLibrary('adding');
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${manga.id}/library/`).then(() => {
|
client.get(`/api/v1/manga/${manga.id}/library/`).then(() => {
|
||||||
setInLibrary('In Library');
|
setInLibrary('In Library');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFromLibrary() {
|
function removeFromLibrary() {
|
||||||
setInLibrary('removing');
|
setInLibrary('removing');
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${manga.id}/library/`, { method: 'DELETE', mode: 'cors' }).then(() => {
|
client.delete(`/api/v1/manga/${manga.id}/library/`).then(() => {
|
||||||
setInLibrary('Not In Library');
|
setInLibrary('Not In Library');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import ExtensionCard from '../components/ExtensionCard';
|
import ExtensionCard from '../components/ExtensionCard';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
export default function Extensions() {
|
export default function Extensions() {
|
||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
@@ -12,8 +13,8 @@ export default function Extensions() {
|
|||||||
const [extensions, setExtensions] = useState<IExtension[]>([]);
|
const [extensions, setExtensions] = useState<IExtension[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://127.0.0.1:4567/api/v1/extension/list')
|
client.get('/api/v1/extension/list')
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data) => setExtensions(data));
|
.then((data) => setExtensions(data));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -87,13 +87,14 @@ export default function Library() {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// console.log(client.defaults.baseURL);
|
||||||
// fetch the current tab
|
// fetch the current tab
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
tabs.forEach((tab, index) => {
|
tabs.forEach((tab, index) => {
|
||||||
if (tab.category.order === tabNum && !tab.isFetched) {
|
if (tab.category.order === tabNum && !tab.isFetched) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/category/${tab.category.id}`)
|
client.get(`/api/v1/category/${tab.category.id}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: IManga[]) => {
|
.then((data: IManga[]) => {
|
||||||
const tabsClone = JSON.parse(JSON.stringify(tabs));
|
const tabsClone = JSON.parse(JSON.stringify(tabs));
|
||||||
tabsClone[index].mangas = data;
|
tabsClone[index].mangas = data;
|
||||||
@@ -122,7 +123,7 @@ export default function Library() {
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
));
|
));
|
||||||
|
|
||||||
// 160px is min-width for viewport width of >600
|
// Visual Hack: 160px is min-width for viewport width of >600
|
||||||
const scrollableTabs = window.innerWidth < tabs.length * 160;
|
const scrollableTabs = window.innerWidth < tabs.length * 160;
|
||||||
toRender = (
|
toRender = (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useParams } from 'react-router-dom';
|
|||||||
import ChapterCard from '../components/ChapterCard';
|
import ChapterCard from '../components/ChapterCard';
|
||||||
import MangaDetails from '../components/MangaDetails';
|
import MangaDetails from '../components/MangaDetails';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
export default function Manga() {
|
export default function Manga() {
|
||||||
const { id } = useParams<{id: string}>();
|
const { id } = useParams<{id: string}>();
|
||||||
@@ -16,8 +17,8 @@ export default function Manga() {
|
|||||||
const [chapters, setChapters] = useState<IChapter[]>([]);
|
const [chapters, setChapters] = useState<IChapter[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${id}/`)
|
client.get(`/api/v1/manga/${id}/`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: IManga) => {
|
.then((data: IManga) => {
|
||||||
setManga(data);
|
setManga(data);
|
||||||
setTitle(data.title);
|
setTitle(data.title);
|
||||||
@@ -25,8 +26,8 @@ export default function Manga() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${id}/chapters`)
|
client.get(`/api/v1/manga/${id}/chapters`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data) => setChapters(data));
|
.then((data) => setChapters(data));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import client from '../util/client';
|
||||||
|
import useLocalStorage from '../util/useLocalStorage';
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -17,14 +19,15 @@ const style = {
|
|||||||
const range = (n:number) => Array.from({ length: n }, (value, key) => key);
|
const range = (n:number) => Array.from({ length: n }, (value, key) => key);
|
||||||
|
|
||||||
export default function Reader() {
|
export default function Reader() {
|
||||||
|
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
|
||||||
const [pageCount, setPageCount] = useState<number>(-1);
|
const [pageCount, setPageCount] = useState<number>(-1);
|
||||||
const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>();
|
const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${mangaId}/chapter/${chapterId}`)
|
client.get(`/api/v1/manga/${mangaId}/chapter/${chapterId}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data:IChapter) => {
|
.then((data:IChapter) => {
|
||||||
setTitle(data.name);
|
setTitle(data.name);
|
||||||
setPageCount(data.pageCount);
|
setPageCount(data.pageCount);
|
||||||
@@ -41,7 +44,7 @@ export default function Reader() {
|
|||||||
|
|
||||||
const mapped = range(pageCount).map((index) => (
|
const mapped = range(pageCount).map((index) => (
|
||||||
<div style={{ margin: '0 auto' }}>
|
<div style={{ margin: '0 auto' }}>
|
||||||
<img src={`http://127.0.0.1:4567/api/v1/manga/${mangaId}/chapter/${chapterId}/page/${index}`} alt="f" style={{ maxWidth: '100%' }} />
|
<img src={`${serverAddress}/api/v1/manga/${mangaId}/chapter/${chapterId}/page/${index}`} alt="F" style={{ maxWidth: '100%' }} />
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Button from '@material-ui/core/Button';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from '../components/MangaGrid';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -33,8 +34,8 @@ export default function SearchSingle() {
|
|||||||
const textInput = React.createRef<HTMLInputElement>();
|
const textInput = React.createRef<HTMLInputElement>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}`)
|
client.get(`/api/v1/source/${sourceId}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: { name: string }) => setTitle(`Search: ${data.name}`));
|
.then((data: { name: string }) => setTitle(`Search: ${data.name}`));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -54,8 +55,8 @@ export default function SearchSingle() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchTerm.length > 0) {
|
if (searchTerm.length > 0) {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}/search/${searchTerm}/${lastPageNum}`)
|
client.get(`/api/v1/source/${sourceId}/search/${searchTerm}/${lastPageNum}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
|
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
|
||||||
if (data.mangaList.length > 0) {
|
if (data.mangaList.length > 0) {
|
||||||
setMangas([
|
setMangas([
|
||||||
|
|||||||
@@ -2,16 +2,21 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
|
|
||||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
|
||||||
import ListItemText from '@material-ui/core/ListItemText';
|
|
||||||
import InboxIcon from '@material-ui/icons/Inbox';
|
import InboxIcon from '@material-ui/icons/Inbox';
|
||||||
import Brightness6Icon from '@material-ui/icons/Brightness6';
|
import Brightness6Icon from '@material-ui/icons/Brightness6';
|
||||||
import { ListItemSecondaryAction, Switch } from '@material-ui/core';
|
import DnsIcon from '@material-ui/icons/Dns';
|
||||||
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
|
import {
|
||||||
|
Button, Dialog, DialogActions, DialogContent,
|
||||||
|
DialogContentText, IconButton, ListItemSecondaryAction, Switch, TextField,
|
||||||
|
ListItemIcon, ListItemText,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
import DarkTheme from '../context/DarkTheme';
|
import DarkTheme from '../context/DarkTheme';
|
||||||
|
import useLocalStorage from '../util/useLocalStorage';
|
||||||
|
|
||||||
function ListItemLink(props: ListItemProps<'a', { button?: true }>) {
|
function ListItemLink(props: ListItemProps<'a', { button?: true }>) {
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
@@ -22,9 +27,26 @@ export default function Settings() {
|
|||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
setTitle('Settings');
|
setTitle('Settings');
|
||||||
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
|
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
|
||||||
|
const [serverAddress, setServerAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const [dialogValue, setDialogValue] = useState(serverAddress);
|
||||||
|
|
||||||
|
const handleDialogOpen = () => {
|
||||||
|
setDialogValue(serverAddress);
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogCancel = () => {
|
||||||
|
setDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogSubmit = () => {
|
||||||
|
setDialogOpen(false);
|
||||||
|
setServerAddress(dialogValue);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<List component="nav" style={{ padding: 0 }}>
|
<List component="nav" style={{ padding: 0 }}>
|
||||||
<ListItemLink href="/settings/categories">
|
<ListItemLink href="/settings/categories">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
@@ -45,7 +67,49 @@ export default function Settings() {
|
|||||||
/>
|
/>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<DnsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Server Address" secondary={serverAddress} />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handleDialogOpen();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</div>
|
|
||||||
|
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Enter new category name.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Category Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
value={dialogValue}
|
||||||
|
onChange={(e) => setDialogValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleDialogCancel} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleDialogSubmit} color="primary">
|
||||||
|
Set
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from '../components/MangaGrid';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
export default function SourceMangas(props: { popular: boolean }) {
|
export default function SourceMangas(props: { popular: boolean }) {
|
||||||
const { sourceId } = useParams<{sourceId: string}>();
|
const { sourceId } = useParams<{sourceId: string}>();
|
||||||
@@ -15,15 +16,15 @@ export default function SourceMangas(props: { popular: boolean }) {
|
|||||||
const [lastPageNum, setLastPageNum] = useState<number>(1);
|
const [lastPageNum, setLastPageNum] = useState<number>(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}`)
|
client.get(`/api/v1/source/${sourceId}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: { name: string }) => setTitle(data.name));
|
.then((data: { name: string }) => setTitle(data.name));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sourceType = props.popular ? 'popular' : 'latest';
|
const sourceType = props.popular ? 'popular' : 'latest';
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}/${sourceType}/${lastPageNum}`)
|
client.get(`/api/v1/source/${sourceId}/${sourceType}/${lastPageNum}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
|
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
|
||||||
setMangas([
|
setMangas([
|
||||||
...mangas,
|
...mangas,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import SourceCard from '../components/SourceCard';
|
import SourceCard from '../components/SourceCard';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import client from '../util/client';
|
||||||
|
|
||||||
export default function Sources() {
|
export default function Sources() {
|
||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
@@ -13,8 +14,8 @@ export default function Sources() {
|
|||||||
const [fetched, setFetched] = useState<boolean>(false);
|
const [fetched, setFetched] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://127.0.0.1:4567/api/v1/source/list')
|
client.get('/api/v1/source/list')
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data) => { setSources(data); setFetched(true); });
|
.then((data) => { setSources(data); setFetched(true); });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import DialogContent from '@material-ui/core/DialogContent';
|
|||||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import NavBarTitle from '../../context/NavbarTitle';
|
import NavBarTitle from '../../context/NavbarTitle';
|
||||||
|
import client from '../../util/client';
|
||||||
|
|
||||||
const getItemStyle = (isDragging, draggableStyle, palette) => ({
|
const getItemStyle = (isDragging, draggableStyle, palette) => ({
|
||||||
// styles we need to apply on draggables
|
// styles we need to apply on draggables
|
||||||
@@ -43,7 +44,7 @@ export default function Categories() {
|
|||||||
setTitle('Categories');
|
setTitle('Categories');
|
||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category
|
const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category
|
||||||
const [dialogOpen, setDialogOpen] = React.useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [dialogValue, setDialogValue] = useState('');
|
const [dialogValue, setDialogValue] = useState('');
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -52,8 +53,8 @@ export default function Categories() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dialogOpen) {
|
if (!dialogOpen) {
|
||||||
fetch('http://127.0.0.1:4567/api/v1/category/')
|
client.get('/api/v1/category/')
|
||||||
.then((response) => response.json())
|
.then((response) => response.data)
|
||||||
.then((data) => setCategories(data));
|
.then((data) => setCategories(data));
|
||||||
}
|
}
|
||||||
}, [updateTriggerHolder]);
|
}, [updateTriggerHolder]);
|
||||||
@@ -64,11 +65,8 @@ export default function Categories() {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('from', from + 1);
|
formData.append('from', from + 1);
|
||||||
formData.append('to', to + 1);
|
formData.append('to', to + 1);
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/category/${category.id}/reorder`, {
|
client.post(`/api/v1/category/${category.id}/reorder`, formData)
|
||||||
method: 'PATCH',
|
.finally(() => triggerUpdate());
|
||||||
mode: 'cors',
|
|
||||||
body: formData,
|
|
||||||
}).finally(() => triggerUpdate());
|
|
||||||
|
|
||||||
// also move it in local state to avoid jarring moving behviour...
|
// also move it in local state to avoid jarring moving behviour...
|
||||||
const result = Array.from(list);
|
const result = Array.from(list);
|
||||||
@@ -90,48 +88,40 @@ export default function Categories() {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDialogOpen = () => {
|
|
||||||
setDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetDialog = () => {
|
const resetDialog = () => {
|
||||||
setDialogOpen(false);
|
|
||||||
setDialogValue('');
|
setDialogValue('');
|
||||||
setCategoryToEdit(-1);
|
setCategoryToEdit(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDialogCancel = () => {
|
const handleDialogOpen = () => {
|
||||||
resetDialog();
|
resetDialog();
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogCancel = () => {
|
||||||
|
setDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDialogSubmit = () => {
|
const handleDialogSubmit = () => {
|
||||||
resetDialog();
|
setDialogOpen(false);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('name', dialogValue);
|
formData.append('name', dialogValue);
|
||||||
|
|
||||||
if (categoryToEdit === -1) {
|
if (categoryToEdit === -1) {
|
||||||
fetch('http://127.0.0.1:4567/api/v1/category/', {
|
client.post('/api/v1/category/', formData)
|
||||||
method: 'POST',
|
.finally(() => triggerUpdate());
|
||||||
mode: 'cors',
|
|
||||||
body: formData,
|
|
||||||
}).finally(() => triggerUpdate());
|
|
||||||
} else {
|
} else {
|
||||||
const category = categories[categoryToEdit];
|
const category = categories[categoryToEdit];
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/category/${category.id}`, {
|
client.patch(`/api/v1/category/${category.id}`, formData)
|
||||||
method: 'PATCH',
|
.finally(() => triggerUpdate());
|
||||||
mode: 'cors',
|
|
||||||
body: formData,
|
|
||||||
}).finally(() => triggerUpdate());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteCategory = (index) => {
|
const deleteCategory = (index) => {
|
||||||
const category = categories[index];
|
const category = categories[index];
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/category/${category.id}`, {
|
client.delete(`/api/v1/category/${category.id}`)
|
||||||
method: 'DELETE',
|
.finally(() => triggerUpdate());
|
||||||
mode: 'cors',
|
|
||||||
}).finally(() => triggerUpdate());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -167,8 +157,8 @@ export default function Categories() {
|
|||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCategoryToEdit(index);
|
|
||||||
handleDialogOpen();
|
handleDialogOpen();
|
||||||
|
setCategoryToEdit(index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
@@ -201,7 +191,7 @@ export default function Categories() {
|
|||||||
>
|
>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</Fab>
|
</Fab>
|
||||||
<Dialog open={dialogOpen} onClose={handleDialogCancel} aria-labelledby="form-dialog-title">
|
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
|
||||||
<DialogTitle id="form-dialog-title">
|
<DialogTitle id="form-dialog-title">
|
||||||
{categoryToEdit === -1 ? 'New Catalog' : `Rename: ${categories[categoryToEdit].name}`}
|
{categoryToEdit === -1 ? 'New Catalog' : `Rename: ${categories[categoryToEdit].name}`}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
import axios from 'axios';
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
import storage from './storage';
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
const clientMaker = () => axios.create({
|
import axios from 'axios';
|
||||||
baseURL: storage.getItem('baseURL', 'http://127.0.0.1:4567'),
|
import storage from './localStorage';
|
||||||
|
|
||||||
|
const { hostname, port, protocol } = window.location;
|
||||||
|
|
||||||
|
// if port is 3000 it's probably running from webpack devlopment server
|
||||||
|
let inferredPort;
|
||||||
|
if (port === '3000') { inferredPort = '4567'; } else { inferredPort = port; }
|
||||||
|
|
||||||
|
const client = axios.create({
|
||||||
|
// baseURL must not have traling slash
|
||||||
|
baseURL: storage.getItem('serverBaseURL', `${protocol}//${hostname}:${inferredPort}`),
|
||||||
});
|
});
|
||||||
|
|
||||||
const client = clientMaker();
|
client.interceptors.request.use((config) => {
|
||||||
|
if (config.data instanceof FormData) {
|
||||||
|
Object.assign(config.headers, { 'Content-Type': 'multipart/form-data' });
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
export default client;
|
export default client;
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
function getItem<T>(key: string, defaultValue: T) : T {
|
function getItem<T>(key: string, defaultValue: T) : T {
|
||||||
try {
|
try {
|
||||||
const item = window.localStorage.getItem(key);
|
const item = window.localStorage.getItem(key);
|
||||||
|
|
||||||
if (item !== null) { return JSON.parse(item); }
|
if (item !== null) {
|
||||||
|
return JSON.parse(item);
|
||||||
|
}
|
||||||
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(defaultValue));
|
window.localStorage.setItem(key, JSON.stringify(defaultValue));
|
||||||
} finally {
|
|
||||||
// eslint-disable-next-line no-unsafe-finally
|
/* eslint-disable no-empty */
|
||||||
|
} finally { }
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function setItem<T>(key: string, value: T): void {
|
function setItem<T>(key: string, value: T): void {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(key, JSON.stringify(value));
|
window.localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
} catch (error) { }
|
} finally { }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { getItem, setItem };
|
export default { getItem, setItem };
|
||||||
@@ -3,39 +3,17 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import { useState, Dispatch, SetStateAction } from 'react';
|
import { useState, Dispatch, SetStateAction } from 'react';
|
||||||
|
import storage from './localStorage';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export default function useLocalStorage<T>(key: string, initialValue: T) : [T, Dispatch<SetStateAction<T>>] {
|
export default function useLocalStorage<T>(key: string, defaultValue: T) : [T, Dispatch<SetStateAction<T>>] {
|
||||||
// State to store our value
|
const [storedValue, setStoredValue] = useState<T>(storage.getItem(key, defaultValue));
|
||||||
// Pass initial state function to useState so logic is only executed once
|
|
||||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
||||||
try {
|
|
||||||
// Get from local storage by key
|
|
||||||
const item = window.localStorage.getItem(key);
|
|
||||||
|
|
||||||
// Parse stored json or if null return set and return initialValue
|
|
||||||
if (item !== null) { return JSON.parse(item); }
|
|
||||||
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(initialValue));
|
|
||||||
} finally {
|
|
||||||
// eslint-disable-next-line no-unsafe-finally
|
|
||||||
return initialValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return a wrapped version of useState's setter function that ...
|
|
||||||
// ... persists the new value to localStorage.
|
|
||||||
const setValue = (value: T | ((prevState: T) => T)) => {
|
const setValue = (value: T | ((prevState: T) => T)) => {
|
||||||
try {
|
|
||||||
// Allow value to be a function so we have same API as useState
|
// Allow value to be a function so we have same API as useState
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||||
// Save state
|
|
||||||
setStoredValue(valueToStore);
|
setStoredValue(valueToStore);
|
||||||
// Save to local storage
|
storage.setItem(key, valueToStore);
|
||||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
} catch (error) { }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return [storedValue, setValue];
|
return [storedValue, setValue];
|
||||||
|
|||||||
Reference in New Issue
Block a user