Rework #6
					 11 changed files with 230 additions and 178 deletions
				
			
		| 
						 | 
				
			
			@ -1,19 +1,9 @@
 | 
			
		|||
import { Route, Switch, Redirect } from "wouter";
 | 
			
		||||
import { useTernaryDarkMode } from "usehooks-ts";
 | 
			
		||||
import { useEffect } from "react";
 | 
			
		||||
 | 
			
		||||
import './App.css'
 | 
			
		||||
 | 
			
		||||
// import {
 | 
			
		||||
// 	// DifferencePage,
 | 
			
		||||
// 	// DocumentAbout,
 | 
			
		||||
// 	// Gallery,
 | 
			
		||||
// 	// LoginPage,
 | 
			
		||||
// 	// NotFoundPage,
 | 
			
		||||
// 	// ProfilePage,
 | 
			
		||||
// 	// ReaderPage,
 | 
			
		||||
// 	// SettingPage,
 | 
			
		||||
// 	// TagsPage,
 | 
			
		||||
// } from "./page/mod";
 | 
			
		||||
 | 
			
		||||
import { TooltipProvider } from "./components/ui/tooltip.tsx";
 | 
			
		||||
import Layout from "./components/layout/layout.tsx";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +16,17 @@ import SettingPage from "@/page/settingPage.tsx";
 | 
			
		|||
import ComicPage from "@/page/reader/comicPage.tsx";
 | 
			
		||||
 | 
			
		||||
const App = () => {
 | 
			
		||||
	const { isDarkMode } = useTernaryDarkMode();
 | 
			
		||||
	
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (isDarkMode) {
 | 
			
		||||
            document.body.classList.add("dark");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            document.body.classList.remove("dark");
 | 
			
		||||
        }
 | 
			
		||||
    }, [isDarkMode]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TooltipProvider>
 | 
			
		||||
			<Layout>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										25
									
								
								packages/client/src/components/Spinner.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/client/src/components/Spinner.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
import React from "react";
 | 
			
		||||
 | 
			
		||||
export function Spinner(props: { className?: string; }) {
 | 
			
		||||
    const chars = ["⠋",
 | 
			
		||||
        "⠙",
 | 
			
		||||
        "⠹",
 | 
			
		||||
        "⠸",
 | 
			
		||||
        "⠼",
 | 
			
		||||
        "⠴",
 | 
			
		||||
        "⠦",
 | 
			
		||||
        "⠧",
 | 
			
		||||
        "⠇",
 | 
			
		||||
        "⠏"
 | 
			
		||||
    ];
 | 
			
		||||
    const [index, setIndex] = React.useState(0);
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        const interval = setInterval(() => {
 | 
			
		||||
            setIndex((index + 1) % chars.length);
 | 
			
		||||
        }, 80);
 | 
			
		||||
        return () => clearInterval(interval);
 | 
			
		||||
        // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
    }, [index]);
 | 
			
		||||
 | 
			
		||||
    return <span className={props.className}>{chars[index]}</span>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								packages/client/src/hook/useSearchGallery.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								packages/client/src/hook/useSearchGallery.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
import useSWRInifinite from "swr/infinite";
 | 
			
		||||
import type { Document } from "dbtype/api";
 | 
			
		||||
import { fetcher } from "./fetcher";
 | 
			
		||||
 | 
			
		||||
interface SearchParams {
 | 
			
		||||
    word?: string;
 | 
			
		||||
    tags?: string;
 | 
			
		||||
    limit?: number;
 | 
			
		||||
    cursor?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useSearchGallery({
 | 
			
		||||
    word, tags, limit, cursor,
 | 
			
		||||
}: SearchParams) {
 | 
			
		||||
    
 | 
			
		||||
    return useSWRInifinite<
 | 
			
		||||
    {
 | 
			
		||||
        data: Document[];
 | 
			
		||||
        nextCursor: number | null;
 | 
			
		||||
        hasMore: boolean;
 | 
			
		||||
    }
 | 
			
		||||
    >((index, previous) => {
 | 
			
		||||
        if (!previous && index > 0) return null;
 | 
			
		||||
        if (previous && !previous.hasMore) return null;
 | 
			
		||||
        const search = new URLSearchParams();
 | 
			
		||||
        if (word) search.set("word", word);
 | 
			
		||||
        if (tags) search.set("allow_tag", tags);
 | 
			
		||||
        if (limit) search.set("limit", limit.toString());
 | 
			
		||||
        if (cursor) search.set("cursor", cursor.toString());
 | 
			
		||||
        if (index === 0) {
 | 
			
		||||
            return `/api/doc/search?${search.toString()}`;
 | 
			
		||||
        }
 | 
			
		||||
        if (!previous || !previous.data) return null;
 | 
			
		||||
        const last = previous.data[previous.data.length - 1];
 | 
			
		||||
        search.set("cursor", last.id.toString());
 | 
			
		||||
        return `/api/doc/search?${search.toString()}`;
 | 
			
		||||
    }, async (url) => {
 | 
			
		||||
        const res = await fetcher(url);
 | 
			
		||||
        return {
 | 
			
		||||
            data: res,
 | 
			
		||||
            nextCursor: res.length === 0 ? null : res[res.length - 1].id,
 | 
			
		||||
            hasMore: limit ? res.length === limit : (res.length === 20),
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
import useSWR from "swr";
 | 
			
		||||
import type { Document } from "dbtype/api";
 | 
			
		||||
import { fetcher } from "./fetcher";
 | 
			
		||||
 | 
			
		||||
interface SearchParams {
 | 
			
		||||
    word?: string;
 | 
			
		||||
    tags?: string;
 | 
			
		||||
    limit?: number;
 | 
			
		||||
    cursor?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useSearchGallery({
 | 
			
		||||
    word, tags, limit, cursor,
 | 
			
		||||
}: SearchParams) {
 | 
			
		||||
    const search = new URLSearchParams();
 | 
			
		||||
    if (word) search.set("word", word);
 | 
			
		||||
    if (tags) search.set("allow_tag", tags);
 | 
			
		||||
    if (limit) search.set("limit", limit.toString());
 | 
			
		||||
    if (cursor) search.set("cursor", cursor.toString());
 | 
			
		||||
    return useSWR<
 | 
			
		||||
        Document[]
 | 
			
		||||
    >(`/api/doc/search?${search.toString()}`, fetcher);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
 | 
			
		||||
import { useGalleryDoc } from "../hook/useGalleryDoc";
 | 
			
		||||
import { useGalleryDoc } from "../hook/useGalleryDoc.ts";
 | 
			
		||||
import TagBadge from "@/components/gallery/TagBadge";
 | 
			
		||||
import StyledLink from "@/components/gallery/StyledLink";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,126 +1,126 @@
 | 
			
		|||
import { Box, Button, Paper, Typography } from "@mui/material";
 | 
			
		||||
import React, { useContext, useEffect, useState } from "react";
 | 
			
		||||
import { CommonMenuList, Headline } from "../component/mod";
 | 
			
		||||
import { UserContext } from "../state";
 | 
			
		||||
import { PagePad } from "../component/pagepad";
 | 
			
		||||
// import { Box, Button, Paper, Typography } from "@mui/material";
 | 
			
		||||
// import React, { useContext, useEffect, useState } from "react";
 | 
			
		||||
// import { CommonMenuList, Headline } from "../component/mod";
 | 
			
		||||
// import { UserContext } from "../state";
 | 
			
		||||
// import { PagePad } from "../component/pagepad";
 | 
			
		||||
 | 
			
		||||
type FileDifference = {
 | 
			
		||||
	type: string;
 | 
			
		||||
	value: {
 | 
			
		||||
		type: string;
 | 
			
		||||
		path: string;
 | 
			
		||||
	}[];
 | 
			
		||||
};
 | 
			
		||||
// type FileDifference = {
 | 
			
		||||
// 	type: string;
 | 
			
		||||
// 	value: {
 | 
			
		||||
// 		type: string;
 | 
			
		||||
// 		path: string;
 | 
			
		||||
// 	}[];
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
function TypeDifference(prop: {
 | 
			
		||||
	content: FileDifference;
 | 
			
		||||
	onCommit: (v: { type: string; path: string }) => void;
 | 
			
		||||
	onCommitAll: (type: string) => void;
 | 
			
		||||
}) {
 | 
			
		||||
	// const classes = useStyles();
 | 
			
		||||
	const x = prop.content;
 | 
			
		||||
	const [button_disable, set_disable] = useState(false);
 | 
			
		||||
// function TypeDifference(prop: {
 | 
			
		||||
// 	content: FileDifference;
 | 
			
		||||
// 	onCommit: (v: { type: string; path: string }) => void;
 | 
			
		||||
// 	onCommitAll: (type: string) => void;
 | 
			
		||||
// }) {
 | 
			
		||||
// 	// const classes = useStyles();
 | 
			
		||||
// 	const x = prop.content;
 | 
			
		||||
// 	const [button_disable, set_disable] = useState(false);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<Paper /*className={classes.paper}*/>
 | 
			
		||||
			<Box /*className={classes.contentTitle}*/>
 | 
			
		||||
				<Typography variant="h3">{x.type}</Typography>
 | 
			
		||||
				<Button
 | 
			
		||||
					variant="contained"
 | 
			
		||||
					key={x.type}
 | 
			
		||||
					onClick={() => {
 | 
			
		||||
						set_disable(true);
 | 
			
		||||
						prop.onCommitAll(x.type);
 | 
			
		||||
						set_disable(false);
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					Commit all
 | 
			
		||||
				</Button>
 | 
			
		||||
			</Box>
 | 
			
		||||
			{x.value.map((y) => (
 | 
			
		||||
				<Box sx={{ display: "flex" }} key={y.path}>
 | 
			
		||||
					<Button
 | 
			
		||||
						variant="contained"
 | 
			
		||||
						onClick={() => {
 | 
			
		||||
							set_disable(true);
 | 
			
		||||
							prop.onCommit(y);
 | 
			
		||||
							set_disable(false);
 | 
			
		||||
						}}
 | 
			
		||||
						disabled={button_disable}
 | 
			
		||||
					>
 | 
			
		||||
						Commit
 | 
			
		||||
					</Button>
 | 
			
		||||
					<Typography variant="h5">{y.path}</Typography>
 | 
			
		||||
				</Box>
 | 
			
		||||
			))}
 | 
			
		||||
		</Paper>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
// 	return (
 | 
			
		||||
// 		<Paper /*className={classes.paper}*/>
 | 
			
		||||
// 			<Box /*className={classes.contentTitle}*/>
 | 
			
		||||
// 				<Typography variant="h3">{x.type}</Typography>
 | 
			
		||||
// 				<Button
 | 
			
		||||
// 					variant="contained"
 | 
			
		||||
// 					key={x.type}
 | 
			
		||||
// 					onClick={() => {
 | 
			
		||||
// 						set_disable(true);
 | 
			
		||||
// 						prop.onCommitAll(x.type);
 | 
			
		||||
// 						set_disable(false);
 | 
			
		||||
// 					}}
 | 
			
		||||
// 				>
 | 
			
		||||
// 					Commit all
 | 
			
		||||
// 				</Button>
 | 
			
		||||
// 			</Box>
 | 
			
		||||
// 			{x.value.map((y) => (
 | 
			
		||||
// 				<Box sx={{ display: "flex" }} key={y.path}>
 | 
			
		||||
// 					<Button
 | 
			
		||||
// 						variant="contained"
 | 
			
		||||
// 						onClick={() => {
 | 
			
		||||
// 							set_disable(true);
 | 
			
		||||
// 							prop.onCommit(y);
 | 
			
		||||
// 							set_disable(false);
 | 
			
		||||
// 						}}
 | 
			
		||||
// 						disabled={button_disable}
 | 
			
		||||
// 					>
 | 
			
		||||
// 						Commit
 | 
			
		||||
// 					</Button>
 | 
			
		||||
// 					<Typography variant="h5">{y.path}</Typography>
 | 
			
		||||
// 				</Box>
 | 
			
		||||
// 			))}
 | 
			
		||||
// 		</Paper>
 | 
			
		||||
// 	);
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export function DifferencePage() {
 | 
			
		||||
	const ctx = useContext(UserContext);
 | 
			
		||||
	// const classes = useStyles();
 | 
			
		||||
	const [diffList, setDiffList] = useState<FileDifference[]>([]);
 | 
			
		||||
	const doLoad = async () => {
 | 
			
		||||
		const list = await fetch("/api/diff/list");
 | 
			
		||||
		if (list.ok) {
 | 
			
		||||
			const inner = await list.json();
 | 
			
		||||
			setDiffList(inner);
 | 
			
		||||
		} else {
 | 
			
		||||
			// setDiffList([]);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	const Commit = async (x: { type: string; path: string }) => {
 | 
			
		||||
		const res = await fetch("/api/diff/commit", {
 | 
			
		||||
			method: "POST",
 | 
			
		||||
			body: JSON.stringify([{ ...x }]),
 | 
			
		||||
			headers: {
 | 
			
		||||
				"content-type": "application/json",
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
		const bb = await res.json();
 | 
			
		||||
		if (bb.ok) {
 | 
			
		||||
			doLoad();
 | 
			
		||||
		} else {
 | 
			
		||||
			console.error("fail to add document");
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	const CommitAll = async (type: string) => {
 | 
			
		||||
		const res = await fetch("/api/diff/commitall", {
 | 
			
		||||
			method: "POST",
 | 
			
		||||
			body: JSON.stringify({ type: type }),
 | 
			
		||||
			headers: {
 | 
			
		||||
				"content-type": "application/json",
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
		const bb = await res.json();
 | 
			
		||||
		if (bb.ok) {
 | 
			
		||||
			doLoad();
 | 
			
		||||
		} else {
 | 
			
		||||
			console.error("fail to add document");
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		doLoad();
 | 
			
		||||
		const i = setInterval(doLoad, 5000);
 | 
			
		||||
		return () => {
 | 
			
		||||
			clearInterval(i);
 | 
			
		||||
		};
 | 
			
		||||
	}, []);
 | 
			
		||||
	const menu = CommonMenuList();
 | 
			
		||||
	return (
 | 
			
		||||
		<Headline menu={menu}>
 | 
			
		||||
			<PagePad>
 | 
			
		||||
				{ctx.username == "admin" ? (
 | 
			
		||||
					<div>
 | 
			
		||||
						{diffList.map((x) => (
 | 
			
		||||
							<TypeDifference key={x.type} content={x} onCommit={Commit} onCommitAll={CommitAll} />
 | 
			
		||||
						))}
 | 
			
		||||
					</div>
 | 
			
		||||
				) : (
 | 
			
		||||
					<Typography variant="h2">Not Allowed : please login as an admin</Typography>
 | 
			
		||||
				)}
 | 
			
		||||
			</PagePad>
 | 
			
		||||
		</Headline>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
// export function DifferencePage() {
 | 
			
		||||
// 	const ctx = useContext(UserContext);
 | 
			
		||||
// 	// const classes = useStyles();
 | 
			
		||||
// 	const [diffList, setDiffList] = useState<FileDifference[]>([]);
 | 
			
		||||
// 	const doLoad = async () => {
 | 
			
		||||
// 		const list = await fetch("/api/diff/list");
 | 
			
		||||
// 		if (list.ok) {
 | 
			
		||||
// 			const inner = await list.json();
 | 
			
		||||
// 			setDiffList(inner);
 | 
			
		||||
// 		} else {
 | 
			
		||||
// 			// setDiffList([]);
 | 
			
		||||
// 		}
 | 
			
		||||
// 	};
 | 
			
		||||
// 	const Commit = async (x: { type: string; path: string }) => {
 | 
			
		||||
// 		const res = await fetch("/api/diff/commit", {
 | 
			
		||||
// 			method: "POST",
 | 
			
		||||
// 			body: JSON.stringify([{ ...x }]),
 | 
			
		||||
// 			headers: {
 | 
			
		||||
// 				"content-type": "application/json",
 | 
			
		||||
// 			},
 | 
			
		||||
// 		});
 | 
			
		||||
// 		const bb = await res.json();
 | 
			
		||||
// 		if (bb.ok) {
 | 
			
		||||
// 			doLoad();
 | 
			
		||||
// 		} else {
 | 
			
		||||
// 			console.error("fail to add document");
 | 
			
		||||
// 		}
 | 
			
		||||
// 	};
 | 
			
		||||
// 	const CommitAll = async (type: string) => {
 | 
			
		||||
// 		const res = await fetch("/api/diff/commitall", {
 | 
			
		||||
// 			method: "POST",
 | 
			
		||||
// 			body: JSON.stringify({ type: type }),
 | 
			
		||||
// 			headers: {
 | 
			
		||||
// 				"content-type": "application/json",
 | 
			
		||||
// 			},
 | 
			
		||||
// 		});
 | 
			
		||||
// 		const bb = await res.json();
 | 
			
		||||
// 		if (bb.ok) {
 | 
			
		||||
// 			doLoad();
 | 
			
		||||
// 		} else {
 | 
			
		||||
// 			console.error("fail to add document");
 | 
			
		||||
// 		}
 | 
			
		||||
// 	};
 | 
			
		||||
// 	useEffect(() => {
 | 
			
		||||
// 		doLoad();
 | 
			
		||||
// 		const i = setInterval(doLoad, 5000);
 | 
			
		||||
// 		return () => {
 | 
			
		||||
// 			clearInterval(i);
 | 
			
		||||
// 		};
 | 
			
		||||
// 	}, []);
 | 
			
		||||
// 	const menu = CommonMenuList();
 | 
			
		||||
// 	return (
 | 
			
		||||
// 		<Headline menu={menu}>
 | 
			
		||||
// 			<PagePad>
 | 
			
		||||
// 				{ctx.username == "admin" ? (
 | 
			
		||||
// 					<div>
 | 
			
		||||
// 						{diffList.map((x) => (
 | 
			
		||||
// 							<TypeDifference key={x.type} content={x} onCommit={Commit} onCommitAll={CommitAll} />
 | 
			
		||||
// 						))}
 | 
			
		||||
// 					</div>
 | 
			
		||||
// 				) : (
 | 
			
		||||
// 					<Typography variant="h2">Not Allowed : please login as an admin</Typography>
 | 
			
		||||
// 				)}
 | 
			
		||||
// 			</PagePad>
 | 
			
		||||
// 		</Headline>
 | 
			
		||||
// 	);
 | 
			
		||||
// }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,8 @@ import { Input } from "@/components/ui/input.tsx";
 | 
			
		|||
import { Button } from "@/components/ui/button.tsx";
 | 
			
		||||
import { GalleryCard } from "@/components/gallery/GalleryCard.tsx";
 | 
			
		||||
import TagBadge from "@/components/gallery/TagBadge.tsx";
 | 
			
		||||
import { useSearchGallery } from "../hook/useSearchGallery";
 | 
			
		||||
import { useSearchGallery } from "../hook/useSearchGallery.ts";
 | 
			
		||||
import { Spinner } from "../components/Spinner.tsx";
 | 
			
		||||
 | 
			
		||||
export default function Gallery() {
 | 
			
		||||
    const search = useSearch();
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +13,7 @@ export default function Gallery() {
 | 
			
		|||
    const tags = searchParams.get("allow_tag") ?? undefined;
 | 
			
		||||
    const limit = searchParams.get("limit");
 | 
			
		||||
    const cursor = searchParams.get("cursor");
 | 
			
		||||
    const { data, error, isLoading } = useSearchGallery({
 | 
			
		||||
    const { data, error, isLoading, size, setSize } = useSearchGallery({
 | 
			
		||||
        word, tags,
 | 
			
		||||
        limit: limit ? Number.parseInt(limit) : undefined,
 | 
			
		||||
        cursor: cursor ? Number.parseInt(cursor) : undefined
 | 
			
		||||
| 
						 | 
				
			
			@ -25,28 +26,39 @@ export default function Gallery() {
 | 
			
		|||
        return <div className="p-4">Error: {String(error)}</div>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const isLoadingMore = data && size > 0 && (data[size - 1] === undefined);
 | 
			
		||||
    const isReachingEnd = data && data[size - 1]?.hasMore === false;
 | 
			
		||||
 | 
			
		||||
    return (<div className="p-4 grid gap-2 overflow-auto h-screen items-start content-start">
 | 
			
		||||
        <div className="flex space-x-2">
 | 
			
		||||
            <Input className="flex-1"/>
 | 
			
		||||
            <Input className="flex-1" />
 | 
			
		||||
            <Button className="flex-none">Search</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
        {(word || tags) &&
 | 
			
		||||
            <div className="bg-primary rounded-full p-1 sticky top-0 shadow-md">
 | 
			
		||||
                {word && <span className="text-primary-foreground ml-4">Search: {word}</span>}
 | 
			
		||||
                {tags && <span className="text-primary-foreground ml-4">Tags: <ul className="inline-flex">{tags.split(",").map(x=> <TagBadge tagname={x} key={x}/>)}</ul></span>}
 | 
			
		||||
                {tags && <span className="text-primary-foreground ml-4">Tags: <ul className="inline-flex">{tags.split(",").map(x => <TagBadge tagname={x} key={x} />)}</ul></span>}
 | 
			
		||||
            </div>
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            data?.length === 0 && <div className="p-4 text-3xl">No results</div>
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            // TODO: implement infinite scroll
 | 
			
		||||
            data?.map((x) => {
 | 
			
		||||
                return (
 | 
			
		||||
                    <GalleryCard doc={x} key={x.id} />
 | 
			
		||||
                );
 | 
			
		||||
            // TODO: date based grouping 
 | 
			
		||||
            data?.map((docs) => {
 | 
			
		||||
                return docs.data.map((x) => {
 | 
			
		||||
                    return (
 | 
			
		||||
                        <GalleryCard doc={x} key={x.id} />
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            <Button className="w-full" onClick={() => setSize(size + 1)}
 | 
			
		||||
                disabled={isReachingEnd || isLoadingMore}
 | 
			
		||||
            > {isLoadingMore && <Spinner className="mr-1" />}{size + 1} Load more</Button>
 | 
			
		||||
        }
 | 
			
		||||
    </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { useGalleryDoc } from "@/hook/useGalleryDoc";
 | 
			
		||||
import { useGalleryDoc } from "@/hook/useGalleryDoc.ts";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import type { Document } from "dbtype/api";
 | 
			
		||||
import { useCallback, useEffect, useRef, useState } from "react";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,18 +43,9 @@ function DarkModeView() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export function SettingPage() {
 | 
			
		||||
    const { setTernaryDarkMode, ternaryDarkMode, isDarkMode } = useTernaryDarkMode();
 | 
			
		||||
    const { setTernaryDarkMode, ternaryDarkMode } = useTernaryDarkMode();
 | 
			
		||||
    const isSystemDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (isDarkMode) {
 | 
			
		||||
            document.body.classList.add("dark");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            document.body.classList.remove("dark");
 | 
			
		||||
        }
 | 
			
		||||
    }, [isDarkMode]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="p-4">
 | 
			
		||||
            <Card>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue