Compare commits

...

24 Commits

Author SHA1 Message Date
Aria Moradi 083996a48d wating on: https://github.com/Kotlin/kotlinx.coroutines/issues/261
CI Publish / Validate Gradle Wrapper (push) Successful in 11s
CI Publish / Build FatJar (push) Failing after 17s
2021-05-17 14:30:59 +04:30
Aria Moradi 9d38f478e3 fix slow manga thumbnails issue, next manga reset page issue 2021-05-17 14:22:24 +04:30
Aria Moradi 57274a0a01 remove unused electron script 2021-05-17 12:32:15 +04:30
Aria Moradi b3b56b7fc8 also build windows package for publish 2021-05-17 12:30:15 +04:30
Forgenn 0b690577da Load next chapter when getting to the last page (#84)
* Load next chapter when scrolling to the bottom (Webtoon, Continues Vertical)

* Load next chapter when scrolling to the bottom (Paged reader)

* Added missing types to IReaderProps

* Move load next chapter when at last page to VerticalReader

* Dependency fix

* Use react history for loading next page
2021-05-17 12:27:14 +04:30
Aria Moradi e9683a3a37 bump to v0.3.3 2021-05-17 12:02:01 +04:30
Aria Moradi f8f67b3eba finish up 2021-05-17 11:59:59 +04:30
Aria Moradi 7b16b082d8 needs kt 2021-05-17 11:55:49 +04:30
Aria Moradi 2a783f0d8e btter folder name 2021-05-17 11:54:33 +04:30
Aria Moradi 42ae32de33 give the correct path 2021-05-17 11:40:38 +04:30
Aria Moradi cec7ddc486 update file permissions 2021-05-17 11:28:26 +04:30
Aria Moradi 9c55fc3868 make windows package with packr 2021-05-17 11:20:24 +04:30
Syer10 104c5a8d83 Code cleanup (#85)
* GC Unused or only used once objects

* Move things around a bit

* Revert some changes

* Fix imports

* Revert about change

* Put back logger

* Private logger

* Revert systemtray

* Move import
2021-05-17 02:48:01 +04:30
Aria Moradi 7450b16742 - Reader -> Pager
- add cloneObject
- add missing copyright notices
2021-05-17 01:38:59 +04:30
Aria Moradi 3ecd0931a1 missing from the previous commit 2021-05-17 01:37:25 +04:30
Aria Moradi 2f2a52ae2f Move navbars 2021-05-17 01:36:31 +04:30
Aria Moradi f464087c30 also handle Errors from java 2021-05-16 23:58:32 +04:30
Aria Moradi 2364960388 Merge branch 'master' of github.com:Suwayomi/Tachidesk 2021-05-16 22:15:35 +04:30
Aria Moradi 76be4d64cd update launch4j's jre and make it 64bit 2021-05-16 12:39:05 +04:30
Aria Moradi 7d98e8ce47 [SKIP CI] correct link 2021-05-16 01:53:35 +04:30
Aria Moradi 40831fc681 [SKIP CI] new links 2021-05-16 01:52:01 +04:30
Aria Moradi e38e7ccf26 [SKIP CI] troubleshooting from the wiki 2021-05-16 01:48:49 +04:30
Aria Moradi 98b9e2f2cf [SKIP CI] no need to delete data anymore... 2021-05-16 01:47:33 +04:30
Aria Moradi 4bf3c12f76 fixed when spinner stops just after first offline chapter fetch 2021-05-16 01:07:48 +04:30
35 changed files with 381 additions and 296 deletions
+1
View File
@@ -10,6 +10,7 @@ cp -f $new_jar_build Tachidesk-latest.jar
rm -rf latest_pointer/* rm -rf latest_pointer/*
cp $new_jar_build latest_pointer cp $new_jar_build latest_pointer
cp ../master/server/build/Tachidesk-*.zip latest_pointer
latest=$(ls *.jar | tail -n1 | sed -e's/Tachidesk-\|.jar//g') latest=$(ls *.jar | tail -n1 | sed -e's/Tachidesk-\|.jar//g')
echo "{ \"latest\": \"$latest\" }" > index.json echo "{ \"latest\": \"$latest\" }" > index.json
+7 -2
View File
@@ -59,16 +59,21 @@ jobs:
**/react/node_modules **/react/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/react/yarn.lock') }} key: ${{ runner.os }}-${{ hashFiles('**/react/yarn.lock') }}
- name: Build and copy webUI, Build Jar and launch4j - name: Build and copy webUI, Build Jar
uses: eskatos/gradle-command-action@v1 uses: eskatos/gradle-command-action@v1
with: with:
build-root-directory: master build-root-directory: master
wrapper-directory: master wrapper-directory: master
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace arguments: :webUI:copyBuild :server:shadowJar --stacktrace
wrapper-cache-enabled: true wrapper-cache-enabled: true
dependencies-cache-enabled: true dependencies-cache-enabled: true
configuration-cache-enabled: true configuration-cache-enabled: true
- name: make windows package
run: |
cd master/scripts
./windows-bundler.sh
- name: Checkout preview branch - name: Checkout preview branch
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
+9 -34
View File
@@ -56,54 +56,29 @@ jobs:
with: with:
path: | path: |
**/react/node_modules **/react/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-${{ hashFiles('**/react/yarn.lock') }}
- name: Build and copy webUI, Build Jar and launch4j - name: Build and copy webUI, Build Jar
uses: eskatos/gradle-command-action@v1 uses: eskatos/gradle-command-action@v1
with: with:
build-root-directory: master build-root-directory: master
wrapper-directory: master wrapper-directory: master
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace arguments: :webUI:copyBuild :server:shadowJar --stacktrace
wrapper-cache-enabled: true wrapper-cache-enabled: true
dependencies-cache-enabled: true dependencies-cache-enabled: true
configuration-cache-enabled: true configuration-cache-enabled: true
- name: make windows package
run: |
cd master/scripts
./windows-bundler.sh
- name: Upload Release - name: Upload Release
uses: xresloader/upload-to-github-release@master uses: xresloader/upload-to-github-release@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
file: "master/server/build/*.jar;master/server/build/*-win32.zip" file: "master/server/build/*.jar;master/server/build/*.zip"
tags: true tags: true
draft: true draft: true
verbose: true verbose: true
# - name: Create Release
# id: create_release
# uses: actions/create-release@v1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# tag_name: ${{ github.ref }}
# release_name: Release ${{ github.ref }}
# body: |
# Release body
# draft: false
# prerelease: true
#
# - name: Get the Ref
# id: get-ref
# uses: ankitvgupta/ref-to-tag-action@master
# with:
# ref: ${{ github.ref }}
# head_ref: ${{ github.head_ref }}
#
# - name: Get the tag
# run: echo "The tag was ${{ steps.get-ref.outputs.tag }}"
#
# - name: Upload Release
# uses: AButler/upload-release-assets@v2.0
# with:
# files: 'master/repo/*'
# repo-token: ${{ secrets.GITHUB_TOKEN }}
# release-tag: ${{ steps.get-ref.outputs.tag }}
+1 -1
View File
@@ -24,7 +24,7 @@ Here is a list of current features:
- Ability to download Mangas for offline read(This partially works) - Ability to download Mangas for offline read(This partially works)
- Backup and restore support powered by Tachiyomi Legacy Backups - Backup and restore support powered by Tachiyomi Legacy Backups
**Note:** Keep in mind that Tachidesk is alpha software and can break rarely and/or with each update, so you may have to delete your data to fix it. See [General troubleshooting](#general-troubleshooting) and [Support and help](#support-and-help) if it happens. **Note:** Keep in mind that Tachidesk is alpha software and can break rarely and/or with each update. See [Troubleshooting](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting) if it happens.
## Downloading and Running the app ## Downloading and Running the app
### All Operating Systems ### All Operating Systems
+1
View File
@@ -0,0 +1 @@
jre\bin\java -Dir.armor.tachidesk.debugLogsEnabled=true -jar Tachidesk.jar
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# Copyright (C) Contributors to the Suwayomi project
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
echo "Downloading packr jar..."
packr="packr-all-4.0.0.jar"
curl -L "https://github.com/libgdx/packr/releases/download/4.0.0/packr-all-4.0.0.jar" -o $packr
echo "Downloading jre..."
jre="OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip"
curl -L "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u292-b10/OpenJDK8U-jre_x64_windows_hotspot_8u292b10.zip" -o $jre
echo "creating windows bundle"
jar=$(ls ../server/build/Tachidesk-*.jar)
jar_name=$(echo $jar | cut -d'/' -f4)
release_name=$(echo $jar_name | cut -d'.' -f4 --complement)-win64
cp $jar "Tachidesk.jar"
java -jar $packr \
--platform windows64 \
--jdk $jre \
--executable Tachidesk \
--classpath Tachidesk.jar \
--mainclass ir.armor.tachidesk.MainKt \
--vmargs Xmx4G \
--output $release_name
cp resources/Tachidesk-debug.bat $release_name
zip_name=$release_name.zip
zip -9 -r $zip_name $release_name
cp $zip_name ../server/build/
+3 -53
View File
@@ -83,7 +83,7 @@ dependencies {
testImplementation(kotlin("test-junit5")) testImplementation(kotlin("test-junit5"))
} }
val MainClass = "ir.armor.tachidesk.Main" val MainClass = "ir.armor.tachidesk.MainKt"
application { application {
mainClass.set(MainClass) mainClass.set(MainClass)
} }
@@ -97,7 +97,7 @@ sourceSets {
} }
// should be bumped with each stable release // should be bumped with each stable release
val tachideskVersion = "v0.3.2" val tachideskVersion = "v0.3.3"
// counts commit count on master // counts commit count on master
val tachideskRevision = Runtime val tachideskRevision = Runtime
@@ -131,7 +131,7 @@ launch4j { //used for windows
bundledJrePath = "jre" bundledJrePath = "jre"
bundledJre64Bit = true bundledJre64Bit = true
jreMinVersion = "8" jreMinVersion = "8"
outputDir = "${rootProject.name}-$tachideskVersion-$tachideskRevision-win32" outputDir = "${rootProject.name}-$tachideskVersion-$tachideskRevision-win64"
icon = "${projectDir}/src/main/resources/icon/faviconlogo.ico" icon = "${projectDir}/src/main/resources/icon/faviconlogo.ico"
jar = "${projectDir}/build/${rootProject.name}-$tachideskVersion-$tachideskRevision.jar" jar = "${projectDir}/build/${rootProject.name}-$tachideskVersion-$tachideskRevision.jar"
} }
@@ -169,56 +169,6 @@ tasks {
useJUnit() useJUnit()
} }
register<Zip>("windowsPackage") {
from(fileTree("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32"))
destinationDirectory.set(File("$buildDir"))
archiveFileName.set("${rootProject.name}-$tachideskVersion-$tachideskRevision-win32.zip")
dependsOn("windowsPackageWorkaround2")
}
register<Delete>("windowsPackageWorkaround2") {
delete(
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/jre",
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/lib",
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/server.exe",
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/Tachidesk-$tachideskVersion-$tachideskRevision-win32/Tachidesk-$tachideskVersion-$tachideskRevision-win32"
)
dependsOn("windowsPackageWorkaround")
}
register<Copy>("windowsPackageWorkaround") {
from("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32")
into("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32")
dependsOn("deleteUnwantedJreDir")
}
register<Delete>("deleteUnwantedJreDir") {
delete(
"$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32/jdk8u282-b08-jre"
)
dependsOn("addJreToDistributable")
}
register<Copy>("addJreToDistributable") {
from(zipTree("$buildDir/OpenJDK8U-jre_x86-32_windows_hotspot_8u282b08.zip"))
into("$buildDir/${rootProject.name}-$tachideskVersion-$tachideskRevision-win32")
eachFile {
path = path.replace(".*-jre".toRegex(), "jre")
}
dependsOn("downloadJre")
dependsOn("createExe")
}
named("createExe") {
dependsOn("shadowJar")
}
register<de.undercouch.gradle.tasks.download.Download>("downloadJre") {
src("https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u282-b08/OpenJDK8U-jre_x86-32_windows_hotspot_8u282b08.zip")
dest("$buildDir/OpenJDK8U-jre_x86-32_windows_hotspot_8u282b08.zip")
overwrite(false)
onlyIfModified(true)
}
withType<ShadowJar> { withType<ShadowJar> {
destinationDirectory.set(File("$rootDir/server/build")) destinationDirectory.set(File("$rootDir/server/build"))
@@ -10,13 +10,7 @@ package ir.armor.tachidesk
import ir.armor.tachidesk.server.JavalinSetup.javalinSetup import ir.armor.tachidesk.server.JavalinSetup.javalinSetup
import ir.armor.tachidesk.server.applicationSetup import ir.armor.tachidesk.server.applicationSetup
class Main { fun main() {
companion object { applicationSetup()
javalinSetup()
@JvmStatic
fun main(args: Array<String>) {
applicationSetup()
javalinSetup()
}
}
} }
@@ -23,8 +23,9 @@ object CachedImageResponse {
} }
private fun findFileNameStartingWith(directoryPath: String, fileName: String): String? { private fun findFileNameStartingWith(directoryPath: String, fileName: String): String? {
val target = "$fileName."
File(directoryPath).listFiles().forEach { file -> File(directoryPath).listFiles().forEach { file ->
if (file.name.startsWith("$fileName.")) if (file.name.startsWith(target))
return "$directoryPath/${file.name}" return "$directoryPath/${file.name}"
} }
return null return null
@@ -15,7 +15,7 @@ import org.kodein.di.DI
import org.kodein.di.conf.global import org.kodein.di.conf.global
import org.kodein.di.instance import org.kodein.di.instance
object DBMangaer { object DBManager {
val db by lazy { val db by lazy {
val applicationDirs by DI.global.instance<ApplicationDirs>() val applicationDirs by DI.global.instance<ApplicationDirs>()
Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver") Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver")
@@ -24,7 +24,7 @@ object DBMangaer {
fun databaseUp() { fun databaseUp() {
// must mention db object so the lazy block executes // must mention db object so the lazy block executes
val db = DBMangaer.db val db = DBManager.db
db.useNestedTransactions = true db.useNestedTransactions = true
val migrations = loadMigrationsFrom("ir.armor.tachidesk.model.database.migration") val migrations = loadMigrationsFrom("ir.armor.tachidesk.model.database.migration")
@@ -14,104 +14,121 @@ import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@Suppress("ClassName", "unused")
class M0001_Initial : Migration() { class M0001_Initial : Migration() {
private object ExtensionTable : IntIdTable() { private class ExtensionTable : IntIdTable() {
val apkName = varchar("apk_name", 1024) init {
varchar("apk_name", 1024)
// default is the local source icon from tachiyomi
varchar("icon_url", 2048)
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp")
varchar("name", 128)
varchar("pkg_name", 128)
varchar("version_name", 16)
integer("version_code")
varchar("lang", 10)
bool("is_nsfw")
// default is the local source icon from tachiyomi bool("is_installed").default(false)
val iconUrl = varchar("icon_url", 2048) bool("has_update").default(false)
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp") bool("is_obsolete").default(false)
val name = varchar("name", 128) varchar("class_name", 1024).default("") // fully qualified name
val pkgName = varchar("pkg_name", 128) }
val versionName = varchar("version_name", 16)
val versionCode = integer("version_code")
val lang = varchar("lang", 10)
val isNsfw = bool("is_nsfw")
val isInstalled = bool("is_installed").default(false)
val hasUpdate = bool("has_update").default(false)
val isObsolete = bool("is_obsolete").default(false)
val classFQName = varchar("class_name", 1024).default("") // fully qualified name
} }
private object SourceTable : IdTable<Long>() { private class SourceTable(extensionTable: ExtensionTable) : IdTable<Long>() {
override val id = long("id").entityId() override val id = long("id").entityId()
val name = varchar("name", 128) init {
val lang = varchar("lang", 10) varchar("name", 128)
val extension = reference("extension", ExtensionTable) varchar("lang", 10)
val partOfFactorySource = bool("part_of_factory_source").default(false) reference("extension", extensionTable)
bool("part_of_factory_source").default(false)
}
} }
private object MangaTable : IntIdTable() { private class MangaTable : IntIdTable() {
val url = varchar("url", 2048) init {
val title = varchar("title", 512) varchar("url", 2048)
val initialized = bool("initialized").default(false) varchar("title", 512)
bool("initialized").default(false)
val artist = varchar("artist", 64).nullable() varchar("artist", 64).nullable()
val author = varchar("author", 64).nullable() varchar("author", 64).nullable()
val description = varchar("description", 4096).nullable() varchar("description", 4096).nullable()
val genre = varchar("genre", 1024).nullable() varchar("genre", 1024).nullable()
// val status = enumeration("status", MangaStatus::class).default(MangaStatus.UNKNOWN) // val status = enumeration("status", MangaStatus::class).default(MangaStatus.UNKNOWN)
val status = integer("status").default(SManga.UNKNOWN) integer("status").default(SManga.UNKNOWN)
val thumbnail_url = varchar("thumbnail_url", 2048).nullable() varchar("thumbnail_url", 2048).nullable()
val inLibrary = bool("in_library").default(false) bool("in_library").default(false)
val defaultCategory = bool("default_category").default(true) bool("default_category").default(true)
// source is used by some ancestor of IntIdTable // source is used by some ancestor of IntIdTable
val sourceReference = long("source") long("source")
}
} }
private object ChapterTable : IntIdTable() { private class ChapterTable(mangaTable: MangaTable) : IntIdTable() {
val url = varchar("url", 2048) init {
val name = varchar("name", 512) varchar("url", 2048)
val date_upload = long("date_upload").default(0) varchar("name", 512)
val chapter_number = float("chapter_number").default(-1f) long("date_upload").default(0)
val scanlator = varchar("scanlator", 128).nullable() float("chapter_number").default(-1f)
varchar("scanlator", 128).nullable()
val isRead = bool("read").default(false) bool("read").default(false)
val isBookmarked = bool("bookmark").default(false) bool("bookmark").default(false)
val lastPageRead = integer("last_page_read").default(0) integer("last_page_read").default(0)
val chapterIndex = integer("number_in_list") integer("number_in_list")
reference("manga", mangaTable)
val manga = reference("manga", MangaTable) }
} }
private object PageTable : IntIdTable() { private class PageTable(chapterTable: ChapterTable) : IntIdTable() {
val index = integer("index") init {
val url = varchar("url", 2048) integer("index")
val imageUrl = varchar("imageUrl", 2048).nullable() varchar("url", 2048)
varchar("imageUrl", 2048).nullable()
val chapter = reference("chapter", ChapterTable) reference("chapter", chapterTable)
}
} }
private object CategoryTable : IntIdTable() { private class CategoryTable : IntIdTable() {
val name = varchar("name", 64) init {
val isLanding = bool("is_landing").default(false) varchar("name", 64)
val order = integer("order").default(0) bool("is_landing").default(false)
integer("order").default(0)
}
} }
private object CategoryMangaTable : IntIdTable() { private class CategoryMangaTable : IntIdTable() {
val category = reference("category", ir.armor.tachidesk.model.database.table.CategoryTable) init {
val manga = reference("manga", ir.armor.tachidesk.model.database.table.MangaTable) reference("category", ir.armor.tachidesk.model.database.table.CategoryTable)
reference("manga", ir.armor.tachidesk.model.database.table.MangaTable)
}
} }
/** initial migration, create all tables */ /** initial migration, create all tables */
override fun run() { override fun run() {
transaction { transaction {
val extensionTable = ExtensionTable()
val sourceTable = SourceTable(extensionTable)
val mangaTable = MangaTable()
val chapterTable = ChapterTable(mangaTable)
val pageTable = PageTable(chapterTable)
val categoryTable = CategoryTable()
val categoryMangaTable = CategoryMangaTable()
SchemaUtils.create( SchemaUtils.create(
ExtensionTable, extensionTable,
ExtensionTable, sourceTable,
SourceTable, mangaTable,
MangaTable, chapterTable,
ChapterTable, pageTable,
PageTable, categoryTable,
CategoryTable, categoryMangaTable,
CategoryMangaTable,
) )
} }
} }
@@ -11,6 +11,7 @@ import ir.armor.tachidesk.model.database.migration.lib.Migration
import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.currentDialect import org.jetbrains.exposed.sql.vendors.currentDialect
@Suppress("ClassName", "unused")
class M0002_ChapterTableIndexRename : Migration() { class M0002_ChapterTableIndexRename : Migration() {
/** this migration renamed ChapterTable.NUMBER_IN_LIST to ChapterTable.INDEX */ /** this migration renamed ChapterTable.NUMBER_IN_LIST to ChapterTable.INDEX */
override fun run() { override fun run() {
@@ -54,6 +54,7 @@ fun runMigrations(migrations: List<Migration>, database: Database = TransactionM
logger.info { "Migrations finished successfully" } logger.info { "Migrations finished successfully" }
} }
@Suppress("UnstableApiUsage")
fun loadMigrationsFrom(classPath: String): List<Migration> { fun loadMigrationsFrom(classPath: String): List<Migration> {
return ClassPath.from(Thread.currentThread().contextClassLoader) return ClassPath.from(Thread.currentThread().contextClassLoader)
.getTopLevelClasses(classPath) .getTopLevelClasses(classPath)
@@ -18,8 +18,8 @@ object CategoryTable : IntIdTable() {
} }
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass( fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
categoryEntry[CategoryTable.id].value, categoryEntry[this.id].value,
categoryEntry[CategoryTable.order], categoryEntry[this.order],
categoryEntry[CategoryTable.name], categoryEntry[this.name],
categoryEntry[CategoryTable.isLanding], categoryEntry[this.isLanding],
) )
@@ -30,14 +30,14 @@ object ChapterTable : IntIdTable() {
fun ChapterTable.toDataClass(chapterEntry: ResultRow) = fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
ChapterDataClass( ChapterDataClass(
chapterEntry[ChapterTable.url], chapterEntry[this.url],
chapterEntry[ChapterTable.name], chapterEntry[this.name],
chapterEntry[ChapterTable.date_upload], chapterEntry[this.date_upload],
chapterEntry[ChapterTable.chapter_number], chapterEntry[this.chapter_number],
chapterEntry[ChapterTable.scanlator], chapterEntry[this.scanlator],
chapterEntry[ChapterTable.manga].value, chapterEntry[this.manga].value,
chapterEntry[ChapterTable.isRead], chapterEntry[this.isRead],
chapterEntry[ChapterTable.isBookmarked], chapterEntry[this.isBookmarked],
chapterEntry[ChapterTable.lastPageRead], chapterEntry[this.lastPageRead],
chapterEntry[ChapterTable.chapterIndex], chapterEntry[this.chapterIndex],
) )
@@ -36,21 +36,21 @@ object MangaTable : IntIdTable() {
fun MangaTable.toDataClass(mangaEntry: ResultRow) = fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass( MangaDataClass(
mangaEntry[MangaTable.id].value, mangaEntry[this.id].value,
mangaEntry[MangaTable.sourceReference].toString(), mangaEntry[this.sourceReference].toString(),
mangaEntry[MangaTable.url], mangaEntry[this.url],
mangaEntry[MangaTable.title], mangaEntry[this.title],
proxyThumbnailUrl(mangaEntry[MangaTable.id].value), proxyThumbnailUrl(mangaEntry[this.id].value),
mangaEntry[MangaTable.initialized], mangaEntry[this.initialized],
mangaEntry[MangaTable.artist], mangaEntry[this.artist],
mangaEntry[MangaTable.author], mangaEntry[this.author],
mangaEntry[MangaTable.description], mangaEntry[this.description],
mangaEntry[MangaTable.genre], mangaEntry[this.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, MangaStatus.valueOf(mangaEntry[this.status]).name,
mangaEntry[MangaTable.inLibrary] mangaEntry[this.inLibrary]
) )
enum class MangaStatus(val status: Int) { enum class MangaStatus(val status: Int) {
@@ -1,7 +1,6 @@
package ir.armor.tachidesk.server package ir.armor.tachidesk.server
import io.javalin.Javalin import io.javalin.Javalin
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.impl.Category.createCategory import ir.armor.tachidesk.impl.Category.createCategory
import ir.armor.tachidesk.impl.Category.getCategoryList import ir.armor.tachidesk.impl.Category.getCategoryList
import ir.armor.tachidesk.impl.Category.removeCategory import ir.armor.tachidesk.impl.Category.removeCategory
@@ -37,14 +36,14 @@ import ir.armor.tachidesk.impl.backup.legacy.LegacyBackupImport.restoreLegacyBac
import ir.armor.tachidesk.server.internal.About.getAbout import ir.armor.tachidesk.server.internal.About.getAbout
import ir.armor.tachidesk.server.util.openInBrowser import ir.armor.tachidesk.server.util.openInBrowser
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.future.future import kotlinx.coroutines.future.future
import mu.KotlinLogging import mu.KotlinLogging
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import kotlin.concurrent.thread import kotlin.concurrent.thread
/* /*
@@ -56,7 +55,8 @@ import kotlin.concurrent.thread
object JavalinSetup { object JavalinSetup {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val scope = CoroutineScope(Executors.newFixedThreadPool(200).asCoroutineDispatcher())
private fun <T> future(block: suspend CoroutineScope.() -> T): CompletableFuture<T> { private fun <T> future(block: suspend CoroutineScope.() -> T): CompletableFuture<T> {
return scope.future(block = block) return scope.future(block = block)
@@ -68,7 +68,7 @@ object JavalinSetup {
val app = Javalin.create { config -> val app = Javalin.create { config ->
try { try {
// if the bellow line throws an exception then webUI is not bundled // if the bellow line throws an exception then webUI is not bundled
Main::class.java.getResource("/react/index.html") this::class.java.getResource("/react/index.html")
// no exception so we can tell javalin to serve webUI // no exception so we can tell javalin to serve webUI
hasWebUiBundled = true hasWebUiBundled = true
@@ -21,7 +21,7 @@ class ServerConfig(config: Config) : ConfigModule(config) {
val socksProxyPort: String by config val socksProxyPort: String by config
// misc // misc
val debugLogsEnabled: Boolean by config val debugLogsEnabled: Boolean = System.getProperty("ir.armor.tachidesk.debugLogsEnabled", config.getString("debugLogsEnabled")).toBoolean()
val systemTrayEnabled: Boolean by config val systemTrayEnabled: Boolean by config
val initialOpenInBrowserEnabled: Boolean by config val initialOpenInBrowserEnabled: Boolean by config
@@ -9,7 +9,6 @@ package ir.armor.tachidesk.server
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.model.database.databaseUp import ir.armor.tachidesk.model.database.databaseUp
import ir.armor.tachidesk.server.util.systemTray import ir.armor.tachidesk.server.util.systemTray
import mu.KotlinLogging import mu.KotlinLogging
@@ -81,7 +80,7 @@ fun applicationSetup() {
try { try {
val dataConfFile = File("${applicationDirs.dataRoot}/server.conf") val dataConfFile = File("${applicationDirs.dataRoot}/server.conf")
if (!dataConfFile.exists()) { if (!dataConfFile.exists()) {
Main::class.java.getResourceAsStream("/server-reference.conf").use { input -> JavalinSetup::class.java.getResourceAsStream("/server-reference.conf").use { input ->
dataConfFile.outputStream().use { output -> dataConfFile.outputStream().use { output ->
input.copyTo(output) input.copyTo(output)
} }
@@ -97,7 +96,7 @@ fun applicationSetup() {
if (serverConfig.systemTrayEnabled) { if (serverConfig.systemTrayEnabled) {
try { try {
systemTray systemTray
} catch (e: Exception) { } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error
e.printStackTrace() e.printStackTrace()
} }
} }
@@ -12,15 +12,15 @@ import dorkbox.systemTray.SystemTray
import dorkbox.systemTray.SystemTray.TrayType import dorkbox.systemTray.SystemTray.TrayType
import dorkbox.util.CacheUtil import dorkbox.util.CacheUtil
import dorkbox.util.Desktop import dorkbox.util.Desktop
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.server.BuildConfig import ir.armor.tachidesk.server.BuildConfig
import ir.armor.tachidesk.server.ServerConfig
import ir.armor.tachidesk.server.serverConfig import ir.armor.tachidesk.server.serverConfig
import kotlin.system.exitProcess import kotlin.system.exitProcess
fun openInBrowser() { fun openInBrowser() {
try { try {
Desktop.browseURL("http://127.0.0.1:4567") Desktop.browseURL("http://127.0.0.1:4567")
} catch (e: Exception) { } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error
e.printStackTrace() e.printStackTrace()
} }
} }
@@ -45,11 +45,11 @@ fun systemTray(): SystemTray? {
} }
) )
val icon = Main::class.java.getResource("/icon/faviconlogo.png") val icon = ServerConfig::class.java.getResource("/icon/faviconlogo.png")
// systemTray.setTooltip("Tachidesk") // systemTray.setTooltip("Tachidesk")
systemTray.setImage(icon) systemTray.setImage(icon)
// systemTray.status = "No Mail" // systemTray.status = "No Mail"
mainMenu.add( mainMenu.add(
MenuItem("Quit") { MenuItem("Quit") {
+1 -1
View File
@@ -13,7 +13,7 @@ import { Container } from '@material-ui/core';
import CssBaseline from '@material-ui/core/CssBaseline'; import CssBaseline from '@material-ui/core/CssBaseline';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
import NavBar from './components/NavBar'; import NavBar from './components/navbar/NavBar';
import Sources from './screens/Sources'; import Sources from './screens/Sources';
import Extensions from './screens/Extensions'; import Extensions from './screens/Extensions';
import SourceMangas from './screens/SourceMangas'; import SourceMangas from './screens/SourceMangas';
@@ -18,6 +18,7 @@ import FilterListIcon from '@material-ui/icons/FilterList';
import { List, ListItemSecondaryAction, ListItemText } from '@material-ui/core'; import { List, ListItemSecondaryAction, ListItemText } from '@material-ui/core';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import { langCodeToName } from '../util/language'; import { langCodeToName } from '../util/language';
import cloneObject from '../util/cloneObject';
const useStyles = makeStyles(() => createStyles({ const useStyles = makeStyles(() => createStyles({
paper: { paper: {
@@ -54,7 +55,7 @@ export default function ExtensionLangSelect(props: IProps) {
if (checked) { if (checked) {
setMShownLangs([...mShownLangs, lang]); setMShownLangs([...mShownLangs, lang]);
} else { } else {
const clone = JSON.parse(JSON.stringify(mShownLangs)); const clone = cloneObject(mShownLangs);
clone.splice(clone.indexOf(lang), 1); clone.splice(clone.indexOf(lang), 1);
setMShownLangs(clone); setMShownLangs(clone);
} }
+7
View File
@@ -1,4 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import React from 'react'; import React from 'react';
import Slide, { SlideProps } from '@material-ui/core/Slide'; import Slide, { SlideProps } from '@material-ui/core/Slide';
@@ -12,9 +12,9 @@ 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 NavBarContext from '../context/NavbarContext'; import NavBarContext from '../../context/NavbarContext';
import DarkTheme from '../context/DarkTheme'; import DarkTheme from '../../context/DarkTheme';
import TemporaryDrawer from './TemporaryDrawer'; import TemporaryDrawer from '../TemporaryDrawer';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
@@ -30,8 +30,8 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import Collapse from '@material-ui/core/Collapse'; import Collapse from '@material-ui/core/Collapse';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import DarkTheme from '../context/DarkTheme'; import DarkTheme from '../../context/DarkTheme';
import NavBarContext from '../context/NavbarContext'; import NavBarContext from '../../context/NavbarContext';
const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({ const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
// main container and root div need to change classes... // main container and root div need to change classes...
@@ -46,7 +46,7 @@ const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
position: settings.staticNav ? 'sticky' : 'fixed', position: settings.staticNav ? 'sticky' : 'fixed',
top: 0, top: 0,
left: 0, left: 0,
minWidth: '300px', width: '300px',
height: '100vh', height: '100vh',
overflowY: 'auto', overflowY: 'auto',
backgroundColor: '#0a0b0b', backgroundColor: '#0a0b0b',
@@ -143,6 +143,7 @@ export const defaultReaderSettings = () => ({
staticNav: false, staticNav: false,
showPageNumber: true, showPageNumber: true,
continuesPageGap: false, continuesPageGap: false,
loadNextonEnding: false,
readerType: 'ContinuesVertical', readerType: 'ContinuesVertical',
} as IReaderSettings); } as IReaderSettings);
@@ -277,6 +278,16 @@ export default function ReaderNavBar(props: IProps) {
/> />
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ListItem>
<ListItemText primary="Load next chapter at ending" />
<ListItemSecondaryAction>
<Switch
edge="end"
checked={settings.loadNextonEnding}
onChange={(e) => setSettingValue('loadNextonEnding', e.target.checked)}
/>
</ListItemSecondaryAction>
</ListItem>
<ListItem> <ListItem>
<ListItemText primary="Reader Type" /> <ListItemText primary="Reader Type" />
<Select <Select
@@ -1,3 +1,10 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import React from 'react'; import React from 'react';
@@ -1,36 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import Page from './Page';
const useStyles = makeStyles({
reader: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: '0 auto',
width: '100%',
},
});
export default function VerticalReader(props: IReaderProps) {
const { pages, settings, setCurPage } = props;
const classes = useStyles();
return (
<div className={classes.reader}>
{
pages.map((page) => (
<Page
key={page.index}
index={page.index}
src={page.src}
setCurPage={setCurPage}
settings={settings}
/>
))
}
</div>
);
}
@@ -1,7 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import React from 'react'; import React from 'react';
import Page from './Page'; import Page from '../Page';
const useStyles = makeStyles({ const useStyles = makeStyles({
reader: { reader: {
@@ -21,7 +28,7 @@ interface IProps {
settings: IReaderSettings settings: IReaderSettings
} }
export default function HorizontalReader(props: IProps) { export default function HorizontalPager(props: IProps) {
const { pages, settings, setCurPage } = props; const { pages, settings, setCurPage } = props;
const classes = useStyles(); const classes = useStyles();
@@ -1,7 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect } from 'react'; import React, { useEffect, useRef } from 'react';
import Page from './Page'; import { useHistory } from 'react-router-dom';
import Page from '../Page';
const useStyles = makeStyles({ const useStyles = makeStyles({
reader: { reader: {
@@ -16,13 +24,20 @@ const useStyles = makeStyles({
export default function PagedReader(props: IReaderProps) { export default function PagedReader(props: IReaderProps) {
const { const {
pages, settings, setCurPage, curPage, pages, settings, setCurPage, curPage, manga, chapter,
} = props; } = props;
const classes = useStyles(); const classes = useStyles();
const history = useHistory();
const pageRef = useRef<HTMLDivElement>(null);
function nextPage() { function nextPage() {
if (curPage < pages.length - 1) { setCurPage(curPage + 1); } if (curPage < pages.length - 1) {
setCurPage(curPage + 1);
} else if (settings.loadNextonEnding) {
history.push(`/manga/${manga.id}/chapter/${chapter.index + 1}`);
}
} }
function prevPage() { function prevPage() {
@@ -52,16 +67,16 @@ export default function PagedReader(props: IReaderProps) {
useEffect(() => { useEffect(() => {
document.addEventListener('keyup', keyboardControl, false); document.addEventListener('keyup', keyboardControl, false);
document.addEventListener('click', clickControl); pageRef.current?.addEventListener('click', clickControl);
return () => { return () => {
document.removeEventListener('keyup', keyboardControl); document.removeEventListener('keyup', keyboardControl);
document.removeEventListener('click', clickControl); pageRef.current?.removeEventListener('click', clickControl);
}; };
}, [curPage]); }, [curPage, pageRef]);
return ( return (
<div className={classes.reader}> <div ref={pageRef} className={classes.reader}>
<Page <Page
key={curPage} key={curPage}
index={curPage} index={curPage}
@@ -0,0 +1,61 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import Page from '../Page';
const useStyles = makeStyles({
reader: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: '0 auto',
width: '100%',
},
});
export default function VerticalReader(props: IReaderProps) {
const {
pages, settings, setCurPage, curPage, manga, chapter,
} = props;
const classes = useStyles();
const history = useHistory();
const handleLoadNextonEnding = () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
setCurPage(0);
history.push(`/manga/${manga.id}/chapter/${chapter.index + 1}`);
}
};
useEffect(() => {
if (settings.loadNextonEnding) { window.addEventListener('scroll', handleLoadNextonEnding); }
return () => {
window.removeEventListener('scroll', handleLoadNextonEnding);
};
}, []);
return (
<div className={classes.reader}>
{
pages.map((page) => (
<Page
key={page.index}
index={page.index}
src={page.src}
setCurPage={setCurPage}
settings={settings}
/>
))
}
</div>
);
}
+2 -1
View File
@@ -10,6 +10,7 @@ import React, { useContext, useEffect, useState } from 'react';
import MangaGrid from '../components/MangaGrid'; import MangaGrid from '../components/MangaGrid';
import NavbarContext from '../context/NavbarContext'; import NavbarContext from '../context/NavbarContext';
import client from '../util/client'; import client from '../util/client';
import cloneObject from '../util/cloneObject';
interface IMangaCategory { interface IMangaCategory {
category: ICategory category: ICategory
@@ -98,7 +99,7 @@ export default function Library() {
client.get(`/api/v1/category/${tab.category.id}`) client.get(`/api/v1/category/${tab.category.id}`)
.then((response) => response.data) .then((response) => response.data)
.then((data: IManga[]) => { .then((data: IManga[]) => {
const tabsClone = JSON.parse(JSON.stringify(tabs)); const tabsClone = cloneObject(tabs);
tabsClone[index].mangas = data; tabsClone[index].mangas = data;
tabsClone[index].isFetched = true; tabsClone[index].isFetched = true;
+9 -14
View File
@@ -44,10 +44,6 @@ const useStyles = makeStyles((theme: Theme) => ({
}, },
})); }));
// const InnerItem = React.memo(({ chapters, index }: any) => (
// <ChapterCard chapter={chapters[index]} />
// ));
export default function Manga() { export default function Manga() {
const classes = useStyles(); const classes = useStyles();
const theme = useTheme(); const theme = useTheme();
@@ -60,6 +56,7 @@ export default function Manga() {
const [manga, setManga] = useState<IManga>(); const [manga, setManga] = useState<IManga>();
const [chapters, setChapters] = useState<IChapter[]>([]); const [chapters, setChapters] = useState<IChapter[]>([]);
const [fetchedChapters, setFetchedChapters] = useState(false); const [fetchedChapters, setFetchedChapters] = useState(false);
const [noChaptersFound, setNoChaptersFound] = useState(false);
const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0); const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0);
function triggerChaptersUpdate() { function triggerChaptersUpdate() {
@@ -84,20 +81,13 @@ export default function Manga() {
.then((data) => { .then((data) => {
if (data.length === 0 && fetchedChapters) { if (data.length === 0 && fetchedChapters) {
makeToast('No chapters found', 'warning'); makeToast('No chapters found', 'warning');
setNoChaptersFound(true);
} }
setChapters(data); setChapters(data);
}) })
.then(() => setFetchedChapters(true)); .then(() => setFetchedChapters(true));
}, [chapters.length, fetchedChapters, chapterUpdateTriggerer]); }, [chapters.length, fetchedChapters, chapterUpdateTriggerer]);
// const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />;
const itemContent = (index:any) => (
<ChapterCard
chapter={chapters[index]}
triggerChaptersUpdate={triggerChaptersUpdate}
/>
);
return ( return (
<div className={classes.root}> <div className={classes.root}>
<LoadingPlaceholder <LoadingPlaceholder
@@ -107,7 +97,7 @@ export default function Manga() {
/> />
<LoadingPlaceholder <LoadingPlaceholder
shouldRender={chapters.length > 0 || fetchedChapters} shouldRender={chapters.length > 0 || noChaptersFound}
> >
<Virtuoso <Virtuoso
style={{ // override Virtuoso default values and set them with class style={{ // override Virtuoso default values and set them with class
@@ -115,7 +105,12 @@ export default function Manga() {
}} }}
className={classes.chapters} className={classes.chapters}
totalCount={chapters.length} totalCount={chapters.length}
itemContent={itemContent} itemContent={(index:number) => (
<ChapterCard
chapter={chapters[index]}
triggerChaptersUpdate={triggerChaptersUpdate}
/>
)}
useWindowScroll={window.innerWidth < 960} useWindowScroll={window.innerWidth < 960}
overscan={window.innerHeight * 0.5} overscan={window.innerHeight * 0.5}
/> />
+29 -12
View File
@@ -10,15 +10,16 @@ import CircularProgress from '@material-ui/core/CircularProgress';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import HorizontalReader from '../components/reader/HorizontalReader'; import HorizontalPager from '../components/reader/pager/HorizontalPager';
import Page from '../components/reader/Page'; import Page from '../components/reader/Page';
import PageNumber from '../components/reader/PageNumber'; import PageNumber from '../components/reader/PageNumber';
import PagedReader from '../components/reader/PagedReader'; import WebtoonPager from '../components/reader/pager/PagedPager';
import VerticalReader from '../components/reader/VerticalReader'; import VerticalPager from '../components/reader/pager/VerticalPager';
import ReaderNavBar, { defaultReaderSettings } from '../components/ReaderNavBar'; import ReaderNavBar, { defaultReaderSettings } from '../components/navbar/ReaderNavBar';
import NavbarContext from '../context/NavbarContext'; import NavbarContext from '../context/NavbarContext';
import client from '../util/client'; import client from '../util/client';
import useLocalStorage from '../util/useLocalStorage'; import useLocalStorage from '../util/useLocalStorage';
import cloneObject from '../util/cloneObject';
const useStyles = (settings: IReaderSettings) => makeStyles({ const useStyles = (settings: IReaderSettings) => makeStyles({
root: { root: {
@@ -33,24 +34,24 @@ const useStyles = (settings: IReaderSettings) => makeStyles({
const getReaderComponent = (readerType: ReaderType) => { const getReaderComponent = (readerType: ReaderType) => {
switch (readerType) { switch (readerType) {
case 'ContinuesVertical': case 'ContinuesVertical':
return VerticalReader; return VerticalPager;
break; break;
case 'Webtoon': case 'Webtoon':
return VerticalReader; return VerticalPager;
break; break;
case 'SingleVertical': case 'SingleVertical':
return PagedReader; return WebtoonPager;
break; break;
case 'SingleRTL': case 'SingleRTL':
return PagedReader; return WebtoonPager;
break; break;
case 'SingleLTR': case 'SingleLTR':
return PagedReader; return WebtoonPager;
break; break;
case 'ContinuesHorizontal': case 'ContinuesHorizontal':
return HorizontalReader; return HorizontalPager;
default: default:
return VerticalReader; return VerticalPager;
break; break;
} }
}; };
@@ -69,9 +70,22 @@ export default function Reader() {
const [manga, setManga] = useState<IMangaCard | IManga>({ id: +mangaId, title: '', thumbnailUrl: '' }); const [manga, setManga] = useState<IMangaCard | IManga>({ id: +mangaId, title: '', thumbnailUrl: '' });
const [chapter, setChapter] = useState<IChapter | IPartialChpter>(initialChapter()); const [chapter, setChapter] = useState<IChapter | IPartialChpter>(initialChapter());
const [curPage, setCurPage] = useState<number>(0); const [curPage, setCurPage] = useState<number>(0);
const { setOverride, setTitle } = useContext(NavbarContext); const { setOverride, setTitle } = useContext(NavbarContext);
useEffect(() => { useEffect(() => {
// make sure settings has all the keys
const settingsClone = cloneObject(settings) as any;
const defualtSettings = defaultReaderSettings();
let shouldUpdateSettings = false;
Object.keys(defualtSettings).forEach((key) => {
const keyOf = key as keyof IReaderSettings;
if (settings[keyOf] === undefined) {
settingsClone[keyOf] = defualtSettings[keyOf];
shouldUpdateSettings = true;
}
});
if (shouldUpdateSettings) { setSettings(settingsClone); }
// set the custom navbar
setOverride( setOverride(
{ {
status: true, status: true,
@@ -103,6 +117,7 @@ export default function Reader() {
useEffect(() => { useEffect(() => {
setChapter(initialChapter); setChapter(initialChapter);
setCurPage(0);
client.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`) client.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`)
.then((response) => response.data) .then((response) => response.data)
.then((data:IChapter) => { .then((data:IChapter) => {
@@ -138,6 +153,8 @@ export default function Reader() {
setCurPage={setCurPage} setCurPage={setCurPage}
curPage={curPage} curPage={curPage}
settings={settings} settings={settings}
manga={manga}
chapter={chapter}
/> />
</div> </div>
); );
+3
View File
@@ -99,6 +99,7 @@ type ReaderType =
interface IReaderSettings{ interface IReaderSettings{
staticNav: boolean staticNav: boolean
showPageNumber: boolean showPageNumber: boolean
loadNextonEnding: boolean
readerType: ReaderType readerType: ReaderType
} }
@@ -113,4 +114,6 @@ interface IReaderProps {
setCurPage: React.Dispatch<React.SetStateAction<number>> setCurPage: React.Dispatch<React.SetStateAction<number>>
curPage: number curPage: number
settings: IReaderSettings settings: IReaderSettings
manga: IMangaCard | IManga
chapter: IChapter | IPartialChpter
} }
+10
View File
@@ -0,0 +1,10 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
export default function cloneObject<T extends object>(obj: T) {
return JSON.parse(JSON.stringify(obj)) as T;
}