Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1cd2cfc8c | |||
| 832c224ed4 | |||
| 99316f4bd5 | |||
| 9caae5f1e5 | |||
| 345be95ce9 | |||
| 6fe68841b7 | |||
| eaff2c15a9 | |||
| 5eb8dc66a8 | |||
| 49715c81e4 | |||
| 3398409555 | |||
| f05aa0589a | |||
| fbc71ce781 | |||
| ca9c671886 | |||
| bd109ba11f | |||
| 0ff770a98b | |||
| ed7bb408a3 | |||
| 84676b9156 | |||
| dcdd50ffe1 | |||
| afb21c59f0 | |||
| e219179519 | |||
| 15a2115c5a | |||
| 94c6f33925 | |||
| 202e38871d | |||
| 3f75b84651 | |||
| f171b785a0 |
@@ -5,12 +5,10 @@ Tachidesk is as multi-platform as you can get. Any platform that runs java and/o
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
## How does it work?
|
|
||||||
This project has two components:
|
|
||||||
1. **server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run apk extensions. All this concludes to serving a REST API to `webUI`.
|
|
||||||
2. **webUI:** A react SPA project that works with the server to do the presentation.
|
|
||||||
|
|
||||||
## How do I run the thing?
|
## How do I run the thing?
|
||||||
|
#### Prerequisites
|
||||||
|
You should have java 8 or newer and a modern browser installed. Also an internet connection is required as almost everything this app does is downloading stuff.
|
||||||
|
|
||||||
#### Running pre-built jar packages
|
#### Running pre-built jar packages
|
||||||
Download the latest (or a working more stable) release from [the repo branch](https://github.com/AriaMoradi/Tachidesk/tree/repo) or obtain it from [the releases section](https://github.com/AriaMoradi/Tachidesk/releases).
|
Download the latest (or a working more stable) release from [the repo branch](https://github.com/AriaMoradi/Tachidesk/tree/repo) or obtain it from [the releases section](https://github.com/AriaMoradi/Tachidesk/releases).
|
||||||
|
|
||||||
@@ -18,6 +16,9 @@ Double click on the jar file or run `java -jar Tachidesk-latest.jar` or `java -j
|
|||||||
|
|
||||||
The server will be running on `http://localhost:4567` open this url in your browser.
|
The server will be running on `http://localhost:4567` open this url in your browser.
|
||||||
|
|
||||||
|
#### Running on Docker
|
||||||
|
Check [arbuilder's repo](https://github.com/arbuilder/Tachidesk-docker) out for more details and the dockerfile.
|
||||||
|
|
||||||
## Building from source
|
## Building from source
|
||||||
### Get Android stubs jar
|
### Get Android stubs jar
|
||||||
#### Manual download
|
#### Manual download
|
||||||
@@ -37,13 +38,26 @@ How to do it is described in `webUI/react/README.md` but for short,
|
|||||||
and supports HMR and all the other goodies you'll need.
|
and supports HMR and all the other goodies you'll need.
|
||||||
|
|
||||||
## Is this application usable? Should I test it?
|
## Is this application usable? Should I test it?
|
||||||
Checkout [the state of project](https://github.com/AriaMoradi/Tachidesk/issues/2) to see what's implemented.
|
If you'd ask me, I'd tell you If you want to read your manga **online** from tachiyomi or in one place and bypass all the ads, you can use Tachidesk.
|
||||||
|
|
||||||
|
There are almost no quality of life features, including no library, no downloading for offline enjoyment and sadly no MangaDex search.
|
||||||
|
|
||||||
|
Anyways, for more info checkout [finished milestone #1](https://github.com/AriaMoradi/Tachidesk/issues/2) and [milestone #2](https://github.com/AriaMoradi/Tachidesk/projects/1) to see what's implemented.
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
This project has two components:
|
||||||
|
1. **server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run apk extensions. All this concludes to serving a REST API to `webUI`.
|
||||||
|
2. **webUI:** A react SPA project that works with the server to do the presentation.
|
||||||
|
|
||||||
## Credit
|
## Credit
|
||||||
The `AndroidCompat` module and `scripts/getAndroid.sh` was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
|
The `AndroidCompat` module and `scripts/getAndroid.sh` was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
|
||||||
|
|
||||||
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
|
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
|
||||||
|
|
||||||
|
Changes to both codebases is licensed under `MPL v. 2.0` as the rest of this project.
|
||||||
|
|
||||||
|
You can obtain a copy of the license from http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2020-2021 Aria Moradi and contributors
|
Copyright (C) 2020-2021 Aria Moradi and contributors
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ plugins {
|
|||||||
id("org.jmailen.kotlinter") version "3.3.0"
|
id("org.jmailen.kotlinter") version "3.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
val TachideskVersion = "v0.1.0"
|
val TachideskVersion = "v0.1.3"
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk;
|
package ir.armor.tachidesk;
|
||||||
|
|
||||||
|
/* 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 org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.NamedNodeMap;
|
import org.w3c.dom.NamedNodeMap;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class NetworkHelper(context: Context) {
|
|||||||
// .cache(Cache(cacheDir, cacheSize))
|
// .cache(Cache(cacheDir, cacheSize))
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
// .dispatcher(Dispatcher(Executors.newFixedThreadPool(1)))
|
||||||
|
|
||||||
// .addInterceptor(UserAgentInterceptor())
|
// .addInterceptor(UserAgentInterceptor())
|
||||||
|
|
||||||
// if (BuildConfig.DEBUG) {
|
// if (BuildConfig.DEBUG) {
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package ir.armor.tachidesk
|
package ir.armor.tachidesk
|
||||||
|
|
||||||
|
/* 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 net.harawata.appdirs.AppDirsFactory
|
import net.harawata.appdirs.AppDirsFactory
|
||||||
|
|
||||||
object Config {
|
object Config {
|
||||||
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
||||||
val extensionsRoot = "$dataRoot/extensions"
|
val extensionsRoot = "$dataRoot/extensions"
|
||||||
|
val thumbnailsRoot = "$dataRoot/thumbnails"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk
|
package ir.armor.tachidesk
|
||||||
|
|
||||||
|
/* 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 eu.kanade.tachiyomi.App
|
import eu.kanade.tachiyomi.App
|
||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import ir.armor.tachidesk.util.applicationSetup
|
import ir.armor.tachidesk.util.applicationSetup
|
||||||
@@ -7,10 +11,13 @@ import ir.armor.tachidesk.util.getChapterList
|
|||||||
import ir.armor.tachidesk.util.getExtensionList
|
import ir.armor.tachidesk.util.getExtensionList
|
||||||
import ir.armor.tachidesk.util.getManga
|
import ir.armor.tachidesk.util.getManga
|
||||||
import ir.armor.tachidesk.util.getMangaList
|
import ir.armor.tachidesk.util.getMangaList
|
||||||
|
import ir.armor.tachidesk.util.getMangaUpdateQueueThread
|
||||||
import ir.armor.tachidesk.util.getPages
|
import ir.armor.tachidesk.util.getPages
|
||||||
import ir.armor.tachidesk.util.getSource
|
import ir.armor.tachidesk.util.getSource
|
||||||
import ir.armor.tachidesk.util.getSourceList
|
import ir.armor.tachidesk.util.getSourceList
|
||||||
|
import ir.armor.tachidesk.util.getThumbnail
|
||||||
import ir.armor.tachidesk.util.installAPK
|
import ir.armor.tachidesk.util.installAPK
|
||||||
|
import ir.armor.tachidesk.util.removeExtension
|
||||||
import ir.armor.tachidesk.util.sourceFilters
|
import ir.armor.tachidesk.util.sourceFilters
|
||||||
import ir.armor.tachidesk.util.sourceGlobalSearch
|
import ir.armor.tachidesk.util.sourceGlobalSearch
|
||||||
import ir.armor.tachidesk.util.sourceSearch
|
import ir.armor.tachidesk.util.sourceSearch
|
||||||
@@ -50,6 +57,8 @@ class Main {
|
|||||||
// start app
|
// start app
|
||||||
androidCompat.startApp(App())
|
androidCompat.startApp(App())
|
||||||
|
|
||||||
|
Thread(getMangaUpdateQueueThread).start()
|
||||||
|
|
||||||
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")
|
||||||
@@ -71,11 +80,20 @@ class Main {
|
|||||||
|
|
||||||
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("installing $apkName")
|
||||||
|
|
||||||
ctx.status(
|
ctx.status(
|
||||||
installAPK(apkName)
|
installAPK(apkName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.get("/api/v1/extension/uninstall/:apkName") { ctx ->
|
||||||
|
val apkName = ctx.pathParam("apkName")
|
||||||
|
println("uninstalling $apkName")
|
||||||
|
removeExtension(apkName)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
app.get("/api/v1/source/list") { ctx ->
|
app.get("/api/v1/source/list") { ctx ->
|
||||||
ctx.json(getSourceList())
|
ctx.json(getSourceList())
|
||||||
}
|
}
|
||||||
@@ -112,6 +130,15 @@ class Main {
|
|||||||
ctx.json(getPages(chapterId, mangaId))
|
ctx.json(getPages(chapterId, mangaId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
println("got request for: $mangaId")
|
||||||
|
val result = getThumbnail(mangaId)
|
||||||
|
|
||||||
|
ctx.result(result.first)
|
||||||
|
ctx.header("content-type", result.second)
|
||||||
|
}
|
||||||
|
|
||||||
// global search
|
// global search
|
||||||
app.get("/api/v1/search/:searchTerm") { ctx ->
|
app.get("/api/v1/search/:searchTerm") { ctx ->
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database
|
package ir.armor.tachidesk.database
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.Config
|
import ir.armor.tachidesk.Config
|
||||||
import ir.armor.tachidesk.database.table.ChapterTable
|
import ir.armor.tachidesk.database.table.ChapterTable
|
||||||
import ir.armor.tachidesk.database.table.ExtensionsTable
|
import ir.armor.tachidesk.database.table.ExtensionsTable
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
data class ChapterDataClass(
|
data class ChapterDataClass(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val url: String,
|
val url: String,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
data class ExtensionDataClass(
|
data class ExtensionDataClass(
|
||||||
val name: String,
|
val name: String,
|
||||||
val pkgName: String,
|
val pkgName: String,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.database.table.MangaStatus
|
import ir.armor.tachidesk.database.table.MangaStatus
|
||||||
|
|
||||||
data class MangaDataClass(
|
data class MangaDataClass(
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
data class PageDataClass(
|
data class PageDataClass(
|
||||||
val index: Int,
|
val index: Int,
|
||||||
var imageUrl: String,
|
var imageUrl: String,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.dataclass
|
package ir.armor.tachidesk.database.dataclass
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
data class SourceDataClass(
|
data class SourceDataClass(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.database.table.ExtensionsTable
|
import ir.armor.tachidesk.database.table.ExtensionsTable
|
||||||
import org.jetbrains.exposed.dao.IntEntity
|
import org.jetbrains.exposed.dao.IntEntity
|
||||||
import org.jetbrains.exposed.dao.IntEntityClass
|
import org.jetbrains.exposed.dao.IntEntityClass
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import org.jetbrains.exposed.dao.IntEntity
|
import org.jetbrains.exposed.dao.IntEntity
|
||||||
import org.jetbrains.exposed.dao.IntEntityClass
|
import org.jetbrains.exposed.dao.IntEntityClass
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.database.entity
|
package ir.armor.tachidesk.database.entity
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
import org.jetbrains.exposed.dao.EntityClass
|
import org.jetbrains.exposed.dao.EntityClass
|
||||||
import org.jetbrains.exposed.dao.LongEntity
|
import org.jetbrains.exposed.dao.LongEntity
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass
|
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
fun writeStream(fileStream: InputStream, path: String) {
|
||||||
|
Files.newOutputStream(Paths.get(path)).use { os ->
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var len: Int
|
||||||
|
while (fileStream.read(buffer).also { len = it } > 0) {
|
||||||
|
os.write(buffer, 0, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pathToInputStream(path: String): InputStream {
|
||||||
|
return BufferedInputStream(FileInputStream(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findFileNameStartingWith(directoryPath: String, fileName: String): String? {
|
||||||
|
File(directoryPath).listFiles().forEach { file ->
|
||||||
|
if (file.name.startsWith(fileName))
|
||||||
|
return "$directoryPath/${file.name}"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -1,76 +1,171 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import ir.armor.tachidesk.Config
|
||||||
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 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
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
|
|
||||||
fun getManga(mangaId: Int): MangaDataClass {
|
val getMangaUpdateQueue = ArrayBlockingQueue<Pair<Int, SManga?>>(1000)
|
||||||
return transaction {
|
@Volatile
|
||||||
var mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
var getMangaCount = 0
|
||||||
|
|
||||||
return@transaction if (mangaEntry[MangaTable.initialized]) {
|
val getMangaUpdateQueueThread = Runnable {
|
||||||
MangaDataClass(
|
while (true) {
|
||||||
mangaId,
|
val p = getMangaUpdateQueue.take()
|
||||||
mangaEntry[MangaTable.sourceReference].value,
|
println("took ${p.first}")
|
||||||
|
while (getMangaCount > 0) {
|
||||||
|
println("count is $getMangaCount")
|
||||||
|
Thread.sleep(1000)
|
||||||
|
}
|
||||||
|
val mangaId = p.first
|
||||||
|
println("working on $mangaId")
|
||||||
|
val fetchedManga = p.second!!
|
||||||
|
try {
|
||||||
|
transaction {
|
||||||
|
println("transaction start $mangaId")
|
||||||
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
|
|
||||||
mangaEntry[MangaTable.url],
|
it[MangaTable.initialized] = true
|
||||||
mangaEntry[MangaTable.title],
|
|
||||||
mangaEntry[MangaTable.thumbnail_url],
|
|
||||||
|
|
||||||
true,
|
it[MangaTable.artist] = fetchedManga.artist
|
||||||
|
it[MangaTable.author] = fetchedManga.author
|
||||||
mangaEntry[MangaTable.artist],
|
it[MangaTable.description] = fetchedManga.description
|
||||||
mangaEntry[MangaTable.author],
|
it[MangaTable.genre] = fetchedManga.genre
|
||||||
mangaEntry[MangaTable.description],
|
it[MangaTable.status] = fetchedManga.status
|
||||||
mangaEntry[MangaTable.genre],
|
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
||||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||||
)
|
|
||||||
} else { // initialize manga
|
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
|
|
||||||
val fetchedManga = source.fetchMangaDetails(
|
|
||||||
SManga.create().apply {
|
|
||||||
url = mangaEntry[MangaTable.url]
|
|
||||||
title = mangaEntry[MangaTable.title]
|
|
||||||
}
|
}
|
||||||
).toBlocking().first()
|
println("transaction end $mangaId")
|
||||||
|
|
||||||
// update database
|
|
||||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
|
||||||
// it[url] = fetchedManga.url
|
|
||||||
// it[title] = fetchedManga.title
|
|
||||||
it[initialized] = true
|
|
||||||
|
|
||||||
it[artist] = fetchedManga.artist
|
|
||||||
it[author] = fetchedManga.author
|
|
||||||
it[description] = fetchedManga.description
|
|
||||||
it[genre] = fetchedManga.genre
|
|
||||||
it[status] = fetchedManga.status
|
|
||||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
|
||||||
it[thumbnail_url] = fetchedManga.thumbnail_url
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
println(e)
|
||||||
|
}
|
||||||
MangaDataClass(
|
}
|
||||||
mangaId,
|
}
|
||||||
mangaEntry[MangaTable.sourceReference].value,
|
|
||||||
|
fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
|
||||||
mangaEntry[MangaTable.url],
|
synchronized(getMangaCount) {
|
||||||
mangaEntry[MangaTable.title],
|
getMangaCount++
|
||||||
mangaEntry[MangaTable.thumbnail_url],
|
}
|
||||||
|
return try {
|
||||||
true,
|
transaction {
|
||||||
|
var mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
||||||
mangaEntry[MangaTable.artist],
|
|
||||||
mangaEntry[MangaTable.author],
|
return@transaction if (mangaEntry[MangaTable.initialized]) {
|
||||||
mangaEntry[MangaTable.description],
|
println("${mangaEntry[MangaTable.title]} is initialized")
|
||||||
mangaEntry[MangaTable.genre],
|
println("${mangaEntry[MangaTable.thumbnail_url]}")
|
||||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
MangaDataClass(
|
||||||
)
|
mangaId,
|
||||||
|
mangaEntry[MangaTable.sourceReference].value,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.url],
|
||||||
|
mangaEntry[MangaTable.title],
|
||||||
|
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url],
|
||||||
|
|
||||||
|
true,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.artist],
|
||||||
|
mangaEntry[MangaTable.author],
|
||||||
|
mangaEntry[MangaTable.description],
|
||||||
|
mangaEntry[MangaTable.genre],
|
||||||
|
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||||
|
)
|
||||||
|
} else { // initialize manga
|
||||||
|
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
|
||||||
|
val fetchedManga = source.fetchMangaDetails(
|
||||||
|
SManga.create().apply {
|
||||||
|
url = mangaEntry[MangaTable.url]
|
||||||
|
title = mangaEntry[MangaTable.title]
|
||||||
|
}
|
||||||
|
).toBlocking().first()
|
||||||
|
|
||||||
|
// update database
|
||||||
|
// TODO: sqlite gets fucked here
|
||||||
|
println("putting $mangaId")
|
||||||
|
getMangaUpdateQueue.put(Pair(mangaId, fetchedManga))
|
||||||
|
|
||||||
|
// mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
||||||
|
val newThumbnail =
|
||||||
|
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty()) {
|
||||||
|
fetchedManga.thumbnail_url
|
||||||
|
} else mangaEntry[MangaTable.thumbnail_url]
|
||||||
|
|
||||||
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
mangaEntry[MangaTable.sourceReference].value,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.url],
|
||||||
|
mangaEntry[MangaTable.title],
|
||||||
|
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail,
|
||||||
|
|
||||||
|
true,
|
||||||
|
|
||||||
|
fetchedManga.artist,
|
||||||
|
fetchedManga.author,
|
||||||
|
fetchedManga.description,
|
||||||
|
fetchedManga.genre,
|
||||||
|
MangaStatus.valueOf(fetchedManga.status).name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
synchronized(getMangaCount) {
|
||||||
|
getMangaCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||||
|
return transaction {
|
||||||
|
var filePath = Config.thumbnailsRoot + "/$mangaId"
|
||||||
|
var mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
||||||
|
|
||||||
|
val potentialCache = findFileNameStartingWith(Config.thumbnailsRoot, mangaId.toString())
|
||||||
|
if (potentialCache != null) {
|
||||||
|
println("using cached thumbnail file")
|
||||||
|
return@transaction Pair(
|
||||||
|
pathToInputStream(potentialCache),
|
||||||
|
"image/${potentialCache.substringAfter("$mangaId.")}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceId = mangaEntry[MangaTable.sourceReference].value
|
||||||
|
println("getting source for $mangaId")
|
||||||
|
val source = getHttpSource(sourceId)
|
||||||
|
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
|
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
|
||||||
|
thumbnailUrl = getManga(mangaId, proxyThumbnail = false).thumbnailUrl!!
|
||||||
|
}
|
||||||
|
println(thumbnailUrl)
|
||||||
|
val response = source.client.newCall(
|
||||||
|
GET(thumbnailUrl, source.headers)
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
println(response.code)
|
||||||
|
|
||||||
|
if (response.code == 200) {
|
||||||
|
val contentType = response.headers["content-type"]!!
|
||||||
|
filePath += "." + contentType.substringAfter("image/")
|
||||||
|
|
||||||
|
writeStream(response.body!!.byteStream(), filePath)
|
||||||
|
|
||||||
|
return@transaction Pair(
|
||||||
|
pathToInputStream(filePath),
|
||||||
|
contentType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw Exception("request error! ${response.code}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 eu.kanade.tachiyomi.source.model.MangasPage
|
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.dataclass.PagedMangaListDataClass
|
import ir.armor.tachidesk.database.dataclass.PagedMangaListDataClass
|
||||||
@@ -9,6 +13,10 @@ 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 proxyThumbnailUrl(mangaId: Int): String {
|
||||||
|
return "http://127.0.0.1:4567/api/v1/manga/$mangaId/thumbnail"
|
||||||
|
}
|
||||||
|
|
||||||
fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
||||||
val source = getHttpSource(sourceId.toLong())
|
val source = getHttpSource(sourceId.toLong())
|
||||||
val mangasPage = if (popular) {
|
val mangasPage = if (popular) {
|
||||||
@@ -27,8 +35,8 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
|
|||||||
val mangaList = transaction {
|
val mangaList = 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()
|
||||||
var mangaEntityId = if (mangaEntry == null) { // create manga entry
|
if (mangaEntry == null) { // create manga entry
|
||||||
MangaTable.insertAndGetId {
|
val mangaId = MangaTable.insertAndGetId {
|
||||||
it[url] = manga.url
|
it[url] = manga.url
|
||||||
it[title] = manga.title
|
it[title] = manga.title
|
||||||
|
|
||||||
@@ -37,30 +45,46 @@ fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
|
|||||||
it[description] = manga.description
|
it[description] = manga.description
|
||||||
it[genre] = manga.genre
|
it[genre] = manga.genre
|
||||||
it[status] = manga.status
|
it[status] = manga.status
|
||||||
it[thumbnail_url] = manga.genre
|
it[thumbnail_url] = manga.thumbnail_url
|
||||||
|
|
||||||
it[sourceReference] = sourceId
|
it[sourceReference] = sourceId
|
||||||
}.value
|
}.value
|
||||||
|
|
||||||
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
sourceId,
|
||||||
|
|
||||||
|
manga.url,
|
||||||
|
manga.title,
|
||||||
|
proxyThumbnailUrl(mangaId),
|
||||||
|
|
||||||
|
manga.initialized,
|
||||||
|
|
||||||
|
manga.artist,
|
||||||
|
manga.author,
|
||||||
|
manga.description,
|
||||||
|
manga.genre,
|
||||||
|
MangaStatus.valueOf(manga.status).name,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
mangaEntry[MangaTable.id].value
|
val mangaId = mangaEntry[MangaTable.id].value
|
||||||
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
sourceId,
|
||||||
|
|
||||||
|
manga.url,
|
||||||
|
manga.title,
|
||||||
|
proxyThumbnailUrl(mangaId),
|
||||||
|
|
||||||
|
true,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.artist],
|
||||||
|
mangaEntry[MangaTable.author],
|
||||||
|
mangaEntry[MangaTable.description],
|
||||||
|
mangaEntry[MangaTable.genre],
|
||||||
|
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MangaDataClass(
|
|
||||||
mangaEntityId,
|
|
||||||
sourceId,
|
|
||||||
|
|
||||||
manga.url,
|
|
||||||
manga.title,
|
|
||||||
manga.thumbnail_url,
|
|
||||||
|
|
||||||
manga.initialized,
|
|
||||||
|
|
||||||
manga.artist,
|
|
||||||
manga.author,
|
|
||||||
manga.description,
|
|
||||||
manga.genre,
|
|
||||||
MangaStatus.valueOf(manga.status).name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PagedMangaListDataClass(
|
return PagedMangaListDataClass(
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.database.dataclass.PagedMangaListDataClass
|
import ir.armor.tachidesk.database.dataclass.PagedMangaListDataClass
|
||||||
|
|
||||||
fun sourceFilters(sourceId: Long) {
|
fun sourceFilters(sourceId: Long) {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import ir.armor.tachidesk.Config
|
import ir.armor.tachidesk.Config
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 ir.armor.tachidesk.Config
|
import ir.armor.tachidesk.Config
|
||||||
import ir.armor.tachidesk.database.makeDataBaseTables
|
import ir.armor.tachidesk.database.makeDataBaseTables
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -8,6 +12,7 @@ fun applicationSetup() {
|
|||||||
// make dirs we need
|
// make dirs we need
|
||||||
File(Config.dataRoot).mkdirs()
|
File(Config.dataRoot).mkdirs()
|
||||||
File(Config.extensionsRoot).mkdirs()
|
File(Config.extensionsRoot).mkdirs()
|
||||||
|
File(Config.thumbnailsRoot).mkdirs()
|
||||||
|
|
||||||
makeDataBaseTables()
|
makeDataBaseTables()
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-2
@@ -1,5 +1,9 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
|
/* 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 com.googlecode.dex2jar.tools.Dex2jarCmd
|
import com.googlecode.dex2jar.tools.Dex2jarCmd
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
@@ -13,6 +17,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
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
|
||||||
@@ -28,8 +33,8 @@ fun installAPK(apkName: String): Int {
|
|||||||
val dirPathWithoutType = "${Config.extensionsRoot}/$fileNameWithoutType"
|
val dirPathWithoutType = "${Config.extensionsRoot}/$fileNameWithoutType"
|
||||||
|
|
||||||
// check if we don't have the dex file already downloaded
|
// check if we don't have the dex file already downloaded
|
||||||
val dexPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar"
|
val jarPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar"
|
||||||
if (!File(dexPath).exists()) {
|
if (!File(jarPath).exists()) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val api = ExtensionGithubApi()
|
val api = ExtensionGithubApi()
|
||||||
val apkToDownload = api.getApkUrl(extensionRecord)
|
val apkToDownload = api.getApkUrl(extensionRecord)
|
||||||
@@ -126,3 +131,21 @@ private fun downloadAPKFile(url: String, apkPath: String) {
|
|||||||
sink.writeAll(response.body!!.source())
|
sink.writeAll(response.body!!.source())
|
||||||
sink.close()
|
sink.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeExtension(pkgName: String) {
|
||||||
|
val extensionRecord = getExtensionList(true).first { it.apkName == pkgName }
|
||||||
|
val fileNameWithoutType = pkgName.substringBefore(".apk")
|
||||||
|
val jarPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar"
|
||||||
|
transaction {
|
||||||
|
val extensionId = ExtensionsTable.select { ExtensionsTable.name eq extensionRecord.name }.first()[ExtensionsTable.id]
|
||||||
|
|
||||||
|
SourceTable.deleteWhere { SourceTable.extension eq extensionId }
|
||||||
|
ExtensionsTable.update({ ExtensionsTable.name eq extensionRecord.name }) {
|
||||||
|
it[ExtensionsTable.installed] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File(jarPath).exists()) {
|
||||||
|
File(jarPath).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
-30
@@ -1,7 +1,15 @@
|
|||||||
|
/* 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 React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
BrowserRouter as Router, Route, Switch,
|
BrowserRouter as Router, Route, Switch,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
import { Container } from '@material-ui/core';
|
||||||
|
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||||
|
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
|
||||||
|
|
||||||
import NavBar from './components/NavBar';
|
import NavBar from './components/NavBar';
|
||||||
import Home from './screens/Home';
|
import Home from './screens/Home';
|
||||||
import Sources from './screens/Sources';
|
import Sources from './screens/Sources';
|
||||||
@@ -11,43 +19,77 @@ import Manga from './screens/Manga';
|
|||||||
import Reader from './screens/Reader';
|
import Reader from './screens/Reader';
|
||||||
import Search from './screens/SearchSingle';
|
import Search from './screens/SearchSingle';
|
||||||
import NavBarTitle from './context/NavbarTitle';
|
import NavBarTitle from './context/NavbarTitle';
|
||||||
|
import DarkTheme from './context/DarkTheme';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [title, setTitle] = useState<string>('Tachidesk');
|
const [title, setTitle] = useState<string>('Tachidesk');
|
||||||
const contextValue = { title, setTitle };
|
const [darkTheme, setDarkTheme] = useState<boolean>(true);
|
||||||
|
const navTitleContext = { title, setTitle };
|
||||||
|
const darkThemeContext = { darkTheme, setDarkTheme };
|
||||||
|
|
||||||
|
const theme = React.useMemo(
|
||||||
|
() => createMuiTheme({
|
||||||
|
palette: {
|
||||||
|
type: darkTheme ? 'dark' : 'light',
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
MuiCssBaseline: {
|
||||||
|
'@global': {
|
||||||
|
'*::-webkit-scrollbar': {
|
||||||
|
width: '10px',
|
||||||
|
background: darkTheme ? '#222' : '#e1e1e1',
|
||||||
|
|
||||||
|
},
|
||||||
|
'*::-webkit-scrollbar-thumb': {
|
||||||
|
background: darkTheme ? '#111' : '#aaa',
|
||||||
|
borderRadius: '5px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[darkTheme],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<NavBarTitle.Provider value={contextValue}>
|
|
||||||
<NavBar />
|
|
||||||
|
|
||||||
<Switch>
|
<ThemeProvider theme={theme}>
|
||||||
<Route path="/sources/:sourceId/search/">
|
<NavBarTitle.Provider value={navTitleContext}>
|
||||||
<Search />
|
<CssBaseline />
|
||||||
</Route>
|
<DarkTheme.Provider value={darkThemeContext}>
|
||||||
<Route path="/extensions">
|
<NavBar />
|
||||||
<Extensions />
|
</DarkTheme.Provider>
|
||||||
</Route>
|
<Container maxWidth={false} disableGutters>
|
||||||
<Route path="/sources/:sourceId/popular/">
|
<Switch>
|
||||||
<MangaList popular />
|
<Route path="/sources/:sourceId/search/">
|
||||||
</Route>
|
<Search />
|
||||||
<Route path="/sources/:sourceId/latest/">
|
</Route>
|
||||||
<MangaList popular={false} />
|
<Route path="/extensions">
|
||||||
</Route>
|
<Extensions />
|
||||||
<Route path="/sources">
|
</Route>
|
||||||
<Sources />
|
<Route path="/sources/:sourceId/popular/">
|
||||||
</Route>
|
<MangaList popular />
|
||||||
<Route path="/manga/:mangaId/chapter/:chapterId">
|
</Route>
|
||||||
<Reader />
|
<Route path="/sources/:sourceId/latest/">
|
||||||
</Route>
|
<MangaList popular={false} />
|
||||||
<Route path="/manga/:id">
|
</Route>
|
||||||
<Manga />
|
<Route path="/sources">
|
||||||
</Route>
|
<Sources />
|
||||||
<Route path="/">
|
</Route>
|
||||||
<Home />
|
<Route path="/manga/:mangaId/chapter/:chapterId">
|
||||||
</Route>
|
<Reader />
|
||||||
</Switch>
|
</Route>
|
||||||
</NavBarTitle.Provider>
|
<Route path="/manga/:id">
|
||||||
|
<Manga />
|
||||||
|
</Route>
|
||||||
|
<Route path="/">
|
||||||
|
<Home />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Container>
|
||||||
|
</NavBarTitle.Provider>
|
||||||
|
</ThemeProvider>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 React, { useState } from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
@@ -42,7 +46,7 @@ export default function ExtensionCard(props: IProps) {
|
|||||||
name, lang, versionName, iconUrl, installed, apkName,
|
name, lang, versionName, iconUrl, installed, apkName,
|
||||||
},
|
},
|
||||||
} = props;
|
} = props;
|
||||||
const [installedState, setInstalledState] = useState<string>((installed ? 'installed' : 'install'));
|
const [installedState, setInstalledState] = useState<string>((installed ? 'uninstall' : 'install'));
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const langPress = lang === 'all' ? 'All' : lang.toUpperCase();
|
const langPress = lang === 'all' ? 'All' : lang.toUpperCase();
|
||||||
@@ -50,10 +54,25 @@ export default function ExtensionCard(props: IProps) {
|
|||||||
function install() {
|
function install() {
|
||||||
setInstalledState('installing');
|
setInstalledState('installing');
|
||||||
fetch(`http://127.0.0.1:4567/api/v1/extension/install/${apkName}`).then(() => {
|
fetch(`http://127.0.0.1:4567/api/v1/extension/install/${apkName}`).then(() => {
|
||||||
setInstalledState('installed');
|
setInstalledState('uninstall');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uninstall() {
|
||||||
|
setInstalledState('uninstalling');
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/extension/uninstall/${apkName}`).then(() => {
|
||||||
|
setInstalledState('install');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleButtonClick() {
|
||||||
|
if (installedState === 'install') {
|
||||||
|
install();
|
||||||
|
} else {
|
||||||
|
uninstall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className={classes.root}>
|
<CardContent className={classes.root}>
|
||||||
@@ -76,7 +95,7 @@ export default function ExtensionCard(props: IProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button variant="outlined" onClick={() => install()}>{installedState}</Button>
|
<Button variant="outlined" onClick={() => handleButtonClick()}>{installedState}</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
@@ -5,6 +9,7 @@ import CardActionArea from '@material-ui/core/CardActionArea';
|
|||||||
import CardMedia from '@material-ui/core/CardMedia';
|
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';
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: {
|
root: {
|
||||||
@@ -38,8 +43,6 @@ const useStyles = makeStyles({
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
manga: IManga
|
manga: IManga
|
||||||
// eslint-disable-next-line react/no-unused-prop-types, react/require-default-props
|
|
||||||
// ref?: false | React.MutableRefObject<HTMLInputElement | undefined>
|
|
||||||
}
|
}
|
||||||
const MangaCard = React.forwardRef((props: IProps, ref) => {
|
const MangaCard = React.forwardRef((props: IProps, ref) => {
|
||||||
const {
|
const {
|
||||||
@@ -50,23 +53,25 @@ const MangaCard = React.forwardRef((props: IProps, ref) => {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`/manga/${id}/`}>
|
<Grid item xs={6} sm={4} md={3} lg={2}>
|
||||||
<Card className={classes.root} ref={ref}>
|
<Link to={`/manga/${id}/`}>
|
||||||
<CardActionArea>
|
<Card className={classes.root} ref={ref}>
|
||||||
<div className={classes.wrapper}>
|
<CardActionArea>
|
||||||
<CardMedia
|
<div className={classes.wrapper}>
|
||||||
className={classes.image}
|
<CardMedia
|
||||||
component="img"
|
className={classes.image}
|
||||||
alt={title}
|
component="img"
|
||||||
image={thumbnailUrl}
|
alt={title}
|
||||||
title={title}
|
image={thumbnailUrl}
|
||||||
/>
|
title={title}
|
||||||
<div className={classes.gradient} />
|
/>
|
||||||
<Typography className={classes.title} variant="h5" component="h2">{title}</Typography>
|
<div className={classes.gradient} />
|
||||||
</div>
|
<Typography className={classes.title} variant="h5" component="h2">{title}</Typography>
|
||||||
</CardActionArea>
|
</div>
|
||||||
</Card>
|
</CardActionArea>
|
||||||
</Link>
|
</Card>
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface IProps{
|
interface IProps{
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
/* 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, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Grid from '@material-ui/core/Grid';
|
||||||
import MangaCard from './MangaCard';
|
import MangaCard from './MangaCard';
|
||||||
|
|
||||||
interface IProps{
|
interface IProps{
|
||||||
@@ -41,12 +46,11 @@ export default function MangaGrid(props: IProps) {
|
|||||||
return <MangaCard manga={it} />;
|
return <MangaCard manga={it} />;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, auto)', gridGap: '1em' }}>
|
<Grid container spacing={1} xs={12} style={{ margin: 0, padding: '5px' }}>
|
||||||
{mapped}
|
{mapped}
|
||||||
</div>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
|
/* 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, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import MoreIcon from '@material-ui/icons/MoreVert';
|
||||||
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';
|
||||||
import Typography from '@material-ui/core/Typography';
|
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 MenuItem from '@material-ui/core/MenuItem';
|
||||||
|
import Menu from '@material-ui/core/Menu';
|
||||||
|
|
||||||
import TemporaryDrawer from './TemporaryDrawer';
|
import TemporaryDrawer from './TemporaryDrawer';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
import DarkTheme from '../context/DarkTheme';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -20,14 +29,35 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// const theme = createMuiTheme({
|
||||||
|
// overrides: {
|
||||||
|
// MuiAppBar: {
|
||||||
|
// colorPrimary: { backgroundColor: '#FFC0CB' },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// palette: { type: 'dark' },
|
||||||
|
// });
|
||||||
|
|
||||||
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 [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
const { title } = useContext(NavBarTitle);
|
const { title } = useContext(NavBarTitle);
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
|
||||||
|
|
||||||
|
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<AppBar position="static">
|
<AppBar position="static" color={darkTheme ? 'default' : 'primary'}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton
|
<IconButton
|
||||||
edge="start"
|
edge="start"
|
||||||
@@ -42,6 +72,42 @@ export default function NavBar() {
|
|||||||
<Typography variant="h6" className={classes.title}>
|
<Typography variant="h6" className={classes.title}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleMenu}
|
||||||
|
aria-label="display more actions"
|
||||||
|
edge="end"
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => { setDarkTheme(true); handleClose(); }}
|
||||||
|
>
|
||||||
|
Dark Theme
|
||||||
|
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => { setDarkTheme(false); handleClose(); }}
|
||||||
|
>
|
||||||
|
Light Theme
|
||||||
|
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
<TemporaryDrawer drawerOpen={drawerOpen} setDrawerOpen={setDrawerOpen} />
|
<TemporaryDrawer drawerOpen={drawerOpen} setDrawerOpen={setDrawerOpen} />
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import Drawer from '@material-ui/core/Drawer';
|
import Drawer from '@material-ui/core/Drawer';
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* 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 from 'react';
|
||||||
|
|
||||||
|
type ContextType = {
|
||||||
|
darkTheme: boolean
|
||||||
|
setDarkTheme: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
};
|
||||||
|
|
||||||
|
const DarkTheme = React.createContext<ContextType>({
|
||||||
|
darkTheme: true,
|
||||||
|
setDarkTheme: ():void => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DarkTheme;
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|||||||
Vendored
+4
@@ -1 +1,5 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
/// <reference types="react-scripts" />
|
/// <reference types="react-scripts" />
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 { ReportHandler } from 'web-vitals';
|
import { ReportHandler } from 'web-vitals';
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 React, { useContext, useEffect, useState } from 'react';
|
||||||
import ExtensionCard from '../components/ExtensionCard';
|
import ExtensionCard from '../components/ExtensionCard';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
@@ -6,7 +10,6 @@ export default function Extensions() {
|
|||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
setTitle('Extensions');
|
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')
|
||||||
@@ -15,10 +18,7 @@ export default function Extensions() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (extensions.length === 0) {
|
if (extensions.length === 0) {
|
||||||
mapped = <h3>wait</h3>;
|
return <h3>wait</h3>;
|
||||||
} else {
|
|
||||||
mapped = extensions.map((it) => <ExtensionCard extension={it} />);
|
|
||||||
}
|
}
|
||||||
|
return <>{extensions.map((it) => <ExtensionCard extension={it} />)}</>;
|
||||||
return <h2>{mapped}</h2>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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, { useEffect, useState, useContext } 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';
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 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';
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 React, { useContext, useEffect, useState } from 'react';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
@@ -82,7 +86,7 @@ export default function SearchSingle() {
|
|||||||
<form className={classes.root} noValidate autoComplete="off">
|
<form className={classes.root} noValidate autoComplete="off">
|
||||||
<TextField inputRef={textInput} error={error} id="standard-basic" label="Search text.." />
|
<TextField inputRef={textInput} error={error} id="standard-basic" label="Search text.." />
|
||||||
<Button variant="contained" color="primary" onClick={() => processInput()}>
|
<Button variant="contained" color="primary" onClick={() => processInput()}>
|
||||||
Primary
|
Search
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
{mangaGrid}
|
{mangaGrid}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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 React, { useContext, useEffect, useState } from 'react';
|
||||||
import SourceCard from '../components/SourceCard';
|
import SourceCard from '../components/SourceCard';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
@@ -6,7 +10,6 @@ export default function Sources() {
|
|||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
setTitle('Sources');
|
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')
|
||||||
@@ -15,10 +18,7 @@ export default function Sources() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (sources.length === 0) {
|
if (sources.length === 0) {
|
||||||
mapped = <h3>wait</h3>;
|
return (<h3>wait</h3>);
|
||||||
} else {
|
|
||||||
mapped = sources.map((it) => <SourceCard source={it} />);
|
|
||||||
}
|
}
|
||||||
|
return <>{sources.map((it) => <SourceCard source={it} />)}</>;
|
||||||
return <h2>{mapped}</h2>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
@@ -1,3 +1,7 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
interface IExtension {
|
interface IExtension {
|
||||||
name: string
|
name: string
|
||||||
lang: string
|
lang: string
|
||||||
|
|||||||
Reference in New Issue
Block a user