Compare commits

...

10 Commits

Author SHA1 Message Date
Aria Moradi 9cd93d467c bump version
Publish / Validate Gradle Wrapper (push) Successful in 11s
Publish / Build FatJar (push) Failing after 17s
2021-03-16 16:15:20 +03:30
Aria Moradi 257f8a5a27 fix extensions not showing the all pesudo-language 2021-03-16 16:10:06 +03:30
Aria Moradi 79bab08cae improvements 2021-03-16 16:04:29 +03:30
Aria Moradi 4e699e4f5a update dex2jar 2021-03-16 15:44:50 +03:30
Aria Moradi 1128f40bac closes #32 2021-03-14 23:57:33 +03:30
Aria Moradi 53ef836326 fix windows path 2021-03-14 20:28:23 +03:30
Aria Moradi b8df0e89e5 Don't show installed if nothing is installed 2021-03-14 14:09:31 +03:30
Aria Moradi 472bfec6bf improve docs 2021-03-14 01:26:52 +03:30
Aria Moradi c1b86cedd2 move getAndroid.sh 2021-03-14 01:02:43 +03:30
Aria Moradi 428c65f075 Enforce more limits on the issue format. 2021-03-13 22:59:37 +03:30
50 changed files with 179 additions and 68 deletions
+3 -3
View File
@@ -34,10 +34,10 @@ Note that the issue will be automatically closed if you do not fill out the titl
2. Second Step
### Expected behavior
Describe what should have happened
Describe what should have happened. Remove this line after you are done.
### Actual behavior
Describe what happens instead
Describe what happens instead. Remove this line after you are done.
## Other details
Describe additional details If necessary
Describe additional details If necessary. Remove this line after you are done.
+2 -2
View File
@@ -23,7 +23,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
---
## What feature should be added to Tachidesk?
Explain What the feature is and how it should work in detail
Explain What the feature is and how it should work in detail. Remove this line after you are done.
## Why/Project's Benefit/Existing Problem
Explain why this should be added
Explain why this should be added. Remove this line after you are done.
+5
View File
@@ -28,5 +28,10 @@ jobs:
"type": "body",
"regex": "(Tachidesk version|Server Operating System|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
"message": "The requested information was not filled out"
},
{
"type": "body",
"regex": ".*Remove this line after you are done.*",
"message": "The lines requesting to be removed were not removed."
}
]
@@ -1,4 +1,11 @@
#!/usr/bin/env bash
# foolproof against running from AndroidCompat dir instead of running from project root
if [ "$(basename $(pwd))" = "AndroidCompat" ]; then
cd ..
fi
echo "Getting required Android.jar..."
rm -rf "tmp"
mkdir -p "tmp"
+10 -11
View File
@@ -21,19 +21,18 @@ Here is a list of current features:
Anyways, for more info checkout [finished milestone #1](https://github.com/AriaMoradi/Tachidesk/issues/2) and [milestone #2](https://github.com/AriaMoradi/Tachidesk/projects/1) to see what's implemented in more detail.
## Downloading and Running the app
#### Prerequisites
You should have The Java Runtime Environment(JRE) 8 or newer (if you're not planning to use the Windows specific build) and a modern browser installed. Also an internet connection is required as almost everything this app does is downloading stuff.
#### Download the app
### Downloading the app
Download the latest jar or windows(win32) release from [the releases section](https://github.com/AriaMoradi/Tachidesk/releases).
#### Running pre-built jar packages
### All Operating Systems
You should have The Java Runtime Environment(JRE) 8 or newer and a modern browser installed. Also an internet connection is required as almost everything this app does is downloading stuff.
Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxx.jar` from a Terminal/Command Prompt window to run the app which will open a new browser window automatically. Also the System Tray Icon is your friend if you need to open the browser window again or close Tachidesk.
#### Running pre-built Windows packages
Windows specific builds have java bundled inside them, so you don't have to install java to use it. Unzip `Tachidesk-vX.Y.Z-rxxx-win32.zip` and run `server.exe`, the rest will work like the jar release.
### Windows only
The Windows specific build has java bundled inside, so you don't have to install java to use it. Unzip `Tachidesk-vX.Y.Z-rxxx-win32.zip` and run `server.exe`.
#### Running on Docker
### Running on Docker
Check [arbuilder's repo](https://github.com/arbuilder/Tachidesk-docker) out for more details and the dockerfile.
## General troubleshooting
@@ -43,7 +42,7 @@ On Mac OS X : `/Users/<Account>/Library/Application Support/Tachidesk`
On Windows XP : `C:\Documents and Settings\<Account>\Application Data\Local Settings\Tachidesk`
On Windows 7 and later : `C:\Users\<Account>\AppData\Tachidesk`
On Windows 7 and later : `C:\Users\<Account>\AppData\Local\Tachidesk`
On Unix/Linux : `/home/<account>/.local/share/Tachidesk`
@@ -60,7 +59,7 @@ This project has two components:
#### Manual download
Download [android.jar](https://raw.githubusercontent.com/AriaMoradi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
#### Automated download(needs `bash`, `curl`, `base64`, `zip` to work)
Run `scripts/getAndroid.sh` from project's root directory to download and rebuild the jar file from Google's repository.
Run `AndroidCompat/getAndroid.sh` from project's root directory to download and rebuild the jar file from Google's repository.
### building the jar
Run `./gradlew shadowJar`, the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
### building the Windows package
@@ -76,7 +75,7 @@ How to do it is described in `webUI/react/README.md` but for short,
and supports HMR and all the other goodies you'll need.
## Credit
The `AndroidCompat` module and `scripts/getAndroid.sh` was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
The `AndroidCompat` module was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
+4 -4
View File
@@ -9,7 +9,7 @@ plugins {
id("edu.sc.seis.launch4j") version "2.4.9"
}
val TachideskVersion = "v0.2.4"
val TachideskVersion = "v0.2.5"
repositories {
@@ -63,7 +63,7 @@ dependencies {
val coroutinesVersion = "1.3.9"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
// dex2jar
// dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon
implementation(fileTree("lib/dex2jar/"))
// api
@@ -88,8 +88,8 @@ dependencies {
implementation(project(":AndroidCompat:Config"))
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
// testImplementation("org.jetbrains.kotlin:kotlin-test")
// testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
val name = "ir.armor.tachidesk.Main"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,67 @@
==== dx-*.jar
Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0.html
==== antlr-*.jar
[The BSD License]
Copyright (c) 2003-2007, Terence Parr
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the author nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
==== asm-*.jar
ASM: a very small and fast Java bytecode manipulation framework
Copyright (c) 2000-2005 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
@@ -58,6 +58,10 @@ class Main {
openInBrowser()
}
app.exception(NullPointerException::class.java) { _, ctx ->
ctx.status(404)
}
app.get("/api/v1/extension/list") { ctx ->
ctx.json(getExtensionList())
}
@@ -78,6 +82,7 @@ class Main {
ctx.status(200)
}
// icon for extension named `apkName`
app.get("/api/v1/extension/icon/:apkName") { ctx ->
val apkName = ctx.pathParam("apkName")
val result = getExtensionIcon(apkName)
@@ -86,31 +91,38 @@ class Main {
ctx.header("content-type", result.second)
}
// list of sources
app.get("/api/v1/source/list") { ctx ->
ctx.json(getSourceList())
}
// fetch source with id `sourceId`
app.get("/api/v1/source/:sourceId") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
ctx.json(getSource(sourceId))
}
// popular mangas from source with id `sourceId`
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
val pageNum = ctx.pathParam("pageNum").toInt()
ctx.json(getMangaList(sourceId, pageNum, popular = true))
}
// latest mangas from source with id `sourceId`
app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
val pageNum = ctx.pathParam("pageNum").toInt()
ctx.json(getMangaList(sourceId, pageNum, popular = false))
}
// get manga info
app.get("/api/v1/manga/:mangaId/") { ctx ->
val mangaId = ctx.pathParam("mangaId").toInt()
ctx.json(getManga(mangaId))
}
// manga thumbnail
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
val mangaId = ctx.pathParam("mangaId").toInt()
val result = getThumbnail(mangaId)
@@ -133,7 +145,7 @@ class Main {
ctx.status(200)
}
// adds the manga to category
// list manga's categories
app.get("api/v1/manga/:mangaId/category/") { ctx ->
val mangaId = ctx.pathParam("mangaId").toInt()
ctx.json(getMangaCategories(mangaId))
@@ -215,7 +227,7 @@ class Main {
// category modification
app.patch("/api/v1/category/:categoryId") { ctx ->
val categoryId = ctx.pathParam("categoryId")!!.toInt()
val categoryId = ctx.pathParam("categoryId").toInt()
val name = ctx.formParam("name")
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
updateCategory(categoryId, name, isLanding)
@@ -8,7 +8,7 @@ import ir.armor.tachidesk.database.table.MangaStatus
data class MangaDataClass(
val id: Int,
val sourceId: Long,
val sourceId: String,
val url: String,
val title: String,
@@ -21,7 +21,8 @@ data class MangaDataClass(
val description: String? = null,
val genre: String? = null,
val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false
val inLibrary: Boolean = false,
val source: SourceDataClass? = null
)
data class PagedMangaListDataClass(
@@ -6,8 +6,8 @@ package ir.armor.tachidesk.database.dataclass
data class SourceDataClass(
val id: String,
val name: String,
val lang: String,
val iconUrl: String,
val supportsLatest: Boolean
val name: String?,
val lang: String?,
val iconUrl: String?,
val supportsLatest: Boolean?
)
@@ -28,13 +28,13 @@ object MangaTable : IntIdTable() {
val defaultCategory = bool("default_category").default(true)
// source is used by some ancestor of IntIdTable
val sourceReference = reference("source", SourceTable)
val sourceReference = long("source")
}
fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass(
mangaEntry[MangaTable.id].value,
mangaEntry[sourceReference].value,
mangaEntry[sourceReference].toString(),
mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title],
@@ -18,7 +18,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
fun getChapterList(mangaId: Int): List<ChapterDataClass> {
val mangaDetails = getManga(mangaId)
val source = getHttpSource(mangaDetails.sourceId)
val source = getHttpSource(mangaDetails.sourceId.toLong())
val chapterList = source.fetchChapterList(
SManga.create().apply {
@@ -62,7 +62,7 @@ fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass {
val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!!
assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check
val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
val pageList = source.fetchPageList(
SChapter.create().apply {
@@ -21,7 +21,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
return if (mangaEntry[MangaTable.initialized]) {
MangaDataClass(
mangaId,
mangaEntry[MangaTable.sourceReference].value,
mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title],
@@ -34,10 +34,11 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary]
mangaEntry[MangaTable.inLibrary],
getSource(mangaEntry[MangaTable.sourceReference])
)
} else { // initialize manga
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
val fetchedManga = source.fetchMangaDetails(
SManga.create().apply {
url = mangaEntry[MangaTable.url]
@@ -65,7 +66,7 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
MangaDataClass(
mangaId,
mangaEntry[MangaTable.sourceReference].value,
mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title],
@@ -78,7 +79,8 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
fetchedManga.description,
fetchedManga.genre,
MangaStatus.valueOf(fetchedManga.status).name,
false
false,
getSource(mangaEntry[MangaTable.sourceReference])
)
}
}
@@ -89,7 +91,7 @@ fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
val fileName = mangaId.toString()
return getCachedResponse(saveDir, fileName) {
val sourceId = mangaEntry[MangaTable.sourceReference].value
val sourceId = mangaEntry[MangaTable.sourceReference]
val source = getHttpSource(sourceId)
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
@@ -52,7 +52,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
MangaDataClass(
mangaId,
sourceId,
sourceId.toString(),
manga.url,
manga.title,
@@ -70,7 +70,7 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
val mangaId = mangaEntry[MangaTable.id].value
MangaDataClass(
mangaId,
sourceId,
sourceId.toString(),
manga.url,
manga.title,
@@ -27,7 +27,7 @@ fun getTrueImageUrl(page: Page, source: HttpSource): String {
fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.firstOrNull()!! }
@@ -56,7 +56,7 @@ fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, St
fun getChapterDir(mangaId: Int, chapterId: Int): String {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val sourceId = mangaEntry[MangaTable.sourceReference].value
val sourceId = mangaEntry[MangaTable.sourceReference]
val source = getHttpSource(sourceId)
val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! }
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }
@@ -18,6 +18,7 @@ fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaLi
}
fun sourceGlobalSearch(searchTerm: String) {
// TODO
}
data class FilterWrapper(
@@ -15,14 +15,18 @@ import ir.armor.tachidesk.database.table.SourceTable
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import java.lang.NullPointerException
import java.net.URL
import java.net.URLClassLoader
import java.util.Locale
private val sourceCache = mutableListOf<Pair<Long, HttpSource>>()
private val extensionCache = mutableListOf<Pair<String, Any>>()
fun getHttpSource(sourceId: Long): HttpSource {
val sourceRecord = transaction {
SourceEntity.findById(sourceId)
} ?: throw NullPointerException("Source with id $sourceId is not installed")
val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId }
if (cachedResult != null) {
println("used cached HttpSource: ${cachedResult.second.name}")
@@ -30,7 +34,6 @@ fun getHttpSource(sourceId: Long): HttpSource {
}
val result: HttpSource = transaction {
val sourceRecord = SourceEntity.findById(sourceId)!!
val extensionId = sourceRecord.extension.id.value
val extensionRecord = ExtensionEntity.findById(extensionId)!!
val apkName = extensionRecord.apkName
@@ -87,14 +90,14 @@ fun getSourceList(): List<SourceDataClass> {
fun getSource(sourceId: Long): SourceDataClass {
return transaction {
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
return@transaction SourceDataClass(
source[SourceTable.id].value.toString(),
source[SourceTable.name],
Locale(source[SourceTable.lang]).getDisplayLanguage(Locale(source[SourceTable.lang])),
ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl],
getHttpSource(source[SourceTable.id].value).supportsLatest
sourceId.toString(),
source?.get(SourceTable.name),
source?.get(SourceTable.lang),
source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] },
source?.let { getHttpSource(sourceId).supportsLatest }
)
}
}
@@ -1,13 +0,0 @@
/*
* This Kotlin source file was generated by the Gradle 'init' task.
*/
package ir.armor.tachidesk
import kotlin.test.Test
import kotlin.test.assertTrue
class AppTest {
@Test fun testAppHasAGreeting() {
assertTrue(true)
}
}
+13 -2
View File
@@ -19,11 +19,17 @@ const useStyles = makeStyles(() => createStyles({
interface IProps{
manga: IManga
source: ISource
}
function getSourceName(source: ISource) {
if (source.name !== null) { return source.name; }
return source.id;
}
export default function MangaDetails(props: IProps) {
const classes = useStyles();
const { manga } = props;
const { manga, source } = props;
const [inLibrary, setInLibrary] = useState<string>(
manga.inLibrary ? 'In Library' : 'Not In Library',
);
@@ -54,8 +60,13 @@ export default function MangaDetails(props: IProps) {
return (
<div>
<h1>
{manga && manga.title}
{manga.title}
</h1>
<h3>
Source:
{' '}
{getSourceName(source)}
</h3>
<div className={classes.root}>
<Button variant="outlined" onClick={() => handleButtonClick()}>{inLibrary}</Button>
{inLibrary === 'In Library'
+6 -2
View File
@@ -35,9 +35,13 @@ function groupExtensions(extensions: IExtension[]) {
return result;
}
function extensionDefaultLangs() {
return [...defualtLangs(), 'all'];
}
export default function Extensions() {
const { setTitle, setAction } = useContext(NavbarContext);
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', defualtLangs());
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
useEffect(() => {
setTitle('Extensions');
@@ -76,7 +80,7 @@ export default function Extensions() {
<>
{
Object.entries(extensions).map(([lang, list]) => (
(['installed', ...shownLangs].indexOf(lang) !== -1
((['installed', ...shownLangs].indexOf(lang) !== -1 && (list as []).length > 0)
&& (
<React.Fragment key={lang}>
<h1 key={lang} style={{ marginLeft: 25 }}>
+12 -1
View File
@@ -16,6 +16,7 @@ export default function Manga() {
const { id } = useParams<{id: string}>();
const [manga, setManga] = useState<IManga>();
const [source, setSource] = useState<ISource>();
const [chapters, setChapters] = useState<IChapter[]>([]);
useEffect(() => {
@@ -27,6 +28,16 @@ export default function Manga() {
});
}, []);
useEffect(() => {
if (manga !== undefined) {
client.get(`/api/v1/source/${manga.sourceId}`)
.then((response) => response.data)
.then((data: ISource) => {
setSource(data);
});
}
}, [manga]);
useEffect(() => {
client.get(`/api/v1/manga/${id}/chapters`)
.then((response) => response.data)
@@ -41,7 +52,7 @@ export default function Manga() {
return (
<>
{manga && <MangaDetails manga={manga} />}
{(manga && source) && <MangaDetails manga={manga} source={source} />}
{chapterCards}
</>
);
+1
View File
@@ -23,6 +23,7 @@ interface ISource {
interface IManga {
id: number
sourceId?: string
title: string
thumbnailUrl: string
inLibrary?: boolean