diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index eb82b74..0d10ebe 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -6,11 +6,13 @@ package ir.armor.tachidesk import eu.kanade.tachiyomi.App import io.javalin.Javalin +import ir.armor.tachidesk.util.addMangaToLibrary import ir.armor.tachidesk.util.applicationSetup import ir.armor.tachidesk.util.getChapter import ir.armor.tachidesk.util.getChapterList import ir.armor.tachidesk.util.getExtensionIcon import ir.armor.tachidesk.util.getExtensionList +import ir.armor.tachidesk.util.getLibraryMangas import ir.armor.tachidesk.util.getManga import ir.armor.tachidesk.util.getMangaList 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.openInBrowser import ir.armor.tachidesk.util.removeExtension +import ir.armor.tachidesk.util.removeMangaFromLibrary import ir.armor.tachidesk.util.sourceFilters import ir.armor.tachidesk.util.sourceGlobalSearch import ir.armor.tachidesk.util.sourceSearch @@ -73,15 +76,16 @@ class Main { println("Warning: react build files are missing.") hasWebUiBundled = false } + config.enableCorsForAllOrigins() }.start(4567) if (hasWebUiBundled) { openInBrowser() } - app.before() { ctx -> - // allow the client which is running on another port - ctx.header("Access-Control-Allow-Origin", "*") - } +// app.before() { ctx -> +// // allow the client which is running on another port +// ctx.header("Access-Control-Allow-Origin", "*") +// } app.get("/api/v1/extension/list") { ctx -> ctx.json(getExtensionList()) @@ -146,12 +150,17 @@ class Main { // adds the manga to library 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 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 @@ -207,7 +216,7 @@ class Main { // lists all manga in the library, suitable if no categories are defined app.get("/api/v1/library/") { ctx -> - // TODO + ctx.json(getLibraryMangas()) } // category list diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt index 61020ba..3bdab8f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/MangaDataClass.kt @@ -20,7 +20,8 @@ data class MangaDataClass( val author: String? = null, val description: 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( diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt index bfa36c9..13e3a56 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt @@ -5,7 +5,10 @@ package ir.armor.tachidesk.database.table * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 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.sql.ResultRow object MangaTable : IntIdTable() { val url = varchar("url", 2048) @@ -27,6 +30,25 @@ object MangaTable : IntIdTable() { 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) { UNKNOWN(0), ONGOING(1), diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt new file mode 100644 index 0000000..5766ba9 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt @@ -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 { + return transaction { + MangaTable.select { MangaTable.inLibrary eq true }.map { + MangaTable.toDataClass(it) + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt index 708fdc4..81e2c3b 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Manga.kt @@ -34,6 +34,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { mangaEntry[MangaTable.description], mangaEntry[MangaTable.genre], MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + mangaEntry[MangaTable.inLibrary] ) } else { // initialize manga val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value) @@ -77,6 +78,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass { fetchedManga.description, fetchedManga.genre, MangaStatus.valueOf(fetchedManga.status).name, + false ) } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt index 06d616d..b46f854 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/MangaList.kt @@ -64,7 +64,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { manga.author, manga.description, manga.genre, - MangaStatus.valueOf(manga.status).name, + MangaStatus.valueOf(manga.status).name ) } else { val mangaId = mangaEntry[MangaTable.id].value @@ -83,6 +83,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { mangaEntry[MangaTable.description], mangaEntry[MangaTable.genre], MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + mangaEntry[MangaTable.inLibrary] ) } } diff --git a/webUI/node_modules/.yarn-integrity b/webUI/node_modules/.yarn-integrity deleted file mode 100644 index 6848ff5..0000000 --- a/webUI/node_modules/.yarn-integrity +++ /dev/null @@ -1,10 +0,0 @@ -{ - "systemParams": "linux-x64-88", - "modulesFolders": [], - "flags": [], - "linkedModules": [], - "topLevelPatterns": [], - "lockfileEntries": {}, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/webUI/react/src/App.tsx b/webUI/react/src/App.tsx index d8dc910..2a864d6 100644 --- a/webUI/react/src/App.tsx +++ b/webUI/react/src/App.tsx @@ -20,6 +20,7 @@ import Reader from './screens/Reader'; import Search from './screens/SearchSingle'; import NavBarTitle from './context/NavbarTitle'; import DarkTheme from './context/DarkTheme'; +import Library from './screens/Library'; export default function App() { const [title, setTitle] = useState('Tachidesk'); @@ -53,7 +54,6 @@ export default function App() { return ( - @@ -83,6 +83,9 @@ export default function App() { + + + diff --git a/webUI/react/src/components/MangaDetails.tsx b/webUI/react/src/components/MangaDetails.tsx index 3da6a55..8e1cf1c 100644 --- a/webUI/react/src/components/MangaDetails.tsx +++ b/webUI/react/src/components/MangaDetails.tsx @@ -2,20 +2,49 @@ * 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 from 'react'; +import { Button } from '@material-ui/core'; +import React, { useState } from 'react'; interface IProps{ - manga: IManga | undefined + manga: IManga } export default function MangaDetails(props: IProps) { const { manga } = props; + const [inLibrary, setInLibrary] = useState( + 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 ( <>

{manga && manga.title}

+
+ +
); } diff --git a/webUI/react/src/components/TemporaryDrawer.tsx b/webUI/react/src/components/TemporaryDrawer.tsx index ace737a..5304780 100644 --- a/webUI/react/src/components/TemporaryDrawer.tsx +++ b/webUI/react/src/components/TemporaryDrawer.tsx @@ -36,6 +36,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) { onKeyDown={() => setDrawerOpen(false)} > + + + + + + + + diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/Library.tsx new file mode 100644 index 0000000..de83610 --- /dev/null +++ b/webUI/react/src/screens/Library.tsx @@ -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([]); + const [lastPageNum, setLastPageNum] = useState(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 ( + + ); +} diff --git a/webUI/react/src/screens/Manga.tsx b/webUI/react/src/screens/Manga.tsx index ae51e8c..5ba5d93 100644 --- a/webUI/react/src/screens/Manga.tsx +++ b/webUI/react/src/screens/Manga.tsx @@ -38,7 +38,7 @@ export default function Manga() { return ( <> - + {manga && } {chapterCards} ); diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index 0f1cb88..60a2d35 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -25,6 +25,7 @@ interface IManga { id: number title: string thumbnailUrl: string + inLibrary?: boolean } interface IChapter {