From 6f2f228e08b566ae742bce3622999cc2e49b4dac Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Mon, 8 Mar 2021 21:04:42 +0330 Subject: [PATCH] section extension languages --- webUI/react/src/App.tsx | 11 +- webUI/react/src/components/ExtensionCard.tsx | 6 +- .../src/components/ExtensionLangSelect.tsx | 103 ++++++++++++++++++ webUI/react/src/components/NavBar.tsx | 7 +- .../{NavbarTitle.tsx => NavbarContext.tsx} | 8 +- webUI/react/src/screens/Extensions.tsx | 94 ++++++++++++++-- webUI/react/src/screens/Library.tsx | 4 +- webUI/react/src/screens/Manga.tsx | 4 +- webUI/react/src/screens/Reader.tsx | 4 +- webUI/react/src/screens/SearchSingle.tsx | 4 +- webUI/react/src/screens/Settings.tsx | 4 +- webUI/react/src/screens/SourceMangas.tsx | 4 +- webUI/react/src/screens/Sources.tsx | 4 +- .../react/src/screens/settings/Categories.jsx | 4 +- 14 files changed, 228 insertions(+), 33 deletions(-) create mode 100644 webUI/react/src/components/ExtensionLangSelect.tsx rename webUI/react/src/context/{NavbarTitle.tsx => NavbarContext.tsx} (65%) diff --git a/webUI/react/src/App.tsx b/webUI/react/src/App.tsx index ad00ba57..9fe244be 100644 --- a/webUI/react/src/App.tsx +++ b/webUI/react/src/App.tsx @@ -17,7 +17,7 @@ import SourceMangas from './screens/SourceMangas'; import Manga from './screens/Manga'; import Reader from './screens/Reader'; import Search from './screens/SearchSingle'; -import NavBarTitle from './context/NavbarTitle'; +import NavbarContext from './context/NavbarContext'; import DarkTheme from './context/DarkTheme'; import Library from './screens/Library'; import Settings from './screens/Settings'; @@ -26,8 +26,11 @@ import useLocalStorage from './util/useLocalStorage'; export default function App() { const [title, setTitle] = useState('Tachidesk'); + const [action, setAction] = useState(
); const [darkTheme, setDarkTheme] = useLocalStorage('darkTheme', true); - const navTitleContext = { title, setTitle }; + const navBarContext = { + title, setTitle, action, setAction, + }; const darkThemeContext = { darkTheme, setDarkTheme }; const theme = React.useMemo( @@ -57,7 +60,7 @@ export default function App() { return ( - + @@ -103,7 +106,7 @@ export default function App() { /> - + ); diff --git a/webUI/react/src/components/ExtensionCard.tsx b/webUI/react/src/components/ExtensionCard.tsx index f8d52d2e..245075a9 100644 --- a/webUI/react/src/components/ExtensionCard.tsx +++ b/webUI/react/src/components/ExtensionCard.tsx @@ -40,6 +40,7 @@ const useStyles = makeStyles((theme) => ({ interface IProps { extension: IExtension + notifyInstall: () => void } export default function ExtensionCard(props: IProps) { @@ -47,6 +48,7 @@ export default function ExtensionCard(props: IProps) { extension: { name, lang, versionName, installed, apkName, iconUrl, }, + notifyInstall, } = props; const [installedState, setInstalledState] = useState((installed ? 'uninstall' : 'install')); @@ -60,6 +62,7 @@ export default function ExtensionCard(props: IProps) { client.get(`/api/v1/extension/install/${apkName}`) .then(() => { setInstalledState('uninstall'); + notifyInstall(); }); } @@ -67,7 +70,8 @@ export default function ExtensionCard(props: IProps) { setInstalledState('uninstalling'); client.get(`/api/v1/extension/uninstall/${apkName}`) .then(() => { - setInstalledState('install'); + // setInstalledState('install'); + notifyInstall(); }); } diff --git a/webUI/react/src/components/ExtensionLangSelect.tsx b/webUI/react/src/components/ExtensionLangSelect.tsx new file mode 100644 index 00000000..fe1fac1a --- /dev/null +++ b/webUI/react/src/components/ExtensionLangSelect.tsx @@ -0,0 +1,103 @@ +/* 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/. */ + +import React, { useState } from 'react'; +import { makeStyles, createStyles } from '@material-ui/core/styles'; +import Button from '@material-ui/core/Button'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogActions from '@material-ui/core/DialogActions'; +import Dialog from '@material-ui/core/Dialog'; +import Checkbox from '@material-ui/core/Checkbox'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import FormGroup from '@material-ui/core/FormGroup'; +import IconButton from '@material-ui/core/IconButton'; +import FilterListIcon from '@material-ui/icons/FilterList'; + +const useStyles = makeStyles(() => createStyles({ + paper: { + maxHeight: 435, + width: '80%', + }, +})); + +interface IProps { + shownLangs: string[] + setShownLangs: (arg0: string[]) => void + allLangs: string[] +} + +export default function ExtensionLangSelect(props: IProps) { + const { shownLangs, setShownLangs, allLangs } = props; + // hold a copy and only sate state on parent when OK pressed, improves performance + const [mShownLangs, setMShownLangs] = useState(shownLangs); + const classes = useStyles(); + const [open, setOpen] = useState(false); + + const handleCancel = () => { + setOpen(false); + }; + + const handleOk = () => { + setOpen(false); + setShownLangs(mShownLangs); + }; + + const handleChange = (event: React.ChangeEvent, lang: string) => { + const { checked } = event.target as HTMLInputElement; + + if (checked) { + setMShownLangs([...mShownLangs, lang]); + } else { + const clone = JSON.parse(JSON.stringify(mShownLangs)); + clone.splice(clone.indexOf(lang), 1); + setMShownLangs(clone); + } + }; + + return ( + <> + setOpen(true)} + aria-label="display more actions" + edge="end" + color="inherit" + > + + + + Enabled Languages + + + {allLangs.map((lang) => ( + handleChange(e, lang)} + color="default" + /> + )} + label={lang} + /> + ))} + + + + + + + + + + ); +} diff --git a/webUI/react/src/components/NavBar.tsx b/webUI/react/src/components/NavBar.tsx index 006ee4a4..bdbba2c9 100644 --- a/webUI/react/src/components/NavBar.tsx +++ b/webUI/react/src/components/NavBar.tsx @@ -16,7 +16,7 @@ import MenuItem from '@material-ui/core/MenuItem'; import Menu from '@material-ui/core/Menu'; import TemporaryDrawer from './TemporaryDrawer'; -import NavBarTitle from '../context/NavbarTitle'; +import NavBarContext from '../context/NavbarContext'; import DarkTheme from '../context/DarkTheme'; const useStyles = makeStyles((theme) => ({ @@ -44,7 +44,7 @@ export default function NavBar() { const classes = useStyles(); const [drawerOpen, setDrawerOpen] = useState(false); const [anchorEl, setAnchorEl] = React.useState(null); - const { title } = useContext(NavBarTitle); + const { title, action } = useContext(NavBarContext); const open = Boolean(anchorEl); const { darkTheme } = useContext(DarkTheme); @@ -74,13 +74,14 @@ export default function NavBar() { {title} + {action} {/* - + */} {/* > + action: any + setAction: React.Dispatch> }; -const NavBarTitle = React.createContext({ +const NavBarContext = React.createContext({ title: 'Tachidesk', setTitle: ():void => {}, + action:
, + setAction: ():void => {}, }); -export default NavBarTitle; +export default NavBarContext; diff --git a/webUI/react/src/screens/Extensions.tsx b/webUI/react/src/screens/Extensions.tsx index a0fc243b..dacce8e9 100644 --- a/webUI/react/src/screens/Extensions.tsx +++ b/webUI/react/src/screens/Extensions.tsx @@ -4,22 +4,102 @@ import React, { useContext, useEffect, useState } from 'react'; import ExtensionCard from '../components/ExtensionCard'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; +import useLocalStorage from '../util/useLocalStorage'; +import ExtensionLangSelect from '../components/ExtensionLangSelect'; + +const allLangs: string[] = []; + +function groupExtensions(extensions: IExtension[]) { + allLangs.length = 0; // empty the array + const result = { installed: [] } as any; + extensions.sort((a, b) => ((a.apkName > b.apkName) ? 1 : -1)); + + extensions.forEach((extension) => { + if (result[extension.lang] === undefined) { + result[extension.lang] = []; + allLangs.push(extension.lang); + } + if (extension.installed) { + result.installed.push(extension); + } else { + result[extension.lang].push(extension); + } + }); + + return result; +} + +function defualtLangs() { + return [ + 'all', + 'en', + ]; +} export default function Extensions() { - const { setTitle } = useContext(NavBarTitle); - setTitle('Extensions'); - const [extensions, setExtensions] = useState([]); + const { setTitle, setAction } = useContext(NavbarContext); + const [shownLangs, setShownLangs] = useLocalStorage('shownExtensionLangs', defualtLangs()); + + useEffect(() => { + setTitle('Extensions'); + setAction( + , + ); + }, [shownLangs]); + + const [extensionsRaw, setExtensionsRaw] = useState([]); + const [extensions, setExtensions] = useState({}); + + const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack + const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack useEffect(() => { client.get('/api/v1/extension/list') .then((response) => response.data) - .then((data) => setExtensions(data)); - }, []); + .then((data) => setExtensionsRaw(data)); + }, [updateTriggerHolder]); + + useEffect(() => { + if (extensionsRaw.length > 0) { + const groupedExtension = groupExtensions(extensionsRaw); + setExtensions(groupedExtension); + } + }, [extensionsRaw]); if (extensions.length === 0) { return

loading...

; } - return <>{extensions.map((it) => )}; + return ( + <> + { + Object.entries(extensions).map(([lang, list]) => ( + <> + {['installed', ...shownLangs].indexOf(lang) !== -1 + && ( + <> +

{lang}

+ {(list as IExtension[]).map((it) => ( + { + triggerUpdate(); + }} + /> + ))} + + ) } + + )) + } + + ); } diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/Library.tsx index 5253d091..6ac7c223 100644 --- a/webUI/react/src/screens/Library.tsx +++ b/webUI/react/src/screens/Library.tsx @@ -5,7 +5,7 @@ import { Tab, Tabs } from '@material-ui/core'; import React, { useContext, useEffect, useState } from 'react'; import MangaGrid from '../components/MangaGrid'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; interface IMangaCategory { @@ -37,7 +37,7 @@ function TabPanel(props: TabPanelProps) { } export default function Library() { - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); const [tabs, setTabs] = useState([]); const [tabNum, setTabNum] = useState(0); diff --git a/webUI/react/src/screens/Manga.tsx b/webUI/react/src/screens/Manga.tsx index 3c2bc209..96a78c52 100644 --- a/webUI/react/src/screens/Manga.tsx +++ b/webUI/react/src/screens/Manga.tsx @@ -6,12 +6,12 @@ import React, { useEffect, useState, useContext } from 'react'; import { useParams } from 'react-router-dom'; import ChapterCard from '../components/ChapterCard'; import MangaDetails from '../components/MangaDetails'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; export default function Manga() { const { id } = useParams<{id: string}>(); - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); const [manga, setManga] = useState(); const [chapters, setChapters] = useState([]); diff --git a/webUI/react/src/screens/Reader.tsx b/webUI/react/src/screens/Reader.tsx index f31a2c04..9f535510 100644 --- a/webUI/react/src/screens/Reader.tsx +++ b/webUI/react/src/screens/Reader.tsx @@ -4,7 +4,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; import useLocalStorage from '../util/useLocalStorage'; @@ -20,7 +20,7 @@ const range = (n:number) => Array.from({ length: n }, (value, key) => key); export default function Reader() { const [serverAddress] = useLocalStorage('serverBaseURL', ''); - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); const [pageCount, setPageCount] = useState(-1); const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>(); diff --git a/webUI/react/src/screens/SearchSingle.tsx b/webUI/react/src/screens/SearchSingle.tsx index fef7f67f..c5263eb2 100644 --- a/webUI/react/src/screens/SearchSingle.tsx +++ b/webUI/react/src/screens/SearchSingle.tsx @@ -8,7 +8,7 @@ import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button'; import { useParams } from 'react-router-dom'; import MangaGrid from '../components/MangaGrid'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; const useStyles = makeStyles((theme) => ({ @@ -21,7 +21,7 @@ const useStyles = makeStyles((theme) => ({ })); export default function SearchSingle() { - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); const { sourceId } = useParams<{sourceId: string}>(); const classes = useStyles(); const [error, setError] = useState(false); diff --git a/webUI/react/src/screens/Settings.tsx b/webUI/react/src/screens/Settings.tsx index d0fa4f40..40eadac8 100644 --- a/webUI/react/src/screens/Settings.tsx +++ b/webUI/react/src/screens/Settings.tsx @@ -14,7 +14,7 @@ import { ListItemIcon, ListItemText, } from '@material-ui/core'; import ListItem, { ListItemProps } from '@material-ui/core/ListItem'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import DarkTheme from '../context/DarkTheme'; import useLocalStorage from '../util/useLocalStorage'; @@ -24,7 +24,7 @@ function ListItemLink(props: ListItemProps<'a', { button?: true }>) { } export default function Settings() { - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); setTitle('Settings'); const { darkTheme, setDarkTheme } = useContext(DarkTheme); const [serverAddress, setServerAddress] = useLocalStorage('serverBaseURL', ''); diff --git a/webUI/react/src/screens/SourceMangas.tsx b/webUI/react/src/screens/SourceMangas.tsx index c4cc2ff2..8647933b 100644 --- a/webUI/react/src/screens/SourceMangas.tsx +++ b/webUI/react/src/screens/SourceMangas.tsx @@ -5,12 +5,12 @@ import React, { useContext, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import MangaGrid from '../components/MangaGrid'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; export default function SourceMangas(props: { popular: boolean }) { const { sourceId } = useParams<{sourceId: string}>(); - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); const [mangas, setMangas] = useState([]); const [hasNextPage, setHasNextPage] = useState(false); const [lastPageNum, setLastPageNum] = useState(1); diff --git a/webUI/react/src/screens/Sources.tsx b/webUI/react/src/screens/Sources.tsx index 3715049a..69a76cba 100644 --- a/webUI/react/src/screens/Sources.tsx +++ b/webUI/react/src/screens/Sources.tsx @@ -4,11 +4,11 @@ import React, { useContext, useEffect, useState } from 'react'; import SourceCard from '../components/SourceCard'; -import NavBarTitle from '../context/NavbarTitle'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; export default function Sources() { - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); setTitle('Sources'); const [sources, setSources] = useState([]); const [fetched, setFetched] = useState(false); diff --git a/webUI/react/src/screens/settings/Categories.jsx b/webUI/react/src/screens/settings/Categories.jsx index 18e6dfc9..043714fb 100644 --- a/webUI/react/src/screens/settings/Categories.jsx +++ b/webUI/react/src/screens/settings/Categories.jsx @@ -27,7 +27,7 @@ import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; -import NavBarTitle from '../../context/NavbarTitle'; +import NavbarContext from '../../context/NavbarContext'; import client from '../../util/client'; const getItemStyle = (isDragging, draggableStyle, palette) => ({ @@ -40,7 +40,7 @@ const getItemStyle = (isDragging, draggableStyle, palette) => ({ }); export default function Categories() { - const { setTitle } = useContext(NavBarTitle); + const { setTitle } = useContext(NavbarContext); setTitle('Categories'); const [categories, setCategories] = useState([]); const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category