mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-11-01 15:05:06 +01:00
finished the category screen
This commit is contained in:
parent
5a9d216fb7
commit
f1cc37d0db
@ -29,6 +29,7 @@ import ir.armor.tachidesk.util.removeCategory
|
|||||||
import ir.armor.tachidesk.util.removeExtension
|
import ir.armor.tachidesk.util.removeExtension
|
||||||
import ir.armor.tachidesk.util.removeMangaFromCategory
|
import ir.armor.tachidesk.util.removeMangaFromCategory
|
||||||
import ir.armor.tachidesk.util.removeMangaFromLibrary
|
import ir.armor.tachidesk.util.removeMangaFromLibrary
|
||||||
|
import ir.armor.tachidesk.util.reorderCategory
|
||||||
import ir.armor.tachidesk.util.sourceFilters
|
import ir.armor.tachidesk.util.sourceFilters
|
||||||
import ir.armor.tachidesk.util.sourceGlobalSearch
|
import ir.armor.tachidesk.util.sourceGlobalSearch
|
||||||
import ir.armor.tachidesk.util.sourceSearch
|
import ir.armor.tachidesk.util.sourceSearch
|
||||||
@ -60,7 +61,7 @@ class Main {
|
|||||||
|
|
||||||
// make sure everything we need exists
|
// make sure everything we need exists
|
||||||
applicationSetup()
|
applicationSetup()
|
||||||
val tray = systemTray()
|
val tray = systemTray() // assign it to a variable so it's kept in the memory and not garbage collected
|
||||||
|
|
||||||
registerConfigModules()
|
registerConfigModules()
|
||||||
|
|
||||||
@ -227,7 +228,7 @@ class Main {
|
|||||||
ctx.json(sourceFilters(sourceId))
|
ctx.json(sourceFilters(sourceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// lists all manga in the library, suitable if no categories are defined
|
// 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())
|
||||||
}
|
}
|
||||||
@ -240,20 +241,28 @@ class Main {
|
|||||||
// category create
|
// category create
|
||||||
app.post("/api/v1/category/") { ctx ->
|
app.post("/api/v1/category/") { ctx ->
|
||||||
val name = ctx.formParam("name")!!
|
val name = ctx.formParam("name")!!
|
||||||
val isLanding = ctx.formParam("isLanding", "false").toBoolean()
|
createCategory(name)
|
||||||
createCategory(name, isLanding)
|
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// category modification
|
// category modification
|
||||||
app.put("/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 = ctx.formParam("isLanding").toBoolean()
|
val isLanding = if (ctx.formParam("isLanding") != null) ctx.formParam("isLanding")?.toBoolean() else null
|
||||||
updateCategory(categoryId, name, isLanding)
|
updateCategory(categoryId, name, isLanding)
|
||||||
ctx.status(200)
|
ctx.status(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// category re-ordering
|
||||||
|
app.patch("/api/v1/category/:categoryId/reorder") { ctx ->
|
||||||
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
val from = ctx.formParam("from")!!.toInt()
|
||||||
|
val to = ctx.formParam("to")!!.toInt()
|
||||||
|
reorderCategory(categoryId, from, to)
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
// category delete
|
// category delete
|
||||||
app.delete("/api/v1/category/:categoryId") { ctx ->
|
app.delete("/api/v1/category/:categoryId") { ctx ->
|
||||||
val categoryId = ctx.pathParam("categoryId").toInt()
|
val categoryId = ctx.pathParam("categoryId").toInt()
|
||||||
|
@ -29,12 +29,14 @@ fun makeDataBaseTables() {
|
|||||||
// db.useNestedTransactions = true
|
// db.useNestedTransactions = true
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
SchemaUtils.create(ExtensionTable)
|
SchemaUtils.createMissingTablesAndColumns(
|
||||||
SchemaUtils.create(SourceTable)
|
ExtensionTable,
|
||||||
SchemaUtils.create(MangaTable)
|
SourceTable,
|
||||||
SchemaUtils.create(ChapterTable)
|
MangaTable,
|
||||||
SchemaUtils.create(PageTable)
|
ChapterTable,
|
||||||
SchemaUtils.create(CategoryTable)
|
PageTable,
|
||||||
SchemaUtils.create(CategoryMangaTable)
|
CategoryTable,
|
||||||
|
CategoryMangaTable,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ package ir.armor.tachidesk.database.dataclass
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
data class CategoryDataClass(
|
data class CategoryDataClass(
|
||||||
|
val id: Int,
|
||||||
|
val order: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
val isLanding: Boolean
|
val isLanding: Boolean
|
||||||
)
|
)
|
||||||
|
@ -11,9 +11,12 @@ 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 isLanding = bool("is_landing").default(false)
|
||||||
|
val order = integer("order").default(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
|
fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass(
|
||||||
|
categoryEntry[CategoryTable.id].value,
|
||||||
|
categoryEntry[CategoryTable.order],
|
||||||
categoryEntry[CategoryTable.name],
|
categoryEntry[CategoryTable.name],
|
||||||
categoryEntry[CategoryTable.isLanding],
|
categoryEntry[CategoryTable.isLanding],
|
||||||
)
|
)
|
||||||
|
@ -25,6 +25,7 @@ object MangaTable : IntIdTable() {
|
|||||||
val thumbnail_url = varchar("thumbnail_url", 2048).nullable()
|
val thumbnail_url = varchar("thumbnail_url", 2048).nullable()
|
||||||
|
|
||||||
val inLibrary = bool("in_library").default(false)
|
val inLibrary = bool("in_library").default(false)
|
||||||
|
val defaultCategory = bool("default_category").default(true)
|
||||||
|
|
||||||
// source is used by some ancestor of IntIdTable
|
// source is used by some ancestor of IntIdTable
|
||||||
val sourceReference = reference("source", SourceTable)
|
val sourceReference = reference("source", SourceTable)
|
||||||
|
@ -3,6 +3,7 @@ package ir.armor.tachidesk.util
|
|||||||
import ir.armor.tachidesk.database.dataclass.CategoryDataClass
|
import ir.armor.tachidesk.database.dataclass.CategoryDataClass
|
||||||
import ir.armor.tachidesk.database.table.CategoryTable
|
import ir.armor.tachidesk.database.table.CategoryTable
|
||||||
import ir.armor.tachidesk.database.table.toDataClass
|
import ir.armor.tachidesk.database.table.toDataClass
|
||||||
|
import org.jetbrains.exposed.sql.SortOrder
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
@ -14,21 +15,34 @@ import org.jetbrains.exposed.sql.update
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
fun createCategory(name: String, isLanding: Boolean) {
|
fun createCategory(name: String) {
|
||||||
transaction {
|
transaction {
|
||||||
|
val count = CategoryTable.selectAll().count()
|
||||||
if (CategoryTable.select { CategoryTable.name eq name }.firstOrNull() == null)
|
if (CategoryTable.select { CategoryTable.name eq name }.firstOrNull() == null)
|
||||||
CategoryTable.insert {
|
CategoryTable.insert {
|
||||||
it[CategoryTable.name] = name
|
it[CategoryTable.name] = name
|
||||||
it[CategoryTable.isLanding] = isLanding
|
it[CategoryTable.order] = count.toInt() + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCategory(categoryId: Int, name: String, isLanding: Boolean) {
|
fun updateCategory(categoryId: Int, name: String?, isLanding: Boolean?) {
|
||||||
transaction {
|
transaction {
|
||||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||||
it[CategoryTable.name] = name
|
if (name != null) it[CategoryTable.name] = name
|
||||||
it[CategoryTable.isLanding] = isLanding
|
if (isLanding != null) it[CategoryTable.isLanding] = isLanding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reorderCategory(categoryId: Int, from: Int, to: Int) {
|
||||||
|
transaction {
|
||||||
|
val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList()
|
||||||
|
categories.add(to - 1, categories.removeAt(from - 1))
|
||||||
|
categories.forEachIndexed { index, cat ->
|
||||||
|
CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) {
|
||||||
|
it[CategoryTable.order] = index + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +55,7 @@ fun removeCategory(categoryId: Int) {
|
|||||||
|
|
||||||
fun getCategoryList(): List<CategoryDataClass> {
|
fun getCategoryList(): List<CategoryDataClass> {
|
||||||
return transaction {
|
return transaction {
|
||||||
CategoryTable.selectAll().map {
|
CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).map {
|
||||||
CategoryTable.toDataClass(it)
|
CategoryTable.toDataClass(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import org.jetbrains.exposed.sql.deleteWhere
|
|||||||
import org.jetbrains.exposed.sql.insert
|
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
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@ -21,6 +22,10 @@ fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
|||||||
it[CategoryMangaTable.category] = categoryId
|
it[CategoryMangaTable.category] = categoryId
|
||||||
it[CategoryMangaTable.manga] = mangaId
|
it[CategoryMangaTable.manga] = mangaId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
|
it[MangaTable.defaultCategory] = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,6 +33,11 @@ fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
|||||||
fun removeMangaFromCategory(mangaId: Int, categoryId: Int) {
|
fun removeMangaFromCategory(mangaId: Int, categoryId: Int) {
|
||||||
transaction {
|
transaction {
|
||||||
CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }
|
CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }
|
||||||
|
if (CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.count() == 0L) {
|
||||||
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
|
it[MangaTable.defaultCategory] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package ir.armor.tachidesk.util
|
|||||||
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
import ir.armor.tachidesk.database.dataclass.MangaDataClass
|
||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.database.table.toDataClass
|
import ir.armor.tachidesk.database.table.toDataClass
|
||||||
|
import org.jetbrains.exposed.sql.and
|
||||||
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
|
||||||
@ -35,7 +36,7 @@ fun removeMangaFromLibrary(mangaId: Int) {
|
|||||||
|
|
||||||
fun getLibraryMangas(): List<MangaDataClass> {
|
fun getLibraryMangas(): List<MangaDataClass> {
|
||||||
return transaction {
|
return transaction {
|
||||||
MangaTable.select { MangaTable.inLibrary eq true }.map {
|
MangaTable.select { (MangaTable.inLibrary eq true) and (MangaTable.defaultCategory eq true) }.map {
|
||||||
MangaTable.toDataClass(it)
|
MangaTable.toDataClass(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"fontsource-roboto": "^4.0.0",
|
"fontsource-roboto": "^4.0.0",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
|
"react-beautiful-dnd": "^13.0.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.1",
|
"react-scripts": "4.0.1",
|
||||||
|
@ -14,13 +14,15 @@ import NavBar from './components/NavBar';
|
|||||||
import Home from './screens/Home';
|
import Home from './screens/Home';
|
||||||
import Sources from './screens/Sources';
|
import Sources from './screens/Sources';
|
||||||
import Extensions from './screens/Extensions';
|
import Extensions from './screens/Extensions';
|
||||||
import MangaList from './screens/MangaList';
|
import SourceMangas from './screens/SourceMangas';
|
||||||
import Manga from './screens/Manga';
|
import Manga from './screens/Manga';
|
||||||
import Reader from './screens/Reader';
|
import Reader from './screens/Reader';
|
||||||
import Search from './screens/SearchSingle';
|
import Search from './screens/SearchSingle';
|
||||||
import NavBarTitle from './context/NavbarTitle';
|
import NavBarTitle from './context/NavbarTitle';
|
||||||
import DarkTheme from './context/DarkTheme';
|
import DarkTheme from './context/DarkTheme';
|
||||||
import Library from './screens/Library';
|
import Library from './screens/Library';
|
||||||
|
import Settings from './screens/Settings';
|
||||||
|
import Categories from './screens/settings/Categories';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [title, setTitle] = useState<string>('Tachidesk');
|
const [title, setTitle] = useState<string>('Tachidesk');
|
||||||
@ -69,10 +71,10 @@ export default function App() {
|
|||||||
<Extensions />
|
<Extensions />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/sources/:sourceId/popular/">
|
<Route path="/sources/:sourceId/popular/">
|
||||||
<MangaList popular />
|
<SourceMangas popular />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/sources/:sourceId/latest/">
|
<Route path="/sources/:sourceId/latest/">
|
||||||
<MangaList popular={false} />
|
<SourceMangas popular={false} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/sources">
|
<Route path="/sources">
|
||||||
<Sources />
|
<Sources />
|
||||||
@ -86,6 +88,12 @@ export default function App() {
|
|||||||
<Route path="/library">
|
<Route path="/library">
|
||||||
<Library />
|
<Library />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/settings/categories">
|
||||||
|
<Categories />
|
||||||
|
</Route>
|
||||||
|
<Route path="/settings">
|
||||||
|
<Settings />
|
||||||
|
</Route>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Home />
|
<Home />
|
||||||
</Route>
|
</Route>
|
||||||
|
@ -60,6 +60,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
|
|||||||
<ListItemText primary="Sources" />
|
<ListItemText primary="Sources" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to="/settings" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
|
<ListItem button key="settings">
|
||||||
|
<ListItemIcon>
|
||||||
|
<InboxIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Settings" />
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
{/* <Link to="/search" style={{ color: 'inherit', textDecoration: 'none' }}>
|
{/* <Link to="/search" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||||
<ListItem button key="Search">
|
<ListItem button key="Search">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
|
@ -2,13 +2,45 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import { Tab, Tabs } from '@material-ui/core';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from '../components/MangaGrid';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
export default function MangaList() {
|
interface IMangaCategory {
|
||||||
|
category: ICategory
|
||||||
|
mangas: IManga[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanel(props: TabPanelProps) {
|
||||||
|
const {
|
||||||
|
children, value, index, ...other
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`simple-tabpanel-${index}`}
|
||||||
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Library() {
|
||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
const [mangas, setMangas] = useState<IManga[]>([]);
|
const [tabs, setTabs] = useState<IMangaCategory[]>([]);
|
||||||
|
const [tabNum, setTabNum] = useState<number>(0);
|
||||||
const [lastPageNum, setLastPageNum] = useState<number>(1);
|
const [lastPageNum, setLastPageNum] = useState<number>(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -19,11 +51,66 @@ export default function MangaList() {
|
|||||||
fetch('http://127.0.0.1:4567/api/v1/library')
|
fetch('http://127.0.0.1:4567/api/v1/library')
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data: IManga[]) => {
|
.then((data: IManga[]) => {
|
||||||
setMangas(data);
|
if (data.length > 0) {
|
||||||
|
setTabs([
|
||||||
|
...tabs,
|
||||||
|
{
|
||||||
|
category: {
|
||||||
|
name: 'Default', isLanding: true, order: 0, id: 0,
|
||||||
|
},
|
||||||
|
mangas: data,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [lastPageNum]);
|
}, []);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
|
fetch('http://127.0.0.1:4567/api/v1/category')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data: ICategory[]) => {
|
||||||
|
const mangaCategories = data.map((category) => ({
|
||||||
|
category,
|
||||||
|
mangas: [] as IManga[],
|
||||||
|
}));
|
||||||
|
setTabs([...tabs, ...mangaCategories]);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => setTabNum(newValue);
|
||||||
|
|
||||||
|
let toRender;
|
||||||
|
if (tabs.length > 1) {
|
||||||
|
const tabDefines = tabs.map((tab) => (<Tab label={tab.category.name} />));
|
||||||
|
|
||||||
|
const tabBodies = tabs.map((tab) => (
|
||||||
|
<TabPanel value={tabNum} index={0}>
|
||||||
|
<MangaGrid
|
||||||
|
mangas={tab.mangas}
|
||||||
|
hasNextPage={false}
|
||||||
|
lastPageNum={lastPageNum}
|
||||||
|
setLastPageNum={setLastPageNum}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
));
|
||||||
|
toRender = (
|
||||||
|
<>
|
||||||
|
<Tabs
|
||||||
|
value={tabNum}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
{tabDefines}
|
||||||
|
</Tabs>
|
||||||
|
{tabBodies}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const mangas = tabs.length === 1 ? tabs[0].mangas : [];
|
||||||
|
toRender = (
|
||||||
<MangaGrid
|
<MangaGrid
|
||||||
mangas={mangas}
|
mangas={mangas}
|
||||||
hasNextPage={false}
|
hasNextPage={false}
|
||||||
@ -32,3 +119,6 @@ export default function MangaList() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return toRender;
|
||||||
|
}
|
||||||
|
34
webUI/react/src/screens/Settings.tsx
Normal file
34
webUI/react/src/screens/Settings.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/* 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 } from 'react';
|
||||||
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
import InboxIcon from '@material-ui/icons/Inbox';
|
||||||
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
|
function ListItemLink(props: ListItemProps<'a', { button?: true }>) {
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return <ListItem button component="a" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Settings() {
|
||||||
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
setTitle('Settings');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<List component="nav" style={{ padding: 0 }}>
|
||||||
|
<ListItemLink href="/settings/categories">
|
||||||
|
<ListItemIcon>
|
||||||
|
<InboxIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Categories" />
|
||||||
|
</ListItemLink>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { useParams } from 'react-router-dom';
|
|||||||
import MangaGrid from '../components/MangaGrid';
|
import MangaGrid from '../components/MangaGrid';
|
||||||
import NavBarTitle from '../context/NavbarTitle';
|
import NavBarTitle from '../context/NavbarTitle';
|
||||||
|
|
||||||
export default function MangaList(props: { popular: boolean }) {
|
export default function SourceMangas(props: { popular: boolean }) {
|
||||||
const { sourceId } = useParams<{sourceId: string}>();
|
const { sourceId } = useParams<{sourceId: string}>();
|
||||||
const { setTitle } = useContext(NavBarTitle);
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
const [mangas, setMangas] = useState<IManga[]>([]);
|
const [mangas, setMangas] = useState<IManga[]>([]);
|
231
webUI/react/src/screens/settings/Categories.jsx
Normal file
231
webUI/react/src/screens/settings/Categories.jsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
|
/* eslint-disable react/destructuring-assignment */
|
||||||
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
ListItemIcon,
|
||||||
|
IconButton,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||||
|
import DragHandleIcon from '@material-ui/icons/DragHandle';
|
||||||
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
|
import { useTheme } from '@material-ui/core/styles';
|
||||||
|
import Fab from '@material-ui/core/Fab';
|
||||||
|
import AddIcon from '@material-ui/icons/Add';
|
||||||
|
import DeleteIcon from '@material-ui/icons/Delete';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
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 NavBarTitle from '../../context/NavbarTitle';
|
||||||
|
|
||||||
|
const getItemStyle = (isDragging, draggableStyle, palette) => ({
|
||||||
|
// styles we need to apply on draggables
|
||||||
|
...draggableStyle,
|
||||||
|
|
||||||
|
...(isDragging && {
|
||||||
|
background: palette.type === 'dark' ? '#424242' : 'rgb(235,235,235)',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Categories() {
|
||||||
|
const { setTitle } = useContext(NavBarTitle);
|
||||||
|
setTitle('Categories');
|
||||||
|
const [categories, setCategories] = useState([]);
|
||||||
|
const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category
|
||||||
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
||||||
|
const [dialogValue, setDialogValue] = useState('');
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack
|
||||||
|
const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dialogOpen) {
|
||||||
|
fetch('http://127.0.0.1:4567/api/v1/category/')
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => setCategories(data));
|
||||||
|
}
|
||||||
|
}, [updateTriggerHolder]);
|
||||||
|
|
||||||
|
const categoryReorder = (list, from, to) => {
|
||||||
|
const category = list[from];
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('from', from + 1);
|
||||||
|
formData.append('to', to + 1);
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/category/${category.id}/reorder`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: formData,
|
||||||
|
}).finally(() => triggerUpdate());
|
||||||
|
|
||||||
|
// also move it in local state to avoid jarring moving behviour...
|
||||||
|
const result = Array.from(list);
|
||||||
|
const [removed] = result.splice(from, 1);
|
||||||
|
result.splice(to, 0, removed);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd = (result) => {
|
||||||
|
// dropped outside the list?
|
||||||
|
if (!result.destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCategories(categoryReorder(
|
||||||
|
categories,
|
||||||
|
result.source.index,
|
||||||
|
result.destination.index,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogOpen = () => {
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetDialog = () => {
|
||||||
|
setDialogOpen(false);
|
||||||
|
setDialogValue('');
|
||||||
|
setCategoryToEdit(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogCancel = () => {
|
||||||
|
resetDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogSubmit = () => {
|
||||||
|
resetDialog();
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', dialogValue);
|
||||||
|
|
||||||
|
if (categoryToEdit === -1) {
|
||||||
|
fetch('http://127.0.0.1:4567/api/v1/category/', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
}).finally(() => triggerUpdate());
|
||||||
|
} else {
|
||||||
|
const category = categories[categoryToEdit];
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/category/${category.id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: formData,
|
||||||
|
}).finally(() => triggerUpdate());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCategory = (index) => {
|
||||||
|
const category = categories[index];
|
||||||
|
fetch(`http://127.0.0.1:4567/api/v1/category/${category.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
}).finally(() => triggerUpdate());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
<Droppable droppableId="droppable">
|
||||||
|
{(provided) => (
|
||||||
|
<List ref={provided.innerRef}>
|
||||||
|
{categories.map((item, index) => (
|
||||||
|
<Draggable
|
||||||
|
key={item.id}
|
||||||
|
draggableId={item.id.toString()}
|
||||||
|
index={index}
|
||||||
|
>
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<ListItem
|
||||||
|
ContainerComponent="li"
|
||||||
|
ContainerProps={{ ref: provided.innerRef }}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
style={getItemStyle(
|
||||||
|
snapshot.isDragging,
|
||||||
|
provided.draggableProps.style,
|
||||||
|
theme.palette,
|
||||||
|
)}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<DragHandleIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={item.name}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setCategoryToEdit(index);
|
||||||
|
handleDialogOpen();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
deleteCategory(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
<Fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="add"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: theme.spacing(2),
|
||||||
|
right: theme.spacing(2),
|
||||||
|
}}
|
||||||
|
onClick={handleDialogOpen}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</Fab>
|
||||||
|
<Dialog open={dialogOpen} onClose={handleDialogCancel} aria-labelledby="form-dialog-title">
|
||||||
|
<DialogTitle id="form-dialog-title">
|
||||||
|
{categoryToEdit === -1 ? 'New Catalog' : `Rename: ${categories[categoryToEdit].name}`}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Enter new category name.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Category Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
value={dialogValue}
|
||||||
|
onChange={(e) => setDialogValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleDialogCancel} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleDialogSubmit} color="primary">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
7
webUI/react/src/typings.d.ts
vendored
7
webUI/react/src/typings.d.ts
vendored
@ -38,3 +38,10 @@ interface IChapter {
|
|||||||
mangaId: number
|
mangaId: number
|
||||||
pageCount: number
|
pageCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ICategory {
|
||||||
|
id: number
|
||||||
|
order: number
|
||||||
|
name: String
|
||||||
|
isLanding: boolean
|
||||||
|
}
|
||||||
|
@ -3744,6 +3744,13 @@ css-blank-pseudo@^0.1.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss "^7.0.5"
|
postcss "^7.0.5"
|
||||||
|
|
||||||
|
css-box-model@^1.2.0:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||||
|
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||||
|
dependencies:
|
||||||
|
tiny-invariant "^1.0.6"
|
||||||
|
|
||||||
css-color-names@0.0.4, css-color-names@^0.0.4:
|
css-color-names@0.0.4, css-color-names@^0.0.4:
|
||||||
version "0.0.4"
|
version "0.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||||
@ -7344,6 +7351,11 @@ media-typer@0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
memoize-one@^5.1.1:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||||
|
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
||||||
|
|
||||||
memory-fs@^0.4.1:
|
memory-fs@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
|
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
|
||||||
@ -9208,6 +9220,11 @@ querystringify@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||||
|
|
||||||
|
raf-schd@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0"
|
||||||
|
integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==
|
||||||
|
|
||||||
raf@^3.4.1:
|
raf@^3.4.1:
|
||||||
version "3.4.1"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||||
@ -9257,6 +9274,19 @@ react-app-polyfill@^2.0.0:
|
|||||||
regenerator-runtime "^0.13.7"
|
regenerator-runtime "^0.13.7"
|
||||||
whatwg-fetch "^3.4.1"
|
whatwg-fetch "^3.4.1"
|
||||||
|
|
||||||
|
react-beautiful-dnd@^13.0.0:
|
||||||
|
version "13.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40"
|
||||||
|
integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.8.4"
|
||||||
|
css-box-model "^1.2.0"
|
||||||
|
memoize-one "^5.1.1"
|
||||||
|
raf-schd "^4.0.2"
|
||||||
|
react-redux "^7.1.1"
|
||||||
|
redux "^4.0.4"
|
||||||
|
use-memo-one "^1.1.1"
|
||||||
|
|
||||||
react-dev-utils@^11.0.1:
|
react-dev-utils@^11.0.1:
|
||||||
version "11.0.1"
|
version "11.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.1.tgz#30106c2055acfd6b047d2dc478a85c356e66fe45"
|
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.1.tgz#30106c2055acfd6b047d2dc478a85c356e66fe45"
|
||||||
@ -9301,7 +9331,7 @@ react-error-overlay@^6.0.8:
|
|||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de"
|
||||||
integrity sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==
|
integrity sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==
|
||||||
|
|
||||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
@ -9311,6 +9341,17 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
||||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||||
|
|
||||||
|
react-redux@^7.1.1:
|
||||||
|
version "7.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736"
|
||||||
|
integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.1"
|
||||||
|
hoist-non-react-statics "^3.3.2"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^16.13.1"
|
||||||
|
|
||||||
react-refresh@^0.8.3:
|
react-refresh@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
||||||
@ -9518,6 +9559,14 @@ redent@^3.0.0:
|
|||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
|
redux@^4.0.4:
|
||||||
|
version "4.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||||
|
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
symbol-observable "^1.2.0"
|
||||||
|
|
||||||
regenerate-unicode-properties@^8.2.0:
|
regenerate-unicode-properties@^8.2.0:
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
|
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
|
||||||
@ -10672,7 +10721,7 @@ svgo@^1.0.0, svgo@^1.2.2:
|
|||||||
unquote "~1.1.1"
|
unquote "~1.1.1"
|
||||||
util.promisify "~1.0.0"
|
util.promisify "~1.0.0"
|
||||||
|
|
||||||
symbol-observable@1.2.0:
|
symbol-observable@1.2.0, symbol-observable@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||||
@ -10823,7 +10872,7 @@ timsort@^0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||||
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
||||||
|
|
||||||
tiny-invariant@^1.0.2:
|
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
||||||
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
|
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
|
||||||
@ -11176,6 +11225,11 @@ url@^0.11.0:
|
|||||||
punycode "1.3.2"
|
punycode "1.3.2"
|
||||||
querystring "0.2.0"
|
querystring "0.2.0"
|
||||||
|
|
||||||
|
use-memo-one@^1.1.1:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
|
||||||
|
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
|
||||||
|
|
||||||
use@^3.1.0:
|
use@^3.1.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||||
|
Loading…
Reference in New Issue
Block a user