From d57827b6d3e11458e9eeedca5ed1957f81ff89f9 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Thu, 24 Dec 2020 19:37:27 +0330 Subject: [PATCH 1/3] commit what we got --- server/build.gradle.kts | 11 ++++ .../main/kotlin/ir/armor/tachidesk/Config.kt | 7 +++ .../main/kotlin/ir/armor/tachidesk/Main.kt | 50 +++++++++++++------ .../ir/armor/tachidesk/database/DBMangaer.kt | 24 +++++++++ .../database/model/ExtensionTable.kt | 30 +++++++++++ .../tachidesk/database/model/SourcesTable.kt | 26 ++++++++++ .../ir/armor/tachidesk/extension/Extension.kt | 4 ++ 7 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/Config.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/database/model/SourcesTable.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 47c3b91..9d167ab 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -15,6 +15,7 @@ compileKotlin.kotlinOptions { repositories { mavenCentral() + jcenter() } dependencies { @@ -69,6 +70,16 @@ dependencies { implementation("org.slf4j:slf4j-api:1.8.0-beta4") implementation("com.fasterxml.jackson.core:jackson-databind:2.10.3") + // to get application content root + implementation("net.harawata:appdirs:1.2.0") + + // Exposed ORM + val exposed_version = "0.28.1" + implementation ("org.jetbrains.exposed:exposed-core:$exposed_version") + implementation ("org.jetbrains.exposed:exposed-dao:$exposed_version") + implementation ("org.jetbrains.exposed:exposed-jdbc:$exposed_version") + implementation ("org.xerial:sqlite-jdbc:3.30.1") + testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Config.kt b/server/src/main/kotlin/ir/armor/tachidesk/Config.kt new file mode 100644 index 0000000..40bdbbd --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/Config.kt @@ -0,0 +1,7 @@ +package ir.armor.tachidesk + +import net.harawata.appdirs.AppDirsFactory + +object Config { + val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk",null, null) +} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index 3dab39e..089dc71 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -7,19 +7,25 @@ import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.online.HttpSource import io.javalin.Javalin import io.javalin.http.Context +import ir.armor.tachidesk.database.DBMangaer +import ir.armor.tachidesk.database.makeDataBaseTables +import ir.armor.tachidesk.database.model.ExtensionsTable +import ir.armor.tachidesk.database.model.SourcesTable import kotlinx.coroutines.runBlocking +import net.harawata.appdirs.AppDirsFactory import okhttp3.Request import okio.buffer import okio.sink +import org.jetbrains.exposed.sql.Database import java.io.File import java.net.URL import java.net.URLClassLoader +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction class Main { companion object { - const val contentRoot = "/tmp/tachidesk" - @JvmStatic fun downloadAPK(url: String, apkPath: String){ val request = Request.Builder().url(url).build() @@ -33,6 +39,7 @@ class Main { @JvmStatic fun testExtensionExecution(){ + val contentRoot = Config.dataRoot + "/extensions" File(contentRoot).mkdirs() var sourcePkg = "" @@ -61,7 +68,7 @@ class Main { // dex -> jar Dex2jarCmd.main(dexFilePath, "-o", jarFilePath, "--force") - val child = URLClassLoader(arrayOf(URL("file:$jarFilePath")), this.javaClass.classLoader) + val child = URLClassLoader(arrayOf(URL("file:$jarFilePath")), this::class.java.classLoader) val classToLoad = Class.forName(className, true, child) val instance = classToLoad.newInstance() as HttpSource val result = instance.fetchPopularManga(1) @@ -75,19 +82,34 @@ class Main { @JvmStatic fun main(args: Array) { - val app = Javalin.create().start(4567) + // make sure data everything we need exists + File(Config.dataRoot).mkdirs() + makeDataBaseTables() - app.before() { ctx -> - ctx.header("Access-Control-Allow-Origin", "*") - } - app.get("/api/v1/extensions") { ctx -> - runBlocking { - val api = ExtensionGithubApi() - val sources = api.findExtensions() - ctx.json(sources) - } - } +// val app = Javalin.create().start(4567) +// +// app.before() { ctx -> +// ctx.header("Access-Control-Allow-Origin", "*") +// } +// +// app.get("/api/v1/extensions") { ctx -> +// runBlocking { +// val api = ExtensionGithubApi() +// val sources = api.findExtensions() +// ctx.json(sources) +// } +// } +// +// app.get("/api/v1/extensions/install/:extensionURL") { ctx -> +// ctx.pathParam("extensionURL") +// }) + + +// ExtensionTable.new { +// name = "khar" +// pkgName = "eu.khar" +// } } } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt new file mode 100644 index 0000000..807490a --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt @@ -0,0 +1,24 @@ +package ir.armor.tachidesk.database + +import ir.armor.tachidesk.Config +import ir.armor.tachidesk.database.model.ExtensionsTable +import ir.armor.tachidesk.database.model.SourcesTable +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction + +object DBMangaer { + val db by lazy { + Database.connect("jdbc:sqlite:${Config.dataRoot}/database.db", "org.sqlite.JDBC") + } +} + +fun makeDataBaseTables() { + // mention db object to connect + DBMangaer.db + + transaction { + SchemaUtils.create(ExtensionsTable) + SchemaUtils.create(SourcesTable) + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt new file mode 100644 index 0000000..646e078 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt @@ -0,0 +1,30 @@ +package ir.armor.tachidesk.database.model + +import eu.kanade.tachiyomi.source.Source +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Table + + +object ExtensionsTable : IntIdTable() { + 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", 5) + val isNsfw = bool("is_nsfw") +} + +//class Extension(id: EntityID) : IntEntity(id) { +// companion object : IntEntityClass(ExtensionsTable) +// +// val name by ExtensionsTable.name +// val pkgName by ExtensionsTable.pkgName +// val versionName by ExtensionsTable.versionName +// val versionCode by ExtensionsTable.versionCode +// val lang by ExtensionsTable.lang +// val isNsfw by ExtensionsTable.isNsfw +//} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/model/SourcesTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/model/SourcesTable.kt new file mode 100644 index 0000000..c83cbac --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/model/SourcesTable.kt @@ -0,0 +1,26 @@ +package ir.armor.tachidesk.database.model + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Table + +object SourcesTable : Table() { + val id: Column = long("id") + val name: Column = varchar("name", 128) + val extension = reference("extension", ExtensionsTable) + override val primaryKey = PrimaryKey(id) +} + +//class Source : Entity() { +// companion object : Entity(SourcesTable) +// +// val name by ExtensionsTable.name +// val pkgName by ExtensionsTable.pkgName +// val versionName by ExtensionsTable.versionName +// val versionCode by ExtensionsTable.versionCode +// val lang by ExtensionsTable.lang +// val isNsfw by ExtensionsTable.isNsfw +//} \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt b/server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt new file mode 100644 index 0000000..9d30482 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt @@ -0,0 +1,4 @@ +package ir.armor.tachidesk.extension + +class Extension { +} \ No newline at end of file From f16bead363ed239b11ff46a70797ba38210c1233 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Fri, 25 Dec 2020 02:39:30 +0330 Subject: [PATCH 2/3] apk installation --- .../extension/api/ExtensionGithubApi.kt | 5 + .../main/kotlin/ir/armor/tachidesk/Config.kt | 1 + .../main/kotlin/ir/armor/tachidesk/Main.kt | 174 ++++++++++++++---- .../database/model/ExtensionTable.kt | 29 +-- .../ir/armor/tachidesk/extension/Extension.kt | 4 - 5 files changed, 162 insertions(+), 51 deletions(-) delete mode 100644 server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index 8f41ef1..71593f2 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -7,6 +7,7 @@ import com.github.salomonbrys.kotson.int import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.util.ExtensionLoader +import ir.armor.tachidesk.database.model.ExtensionDataClass //import kotlinx.coroutines.Dispatchers //import kotlinx.coroutines.withContext import kotlinx.serialization.json.JsonArray @@ -75,6 +76,10 @@ internal class ExtensionGithubApi { return "$REPO_URL_PREFIX/apk/${extension.apkName}" } + fun getApkUrl(extension: ExtensionDataClass): String { + return "$REPO_URL_PREFIX/apk/${extension.apkName}" + } + companion object { const val BASE_URL = "https://raw.githubusercontent.com/" const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo" diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Config.kt b/server/src/main/kotlin/ir/armor/tachidesk/Config.kt index 40bdbbd..c3822e5 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Config.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Config.kt @@ -4,4 +4,5 @@ import net.harawata.appdirs.AppDirsFactory object Config { val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk",null, null) + val extensionsRoot = "$dataRoot/extensions" } \ No newline at end of file diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index 089dc71..1e229bc 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -2,32 +2,31 @@ package ir.armor.tachidesk import com.googlecode.dex2jar.tools.Dex2jarCmd import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi +import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.online.HttpSource import io.javalin.Javalin -import io.javalin.http.Context -import ir.armor.tachidesk.database.DBMangaer import ir.armor.tachidesk.database.makeDataBaseTables +import ir.armor.tachidesk.database.model.ExtensionDataClass import ir.armor.tachidesk.database.model.ExtensionsTable -import ir.armor.tachidesk.database.model.SourcesTable import kotlinx.coroutines.runBlocking -import net.harawata.appdirs.AppDirsFactory import okhttp3.Request import okio.buffer import okio.sink -import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.* import java.io.File import java.net.URL import java.net.URLClassLoader -import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.transactions.transaction - class Main { companion object { + var lastExtensionCheck: Long = 0 + + @JvmStatic - fun downloadAPK(url: String, apkPath: String){ + fun downloadAPKFile(url: String, apkPath: String) { val request = Request.Builder().url(url).build() val response = NetworkHelper().client.newCall(request).execute(); @@ -38,9 +37,8 @@ class Main { } @JvmStatic - fun testExtensionExecution(){ - val contentRoot = Config.dataRoot + "/extensions" - File(contentRoot).mkdirs() + fun testExtensionExecution() { + File(Config.extensionsRoot).mkdirs() var sourcePkg = "" // get list of extensions @@ -55,13 +53,13 @@ class Main { } val apkFileName = apkToDownload.split("/").last() - val apkFilePath = "$contentRoot/$apkFileName" + val apkFilePath = "${Config.extensionsRoot}/$apkFileName" val zipDirPath = apkFilePath.substringBefore(".apk") val jarFilePath = "$zipDirPath.jar" val dexFilePath = "$zipDirPath.dex" // download apk file - downloadAPK(apkToDownload, apkFilePath) + downloadAPKFile(apkToDownload, apkFilePath) val className = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath) @@ -76,40 +74,144 @@ class Main { mangasPage.mangas.forEach { println(it.title) } -// exitProcess(0) + } + + fun extensionDatabaseIsEmtpy(): Boolean { + return transaction { + return@transaction ExtensionsTable.selectAll().count() == 0L + } + } + + fun getExtensionList(offline: Boolean = false): List { + // update if 60 seconds has passed or requested offline and database is empty + if (lastExtensionCheck + 60 * 1000 < System.currentTimeMillis() || (offline && extensionDatabaseIsEmtpy())) { + println("Getting extensions list from the internet") + lastExtensionCheck = System.currentTimeMillis() + var foundExtensions: List + runBlocking { + val api = ExtensionGithubApi() + foundExtensions = api.findExtensions() + transaction { + foundExtensions.forEach { foundExtension -> + val extensionRecord = ExtensionsTable.select { ExtensionsTable.name eq foundExtension.name }.firstOrNull() + if (extensionRecord != null) { + // update the record + ExtensionsTable.update({ ExtensionsTable.name eq foundExtension.name }) { + it[name] = foundExtension.name + it[pkgName] = foundExtension.pkgName + it[versionName] = foundExtension.versionName + it[versionCode] = foundExtension.versionCode + it[lang] = foundExtension.lang + it[isNsfw] = foundExtension.isNsfw + it[apkName] = foundExtension.apkName + it[iconUrl] = foundExtension.iconUrl + } + } else { + // insert new record + ExtensionsTable.insert { + it[name] = foundExtension.name + it[pkgName] = foundExtension.pkgName + it[versionName] = foundExtension.versionName + it[versionCode] = foundExtension.versionCode + it[lang] = foundExtension.lang + it[isNsfw] = foundExtension.isNsfw + it[apkName] = foundExtension.apkName + it[iconUrl] = foundExtension.iconUrl + } + } + } + } + } + } + + return transaction { + return@transaction ExtensionsTable.selectAll().map { + ExtensionDataClass( + it[ExtensionsTable.name], + it[ExtensionsTable.pkgName], + it[ExtensionsTable.versionName], + it[ExtensionsTable.versionCode], + it[ExtensionsTable.lang], + it[ExtensionsTable.isNsfw], + it[ExtensionsTable.apkName], + it[ExtensionsTable.iconUrl], + it[ExtensionsTable.installed], + it[ExtensionsTable.classFQName] + ) + } + + } + } + + fun downloadApk(apkName: String): Int { + val extension = getExtensionList(true).first { it.apkName == apkName } + val fileNameWithoutType = apkName.substringBefore(".apk") + val dirPathWithoutType = "${Config.extensionsRoot}/$apkName" + + // check if we don't have the dex file already downloaded + val dexPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar" + if (!File(dexPath).exists()) { + runBlocking { + val api = ExtensionGithubApi() + val apkToDownload = api.getApkUrl(extension) + + val apkFilePath = "$dirPathWithoutType.apk" + val jarFilePath = "$dirPathWithoutType.jar" + val dexFilePath = "$dirPathWithoutType.dex" + + // download apk file + downloadAPKFile(apkToDownload, apkFilePath) + + + val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath) + + // dex -> jar + Dex2jarCmd.main(dexFilePath, "-o", jarFilePath, "--force") + + File(apkFilePath).delete() + + transaction { + ExtensionsTable.update({ ExtensionsTable.name eq extension.name }) { + it[installed] = true + it[classFQName] = className + } + } + + } + return 201 // we downloaded successfully + } + else { + return 302 + } } @JvmStatic fun main(args: Array) { - // make sure data everything we need exists + // make sure everything we need exists File(Config.dataRoot).mkdirs() + File(Config.extensionsRoot).mkdirs() makeDataBaseTables() -// val app = Javalin.create().start(4567) -// -// app.before() { ctx -> -// ctx.header("Access-Control-Allow-Origin", "*") -// } -// -// app.get("/api/v1/extensions") { ctx -> -// runBlocking { -// val api = ExtensionGithubApi() -// val sources = api.findExtensions() -// ctx.json(sources) -// } -// } -// -// app.get("/api/v1/extensions/install/:extensionURL") { ctx -> -// ctx.pathParam("extensionURL") -// }) + val app = Javalin.create().start(4567) + + app.before() { ctx -> + ctx.header("Access-Control-Allow-Origin", "*") // allow the client which is running on another port + } + + app.get("/api/v1/extensions") { ctx -> + ctx.json(getExtensionList()) + } -// ExtensionTable.new { -// name = "khar" -// pkgName = "eu.khar" -// } + app.get("/api/v1/extensions/install/:apkName") { ctx -> + val apkName = ctx.pathParam("apkName") + ctx.status( + downloadApk(apkName) + ) + } + } } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt index 646e078..9cd3a05 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/model/ExtensionTable.kt @@ -15,16 +15,23 @@ object ExtensionsTable : IntIdTable() { val versionName = varchar("version_name", 16) val versionCode = integer("version_code") val lang = varchar("lang", 5) - val isNsfw = bool("is_nsfw") + val isNsfw = bool("is_nsfw") + val apkName = varchar("apk_name", 1024) + val iconUrl = varchar("icon_url", 2048) + + val installed = bool("installed").default(false) + val classFQName = varchar("class_name", 256).default("") // fully qualified name } -//class Extension(id: EntityID) : IntEntity(id) { -// companion object : IntEntityClass(ExtensionsTable) -// -// val name by ExtensionsTable.name -// val pkgName by ExtensionsTable.pkgName -// val versionName by ExtensionsTable.versionName -// val versionCode by ExtensionsTable.versionCode -// val lang by ExtensionsTable.lang -// val isNsfw by ExtensionsTable.isNsfw -//} +data class ExtensionDataClass( + val name: String, + val pkgName: String, + val versionName: String, + val versionCode: Int, + val lang: String, + val isNsfw: Boolean, + val apkName: String, + val iconUrl : String, + val installed: Boolean, + val classFQName: String, +) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt b/server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt deleted file mode 100644 index 9d30482..0000000 --- a/server/src/main/kotlin/ir/armor/tachidesk/extension/Extension.kt +++ /dev/null @@ -1,4 +0,0 @@ -package ir.armor.tachidesk.extension - -class Extension { -} \ No newline at end of file From 5ddc087a6d3d6763f9da990f9da5c383bdfc64d5 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Fri, 25 Dec 2020 03:08:08 +0330 Subject: [PATCH 3/3] installation done --- .../main/kotlin/ir/armor/tachidesk/Main.kt | 1 + webUI/react/src/components/ExtensionCard.tsx | 26 ++++++++++++------- webUI/react/src/typings.d.ts | 2 ++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index 1e229bc..dbd71ca 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -207,6 +207,7 @@ class Main { app.get("/api/v1/extensions/install/:apkName") { ctx -> val apkName = ctx.pathParam("apkName") + println(apkName) ctx.status( downloadApk(apkName) ) diff --git a/webUI/react/src/components/ExtensionCard.tsx b/webUI/react/src/components/ExtensionCard.tsx index 0b0a03c..d8005da 100644 --- a/webUI/react/src/components/ExtensionCard.tsx +++ b/webUI/react/src/components/ExtensionCard.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +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'; @@ -36,16 +36,24 @@ interface IProps { extension: IExtension } -export default function ExtensionCard({ - extension: { - name, lang, versionName, iconUrl, - }, -}: IProps) { +export default function ExtensionCard(props: IProps) { + const { + extension: { + name, lang, versionName, iconUrl, installed, apkName, + }, + } = props; + const [installedState, setInstalledState] = useState((installed ? 'installed' : 'install')); + const classes = useStyles(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const bull = ; const langPress = lang === 'all' ? 'All' : lang.toUpperCase(); + function install() { + setInstalledState('installing'); + fetch(`http://127.0.0.1:4567/api/v1/extensions/install/${apkName}`).then(() => { + setInstalledState('installed'); + }); + } + return ( @@ -68,7 +76,7 @@ export default function ExtensionCard({ - + ); diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index dcc4045..1a21c32 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -3,4 +3,6 @@ interface IExtension { lang: string versionName: string iconUrl: string + installed: boolean + apkName: string }