diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt index 81599c9..004d437 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Category.kt @@ -35,16 +35,16 @@ object Category { } } - fun updateCategory(categoryId: Int, name: String?, isLanding: Boolean?) { + fun updateCategory(categoryId: Int, name: String?, isDefault: Boolean?) { transaction { CategoryTable.update({ CategoryTable.id eq categoryId }) { if (name != null) it[CategoryTable.name] = name - if (isLanding != null) it[CategoryTable.isLanding] = isLanding + if (isDefault != null) it[CategoryTable.isDefault] = isDefault } } } -/** + /** * Move the category from position `from` to `to` */ fun reorderCategory(categoryId: Int, from: Int, to: Int) { diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt index 6bffb82..c08d39e 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Library.kt @@ -9,25 +9,37 @@ package ir.armor.tachidesk.impl import ir.armor.tachidesk.impl.Manga.getManga import ir.armor.tachidesk.model.database.table.CategoryMangaTable +import ir.armor.tachidesk.model.database.table.CategoryTable import ir.armor.tachidesk.model.database.table.MangaTable import ir.armor.tachidesk.model.database.table.toDataClass import ir.armor.tachidesk.model.dataclass.MangaDataClass import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update object Library { -// TODO: `Category.isLanding` is to handle the default categories a new library manga gets, + // TODO: `Category.isLanding` is to handle the default categories a new library manga gets, // ..implement that shit at some time... // ..also Consider to rename it to `isDefault` suspend fun addMangaToLibrary(mangaId: Int) { val manga = getManga(mangaId) if (!manga.inLibrary) { transaction { + val defaultCategories = CategoryTable.select { CategoryTable.isDefault eq true }.toList() + MangaTable.update({ MangaTable.id eq manga.id }) { - it[inLibrary] = true + it[MangaTable.inLibrary] = true + it[MangaTable.defaultCategory] = defaultCategories.isEmpty() + } + + defaultCategories.forEach { category -> + CategoryMangaTable.insert { + it[CategoryMangaTable.category] = category[CategoryTable.id].value + it[CategoryMangaTable.manga] = mangaId + } } } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt index 4b185b0..eebc9ce 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Search.kt @@ -13,7 +13,7 @@ import ir.armor.tachidesk.impl.util.lang.awaitSingle import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass object Search { -// TODO + // TODO fun sourceFilters(sourceId: Long) { val source = getHttpSource(sourceId) // source.getFilterList().toItems() @@ -34,7 +34,7 @@ object Search { val filter: Any ) -/** + /** * Note: Exhentai had a filter serializer (now in SY) that we might be able to steal */ // private fun FilterList.toFilterWrapper(): List { diff --git a/server/src/main/kotlin/ir/armor/tachidesk/model/database/migration/M0003_DefaultCategory.kt b/server/src/main/kotlin/ir/armor/tachidesk/model/database/migration/M0003_DefaultCategory.kt new file mode 100644 index 0000000..959d959 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/model/database/migration/M0003_DefaultCategory.kt @@ -0,0 +1,24 @@ +package ir.armor.tachidesk.model.database.migration + +import ir.armor.tachidesk.model.database.migration.lib.Migration +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.jetbrains.exposed.sql.vendors.currentDialect + +/* + * 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/. */ + +@Suppress("ClassName", "unused") +class M0003_DefaultCategory : Migration() { + /** this migration renamed CategoryTable.IS_LANDING to ChapterTable.IS_DEFAULT */ + override fun run() { + with(TransactionManager.current()) { + exec("ALTER TABLE CATEGORY ALTER COLUMN IS_LANDING RENAME TO IS_DEFAULT") + commit() + currentDialect.resetCaches() + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/model/database/table/CategoryTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/model/database/table/CategoryTable.kt index 1ddee64..c08177f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/model/database/table/CategoryTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/model/database/table/CategoryTable.kt @@ -13,13 +13,13 @@ import org.jetbrains.exposed.sql.ResultRow object CategoryTable : IntIdTable() { val name = varchar("name", 64) - val isLanding = bool("is_landing").default(false) val order = integer("order").default(0) + val isDefault = bool("is_default").default(false) } fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass( categoryEntry[this.id].value, categoryEntry[this.order], categoryEntry[this.name], - categoryEntry[this.isLanding], + categoryEntry[this.isDefault], ) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/CategoryDataClass.kt b/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/CategoryDataClass.kt index c18faff..06c6285 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/CategoryDataClass.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/CategoryDataClass.kt @@ -11,5 +11,5 @@ data class CategoryDataClass( val id: Int, val order: Int, val name: String, - val isLanding: Boolean + val default: Boolean ) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt index 2ed62ad..a532e01 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt @@ -13,9 +13,8 @@ import ir.armor.tachidesk.impl.CategoryManga.removeMangaFromCategory import ir.armor.tachidesk.impl.Chapter.getChapter import ir.armor.tachidesk.impl.Chapter.getChapterList import ir.armor.tachidesk.impl.Chapter.modifyChapter -import ir.armor.tachidesk.impl.Library.addMangaToLibrary +import ir.armor.tachidesk.impl.Library import ir.armor.tachidesk.impl.Library.getLibraryMangas -import ir.armor.tachidesk.impl.Library.removeMangaFromLibrary import ir.armor.tachidesk.impl.Manga.getManga import ir.armor.tachidesk.impl.Manga.getMangaThumbnail import ir.armor.tachidesk.impl.MangaList.getMangaList @@ -216,24 +215,6 @@ object JavalinSetup { ) } - // adds the manga to library - app.get("api/v1/manga/:mangaId/library") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - - ctx.result( - future { addMangaToLibrary(mangaId) } - ) - } - - // removes the manga from the library - app.delete("api/v1/manga/:mangaId/library") { ctx -> - val mangaId = ctx.pathParam("mangaId").toInt() - - ctx.result( - future { removeMangaFromLibrary(mangaId) } - ) - } - // list manga's categories app.get("api/v1/manga/:mangaId/category/") { ctx -> val mangaId = ctx.pathParam("mangaId").toInt() @@ -332,6 +313,24 @@ object JavalinSetup { ctx.json(sourceFilters(sourceId)) } + // adds the manga to library + app.get("api/v1/manga/:mangaId/library") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + + ctx.result( + future { Library.addMangaToLibrary(mangaId) } + ) + } + + // removes the manga from the library + app.delete("api/v1/manga/:mangaId/library") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + + ctx.result( + future { Library.removeMangaFromLibrary(mangaId) } + ) + } + // lists mangas that have no category assigned app.get("/api/v1/library/") { ctx -> ctx.json(getLibraryMangas()) @@ -358,8 +357,8 @@ object JavalinSetup { app.patch("/api/v1/category/:categoryId") { ctx -> val categoryId = ctx.pathParam("categoryId").toInt() val name = ctx.formParam("name") - val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null - updateCategory(categoryId, name, isLanding) + val isDefault = ctx.formParam("default")?.toBoolean() + updateCategory(categoryId, name, isDefault) ctx.status(200) } diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/Library.tsx index c55c305..9f4320c 100644 --- a/webUI/react/src/screens/Library.tsx +++ b/webUI/react/src/screens/Library.tsx @@ -72,7 +72,7 @@ export default function Library() { const defaultCategoryTab = { category: { name: 'Default', - isLanding: true, + default: true, order: 0, id: -1, }, diff --git a/webUI/react/src/screens/Settings.tsx b/webUI/react/src/screens/Settings.tsx index 0d02bd6..a9bf237 100644 --- a/webUI/react/src/screens/Settings.tsx +++ b/webUI/react/src/screens/Settings.tsx @@ -7,7 +7,8 @@ import React, { useContext, useEffect, useState } from 'react'; import List from '@material-ui/core/List'; -import InboxIcon from '@material-ui/icons/Inbox'; +import ListAltIcon from '@material-ui/icons/ListAlt'; +import BackupIcon from '@material-ui/icons/Backup'; import Brightness6Icon from '@material-ui/icons/Brightness6'; import DnsIcon from '@material-ui/icons/Dns'; import EditIcon from '@material-ui/icons/Edit'; @@ -50,13 +51,13 @@ export default function Settings() { - + - + diff --git a/webUI/react/src/screens/settings/Categories.jsx b/webUI/react/src/screens/settings/Categories.jsx index 1e0d2bd..0d61986 100644 --- a/webUI/react/src/screens/settings/Categories.jsx +++ b/webUI/react/src/screens/settings/Categories.jsx @@ -28,8 +28,9 @@ import TextField from '@material-ui/core/TextField'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; +import Checkbox from '@material-ui/core/Checkbox'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; import NavbarContext from '../../context/NavbarContext'; import client from '../../util/client'; @@ -49,7 +50,8 @@ export default function Categories() { const [categories, setCategories] = useState([]); const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category const [dialogOpen, setDialogOpen] = useState(false); - const [dialogValue, setDialogValue] = useState(''); + const [dialogName, setDialogName] = useState(''); + const [dialogDefault, setDialogDefault] = useState(false); const theme = useTheme(); const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack @@ -93,7 +95,8 @@ export default function Categories() { }; const resetDialog = () => { - setDialogValue(''); + setDialogName(''); + setDialogDefault(false); setCategoryToEdit(-1); }; @@ -102,6 +105,13 @@ export default function Categories() { setDialogOpen(true); }; + const handleEditDialogOpen = (index) => { + setDialogName(categories[index].name); + setDialogDefault(categories[index].default); + setCategoryToEdit(index); + setDialogOpen(true); + }; + const handleDialogCancel = () => { setDialogOpen(false); }; @@ -110,7 +120,8 @@ export default function Categories() { setDialogOpen(false); const formData = new FormData(); - formData.append('name', dialogValue); + formData.append('name', dialogName); + formData.append('default', dialogDefault); if (categoryToEdit === -1) { client.post('/api/v1/category/', formData) @@ -161,8 +172,7 @@ export default function Categories() { /> { - handleDialogOpen(); - setCategoryToEdit(index); + handleEditDialogOpen(index); }} > @@ -197,12 +207,9 @@ export default function Categories() { - {categoryToEdit === -1 ? 'New Catalog' : `Rename: ${categories[categoryToEdit].name}`} + {categoryToEdit === -1 ? 'New Catalog' : 'Edit Catalog'} - - Enter new category name. - setDialogValue(e.target.value)} + value={dialogName} + onChange={(e) => setDialogName(e.target.value)} + /> + setDialogDefault(e.target.checked)} + color="default" + /> + )} + label="Default category when adding new manga to library" /> diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index 58ebf12..f318138 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -80,7 +80,7 @@ interface ICategory { id: number order: number name: String - isLanding: boolean + default: boolean } interface INavbarOverride {