mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-11-01 06:55:06 +01:00
can work with anime extensions successfully
This commit is contained in:
parent
994ae97256
commit
c17e3bd04f
@ -8,62 +8,67 @@ package suwayomi.anime
|
|||||||
* 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 io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import suwayomi.server.JavalinSetup
|
import suwayomi.anime.impl.extension.Extension.getExtensionIcon
|
||||||
|
import suwayomi.anime.impl.extension.Extension.installExtension
|
||||||
|
import suwayomi.anime.impl.extension.Extension.uninstallExtension
|
||||||
|
import suwayomi.anime.impl.extension.Extension.updateExtension
|
||||||
import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList
|
import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList
|
||||||
|
import suwayomi.server.JavalinSetup
|
||||||
|
import suwayomi.server.JavalinSetup.future
|
||||||
|
|
||||||
object AnimeAPI {
|
object AnimeAPI {
|
||||||
fun defineEndpoints(app: Javalin) {
|
fun defineEndpoints(app: Javalin) {
|
||||||
// list all extensions
|
// list all extensions
|
||||||
app.get("/api/v1/extension/list") { ctx ->
|
app.get("/api/v1/anime/extension/list") { ctx ->
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
getExtensionList()
|
getExtensionList()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// // install extension identified with "pkgName"
|
// install extension identified with "pkgName"
|
||||||
// app.get("/api/v1/extension/install/:pkgName") { ctx ->
|
app.get("/api/v1/anime/extension/install/:pkgName") { ctx ->
|
||||||
// val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
//
|
|
||||||
// ctx.json(
|
ctx.json(
|
||||||
// JavalinSetup.future {
|
JavalinSetup.future {
|
||||||
// installExtension(pkgName)
|
installExtension(pkgName)
|
||||||
// }
|
}
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // update extension identified with "pkgName"
|
// update extension identified with "pkgName"
|
||||||
// app.get("/api/v1/extension/update/:pkgName") { ctx ->
|
app.get("/api/v1/anime/extension/update/:pkgName") { ctx ->
|
||||||
// val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
//
|
|
||||||
// ctx.json(
|
ctx.json(
|
||||||
// JavalinSetup.future {
|
JavalinSetup.future {
|
||||||
// updateExtension(pkgName)
|
updateExtension(pkgName)
|
||||||
// }
|
}
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // uninstall extension identified with "pkgName"
|
// uninstall extension identified with "pkgName"
|
||||||
// app.get("/api/v1/extension/uninstall/:pkgName") { ctx ->
|
app.get("/api/v1/anime/extension/uninstall/:pkgName") { ctx ->
|
||||||
// val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
//
|
|
||||||
// uninstallExtension(pkgName)
|
uninstallExtension(pkgName)
|
||||||
// ctx.status(200)
|
ctx.status(200)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // icon for extension named `apkName`
|
// icon for extension named `apkName`
|
||||||
// app.get("/api/v1/extension/icon/:apkName") { ctx -> // TODO: move to pkgName
|
app.get("/api/v1/anime/extension/icon/:apkName") { ctx -> // TODO: move to pkgName
|
||||||
// val apkName = ctx.pathParam("apkName")
|
val apkName = ctx.pathParam("apkName")
|
||||||
//
|
|
||||||
// ctx.result(
|
ctx.result(
|
||||||
// JavalinSetup.future { getExtensionIcon(apkName) }
|
JavalinSetup.future { getExtensionIcon(apkName) }
|
||||||
// .thenApply {
|
.thenApply {
|
||||||
// ctx.header("content-type", it.second)
|
ctx.header("content-type", it.second)
|
||||||
// it.first
|
it.first
|
||||||
// }
|
}
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // list of sources
|
// // list of sources
|
||||||
// app.get("/api/v1/source/list") { ctx ->
|
// app.get("/api/v1/source/list") { ctx ->
|
||||||
|
@ -10,9 +10,9 @@ package suwayomi.anime.impl.extension
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.AnimeCatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.AnimeSource
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.AnimeSourceFactory
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
@ -125,11 +125,11 @@ object Extension {
|
|||||||
File(dexFilePath).delete()
|
File(dexFilePath).delete()
|
||||||
|
|
||||||
// collect sources from the extension
|
// collect sources from the extension
|
||||||
val sources: List<CatalogueSource> = when (val instance = loadExtensionSources(jarFilePath, className)) {
|
val sources: List<AnimeCatalogueSource> = when (val instance = loadExtensionSources(jarFilePath, className)) {
|
||||||
is Source -> listOf(instance)
|
is AnimeSource -> listOf(instance)
|
||||||
is SourceFactory -> instance.createSources()
|
is AnimeSourceFactory -> instance.createSources()
|
||||||
else -> throw RuntimeException("Unknown source class type! ${instance.javaClass}")
|
else -> throw RuntimeException("Unknown source class type! ${instance.javaClass}")
|
||||||
}.map { it as CatalogueSource }
|
}.map { it as AnimeCatalogueSource }
|
||||||
|
|
||||||
val langs = sources.map { it.lang }.toSet()
|
val langs = sources.map { it.lang }.toSet()
|
||||||
val extensionLang = when (langs.size) {
|
val extensionLang = when (langs.size) {
|
||||||
@ -246,6 +246,6 @@ object Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getExtensionIconUrl(apkName: String): String {
|
fun getExtensionIconUrl(apkName: String): String {
|
||||||
return "/api/v1/extension/icon/$apkName"
|
return "/api/v1/anime/extension/icon/$apkName"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,15 +32,14 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
|
|
||||||
object PackageTools {
|
object PackageTools {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||||
|
|
||||||
const val EXTENSION_FEATURE = "tachiyomi.extension"
|
const val EXTENSION_FEATURE = "tachiyomi.animeextension"
|
||||||
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
const val METADATA_SOURCE_CLASS = "tachiyomi.animeextension.class"
|
||||||
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
|
const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory"
|
||||||
const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
const val METADATA_NSFW = "tachiyomi.animeextension.nsfw"
|
||||||
const val LIB_VERSION_MIN = 1.3
|
const val LIB_VERSION_MIN = 1.3
|
||||||
const val LIB_VERSION_MAX = 1.3
|
const val LIB_VERSION_MAX = 1.3
|
||||||
|
|
||||||
@ -58,20 +57,20 @@ object PackageTools {
|
|||||||
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
||||||
val handler = BaksmaliBaseDexExceptionHandler()
|
val handler = BaksmaliBaseDexExceptionHandler()
|
||||||
Dex2jar
|
Dex2jar
|
||||||
.from(reader)
|
.from(reader)
|
||||||
.withExceptionHandler(handler)
|
.withExceptionHandler(handler)
|
||||||
.reUseReg(false)
|
.reUseReg(false)
|
||||||
.topoLogicalSort()
|
.topoLogicalSort()
|
||||||
.skipDebug(true)
|
.skipDebug(true)
|
||||||
.optimizeSynchronized(false)
|
.optimizeSynchronized(false)
|
||||||
.printIR(false)
|
.printIR(false)
|
||||||
.noCode(false)
|
.noCode(false)
|
||||||
.skipExceptions(false)
|
.skipExceptions(false)
|
||||||
.to(jarFilePath)
|
.to(jarFilePath)
|
||||||
if (handler.hasException()) {
|
if (handler.hasException()) {
|
||||||
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
||||||
logger.error(
|
logger.error(
|
||||||
"""
|
"""
|
||||||
Detail Error Information in File $errorFile
|
Detail Error Information in File $errorFile
|
||||||
Please report this file to one of following link if possible (any one).
|
Please report this file to one of following link if possible (any one).
|
||||||
https://sourceforge.net/p/dex2jar/tickets/
|
https://sourceforge.net/p/dex2jar/tickets/
|
||||||
@ -101,27 +100,27 @@ object PackageTools {
|
|||||||
val appTag = doc.getElementsByTagName("application").item(0)
|
val appTag = doc.getElementsByTagName("application").item(0)
|
||||||
|
|
||||||
appTag?.childNodes?.toList()
|
appTag?.childNodes?.toList()
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter {
|
.filter {
|
||||||
it.nodeType == Node.ELEMENT_NODE
|
it.nodeType == Node.ELEMENT_NODE
|
||||||
}.map {
|
}.map {
|
||||||
it as Element
|
it as Element
|
||||||
}.filter {
|
}.filter {
|
||||||
it.tagName == "meta-data"
|
it.tagName == "meta-data"
|
||||||
}.forEach {
|
}.forEach {
|
||||||
putString(
|
putString(
|
||||||
it.attributes.getNamedItem("android:name").nodeValue,
|
it.attributes.getNamedItem("android:name").nodeValue,
|
||||||
it.attributes.getNamedItem("android:value").nodeValue
|
it.attributes.getNamedItem("android:value").nodeValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signatures = (
|
signatures = (
|
||||||
parsed.apkSingers.flatMap { it.certificateMetas }
|
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||||
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
|
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
|
||||||
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
||||||
.map { Signature(it.data) }.toTypedArray()
|
.map { Signature(it.data) }.toTypedArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.future.future
|
import kotlinx.coroutines.future.future
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
|
import suwayomi.anime.AnimeAPI
|
||||||
import suwayomi.server.util.Browser
|
import suwayomi.server.util.Browser
|
||||||
import suwayomi.tachidesk.TachideskAPI
|
import suwayomi.tachidesk.TachideskAPI
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -75,5 +76,6 @@ object JavalinSetup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TachideskAPI.defineEndpoints(app)
|
TachideskAPI.defineEndpoints(app)
|
||||||
|
AnimeAPI.defineEndpoints(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package suwayomi.server.database.migration
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IdTable
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import suwayomi.server.database.migration.lib.Migration
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
class M0004_AnimeTablesBatch1 : Migration() {
|
||||||
|
private object AnimeExtensionTable : IntIdTable() {
|
||||||
|
val apkName = varchar("apk_name", 1024)
|
||||||
|
|
||||||
|
// default is the local source icon from tachiyomi
|
||||||
|
val iconUrl = varchar("icon_url", 2048)
|
||||||
|
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp")
|
||||||
|
|
||||||
|
val name = varchar("name", 128)
|
||||||
|
val pkgName = varchar("pkg_name", 128)
|
||||||
|
val versionName = varchar("version_name", 16)
|
||||||
|
val versionCode = integer("version_code")
|
||||||
|
val lang = varchar("lang", 10)
|
||||||
|
val isNsfw = bool("is_nsfw")
|
||||||
|
|
||||||
|
val isInstalled = bool("is_installed").default(false)
|
||||||
|
val hasUpdate = bool("has_update").default(false)
|
||||||
|
val isObsolete = bool("is_obsolete").default(false)
|
||||||
|
|
||||||
|
val classFQName = varchar("class_name", 1024).default("") // fully qualified name
|
||||||
|
}
|
||||||
|
|
||||||
|
private object AnimeSourceTable : IdTable<Long>() {
|
||||||
|
override val id = long("id").entityId()
|
||||||
|
val name = varchar("name", 128)
|
||||||
|
val lang = varchar("lang", 10)
|
||||||
|
val extension = reference("extension", suwayomi.anime.model.table.AnimeExtensionTable)
|
||||||
|
val partOfFactorySource = bool("part_of_factory_source").default(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
transaction {
|
||||||
|
SchemaUtils.create(
|
||||||
|
AnimeExtensionTable,
|
||||||
|
AnimeSourceTable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ package suwayomi.tachidesk
|
|||||||
* 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 io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import suwayomi.server.JavalinSetup
|
|
||||||
import suwayomi.server.JavalinSetup.future
|
import suwayomi.server.JavalinSetup.future
|
||||||
import suwayomi.server.impl.About
|
import suwayomi.server.impl.About
|
||||||
import suwayomi.tachidesk.impl.Category
|
import suwayomi.tachidesk.impl.Category
|
||||||
@ -47,7 +46,7 @@ object TachideskAPI {
|
|||||||
// list all extensions
|
// list all extensions
|
||||||
app.get("/api/v1/extension/list") { ctx ->
|
app.get("/api/v1/extension/list") { ctx ->
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
getExtensionList()
|
getExtensionList()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -58,7 +57,7 @@ object TachideskAPI {
|
|||||||
val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
|
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
installExtension(pkgName)
|
installExtension(pkgName)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -69,7 +68,7 @@ object TachideskAPI {
|
|||||||
val pkgName = ctx.pathParam("pkgName")
|
val pkgName = ctx.pathParam("pkgName")
|
||||||
|
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
updateExtension(pkgName)
|
updateExtension(pkgName)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -88,7 +87,7 @@ object TachideskAPI {
|
|||||||
val apkName = ctx.pathParam("apkName")
|
val apkName = ctx.pathParam("apkName")
|
||||||
|
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future { getExtensionIcon(apkName) }
|
future { getExtensionIcon(apkName) }
|
||||||
.thenApply {
|
.thenApply {
|
||||||
ctx.header("content-type", it.second)
|
ctx.header("content-type", it.second)
|
||||||
it.first
|
it.first
|
||||||
@ -112,7 +111,7 @@ object TachideskAPI {
|
|||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
getMangaList(sourceId, pageNum, popular = true)
|
getMangaList(sourceId, pageNum, popular = true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -123,7 +122,7 @@ object TachideskAPI {
|
|||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
getMangaList(sourceId, pageNum, popular = false)
|
getMangaList(sourceId, pageNum, popular = false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -135,7 +134,7 @@ object TachideskAPI {
|
|||||||
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
||||||
|
|
||||||
ctx.json(
|
ctx.json(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
getManga(mangaId, onlineFetch)
|
getManga(mangaId, onlineFetch)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -146,7 +145,7 @@ object TachideskAPI {
|
|||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future { getMangaThumbnail(mangaId) }
|
future { getMangaThumbnail(mangaId) }
|
||||||
.thenApply {
|
.thenApply {
|
||||||
ctx.header("content-type", it.second)
|
ctx.header("content-type", it.second)
|
||||||
it.first
|
it.first
|
||||||
@ -182,14 +181,14 @@ object TachideskAPI {
|
|||||||
|
|
||||||
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean()
|
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean()
|
||||||
|
|
||||||
ctx.json(JavalinSetup.future { getChapterList(mangaId, onlineFetch) })
|
ctx.json(future { getChapterList(mangaId, onlineFetch) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to display a chapter, get a chapter in order to show it's pages
|
// used to display a chapter, get a chapter in order to show it's pages
|
||||||
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
|
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
|
||||||
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
ctx.json(JavalinSetup.future { getChapter(chapterIndex, mangaId) })
|
ctx.json(future { getChapter(chapterIndex, mangaId) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// used to modify a chapter's parameters
|
// used to modify a chapter's parameters
|
||||||
@ -214,7 +213,7 @@ object TachideskAPI {
|
|||||||
val index = ctx.pathParam("index").toInt()
|
val index = ctx.pathParam("index").toInt()
|
||||||
|
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future { getPageImage(mangaId, chapterIndex, index) }
|
future { getPageImage(mangaId, chapterIndex, index) }
|
||||||
.thenApply {
|
.thenApply {
|
||||||
ctx.header("content-type", it.second)
|
ctx.header("content-type", it.second)
|
||||||
it.first
|
it.first
|
||||||
@ -243,7 +242,7 @@ object TachideskAPI {
|
|||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
ctx.json(JavalinSetup.future { sourceSearch(sourceId, searchTerm, pageNum) })
|
ctx.json(future { sourceSearch(sourceId, searchTerm, pageNum) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// source filter list
|
// source filter list
|
||||||
@ -257,7 +256,7 @@ object TachideskAPI {
|
|||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future { addMangaToLibrary(mangaId) }
|
future { addMangaToLibrary(mangaId) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +265,7 @@ object TachideskAPI {
|
|||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future { removeMangaFromLibrary(mangaId) }
|
future { removeMangaFromLibrary(mangaId) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +334,7 @@ object TachideskAPI {
|
|||||||
// expects a Tachiyomi legacy backup json as a file upload, the file must be named "backup.json"
|
// expects a Tachiyomi legacy backup json as a file upload, the file must be named "backup.json"
|
||||||
app.post("/api/v1/backup/legacy/import/file") { ctx ->
|
app.post("/api/v1/backup/legacy/import/file") { ctx ->
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content)
|
restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -345,7 +344,7 @@ object TachideskAPI {
|
|||||||
app.get("/api/v1/backup/legacy/export") { ctx ->
|
app.get("/api/v1/backup/legacy/export") { ctx ->
|
||||||
ctx.contentType("application/json")
|
ctx.contentType("application/json")
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
createLegacyBackup(
|
createLegacyBackup(
|
||||||
BackupFlags(
|
BackupFlags(
|
||||||
includeManga = true,
|
includeManga = true,
|
||||||
@ -367,7 +366,7 @@ object TachideskAPI {
|
|||||||
|
|
||||||
ctx.header("Content-Disposition", "attachment; filename=\"tachidesk_$currentDate.json\"")
|
ctx.header("Content-Disposition", "attachment; filename=\"tachidesk_$currentDate.json\"")
|
||||||
ctx.result(
|
ctx.result(
|
||||||
JavalinSetup.future {
|
future {
|
||||||
createLegacyBackup(
|
createLegacyBackup(
|
||||||
BackupFlags(
|
BackupFlags(
|
||||||
includeManga = true,
|
includeManga = true,
|
||||||
|
@ -15,8 +15,8 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.conf.global
|
import org.kodein.di.conf.global
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.impl.util.PackageTools.loadExtensionSources
|
|
||||||
import suwayomi.server.ApplicationDirs
|
import suwayomi.server.ApplicationDirs
|
||||||
|
import suwayomi.tachidesk.impl.util.PackageTools.loadExtensionSources
|
||||||
import suwayomi.tachidesk.model.table.ExtensionTable
|
import suwayomi.tachidesk.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.model.table.SourceTable
|
import suwayomi.tachidesk.model.table.SourceTable
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
@ -32,7 +32,6 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
|
|
||||||
object PackageTools {
|
object PackageTools {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||||
|
@ -7,27 +7,29 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router, Redirect, Route, Switch,
|
BrowserRouter as Router, Switch,
|
||||||
|
Route,
|
||||||
|
Redirect,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { Container } from '@material-ui/core';
|
import { Container } from '@material-ui/core';
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||||
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
|
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
|
||||||
|
import NavBar from 'components/navbar/NavBar';
|
||||||
import NavBar from './components/navbar/NavBar';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import Sources from './screens/Sources';
|
import DarkTheme from 'context/DarkTheme';
|
||||||
import Extensions from './screens/Extensions';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import SourceMangas from './screens/SourceMangas';
|
import Sources from 'screens/manga/Sources';
|
||||||
import Manga from './screens/Manga';
|
import Settings from 'screens/Settings';
|
||||||
import Reader from './screens/Reader';
|
import About from 'screens/settings/About';
|
||||||
import Search from './screens/SearchSingle';
|
import Categories from 'screens/settings/Categories';
|
||||||
import NavbarContext from './context/NavbarContext';
|
import Backup from 'screens/settings/Backup';
|
||||||
import DarkTheme from './context/DarkTheme';
|
import Library from 'screens/manga/Library';
|
||||||
import Library from './screens/Library';
|
import SearchSingle from 'screens/manga/SearchSingle';
|
||||||
import Settings from './screens/Settings';
|
import Manga from 'screens/manga/Manga';
|
||||||
import Categories from './screens/settings/Categories';
|
import MangaExtensions from 'screens/manga/MangaExtensions';
|
||||||
import Backup from './screens/settings/Backup';
|
import SourceMangas from 'screens/manga/SourceMangas';
|
||||||
import useLocalStorage from './util/useLocalStorage';
|
import Reader from 'screens/manga/Reader';
|
||||||
import About from './screens/settings/About';
|
import AnimeExtensions from 'screens/anime/AnimeExtensions';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [title, setTitle] = useState<string>('Tachidesk');
|
const [title, setTitle] = useState<string>('Tachidesk');
|
||||||
@ -78,11 +80,37 @@ export default function App() {
|
|||||||
style={{ paddingTop: '64px' }}
|
style={{ paddingTop: '64px' }}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/sources/:sourceId/search/">
|
{/* general routes */}
|
||||||
<Search />
|
<Route
|
||||||
|
exact
|
||||||
|
path="/"
|
||||||
|
render={() => (
|
||||||
|
<Redirect to="/library" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route path="/settings/about">
|
||||||
|
<About />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/extensions">
|
<Route path="/settings/categories">
|
||||||
<Extensions />
|
<Categories />
|
||||||
|
</Route>
|
||||||
|
<Route path="/settings/backup">
|
||||||
|
<Backup />
|
||||||
|
</Route>
|
||||||
|
<Route path="/settings">
|
||||||
|
<DarkTheme.Provider value={darkThemeContext}>
|
||||||
|
<Settings />
|
||||||
|
</DarkTheme.Provider>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Manga Routes */}
|
||||||
|
|
||||||
|
<Route path="/sources/:sourceId/search/">
|
||||||
|
<SearchSingle />
|
||||||
|
</Route>
|
||||||
|
<Route path="/manga/extensions">
|
||||||
|
<MangaExtensions />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/sources/:sourceId/popular/">
|
<Route path="/sources/:sourceId/popular/">
|
||||||
<SourceMangas popular />
|
<SourceMangas popular />
|
||||||
@ -102,36 +130,20 @@ export default function App() {
|
|||||||
<Route path="/library">
|
<Route path="/library">
|
||||||
<Library />
|
<Library />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/settings/about">
|
|
||||||
<About />
|
|
||||||
</Route>
|
|
||||||
<Route path="/settings/categories">
|
|
||||||
<Categories />
|
|
||||||
</Route>
|
|
||||||
<Route path="/settings/backup">
|
|
||||||
<Backup />
|
|
||||||
</Route>
|
|
||||||
<Route path="/settings">
|
|
||||||
<DarkTheme.Provider value={darkThemeContext}>
|
|
||||||
<Settings />
|
|
||||||
</DarkTheme.Provider>
|
|
||||||
</Route>
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
path="/manga/:mangaId/chapter/:chapterIndex"
|
||||||
path="/"
|
// passing a key re-mounts the reader when changing chapters
|
||||||
render={() => (
|
render={
|
||||||
<Redirect to="/library" />
|
(props:any) => <Reader key={props.match.params.chapterIndex} />
|
||||||
)}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Anime Routes */}
|
||||||
|
<Route path="/anime/extensions">
|
||||||
|
<AnimeExtensions />
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Container>
|
</Container>
|
||||||
<Switch>
|
|
||||||
<Route
|
|
||||||
path="/manga/:mangaId/chapter/:chapterIndex"
|
|
||||||
// passing a key re-mounts the reader when changing chapters
|
|
||||||
render={(props:any) => <Reader key={props.match.params.chapterIndex} />}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</NavbarContext.Provider>
|
</NavbarContext.Provider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Router>
|
</Router>
|
||||||
|
@ -55,12 +55,20 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
|
|||||||
<ListItemText primary="Library" />
|
<ListItemText primary="Library" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/extensions" style={{ color: 'inherit', textDecoration: 'none' }}>
|
<Link to="/manga/extensions" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
<ListItem button key="Extensions">
|
<ListItem button key="Extensions">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<ExtensionIcon />
|
<ExtensionIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Extensions" />
|
<ListItemText primary="Manga Extensions" />
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
|
<Link to="/anime/extensions" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<ListItem button key="Extensions">
|
||||||
|
<ListItemIcon>
|
||||||
|
<ExtensionIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Anime Extensions" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/sources" style={{ color: 'inherit', textDecoration: 'none' }}>
|
<Link to="/sources" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
149
webUI/react/src/components/anime/ExtensionCard.tsx
Normal file
149
webUI/react/src/components/anime/ExtensionCard.tsx
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* 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 } from '@material-ui/core/styles';
|
||||||
|
import Card from '@material-ui/core/Card';
|
||||||
|
import CardContent from '@material-ui/core/CardContent';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import client from 'util/client';
|
||||||
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
bullet: {
|
||||||
|
display: 'inline-block',
|
||||||
|
margin: '0 2px',
|
||||||
|
transform: 'scale(0.8)',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
width: theme.spacing(7),
|
||||||
|
height: theme.spacing(7),
|
||||||
|
flex: '0 0 auto',
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
extension: IExtension
|
||||||
|
notifyInstall: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ExtensionCard(props: IProps) {
|
||||||
|
const {
|
||||||
|
extension: {
|
||||||
|
name, lang, versionName, installed, hasUpdate, obsolete, pkgName, iconUrl,
|
||||||
|
},
|
||||||
|
notifyInstall,
|
||||||
|
} = props;
|
||||||
|
const [installedState, setInstalledState] = useState<string>(
|
||||||
|
() => {
|
||||||
|
if (obsolete) { return 'obsolete'; }
|
||||||
|
if (hasUpdate) { return 'update'; }
|
||||||
|
return (installed ? 'uninstall' : 'install');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
const langPress = lang === 'all' ? 'All' : lang.toUpperCase();
|
||||||
|
|
||||||
|
function install() {
|
||||||
|
setInstalledState('installing');
|
||||||
|
client.get(`/api/v1/anime/extension/install/${pkgName}`)
|
||||||
|
.then(() => {
|
||||||
|
setInstalledState('uninstall');
|
||||||
|
notifyInstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
setInstalledState('updating');
|
||||||
|
client.get(`/api/v1/anime/extension/update/${pkgName}`)
|
||||||
|
.then(() => {
|
||||||
|
setInstalledState('uninstall');
|
||||||
|
notifyInstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uninstall() {
|
||||||
|
setInstalledState('uninstalling');
|
||||||
|
client.get(`/api/v1/anime/extension/uninstall/${pkgName}`)
|
||||||
|
.then(() => {
|
||||||
|
// setInstalledState('install');
|
||||||
|
notifyInstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleButtonClick() {
|
||||||
|
switch (installedState) {
|
||||||
|
case 'install':
|
||||||
|
install();
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
update();
|
||||||
|
break;
|
||||||
|
case 'obsolete':
|
||||||
|
uninstall();
|
||||||
|
setTimeout(() => window.location.reload(), 3000);
|
||||||
|
break;
|
||||||
|
case 'uninstall':
|
||||||
|
uninstall();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent className={classes.root}>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<Avatar
|
||||||
|
variant="rounded"
|
||||||
|
className={classes.icon}
|
||||||
|
alt={name}
|
||||||
|
src={serverAddress + iconUrl}
|
||||||
|
/>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Typography variant="h5" component="h2">
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" display="block" gutterBottom>
|
||||||
|
{langPress}
|
||||||
|
{' '}
|
||||||
|
{versionName}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
style={{ color: installedState === 'obsolete' ? 'red' : 'inherit' }}
|
||||||
|
onClick={() => handleButtonClick()}
|
||||||
|
>
|
||||||
|
{installedState}
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@ -15,7 +15,7 @@ import Dialog from '@material-ui/core/Dialog';
|
|||||||
import Checkbox from '@material-ui/core/Checkbox';
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
import FormGroup from '@material-ui/core/FormGroup';
|
import FormGroup from '@material-ui/core/FormGroup';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => createStyles({
|
const useStyles = makeStyles(() => createStyles({
|
||||||
paper: {
|
paper: {
|
@ -16,7 +16,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import Menu from '@material-ui/core/Menu';
|
import Menu from '@material-ui/core/Menu';
|
||||||
import MenuItem from '@material-ui/core/MenuItem';
|
import MenuItem from '@material-ui/core/MenuItem';
|
||||||
import BookmarkIcon from '@material-ui/icons/Bookmark';
|
import BookmarkIcon from '@material-ui/icons/Bookmark';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
@ -12,8 +12,8 @@ import CardContent from '@material-ui/core/CardContent';
|
|||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
@ -17,8 +17,8 @@ import IconButton from '@material-ui/core/IconButton';
|
|||||||
import FilterListIcon from '@material-ui/icons/FilterList';
|
import FilterListIcon from '@material-ui/icons/FilterList';
|
||||||
import { List, ListItemSecondaryAction, ListItemText } from '@material-ui/core';
|
import { List, ListItemSecondaryAction, ListItemText } from '@material-ui/core';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
import { langCodeToName } from '../util/language';
|
import { langCodeToName } from 'util/language';
|
||||||
import cloneObject from '../util/cloneObject';
|
import cloneObject from 'util/cloneObject';
|
||||||
|
|
||||||
const useStyles = makeStyles(() => createStyles({
|
const useStyles = makeStyles(() => createStyles({
|
||||||
paper: {
|
paper: {
|
@ -13,7 +13,7 @@ import CardMedia from '@material-ui/core/CardMedia';
|
|||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Grid } from '@material-ui/core';
|
import { Grid } from '@material-ui/core';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: {
|
root: {
|
@ -13,9 +13,9 @@ import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
|
|||||||
import FilterListIcon from '@material-ui/icons/FilterList';
|
import FilterListIcon from '@material-ui/icons/FilterList';
|
||||||
import PublicIcon from '@material-ui/icons/Public';
|
import PublicIcon from '@material-ui/icons/Public';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import CategorySelect from './CategorySelect';
|
import CategorySelect from './CategorySelect';
|
||||||
|
|
||||||
const useStyles = (inLibrary: string) => makeStyles((theme: Theme) => ({
|
const useStyles = (inLibrary: string) => makeStyles((theme: Theme) => ({
|
@ -12,8 +12,8 @@ import CardContent from '@material-ui/core/CardContent';
|
|||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import { langCodeToName } from '../util/language';
|
import { langCodeToName } from 'util/language';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
112
webUI/react/src/screens/anime/AnimeExtensions.tsx
Normal file
112
webUI/react/src/screens/anime/AnimeExtensions.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* 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 ExtensionCard from 'components/anime/ExtensionCard';
|
||||||
|
import NavbarContext from 'context/NavbarContext';
|
||||||
|
import client from 'util/client';
|
||||||
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
import ExtensionLangSelect from 'components/manga/ExtensionLangSelect';
|
||||||
|
import { defualtLangs, langCodeToName, langSortCmp } from 'util/language';
|
||||||
|
|
||||||
|
const allLangs: string[] = [];
|
||||||
|
|
||||||
|
function groupExtensions(extensions: IExtension[]) {
|
||||||
|
allLangs.length = 0; // empty the array
|
||||||
|
const result = { installed: [], 'updates pending': [] } as any;
|
||||||
|
extensions.sort((a, b) => ((a.apkName > b.apkName) ? 1 : -1));
|
||||||
|
|
||||||
|
extensions.forEach((extension) => {
|
||||||
|
if (result[extension.lang] === undefined) {
|
||||||
|
result[extension.lang] = [];
|
||||||
|
if (extension.lang !== 'all') { allLangs.push(extension.lang); }
|
||||||
|
}
|
||||||
|
if (extension.installed) {
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
result['updates pending'].push(extension);
|
||||||
|
} else {
|
||||||
|
result.installed.push(extension);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[extension.lang].push(extension);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// put english first for convience
|
||||||
|
allLangs.sort(langSortCmp);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extensionDefaultLangs() {
|
||||||
|
return [...defualtLangs(), 'all'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AnimeExtensions() {
|
||||||
|
const { setTitle, setAction } = useContext(NavbarContext);
|
||||||
|
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle('Extensions');
|
||||||
|
setAction(
|
||||||
|
<ExtensionLangSelect
|
||||||
|
shownLangs={shownLangs}
|
||||||
|
setShownLangs={setShownLangs}
|
||||||
|
allLangs={allLangs}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}, [shownLangs]);
|
||||||
|
|
||||||
|
const [extensionsRaw, setExtensionsRaw] = useState<IExtension[]>([]);
|
||||||
|
const [extensions, setExtensions] = useState<any>({});
|
||||||
|
|
||||||
|
const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack
|
||||||
|
const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
client.get('/api/v1/anime/extension/list')
|
||||||
|
.then((response) => response.data)
|
||||||
|
.then((data) => setExtensionsRaw(data));
|
||||||
|
}, [updateTriggerHolder]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (extensionsRaw.length > 0) {
|
||||||
|
const groupedExtension = groupExtensions(extensionsRaw);
|
||||||
|
setExtensions(groupedExtension);
|
||||||
|
}
|
||||||
|
}, [extensionsRaw]);
|
||||||
|
|
||||||
|
if (Object.entries(extensions).length === 0) {
|
||||||
|
return <h3>loading...</h3>;
|
||||||
|
}
|
||||||
|
const groupsToShow = ['updates pending', 'installed', ...shownLangs];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
Object.entries(extensions).map(([lang, list]) => (
|
||||||
|
((groupsToShow.indexOf(lang) !== -1 && (list as []).length > 0)
|
||||||
|
&& (
|
||||||
|
<React.Fragment key={lang}>
|
||||||
|
<h1 key={lang} style={{ marginLeft: 25 }}>
|
||||||
|
{langCodeToName(lang)}
|
||||||
|
</h1>
|
||||||
|
{(list as IExtension[]).map((it) => (
|
||||||
|
<ExtensionCard
|
||||||
|
key={it.apkName}
|
||||||
|
extension={it}
|
||||||
|
notifyInstall={() => {
|
||||||
|
triggerUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
import { Tab, Tabs } from '@material-ui/core';
|
import { Tab, Tabs } from '@material-ui/core';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from 'components/manga/MangaGrid';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import cloneObject from '../util/cloneObject';
|
import cloneObject from 'util/cloneObject';
|
||||||
|
|
||||||
interface IMangaCategory {
|
interface IMangaCategory {
|
||||||
category: ICategory
|
category: ICategory
|
@ -9,12 +9,12 @@ import React, { useEffect, useState, useContext } from 'react';
|
|||||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import ChapterCard from '../components/ChapterCard';
|
import ChapterCard from 'components/manga/ChapterCard';
|
||||||
import MangaDetails from '../components/MangaDetails';
|
import MangaDetails from 'components/manga/MangaDetails';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import LoadingPlaceholder from '../components/LoadingPlaceholder';
|
import LoadingPlaceholder from 'components/LoadingPlaceholder';
|
||||||
import makeToast from '../components/Toast';
|
import makeToast from 'components/Toast';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
root: {
|
root: {
|
@ -6,12 +6,12 @@
|
|||||||
* 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, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import ExtensionCard from '../components/ExtensionCard';
|
import ExtensionCard from 'components/manga/ExtensionCard';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import ExtensionLangSelect from '../components/ExtensionLangSelect';
|
import ExtensionLangSelect from 'components/manga/ExtensionLangSelect';
|
||||||
import { defualtLangs, langCodeToName, langSortCmp } from '../util/language';
|
import { defualtLangs, langCodeToName, langSortCmp } from 'util/language';
|
||||||
|
|
||||||
const allLangs: string[] = [];
|
const allLangs: string[] = [];
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ function extensionDefaultLangs() {
|
|||||||
return [...defualtLangs(), 'all'];
|
return [...defualtLangs(), 'all'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Extensions() {
|
export default function MangaExtensions() {
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const { setTitle, setAction } = useContext(NavbarContext);
|
||||||
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
|
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
|
||||||
|
|
@ -9,15 +9,15 @@ import CircularProgress from '@material-ui/core/CircularProgress';
|
|||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import HorizontalPager from '../components/reader/pager/HorizontalPager';
|
import HorizontalPager from 'components/manga/reader/pager/HorizontalPager';
|
||||||
import PageNumber from '../components/reader/PageNumber';
|
import PageNumber from 'components/manga/reader/PageNumber';
|
||||||
import WebtoonPager from '../components/reader/pager/PagedPager';
|
import WebtoonPager from 'components/manga/reader/pager/PagedPager';
|
||||||
import VerticalPager from '../components/reader/pager/VerticalPager';
|
import VerticalPager from 'components/manga/reader/pager/VerticalPager';
|
||||||
import ReaderNavBar, { defaultReaderSettings } from '../components/navbar/ReaderNavBar';
|
import ReaderNavBar, { defaultReaderSettings } from 'components/navbar/ReaderNavBar';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import cloneObject from '../util/cloneObject';
|
import cloneObject from 'util/cloneObject';
|
||||||
|
|
||||||
const useStyles = (settings: IReaderSettings) => makeStyles({
|
const useStyles = (settings: IReaderSettings) => makeStyles({
|
||||||
root: {
|
root: {
|
@ -10,9 +10,9 @@ import { makeStyles } from '@material-ui/core/styles';
|
|||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from 'components/manga/MangaGrid';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from 'components/manga/MangaGrid';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
|
|
||||||
export default function SourceMangas(props: { popular: boolean }) {
|
export default function SourceMangas(props: { popular: boolean }) {
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const { setTitle, setAction } = useContext(NavbarContext);
|
@ -6,12 +6,12 @@
|
|||||||
* 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, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import ExtensionLangSelect from '../components/ExtensionLangSelect';
|
import ExtensionLangSelect from 'components/manga/ExtensionLangSelect';
|
||||||
import SourceCard from '../components/SourceCard';
|
import SourceCard from 'components/manga/SourceCard';
|
||||||
import NavbarContext from '../context/NavbarContext';
|
import NavbarContext from 'context/NavbarContext';
|
||||||
import client from '../util/client';
|
import client from 'util/client';
|
||||||
import { defualtLangs, langCodeToName, langSortCmp } from '../util/language';
|
import { defualtLangs, langCodeToName, langSortCmp } from 'util/language';
|
||||||
import useLocalStorage from '../util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
|
||||||
function sourceToLangList(sources: ISource[]) {
|
function sourceToLangList(sources: ISource[]) {
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./src",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
|
Loading…
Reference in New Issue
Block a user