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/*
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')
echo "{ \"latest\": \"$latest\" }" > index.json
+8 -3
View File
@@ -59,16 +59,21 @@ jobs:
**/react/node_modules
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
with:
build-root-directory: master
wrapper-directory: master
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace
arguments: :webUI:copyBuild :server:shadowJar --stacktrace
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: make windows package
run: |
cd master/scripts
./windows-bundler.sh
- name: Checkout preview branch
uses: actions/checkout@v2
with:
@@ -77,4 +82,4 @@ jobs:
- name: Deploy preview
run: |
./master/.github/scripts/commit-preview.sh
./master/.github/scripts/commit-preview.sh
+9 -34
View File
@@ -56,54 +56,29 @@ jobs:
with:
path: |
**/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
with:
build-root-directory: master
wrapper-directory: master
arguments: :webUI:copyBuild :server:windowsPackage --stacktrace
arguments: :webUI:copyBuild :server:shadowJar --stacktrace
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: make windows package
run: |
cd master/scripts
./windows-bundler.sh
- name: Upload Release
uses: xresloader/upload-to-github-release@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: "master/server/build/*.jar;master/server/build/*-win32.zip"
file: "master/server/build/*.jar;master/server/build/*.zip"
tags: true
draft: 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)
- 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
### 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/
+5 -55
View File
@@ -83,7 +83,7 @@ dependencies {
testImplementation(kotlin("test-junit5"))
}
val MainClass = "ir.armor.tachidesk.Main"
val MainClass = "ir.armor.tachidesk.MainKt"
application {
mainClass.set(MainClass)
}
@@ -97,7 +97,7 @@ sourceSets {
}
// should be bumped with each stable release
val tachideskVersion = "v0.3.2"
val tachideskVersion = "v0.3.3"
// counts commit count on master
val tachideskRevision = Runtime
@@ -131,7 +131,7 @@ launch4j { //used for windows
bundledJrePath = "jre"
bundledJre64Bit = true
jreMinVersion = "8"
outputDir = "${rootProject.name}-$tachideskVersion-$tachideskRevision-win32"
outputDir = "${rootProject.name}-$tachideskVersion-$tachideskRevision-win64"
icon = "${projectDir}/src/main/resources/icon/faviconlogo.ico"
jar = "${projectDir}/build/${rootProject.name}-$tachideskVersion-$tachideskRevision.jar"
}
@@ -168,58 +168,8 @@ tasks {
test {
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> {
destinationDirectory.set(File("$rootDir/server/build"))
dependsOn("formatKotlin", "lintKotlin")
@@ -10,13 +10,7 @@ package ir.armor.tachidesk
import ir.armor.tachidesk.server.JavalinSetup.javalinSetup
import ir.armor.tachidesk.server.applicationSetup
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
applicationSetup()
javalinSetup()
}
}
fun main() {
applicationSetup()
javalinSetup()
}
@@ -23,8 +23,9 @@ object CachedImageResponse {
}
private fun findFileNameStartingWith(directoryPath: String, fileName: String): String? {
val target = "$fileName."
File(directoryPath).listFiles().forEach { file ->
if (file.name.startsWith("$fileName."))
if (file.name.startsWith(target))
return "$directoryPath/${file.name}"
}
return null
@@ -15,7 +15,7 @@ import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
object DBMangaer {
object DBManager {
val db by lazy {
val applicationDirs by DI.global.instance<ApplicationDirs>()
Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver")
@@ -24,7 +24,7 @@ object DBMangaer {
fun databaseUp() {
// must mention db object so the lazy block executes
val db = DBMangaer.db
val db = DBManager.db
db.useNestedTransactions = true
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.transactions.transaction
@Suppress("ClassName", "unused")
class M0001_Initial : Migration() {
private object ExtensionTable : IntIdTable() {
val apkName = varchar("apk_name", 1024)
private class ExtensionTable : IntIdTable() {
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
val iconUrl = varchar("icon_url", 2048)
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp")
bool("is_installed").default(false)
bool("has_update").default(false)
bool("is_obsolete").default(false)
val name = varchar("name", 128)
val pkgName = varchar("pkg_name", 128)
val versionName = varchar("version_name", 16)
val versionCode = integer("version_code")
val lang = varchar("lang", 10)
val isNsfw = bool("is_nsfw")
val isInstalled = bool("is_installed").default(false)
val hasUpdate = bool("has_update").default(false)
val isObsolete = bool("is_obsolete").default(false)
val classFQName = varchar("class_name", 1024).default("") // fully qualified name
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()
val name = varchar("name", 128)
val lang = varchar("lang", 10)
val extension = reference("extension", ExtensionTable)
val partOfFactorySource = bool("part_of_factory_source").default(false)
init {
varchar("name", 128)
varchar("lang", 10)
reference("extension", extensionTable)
bool("part_of_factory_source").default(false)
}
}
private object MangaTable : IntIdTable() {
val url = varchar("url", 2048)
val title = varchar("title", 512)
val initialized = bool("initialized").default(false)
private class MangaTable : IntIdTable() {
init {
varchar("url", 2048)
varchar("title", 512)
bool("initialized").default(false)
val artist = varchar("artist", 64).nullable()
val author = varchar("author", 64).nullable()
val description = varchar("description", 4096).nullable()
val genre = varchar("genre", 1024).nullable()
varchar("artist", 64).nullable()
varchar("author", 64).nullable()
varchar("description", 4096).nullable()
varchar("genre", 1024).nullable()
// val status = enumeration("status", MangaStatus::class).default(MangaStatus.UNKNOWN)
val status = integer("status").default(SManga.UNKNOWN)
val thumbnail_url = varchar("thumbnail_url", 2048).nullable()
// val status = enumeration("status", MangaStatus::class).default(MangaStatus.UNKNOWN)
integer("status").default(SManga.UNKNOWN)
varchar("thumbnail_url", 2048).nullable()
val inLibrary = bool("in_library").default(false)
val defaultCategory = bool("default_category").default(true)
bool("in_library").default(false)
bool("default_category").default(true)
// source is used by some ancestor of IntIdTable
val sourceReference = long("source")
// source is used by some ancestor of IntIdTable
long("source")
}
}
private object ChapterTable : IntIdTable() {
val url = varchar("url", 2048)
val name = varchar("name", 512)
val date_upload = long("date_upload").default(0)
val chapter_number = float("chapter_number").default(-1f)
val scanlator = varchar("scanlator", 128).nullable()
private class ChapterTable(mangaTable: MangaTable) : IntIdTable() {
init {
varchar("url", 2048)
varchar("name", 512)
long("date_upload").default(0)
float("chapter_number").default(-1f)
varchar("scanlator", 128).nullable()
val isRead = bool("read").default(false)
val isBookmarked = bool("bookmark").default(false)
val lastPageRead = integer("last_page_read").default(0)
bool("read").default(false)
bool("bookmark").default(false)
integer("last_page_read").default(0)
val chapterIndex = integer("number_in_list")
val manga = reference("manga", MangaTable)
integer("number_in_list")
reference("manga", mangaTable)
}
}
private object PageTable : IntIdTable() {
val index = integer("index")
val url = varchar("url", 2048)
val imageUrl = varchar("imageUrl", 2048).nullable()
val chapter = reference("chapter", ChapterTable)
private class PageTable(chapterTable: ChapterTable) : IntIdTable() {
init {
integer("index")
varchar("url", 2048)
varchar("imageUrl", 2048).nullable()
reference("chapter", chapterTable)
}
}
private object CategoryTable : IntIdTable() {
val name = varchar("name", 64)
val isLanding = bool("is_landing").default(false)
val order = integer("order").default(0)
private class CategoryTable : IntIdTable() {
init {
varchar("name", 64)
bool("is_landing").default(false)
integer("order").default(0)
}
}
private object CategoryMangaTable : IntIdTable() {
val category = reference("category", ir.armor.tachidesk.model.database.table.CategoryTable)
val manga = reference("manga", ir.armor.tachidesk.model.database.table.MangaTable)
private class CategoryMangaTable : IntIdTable() {
init {
reference("category", ir.armor.tachidesk.model.database.table.CategoryTable)
reference("manga", ir.armor.tachidesk.model.database.table.MangaTable)
}
}
/** initial migration, create all tables */
override fun run() {
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(
ExtensionTable,
ExtensionTable,
SourceTable,
MangaTable,
ChapterTable,
PageTable,
CategoryTable,
CategoryMangaTable,
extensionTable,
sourceTable,
mangaTable,
chapterTable,
pageTable,
categoryTable,
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.vendors.currentDialect
@Suppress("ClassName", "unused")
class M0002_ChapterTableIndexRename : Migration() {
/** this migration renamed ChapterTable.NUMBER_IN_LIST to ChapterTable.INDEX */
override fun run() {
@@ -54,6 +54,7 @@ fun runMigrations(migrations: List<Migration>, database: Database = TransactionM
logger.info { "Migrations finished successfully" }
}
@Suppress("UnstableApiUsage")
fun loadMigrationsFrom(classPath: String): List<Migration> {
return ClassPath.from(Thread.currentThread().contextClassLoader)
.getTopLevelClasses(classPath)
@@ -18,8 +18,8 @@ object CategoryTable : IntIdTable() {
}
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
categoryEntry[CategoryTable.id].value,
categoryEntry[CategoryTable.order],
categoryEntry[CategoryTable.name],
categoryEntry[CategoryTable.isLanding],
categoryEntry[this.id].value,
categoryEntry[this.order],
categoryEntry[this.name],
categoryEntry[this.isLanding],
)
@@ -30,14 +30,14 @@ object ChapterTable : IntIdTable() {
fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
ChapterDataClass(
chapterEntry[ChapterTable.url],
chapterEntry[ChapterTable.name],
chapterEntry[ChapterTable.date_upload],
chapterEntry[ChapterTable.chapter_number],
chapterEntry[ChapterTable.scanlator],
chapterEntry[ChapterTable.manga].value,
chapterEntry[ChapterTable.isRead],
chapterEntry[ChapterTable.isBookmarked],
chapterEntry[ChapterTable.lastPageRead],
chapterEntry[ChapterTable.chapterIndex],
chapterEntry[this.url],
chapterEntry[this.name],
chapterEntry[this.date_upload],
chapterEntry[this.chapter_number],
chapterEntry[this.scanlator],
chapterEntry[this.manga].value,
chapterEntry[this.isRead],
chapterEntry[this.isBookmarked],
chapterEntry[this.lastPageRead],
chapterEntry[this.chapterIndex],
)
@@ -36,21 +36,21 @@ object MangaTable : IntIdTable() {
fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass(
mangaEntry[MangaTable.id].value,
mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[this.id].value,
mangaEntry[this.sourceReference].toString(),
mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title],
proxyThumbnailUrl(mangaEntry[MangaTable.id].value),
mangaEntry[this.url],
mangaEntry[this.title],
proxyThumbnailUrl(mangaEntry[this.id].value),
mangaEntry[MangaTable.initialized],
mangaEntry[this.initialized],
mangaEntry[MangaTable.artist],
mangaEntry[MangaTable.author],
mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary]
mangaEntry[this.artist],
mangaEntry[this.author],
mangaEntry[this.description],
mangaEntry[this.genre],
MangaStatus.valueOf(mangaEntry[this.status]).name,
mangaEntry[this.inLibrary]
)
enum class MangaStatus(val status: Int) {
@@ -1,7 +1,6 @@
package ir.armor.tachidesk.server
import io.javalin.Javalin
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.impl.Category.createCategory
import ir.armor.tachidesk.impl.Category.getCategoryList
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.util.openInBrowser
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.future.future
import mu.KotlinLogging
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import kotlin.concurrent.thread
/*
@@ -56,7 +55,8 @@ import kotlin.concurrent.thread
object JavalinSetup {
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> {
return scope.future(block = block)
@@ -68,7 +68,7 @@ object JavalinSetup {
val app = Javalin.create { config ->
try {
// 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
hasWebUiBundled = true
@@ -21,7 +21,7 @@ class ServerConfig(config: Config) : ConfigModule(config) {
val socksProxyPort: String by config
// 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 initialOpenInBrowserEnabled: Boolean by config
@@ -9,7 +9,6 @@ package ir.armor.tachidesk.server
import ch.qos.logback.classic.Level
import eu.kanade.tachiyomi.App
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.model.database.databaseUp
import ir.armor.tachidesk.server.util.systemTray
import mu.KotlinLogging
@@ -81,7 +80,7 @@ fun applicationSetup() {
try {
val dataConfFile = File("${applicationDirs.dataRoot}/server.conf")
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 ->
input.copyTo(output)
}
@@ -97,7 +96,7 @@ fun applicationSetup() {
if (serverConfig.systemTrayEnabled) {
try {
systemTray
} catch (e: Exception) {
} catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error
e.printStackTrace()
}
}
@@ -12,15 +12,15 @@ import dorkbox.systemTray.SystemTray
import dorkbox.systemTray.SystemTray.TrayType
import dorkbox.util.CacheUtil
import dorkbox.util.Desktop
import ir.armor.tachidesk.Main
import ir.armor.tachidesk.server.BuildConfig
import ir.armor.tachidesk.server.ServerConfig
import ir.armor.tachidesk.server.serverConfig
import kotlin.system.exitProcess
fun openInBrowser() {
try {
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()
}
}
@@ -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.status = "No Mail"
// systemTray.status = "No Mail"
mainMenu.add(
MenuItem("Quit") {
+1 -1
View File
@@ -13,7 +13,7 @@ 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/NavBar';
import Sources from './screens/Sources';
import Extensions from './screens/Extensions';
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 ListItem from '@material-ui/core/ListItem';
import { langCodeToName } from '../util/language';
import cloneObject from '../util/cloneObject';
const useStyles = makeStyles(() => createStyles({
paper: {
@@ -54,7 +55,7 @@ export default function ExtensionLangSelect(props: IProps) {
if (checked) {
setMShownLangs([...mShownLangs, lang]);
} else {
const clone = JSON.parse(JSON.stringify(mShownLangs));
const clone = cloneObject(mShownLangs);
clone.splice(clone.indexOf(lang), 1);
setMShownLangs(clone);
}
+7
View File
@@ -1,4 +1,11 @@
/* 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 React from 'react';
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 IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import NavBarContext from '../context/NavbarContext';
import DarkTheme from '../context/DarkTheme';
import TemporaryDrawer from './TemporaryDrawer';
import NavBarContext from '../../context/NavbarContext';
import DarkTheme from '../../context/DarkTheme';
import TemporaryDrawer from '../TemporaryDrawer';
const useStyles = makeStyles((theme) => ({
root: {
@@ -30,8 +30,8 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import Collapse from '@material-ui/core/Collapse';
import Button from '@material-ui/core/Button';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import DarkTheme from '../context/DarkTheme';
import NavBarContext from '../context/NavbarContext';
import DarkTheme from '../../context/DarkTheme';
import NavBarContext from '../../context/NavbarContext';
const useStyles = (settings: IReaderSettings) => makeStyles((theme: Theme) => ({
// 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',
top: 0,
left: 0,
minWidth: '300px',
width: '300px',
height: '100vh',
overflowY: 'auto',
backgroundColor: '#0a0b0b',
@@ -143,6 +143,7 @@ export const defaultReaderSettings = () => ({
staticNav: false,
showPageNumber: true,
continuesPageGap: false,
loadNextonEnding: false,
readerType: 'ContinuesVertical',
} as IReaderSettings);
@@ -277,6 +278,16 @@ export default function ReaderNavBar(props: IProps) {
/>
</ListItemSecondaryAction>
</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>
<ListItemText primary="Reader Type" />
<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 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 */
/*
* 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 from 'react';
import Page from './Page';
import Page from '../Page';
const useStyles = makeStyles({
reader: {
@@ -21,7 +28,7 @@ interface IProps {
settings: IReaderSettings
}
export default function HorizontalReader(props: IProps) {
export default function HorizontalPager(props: IProps) {
const { pages, settings, setCurPage } = props;
const classes = useStyles();
@@ -1,7 +1,15 @@
/* 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 Page from './Page';
import React, { useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import Page from '../Page';
const useStyles = makeStyles({
reader: {
@@ -16,13 +24,20 @@ const useStyles = makeStyles({
export default function PagedReader(props: IReaderProps) {
const {
pages, settings, setCurPage, curPage,
pages, settings, setCurPage, curPage, manga, chapter,
} = props;
const classes = useStyles();
const history = useHistory();
const pageRef = useRef<HTMLDivElement>(null);
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() {
@@ -52,16 +67,16 @@ export default function PagedReader(props: IReaderProps) {
useEffect(() => {
document.addEventListener('keyup', keyboardControl, false);
document.addEventListener('click', clickControl);
pageRef.current?.addEventListener('click', clickControl);
return () => {
document.removeEventListener('keyup', keyboardControl);
document.removeEventListener('click', clickControl);
pageRef.current?.removeEventListener('click', clickControl);
};
}, [curPage]);
}, [curPage, pageRef]);
return (
<div className={classes.reader}>
<div ref={pageRef} className={classes.reader}>
<Page
key={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 NavbarContext from '../context/NavbarContext';
import client from '../util/client';
import cloneObject from '../util/cloneObject';
interface IMangaCategory {
category: ICategory
@@ -98,7 +99,7 @@ export default function Library() {
client.get(`/api/v1/category/${tab.category.id}`)
.then((response) => response.data)
.then((data: IManga[]) => {
const tabsClone = JSON.parse(JSON.stringify(tabs));
const tabsClone = cloneObject(tabs);
tabsClone[index].mangas = data;
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() {
const classes = useStyles();
const theme = useTheme();
@@ -60,6 +56,7 @@ export default function Manga() {
const [manga, setManga] = useState<IManga>();
const [chapters, setChapters] = useState<IChapter[]>([]);
const [fetchedChapters, setFetchedChapters] = useState(false);
const [noChaptersFound, setNoChaptersFound] = useState(false);
const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0);
function triggerChaptersUpdate() {
@@ -84,20 +81,13 @@ export default function Manga() {
.then((data) => {
if (data.length === 0 && fetchedChapters) {
makeToast('No chapters found', 'warning');
setNoChaptersFound(true);
}
setChapters(data);
})
.then(() => setFetchedChapters(true));
}, [chapters.length, fetchedChapters, chapterUpdateTriggerer]);
// const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />;
const itemContent = (index:any) => (
<ChapterCard
chapter={chapters[index]}
triggerChaptersUpdate={triggerChaptersUpdate}
/>
);
return (
<div className={classes.root}>
<LoadingPlaceholder
@@ -107,7 +97,7 @@ export default function Manga() {
/>
<LoadingPlaceholder
shouldRender={chapters.length > 0 || fetchedChapters}
shouldRender={chapters.length > 0 || noChaptersFound}
>
<Virtuoso
style={{ // override Virtuoso default values and set them with class
@@ -115,7 +105,12 @@ export default function Manga() {
}}
className={classes.chapters}
totalCount={chapters.length}
itemContent={itemContent}
itemContent={(index:number) => (
<ChapterCard
chapter={chapters[index]}
triggerChaptersUpdate={triggerChaptersUpdate}
/>
)}
useWindowScroll={window.innerWidth < 960}
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 React, { useContext, useEffect, useState } from 'react';
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 PageNumber from '../components/reader/PageNumber';
import PagedReader from '../components/reader/PagedReader';
import VerticalReader from '../components/reader/VerticalReader';
import ReaderNavBar, { defaultReaderSettings } from '../components/ReaderNavBar';
import WebtoonPager from '../components/reader/pager/PagedPager';
import VerticalPager from '../components/reader/pager/VerticalPager';
import ReaderNavBar, { defaultReaderSettings } from '../components/navbar/ReaderNavBar';
import NavbarContext from '../context/NavbarContext';
import client from '../util/client';
import useLocalStorage from '../util/useLocalStorage';
import cloneObject from '../util/cloneObject';
const useStyles = (settings: IReaderSettings) => makeStyles({
root: {
@@ -33,24 +34,24 @@ const useStyles = (settings: IReaderSettings) => makeStyles({
const getReaderComponent = (readerType: ReaderType) => {
switch (readerType) {
case 'ContinuesVertical':
return VerticalReader;
return VerticalPager;
break;
case 'Webtoon':
return VerticalReader;
return VerticalPager;
break;
case 'SingleVertical':
return PagedReader;
return WebtoonPager;
break;
case 'SingleRTL':
return PagedReader;
return WebtoonPager;
break;
case 'SingleLTR':
return PagedReader;
return WebtoonPager;
break;
case 'ContinuesHorizontal':
return HorizontalReader;
return HorizontalPager;
default:
return VerticalReader;
return VerticalPager;
break;
}
};
@@ -69,9 +70,22 @@ export default function Reader() {
const [manga, setManga] = useState<IMangaCard | IManga>({ id: +mangaId, title: '', thumbnailUrl: '' });
const [chapter, setChapter] = useState<IChapter | IPartialChpter>(initialChapter());
const [curPage, setCurPage] = useState<number>(0);
const { setOverride, setTitle } = useContext(NavbarContext);
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(
{
status: true,
@@ -103,6 +117,7 @@ export default function Reader() {
useEffect(() => {
setChapter(initialChapter);
setCurPage(0);
client.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`)
.then((response) => response.data)
.then((data:IChapter) => {
@@ -138,6 +153,8 @@ export default function Reader() {
setCurPage={setCurPage}
curPage={curPage}
settings={settings}
manga={manga}
chapter={chapter}
/>
</div>
);
+3
View File
@@ -99,6 +99,7 @@ type ReaderType =
interface IReaderSettings{
staticNav: boolean
showPageNumber: boolean
loadNextonEnding: boolean
readerType: ReaderType
}
@@ -113,4 +114,6 @@ interface IReaderProps {
setCurPage: React.Dispatch<React.SetStateAction<number>>
curPage: number
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;
}