mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 19:04:39 -05:00
set default category when adding new manga
This commit is contained in:
@@ -35,16 +35,16 @@ object Category {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCategory(categoryId: Int, name: String?, isLanding: Boolean?) {
|
fun updateCategory(categoryId: Int, name: String?, isDefault: Boolean?) {
|
||||||
transaction {
|
transaction {
|
||||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||||
if (name != null) it[CategoryTable.name] = name
|
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`
|
* Move the category from position `from` to `to`
|
||||||
*/
|
*/
|
||||||
fun reorderCategory(categoryId: Int, from: Int, to: Int) {
|
fun reorderCategory(categoryId: Int, from: Int, to: Int) {
|
||||||
|
|||||||
@@ -9,25 +9,37 @@ package ir.armor.tachidesk.impl
|
|||||||
|
|
||||||
import ir.armor.tachidesk.impl.Manga.getManga
|
import ir.armor.tachidesk.impl.Manga.getManga
|
||||||
import ir.armor.tachidesk.model.database.table.CategoryMangaTable
|
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.MangaTable
|
||||||
import ir.armor.tachidesk.model.database.table.toDataClass
|
import ir.armor.tachidesk.model.database.table.toDataClass
|
||||||
import ir.armor.tachidesk.model.dataclass.MangaDataClass
|
import ir.armor.tachidesk.model.dataclass.MangaDataClass
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
|
|
||||||
object Library {
|
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...
|
// ..implement that shit at some time...
|
||||||
// ..also Consider to rename it to `isDefault`
|
// ..also Consider to rename it to `isDefault`
|
||||||
suspend fun addMangaToLibrary(mangaId: Int) {
|
suspend fun addMangaToLibrary(mangaId: Int) {
|
||||||
val manga = getManga(mangaId)
|
val manga = getManga(mangaId)
|
||||||
if (!manga.inLibrary) {
|
if (!manga.inLibrary) {
|
||||||
transaction {
|
transaction {
|
||||||
|
val defaultCategories = CategoryTable.select { CategoryTable.isDefault eq true }.toList()
|
||||||
|
|
||||||
MangaTable.update({ MangaTable.id eq manga.id }) {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import ir.armor.tachidesk.impl.util.lang.awaitSingle
|
|||||||
import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass
|
import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass
|
||||||
|
|
||||||
object Search {
|
object Search {
|
||||||
// TODO
|
// TODO
|
||||||
fun sourceFilters(sourceId: Long) {
|
fun sourceFilters(sourceId: Long) {
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
// source.getFilterList().toItems()
|
// source.getFilterList().toItems()
|
||||||
@@ -34,7 +34,7 @@ object Search {
|
|||||||
val filter: Any
|
val filter: Any
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
|
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
|
||||||
*/
|
*/
|
||||||
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
|
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,13 @@ import org.jetbrains.exposed.sql.ResultRow
|
|||||||
|
|
||||||
object CategoryTable : IntIdTable() {
|
object CategoryTable : IntIdTable() {
|
||||||
val name = varchar("name", 64)
|
val name = varchar("name", 64)
|
||||||
val isLanding = bool("is_landing").default(false)
|
|
||||||
val order = integer("order").default(0)
|
val order = integer("order").default(0)
|
||||||
|
val isDefault = bool("is_default").default(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
|
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
|
||||||
categoryEntry[this.id].value,
|
categoryEntry[this.id].value,
|
||||||
categoryEntry[this.order],
|
categoryEntry[this.order],
|
||||||
categoryEntry[this.name],
|
categoryEntry[this.name],
|
||||||
categoryEntry[this.isLanding],
|
categoryEntry[this.isDefault],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ data class CategoryDataClass(
|
|||||||
val id: Int,
|
val id: Int,
|
||||||
val order: Int,
|
val order: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
val isLanding: Boolean
|
val default: Boolean
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import ir.armor.tachidesk.impl.CategoryManga.removeMangaFromCategory
|
|||||||
import ir.armor.tachidesk.impl.Chapter.getChapter
|
import ir.armor.tachidesk.impl.Chapter.getChapter
|
||||||
import ir.armor.tachidesk.impl.Chapter.getChapterList
|
import ir.armor.tachidesk.impl.Chapter.getChapterList
|
||||||
import ir.armor.tachidesk.impl.Chapter.modifyChapter
|
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.getLibraryMangas
|
||||||
import ir.armor.tachidesk.impl.Library.removeMangaFromLibrary
|
|
||||||
import ir.armor.tachidesk.impl.Manga.getManga
|
import ir.armor.tachidesk.impl.Manga.getManga
|
||||||
import ir.armor.tachidesk.impl.Manga.getMangaThumbnail
|
import ir.armor.tachidesk.impl.Manga.getMangaThumbnail
|
||||||
import ir.armor.tachidesk.impl.MangaList.getMangaList
|
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
|
// list manga's categories
|
||||||
app.get("api/v1/manga/:mangaId/category/") { ctx ->
|
app.get("api/v1/manga/:mangaId/category/") { ctx ->
|
||||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
@@ -332,6 +313,24 @@ object JavalinSetup {
|
|||||||
ctx.json(sourceFilters(sourceId))
|
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
|
// lists mangas that have no category assigned
|
||||||
app.get("/api/v1/library/") { ctx ->
|
app.get("/api/v1/library/") { ctx ->
|
||||||
ctx.json(getLibraryMangas())
|
ctx.json(getLibraryMangas())
|
||||||
@@ -358,8 +357,8 @@ object JavalinSetup {
|
|||||||
app.patch("/api/v1/category/:categoryId") { ctx ->
|
app.patch("/api/v1/category/:categoryId") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
val name = ctx.formParam("name")
|
val name = ctx.formParam("name")
|
||||||
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
|
val isDefault = ctx.formParam("default")?.toBoolean()
|
||||||
updateCategory(categoryId, name, isLanding)
|
updateCategory(categoryId, name, isDefault)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function Library() {
|
|||||||
const defaultCategoryTab = {
|
const defaultCategoryTab = {
|
||||||
category: {
|
category: {
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
isLanding: true,
|
default: true,
|
||||||
order: 0,
|
order: 0,
|
||||||
id: -1,
|
id: -1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import List from '@material-ui/core/List';
|
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 Brightness6Icon from '@material-ui/icons/Brightness6';
|
||||||
import DnsIcon from '@material-ui/icons/Dns';
|
import DnsIcon from '@material-ui/icons/Dns';
|
||||||
import EditIcon from '@material-ui/icons/Edit';
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
@@ -50,13 +51,13 @@ export default function Settings() {
|
|||||||
<List style={{ padding: 0 }}>
|
<List style={{ padding: 0 }}>
|
||||||
<ListItemLink href="/settings/categories">
|
<ListItemLink href="/settings/categories">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<InboxIcon />
|
<ListAltIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Categories" />
|
<ListItemText primary="Categories" />
|
||||||
</ListItemLink>
|
</ListItemLink>
|
||||||
<ListItemLink href="/settings/backup">
|
<ListItemLink href="/settings/backup">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<InboxIcon />
|
<BackupIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Backup" />
|
<ListItemText primary="Backup" />
|
||||||
</ListItemLink>
|
</ListItemLink>
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ import TextField from '@material-ui/core/TextField';
|
|||||||
import Dialog from '@material-ui/core/Dialog';
|
import Dialog from '@material-ui/core/Dialog';
|
||||||
import DialogActions from '@material-ui/core/DialogActions';
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
import DialogContent from '@material-ui/core/DialogContent';
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
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 NavbarContext from '../../context/NavbarContext';
|
||||||
import client from '../../util/client';
|
import client from '../../util/client';
|
||||||
|
|
||||||
@@ -49,7 +50,8 @@ export default function Categories() {
|
|||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category
|
const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [dialogValue, setDialogValue] = useState('');
|
const [dialogName, setDialogName] = useState('');
|
||||||
|
const [dialogDefault, setDialogDefault] = useState(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack
|
const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack
|
||||||
@@ -93,7 +95,8 @@ export default function Categories() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetDialog = () => {
|
const resetDialog = () => {
|
||||||
setDialogValue('');
|
setDialogName('');
|
||||||
|
setDialogDefault(false);
|
||||||
setCategoryToEdit(-1);
|
setCategoryToEdit(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,6 +105,13 @@ export default function Categories() {
|
|||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditDialogOpen = (index) => {
|
||||||
|
setDialogName(categories[index].name);
|
||||||
|
setDialogDefault(categories[index].default);
|
||||||
|
setCategoryToEdit(index);
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDialogCancel = () => {
|
const handleDialogCancel = () => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
};
|
};
|
||||||
@@ -110,7 +120,8 @@ export default function Categories() {
|
|||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('name', dialogValue);
|
formData.append('name', dialogName);
|
||||||
|
formData.append('default', dialogDefault);
|
||||||
|
|
||||||
if (categoryToEdit === -1) {
|
if (categoryToEdit === -1) {
|
||||||
client.post('/api/v1/category/', formData)
|
client.post('/api/v1/category/', formData)
|
||||||
@@ -161,8 +172,7 @@ export default function Categories() {
|
|||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDialogOpen();
|
handleEditDialogOpen(index);
|
||||||
setCategoryToEdit(index);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
@@ -197,12 +207,9 @@ export default function Categories() {
|
|||||||
</Fab>
|
</Fab>
|
||||||
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
|
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
|
||||||
<DialogTitle id="form-dialog-title">
|
<DialogTitle id="form-dialog-title">
|
||||||
{categoryToEdit === -1 ? 'New Catalog' : `Rename: ${categories[categoryToEdit].name}`}
|
{categoryToEdit === -1 ? 'New Catalog' : 'Edit Catalog'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
|
||||||
Enter new category name.
|
|
||||||
</DialogContentText>
|
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
margin="dense"
|
margin="dense"
|
||||||
@@ -210,8 +217,18 @@ export default function Categories() {
|
|||||||
label="Category Name"
|
label="Category Name"
|
||||||
type="text"
|
type="text"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={dialogValue}
|
value={dialogName}
|
||||||
onChange={(e) => setDialogValue(e.target.value)}
|
onChange={(e) => setDialogName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={(
|
||||||
|
<Checkbox
|
||||||
|
checked={dialogDefault}
|
||||||
|
onChange={(e) => setDialogDefault(e.target.checked)}
|
||||||
|
color="default"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
label="Default category when adding new manga to library"
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|||||||
2
webUI/react/src/typings.d.ts
vendored
2
webUI/react/src/typings.d.ts
vendored
@@ -80,7 +80,7 @@ interface ICategory {
|
|||||||
id: number
|
id: number
|
||||||
order: number
|
order: number
|
||||||
name: String
|
name: String
|
||||||
isLanding: boolean
|
default: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface INavbarOverride {
|
interface INavbarOverride {
|
||||||
|
|||||||
Reference in New Issue
Block a user