From f1cc37d0db221ef1bccddabc2ef471879987e9b1 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 20 Feb 2021 01:23:52 +0330 Subject: [PATCH] finished the category screen --- .../main/kotlin/ir/armor/tachidesk/Main.kt | 25 +- .../ir/armor/tachidesk/database/DBMangaer.kt | 16 +- .../database/dataclass/CategoryDataClass.kt | 2 + .../tachidesk/database/table/CategoryTable.kt | 3 + .../tachidesk/database/table/MangaTable.kt | 1 + .../ir/armor/tachidesk/util/Category.kt | 26 +- .../ir/armor/tachidesk/util/CategoryManga.kt | 10 + .../kotlin/ir/armor/tachidesk/util/Library.kt | 3 +- webUI/react/package.json | 1 + webUI/react/src/App.tsx | 14 +- .../react/src/components/TemporaryDrawer.tsx | 8 + webUI/react/src/screens/Library.tsx | 114 ++++++++- webUI/react/src/screens/Settings.tsx | 34 +++ .../{MangaList.tsx => SourceMangas.tsx} | 2 +- .../react/src/screens/settings/Categories.jsx | 231 ++++++++++++++++++ webUI/react/src/typings.d.ts | 7 + webUI/react/yarn.lock | 60 ++++- 17 files changed, 516 insertions(+), 41 deletions(-) create mode 100644 webUI/react/src/screens/Settings.tsx rename webUI/react/src/screens/{MangaList.tsx => SourceMangas.tsx} (96%) create mode 100644 webUI/react/src/screens/settings/Categories.jsx diff --git a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt index 0e031dd..a032ca7 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/Main.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -29,6 +29,7 @@ import ir.armor.tachidesk.util.removeCategory import ir.armor.tachidesk.util.removeExtension import ir.armor.tachidesk.util.removeMangaFromCategory import ir.armor.tachidesk.util.removeMangaFromLibrary +import ir.armor.tachidesk.util.reorderCategory import ir.armor.tachidesk.util.sourceFilters import ir.armor.tachidesk.util.sourceGlobalSearch import ir.armor.tachidesk.util.sourceSearch @@ -60,7 +61,7 @@ class Main { // make sure everything we need exists applicationSetup() - val tray = systemTray() + val tray = systemTray() // assign it to a variable so it's kept in the memory and not garbage collected registerConfigModules() @@ -227,7 +228,7 @@ class Main { 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 -> ctx.json(getLibraryMangas()) } @@ -240,20 +241,28 @@ class Main { // category create app.post("/api/v1/category/") { ctx -> val name = ctx.formParam("name")!! - val isLanding = ctx.formParam("isLanding", "false").toBoolean() - createCategory(name, isLanding) + createCategory(name) ctx.status(200) } // category modification - app.put("/api/v1/category/:categoryId") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - val name = ctx.formParam("name")!! - val isLanding = ctx.formParam("isLanding").toBoolean() + 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) 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 app.delete("/api/v1/category/:categoryId") { ctx -> val categoryId = ctx.pathParam("categoryId").toInt() diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt index 04ed427..a379647 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/DBMangaer.kt @@ -29,12 +29,14 @@ fun makeDataBaseTables() { // db.useNestedTransactions = true transaction { - SchemaUtils.create(ExtensionTable) - SchemaUtils.create(SourceTable) - SchemaUtils.create(MangaTable) - SchemaUtils.create(ChapterTable) - SchemaUtils.create(PageTable) - SchemaUtils.create(CategoryTable) - SchemaUtils.create(CategoryMangaTable) + SchemaUtils.createMissingTablesAndColumns( + ExtensionTable, + SourceTable, + MangaTable, + ChapterTable, + PageTable, + CategoryTable, + CategoryMangaTable, + ) } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/CategoryDataClass.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/CategoryDataClass.kt index 6687b0d..81e0a87 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/CategoryDataClass.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/dataclass/CategoryDataClass.kt @@ -5,6 +5,8 @@ package ir.armor.tachidesk.database.dataclass * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ data class CategoryDataClass( + val id: Int, + val order: Int, val name: String, val isLanding: Boolean ) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/table/CategoryTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/table/CategoryTable.kt index ae2af4d..d645c92 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/table/CategoryTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/table/CategoryTable.kt @@ -11,9 +11,12 @@ 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) } fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass( + categoryEntry[CategoryTable.id].value, + categoryEntry[CategoryTable.order], categoryEntry[CategoryTable.name], categoryEntry[CategoryTable.isLanding], ) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt index 13e3a56..f2a1275 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/database/table/MangaTable.kt @@ -25,6 +25,7 @@ object MangaTable : IntIdTable() { val thumbnail_url = varchar("thumbnail_url", 2048).nullable() val inLibrary = bool("in_library").default(false) + val defaultCategory = bool("default_category").default(true) // source is used by some ancestor of IntIdTable val sourceReference = reference("source", SourceTable) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt index 80ad67d..37da061 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Category.kt @@ -3,6 +3,7 @@ package ir.armor.tachidesk.util import ir.armor.tachidesk.database.dataclass.CategoryDataClass import ir.armor.tachidesk.database.table.CategoryTable import ir.armor.tachidesk.database.table.toDataClass +import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.insert 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 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -fun createCategory(name: String, isLanding: Boolean) { +fun createCategory(name: String) { transaction { + val count = CategoryTable.selectAll().count() if (CategoryTable.select { CategoryTable.name eq name }.firstOrNull() == null) CategoryTable.insert { 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 { CategoryTable.update({ CategoryTable.id eq categoryId }) { - it[CategoryTable.name] = name - it[CategoryTable.isLanding] = isLanding + if (name != null) it[CategoryTable.name] = name + 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 { return transaction { - CategoryTable.selectAll().map { + CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).map { CategoryTable.toDataClass(it) } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt index 9903f44..8a542a7 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/CategoryManga.kt @@ -9,6 +9,7 @@ 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 /* 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 @@ -21,6 +22,10 @@ fun addMangaToCategory(mangaId: Int, categoryId: Int) { it[CategoryMangaTable.category] = categoryId 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) { transaction { 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 + } + } } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt index 5766ba9..97abcec 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/Library.kt @@ -3,6 +3,7 @@ package ir.armor.tachidesk.util import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.database.table.toDataClass +import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update @@ -35,7 +36,7 @@ fun removeMangaFromLibrary(mangaId: Int) { fun getLibraryMangas(): List { return transaction { - MangaTable.select { MangaTable.inLibrary eq true }.map { + MangaTable.select { (MangaTable.inLibrary eq true) and (MangaTable.defaultCategory eq true) }.map { MangaTable.toDataClass(it) } } diff --git a/webUI/react/package.json b/webUI/react/package.json index 1c5738d..b316510 100644 --- a/webUI/react/package.json +++ b/webUI/react/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^12.1.10", "fontsource-roboto": "^4.0.0", "react": "^17.0.1", + "react-beautiful-dnd": "^13.0.0", "react-dom": "^17.0.1", "react-router-dom": "^5.2.0", "react-scripts": "4.0.1", diff --git a/webUI/react/src/App.tsx b/webUI/react/src/App.tsx index 2a864d6..bc24a22 100644 --- a/webUI/react/src/App.tsx +++ b/webUI/react/src/App.tsx @@ -14,13 +14,15 @@ import NavBar from './components/NavBar'; import Home from './screens/Home'; import Sources from './screens/Sources'; import Extensions from './screens/Extensions'; -import MangaList from './screens/MangaList'; +import SourceMangas from './screens/SourceMangas'; import Manga from './screens/Manga'; import Reader from './screens/Reader'; import Search from './screens/SearchSingle'; import NavBarTitle from './context/NavbarTitle'; import DarkTheme from './context/DarkTheme'; import Library from './screens/Library'; +import Settings from './screens/Settings'; +import Categories from './screens/settings/Categories'; export default function App() { const [title, setTitle] = useState('Tachidesk'); @@ -69,10 +71,10 @@ export default function App() { - + - + @@ -86,6 +88,12 @@ export default function App() { + + + + + + diff --git a/webUI/react/src/components/TemporaryDrawer.tsx b/webUI/react/src/components/TemporaryDrawer.tsx index 5304780..89210c6 100644 --- a/webUI/react/src/components/TemporaryDrawer.tsx +++ b/webUI/react/src/components/TemporaryDrawer.tsx @@ -60,6 +60,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) { + + + + + + + + {/* diff --git a/webUI/react/src/screens/Library.tsx b/webUI/react/src/screens/Library.tsx index de83610..4ca3180 100644 --- a/webUI/react/src/screens/Library.tsx +++ b/webUI/react/src/screens/Library.tsx @@ -2,13 +2,45 @@ * 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 { Tab, Tabs } from '@material-ui/core'; import React, { useContext, useEffect, useState } from 'react'; import MangaGrid from '../components/MangaGrid'; 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 ( + + ); +} + +export default function Library() { const { setTitle } = useContext(NavBarTitle); - const [mangas, setMangas] = useState([]); + const [tabs, setTabs] = useState([]); + const [tabNum, setTabNum] = useState(0); const [lastPageNum, setLastPageNum] = useState(1); useEffect(() => { @@ -19,16 +51,74 @@ export default function MangaList() { fetch('http://127.0.0.1:4567/api/v1/library') .then((response) => response.json()) .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) => ()); + + const tabBodies = tabs.map((tab) => ( + + + + )); + toRender = ( + <> + + {tabDefines} + + {tabBodies} + + ); + } else { + const mangas = tabs.length === 1 ? tabs[0].mangas : []; + toRender = ( + + ); + } + + return toRender; } diff --git a/webUI/react/src/screens/Settings.tsx b/webUI/react/src/screens/Settings.tsx new file mode 100644 index 0000000..78462c1 --- /dev/null +++ b/webUI/react/src/screens/Settings.tsx @@ -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 ; +} + +export default function Settings() { + const { setTitle } = useContext(NavBarTitle); + setTitle('Settings'); + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/webUI/react/src/screens/MangaList.tsx b/webUI/react/src/screens/SourceMangas.tsx similarity index 96% rename from webUI/react/src/screens/MangaList.tsx rename to webUI/react/src/screens/SourceMangas.tsx index 78dd7dd..24d5c28 100644 --- a/webUI/react/src/screens/MangaList.tsx +++ b/webUI/react/src/screens/SourceMangas.tsx @@ -7,7 +7,7 @@ import { useParams } from 'react-router-dom'; import MangaGrid from '../components/MangaGrid'; 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 { setTitle } = useContext(NavBarTitle); const [mangas, setMangas] = useState([]); diff --git a/webUI/react/src/screens/settings/Categories.jsx b/webUI/react/src/screens/settings/Categories.jsx new file mode 100644 index 0000000..fa92385 --- /dev/null +++ b/webUI/react/src/screens/settings/Categories.jsx @@ -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 ( + <> + + + {(provided) => ( + + {categories.map((item, index) => ( + + {(provided, snapshot) => ( + + + + + + { + setCategoryToEdit(index); + handleDialogOpen(); + }} + > + + + { + deleteCategory(index); + }} + > + + + + )} + + ))} + {provided.placeholder} + + )} + + + + + + + + {categoryToEdit === -1 ? 'New Catalog' : `Rename: ${categories[categoryToEdit].name}`} + + + + Enter new category name. + + setDialogValue(e.target.value)} + /> + + + + + + + + + ); +} diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index 60a2d35..d969e34 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -38,3 +38,10 @@ interface IChapter { mangaId: number pageCount: number } + +interface ICategory { + id: number + order: number + name: String + isLanding: boolean +} diff --git a/webUI/react/yarn.lock b/webUI/react/yarn.lock index 6a1f50f..5ed1461 100644 --- a/webUI/react/yarn.lock +++ b/webUI/react/yarn.lock @@ -3744,6 +3744,13 @@ css-blank-pseudo@^0.1.4: dependencies: 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: version "0.0.4" 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" 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: version "0.4.1" 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" 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: version "3.4.1" 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" 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: version "11.0.1" 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" 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" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 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" 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: version "0.8.3" 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" 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: version "8.2.0" 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" util.promisify "~1.0.0" -symbol-observable@1.2.0: +symbol-observable@1.2.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" 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" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-invariant@^1.0.2: +tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== @@ -11176,6 +11225,11 @@ url@^0.11.0: punycode "1.3.2" 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: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"