add library

This commit is contained in:
Aria Moradi 2021-02-13 21:12:18 +03:30
parent eb90db7ce6
commit 09d624a4e2
13 changed files with 165 additions and 23 deletions

View File

@ -6,11 +6,13 @@ package ir.armor.tachidesk
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import io.javalin.Javalin import io.javalin.Javalin
import ir.armor.tachidesk.util.addMangaToLibrary
import ir.armor.tachidesk.util.applicationSetup import ir.armor.tachidesk.util.applicationSetup
import ir.armor.tachidesk.util.getChapter import ir.armor.tachidesk.util.getChapter
import ir.armor.tachidesk.util.getChapterList import ir.armor.tachidesk.util.getChapterList
import ir.armor.tachidesk.util.getExtensionIcon import ir.armor.tachidesk.util.getExtensionIcon
import ir.armor.tachidesk.util.getExtensionList import ir.armor.tachidesk.util.getExtensionList
import ir.armor.tachidesk.util.getLibraryMangas
import ir.armor.tachidesk.util.getManga import ir.armor.tachidesk.util.getManga
import ir.armor.tachidesk.util.getMangaList import ir.armor.tachidesk.util.getMangaList
import ir.armor.tachidesk.util.getPageImage import ir.armor.tachidesk.util.getPageImage
@ -20,6 +22,7 @@ import ir.armor.tachidesk.util.getThumbnail
import ir.armor.tachidesk.util.installAPK import ir.armor.tachidesk.util.installAPK
import ir.armor.tachidesk.util.openInBrowser import ir.armor.tachidesk.util.openInBrowser
import ir.armor.tachidesk.util.removeExtension import ir.armor.tachidesk.util.removeExtension
import ir.armor.tachidesk.util.removeMangaFromLibrary
import ir.armor.tachidesk.util.sourceFilters import ir.armor.tachidesk.util.sourceFilters
import ir.armor.tachidesk.util.sourceGlobalSearch import ir.armor.tachidesk.util.sourceGlobalSearch
import ir.armor.tachidesk.util.sourceSearch import ir.armor.tachidesk.util.sourceSearch
@ -73,15 +76,16 @@ class Main {
println("Warning: react build files are missing.") println("Warning: react build files are missing.")
hasWebUiBundled = false hasWebUiBundled = false
} }
config.enableCorsForAllOrigins()
}.start(4567) }.start(4567)
if (hasWebUiBundled) { if (hasWebUiBundled) {
openInBrowser() openInBrowser()
} }
app.before() { ctx -> // app.before() { ctx ->
// allow the client which is running on another port // // allow the client which is running on another port
ctx.header("Access-Control-Allow-Origin", "*") // ctx.header("Access-Control-Allow-Origin", "*")
} // }
app.get("/api/v1/extension/list") { ctx -> app.get("/api/v1/extension/list") { ctx ->
ctx.json(getExtensionList()) ctx.json(getExtensionList())
@ -146,12 +150,17 @@ class Main {
// adds the manga to library // adds the manga to library
app.get("api/v1/manga/:mangaId/library") { ctx -> app.get("api/v1/manga/:mangaId/library") { ctx ->
// TODO val mangaId = ctx.pathParam("mangaId").toInt()
addMangaToLibrary(mangaId)
ctx.status(200)
} }
// removes the manga from the library // removes the manga from the library
app.delete("api/v1/manga/:mangaId/library") { ctx -> app.delete("api/v1/manga/:mangaId/library") { ctx ->
// TODO val mangaId = ctx.pathParam("mangaId").toInt()
println("fuck")
removeMangaFromLibrary(mangaId)
ctx.status(200)
} }
// adds the manga to category // adds the manga to category
@ -207,7 +216,7 @@ class Main {
// lists all manga in the library, suitable if no categories are defined // lists all manga in the library, suitable if no categories are defined
app.get("/api/v1/library/") { ctx -> app.get("/api/v1/library/") { ctx ->
// TODO ctx.json(getLibraryMangas())
} }
// category list // category list

View File

@ -20,7 +20,8 @@ data class MangaDataClass(
val author: String? = null, val author: String? = null,
val description: String? = null, val description: String? = null,
val genre: String? = null, val genre: String? = null,
val status: String = MangaStatus.UNKNOWN.name val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false
) )
data class PagedMangaListDataClass( data class PagedMangaListDataClass(

View File

@ -5,7 +5,10 @@ package ir.armor.tachidesk.database.table
* 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 eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import ir.armor.tachidesk.database.dataclass.MangaDataClass
import ir.armor.tachidesk.util.proxyThumbnailUrl
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow
object MangaTable : IntIdTable() { object MangaTable : IntIdTable() {
val url = varchar("url", 2048) val url = varchar("url", 2048)
@ -27,6 +30,25 @@ object MangaTable : IntIdTable() {
val sourceReference = reference("source", SourceTable) val sourceReference = reference("source", SourceTable)
} }
fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass(
mangaEntry[MangaTable.id].value,
mangaEntry[sourceReference].value,
mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title],
proxyThumbnailUrl(mangaEntry[MangaTable.id].value),
mangaEntry[MangaTable.initialized],
mangaEntry[MangaTable.artist],
mangaEntry[MangaTable.author],
mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary]
)
enum class MangaStatus(val status: Int) { enum class MangaStatus(val status: Int) {
UNKNOWN(0), UNKNOWN(0),
ONGOING(1), ONGOING(1),

View File

@ -0,0 +1,42 @@
package ir.armor.tachidesk.util
import ir.armor.tachidesk.database.dataclass.MangaDataClass
import ir.armor.tachidesk.database.table.MangaTable
import ir.armor.tachidesk.database.table.toDataClass
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
/* 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/. */
fun addMangaToLibrary(mangaId: Int) {
val manga = getManga(mangaId)
if (!manga.inLibrary) {
transaction {
MangaTable.update({ MangaTable.id eq manga.id }) {
it[inLibrary] = true
}
}
}
}
fun removeMangaFromLibrary(mangaId: Int) {
val manga = getManga(mangaId)
if (!manga.inLibrary) {
transaction {
MangaTable.update({ MangaTable.id eq manga.id }) {
it[inLibrary] = false
}
}
}
}
fun getLibraryMangas(): List<MangaDataClass> {
return transaction {
MangaTable.select { MangaTable.inLibrary eq true }.map {
MangaTable.toDataClass(it)
}
}
}

View File

@ -34,6 +34,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
mangaEntry[MangaTable.description], mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre], mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary]
) )
} else { // initialize manga } else { // initialize manga
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value) val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
@ -77,6 +78,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
fetchedManga.description, fetchedManga.description,
fetchedManga.genre, fetchedManga.genre,
MangaStatus.valueOf(fetchedManga.status).name, MangaStatus.valueOf(fetchedManga.status).name,
false
) )
} }
} }

View File

@ -64,7 +64,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
manga.author, manga.author,
manga.description, manga.description,
manga.genre, manga.genre,
MangaStatus.valueOf(manga.status).name, MangaStatus.valueOf(manga.status).name
) )
} else { } else {
val mangaId = mangaEntry[MangaTable.id].value val mangaId = mangaEntry[MangaTable.id].value
@ -83,6 +83,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
mangaEntry[MangaTable.description], mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre], mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary]
) )
} }
} }

10
webUI/node_modules/.yarn-integrity generated vendored
View File

@ -1,10 +0,0 @@
{
"systemParams": "linux-x64-88",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}

View File

@ -20,6 +20,7 @@ import Reader from './screens/Reader';
import Search from './screens/SearchSingle'; import Search from './screens/SearchSingle';
import NavBarTitle from './context/NavbarTitle'; import NavBarTitle from './context/NavbarTitle';
import DarkTheme from './context/DarkTheme'; import DarkTheme from './context/DarkTheme';
import Library from './screens/Library';
export default function App() { export default function App() {
const [title, setTitle] = useState<string>('Tachidesk'); const [title, setTitle] = useState<string>('Tachidesk');
@ -53,7 +54,6 @@ export default function App() {
return ( return (
<Router> <Router>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<NavBarTitle.Provider value={navTitleContext}> <NavBarTitle.Provider value={navTitleContext}>
<CssBaseline /> <CssBaseline />
@ -83,6 +83,9 @@ export default function App() {
<Route path="/manga/:id"> <Route path="/manga/:id">
<Manga /> <Manga />
</Route> </Route>
<Route path="/library">
<Library />
</Route>
<Route path="/"> <Route path="/">
<Home /> <Home />
</Route> </Route>

View File

@ -2,20 +2,49 @@
* 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 from 'react'; import { Button } from '@material-ui/core';
import React, { useState } from 'react';
interface IProps{ interface IProps{
manga: IManga | undefined manga: IManga
} }
export default function MangaDetails(props: IProps) { export default function MangaDetails(props: IProps) {
const { manga } = props; const { manga } = props;
const [inLibrary, setInLibrary] = useState<string>(
manga.inLibrary ? 'In Library' : 'Not In Library',
);
function addToLibrary() {
setInLibrary('adding');
fetch(`http://127.0.0.1:4567/api/v1/manga/${manga.id}/library/`).then(() => {
setInLibrary('In Library');
});
}
function removeFromLibrary() {
setInLibrary('removing');
fetch(`http://127.0.0.1:4567/api/v1/manga/${manga.id}/library/`, { method: 'DELETE', mode: 'cors' }).then(() => {
setInLibrary('Not In Library');
});
}
function handleButtonClick() {
if (inLibrary === 'Not In Library') {
addToLibrary();
} else {
removeFromLibrary();
}
}
return ( return (
<> <>
<h1> <h1>
{manga && manga.title} {manga && manga.title}
</h1> </h1>
<div style={{ display: 'flex', flexDirection: 'row-reverse' }}>
<Button variant="outlined" onClick={() => handleButtonClick()}>{inLibrary}</Button>
</div>
</> </>
); );
} }

View File

@ -36,6 +36,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
onKeyDown={() => setDrawerOpen(false)} onKeyDown={() => setDrawerOpen(false)}
> >
<List> <List>
<Link to="/library" style={{ color: 'inherit', textDecoration: 'none' }}>
<ListItem button key="Library">
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Library" />
</ListItem>
</Link>
<Link to="/extensions" style={{ color: 'inherit', textDecoration: 'none' }}> <Link to="/extensions" style={{ color: 'inherit', textDecoration: 'none' }}>
<ListItem button key="Extensions"> <ListItem button key="Extensions">
<ListItemIcon> <ListItemIcon>

View File

@ -0,0 +1,34 @@
/* 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, { useContext, useEffect, useState } from 'react';
import MangaGrid from '../components/MangaGrid';
import NavBarTitle from '../context/NavbarTitle';
export default function MangaList() {
const { setTitle } = useContext(NavBarTitle);
const [mangas, setMangas] = useState<IManga[]>([]);
const [lastPageNum, setLastPageNum] = useState<number>(1);
useEffect(() => {
setTitle('Library');
}, []);
useEffect(() => {
fetch('http://127.0.0.1:4567/api/v1/library')
.then((response) => response.json())
.then((data: IManga[]) => {
setMangas(data);
});
}, [lastPageNum]);
return (
<MangaGrid
mangas={mangas}
hasNextPage={false}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
/>
);
}

View File

@ -38,7 +38,7 @@ export default function Manga() {
return ( return (
<> <>
<MangaDetails manga={manga} /> {manga && <MangaDetails manga={manga} />}
{chapterCards} {chapterCards}
</> </>
); );

View File

@ -25,6 +25,7 @@ interface IManga {
id: number id: number
title: string title: string
thumbnailUrl: string thumbnailUrl: string
inLibrary?: boolean
} }
interface IChapter { interface IChapter {