migrate to axios, front-end part of configurable ServerAddress

This commit is contained in:
Aria Moradi
2021-03-07 22:25:29 +03:30
parent a59f974537
commit c1786f8e24
15 changed files with 182 additions and 114 deletions

View File

@@ -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());
}; };

View File

@@ -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');
}); });
} }

View File

@@ -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');
}); });
} }

View File

@@ -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));
}, []); }, []);

View File

@@ -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 = (
<> <>

View File

@@ -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));
}, []); }, []);

View File

@@ -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 (

View File

@@ -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([

View File

@@ -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>
</>
); );
} }

View File

@@ -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,

View File

@@ -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); });
}, []); }, []);

View File

@@ -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>

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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];