From c17e3bd04fa597d0aeeab57bfd94d8aa645074d0 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Thu, 27 May 2021 05:13:01 +0430 Subject: [PATCH] can work with anime extensions successfully --- .../main/kotlin/suwayomi/anime/AnimeAPI.kt | 95 +++++------ .../anime/impl/extension/Extension.kt | 16 +- .../suwayomi/anime/impl/util/PackageTools.kt | 67 ++++---- .../kotlin/suwayomi/server/JavalinSetup.kt | 2 + .../migration/M0004_AnimeTablesBatch1.kt | 54 +++++++ .../kotlin/suwayomi/tachidesk/TachideskAPI.kt | 35 ++-- .../tachidesk/impl/util/GetHttpSource.kt | 2 +- .../tachidesk/impl/util/PackageTools.kt | 1 - webUI/react/src/App.tsx | 106 +++++++------ .../react/src/components/TemporaryDrawer.tsx | 12 +- .../src/components/anime/ExtensionCard.tsx | 149 ++++++++++++++++++ .../components/{ => manga}/CategorySelect.tsx | 2 +- .../components/{ => manga}/ChapterCard.tsx | 2 +- .../components/{ => manga}/ExtensionCard.tsx | 4 +- .../{ => manga}/ExtensionLangSelect.tsx | 4 +- .../src/components/{ => manga}/MangaCard.tsx | 2 +- .../components/{ => manga}/MangaDetails.tsx | 6 +- .../src/components/{ => manga}/MangaGrid.tsx | 0 .../src/components/{ => manga}/SourceCard.tsx | 4 +- .../components/{ => manga}/reader/Page.tsx | 0 .../{ => manga}/reader/PageNumber.tsx | 0 .../reader/pager/HorizontalPager.tsx | 0 .../{ => manga}/reader/pager/PagedPager.tsx | 0 .../reader/pager/VerticalPager.tsx | 0 .../src/screens/anime/AnimeExtensions.tsx | 112 +++++++++++++ .../react/src/screens/{ => manga}/Library.tsx | 8 +- webUI/react/src/screens/{ => manga}/Manga.tsx | 12 +- .../MangaExtensions.tsx} | 14 +- .../react/src/screens/{ => manga}/Reader.tsx | 18 +-- .../src/screens/{ => manga}/SearchSingle.tsx | 6 +- .../src/screens/{ => manga}/SourceMangas.tsx | 6 +- .../react/src/screens/{ => manga}/Sources.tsx | 12 +- webUI/react/tsconfig.json | 1 + 33 files changed, 546 insertions(+), 206 deletions(-) create mode 100644 server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt create mode 100644 webUI/react/src/components/anime/ExtensionCard.tsx rename webUI/react/src/components/{ => manga}/CategorySelect.tsx (99%) rename webUI/react/src/components/{ => manga}/ChapterCard.tsx (99%) rename webUI/react/src/components/{ => manga}/ExtensionCard.tsx (97%) rename webUI/react/src/components/{ => manga}/ExtensionLangSelect.tsx (97%) rename webUI/react/src/components/{ => manga}/MangaCard.tsx (97%) rename webUI/react/src/components/{ => manga}/MangaDetails.tsx (98%) rename webUI/react/src/components/{ => manga}/MangaGrid.tsx (100%) rename webUI/react/src/components/{ => manga}/SourceCard.tsx (96%) rename webUI/react/src/components/{ => manga}/reader/Page.tsx (100%) rename webUI/react/src/components/{ => manga}/reader/PageNumber.tsx (100%) rename webUI/react/src/components/{ => manga}/reader/pager/HorizontalPager.tsx (100%) rename webUI/react/src/components/{ => manga}/reader/pager/PagedPager.tsx (100%) rename webUI/react/src/components/{ => manga}/reader/pager/VerticalPager.tsx (100%) create mode 100644 webUI/react/src/screens/anime/AnimeExtensions.tsx rename webUI/react/src/screens/{ => manga}/Library.tsx (96%) rename webUI/react/src/screens/{ => manga}/Manga.tsx (92%) rename webUI/react/src/screens/{Extensions.tsx => manga/MangaExtensions.tsx} (90%) rename webUI/react/src/screens/{ => manga}/Reader.tsx (91%) rename webUI/react/src/screens/{ => manga}/SearchSingle.tsx (96%) rename webUI/react/src/screens/{ => manga}/SourceMangas.tsx (92%) rename webUI/react/src/screens/{ => manga}/Sources.tsx (88%) diff --git a/server/src/main/kotlin/suwayomi/anime/AnimeAPI.kt b/server/src/main/kotlin/suwayomi/anime/AnimeAPI.kt index 7bef99a4..e4d0a349 100644 --- a/server/src/main/kotlin/suwayomi/anime/AnimeAPI.kt +++ b/server/src/main/kotlin/suwayomi/anime/AnimeAPI.kt @@ -8,62 +8,67 @@ package suwayomi.anime * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import io.javalin.Javalin -import suwayomi.server.JavalinSetup +import suwayomi.anime.impl.extension.Extension.getExtensionIcon +import suwayomi.anime.impl.extension.Extension.installExtension +import suwayomi.anime.impl.extension.Extension.uninstallExtension +import suwayomi.anime.impl.extension.Extension.updateExtension import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList +import suwayomi.server.JavalinSetup +import suwayomi.server.JavalinSetup.future object AnimeAPI { fun defineEndpoints(app: Javalin) { // list all extensions - app.get("/api/v1/extension/list") { ctx -> + app.get("/api/v1/anime/extension/list") { ctx -> ctx.json( - JavalinSetup.future { + future { getExtensionList() } ) } -// // install extension identified with "pkgName" -// app.get("/api/v1/extension/install/:pkgName") { ctx -> -// val pkgName = ctx.pathParam("pkgName") -// -// ctx.json( -// JavalinSetup.future { -// installExtension(pkgName) -// } -// ) -// } -// -// // update extension identified with "pkgName" -// app.get("/api/v1/extension/update/:pkgName") { ctx -> -// val pkgName = ctx.pathParam("pkgName") -// -// ctx.json( -// JavalinSetup.future { -// updateExtension(pkgName) -// } -// ) -// } -// -// // uninstall extension identified with "pkgName" -// app.get("/api/v1/extension/uninstall/:pkgName") { ctx -> -// val pkgName = ctx.pathParam("pkgName") -// -// uninstallExtension(pkgName) -// ctx.status(200) -// } -// -// // icon for extension named `apkName` -// app.get("/api/v1/extension/icon/:apkName") { ctx -> // TODO: move to pkgName -// val apkName = ctx.pathParam("apkName") -// -// ctx.result( -// JavalinSetup.future { getExtensionIcon(apkName) } -// .thenApply { -// ctx.header("content-type", it.second) -// it.first -// } -// ) -// } + // install extension identified with "pkgName" + app.get("/api/v1/anime/extension/install/:pkgName") { ctx -> + val pkgName = ctx.pathParam("pkgName") + + ctx.json( + JavalinSetup.future { + installExtension(pkgName) + } + ) + } + + // update extension identified with "pkgName" + app.get("/api/v1/anime/extension/update/:pkgName") { ctx -> + val pkgName = ctx.pathParam("pkgName") + + ctx.json( + JavalinSetup.future { + updateExtension(pkgName) + } + ) + } + + // uninstall extension identified with "pkgName" + app.get("/api/v1/anime/extension/uninstall/:pkgName") { ctx -> + val pkgName = ctx.pathParam("pkgName") + + uninstallExtension(pkgName) + ctx.status(200) + } + + // icon for extension named `apkName` + app.get("/api/v1/anime/extension/icon/:apkName") { ctx -> // TODO: move to pkgName + val apkName = ctx.pathParam("apkName") + + ctx.result( + JavalinSetup.future { getExtensionIcon(apkName) } + .thenApply { + ctx.header("content-type", it.second) + it.first + } + ) + } // // list of sources // app.get("/api/v1/source/list") { ctx -> diff --git a/server/src/main/kotlin/suwayomi/anime/impl/extension/Extension.kt b/server/src/main/kotlin/suwayomi/anime/impl/extension/Extension.kt index 08543c2a..f02cc897 100644 --- a/server/src/main/kotlin/suwayomi/anime/impl/extension/Extension.kt +++ b/server/src/main/kotlin/suwayomi/anime/impl/extension/Extension.kt @@ -10,9 +10,9 @@ package suwayomi.anime.impl.extension import android.net.Uri import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.AnimeCatalogueSource +import eu.kanade.tachiyomi.source.AnimeSource +import eu.kanade.tachiyomi.source.AnimeSourceFactory import mu.KotlinLogging import okhttp3.Request import okio.buffer @@ -125,11 +125,11 @@ object Extension { File(dexFilePath).delete() // collect sources from the extension - val sources: List = when (val instance = loadExtensionSources(jarFilePath, className)) { - is Source -> listOf(instance) - is SourceFactory -> instance.createSources() + val sources: List = when (val instance = loadExtensionSources(jarFilePath, className)) { + is AnimeSource -> listOf(instance) + is AnimeSourceFactory -> instance.createSources() else -> throw RuntimeException("Unknown source class type! ${instance.javaClass}") - }.map { it as CatalogueSource } + }.map { it as AnimeCatalogueSource } val langs = sources.map { it.lang }.toSet() val extensionLang = when (langs.size) { @@ -246,6 +246,6 @@ object Extension { } fun getExtensionIconUrl(apkName: String): String { - return "/api/v1/extension/icon/$apkName" + return "/api/v1/anime/extension/icon/$apkName" } } diff --git a/server/src/main/kotlin/suwayomi/anime/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/anime/impl/util/PackageTools.kt index 33c5c3a3..433db015 100644 --- a/server/src/main/kotlin/suwayomi/anime/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/anime/impl/util/PackageTools.kt @@ -32,15 +32,14 @@ import java.nio.file.Files import java.nio.file.Path import javax.xml.parsers.DocumentBuilderFactory - object PackageTools { private val logger = KotlinLogging.logger {} private val applicationDirs by DI.global.instance() - const val EXTENSION_FEATURE = "tachiyomi.extension" - const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" - const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" - const val METADATA_NSFW = "tachiyomi.extension.nsfw" + const val EXTENSION_FEATURE = "tachiyomi.animeextension" + const val METADATA_SOURCE_CLASS = "tachiyomi.animeextension.class" + const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory" + const val METADATA_NSFW = "tachiyomi.animeextension.nsfw" const val LIB_VERSION_MIN = 1.3 const val LIB_VERSION_MAX = 1.3 @@ -58,20 +57,20 @@ object PackageTools { val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath())) val handler = BaksmaliBaseDexExceptionHandler() Dex2jar - .from(reader) - .withExceptionHandler(handler) - .reUseReg(false) - .topoLogicalSort() - .skipDebug(true) - .optimizeSynchronized(false) - .printIR(false) - .noCode(false) - .skipExceptions(false) - .to(jarFilePath) + .from(reader) + .withExceptionHandler(handler) + .reUseReg(false) + .topoLogicalSort() + .skipDebug(true) + .optimizeSynchronized(false) + .printIR(false) + .noCode(false) + .skipExceptions(false) + .to(jarFilePath) if (handler.hasException()) { val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt") logger.error( - """ + """ Detail Error Information in File $errorFile Please report this file to one of following link if possible (any one). https://sourceforge.net/p/dex2jar/tickets/ @@ -101,27 +100,27 @@ object PackageTools { val appTag = doc.getElementsByTagName("application").item(0) appTag?.childNodes?.toList() - .orEmpty() - .asSequence() - .filter { - it.nodeType == Node.ELEMENT_NODE - }.map { - it as Element - }.filter { - it.tagName == "meta-data" - }.forEach { - putString( - it.attributes.getNamedItem("android:name").nodeValue, - it.attributes.getNamedItem("android:value").nodeValue - ) - } + .orEmpty() + .asSequence() + .filter { + it.nodeType == Node.ELEMENT_NODE + }.map { + it as Element + }.filter { + it.tagName == "meta-data" + }.forEach { + putString( + it.attributes.getNamedItem("android:name").nodeValue, + it.attributes.getNamedItem("android:value").nodeValue + ) + } } signatures = ( - parsed.apkSingers.flatMap { it.certificateMetas } - /*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/ - ) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72 - .map { Signature(it.data) }.toTypedArray() + parsed.apkSingers.flatMap { it.certificateMetas } + /*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/ + ) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72 + .map { Signature(it.data) }.toTypedArray() } } diff --git a/server/src/main/kotlin/suwayomi/server/JavalinSetup.kt b/server/src/main/kotlin/suwayomi/server/JavalinSetup.kt index f87ff155..d431c9c2 100644 --- a/server/src/main/kotlin/suwayomi/server/JavalinSetup.kt +++ b/server/src/main/kotlin/suwayomi/server/JavalinSetup.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.future.future import mu.KotlinLogging +import suwayomi.anime.AnimeAPI import suwayomi.server.util.Browser import suwayomi.tachidesk.TachideskAPI import java.io.IOException @@ -75,5 +76,6 @@ object JavalinSetup { } TachideskAPI.defineEndpoints(app) + AnimeAPI.defineEndpoints(app) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt new file mode 100644 index 00000000..5a6e8b17 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt @@ -0,0 +1,54 @@ +package suwayomi.server.database.migration + +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.server.database.migration.lib.Migration + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +class M0004_AnimeTablesBatch1 : Migration() { + private object AnimeExtensionTable : IntIdTable() { + val apkName = varchar("apk_name", 1024) + + // default is the local source icon from tachiyomi + val iconUrl = varchar("icon_url", 2048) + .default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp") + + val name = varchar("name", 128) + val pkgName = varchar("pkg_name", 128) + val versionName = varchar("version_name", 16) + val versionCode = integer("version_code") + val lang = varchar("lang", 10) + val isNsfw = bool("is_nsfw") + + val isInstalled = bool("is_installed").default(false) + val hasUpdate = bool("has_update").default(false) + val isObsolete = bool("is_obsolete").default(false) + + val classFQName = varchar("class_name", 1024).default("") // fully qualified name + } + + private object AnimeSourceTable : IdTable() { + override val id = long("id").entityId() + val name = varchar("name", 128) + val lang = varchar("lang", 10) + val extension = reference("extension", suwayomi.anime.model.table.AnimeExtensionTable) + val partOfFactorySource = bool("part_of_factory_source").default(false) + } + + override fun run() { + transaction { + SchemaUtils.create( + AnimeExtensionTable, + AnimeSourceTable + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt index 6334c3b7..97b9dd98 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt @@ -8,7 +8,6 @@ package suwayomi.tachidesk * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import io.javalin.Javalin -import suwayomi.server.JavalinSetup import suwayomi.server.JavalinSetup.future import suwayomi.server.impl.About import suwayomi.tachidesk.impl.Category @@ -47,7 +46,7 @@ object TachideskAPI { // list all extensions app.get("/api/v1/extension/list") { ctx -> ctx.json( - JavalinSetup.future { + future { getExtensionList() } ) @@ -58,7 +57,7 @@ object TachideskAPI { val pkgName = ctx.pathParam("pkgName") ctx.json( - JavalinSetup.future { + future { installExtension(pkgName) } ) @@ -69,7 +68,7 @@ object TachideskAPI { val pkgName = ctx.pathParam("pkgName") ctx.json( - JavalinSetup.future { + future { updateExtension(pkgName) } ) @@ -88,7 +87,7 @@ object TachideskAPI { val apkName = ctx.pathParam("apkName") ctx.result( - JavalinSetup.future { getExtensionIcon(apkName) } + future { getExtensionIcon(apkName) } .thenApply { ctx.header("content-type", it.second) it.first @@ -112,7 +111,7 @@ object TachideskAPI { val sourceId = ctx.pathParam("sourceId").toLong() val pageNum = ctx.pathParam("pageNum").toInt() ctx.json( - JavalinSetup.future { + future { getMangaList(sourceId, pageNum, popular = true) } ) @@ -123,7 +122,7 @@ object TachideskAPI { val sourceId = ctx.pathParam("sourceId").toLong() val pageNum = ctx.pathParam("pageNum").toInt() ctx.json( - JavalinSetup.future { + future { getMangaList(sourceId, pageNum, popular = false) } ) @@ -135,7 +134,7 @@ object TachideskAPI { val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean() ctx.json( - JavalinSetup.future { + future { getManga(mangaId, onlineFetch) } ) @@ -146,7 +145,7 @@ object TachideskAPI { val mangaId = ctx.pathParam("mangaId").toInt() ctx.result( - JavalinSetup.future { getMangaThumbnail(mangaId) } + future { getMangaThumbnail(mangaId) } .thenApply { ctx.header("content-type", it.second) it.first @@ -182,14 +181,14 @@ object TachideskAPI { val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean() - ctx.json(JavalinSetup.future { getChapterList(mangaId, onlineFetch) }) + ctx.json(future { getChapterList(mangaId, onlineFetch) }) } // used to display a chapter, get a chapter in order to show it's pages app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx -> val chapterIndex = ctx.pathParam("chapterIndex").toInt() val mangaId = ctx.pathParam("mangaId").toInt() - ctx.json(JavalinSetup.future { getChapter(chapterIndex, mangaId) }) + ctx.json(future { getChapter(chapterIndex, mangaId) }) } // used to modify a chapter's parameters @@ -214,7 +213,7 @@ object TachideskAPI { val index = ctx.pathParam("index").toInt() ctx.result( - JavalinSetup.future { getPageImage(mangaId, chapterIndex, index) } + future { getPageImage(mangaId, chapterIndex, index) } .thenApply { ctx.header("content-type", it.second) it.first @@ -243,7 +242,7 @@ object TachideskAPI { val sourceId = ctx.pathParam("sourceId").toLong() val searchTerm = ctx.pathParam("searchTerm") val pageNum = ctx.pathParam("pageNum").toInt() - ctx.json(JavalinSetup.future { sourceSearch(sourceId, searchTerm, pageNum) }) + ctx.json(future { sourceSearch(sourceId, searchTerm, pageNum) }) } // source filter list @@ -257,7 +256,7 @@ object TachideskAPI { val mangaId = ctx.pathParam("mangaId").toInt() ctx.result( - JavalinSetup.future { addMangaToLibrary(mangaId) } + future { addMangaToLibrary(mangaId) } ) } @@ -266,7 +265,7 @@ object TachideskAPI { val mangaId = ctx.pathParam("mangaId").toInt() ctx.result( - JavalinSetup.future { removeMangaFromLibrary(mangaId) } + future { removeMangaFromLibrary(mangaId) } ) } @@ -335,7 +334,7 @@ object TachideskAPI { // expects a Tachiyomi legacy backup json as a file upload, the file must be named "backup.json" app.post("/api/v1/backup/legacy/import/file") { ctx -> ctx.result( - JavalinSetup.future { + future { restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content) } ) @@ -345,7 +344,7 @@ object TachideskAPI { app.get("/api/v1/backup/legacy/export") { ctx -> ctx.contentType("application/json") ctx.result( - JavalinSetup.future { + future { createLegacyBackup( BackupFlags( includeManga = true, @@ -367,7 +366,7 @@ object TachideskAPI { ctx.header("Content-Disposition", "attachment; filename=\"tachidesk_$currentDate.json\"") ctx.result( - JavalinSetup.future { + future { createLegacyBackup( BackupFlags( includeManga = true, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/util/GetHttpSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/util/GetHttpSource.kt index b1bf0962..a32cc0b1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/util/GetHttpSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/util/GetHttpSource.kt @@ -15,8 +15,8 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.kodein.di.DI import org.kodein.di.conf.global import org.kodein.di.instance -import suwayomi.tachidesk.impl.util.PackageTools.loadExtensionSources import suwayomi.server.ApplicationDirs +import suwayomi.tachidesk.impl.util.PackageTools.loadExtensionSources import suwayomi.tachidesk.model.table.ExtensionTable import suwayomi.tachidesk.model.table.SourceTable import java.util.concurrent.ConcurrentHashMap diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/util/PackageTools.kt index e4e6648a..6f722993 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/util/PackageTools.kt @@ -32,7 +32,6 @@ import java.nio.file.Files import java.nio.file.Path import javax.xml.parsers.DocumentBuilderFactory - object PackageTools { private val logger = KotlinLogging.logger {} private val applicationDirs by DI.global.instance() diff --git a/webUI/react/src/App.tsx b/webUI/react/src/App.tsx index c933b1f2..aed406b2 100644 --- a/webUI/react/src/App.tsx +++ b/webUI/react/src/App.tsx @@ -7,27 +7,29 @@ import React, { useState } from 'react'; import { - BrowserRouter as Router, Redirect, Route, Switch, + BrowserRouter as Router, Switch, + Route, + Redirect, } 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/NavBar'; -import Sources from './screens/Sources'; -import Extensions from './screens/Extensions'; -import SourceMangas from './screens/SourceMangas'; -import Manga from './screens/Manga'; -import Reader from './screens/Reader'; -import Search from './screens/SearchSingle'; -import NavbarContext from './context/NavbarContext'; -import DarkTheme from './context/DarkTheme'; -import Library from './screens/Library'; -import Settings from './screens/Settings'; -import Categories from './screens/settings/Categories'; -import Backup from './screens/settings/Backup'; -import useLocalStorage from './util/useLocalStorage'; -import About from './screens/settings/About'; +import NavBar from 'components/navbar/NavBar'; +import NavbarContext from 'context/NavbarContext'; +import DarkTheme from 'context/DarkTheme'; +import useLocalStorage from 'util/useLocalStorage'; +import Sources from 'screens/manga/Sources'; +import Settings from 'screens/Settings'; +import About from 'screens/settings/About'; +import Categories from 'screens/settings/Categories'; +import Backup from 'screens/settings/Backup'; +import Library from 'screens/manga/Library'; +import SearchSingle from 'screens/manga/SearchSingle'; +import Manga from 'screens/manga/Manga'; +import MangaExtensions from 'screens/manga/MangaExtensions'; +import SourceMangas from 'screens/manga/SourceMangas'; +import Reader from 'screens/manga/Reader'; +import AnimeExtensions from 'screens/anime/AnimeExtensions'; export default function App() { const [title, setTitle] = useState('Tachidesk'); @@ -78,11 +80,37 @@ export default function App() { style={{ paddingTop: '64px' }} > - - + {/* general routes */} + ( + + )} + /> + + + - - + + + + + + + + + + + + + {/* Manga Routes */} + + + + + + @@ -102,36 +130,20 @@ export default function App() { - - - - - - - - - - - - - - ( - - )} + path="/manga/:mangaId/chapter/:chapterIndex" + // passing a key re-mounts the reader when changing chapters + render={ + (props:any) => + } /> + + {/* Anime Routes */} + + + - - } - /> - diff --git a/webUI/react/src/components/TemporaryDrawer.tsx b/webUI/react/src/components/TemporaryDrawer.tsx index 279cf4db..8873a6d3 100644 --- a/webUI/react/src/components/TemporaryDrawer.tsx +++ b/webUI/react/src/components/TemporaryDrawer.tsx @@ -55,12 +55,20 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) { - + - + + + + + + + + + diff --git a/webUI/react/src/components/anime/ExtensionCard.tsx b/webUI/react/src/components/anime/ExtensionCard.tsx new file mode 100644 index 00000000..0335e3aa --- /dev/null +++ b/webUI/react/src/components/anime/ExtensionCard.tsx @@ -0,0 +1,149 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import React, { useState } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import Button from '@material-ui/core/Button'; +import Avatar from '@material-ui/core/Avatar'; +import Typography from '@material-ui/core/Typography'; +import client from 'util/client'; +import useLocalStorage from 'util/useLocalStorage'; + +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: 16, + }, + bullet: { + display: 'inline-block', + margin: '0 2px', + transform: 'scale(0.8)', + }, + title: { + fontSize: 14, + }, + pos: { + marginBottom: 12, + }, + icon: { + width: theme.spacing(7), + height: theme.spacing(7), + flex: '0 0 auto', + marginRight: 16, + }, +})); + +interface IProps { + extension: IExtension + notifyInstall: () => void +} + +export default function ExtensionCard(props: IProps) { + const { + extension: { + name, lang, versionName, installed, hasUpdate, obsolete, pkgName, iconUrl, + }, + notifyInstall, + } = props; + const [installedState, setInstalledState] = useState( + () => { + if (obsolete) { return 'obsolete'; } + if (hasUpdate) { return 'update'; } + return (installed ? 'uninstall' : 'install'); + }, + ); + + const [serverAddress] = useLocalStorage('serverBaseURL', ''); + + const classes = useStyles(); + const langPress = lang === 'all' ? 'All' : lang.toUpperCase(); + + function install() { + setInstalledState('installing'); + client.get(`/api/v1/anime/extension/install/${pkgName}`) + .then(() => { + setInstalledState('uninstall'); + notifyInstall(); + }); + } + + function update() { + setInstalledState('updating'); + client.get(`/api/v1/anime/extension/update/${pkgName}`) + .then(() => { + setInstalledState('uninstall'); + notifyInstall(); + }); + } + + function uninstall() { + setInstalledState('uninstalling'); + client.get(`/api/v1/anime/extension/uninstall/${pkgName}`) + .then(() => { + // setInstalledState('install'); + notifyInstall(); + }); + } + + function handleButtonClick() { + switch (installedState) { + case 'install': + install(); + break; + case 'update': + update(); + break; + case 'obsolete': + uninstall(); + setTimeout(() => window.location.reload(), 3000); + break; + case 'uninstall': + uninstall(); + break; + default: + break; + } + } + + return ( + + +
+ +
+ + {name} + + + {langPress} + {' '} + {versionName} + +
+
+ + +
+
+ ); +} diff --git a/webUI/react/src/components/CategorySelect.tsx b/webUI/react/src/components/manga/CategorySelect.tsx similarity index 99% rename from webUI/react/src/components/CategorySelect.tsx rename to webUI/react/src/components/manga/CategorySelect.tsx index cb4dd31a..6265c3a5 100644 --- a/webUI/react/src/components/CategorySelect.tsx +++ b/webUI/react/src/components/manga/CategorySelect.tsx @@ -15,7 +15,7 @@ import Dialog from '@material-ui/core/Dialog'; import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormGroup from '@material-ui/core/FormGroup'; -import client from '../util/client'; +import client from 'util/client'; const useStyles = makeStyles(() => createStyles({ paper: { diff --git a/webUI/react/src/components/ChapterCard.tsx b/webUI/react/src/components/manga/ChapterCard.tsx similarity index 99% rename from webUI/react/src/components/ChapterCard.tsx rename to webUI/react/src/components/manga/ChapterCard.tsx index f96cefb4..ccf4c0b8 100644 --- a/webUI/react/src/components/ChapterCard.tsx +++ b/webUI/react/src/components/manga/ChapterCard.tsx @@ -16,7 +16,7 @@ import { Link } from 'react-router-dom'; import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; import BookmarkIcon from '@material-ui/icons/Bookmark'; -import client from '../util/client'; +import client from 'util/client'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/webUI/react/src/components/ExtensionCard.tsx b/webUI/react/src/components/manga/ExtensionCard.tsx similarity index 97% rename from webUI/react/src/components/ExtensionCard.tsx rename to webUI/react/src/components/manga/ExtensionCard.tsx index 5b334cba..806c0750 100644 --- a/webUI/react/src/components/ExtensionCard.tsx +++ b/webUI/react/src/components/manga/ExtensionCard.tsx @@ -12,8 +12,8 @@ import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Avatar from '@material-ui/core/Avatar'; import Typography from '@material-ui/core/Typography'; -import client from '../util/client'; -import useLocalStorage from '../util/useLocalStorage'; +import client from 'util/client'; +import useLocalStorage from 'util/useLocalStorage'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/webUI/react/src/components/ExtensionLangSelect.tsx b/webUI/react/src/components/manga/ExtensionLangSelect.tsx similarity index 97% rename from webUI/react/src/components/ExtensionLangSelect.tsx rename to webUI/react/src/components/manga/ExtensionLangSelect.tsx index 78dba3c6..64800344 100644 --- a/webUI/react/src/components/ExtensionLangSelect.tsx +++ b/webUI/react/src/components/manga/ExtensionLangSelect.tsx @@ -17,8 +17,8 @@ import IconButton from '@material-ui/core/IconButton'; 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'; +import { langCodeToName } from 'util/language'; +import cloneObject from 'util/cloneObject'; const useStyles = makeStyles(() => createStyles({ paper: { diff --git a/webUI/react/src/components/MangaCard.tsx b/webUI/react/src/components/manga/MangaCard.tsx similarity index 97% rename from webUI/react/src/components/MangaCard.tsx rename to webUI/react/src/components/manga/MangaCard.tsx index 82220ec9..9f3da4de 100644 --- a/webUI/react/src/components/MangaCard.tsx +++ b/webUI/react/src/components/manga/MangaCard.tsx @@ -13,7 +13,7 @@ import CardMedia from '@material-ui/core/CardMedia'; import Typography from '@material-ui/core/Typography'; import { Link } from 'react-router-dom'; import { Grid } from '@material-ui/core'; -import useLocalStorage from '../util/useLocalStorage'; +import useLocalStorage from 'util/useLocalStorage'; const useStyles = makeStyles({ root: { diff --git a/webUI/react/src/components/MangaDetails.tsx b/webUI/react/src/components/manga/MangaDetails.tsx similarity index 98% rename from webUI/react/src/components/MangaDetails.tsx rename to webUI/react/src/components/manga/MangaDetails.tsx index 60ac6093..fbfd0fc1 100644 --- a/webUI/react/src/components/MangaDetails.tsx +++ b/webUI/react/src/components/manga/MangaDetails.tsx @@ -13,9 +13,9 @@ import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'; import FilterListIcon from '@material-ui/icons/FilterList'; import PublicIcon from '@material-ui/icons/Public'; import React, { useContext, useEffect, useState } from 'react'; -import NavbarContext from '../context/NavbarContext'; -import client from '../util/client'; -import useLocalStorage from '../util/useLocalStorage'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; +import useLocalStorage from 'util/useLocalStorage'; import CategorySelect from './CategorySelect'; const useStyles = (inLibrary: string) => makeStyles((theme: Theme) => ({ diff --git a/webUI/react/src/components/MangaGrid.tsx b/webUI/react/src/components/manga/MangaGrid.tsx similarity index 100% rename from webUI/react/src/components/MangaGrid.tsx rename to webUI/react/src/components/manga/MangaGrid.tsx diff --git a/webUI/react/src/components/SourceCard.tsx b/webUI/react/src/components/manga/SourceCard.tsx similarity index 96% rename from webUI/react/src/components/SourceCard.tsx rename to webUI/react/src/components/manga/SourceCard.tsx index e1562fed..79943b8c 100644 --- a/webUI/react/src/components/SourceCard.tsx +++ b/webUI/react/src/components/manga/SourceCard.tsx @@ -12,8 +12,8 @@ import CardContent from '@material-ui/core/CardContent'; import Button from '@material-ui/core/Button'; import Avatar from '@material-ui/core/Avatar'; import Typography from '@material-ui/core/Typography'; -import useLocalStorage from '../util/useLocalStorage'; -import { langCodeToName } from '../util/language'; +import useLocalStorage from 'util/useLocalStorage'; +import { langCodeToName } from 'util/language'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/webUI/react/src/components/reader/Page.tsx b/webUI/react/src/components/manga/reader/Page.tsx similarity index 100% rename from webUI/react/src/components/reader/Page.tsx rename to webUI/react/src/components/manga/reader/Page.tsx diff --git a/webUI/react/src/components/reader/PageNumber.tsx b/webUI/react/src/components/manga/reader/PageNumber.tsx similarity index 100% rename from webUI/react/src/components/reader/PageNumber.tsx rename to webUI/react/src/components/manga/reader/PageNumber.tsx diff --git a/webUI/react/src/components/reader/pager/HorizontalPager.tsx b/webUI/react/src/components/manga/reader/pager/HorizontalPager.tsx similarity index 100% rename from webUI/react/src/components/reader/pager/HorizontalPager.tsx rename to webUI/react/src/components/manga/reader/pager/HorizontalPager.tsx diff --git a/webUI/react/src/components/reader/pager/PagedPager.tsx b/webUI/react/src/components/manga/reader/pager/PagedPager.tsx similarity index 100% rename from webUI/react/src/components/reader/pager/PagedPager.tsx rename to webUI/react/src/components/manga/reader/pager/PagedPager.tsx diff --git a/webUI/react/src/components/reader/pager/VerticalPager.tsx b/webUI/react/src/components/manga/reader/pager/VerticalPager.tsx similarity index 100% rename from webUI/react/src/components/reader/pager/VerticalPager.tsx rename to webUI/react/src/components/manga/reader/pager/VerticalPager.tsx diff --git a/webUI/react/src/screens/anime/AnimeExtensions.tsx b/webUI/react/src/screens/anime/AnimeExtensions.tsx new file mode 100644 index 00000000..0dac81c2 --- /dev/null +++ b/webUI/react/src/screens/anime/AnimeExtensions.tsx @@ -0,0 +1,112 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import React, { useContext, useEffect, useState } from 'react'; +import ExtensionCard from 'components/anime/ExtensionCard'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; +import useLocalStorage from 'util/useLocalStorage'; +import ExtensionLangSelect from 'components/manga/ExtensionLangSelect'; +import { defualtLangs, langCodeToName, langSortCmp } from 'util/language'; + +const allLangs: string[] = []; + +function groupExtensions(extensions: IExtension[]) { + allLangs.length = 0; // empty the array + const result = { installed: [], 'updates pending': [] } as any; + extensions.sort((a, b) => ((a.apkName > b.apkName) ? 1 : -1)); + + extensions.forEach((extension) => { + if (result[extension.lang] === undefined) { + result[extension.lang] = []; + if (extension.lang !== 'all') { allLangs.push(extension.lang); } + } + if (extension.installed) { + if (extension.hasUpdate) { + result['updates pending'].push(extension); + } else { + result.installed.push(extension); + } + } else { + result[extension.lang].push(extension); + } + }); + + // put english first for convience + allLangs.sort(langSortCmp); + + return result; +} + +function extensionDefaultLangs() { + return [...defualtLangs(), 'all']; +} + +export default function AnimeExtensions() { + const { setTitle, setAction } = useContext(NavbarContext); + const [shownLangs, setShownLangs] = useLocalStorage('shownExtensionLangs', extensionDefaultLangs()); + + useEffect(() => { + setTitle('Extensions'); + setAction( + , + ); + }, [shownLangs]); + + const [extensionsRaw, setExtensionsRaw] = useState([]); + const [extensions, setExtensions] = useState({}); + + const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack + const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack + + useEffect(() => { + client.get('/api/v1/anime/extension/list') + .then((response) => response.data) + .then((data) => setExtensionsRaw(data)); + }, [updateTriggerHolder]); + + useEffect(() => { + if (extensionsRaw.length > 0) { + const groupedExtension = groupExtensions(extensionsRaw); + setExtensions(groupedExtension); + } + }, [extensionsRaw]); + + if (Object.entries(extensions).length === 0) { + return

loading...

; + } + const groupsToShow = ['updates pending', 'installed', ...shownLangs]; + return ( + <> + { + Object.entries(extensions).map(([lang, list]) => ( + ((groupsToShow.indexOf(lang) !== -1 && (list as []).length > 0) + && ( + +

+ {langCodeToName(lang)} +

+ {(list as IExtension[]).map((it) => ( + { + triggerUpdate(); + }} + /> + ))} +
+ )) + )) + } + + ); +} diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/manga/Library.tsx similarity index 96% rename from webUI/react/src/screens/Library.tsx rename to webUI/react/src/screens/manga/Library.tsx index 9f4320c5..5c9983d8 100644 --- a/webUI/react/src/screens/Library.tsx +++ b/webUI/react/src/screens/manga/Library.tsx @@ -7,10 +7,10 @@ import { Tab, Tabs } from '@material-ui/core'; 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'; +import MangaGrid from 'components/manga/MangaGrid'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; +import cloneObject from 'util/cloneObject'; interface IMangaCategory { category: ICategory diff --git a/webUI/react/src/screens/Manga.tsx b/webUI/react/src/screens/manga/Manga.tsx similarity index 92% rename from webUI/react/src/screens/Manga.tsx rename to webUI/react/src/screens/manga/Manga.tsx index 6b0759b5..0f87ebeb 100644 --- a/webUI/react/src/screens/Manga.tsx +++ b/webUI/react/src/screens/manga/Manga.tsx @@ -9,12 +9,12 @@ import React, { useEffect, useState, useContext } from 'react'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { useParams } from 'react-router-dom'; import { Virtuoso } from 'react-virtuoso'; -import ChapterCard from '../components/ChapterCard'; -import MangaDetails from '../components/MangaDetails'; -import NavbarContext from '../context/NavbarContext'; -import client from '../util/client'; -import LoadingPlaceholder from '../components/LoadingPlaceholder'; -import makeToast from '../components/Toast'; +import ChapterCard from 'components/manga/ChapterCard'; +import MangaDetails from 'components/manga/MangaDetails'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; +import LoadingPlaceholder from 'components/LoadingPlaceholder'; +import makeToast from 'components/Toast'; const useStyles = makeStyles((theme: Theme) => ({ root: { diff --git a/webUI/react/src/screens/Extensions.tsx b/webUI/react/src/screens/manga/MangaExtensions.tsx similarity index 90% rename from webUI/react/src/screens/Extensions.tsx rename to webUI/react/src/screens/manga/MangaExtensions.tsx index 968d9fd4..b92cb579 100644 --- a/webUI/react/src/screens/Extensions.tsx +++ b/webUI/react/src/screens/manga/MangaExtensions.tsx @@ -6,12 +6,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import React, { useContext, useEffect, useState } from 'react'; -import ExtensionCard from '../components/ExtensionCard'; -import NavbarContext from '../context/NavbarContext'; -import client from '../util/client'; -import useLocalStorage from '../util/useLocalStorage'; -import ExtensionLangSelect from '../components/ExtensionLangSelect'; -import { defualtLangs, langCodeToName, langSortCmp } from '../util/language'; +import ExtensionCard from 'components/manga/ExtensionCard'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; +import useLocalStorage from 'util/useLocalStorage'; +import ExtensionLangSelect from 'components/manga/ExtensionLangSelect'; +import { defualtLangs, langCodeToName, langSortCmp } from 'util/language'; const allLangs: string[] = []; @@ -46,7 +46,7 @@ function extensionDefaultLangs() { return [...defualtLangs(), 'all']; } -export default function Extensions() { +export default function MangaExtensions() { const { setTitle, setAction } = useContext(NavbarContext); const [shownLangs, setShownLangs] = useLocalStorage('shownExtensionLangs', extensionDefaultLangs()); diff --git a/webUI/react/src/screens/Reader.tsx b/webUI/react/src/screens/manga/Reader.tsx similarity index 91% rename from webUI/react/src/screens/Reader.tsx rename to webUI/react/src/screens/manga/Reader.tsx index 4722eb67..c6dde337 100644 --- a/webUI/react/src/screens/Reader.tsx +++ b/webUI/react/src/screens/manga/Reader.tsx @@ -9,15 +9,15 @@ import CircularProgress from '@material-ui/core/CircularProgress'; import { makeStyles } from '@material-ui/core/styles'; import React, { useContext, useEffect, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import HorizontalPager from '../components/reader/pager/HorizontalPager'; -import PageNumber from '../components/reader/PageNumber'; -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'; +import HorizontalPager from 'components/manga/reader/pager/HorizontalPager'; +import PageNumber from 'components/manga/reader/PageNumber'; +import WebtoonPager from 'components/manga/reader/pager/PagedPager'; +import VerticalPager from 'components/manga/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: { diff --git a/webUI/react/src/screens/SearchSingle.tsx b/webUI/react/src/screens/manga/SearchSingle.tsx similarity index 96% rename from webUI/react/src/screens/SearchSingle.tsx rename to webUI/react/src/screens/manga/SearchSingle.tsx index 483b88ea..656b6294 100644 --- a/webUI/react/src/screens/SearchSingle.tsx +++ b/webUI/react/src/screens/manga/SearchSingle.tsx @@ -10,9 +10,9 @@ import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button'; import { useParams } from 'react-router-dom'; -import MangaGrid from '../components/MangaGrid'; -import NavbarContext from '../context/NavbarContext'; -import client from '../util/client'; +import MangaGrid from 'components/manga/MangaGrid'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/webUI/react/src/screens/SourceMangas.tsx b/webUI/react/src/screens/manga/SourceMangas.tsx similarity index 92% rename from webUI/react/src/screens/SourceMangas.tsx rename to webUI/react/src/screens/manga/SourceMangas.tsx index 01d8ab12..9cdee012 100644 --- a/webUI/react/src/screens/SourceMangas.tsx +++ b/webUI/react/src/screens/manga/SourceMangas.tsx @@ -7,9 +7,9 @@ import React, { useContext, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import MangaGrid from '../components/MangaGrid'; -import NavbarContext from '../context/NavbarContext'; -import client from '../util/client'; +import MangaGrid from 'components/manga/MangaGrid'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; export default function SourceMangas(props: { popular: boolean }) { const { setTitle, setAction } = useContext(NavbarContext); diff --git a/webUI/react/src/screens/Sources.tsx b/webUI/react/src/screens/manga/Sources.tsx similarity index 88% rename from webUI/react/src/screens/Sources.tsx rename to webUI/react/src/screens/manga/Sources.tsx index 7b37b251..1cbc7976 100644 --- a/webUI/react/src/screens/Sources.tsx +++ b/webUI/react/src/screens/manga/Sources.tsx @@ -6,12 +6,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import React, { useContext, useEffect, useState } from 'react'; -import ExtensionLangSelect from '../components/ExtensionLangSelect'; -import SourceCard from '../components/SourceCard'; -import NavbarContext from '../context/NavbarContext'; -import client from '../util/client'; -import { defualtLangs, langCodeToName, langSortCmp } from '../util/language'; -import useLocalStorage from '../util/useLocalStorage'; +import ExtensionLangSelect from 'components/manga/ExtensionLangSelect'; +import SourceCard from 'components/manga/SourceCard'; +import NavbarContext from 'context/NavbarContext'; +import client from 'util/client'; +import { defualtLangs, langCodeToName, langSortCmp } from 'util/language'; +import useLocalStorage from 'util/useLocalStorage'; function sourceToLangList(sources: ISource[]) { const result: string[] = []; diff --git a/webUI/react/tsconfig.json b/webUI/react/tsconfig.json index 74caf0df..00a10034 100644 --- a/webUI/react/tsconfig.json +++ b/webUI/react/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "baseUrl": "./src", "target": "es5", "lib": [ "dom",