From c842c51fb6837bead89ef4b3a6351e2ab7f005f9 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Tue, 9 Mar 2021 16:44:09 +0330 Subject: [PATCH] section sources by lang --- .../ir/armor/tachidesk/util/SourceList.kt | 2 +- webUI/react/.eslintrc.js | 2 + .../src/components/ExtensionLangSelect.tsx | 28 +++++---- webUI/react/src/components/SourceCard.tsx | 3 +- webUI/react/src/screens/Extensions.tsx | 31 ++++------ webUI/react/src/screens/Library.tsx | 7 +-- 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 | 9 +-- webUI/react/src/screens/SourceMangas.tsx | 4 +- webUI/react/src/screens/Sources.tsx | 60 ++++++++++++++++++- .../react/src/screens/settings/Categories.jsx | 5 +- webUI/react/src/util/language.tsx | 50 ++++++++++++++++ 14 files changed, 163 insertions(+), 50 deletions(-) create mode 100644 webUI/react/src/util/language.tsx diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt index 9a6c53f..8687d07 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/SourceList.kt @@ -77,7 +77,7 @@ fun getSourceList(): List { SourceDataClass( it[SourceTable.id].value.toString(), it[SourceTable.name], - Locale(it[SourceTable.lang]).getDisplayLanguage(Locale(it[SourceTable.lang])), + it[SourceTable.lang], getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]), getHttpSource(it[SourceTable.id].value).supportsLatest ) diff --git a/webUI/react/.eslintrc.js b/webUI/react/.eslintrc.js index 6f89e18..359020a 100644 --- a/webUI/react/.eslintrc.js +++ b/webUI/react/.eslintrc.js @@ -13,5 +13,7 @@ module.exports = { // Indent props with 4 spaces 'react/jsx-indent-props': ['error', 4], + + 'no-plusplus': ['error', { 'allowForLoopAfterthoughts': true }] }, }; diff --git a/webUI/react/src/components/ExtensionLangSelect.tsx b/webUI/react/src/components/ExtensionLangSelect.tsx index fe1fac1..c5e65c7 100644 --- a/webUI/react/src/components/ExtensionLangSelect.tsx +++ b/webUI/react/src/components/ExtensionLangSelect.tsx @@ -9,11 +9,12 @@ 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 Switch from '@material-ui/core/Switch'; import IconButton from '@material-ui/core/IconButton'; import FilterListIcon from '@material-ui/icons/FilterList'; +import { List, ListItemSecondaryAction, ListItemText } from '@material-ui/core'; +import ListItem from '@material-ui/core/ListItem'; +import { langCodeToName } from '../util/language'; const useStyles = makeStyles(() => createStyles({ paper: { @@ -72,21 +73,22 @@ export default function ExtensionLangSelect(props: IProps) { open={open} > Enabled Languages - - + + {allLangs.map((lang) => ( - + + + + handleChange(e, lang)} - color="default" /> - )} - label={lang} - /> + + + ))} - + diff --git a/webUI/react/src/components/SourceCard.tsx b/webUI/react/src/components/SourceCard.tsx index 20df650..bd71d08 100644 --- a/webUI/react/src/components/SourceCard.tsx +++ b/webUI/react/src/components/SourceCard.tsx @@ -10,6 +10,7 @@ import Button from '@material-ui/core/Button'; import Avatar from '@material-ui/core/Avatar'; import Typography from '@material-ui/core/Typography'; import useLocalStorage from '../util/useLocalStorage'; +import { langCodeToName } from '../util/language'; const useStyles = makeStyles((theme) => ({ root: { @@ -67,7 +68,7 @@ export default function SourceCard(props: IProps) { {name} - {lang} + {langCodeToName(lang)} diff --git a/webUI/react/src/screens/Extensions.tsx b/webUI/react/src/screens/Extensions.tsx index dacce8e..f9ea97a 100644 --- a/webUI/react/src/screens/Extensions.tsx +++ b/webUI/react/src/screens/Extensions.tsx @@ -8,6 +8,7 @@ import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; import useLocalStorage from '../util/useLocalStorage'; import ExtensionLangSelect from '../components/ExtensionLangSelect'; +import { defualtLangs, langCodeToName, langSortCmp } from '../util/language'; const allLangs: string[] = []; @@ -19,7 +20,7 @@ function groupExtensions(extensions: IExtension[]) { extensions.forEach((extension) => { if (result[extension.lang] === undefined) { result[extension.lang] = []; - allLangs.push(extension.lang); + if (extension.lang !== 'all') { allLangs.push(extension.lang); } } if (extension.installed) { result.installed.push(extension); @@ -28,14 +29,10 @@ function groupExtensions(extensions: IExtension[]) { } }); - return result; -} + // put english first for convience + allLangs.sort(langSortCmp); -function defualtLangs() { - return [ - 'all', - 'en', - ]; + return result; } export default function Extensions() { @@ -72,32 +69,30 @@ export default function Extensions() { } }, [extensionsRaw]); - if (extensions.length === 0) { + if (Object.entries(extensions).length === 0) { return

loading...

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

{lang}

+ +

+ {langCodeToName(lang)} +

{(list as IExtension[]).map((it) => ( { triggerUpdate(); }} /> ))} - - ) } - +
+ )) )) } diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/Library.tsx index 6ac7c22..814032f 100644 --- a/webUI/react/src/screens/Library.tsx +++ b/webUI/react/src/screens/Library.tsx @@ -37,16 +37,15 @@ function TabPanel(props: TabPanelProps) { } export default function Library() { - const { setTitle } = useContext(NavbarContext); + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Library'); setAction(<>); }, []); + const [tabs, setTabs] = useState([]); const [tabNum, setTabNum] = useState(0); // a hack so MangaGrid doesn't stop working. I won't change it in case // if I do manga pagination for library.. const [lastPageNum, setLastPageNum] = useState(1); - useEffect(() => { - setTitle('Library'); - }, []); const handleTabChange = (newTab: number) => { setTabNum(newTab); diff --git a/webUI/react/src/screens/Manga.tsx b/webUI/react/src/screens/Manga.tsx index 96a78c5..43281fe 100644 --- a/webUI/react/src/screens/Manga.tsx +++ b/webUI/react/src/screens/Manga.tsx @@ -10,8 +10,10 @@ import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; export default function Manga() { + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Manga'); setAction(<>); }, []); + const { id } = useParams<{id: string}>(); - 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 9f53551..8f24146 100644 --- a/webUI/react/src/screens/Reader.tsx +++ b/webUI/react/src/screens/Reader.tsx @@ -19,8 +19,10 @@ const style = { const range = (n:number) => Array.from({ length: n }, (value, key) => key); export default function Reader() { + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Reader'); setAction(<>); }, []); + const [serverAddress] = useLocalStorage('serverBaseURL', ''); - 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 c5263eb..d13d346 100644 --- a/webUI/react/src/screens/SearchSingle.tsx +++ b/webUI/react/src/screens/SearchSingle.tsx @@ -21,7 +21,9 @@ const useStyles = makeStyles((theme) => ({ })); export default function SearchSingle() { - const { setTitle } = useContext(NavbarContext); + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Search'); setAction(<>); }, []); + 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 40eadac..529bf68 100644 --- a/webUI/react/src/screens/Settings.tsx +++ b/webUI/react/src/screens/Settings.tsx @@ -2,7 +2,7 @@ * 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, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import List from '@material-ui/core/List'; import InboxIcon from '@material-ui/icons/Inbox'; import Brightness6Icon from '@material-ui/icons/Brightness6'; @@ -24,8 +24,9 @@ function ListItemLink(props: ListItemProps<'a', { button?: true }>) { } export default function Settings() { - const { setTitle } = useContext(NavbarContext); - setTitle('Settings'); + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Settings'); setAction(<>); }, []); + const { darkTheme, setDarkTheme } = useContext(DarkTheme); const [serverAddress, setServerAddress] = useLocalStorage('serverBaseURL', ''); const [dialogOpen, setDialogOpen] = useState(false); @@ -47,7 +48,7 @@ export default function Settings() { return ( <> - + diff --git a/webUI/react/src/screens/SourceMangas.tsx b/webUI/react/src/screens/SourceMangas.tsx index 8647933..fdfea4c 100644 --- a/webUI/react/src/screens/SourceMangas.tsx +++ b/webUI/react/src/screens/SourceMangas.tsx @@ -9,8 +9,10 @@ import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; export default function SourceMangas(props: { popular: boolean }) { + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Source'); setAction(<>); }, []); + const { sourceId } = useParams<{sourceId: string}>(); - 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 69a76cb..7467553 100644 --- a/webUI/react/src/screens/Sources.tsx +++ b/webUI/react/src/screens/Sources.tsx @@ -3,16 +3,53 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import React, { useContext, useEffect, useState } from 'react'; +import ExtensionLangSelect from '../components/ExtensionLangSelect'; import SourceCard from '../components/SourceCard'; import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; +import { defualtLangs, langCodeToName, langSortCmp } from '../util/language'; +import useLocalStorage from '../util/useLocalStorage'; + +function sourceToLangList(sources: ISource[]) { + const result: string[] = []; + + sources.forEach((source) => { + if (result.indexOf(source.lang) === -1 && langCodeToName(source.lang) !== 'Error') { result.push(source.lang); } + }); + + result.sort(langSortCmp); + return result; +} + +function groupByLang(sources: ISource[]) { + const result = {} as any; + sources.forEach((source) => { + if (result[source.lang] === undefined) { result[source.lang] = [] as ISource[]; } + result[source.lang].push(source); + }); + + return result; +} export default function Sources() { - const { setTitle } = useContext(NavbarContext); - setTitle('Sources'); + const { setTitle, setAction } = useContext(NavbarContext); + + const [shownLangs, setShownLangs] = useLocalStorage('shownSourceLangs', defualtLangs()); + const [sources, setSources] = useState([]); const [fetched, setFetched] = useState(false); + useEffect(() => { + setTitle('Sources'); + setAction( + , + ); + }, [shownLangs, sources]); + useEffect(() => { client.get('/api/v1/source/list') .then((response) => response.data) @@ -23,5 +60,22 @@ export default function Sources() { if (fetched) return (

No sources found. Install Some Extensions first.

); return (

loading...

); } - return <>{sources.map((it) => )}; + return ( + <> + {/* eslint-disable-next-line max-len */} + {Object.entries(groupByLang(sources)).sort((a, b) => langSortCmp(a[0], b[0])).map(([lang, list]) => ( + shownLangs.indexOf(lang) !== -1 && ( + +

{langCodeToName(lang)}

+ {(list as ISource[]).map((source) => ( + + ))} +
+ ) + ))} + + ); } diff --git a/webUI/react/src/screens/settings/Categories.jsx b/webUI/react/src/screens/settings/Categories.jsx index 043714f..8ac1d98 100644 --- a/webUI/react/src/screens/settings/Categories.jsx +++ b/webUI/react/src/screens/settings/Categories.jsx @@ -40,8 +40,9 @@ const getItemStyle = (isDragging, draggableStyle, palette) => ({ }); export default function Categories() { - const { setTitle } = useContext(NavbarContext); - setTitle('Categories'); + const { setTitle, setAction } = useContext(NavbarContext); + useEffect(() => { setTitle('Categories'); setAction(<>); }, []); + const [categories, setCategories] = useState([]); const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category const [dialogOpen, setDialogOpen] = useState(false); diff --git a/webUI/react/src/util/language.tsx b/webUI/react/src/util/language.tsx new file mode 100644 index 0000000..46c7d2e --- /dev/null +++ b/webUI/react/src/util/language.tsx @@ -0,0 +1,50 @@ +/* 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/. */ + +export const ISOLanguages = [ + { code: 'all', name: 'All', nativeName: 'All' }, + { code: 'installed', name: 'Installed', nativeName: 'Installed' }, + + { code: 'en', name: 'English', nativeName: 'English' }, + { code: 'ca', name: 'Catalan; Valencian', nativeName: 'Català' }, + { code: 'de', name: 'German', nativeName: 'Deutsch' }, + { code: 'es', name: 'Spanish; Castilian', nativeName: 'Español' }, + { code: 'fr', name: 'French', nativeName: 'Français' }, + { code: 'id', name: 'Indonesian', nativeName: 'Indonesia' }, + { code: 'it', name: 'Italian', nativeName: 'Italiano' }, + { code: 'pt', name: 'Portuguese', nativeName: 'Português' }, + { code: 'vi', name: 'Vietnamese', nativeName: 'Tiếng Việt' }, + { code: 'tr', name: 'Turkish', nativeName: 'Türkçe' }, + { code: 'ru', name: 'Russian', nativeName: 'русский' }, + { code: 'ar', name: 'Arabic', nativeName: 'العربية' }, + { code: 'hi', name: 'Hindi', nativeName: 'हिन्दी' }, + { code: 'th', name: 'Thai', nativeName: 'ไทย' }, + { code: 'zh', name: 'Chinese', nativeName: '中文' }, + { code: 'ja', name: 'Japanese', nativeName: '日本語' }, + { code: 'ko', name: 'Korean', nativeName: '한국어' }, +]; + +export function langCodeToName(code: string): string { + for (let i = 0; i < ISOLanguages.length; i++) { + if (ISOLanguages[i].code === code) return ISOLanguages[i].nativeName; + } + return 'Error'; +} + +export function defualtLangs() { + return [ + // todo: infer this from the browser + 'en', + ]; +} + +export const langSortCmp = (a: string, b: string) => { + // puts english first for convience + const aLang = langCodeToName(a); + const bLang = langCodeToName(b); + + if (a === 'en') return -1; + if (b === 'en') return 1; + return aLang > bLang ? 1 : -1; +};