Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c76ad9b74 | |||
| 7d1c63e181 | |||
| 6401b946b6 | |||
| 9a61f58043 | |||
| b854fdeadb | |||
| 6eb4f1ba88 | |||
| ded5e3a73a |
@@ -1,14 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
mkdir -p repo/
|
|
||||||
|
|
||||||
# Get last commit message
|
# Get last commit message
|
||||||
last_commit_log=$(git log -1 --pretty=format:"%s")
|
last_commit_log=$(git log -1 --pretty=format:"%s")
|
||||||
echo "last commit log: $last_commit_log"
|
echo "last commit log: $last_commit_log"
|
||||||
|
|
||||||
filter_count=$(echo "$last_commit_log" | grep -c "[RELEASE CI]" )
|
filter_count=$(echo "$last_commit_log" | grep -c '\[RELEASE CI\]' )
|
||||||
|
echo "count is: $filter_count"
|
||||||
|
|
||||||
if [ "$filter_count" -gt 0 ]; then
|
if [ "$filter_count" -gt 0 ]; then
|
||||||
|
mkdir -p repo/
|
||||||
cp server/build/Tachidesk-*.jar repo/
|
cp server/build/Tachidesk-*.jar repo/
|
||||||
fi
|
fi
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Tachidesk
|
# Tachidesk
|
||||||
A free and open source manga reader than runs extensions built for [Tachiyomi](https://tachiyomi.org/) which runs on desktop operating systems.
|
A free and open source manga reader that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
|
||||||
|
|
||||||
|
Tachidesk is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it.
|
||||||
|
|
||||||
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
|
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ plugins {
|
|||||||
// id("org.jetbrains.kotlin.jvm") version "1.4.21"
|
// id("org.jetbrains.kotlin.jvm") version "1.4.21"
|
||||||
application
|
application
|
||||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||||
|
id("org.jmailen.kotlinter") version "3.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
val TachideskVersion = "v0.0.2"
|
val TachideskVersion = "v0.0.3"
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -139,9 +140,16 @@ tasks {
|
|||||||
|
|
||||||
tasks.withType<ShadowJar> {
|
tasks.withType<ShadowJar> {
|
||||||
destinationDir = File("$rootDir/server/build")
|
destinationDir = File("$rootDir/server/build")
|
||||||
|
dependsOn("lintKotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("processResources") {
|
tasks.named("processResources") {
|
||||||
dependsOn(":webUI:copyBuild")
|
dependsOn(":webUI:copyBuild")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named("run") {
|
||||||
|
dependsOn("formatKotlin", "lintKotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import com.google.gson.Gson
|
|||||||
// import eu.kanade.tachiyomi.data.track.TrackManager
|
// import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
// import eu.kanade.tachiyomi.extension.ExtensionManager
|
// import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.api.*
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
|
import uy.kohesive.injekt.api.addSingleton
|
||||||
|
import uy.kohesive.injekt.api.addSingletonFactory
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class AppModule(val app: Application) : InjektModule {
|
class AppModule(val app: Application) : InjektModule {
|
||||||
|
|
||||||
@@ -56,11 +59,9 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rxAsync { get<DatabaseHelper>() }
|
// rxAsync { get<DatabaseHelper>() }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rxAsync(block: () -> Unit) {
|
private fun rxAsync(block: () -> Unit) {
|
||||||
Observable.fromCallable { block() }.subscribeOn(Schedulers.computation()).subscribe()
|
Observable.fromCallable { block() }.subscribeOn(Schedulers.computation()).subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,8 @@ package eu.kanade.tachiyomi.extension.util
|
|||||||
// import android.content.pm.PackageInfo
|
// import android.content.pm.PackageInfo
|
||||||
// import android.content.pm.PackageManager
|
// import android.content.pm.PackageManager
|
||||||
// import dalvik.system.PathClassLoader
|
// import dalvik.system.PathClassLoader
|
||||||
import eu.kanade.tachiyomi.annoations.Nsfw
|
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
// import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
|
||||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
|
||||||
// import eu.kanade.tachiyomi.util.lang.Hash
|
// import eu.kanade.tachiyomi.util.lang.Hash
|
||||||
// import kotlinx.coroutines.async
|
// import kotlinx.coroutines.async
|
||||||
// import kotlinx.coroutines.runBlocking
|
// import kotlinx.coroutines.runBlocking
|
||||||
|
|||||||
@@ -9,22 +9,15 @@ package eu.kanade.tachiyomi.network
|
|||||||
// import android.webkit.WebView
|
// import android.webkit.WebView
|
||||||
// import android.widget.Toast
|
// import android.widget.Toast
|
||||||
// import eu.kanade.tachiyomi.R
|
// import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
// import eu.kanade.tachiyomi.util.lang.launchUI
|
// import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
// import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
// import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||||
// import eu.kanade.tachiyomi.util.system.WebViewUtil
|
// import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
// import eu.kanade.tachiyomi.util.system.isOutdated
|
// import eu.kanade.tachiyomi.util.system.isOutdated
|
||||||
// import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
// import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
// import eu.kanade.tachiyomi.util.system.toast
|
// import eu.kanade.tachiyomi.util.system.toast
|
||||||
import okhttp3.Cookie
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
// import uy.kohesive.injekt.injectLazy
|
// import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class CloudflareInterceptor() : Interceptor {
|
class CloudflareInterceptor() : Interceptor {
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,11 @@ package eu.kanade.tachiyomi.network
|
|||||||
// import eu.kanade.tachiyomi.BuildConfig
|
// import eu.kanade.tachiyomi.BuildConfig
|
||||||
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import okhttp3.Cache
|
|
||||||
// import okhttp3.HttpUrl.Companion.toHttpUrl
|
// import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
// import okhttp3.dnsoverhttps.DnsOverHttps
|
// import okhttp3.dnsoverhttps.DnsOverHttps
|
||||||
// import okhttp3.logging.HttpLoggingInterceptor
|
// import okhttp3.logging.HttpLoggingInterceptor
|
||||||
// import uy.kohesive.injekt.injectLazy
|
// import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NetworkHelper(context: Context) {
|
class NetworkHelper(context: Context) {
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ package eu.kanade.tachiyomi.network
|
|||||||
|
|
||||||
// import kotlinx.coroutines.suspendCancellableCoroutine
|
// import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Producer
|
import rx.Producer
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
|
|
||||||
fun Call.asObservable(): Observable<Response> {
|
fun Call.asObservable(): Observable<Response> {
|
||||||
return Observable.unsafeCreate { subscriber ->
|
return Observable.unsafeCreate { subscriber ->
|
||||||
|
|||||||
@@ -2,7 +2,18 @@ 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.*
|
import ir.armor.tachidesk.util.applicationSetup
|
||||||
|
import ir.armor.tachidesk.util.getChapterList
|
||||||
|
import ir.armor.tachidesk.util.getExtensionList
|
||||||
|
import ir.armor.tachidesk.util.getManga
|
||||||
|
import ir.armor.tachidesk.util.getMangaList
|
||||||
|
import ir.armor.tachidesk.util.getPages
|
||||||
|
import ir.armor.tachidesk.util.getSource
|
||||||
|
import ir.armor.tachidesk.util.getSourceList
|
||||||
|
import ir.armor.tachidesk.util.installAPK
|
||||||
|
import ir.armor.tachidesk.util.sourceFilters
|
||||||
|
import ir.armor.tachidesk.util.sourceGlobalSearch
|
||||||
|
import ir.armor.tachidesk.util.sourceSearch
|
||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.conf.global
|
import org.kodein.di.conf.global
|
||||||
import xyz.nulldev.androidcompat.AndroidCompat
|
import xyz.nulldev.androidcompat.AndroidCompat
|
||||||
@@ -23,6 +34,10 @@ class Main {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
// System.getProperties()["proxySet"] = "true"
|
||||||
|
// System.getProperties()["socksProxyHost"] = "127.0.0.1"
|
||||||
|
// System.getProperties()["socksProxyPort"] = "2020"
|
||||||
|
|
||||||
// make sure everything we need exists
|
// make sure everything we need exists
|
||||||
applicationSetup()
|
applicationSetup()
|
||||||
|
|
||||||
@@ -35,7 +50,6 @@ class Main {
|
|||||||
// start app
|
// start app
|
||||||
androidCompat.startApp(App())
|
androidCompat.startApp(App())
|
||||||
|
|
||||||
|
|
||||||
val app = Javalin.create { config ->
|
val app = Javalin.create { config ->
|
||||||
try {
|
try {
|
||||||
this::class.java.classLoader.getResource("/react/index.html")
|
this::class.java.classLoader.getResource("/react/index.html")
|
||||||
@@ -46,8 +60,6 @@ class Main {
|
|||||||
}
|
}
|
||||||
}.start(4567)
|
}.start(4567)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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", "*")
|
||||||
@@ -57,7 +69,6 @@ class Main {
|
|||||||
ctx.json(getExtensionList())
|
ctx.json(getExtensionList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
app.get("/api/v1/extension/install/:apkName") { ctx ->
|
app.get("/api/v1/extension/install/:apkName") { ctx ->
|
||||||
val apkName = ctx.pathParam("apkName")
|
val apkName = ctx.pathParam("apkName")
|
||||||
println(apkName)
|
println(apkName)
|
||||||
@@ -69,6 +80,11 @@ class Main {
|
|||||||
ctx.json(getSourceList())
|
ctx.json(getSourceList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/source/:sourceId") { ctx ->
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
ctx.json(getSource(sourceId))
|
||||||
|
}
|
||||||
|
|
||||||
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
|
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
@@ -103,10 +119,11 @@ class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// single source search
|
// single source search
|
||||||
app.get("/api/v1/source/:sourceId/search/:searchTerm") { ctx ->
|
app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx ->
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
ctx.json(sourceSearch(sourceId, searchTerm))
|
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||||
|
ctx.json(sourceSearch(sourceId, searchTerm, pageNum))
|
||||||
}
|
}
|
||||||
|
|
||||||
// source filter list
|
// source filter list
|
||||||
@@ -114,11 +131,6 @@ class Main {
|
|||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
ctx.json(sourceFilters(sourceId))
|
ctx.json(sourceFilters(sourceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ data class MangaDataClass(
|
|||||||
|
|
||||||
val url: String,
|
val url: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val thumbnail_url: String? = null,
|
val thumbnailUrl: String? = null,
|
||||||
|
|
||||||
val initialized: Boolean = false,
|
val initialized: Boolean = false,
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
import org.jetbrains.exposed.dao.*
|
import org.jetbrains.exposed.dao.EntityClass
|
||||||
|
import org.jetbrains.exposed.dao.LongEntity
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
|
||||||
class SourceEntity(id: EntityID<Long>) : LongEntity(id) {
|
class SourceEntity(id: EntityID<Long>) : LongEntity(id) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package ir.armor.tachidesk.database.table
|
package ir.armor.tachidesk.database.table
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
object ChapterTable : IntIdTable() {
|
object ChapterTable : IntIdTable() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package ir.armor.tachidesk.database.table
|
|||||||
|
|
||||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
|
||||||
object ExtensionsTable : IntIdTable() {
|
object ExtensionsTable : IntIdTable() {
|
||||||
val name = varchar("name", 128)
|
val name = varchar("name", 128)
|
||||||
val pkgName = varchar("pkg_name", 128)
|
val pkgName = varchar("pkg_name", 128)
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ fun installAPK(apkName: String): Int {
|
|||||||
// download apk file
|
// download apk file
|
||||||
downloadAPKFile(apkToDownload, apkFilePath)
|
downloadAPKFile(apkToDownload, apkFilePath)
|
||||||
|
|
||||||
|
|
||||||
val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
||||||
println(className)
|
println(className)
|
||||||
// dex -> jar
|
// dex -> jar
|
||||||
@@ -80,7 +79,6 @@ fun installAPK(apkName: String): Int {
|
|||||||
// println(httpSource.name)
|
// println(httpSource.name)
|
||||||
// println()
|
// println()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // multi source
|
} else { // multi source
|
||||||
val sourceFactory = instance as SourceFactory
|
val sourceFactory = instance as SourceFactory
|
||||||
transaction {
|
transaction {
|
||||||
@@ -110,7 +108,6 @@ fun installAPK(apkName: String): Int {
|
|||||||
it[classFQName] = className
|
it[classFQName] = className
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return 201 // we downloaded successfully
|
return 201 // we downloaded successfully
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import ir.armor.tachidesk.database.dataclass.ChapterDataClass
|
import ir.armor.tachidesk.database.dataclass.ChapterDataClass
|
||||||
import ir.armor.tachidesk.database.dataclass.PageDataClass
|
import ir.armor.tachidesk.database.dataclass.PageDataClass
|
||||||
import ir.armor.tachidesk.database.entity.MangaEntity
|
|
||||||
import ir.armor.tachidesk.database.table.ChapterTable
|
import ir.armor.tachidesk.database.table.ChapterTable
|
||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
import org.jetbrains.exposed.sql.insertAndGetId
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
|
|
||||||
fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
||||||
val mangaDetails = getManga(mangaId)
|
val mangaDetails = getManga(mangaId)
|
||||||
val source = getHttpSource(mangaDetails.sourceId)
|
val source = getHttpSource(mangaDetails.sourceId)
|
||||||
@@ -41,7 +39,6 @@ fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return@transaction chapterList.map {
|
return@transaction chapterList.map {
|
||||||
ChapterDataClass(
|
ChapterDataClass(
|
||||||
ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value,
|
ChapterTable.select { ChapterTable.url eq it.url }.firstOrNull()!![ChapterTable.id].value,
|
||||||
@@ -56,7 +53,7 @@ fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPages(chapterId: Int, mangaId: Int): List<PageDataClass> {
|
fun getPages(chapterId: Int, mangaId: Int): Pair<ChapterDataClass, List<PageDataClass>> {
|
||||||
return transaction {
|
return transaction {
|
||||||
val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!!
|
val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!!
|
||||||
assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check
|
assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check
|
||||||
@@ -70,14 +67,25 @@ fun getPages(chapterId: Int, mangaId: Int): List<PageDataClass> {
|
|||||||
}
|
}
|
||||||
).toBlocking().first()
|
).toBlocking().first()
|
||||||
|
|
||||||
return@transaction pagesList.map {
|
val chapter = ChapterDataClass(
|
||||||
|
chapterEntry[ChapterTable.id].value,
|
||||||
|
chapterEntry[ChapterTable.url],
|
||||||
|
chapterEntry[ChapterTable.name],
|
||||||
|
chapterEntry[ChapterTable.date_upload],
|
||||||
|
chapterEntry[ChapterTable.chapter_number],
|
||||||
|
chapterEntry[ChapterTable.scanlator],
|
||||||
|
mangaId
|
||||||
|
)
|
||||||
|
|
||||||
|
val pages = pagesList.map {
|
||||||
PageDataClass(
|
PageDataClass(
|
||||||
it.index,
|
it.index,
|
||||||
getTrueImageUrl(it, source)
|
getTrueImageUrl(it, source)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return@transaction Pair(chapter, pages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTrueImageUrl(page: Page, source: HttpSource): String {
|
fun getTrueImageUrl(page: Page, source: HttpSource): String {
|
||||||
|
|||||||
@@ -79,6 +79,5 @@ fun getExtensionList(offline: Boolean = false): List<ExtensionDataClass> {
|
|||||||
it[ExtensionsTable.classFQName]
|
it[ExtensionsTable.classFQName]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ fun getManga(mangaId: Int): MangaDataClass {
|
|||||||
mangaId,
|
mangaId,
|
||||||
mangaEntry[MangaTable.sourceReference].value,
|
mangaEntry[MangaTable.sourceReference].value,
|
||||||
|
|
||||||
|
|
||||||
mangaEntry[MangaTable.url],
|
mangaEntry[MangaTable.url],
|
||||||
mangaEntry[MangaTable.title],
|
mangaEntry[MangaTable.title],
|
||||||
mangaEntry[MangaTable.thumbnail_url],
|
mangaEntry[MangaTable.thumbnail_url],
|
||||||
@@ -75,4 +74,3 @@ fun getManga(mangaId: Int): MangaDataClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
import ir.armor.tachidesk.database.table.MangaStatus
|
import ir.armor.tachidesk.database.table.MangaStatus
|
||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
import org.jetbrains.exposed.sql.insertAndGetId
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
@@ -19,6 +18,11 @@ fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): List<Manga
|
|||||||
else
|
else
|
||||||
throw Exception("Source $source doesn't support latest")
|
throw Exception("Source $source doesn't support latest")
|
||||||
}
|
}
|
||||||
|
return mangasPage.processEntries(sourceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MangasPage.processEntries(sourceId: Long): List<MangaDataClass> {
|
||||||
|
val mangasPage = this
|
||||||
return transaction {
|
return transaction {
|
||||||
return@transaction mangasPage.mangas.map { manga ->
|
return@transaction mangasPage.mangas.map { manga ->
|
||||||
var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
|
var mangaEntry = MangaTable.select { MangaTable.url eq manga.url }.firstOrNull()
|
||||||
@@ -42,7 +46,7 @@ fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): List<Manga
|
|||||||
|
|
||||||
MangaDataClass(
|
MangaDataClass(
|
||||||
mangaEntityId,
|
mangaEntityId,
|
||||||
sourceId.toLong(),
|
sourceId,
|
||||||
|
|
||||||
manga.url,
|
manga.url,
|
||||||
manga.title,
|
manga.title,
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
|
|
||||||
fun sourceFilters(sourceId: Long) {
|
fun sourceFilters(sourceId: Long) {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
// source.getFilterList().toItems()
|
// source.getFilterList().toItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceSearch(sourceId: Long, searchTerm: String) {
|
fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): List<MangaDataClass> {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
//source.fetchSearchManga()
|
val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).toBlocking().first()
|
||||||
|
return searchManga.processEntries(sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceGlobalSearch(searchTerm: String) {
|
fun sourceGlobalSearch(searchTerm: String) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FilterWrapper(
|
data class FilterWrapper(
|
||||||
@@ -22,7 +21,7 @@ data class FilterWrapper(
|
|||||||
val filter: Any
|
val filter: Any
|
||||||
)
|
)
|
||||||
|
|
||||||
//private fun FilterList.toItems(): List<FilterWrapper> {
|
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
|
||||||
// return mapNotNull { filter ->
|
// return mapNotNull { filter ->
|
||||||
// when (filter) {
|
// when (filter) {
|
||||||
// is Filter.Header -> FilterWrapper("Header",filter)
|
// is Filter.Header -> FilterWrapper("Header",filter)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import org.jetbrains.exposed.sql.selectAll
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
private val sourceCache = mutableListOf<Pair<Long, HttpSource>>()
|
private val sourceCache = mutableListOf<Pair<Long, HttpSource>>()
|
||||||
private val extensionCache = mutableListOf<Pair<String, Any>>()
|
private val extensionCache = mutableListOf<Pair<String, Any>>()
|
||||||
@@ -80,3 +80,17 @@ fun getSourceList(): List<SourceDataClass> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSource(sourceId: Long): SourceDataClass {
|
||||||
|
return transaction {
|
||||||
|
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])),
|
||||||
|
ExtensionsTable.select { ExtensionsTable.id eq source[SourceTable.extension] }.first()[ExtensionsTable.iconUrl],
|
||||||
|
getHttpSource(source[SourceTable.id].value).supportsLatest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,5 @@ fun applicationSetup() {
|
|||||||
File(Config.dataRoot).mkdirs()
|
File(Config.dataRoot).mkdirs()
|
||||||
File(Config.extensionsRoot).mkdirs()
|
File(Config.extensionsRoot).mkdirs()
|
||||||
|
|
||||||
|
|
||||||
makeDataBaseTables()
|
makeDataBaseTables()
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router, Route, Switch,
|
BrowserRouter as Router, Route, Switch,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
@@ -9,15 +9,20 @@ import Extensions from './screens/Extensions';
|
|||||||
import MangaList from './screens/MangaList';
|
import MangaList from './screens/MangaList';
|
||||||
import Manga from './screens/Manga';
|
import Manga from './screens/Manga';
|
||||||
import Reader from './screens/Reader';
|
import Reader from './screens/Reader';
|
||||||
import Search from './screens/Search';
|
import Search from './screens/SearchSingle';
|
||||||
|
import NavBarTitle from './context/NavbarTitle';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const [title, setTitle] = useState<string>('Tachidesk');
|
||||||
|
const contextValue = { title, setTitle };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
|
<NavBarTitle.Provider value={contextValue}>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/search">
|
<Route path="/sources/:sourceId/search/">
|
||||||
<Search />
|
<Search />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/extensions">
|
<Route path="/extensions">
|
||||||
@@ -42,6 +47,7 @@ export default function App() {
|
|||||||
<Home />
|
<Home />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
</NavBarTitle.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import AppBar from '@material-ui/core/AppBar';
|
import AppBar from '@material-ui/core/AppBar';
|
||||||
import Toolbar from '@material-ui/core/Toolbar';
|
import Toolbar from '@material-ui/core/Toolbar';
|
||||||
@@ -6,6 +6,7 @@ import Typography from '@material-ui/core/Typography';
|
|||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import MenuIcon from '@material-ui/icons/Menu';
|
import MenuIcon from '@material-ui/icons/Menu';
|
||||||
import TemporaryDrawer from './TemporaryDrawer';
|
import TemporaryDrawer from './TemporaryDrawer';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -22,6 +23,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
export default function NavBar() {
|
export default function NavBar() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const { title } = useContext(NavBarTitle);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
@@ -38,7 +40,7 @@ export default function NavBar() {
|
|||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" className={classes.title}>
|
<Typography variant="h6" className={classes.title}>
|
||||||
Tachidesk
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ export default function SourceCard(props: IProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
{supportsLatest && <Button variant="outlined" style={{ marginLeft: 20 }} onClick={() => { window.location.href = `sources/${id}/latest/`; }}>Latest</Button>}
|
<Button variant="outlined" style={{ marginLeft: 20 }} onClick={() => { window.location.href = `/sources/${id}/search/`; }}>Search</Button>
|
||||||
<Button variant="outlined" style={{ marginLeft: 20 }} onClick={() => { window.location.href = `sources/${id}/popular/`; }}>Browse</Button>
|
{supportsLatest && <Button variant="outlined" style={{ marginLeft: 20 }} onClick={() => { window.location.href = `/sources/${id}/latest/`; }}>Latest</Button>}
|
||||||
|
<Button variant="outlined" style={{ marginLeft: 20 }} onClick={() => { window.location.href = `/sources/${id}/popular/`; }}>Browse</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
|
|||||||
<ListItemText primary="Sources" />
|
<ListItemText primary="Sources" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/search" style={{ color: 'inherit', textDecoration: 'none' }}>
|
{/* <Link to="/search" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
<ListItem button key="Search">
|
<ListItem button key="Search">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<InboxIcon />
|
<InboxIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Global Search" />
|
<ListItemText primary="Global Search" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link> */}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type ContextType = {
|
||||||
|
title: string
|
||||||
|
setTitle: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
};
|
||||||
|
|
||||||
|
const NavBarTitle = React.createContext<ContextType>({
|
||||||
|
title: 'Tachidesk',
|
||||||
|
setTitle: ():void => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default NavBarTitle;
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import ExtensionCard from '../components/ExtensionCard';
|
import ExtensionCard from '../components/ExtensionCard';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
export default function Extensions() {
|
export default function Extensions() {
|
||||||
let mapped;
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
setTitle('Extensions');
|
||||||
const [extensions, setExtensions] = useState<IExtension[]>([]);
|
const [extensions, setExtensions] = useState<IExtension[]>([]);
|
||||||
|
let mapped;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://127.0.0.1:4567/api/v1/extension/list')
|
fetch('http://127.0.0.1:4567/api/v1/extension/list')
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import ChapterCard from '../components/ChapterCard';
|
import ChapterCard from '../components/ChapterCard';
|
||||||
import MangaDetails from '../components/MangaDetails';
|
import MangaDetails from '../components/MangaDetails';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
export default function Manga() {
|
export default function Manga() {
|
||||||
const { id } = useParams<{id: string}>();
|
const { id } = useParams<{id: string}>();
|
||||||
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
|
||||||
const [manga, setManga] = useState<IManga>();
|
const [manga, setManga] = useState<IManga>();
|
||||||
const [chapters, setChapters] = useState<IChapter[]>([]);
|
const [chapters, setChapters] = useState<IChapter[]>([]);
|
||||||
@@ -12,7 +14,10 @@ export default function Manga() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${id}/`)
|
fetch(`http://127.0.0.1:4567/api/v1/manga/${id}/`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => setManga(data));
|
.then((data: IManga) => {
|
||||||
|
setManga(data);
|
||||||
|
setTitle(data.title);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
import React, { 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/MangaGrid';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
export default function MangaList(props: { popular: boolean }) {
|
export default function MangaList(props: { popular: boolean }) {
|
||||||
const { sourceId } = useParams<{sourceId: string}>();
|
const { sourceId } = useParams<{sourceId: string}>();
|
||||||
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
const [mangas, setMangas] = useState<IManga[]>([]);
|
const [mangas, setMangas] = useState<IManga[]>([]);
|
||||||
const [lastPageNum] = useState<number>(1);
|
const [lastPageNum] = useState<number>(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data: { name: string }) => setTitle(data.name));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sourceType = props.popular ? 'popular' : 'latest';
|
const sourceType = props.popular ? 'popular' : 'latest';
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}/${sourceType}/${lastPageNum}`)
|
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}/${sourceType}/${lastPageNum}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data: { title: string, thumbnail_url: string, id:number }[]) => setMangas(
|
.then((data: IManga[]) => setMangas(
|
||||||
data.map((it) => ({ title: it.title, thumbnailUrl: it.thumbnail_url, id: it.id })),
|
data.map((it) => ({ title: it.title, thumbnailUrl: it.thumbnailUrl, id: it.id })),
|
||||||
));
|
));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -14,14 +15,24 @@ interface IPage {
|
|||||||
imageUrl: string
|
imageUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IData {
|
||||||
|
first: IChapter
|
||||||
|
second: IPage[]
|
||||||
|
}
|
||||||
|
|
||||||
export default function Reader() {
|
export default function Reader() {
|
||||||
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
|
||||||
const [pages, setPages] = useState<IPage[]>([]);
|
const [pages, setPages] = useState<IPage[]>([]);
|
||||||
const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>();
|
const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/manga/${mangaId}/chapter/${chapterId}`)
|
fetch(`http://127.0.0.1:4567/api/v1/manga/${mangaId}/chapter/${chapterId}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => setPages(data));
|
.then((data:IData) => {
|
||||||
|
setTitle(data.first.name);
|
||||||
|
setPages(data.second);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
pages.sort((a, b) => (a.index - b.index));
|
pages.sort((a, b) => (a.index - b.index));
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
|
||||||
import TextField from '@material-ui/core/TextField';
|
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import MangaGrid from '../components/MangaGrid';
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
root: {
|
|
||||||
TextField: {
|
|
||||||
margin: theme.spacing(1),
|
|
||||||
width: '25ch',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default function Search() {
|
|
||||||
const classes = useStyles();
|
|
||||||
const [error, setError] = useState<boolean>(false);
|
|
||||||
const [mangas, setMangas] = useState<IManga[]>([]);
|
|
||||||
const [message, setMessage] = useState<string>('');
|
|
||||||
|
|
||||||
const textInput = React.createRef<HTMLInputElement>();
|
|
||||||
|
|
||||||
function doSearch() {
|
|
||||||
if (textInput.current) {
|
|
||||||
const { value } = textInput.current;
|
|
||||||
if (value === '') { setError(true); } else {
|
|
||||||
setError(false);
|
|
||||||
setMangas([]);
|
|
||||||
setMessage('button pressed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mangaGrid = <MangaGrid mangas={mangas} message={message} />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<form className={classes.root} noValidate autoComplete="off">
|
|
||||||
<TextField inputRef={textInput} error={error} id="standard-basic" label="Search text.." />
|
|
||||||
<Button variant="contained" color="primary" onClick={() => doSearch()}>
|
|
||||||
Primary
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
{mangaGrid}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import TextField from '@material-ui/core/TextField';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import MangaGrid from '../components/MangaGrid';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
TextField: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
width: '25ch',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function SearchSingle() {
|
||||||
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
const { sourceId } = useParams<{sourceId: string}>();
|
||||||
|
const classes = useStyles();
|
||||||
|
const [error, setError] = useState<boolean>(false);
|
||||||
|
const [mangas, setMangas] = useState<IManga[]>([]);
|
||||||
|
const [message, setMessage] = useState<string>('');
|
||||||
|
const [lastPageNum] = useState<number>(1);
|
||||||
|
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
|
const textInput = React.createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data: { name: string }) => setTitle(`Search: ${data.name}`));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function processInput() {
|
||||||
|
if (textInput.current) {
|
||||||
|
const { value } = textInput.current;
|
||||||
|
if (value === '') {
|
||||||
|
setError(true);
|
||||||
|
setMessage('Type something to search');
|
||||||
|
} else {
|
||||||
|
setError(false);
|
||||||
|
setSearchTerm(value);
|
||||||
|
setMessage('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/source/${sourceId}/search/${searchTerm}/${lastPageNum}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data: IManga[]) => {
|
||||||
|
if (data.length > 0) {
|
||||||
|
setMangas(
|
||||||
|
data.map((it) => (
|
||||||
|
{ title: it.title, thumbnailUrl: it.thumbnailUrl, id: it.id }
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setMessage('search qeury returned nothing.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
const mangaGrid = <MangaGrid mangas={mangas} message={message} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form className={classes.root} noValidate autoComplete="off">
|
||||||
|
<TextField inputRef={textInput} error={error} id="standard-basic" label="Search text.." />
|
||||||
|
<Button variant="contained" color="primary" onClick={() => processInput()}>
|
||||||
|
Primary
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
{mangaGrid}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import SourceCard from '../components/SourceCard';
|
import SourceCard from '../components/SourceCard';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
export default function Sources() {
|
export default function Sources() {
|
||||||
let mapped;
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
setTitle('Sources');
|
||||||
const [sources, setSources] = useState<ISource[]>([]);
|
const [sources, setSources] = useState<ISource[]>([]);
|
||||||
|
let mapped;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://127.0.0.1:4567/api/v1/source/list')
|
fetch('http://127.0.0.1:4567/api/v1/source/list')
|
||||||
|
|||||||
Reference in New Issue
Block a user