Compare commits

...

41 Commits

Author SHA1 Message Date
Aria Moradi 8d9f5b0bd9 [RELEASE CI] bug fixes, half-baked search 2021-01-22 02:39:24 +03:30
Aria Moradi 5cbb2a0481 fix SPA urls 2021-01-22 02:33:41 +03:30
Aria Moradi a3b17365b7 [RELEASE CI] fix build issues 2021-01-22 01:55:41 +03:30
Aria Moradi 2847554b8f [RELEASE CI] test release 2021-01-22 01:47:28 +03:30
Aria Moradi fb166eadd4 Update README.md 2021-01-22 01:45:14 +03:30
Aria Moradi 046c11f785 Update README.md 2021-01-22 01:40:47 +03:30
Aria Moradi eac7436b18 clean up bulid file name 2021-01-22 01:37:24 +03:30
Aria Moradi 1a23804e51 only release when told to :D 2021-01-22 00:40:05 +03:30
Aria Moradi 08be142858 Update README.md 2021-01-22 00:36:00 +03:30
Aria Moradi f421dbfe69 Update README.md 2021-01-22 00:34:49 +03:30
Aria Moradi 0d7ad65dbe Update README.md 2021-01-22 00:27:16 +03:30
Aria Moradi 9e946406bc Update README.md 2021-01-22 00:25:47 +03:30
Aria Moradi 9142d46fae Update README.md 2021-01-22 00:23:53 +03:30
Aria Moradi 39ed134f96 improve builds 2021-01-21 23:04:47 +03:30
Aria Moradi 7e4b495398 dummy file to trigger gh actions 2021-01-21 22:53:00 +03:30
Aria Moradi c12242b760 rm package-lock in favor of yarn 2021-01-21 22:43:17 +03:30
Aria Moradi 8b2fd28a54 remove shit, add shit to improve performance 2021-01-21 22:39:22 +03:30
Aria Moradi 466f21a7e8 set -e is the poison 2021-01-21 22:27:03 +03:30
Aria Moradi 26a7e2f1cd fix again? 2021-01-21 16:27:27 +03:30
Aria Moradi c155d78d52 fix build? 2021-01-21 16:20:22 +03:30
Aria Moradi 687dad5fc0 new scripts 2021-01-21 16:01:55 +03:30
Aria Moradi 33c4cdbc48 remove dummyFile 2021-01-21 15:38:20 +03:30
Aria Moradi e46cb9738f add latest revision 2021-01-21 15:35:14 +03:30
Aria Moradi aef37fcc9e add revision 2021-01-21 15:34:21 +03:30
Aria Moradi ac51f40a91 dummy file to trigger gh actions 2021-01-21 15:14:00 +03:30
Aria Moradi cd82af2d76 fix yarn build task 2021-01-21 15:04:11 +03:30
Aria Moradi 790040cd68 add stacktrace 2021-01-21 14:41:45 +03:30
Aria Moradi 95465ec265 update getAndroid 2021-01-21 14:33:02 +03:30
Aria Moradi 5313d91bf2 remove unfinished code for now 2021-01-21 14:29:13 +03:30
Aria Moradi 63783984c6 add getAndroid to workflow 2021-01-21 14:23:22 +03:30
Aria Moradi 97b9b1b6c9 Merge branch 'master' of github.com:AriaMoradi/Tachidesk 2021-01-21 14:15:34 +03:30
Aria Moradi 34a7c24e0b add build workflow 2021-01-21 14:15:05 +03:30
Aria Moradi 12765a771f Update README.md 2021-01-21 13:58:49 +03:30
Aria Moradi 39850c71b0 Update README.md 2021-01-21 13:58:15 +03:30
Aria Moradi 9e43645a67 Update README.md 2021-01-21 13:42:17 +03:30
Aria Moradi 7dc7f4d905 Update README.md 2021-01-21 13:41:03 +03:30
Aria Moradi c537c1bf29 manga search UI done 2021-01-20 15:26:52 +03:30
Aria Moradi 5f3ddbd1b2 Update README.md 2021-01-20 12:24:47 +03:30
Aria Moradi f297e3790c Update README.md 2021-01-20 03:42:01 +03:30
Aria Moradi ef3532357f Update README.md 2021-01-20 03:41:22 +03:30
Aria Moradi 48f29edf6c add dummy file 2021-01-20 03:30:20 +03:30
17 changed files with 358 additions and 44 deletions
@@ -0,0 +1,7 @@
org.gradle.daemon=false
org.gradle.jvmargs=-Xmx5120m
org.gradle.workers.max=5
org.gradle.parallel=true
kotlin.incremental=false
kotlin.compiler.execution.strategy=in-process
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
cp ../master/repo/* .
new_build=$(ls | tail -1)
echo "New build file name: $new_build"
cp -f $new_build Tachidesk-latest.jar
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git status
if [ -n "$(git status --porcelain)" ]; then
git add .
git commit -m "Update repo"
git push
else
echo "No changes to commit"
fi
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
mkdir -p repo/
# Get last commit message
last_commit_log=$(git log -1 --pretty=format:"%s")
echo "last commit log: $last_commit_log"
filter_count=$(echo "$last_commit_log" | grep -c "[RELEASE CI]" )
if [ "$filter_count" -gt 0 ]; then
cp server/build/Tachidesk-*.jar repo/
fi
+84
View File
@@ -0,0 +1,84 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v2
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
build:
name: Build FatJar
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.5.0
with:
access_token: ${{ github.token }}
- name: Checkout master branch
uses: actions/checkout@v2
with:
ref: master
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Copy CI gradle.properties
run: |
cd master
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Download and process android.jar
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
run: |
cd master
./scripts/getAndroid.sh
- name: Build the Jar
uses: eskatos/gradle-command-action@v1
with:
build-root-directory: master
wrapper-directory: master
arguments: :server:shadowJar --stacktrace
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: Create repo artifacts
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
run: |
cd master
./.github/scripts/create-repo.sh
- name: Checkout repo branch
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
uses: actions/checkout@v2
with:
ref: repo
path: repo
- name: Deploy repo
if: github.event_name == 'push' && github.repository == 'AriaMoradi/Tachidesk'
run: |
cd repo
../master/.github/scripts/commit-repo.sh
+31 -14
View File
@@ -1,33 +1,50 @@
# Tachidesk
A not so much port of [Tachiyomi](https://tachiyomi.org/) to the web (and later Electron for the desktop experience)!
A free and open source manga reader than runs extensions built for [Tachiyomi](https://tachiyomi.org/) which runs on desktop operating systems.
Ability to read and write Tachiyomi compatible backups and syncing is a planned feature.
## How does it work?
This project has two components:
1. **server:** contains some of the original Tachiyomi code and serves a REST API
2. **webUI:** A react project that works with the server to do the presentation
1. **server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run apk extensions. All this concludes to serving a REST API to `webUI`.
2. **webUI:** A react SPA project that works with the server to do the presentation.
## How do I run the thing?
### Get Android stubs jar(do this only once)
#### Running pre-built jar packages
Download the latest (or a working more stable) release from [the repo branch](https://github.com/AriaMoradi/Tachidesk/tree/repo) or obtain it from [the releases section](https://github.com/AriaMoradi/Tachidesk/releases).
Double click on the jar file or run `java -jar Tachidesk-latest.jar` or `java -jar Tachidesk-vX.Y.Z-rxxx.jar`
The server will be running on `http://localhost:4567` open this url in your browser.
## Building from source
### Get Android stubs jar
#### Manual download
Download [android.jar](https://raw.githubusercontent.com/AriaMoradi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
#### Building from source(needs `bash`, `curl`, `base64`, `zip` to work)
run `scripts/getAndroid.sh` from project's root directory to download and rebuild the jar file from Google's repository.
Run `scripts/getAndroid.sh` from project's root directory to download and rebuild the jar file from Google's repository.
### building the jar
run `./gradlew :server:fatJar` the resulting jar file will be `server/build/server-1.0-all.jar`. Simply double click on it or run `java -jar server-1.0-all.jar`. The server will be running on `http://localhost:4567` open this url in your browser.
## running for development purposes
### The Server
run `./gradlew :server:run -x :webUI:yarn_build --stacktrace` to run the server
### the webUI
how to do it is described in `webUI/react/README.md` but for short,
Run `./gradlew shadowJar` the resulting built jar file will be `server/build/Tachidesk-vX.Y.Z-rxxx.jar`.
## Running for development purposes
### `server` module
Run `./gradlew :server:run -x :webUI:copyBuild --stacktrace` to run the server
### `webUI` module
How to do it is described in `webUI/react/README.md` but for short,
first cd into `webUI/react` then run `yarn` to install the node modules(do this only once)
then `yarn start` to start the client if a new browser window doesn't start automatically,
then open `http://127.0.0.1:3000` in a modern browser.
then open `http://127.0.0.1:3000` in a modern browser. This is a `create-react-app` project
and supports HMR and all the other goodies you'll need.
## Is the application usable? Should I test it?
## Is this application usable? Should I test it?
Checkout [the state of project](https://github.com/AriaMoradi/Tachidesk/issues/2) to see what's implemented.
## Credit
The `AndroidCompat` module and `scripts/getAndroid.sh` was originally developed by [@null-dev](https://github.com/null-dev) for [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server) and is licensed under `Apache License Version 2.0`.
Parts of [tachiyomi](https://github.com/tachiyomiorg/tachiyomi) is adopted into this codebase, also licensed under `Apache License Version 2.0`.
## License
Copyright (C) 2020 Aria Moradi
Copyright (C) 2020-2021 Aria Moradi and contributors
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
+2 -3
View File
@@ -58,9 +58,8 @@ function dedup() {
pushd ..
dedup AndroidCompat/src/main/java
dedup TachiServer/src/main/java
dedup Tachiyomi-App/src/main/java
dedup Tachiyomi-App/src/compat/java
dedup server/src/main/java
dedup server/src/main/kotlin
popd
popd
+19 -1
View File
@@ -1,4 +1,5 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import java.io.BufferedReader
plugins {
// id("org.jetbrains.kotlin.jvm") version "1.4.21"
@@ -6,6 +7,8 @@ plugins {
id("com.github.johnrengelman.shadow") version "6.1.0"
}
val TachideskVersion = "v0.0.2"
repositories {
mavenCentral()
@@ -102,6 +105,19 @@ sourceSets {
}
}
val TachideskRevision = Runtime
.getRuntime()
.exec("git rev-list master --count")
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
"r"+output.trim()
}
tasks {
jar {
manifest {
@@ -115,12 +131,14 @@ tasks {
}
shadowJar {
manifest.inheritFrom(jar.get().manifest) //will make your shadowJar (produced by jar task) runnable
archiveBaseName.set("Tachidesk")
archiveVersion.set(TachideskVersion)
archiveClassifier.set(TachideskRevision)
}
}
tasks.withType<ShadowJar> {
destinationDir = File("$rootDir/server/build")
//dependsOn(":webUI:copyBuild")
}
tasks.named("processResources") {
@@ -36,10 +36,14 @@ class Main {
androidCompat.startApp(App())
val app = Javalin.create { config ->
// config.addSinglePageRoot("/", "")
config.addStaticFiles("/react")
try {
this::class.java.classLoader.getResource("/react/index.html")
config.addStaticFiles("/react")
config.addSinglePageRoot("/","/react/index.html")
} catch (e: RuntimeException) {
println("Warning: react build files are missing.")
}
}.start(4567)
@@ -68,12 +72,12 @@ class Main {
app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
val pageNum = ctx.pathParam("pageNum").toInt()
ctx.json(getMangaList(sourceId,pageNum,popular = true))
ctx.json(getMangaList(sourceId, pageNum, popular = true))
}
app.get("/api/v1/source/:sourceId/latest/:pageNum") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
val pageNum = ctx.pathParam("pageNum").toInt()
ctx.json(getMangaList(sourceId,pageNum,popular = false))
ctx.json(getMangaList(sourceId, pageNum, popular = false))
}
app.get("/api/v1/manga/:mangaId/") { ctx ->
@@ -89,11 +93,32 @@ class Main {
app.get("/api/v1/manga/:mangaId/chapter/:chapterId") { ctx ->
val chapterId = ctx.pathParam("chapterId").toInt()
val mangaId = ctx.pathParam("mangaId").toInt()
ctx.json(getPages(chapterId,mangaId))
ctx.json(getPages(chapterId, mangaId))
}
// global search
app.get("/api/v1/search/:searchTerm") { ctx ->
val searchTerm = ctx.pathParam("searchTerm")
ctx.json(sourceGlobalSearch(searchTerm))
}
// single source search
app.get("/api/v1/source/:sourceId/search/:searchTerm") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
val searchTerm = ctx.pathParam("searchTerm")
ctx.json(sourceSearch(sourceId, searchTerm))
}
// source filter list
app.get("/api/v1/source/:sourceId/filters/") { ctx ->
val sourceId = ctx.pathParam("sourceId").toLong()
ctx.json(sourceFilters(sourceId))
}
}
}
}
@@ -0,0 +1,59 @@
package ir.armor.tachidesk.util
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
fun sourceFilters(sourceId: Long) {
val source = getHttpSource(sourceId)
//source.getFilterList().toItems()
}
fun sourceSearch(sourceId: Long, searchTerm: String) {
val source = getHttpSource(sourceId)
//source.fetchSearchManga()
}
fun sourceGlobalSearch(searchTerm: String) {
}
data class FilterWrapper(
val type: String,
val filter: Any
)
//private fun FilterList.toItems(): List<FilterWrapper> {
// return mapNotNull { filter ->
// when (filter) {
// is Filter.Header -> FilterWrapper("Header",filter)
// is Filter.Separator -> FilterWrapper("Separator",filter)
// is Filter.CheckBox -> FilterWrapper("CheckBox",filter)
// is Filter.TriState -> FilterWrapper("TriState",filter)
// is Filter.Text -> FilterWrapper("Text",filter)
// is Filter.Select<*> -> FilterWrapper("Select",filter)
// is Filter.Group<*> -> {
// val group = GroupItem(filter)
// val subItems = filter.state.mapNotNull {
// when (it) {
// is Filter.CheckBox -> FilterWrapper("CheckBox",filter)
// is Filter.TriState -> FilterWrapper("TriState",filter)
// is Filter.Text -> FilterWrapper("Text",filter)
// is Filter.Select<*> -> FilterWrapper("Select",filter)
// else -> null
// } as? ISectionable<*, *>
// }
// subItems.forEach { it.header = group }
// group.subItems = subItems
// group
// }
// is Filter.Sort -> {
// val group = SortGroup(filter)
// val subItems = filter.values.map {
// SortItem(it, group)
// }
// group.subItems = subItems
// group
// }
// }
// }
//}
View File
+2 -2
View File
@@ -4,11 +4,11 @@ plugins {
node {
workDir = file("${project.projectDir}/react/")
nodeModulesDir = file("${project.projectDir}/react/node_modules")
nodeModulesDir = file("${project.projectDir}/react/")
}
tasks.named("yarn_build") {
dependsOn("yarn_install")
dependsOn("yarn") // install node_moduels
}
tasks.register<Copy>("copyBuild") {
+1 -1
View File
@@ -47,4 +47,4 @@
"eslint-plugin-react-hooks": "^4.2.0",
"typescript": "^4.1.0"
}
}
}
+4
View File
@@ -9,6 +9,7 @@ import Extensions from './screens/Extensions';
import MangaList from './screens/MangaList';
import Manga from './screens/Manga';
import Reader from './screens/Reader';
import Search from './screens/Search';
export default function App() {
return (
@@ -16,6 +17,9 @@ export default function App() {
<NavBar />
<Switch>
<Route path="/search">
<Search />
</Route>
<Route path="/extensions">
<Extensions />
</Route>
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import MangaCard from './MangaCard';
interface IProps{
mangas: IManga[]
message?: string
}
export default function MangaGrid(props: IProps) {
const { mangas, message } = props;
let mapped;
if (mangas.length === 0) {
mapped = <h3>{message !== undefined ? message : 'loading...'}</h3>;
} else {
mapped = (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, auto)', gridGap: '1em' }}>
{mangas.map((it) => (
<MangaCard manga={it} />
))}
</div>
);
}
return mapped;
}
@@ -48,6 +48,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
<ListItemText primary="Sources" />
</ListItem>
</Link>
<Link to="/search" style={{ color: 'inherit', textDecoration: 'none' }}>
<ListItem button key="Search">
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Global Search" />
</ListItem>
</Link>
</List>
</div>
);
+2 -15
View File
@@ -1,10 +1,9 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import MangaCard from '../components/MangaCard';
import MangaGrid from '../components/MangaGrid';
export default function MangaList(props: { popular: boolean }) {
const { sourceId } = useParams<{sourceId: string}>();
let mapped;
const [mangas, setMangas] = useState<IManga[]>([]);
const [lastPageNum] = useState<number>(1);
@@ -17,17 +16,5 @@ export default function MangaList(props: { popular: boolean }) {
));
}, []);
if (mangas.length === 0) {
mapped = <h3>wait</h3>;
} else {
mapped = (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, auto)', gridGap: '1em' }}>
{mangas.map((it) => (
<MangaCard manga={it} />
))}
</div>
);
}
return mapped;
return <MangaGrid mangas={mangas} />;
}
+48
View File
@@ -0,0 +1,48 @@
import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import MangaGrid from '../components/MangaGrid';
const useStyles = makeStyles((theme) => ({
root: {
TextField: {
margin: theme.spacing(1),
width: '25ch',
},
},
}));
export default function Search() {
const classes = useStyles();
const [error, setError] = useState<boolean>(false);
const [mangas, setMangas] = useState<IManga[]>([]);
const [message, setMessage] = useState<string>('');
const textInput = React.createRef<HTMLInputElement>();
function doSearch() {
if (textInput.current) {
const { value } = textInput.current;
if (value === '') { setError(true); } else {
setError(false);
setMangas([]);
setMessage('button pressed');
}
}
}
const mangaGrid = <MangaGrid mangas={mangas} message={message} />;
return (
<>
<form className={classes.root} noValidate autoComplete="off">
<TextField inputRef={textInput} error={error} id="standard-basic" label="Search text.." />
<Button variant="contained" color="primary" onClick={() => doSearch()}>
Primary
</Button>
</form>
{mangaGrid}
</>
);
}