From 2255c4cd5ac4c50dcd3b0a47a4f02057e6034d70 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 17:12:57 +0800 Subject: [PATCH 01/28] feat: add MUI ImageList --- app/components/explorer/imageLoader/index.tsx | 12 +++++++++--- app/components/explorer/index.tsx | 18 +++++++++++------- app/lib/theme.ts | 5 +++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/components/explorer/imageLoader/index.tsx b/app/components/explorer/imageLoader/index.tsx index 7ffffad..e6c29f7 100644 --- a/app/components/explorer/imageLoader/index.tsx +++ b/app/components/explorer/imageLoader/index.tsx @@ -2,13 +2,17 @@ import { useState, useEffect } from "react"; import { loadImage } from "@/app/lib/imageLoader"; +import { Skeleton } from "@mui/material"; interface Props { path: string; + alt?: string; + width?: number; + height?: number; } export default function ImageLoader(props: Props) { - const { path } = props; + const { path, alt, width, height } = props; const [imageSrc, setImageSrc] = useState(""); useEffect(() => { @@ -24,11 +28,13 @@ export default function ImageLoader(props: Props) { {imageSrc ? ( {"img"} ) : ( -

No image selected

+ )} ); diff --git a/app/components/explorer/index.tsx b/app/components/explorer/index.tsx index ddc9932..d8645ab 100644 --- a/app/components/explorer/index.tsx +++ b/app/components/explorer/index.tsx @@ -8,17 +8,17 @@ import { getItem } from "@/app/lib/items"; import { Item } from "@/app/lib/db/types"; import { ImageList, ImageListItem } from "@mui/material"; +const size = 250; + export default function Explorer() { const subscribeSelected = useTagTreeState((s) => s.subscribeSelected); - const [id, setId] = useState(0); - const [img, setImg] = useState([]); + const [imagePaths, setImagePaths] = useState([]); useEffect(() => { subscribeSelected(onSelected); }, []); const onSelected = async (id: number) => { - setId(id); const tag = await getTag(id, true, true, true); if (tag === null) return; if (!tag.items) return; @@ -37,15 +37,19 @@ export default function Explorer() { const paths = items.map((i) => i!.path); console.log(paths); - setImg(paths); + setImagePaths(paths); } catch (err) {} }; return (
- {img.map((p) => ( - - ))} + + {imagePaths.map((path, index) => ( + + + + ))} +
); } diff --git a/app/lib/theme.ts b/app/lib/theme.ts index bda7ff6..44eb8e7 100644 --- a/app/lib/theme.ts +++ b/app/lib/theme.ts @@ -20,6 +20,11 @@ const theme = createTheme({ variant: "outlined", }, }, + MuiSkeleton: { + defaultProps: { + variant: "rounded", + }, + }, }, }); From 6aff29093adcb0a4ad63881339d1a0e2d0d34120 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 17:18:49 +0800 Subject: [PATCH 02/28] chore: override MUI button text transform to "none" --- .vscode/settings.json | 1 + app/lib/theme.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c5b5586..11c02b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "**/index.*": "${dirname}.${extname} (${filename})" }, "cSpell.words": [ + "Topbar", "zustand" ] } diff --git a/app/lib/theme.ts b/app/lib/theme.ts index 44eb8e7..764a32d 100644 --- a/app/lib/theme.ts +++ b/app/lib/theme.ts @@ -1,4 +1,4 @@ -// MUI theme config +// MUI theme global config // https://mui.com/material-ui/integrations/nextjs/#theming "use client"; @@ -14,6 +14,11 @@ const theme = createTheme({ defaultProps: { variant: "contained", }, + styleOverrides: { + root: { + textTransform: "none", + }, + }, }, MuiTextField: { defaultProps: { From bfd498214e8fd6da9cf9f7f3feae1bebc68a29b1 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 17:32:31 +0800 Subject: [PATCH 03/28] feat: remove some console.log --- app/components/explorer/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/components/explorer/index.tsx b/app/components/explorer/index.tsx index d8645ab..6b6a0df 100644 --- a/app/components/explorer/index.tsx +++ b/app/components/explorer/index.tsx @@ -33,10 +33,7 @@ export default function Explorer() { ); items = items.filter((item) => item !== null); - console.log(items); - const paths = items.map((i) => i!.path); - console.log(paths); setImagePaths(paths); } catch (err) {} }; From 85c550c60ea634941f3d74b73e0e8b8deac98c91 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 17:56:52 +0800 Subject: [PATCH 04/28] feat: update layout --- .vscode/settings.json | 3 +-- app/explorer/page.tsx | 5 +++++ app/layout.tsx | 14 +++++++++++++- app/page.tsx | 21 ++++----------------- app/settings/page.tsx | 3 +++ 5 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 app/explorer/page.tsx create mode 100644 app/settings/page.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 11c02b9..9d0e68a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,8 +2,7 @@ "editor.tabSize": 2, "editor.insertSpaces": true, "workbench.editor.customLabels.patterns": { - "**/route.*": "${dirname}.${extname} (${filename})", - "**/index.*": "${dirname}.${extname} (${filename})" + "{**/index.*,**/route.*,**/layout.*,**/page.*}": "${dirname}.${extname} (${filename})" }, "cSpell.words": [ "Topbar", diff --git a/app/explorer/page.tsx b/app/explorer/page.tsx new file mode 100644 index 0000000..91a31c9 --- /dev/null +++ b/app/explorer/page.tsx @@ -0,0 +1,5 @@ +import Explorer from "@/app/components/explorer"; + +export default async function Page() { + return ; +} diff --git a/app/layout.tsx b/app/layout.tsx index 93a6f62..0a3df85 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,7 +2,10 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter"; import { ThemeProvider } from "@mui/material/styles"; +import { Box, Toolbar } from "@mui/material"; import CssBaseline from "@mui/material/CssBaseline"; +import Sidebar from "@/app/components/sidebar"; +import Topbar from "@/app/components/topbar"; import theme from "@/app/lib/theme"; // import "./globals.css"; @@ -24,7 +27,16 @@ export default function RootLayout({ {/* https://mui.com/material-ui/integrations/nextjs/ */} - {children} + + + + + + + + {children} + + diff --git a/app/page.tsx b/app/page.tsx index f9b3355..8ddf9a4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,19 +1,6 @@ -import { Box, Toolbar } from "@mui/material"; -import Sidebar from "@/app/components/sidebar"; -import Topbar from "@/app/components/topbar"; -import Explorer from "@/app/components/explorer"; +import { redirect } from "next/navigation"; -export default async function Page() { - return ( -
- - - - - - - - -
- ); +export default function HomePage() { + // Redirect to the explorer page + redirect("/explorer"); } diff --git a/app/settings/page.tsx b/app/settings/page.tsx new file mode 100644 index 0000000..a0454d0 --- /dev/null +++ b/app/settings/page.tsx @@ -0,0 +1,3 @@ +export default async function Page() { + return
Settings
; +} From 60af6fc751e82a79ec8cf4685cf86b0f36ca6763 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 18:17:48 +0800 Subject: [PATCH 05/28] feat: update sidebar navigation --- app/components/sidebar/index.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/components/sidebar/index.tsx b/app/components/sidebar/index.tsx index e282622..5663bfc 100644 --- a/app/components/sidebar/index.tsx +++ b/app/components/sidebar/index.tsx @@ -5,6 +5,7 @@ import { Box, Divider, Drawer, + Link, List, ListItem, ListItemButton, @@ -21,6 +22,12 @@ import { useTagTreeState } from "@/app/store/tagTree"; const drawerWidth = 240; const collapsedWidth = 80; +const NavList = [ + { label: "Home", url: "/explorer" }, + { label: "Settings", url: "/settings" }, + { label: "Docs", url: "/api/docs" }, +]; + export default function Sidebar() { const [open, setOpen] = React.useState(true); const { tagTreeItems, updateTagTree, updateSelectedTagId } = @@ -59,12 +66,9 @@ export default function Sidebar() { - {["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => ( - - - - - + {NavList.map((item, index) => ( + + {item.label} ))} From dd2ae889cd5757da5e731c59570f8087f7925266 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 18:24:01 +0800 Subject: [PATCH 06/28] feat: update metadata --- .vscode/settings.json | 1 + app/layout.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d0e68a..d8c0397 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "{**/index.*,**/route.*,**/layout.*,**/page.*}": "${dirname}.${extname} (${filename})" }, "cSpell.words": [ + "nextjs", "Topbar", "zustand" ] diff --git a/app/layout.tsx b/app/layout.tsx index 0a3df85..a09f8f3 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,8 +12,8 @@ import theme from "@/app/lib/theme"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Hie", + description: "Hierarchical tag-based image explorer", }; export default function RootLayout({ From ad419a8a8a5d9a742cdea56cddaac331cec269a5 Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 18:25:54 +0800 Subject: [PATCH 07/28] refactor: rename route settings to database --- .vscode/settings.json | 1 + app/components/sidebar/index.tsx | 2 +- app/{settings => database}/page.tsx | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename app/{settings => database}/page.tsx (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index d8c0397..a3b5838 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ }, "cSpell.words": [ "nextjs", + "tailwindcss", "Topbar", "zustand" ] diff --git a/app/components/sidebar/index.tsx b/app/components/sidebar/index.tsx index 5663bfc..1be084f 100644 --- a/app/components/sidebar/index.tsx +++ b/app/components/sidebar/index.tsx @@ -24,7 +24,7 @@ const collapsedWidth = 80; const NavList = [ { label: "Home", url: "/explorer" }, - { label: "Settings", url: "/settings" }, + { label: "Database", url: "/database" }, { label: "Docs", url: "/api/docs" }, ]; diff --git a/app/settings/page.tsx b/app/database/page.tsx similarity index 100% rename from app/settings/page.tsx rename to app/database/page.tsx From 4500ee73ef6a638ab56080d90f63628b544f9c2e Mon Sep 17 00:00:00 2001 From: ziteh Date: Sun, 25 Aug 2024 18:56:21 +0800 Subject: [PATCH 08/28] feat: add tab to database page --- app/database/page.tsx | 55 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/app/database/page.tsx b/app/database/page.tsx index a0454d0..dce2993 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -1,3 +1,54 @@ -export default async function Page() { - return
Settings
; +"use client"; + +import * as React from "react"; +import { Box, Tabs, Tab } from "@mui/material"; + +const TabItems = ["Tag", "Item", "Folder"]; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function CustomTabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +export default function Page() { + const [tabIndex, setTabIndex] = React.useState(0); + + const handleChange = (event: React.SyntheticEvent, newValue: number) => { + setTabIndex(newValue); + }; + + return ( + + + + {TabItems.map((item) => ( + + ))} + + + + {TabItems.map((item, index) => ( + + {item} + + ))} + + ); } From 0013a064318728604fff7f45cb6d281567cfff05 Mon Sep 17 00:00:00 2001 From: ziteh Date: Tue, 27 Aug 2024 07:43:50 +0800 Subject: [PATCH 09/28] feat: add basic database table --- .../databaseTable/dbTableHead/index.tsx | 104 +++++++++ .../databaseTable/dbTableToolbar/index.tsx | 62 ++++++ app/database/databaseTable/index.tsx | 210 ++++++++++++++++++ app/database/databaseTable/types.ts | 35 +++ app/database/page.tsx | 3 +- 5 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 app/database/databaseTable/dbTableHead/index.tsx create mode 100644 app/database/databaseTable/dbTableToolbar/index.tsx create mode 100644 app/database/databaseTable/index.tsx create mode 100644 app/database/databaseTable/types.ts diff --git a/app/database/databaseTable/dbTableHead/index.tsx b/app/database/databaseTable/dbTableHead/index.tsx new file mode 100644 index 0000000..4374e4b --- /dev/null +++ b/app/database/databaseTable/dbTableHead/index.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { + TableHead, + TableRow, + TableCell, + Checkbox, + TableSortLabel, +} from "@mui/material"; +import { Order, Data, HeadCell } from "@/app/database/databaseTable/types"; + +const headCells: readonly HeadCell[] = [ + { + id: "name", + numeric: false, + disablePadding: true, + label: "Name", + }, + { + id: "calories", + numeric: true, + disablePadding: false, + label: "Type", + }, + { + id: "fat", + numeric: true, + disablePadding: false, + label: "Parent", + }, + { + id: "protein", + numeric: true, + disablePadding: false, + label: "Updated", + }, + { + id: "carbs", + numeric: true, + disablePadding: false, + label: "Created", + }, +]; + +interface Props { + numSelected: number; + order: Order; + orderBy: string; + rowCount: number; + onRequestSort: ( + event: React.MouseEvent, + property: keyof Data + ) => void; + onSelectAllClick: (event: React.ChangeEvent) => void; +} + +export default function DbTableHead(props: Props) { + const { + onSelectAllClick, + order, + orderBy, + numSelected, + rowCount, + onRequestSort, + } = props; + const createSortHandler = + (property: keyof Data) => (event: React.MouseEvent) => { + onRequestSort(event, property); + }; + + return ( + + + + 0 && numSelected < rowCount} + checked={rowCount > 0 && numSelected === rowCount} + onChange={onSelectAllClick} + inputProps={{ + "aria-label": "select all desserts", + }} + /> + + {headCells.map((headCell) => ( + + + {headCell.label} + + + ))} + + + ); +} diff --git a/app/database/databaseTable/dbTableToolbar/index.tsx b/app/database/databaseTable/dbTableToolbar/index.tsx new file mode 100644 index 0000000..519026f --- /dev/null +++ b/app/database/databaseTable/dbTableToolbar/index.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { alpha, IconButton, Toolbar, Tooltip, Typography } from "@mui/material"; +import DeleteIcon from "@mui/icons-material/Delete"; +import FilterListIcon from "@mui/icons-material/FilterList"; + +interface Props { + numSelected: number; +} + +export default function DbTableToolbar(props: Props) { + const { numSelected } = props; + + return ( + 0 && { + bgcolor: (theme) => + alpha( + theme.palette.primary.main, + theme.palette.action.activatedOpacity + ), + }), + }} + > + {numSelected > 0 ? ( + + {numSelected} selected + + ) : ( + + Nutrition + + )} + {numSelected > 0 ? ( + + + + + + ) : ( + + + + + + )} + + ); +} diff --git a/app/database/databaseTable/index.tsx b/app/database/databaseTable/index.tsx new file mode 100644 index 0000000..7f26c79 --- /dev/null +++ b/app/database/databaseTable/index.tsx @@ -0,0 +1,210 @@ +"use client"; + +import * as React from "react"; +import { + Box, + Paper, + TableContainer, + Table, + TableBody, + TableRow, + TableCell, + Checkbox, + TablePagination, +} from "@mui/material"; +import { Order, Data, createData } from "@/app/database/databaseTable/types"; +import DbTableHead from "./dbTableHead"; +import DbTableToolbar from "./dbTableToolbar"; + +const rows = [ + createData(1, "Cupcake", 305, 3.7, 67, 4.3), + createData(2, "Donut", 452, 25.0, 51, 4.9), + createData(3, "Eclair", 262, 16.0, 24, 6.0), + createData(4, "Frozen yoghurt", 159, 6.0, 24, 4.0), + createData(5, "Gingerbread", 356, 16.0, 49, 3.9), + createData(6, "Honeycomb", 408, 3.2, 87, 6.5), + createData(7, "Ice cream sandwich", 237, 9.0, 37, 4.3), + createData(8, "Jelly Bean", 375, 0.0, 94, 0.0), + createData(9, "KitKat", 518, 26.0, 65, 7.0), + createData(10, "Lollipop", 392, 0.2, 98, 0.0), + createData(11, "Marshmallow", 318, 0, 81, 2.0), + createData(12, "Nougat", 360, 19.0, 9, 37.0), + createData(13, "Oreo", 437, 18.0, 63, 4.0), +]; + +function descendingComparator(a: T, b: T, orderBy: keyof T) { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +} + +function getComparator( + order: Order, + orderBy: Key +): ( + a: { [key in Key]: number | string }, + b: { [key in Key]: number | string } +) => number { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +} + +export default function DatabaseTable() { + const [order, setOrder] = React.useState("asc"); + const [orderBy, setOrderBy] = React.useState("calories"); + const [selected, setSelected] = React.useState([]); + const [page, setPage] = React.useState(0); + const [dense, setDense] = React.useState(true); + const [rowsPerPage, setRowsPerPage] = React.useState(25); + + const handleRequestSort = ( + event: React.MouseEvent, + property: keyof Data + ) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + + const handleSelectAllClick = (event: React.ChangeEvent) => { + if (event.target.checked) { + const newSelected = rows.map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + const handleClick = (event: React.MouseEvent, id: number) => { + const selectedIndex = selected.indexOf(id); + let newSelected: readonly number[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1) + ); + } + setSelected(newSelected); + }; + + const handleChangePage = (event: unknown, newPage: number) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const isSelected = (id: number) => selected.indexOf(id) !== -1; + + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0; + + const visibleRows = React.useMemo( + () => + rows + .slice() + .sort(getComparator(order, orderBy)) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage), + [order, orderBy, page, rowsPerPage] + ); + + return ( + + + + + + + + {visibleRows.map((row, index) => { + const isItemSelected = isSelected(row.id); + const labelId = `enhanced-table-checkbox-${index}`; + + return ( + handleClick(event, row.id)} + role="checkbox" + aria-checked={isItemSelected} + tabIndex={-1} + key={row.id} + selected={isItemSelected} + sx={{ cursor: "pointer" }} + > + + + + + {row.name} + + {row.calories} + {row.fat} + {row.carbs} + {row.protein} + + ); + })} + {emptyRows > 0 && ( + + + + )} + +
+
+ +
+
+ ); +} diff --git a/app/database/databaseTable/types.ts b/app/database/databaseTable/types.ts new file mode 100644 index 0000000..a1cc46e --- /dev/null +++ b/app/database/databaseTable/types.ts @@ -0,0 +1,35 @@ +export interface Data { + id: number; + calories: number; + carbs: number; + fat: number; + name: string; + protein: number; +} + +export function createData( + id: number, + name: string, + calories: number, + fat: number, + carbs: number, + protein: number +): Data { + return { + id, + name, + calories, + fat, + carbs, + protein, + }; +} + +export interface HeadCell { + id: keyof Data; + label: string; + disablePadding: boolean; + numeric: boolean; +} + +export type Order = "asc" | "desc"; diff --git a/app/database/page.tsx b/app/database/page.tsx index dce2993..a288599 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { Box, Tabs, Tab } from "@mui/material"; +import DatabaseTable from "./databaseTable"; const TabItems = ["Tag", "Item", "Folder"]; @@ -46,7 +47,7 @@ export default function Page() { {TabItems.map((item, index) => ( - {item} + ))}
From 215543b77be8c850a6656fea68b0b759576ea2d7 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 28 Aug 2024 07:42:00 +0800 Subject: [PATCH 10/28] feat: add rows props for database table --- .../databaseTable/dbTableToolbar/index.tsx | 5 +- app/database/databaseTable/index.tsx | 64 ++++++++----------- app/database/page.tsx | 34 ++++++++-- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/app/database/databaseTable/dbTableToolbar/index.tsx b/app/database/databaseTable/dbTableToolbar/index.tsx index 519026f..4bab4e1 100644 --- a/app/database/databaseTable/dbTableToolbar/index.tsx +++ b/app/database/databaseTable/dbTableToolbar/index.tsx @@ -6,10 +6,11 @@ import FilterListIcon from "@mui/icons-material/FilterList"; interface Props { numSelected: number; + title:string; } export default function DbTableToolbar(props: Props) { - const { numSelected } = props; + const { numSelected ,title } = props; return ( - Nutrition + {title} )} {numSelected > 0 ? ( diff --git a/app/database/databaseTable/index.tsx b/app/database/databaseTable/index.tsx index 7f26c79..d5240e2 100644 --- a/app/database/databaseTable/index.tsx +++ b/app/database/databaseTable/index.tsx @@ -16,22 +16,6 @@ import { Order, Data, createData } from "@/app/database/databaseTable/types"; import DbTableHead from "./dbTableHead"; import DbTableToolbar from "./dbTableToolbar"; -const rows = [ - createData(1, "Cupcake", 305, 3.7, 67, 4.3), - createData(2, "Donut", 452, 25.0, 51, 4.9), - createData(3, "Eclair", 262, 16.0, 24, 6.0), - createData(4, "Frozen yoghurt", 159, 6.0, 24, 4.0), - createData(5, "Gingerbread", 356, 16.0, 49, 3.9), - createData(6, "Honeycomb", 408, 3.2, 87, 6.5), - createData(7, "Ice cream sandwich", 237, 9.0, 37, 4.3), - createData(8, "Jelly Bean", 375, 0.0, 94, 0.0), - createData(9, "KitKat", 518, 26.0, 65, 7.0), - createData(10, "Lollipop", 392, 0.2, 98, 0.0), - createData(11, "Marshmallow", 318, 0, 81, 2.0), - createData(12, "Nougat", 360, 19.0, 9, 37.0), - createData(13, "Oreo", 437, 18.0, 63, 4.0), -]; - function descendingComparator(a: T, b: T, orderBy: keyof T) { if (b[orderBy] < a[orderBy]) { return -1; @@ -42,21 +26,28 @@ function descendingComparator(a: T, b: T, orderBy: keyof T) { return 0; } -function getComparator( +function getComparator( order: Order, - orderBy: Key -): ( - a: { [key in Key]: number | string }, - b: { [key in Key]: number | string } -) => number { + orderBy: keyof T +): (a: T, b: T) => number { return order === "desc" ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); } -export default function DatabaseTable() { +interface Props { + rows: T[]; +} + +export default function DatabaseTable( + props: Props +) { + const { rows } = props; + const [order, setOrder] = React.useState("asc"); - const [orderBy, setOrderBy] = React.useState("calories"); + const [orderBy, setOrderBy] = React.useState( + "name" + ); const [selected, setSelected] = React.useState([]); const [page, setPage] = React.useState(0); const [dense, setDense] = React.useState(true); @@ -64,7 +55,7 @@ export default function DatabaseTable() { const handleRequestSort = ( event: React.MouseEvent, - property: keyof Data + property: keyof T[number] & string ) => { const isAsc = orderBy === property && order === "asc"; setOrder(isAsc ? "desc" : "asc"); @@ -73,7 +64,7 @@ export default function DatabaseTable() { const handleSelectAllClick = (event: React.ChangeEvent) => { if (event.target.checked) { - const newSelected = rows.map((n) => n.id); + const newSelected = rows.map((n) => n[0]); setSelected(newSelected); return; } @@ -122,13 +113,13 @@ export default function DatabaseTable() { .slice() .sort(getComparator(order, orderBy)) .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage), - [order, orderBy, page, rowsPerPage] + [order, orderBy, page, rows, rowsPerPage] ); return ( - + {visibleRows.map((row, index) => { - const isItemSelected = isSelected(row.id); + const isItemSelected = isSelected(row[0]); const labelId = `enhanced-table-checkbox-${index}`; return ( handleClick(event, row.id)} + onClick={(event) => handleClick(event, row[0])} role="checkbox" aria-checked={isItemSelected} tabIndex={-1} - key={row.id} + key={row[0]} selected={isItemSelected} sx={{ cursor: "pointer" }} > @@ -174,12 +165,13 @@ export default function DatabaseTable() { scope="row" padding="none" > - {row.name} + {row[1]} - {row.calories} - {row.fat} - {row.carbs} - {row.protein} + {row.slice(2).map((cell, index) => ( + + {cell} + + ))} ); })} diff --git a/app/database/page.tsx b/app/database/page.tsx index a288599..f553838 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -28,6 +28,32 @@ function CustomTabPanel(props: TabPanelProps) { ); } +const data1: [number, string, number, number, number, number][] = [ + [1, "tag1", 1, 1, 2, 3], + [2, "tag2", 2, 2, 39, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], + [3, "tag3", 3, 3, 9, 1], +]; + export default function Page() { const [tabIndex, setTabIndex] = React.useState(0); @@ -39,15 +65,15 @@ export default function Page() { - {TabItems.map((item) => ( - + {TabItems.map((item, index) => ( + ))} {TabItems.map((item, index) => ( - - + + ))} From c82a956acf9f1d746dfe603de93b1b603a6839cb Mon Sep 17 00:00:00 2001 From: ziteh Date: Mon, 2 Sep 2024 19:56:47 +0800 Subject: [PATCH 11/28] feat: add basic tag database table --- app/database/page.tsx | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/app/database/page.tsx b/app/database/page.tsx index f553838..6917a0f 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { Box, Tabs, Tab } from "@mui/material"; import DatabaseTable from "./databaseTable"; +import { listTag } from "@/app/lib/tags"; const TabItems = ["Tag", "Item", "Folder"]; @@ -56,11 +57,39 @@ const data1: [number, string, number, number, number, number][] = [ export default function Page() { const [tabIndex, setTabIndex] = React.useState(0); + const [tags, setTags] = React.useState< + [number, string, string, string, string, string][] + >([]); const handleChange = (event: React.SyntheticEvent, newValue: number) => { setTabIndex(newValue); }; + React.useEffect(() => { + listTag(true, true, false).then((data) => { + const tagList: [number, string, string, string, string, string][] = + data.map((item) => { + const updateDate = item.updatedAt + ? new Date(item.updatedAt).toISOString() + : "N/A"; + + const createDate = item.createdAt + ? new Date(item.createdAt).toISOString() + : "N/A"; + + return [ + item.id, + item.name, + item.type, + item.parent ? "Has parent" : "No parent", + updateDate, + createDate, + ]; + }); + setTags(tagList); + }); + }, []); + return ( @@ -73,7 +102,7 @@ export default function Page() { {TabItems.map((item, index) => ( - + ))} From 42888bddedef5fdcf7317702a4f49e0b33ed5660 Mon Sep 17 00:00:00 2001 From: ziteh Date: Tue, 3 Sep 2024 20:36:34 +0800 Subject: [PATCH 12/28] feat: add item DB table --- .../databaseTable/dbTableHead/index.tsx | 50 +----- app/database/databaseTable/index.tsx | 6 +- app/database/databaseTable/types.ts | 10 +- app/database/page.tsx | 166 ++++++++++++------ app/lib/items.ts | 4 +- 5 files changed, 132 insertions(+), 104 deletions(-) diff --git a/app/database/databaseTable/dbTableHead/index.tsx b/app/database/databaseTable/dbTableHead/index.tsx index 4374e4b..bdf605a 100644 --- a/app/database/databaseTable/dbTableHead/index.tsx +++ b/app/database/databaseTable/dbTableHead/index.tsx @@ -9,53 +9,19 @@ import { } from "@mui/material"; import { Order, Data, HeadCell } from "@/app/database/databaseTable/types"; -const headCells: readonly HeadCell[] = [ - { - id: "name", - numeric: false, - disablePadding: true, - label: "Name", - }, - { - id: "calories", - numeric: true, - disablePadding: false, - label: "Type", - }, - { - id: "fat", - numeric: true, - disablePadding: false, - label: "Parent", - }, - { - id: "protein", - numeric: true, - disablePadding: false, - label: "Updated", - }, - { - id: "carbs", - numeric: true, - disablePadding: false, - label: "Created", - }, -]; - interface Props { + heads: HeadCell[]; numSelected: number; order: Order; - orderBy: string; + orderBy: number; rowCount: number; - onRequestSort: ( - event: React.MouseEvent, - property: keyof Data - ) => void; + onRequestSort: (event: React.MouseEvent, property: number) => void; onSelectAllClick: (event: React.ChangeEvent) => void; } export default function DbTableHead(props: Props) { const { + heads, onSelectAllClick, order, orderBy, @@ -64,7 +30,7 @@ export default function DbTableHead(props: Props) { onRequestSort, } = props; const createSortHandler = - (property: keyof Data) => (event: React.MouseEvent) => { + (property: number) => (event: React.MouseEvent) => { onRequestSort(event, property); }; @@ -82,11 +48,11 @@ export default function DbTableHead(props: Props) { }} /> - {headCells.map((headCell) => ( + {heads.map((headCell) => ( ( interface Props { rows: T[]; + heads: HeadCell[]; } export default function DatabaseTable( props: Props ) { - const { rows } = props; + const { rows, heads } = props; const [order, setOrder] = React.useState("asc"); const [orderBy, setOrderBy] = React.useState( @@ -127,6 +128,7 @@ export default function DatabaseTable( size={dense ? "small" : "medium"} > ([]); + const [tags, setTags] = React.useState([]); + const [items, setItems] = React.useState([]); const handleChange = (event: React.SyntheticEvent, newValue: number) => { setTabIndex(newValue); @@ -67,44 +96,69 @@ export default function Page() { React.useEffect(() => { listTag(true, true, false).then((data) => { - const tagList: [number, string, string, string, string, string][] = - data.map((item) => { - const updateDate = item.updatedAt - ? new Date(item.updatedAt).toISOString() - : "N/A"; - - const createDate = item.createdAt - ? new Date(item.createdAt).toISOString() - : "N/A"; - - return [ - item.id, - item.name, - item.type, - item.parent ? "Has parent" : "No parent", - updateDate, - createDate, - ]; - }); + const tagList: TagRow[] = data.map((item) => { + const updateDate = item.updatedAt + ? new Date(item.updatedAt).toISOString() + : "N/A"; + + const createDate = item.createdAt + ? new Date(item.createdAt).toISOString() + : "N/A"; + + return [ + item.id, + item.name, + item.type, + item.parent ? "Has parent" : "No parent", + updateDate, + createDate, + ]; + }); setTags(tagList); }); + + listItem().then((data) => { + const itemList: ItemRow[] = data.map((item) => { + const updateDate = item.updatedAt + ? new Date(item.updatedAt).toISOString() + : "N/A"; + + const createDate = item.createdAt + ? new Date(item.createdAt).toISOString() + : "N/A"; + + return [ + item.id, + item.name || "N/A", + item.path, + item.starred ? "Starred" : "", + updateDate, + createDate, + ]; + }); + setItems(itemList); + }); }, []); return ( - {TabItems.map((item, index) => ( - - ))} + + + - {TabItems.map((item, index) => ( - - - - ))} + + + + + + + + + ); } diff --git a/app/lib/items.ts b/app/lib/items.ts index 92e5e77..8c6ad9e 100644 --- a/app/lib/items.ts +++ b/app/lib/items.ts @@ -85,7 +85,7 @@ export async function getItem(id: number): Promise { } } -export async function listItem() { +export async function listItem(): Promise { try { const response = await fetch(apiUrl, { method: "GET", @@ -98,8 +98,10 @@ export async function listItem() { const data = await response.json(); console.debug("Item get:", data); + return data; } catch (error) { console.error("Error during fetch:", error); + return []; } } From e705db83d754a84dfe82c3469ff0c58aee2053f8 Mon Sep 17 00:00:00 2001 From: ziteh Date: Tue, 3 Sep 2024 21:42:10 +0800 Subject: [PATCH 13/28] refactor: move and rename Tag form dialog --- app/components/tag/Button.tsx | 24 ------------------- .../formDialog/tagFormDialog.tsx} | 0 2 files changed, 24 deletions(-) delete mode 100644 app/components/tag/Button.tsx rename app/{components/tag/FormDialog.tsx => database/formDialog/tagFormDialog.tsx} (100%) diff --git a/app/components/tag/Button.tsx b/app/components/tag/Button.tsx deleted file mode 100644 index 94cbde3..0000000 --- a/app/components/tag/Button.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client"; - -import React from "react"; -import FormDialog from "./FormDialog"; -import { Button } from "@mui/material"; - -export default function TagButton() { - const [open, setOpen] = React.useState(false); - - const handleClick = () => { - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - return ( -
- - -
- ); -} diff --git a/app/components/tag/FormDialog.tsx b/app/database/formDialog/tagFormDialog.tsx similarity index 100% rename from app/components/tag/FormDialog.tsx rename to app/database/formDialog/tagFormDialog.tsx From 95ea51b020058bba68e5fa5a64e1474c65c96031 Mon Sep 17 00:00:00 2001 From: ziteh Date: Tue, 3 Sep 2024 21:48:38 +0800 Subject: [PATCH 14/28] feat: add New Tag button to database page --- app/database/formDialog/tagFormDialog.tsx | 7 +++++-- app/database/page.tsx | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/database/formDialog/tagFormDialog.tsx b/app/database/formDialog/tagFormDialog.tsx index e3ba286..e4b731c 100644 --- a/app/database/formDialog/tagFormDialog.tsx +++ b/app/database/formDialog/tagFormDialog.tsx @@ -22,7 +22,7 @@ interface Props { onClose: () => void; } -export default function FormDialog(props: Props) { +export default function TagFormDialog(props: Props) { const { open, data, onClose } = props; const handleClose = () => { @@ -58,7 +58,10 @@ export default function FormDialog(props: Props) { - } label="Star" /> + } + label="Star" + /> ([]); const [items, setItems] = React.useState([]); + const [tagFormOpen, setTagFormOpen] = React.useState(false); + + const handleTagFormOpen = () => { + setTagFormOpen(true); + }; + + const handleTagFormClose = () => { + setTagFormOpen(false); + }; + const handleChange = (event: React.SyntheticEvent, newValue: number) => { setTabIndex(newValue); }; @@ -151,11 +163,15 @@ export default function Page() {
+ + + + From b1b120a7a8e9e810191c4ff7e2a08b2d36d85d9d Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 07:30:46 +0800 Subject: [PATCH 15/28] feat: add parent select for tag form dialog --- app/database/formDialog/tagFormDialog.tsx | 19 +++++++++++++++++-- app/database/page.tsx | 23 +++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/app/database/formDialog/tagFormDialog.tsx b/app/database/formDialog/tagFormDialog.tsx index e4b731c..d76a5d4 100644 --- a/app/database/formDialog/tagFormDialog.tsx +++ b/app/database/formDialog/tagFormDialog.tsx @@ -17,13 +17,14 @@ import { import { createTag } from "@/app/lib/tags"; interface Props { - open: boolean; + existingTags: Tag[]; data?: Tag; + open: boolean; onClose: () => void; } export default function TagFormDialog(props: Props) { - const { open, data, onClose } = props; + const { existingTags, data, open, onClose } = props; const handleClose = () => { onClose(); @@ -85,6 +86,20 @@ export default function TagFormDialog(props: Props) { Category + + + ([]); - const [items, setItems] = React.useState([]); + const [tagRows, setTagRows] = React.useState([]); + const [itemRows, setItemRows] = React.useState([]); + + const [tags, setTags] = React.useState([]); + const [items, setItems] = React.useState([]); const [tagFormOpen, setTagFormOpen] = React.useState(false); @@ -108,6 +111,8 @@ export default function Page() { React.useEffect(() => { listTag(true, true, false).then((data) => { + setTags(data); + const tagList: TagRow[] = data.map((item) => { const updateDate = item.updatedAt ? new Date(item.updatedAt).toISOString() @@ -126,10 +131,12 @@ export default function Page() { createDate, ]; }); - setTags(tagList); + setTagRows(tagList); }); listItem().then((data) => { + setItems(data); + const itemList: ItemRow[] = data.map((item) => { const updateDate = item.updatedAt ? new Date(item.updatedAt).toISOString() @@ -148,7 +155,7 @@ export default function Page() { createDate, ]; }); - setItems(itemList); + setItemRows(itemList); }); }, []); @@ -164,16 +171,16 @@ export default function Page() { - - + + - + - + ); From 1e7fca86818e160b4ed91fe26a09d78d4c19c16f Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 07:42:21 +0800 Subject: [PATCH 16/28] feat: create tag relation on create new tag --- app/database/formDialog/tagFormDialog.tsx | 8 +++++++- app/lib/tags.ts | 8 +++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/database/formDialog/tagFormDialog.tsx b/app/database/formDialog/tagFormDialog.tsx index d76a5d4..82317ae 100644 --- a/app/database/formDialog/tagFormDialog.tsx +++ b/app/database/formDialog/tagFormDialog.tsx @@ -15,6 +15,7 @@ import { TextField, } from "@mui/material"; import { createTag } from "@/app/lib/tags"; +import { createTagRelation } from "@/app/lib/tagRelation"; interface Props { existingTags: Tag[]; @@ -41,7 +42,12 @@ export default function TagFormDialog(props: Props) { const starred = formJson.starred === "on"; const textColor = formJson.textColor || undefined; const backColor = formJson.backColor || undefined; - await createTag(name, type, starred, textColor, backColor); + const response = await createTag(name, type, starred, textColor, backColor); + + const parentId = formJson.parent; + if (parentId && response) { + await createTagRelation(Number(parentId), response.id); + } handleClose(); }; diff --git a/app/lib/tags.ts b/app/lib/tags.ts index 7b47562..92c6fcf 100644 --- a/app/lib/tags.ts +++ b/app/lib/tags.ts @@ -8,7 +8,7 @@ export async function createTag( starred?: boolean, textColor?: string, backColor?: string -): Promise { +): Promise { try { const response = await fetch(apiUrl, { method: "POST", @@ -24,19 +24,21 @@ export async function createTag( if (!response.ok) { console.error("Network response was not ok:", response.statusText); - return; + return null; } const contentType = response.headers.get("Content-Type"); if (!contentType?.includes("application/json")) { console.error("Expected JSON, got:", contentType); - return; + return null; } const data = await response.json(); console.debug("Response data:", data); + return data; } catch (error) { console.error("Error during fetch:", error); + return null; } } From 860dbed9c0231c0cc4c31582f6258ff9bf893093 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 20:18:55 +0800 Subject: [PATCH 17/28] feat: update item of tag form dialog --- app/database/formDialog/tagFormDialog.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/database/formDialog/tagFormDialog.tsx b/app/database/formDialog/tagFormDialog.tsx index 82317ae..e401bb9 100644 --- a/app/database/formDialog/tagFormDialog.tsx +++ b/app/database/formDialog/tagFormDialog.tsx @@ -85,6 +85,7 @@ export default function TagFormDialog(props: Props) { labelId="type-label" label="Type" name="type" + defaultValue={TagType.Normal} required fullWidth > @@ -99,6 +100,7 @@ export default function TagFormDialog(props: Props) { name="parent" fullWidth > + (None) {existingTags.map((tag) => ( {tag.name} From d740c0549eb710dce5557cbd8042c563524b3f60 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 20:37:41 +0800 Subject: [PATCH 18/28] feat: disable auto complete for tag form dialog --- app/database/formDialog/tagFormDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/database/formDialog/tagFormDialog.tsx b/app/database/formDialog/tagFormDialog.tsx index e401bb9..0502287 100644 --- a/app/database/formDialog/tagFormDialog.tsx +++ b/app/database/formDialog/tagFormDialog.tsx @@ -78,6 +78,7 @@ export default function TagFormDialog(props: Props) { required autoFocus fullWidth + autoComplete="off" /> From c406be9af2993a94c6dfd50e378b51a72e230526 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 20:54:50 +0800 Subject: [PATCH 19/28] feat: add folder API lib --- app/lib/db/types.ts | 6 +-- app/lib/folders.ts | 127 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 app/lib/folders.ts diff --git a/app/lib/db/types.ts b/app/lib/db/types.ts index 9d3a865..98ebfef 100644 --- a/app/lib/db/types.ts +++ b/app/lib/db/types.ts @@ -22,8 +22,8 @@ export interface Tag { export interface Item { id: number; path: string; - basePath?: Path; - basePathId: number; + folder?: Folder; + folderId: number; name?: string; starred: boolean; createdAt?: Date; @@ -47,7 +47,7 @@ export interface ItemRelation { item: Item; } -export interface Path { +export interface Folder { id: number; name: string; path: string; diff --git a/app/lib/folders.ts b/app/lib/folders.ts new file mode 100644 index 0000000..0da6e0c --- /dev/null +++ b/app/lib/folders.ts @@ -0,0 +1,127 @@ +import { Folder } from "@/app/lib/db/types"; + +const apiUrl = "http://localhost:3140/api/folders"; // TODO + +export async function createFolder( + name: string, + path: string +): Promise { + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name, path }), + }); + + if (!response.ok) { + console.error("Network response was not ok:", response.statusText); + return null; + } + + const contentType = response.headers.get("Content-Type"); + if (!contentType?.includes("application/json")) { + console.error("Expected JSON, got:", contentType); + return null; + } + + const data: Folder = await response.json(); + console.debug("Response data:", data); + return data; + } catch (error) { + console.error("Error during fetch:", error); + return null; + } +} + +export async function updateFolder(id: number, name: string, path: string) { + try { + const response = await fetch(apiUrl, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id, name, path }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.debug("Folder update:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} + +export async function getFolder( + id: number, + includeItems: boolean +): Promise { + let params: string = ""; + if (includeItems) { + params = "items"; + } + const url = `${apiUrl}/${id}?include=${params}`; + + try { + const response = await fetch(url, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data: Folder = await response.json(); + console.debug("Folder get:", data); + return data; + } catch (error) { + console.error("Error during fetch:", error); + return null; + } +} + +export async function listTag(includeItems: boolean): Promise { + let params: string = ""; + if (includeItems) { + params = "items"; + } + const url = `${apiUrl}?include=${params}`; + + try { + const response = await fetch(url, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data: Folder[] = await response.json(); + console.debug("Folder get:", data); + return data; + } catch (error) { + console.error("Error during fetch:", error); + return []; + } +} + +export async function removeFolder(id: number): Promise { + try { + const response = await fetch(apiUrl, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.debug("Folder deleted:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} From 0c0d30ef4ccb795fda3f5ad12151f64945130e76 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 21:04:22 +0800 Subject: [PATCH 20/28] feat: add path slash post-process --- app/lib/folders.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/lib/folders.ts b/app/lib/folders.ts index 0da6e0c..912f4e4 100644 --- a/app/lib/folders.ts +++ b/app/lib/folders.ts @@ -6,6 +6,11 @@ export async function createFolder( name: string, path: string ): Promise { + path = path.replace(/\\/g, "/"); // Replace backslashes with forward + if (!path.endsWith("/")) { + path += "/"; // Always add trailing slash + } + try { const response = await fetch(apiUrl, { method: "POST", From afe99c19c8bea8d14f9c74b971f9ae8897317673 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 21:07:39 +0800 Subject: [PATCH 21/28] feat: add folder form dialog --- app/database/formDialog/folderFormDialog.tsx | 84 ++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/database/formDialog/folderFormDialog.tsx diff --git a/app/database/formDialog/folderFormDialog.tsx b/app/database/formDialog/folderFormDialog.tsx new file mode 100644 index 0000000..29aa44a --- /dev/null +++ b/app/database/formDialog/folderFormDialog.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, + Grid, + Switch, + TextField, +} from "@mui/material"; +import { Folder } from "@/app/lib/db/types"; +import { createFolder } from "@/app/lib/folders"; + +interface Props { + data?: Folder; + open: boolean; + onClose: () => void; +} + +export default function FolderFormDialog(props: Props) { + const { data, open, onClose } = props; + + const handleClose = () => { + onClose(); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const formJson = Object.fromEntries((formData as any).entries()); + console.debug(formJson); + + const name = formJson.name; + const path = formJson.path; + const response = await createFolder(name, path); + + handleClose(); + }; + + return ( + + Folder + + + + + + + + + + + + + + + + ); +} From c93897b0c4ecae7f2974836d08ef29fec8d41fa8 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 21:21:17 +0800 Subject: [PATCH 22/28] feat: add folder and item form dialog --- app/database/formDialog/folderFormDialog.tsx | 2 - app/database/formDialog/itemFormDialog.tsx | 109 +++++++++++++++++++ app/database/page.tsx | 25 ++++- app/lib/folders.ts | 2 +- app/lib/items.ts | 8 +- 5 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 app/database/formDialog/itemFormDialog.tsx diff --git a/app/database/formDialog/folderFormDialog.tsx b/app/database/formDialog/folderFormDialog.tsx index 29aa44a..37a99df 100644 --- a/app/database/formDialog/folderFormDialog.tsx +++ b/app/database/formDialog/folderFormDialog.tsx @@ -6,9 +6,7 @@ import { DialogActions, DialogContent, DialogTitle, - FormControlLabel, Grid, - Switch, TextField, } from "@mui/material"; import { Folder } from "@/app/lib/db/types"; diff --git a/app/database/formDialog/itemFormDialog.tsx b/app/database/formDialog/itemFormDialog.tsx new file mode 100644 index 0000000..848cad4 --- /dev/null +++ b/app/database/formDialog/itemFormDialog.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, + Grid, + MenuItem, + Select, + Switch, + TextField, +} from "@mui/material"; +import { Item, ItemRelation, Folder } from "@/app/lib/db/types"; +import { createItem } from "@/app/lib/items"; +import { createFolder } from "@/app/lib/folders"; + +interface Props { + data?: Item; + folders: Folder[]; + open: boolean; + onClose: () => void; +} + +export default function FolderFormDialog(props: Props) { + const { data, folders, open, onClose } = props; + + const handleClose = () => { + onClose(); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const formJson = Object.fromEntries((formData as any).entries()); + console.debug(formJson); + + const folderId = Number(formJson.folder); + const path = formJson.path; + const name = formJson.name || undefined; + const starred = formJson.starred === "on"; + const response = await createItem(path, folderId, name, starred); + + handleClose(); + }; + + return ( + + Item + + + + } + label="Star" + /> + + + + + + + + + + + + + + + + + + ); +} diff --git a/app/database/page.tsx b/app/database/page.tsx index 87f7322..b08c94e 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -5,8 +5,11 @@ import { Box, Tabs, Tab, Button } from "@mui/material"; import DatabaseTable from "./databaseTable"; import { listTag } from "@/app/lib/tags"; import { listItem } from "@/app/lib/items"; +import { listFolder } from "@/app/lib/folders"; import { HeadCell, TagRow, ItemRow } from "./databaseTable/types"; import TagFormDialog from "./formDialog/tagFormDialog"; +import ItemFormDialog from "./formDialog/itemFormDialog"; +import { Folder, Item, Tag } from "../lib/db/types"; interface TabPanelProps { children?: React.ReactNode; @@ -94,8 +97,10 @@ export default function Page() { const [tags, setTags] = React.useState([]); const [items, setItems] = React.useState([]); + const [folders, setFolders] = React.useState([]); const [tagFormOpen, setTagFormOpen] = React.useState(false); + const [itemFormOpen, setItemFormOpen] = React.useState(false); const handleTagFormOpen = () => { setTagFormOpen(true); @@ -105,6 +110,14 @@ export default function Page() { setTagFormOpen(false); }; + const handleItemFormOpen = () => { + setItemFormOpen(true); + }; + + const handleItemFormClose = () => { + setItemFormOpen(false); + }; + const handleChange = (event: React.SyntheticEvent, newValue: number) => { setTabIndex(newValue); }; @@ -157,6 +170,10 @@ export default function Page() { }); setItemRows(itemList); }); + + listFolder(false).then((data) => { + setFolders(data); + }); }, []); return ( @@ -171,11 +188,17 @@ export default function Page() { - + + + diff --git a/app/lib/folders.ts b/app/lib/folders.ts index 912f4e4..3ad50b6 100644 --- a/app/lib/folders.ts +++ b/app/lib/folders.ts @@ -86,7 +86,7 @@ export async function getFolder( } } -export async function listTag(includeItems: boolean): Promise { +export async function listFolder(includeItems: boolean): Promise { let params: string = ""; if (includeItems) { params = "items"; diff --git a/app/lib/items.ts b/app/lib/items.ts index 8c6ad9e..d9278f3 100644 --- a/app/lib/items.ts +++ b/app/lib/items.ts @@ -4,6 +4,7 @@ const apiUrl = "/api/items"; export async function createItem( path: string, + folderId: number, name?: string, starred?: boolean ): Promise { @@ -12,9 +13,10 @@ export async function createItem( method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - path: path, - name: name, - starred: starred, + path, + folderId, + name, + starred, }), }); From 6e0ef443a94cfee95b1075066c9105f1367c4692 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 21:38:25 +0800 Subject: [PATCH 23/28] feat: add folder DB table items --- app/database/databaseTable/types.ts | 1 + app/database/page.tsx | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/database/databaseTable/types.ts b/app/database/databaseTable/types.ts index 13d856f..5ecd0c4 100644 --- a/app/database/databaseTable/types.ts +++ b/app/database/databaseTable/types.ts @@ -37,3 +37,4 @@ export type Order = "asc" | "desc"; export type TagRow = [number, string, string, string, string, string]; export type ItemRow = [number, string, string, string, string, string]; +export type FolderRow = [number, string, string]; diff --git a/app/database/page.tsx b/app/database/page.tsx index b08c94e..710b6dc 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -6,7 +6,7 @@ import DatabaseTable from "./databaseTable"; import { listTag } from "@/app/lib/tags"; import { listItem } from "@/app/lib/items"; import { listFolder } from "@/app/lib/folders"; -import { HeadCell, TagRow, ItemRow } from "./databaseTable/types"; +import { HeadCell, TagRow, ItemRow, FolderRow } from "./databaseTable/types"; import TagFormDialog from "./formDialog/tagFormDialog"; import ItemFormDialog from "./formDialog/itemFormDialog"; import { Folder, Item, Tag } from "../lib/db/types"; @@ -89,11 +89,25 @@ const itemHeader: HeadCell[] = [ }, ]; +const folderHeader: HeadCell[] = [ + { + id: 0, + label: "Name", + align: "right", + }, + { + id: 1, + label: "Path", + align: "right", + }, +]; + export default function Page() { const [tabIndex, setTabIndex] = React.useState(0); const [tagRows, setTagRows] = React.useState([]); const [itemRows, setItemRows] = React.useState([]); + const [folderRows, setFolderRows] = React.useState([]); const [tags, setTags] = React.useState([]); const [items, setItems] = React.useState([]); @@ -173,6 +187,11 @@ export default function Page() { listFolder(false).then((data) => { setFolders(data); + + const list: FolderRow[] = data.map((item) => { + return [item.id, item.name, item.path]; + }); + setFolderRows(list); }); }, []); @@ -198,12 +217,16 @@ export default function Page() { - + - + ); From d5ff6f1ed6b86cfd89bbc0ca727921f28d10f287 Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 21:47:58 +0800 Subject: [PATCH 24/28] feat: DB add createdAt and updateAt field for Folder --- app/database/databaseTable/types.ts | 2 +- app/database/page.tsx | 28 +++++++++++++++++++++++----- app/lib/db/types.ts | 2 ++ prisma/schema.prisma | 3 +++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/database/databaseTable/types.ts b/app/database/databaseTable/types.ts index 5ecd0c4..7a315bc 100644 --- a/app/database/databaseTable/types.ts +++ b/app/database/databaseTable/types.ts @@ -37,4 +37,4 @@ export type Order = "asc" | "desc"; export type TagRow = [number, string, string, string, string, string]; export type ItemRow = [number, string, string, string, string, string]; -export type FolderRow = [number, string, string]; +export type FolderRow = [number, string, string, string, string]; diff --git a/app/database/page.tsx b/app/database/page.tsx index 710b6dc..7c3c5bd 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -100,6 +100,16 @@ const folderHeader: HeadCell[] = [ label: "Path", align: "right", }, + { + id: 2, + label: "Updated", + align: "right", + }, + { + id: 3, + label: "Created", + align: "right", + }, ]; export default function Page() { @@ -140,7 +150,7 @@ export default function Page() { listTag(true, true, false).then((data) => { setTags(data); - const tagList: TagRow[] = data.map((item) => { + const list: TagRow[] = data.map((item) => { const updateDate = item.updatedAt ? new Date(item.updatedAt).toISOString() : "N/A"; @@ -158,13 +168,13 @@ export default function Page() { createDate, ]; }); - setTagRows(tagList); + setTagRows(list); }); listItem().then((data) => { setItems(data); - const itemList: ItemRow[] = data.map((item) => { + const list: ItemRow[] = data.map((item) => { const updateDate = item.updatedAt ? new Date(item.updatedAt).toISOString() : "N/A"; @@ -182,14 +192,22 @@ export default function Page() { createDate, ]; }); - setItemRows(itemList); + setItemRows(list); }); listFolder(false).then((data) => { setFolders(data); const list: FolderRow[] = data.map((item) => { - return [item.id, item.name, item.path]; + const updateDate = item.updatedAt + ? new Date(item.updatedAt).toISOString() + : "N/A"; + + const createDate = item.createdAt + ? new Date(item.createdAt).toISOString() + : "N/A"; + + return [item.id, item.name, item.path, updateDate, createDate]; }); setFolderRows(list); }); diff --git a/app/lib/db/types.ts b/app/lib/db/types.ts index 98ebfef..04e75d0 100644 --- a/app/lib/db/types.ts +++ b/app/lib/db/types.ts @@ -52,4 +52,6 @@ export interface Folder { name: string; path: string; items?: Item[]; + createdAt?: Date; + updatedAt?: Date; } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 85a2517..7c86d80 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -64,4 +64,7 @@ model Folder { name String @unique path String @unique items Item[] + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } From 0a1263949fe4bb4a94179be4b2fb3d95652962dd Mon Sep 17 00:00:00 2001 From: ziteh Date: Wed, 4 Sep 2024 21:53:22 +0800 Subject: [PATCH 25/28] feat: add folder form dialog to page --- app/database/formDialog/itemFormDialog.tsx | 2 +- app/database/page.tsx | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/database/formDialog/itemFormDialog.tsx b/app/database/formDialog/itemFormDialog.tsx index 848cad4..fa8d0c0 100644 --- a/app/database/formDialog/itemFormDialog.tsx +++ b/app/database/formDialog/itemFormDialog.tsx @@ -24,7 +24,7 @@ interface Props { onClose: () => void; } -export default function FolderFormDialog(props: Props) { +export default function ItemFormDialog(props: Props) { const { data, folders, open, onClose } = props; const handleClose = () => { diff --git a/app/database/page.tsx b/app/database/page.tsx index 7c3c5bd..485d965 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -9,6 +9,7 @@ import { listFolder } from "@/app/lib/folders"; import { HeadCell, TagRow, ItemRow, FolderRow } from "./databaseTable/types"; import TagFormDialog from "./formDialog/tagFormDialog"; import ItemFormDialog from "./formDialog/itemFormDialog"; +import FolderFormDialog from "./formDialog/folderFormDialog"; import { Folder, Item, Tag } from "../lib/db/types"; interface TabPanelProps { @@ -125,6 +126,7 @@ export default function Page() { const [tagFormOpen, setTagFormOpen] = React.useState(false); const [itemFormOpen, setItemFormOpen] = React.useState(false); + const [folderFormOpen, setFolderFormOpen] = React.useState(false); const handleTagFormOpen = () => { setTagFormOpen(true); @@ -142,6 +144,14 @@ export default function Page() { setItemFormOpen(false); }; + const handleFolderFormOpen = () => { + setFolderFormOpen(true); + }; + + const handleFolderFormClose = () => { + setFolderFormOpen(false); + }; + const handleChange = (event: React.SyntheticEvent, newValue: number) => { setTabIndex(newValue); }; @@ -244,6 +254,11 @@ export default function Page() { + + From e61264775dd26b3802fee74870319db0a272e8a0 Mon Sep 17 00:00:00 2001 From: ziteh Date: Thu, 5 Sep 2024 20:32:19 +0800 Subject: [PATCH 26/28] feat: create item relation when create item --- app/database/formDialog/itemFormDialog.tsx | 71 ++++++++++++- app/database/page.tsx | 1 + app/lib/itemRelation.ts | 112 +++++++++++++++++++++ app/lib/items.ts | 8 +- 4 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 app/lib/itemRelation.ts diff --git a/app/database/formDialog/itemFormDialog.tsx b/app/database/formDialog/itemFormDialog.tsx index fa8d0c0..b9ee01e 100644 --- a/app/database/formDialog/itemFormDialog.tsx +++ b/app/database/formDialog/itemFormDialog.tsx @@ -1,7 +1,9 @@ "use client"; import { + Box, Button, + Chip, Dialog, DialogActions, DialogContent, @@ -9,23 +11,43 @@ import { FormControlLabel, Grid, MenuItem, + OutlinedInput, Select, + SelectChangeEvent, Switch, TextField, } from "@mui/material"; -import { Item, ItemRelation, Folder } from "@/app/lib/db/types"; +import { Item, ItemRelation, Folder, Tag } from "@/app/lib/db/types"; import { createItem } from "@/app/lib/items"; import { createFolder } from "@/app/lib/folders"; +import theme from "@/app/lib/theme"; +import React from "react"; +import { createItemRelation } from "@/app/lib/itemRelation"; + +const SEP = "\t"; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + }, + }, +}; interface Props { data?: Item; folders: Folder[]; + tags: Tag[]; open: boolean; onClose: () => void; } export default function ItemFormDialog(props: Props) { - const { data, folders, open, onClose } = props; + const { data, folders, tags, open, onClose } = props; + const [selectedTags, setSelectedTags] = React.useState([]); const handleClose = () => { onClose(); @@ -43,9 +65,26 @@ export default function ItemFormDialog(props: Props) { const starred = formJson.starred === "on"; const response = await createItem(path, folderId, name, starred); + if (response !== null && selectedTags.length > 0) { + for (const tag of selectedTags) { + const tagId = Number(tag.split(SEP)[0]); + await createItemRelation(tagId, response.id); + } + } + handleClose(); }; + const handleChange = (event: SelectChangeEvent) => { + const { + target: { value }, + } = event; + setSelectedTags( + // On autofill we get a stringified value. + typeof value === "string" ? value.split(",") : value + ); + }; + return ( + + + diff --git a/app/database/page.tsx b/app/database/page.tsx index 485d965..d4a2b48 100644 --- a/app/database/page.tsx +++ b/app/database/page.tsx @@ -247,6 +247,7 @@ export default function Page() { diff --git a/app/lib/itemRelation.ts b/app/lib/itemRelation.ts new file mode 100644 index 0000000..d4b8366 --- /dev/null +++ b/app/lib/itemRelation.ts @@ -0,0 +1,112 @@ +const apiUrl = "http://localhost:3140/api/items/relation"; // TODO + +export async function createItemRelation( + tagId: number, + itemId: number +): Promise { + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ tagId, itemId }), + }); + + if (!response.ok) { + console.error("Network response was not ok:", response.statusText); + return; + } + + const contentType = response.headers.get("Content-Type"); + if (!contentType?.includes("application/json")) { + console.error("Expected JSON, got:", contentType); + return; + } + + const data = await response.json(); + console.debug("Response data:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} + +export async function updateItemRelation( + id: number, + tagId: number, + itemId: number +) { + try { + const response = await fetch(apiUrl, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + id, + tagId, + itemId, + }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.debug("Item relation update:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} + +export async function getTagRelation(id: number) { + try { + const response = await fetch(`${apiUrl}/${id}`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.debug("Item relation get:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} + +export async function listTagRelation() { + try { + const response = await fetch(apiUrl, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.debug("Item relation get:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} + +export async function removeTagRelation(id: number): Promise { + try { + const response = await fetch(apiUrl, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.debug("Item relation deleted:", data); + } catch (error) { + console.error("Error during fetch:", error); + } +} diff --git a/app/lib/items.ts b/app/lib/items.ts index d9278f3..05f3510 100644 --- a/app/lib/items.ts +++ b/app/lib/items.ts @@ -7,7 +7,7 @@ export async function createItem( folderId: number, name?: string, starred?: boolean -): Promise { +): Promise { try { const response = await fetch(apiUrl, { method: "POST", @@ -22,19 +22,21 @@ export async function createItem( if (!response.ok) { console.error("Network response was not ok:", response.statusText); - return; + return null; } const contentType = response.headers.get("Content-Type"); if (!contentType?.includes("application/json")) { console.error("Expected JSON, got:", contentType); - return; + return null; } const data = await response.json(); console.debug("Response data:", data); + return data; } catch (error) { console.error("Error during fetch:", error); + return null; } } From cf811b9da471fe7e7ac160b1e0b5d7e7f5ca990c Mon Sep 17 00:00:00 2001 From: ziteh Date: Thu, 5 Sep 2024 21:08:21 +0800 Subject: [PATCH 27/28] feat: add icon svg --- app/icon.svg | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 app/icon.svg diff --git a/app/icon.svg b/app/icon.svg new file mode 100644 index 0000000..595817d --- /dev/null +++ b/app/icon.svg @@ -0,0 +1,43 @@ + + + + + Hie-icon + + + + + + + + + + Hie-icon + + + + From 6896e206fe2ae6c6855d98de4ad85634107a3068 Mon Sep 17 00:00:00 2001 From: ziteh Date: Thu, 5 Sep 2024 21:23:15 +0800 Subject: [PATCH 28/28] feat: add normalization for Folder and Item path --- app/api/folders/route.ts | 16 ++++++++++++++-- app/api/items/route.ts | 18 +++++++++--------- app/lib/folders.ts | 5 ----- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/api/folders/route.ts b/app/api/folders/route.ts index 62b0fe4..2ebfc72 100644 --- a/app/api/folders/route.ts +++ b/app/api/folders/route.ts @@ -7,8 +7,14 @@ export async function POST(request: Request) { const { name, path } = await request.json(); console.debug("Received data:", { name, path }); + // Normalization + let fmtPath = path.replace(/\\/g, "/"); // Replace backslashes with forward + if (!fmtPath.endsWith("/")) { + fmtPath += "/"; // Always add trailing slash + } + const created = await prisma.folder.create({ - data: { name, path }, + data: { name, path: fmtPath }, }); return NextResponse.json(created); @@ -25,9 +31,15 @@ export async function PATCH(request: Request) { try { const { id, name, path } = await request.json(); + // Normalization + let fmtPath = path.replace(/\\/g, "/"); // Replace backslashes with forward + if (!fmtPath.endsWith("/")) { + fmtPath += "/"; // Always add trailing slash + } + const updated = await prisma.folder.update({ where: { id }, - data: { name, path }, + data: { name, path: fmtPath }, }); return NextResponse.json(updated); diff --git a/app/api/items/route.ts b/app/api/items/route.ts index 21c83fc..c8d9cbb 100644 --- a/app/api/items/route.ts +++ b/app/api/items/route.ts @@ -6,8 +6,11 @@ export async function POST(request: Request) { const { path, folderId, name, starred } = await request.json(); console.debug("Received data:", { path, name, starred }); + // Normalization + const fmtPath = path.replace(/\\/g, "/"); // Replace backslashes with forward + const item = await prisma.item.create({ - data: { path, folderId, name, starred }, + data: { path: fmtPath, folderId, name, starred }, }); return NextResponse.json(item); @@ -19,17 +22,14 @@ export async function POST(request: Request) { export async function PATCH(request: Request) { try { - const { - id, - basePathId: folderId, - path, - name, - starred, - } = await request.json(); + const { id, folderId, path, name, starred } = await request.json(); + + // Normalization + const fmtPath = path.replace(/\\/g, "/"); // Replace backslashes with forward const item = await prisma.item.update({ where: { id }, - data: { path, folderId, name, starred }, + data: { path: fmtPath, folderId, name, starred }, }); return NextResponse.json(item); diff --git a/app/lib/folders.ts b/app/lib/folders.ts index 3ad50b6..6cae394 100644 --- a/app/lib/folders.ts +++ b/app/lib/folders.ts @@ -6,11 +6,6 @@ export async function createFolder( name: string, path: string ): Promise { - path = path.replace(/\\/g, "/"); // Replace backslashes with forward - if (!path.endsWith("/")) { - path += "/"; // Always add trailing slash - } - try { const response = await fetch(apiUrl, { method: "POST",