From 7598df6018edc978d5a019ced3c95fe97302a95b Mon Sep 17 00:00:00 2001 From: monoid Date: Sun, 7 Apr 2024 23:50:39 +0900 Subject: [PATCH] feat: add difference page --- packages/client/package.json | 1 + packages/client/src/App.tsx | 5 +- .../client/src/components/ui/separator.tsx | 29 ++++ packages/client/src/hook/fetcher.ts | 2 +- packages/client/src/hook/useDifference.ts | 38 ++++++ packages/client/src/page/difference.tsx | 126 ------------------ packages/client/src/page/differencePage.tsx | 63 +++++++++ packages/client/src/state/user.ts | 9 +- packages/server/comic_config.json | 2 +- packages/server/src/content/comic.ts | 15 +-- packages/server/src/content/file.ts | 7 +- packages/server/src/db/doc.ts | 6 +- packages/server/src/diff/diff.ts | 1 + packages/server/src/diff/router.ts | 4 +- packages/server/src/login.ts | 5 +- packages/server/src/util/zipwrap.ts | 2 +- pnpm-lock.yaml | 24 ++++ 17 files changed, 185 insertions(+), 154 deletions(-) create mode 100644 packages/client/src/components/ui/separator.tsx create mode 100644 packages/client/src/hook/useDifference.ts delete mode 100644 packages/client/src/page/difference.tsx create mode 100644 packages/client/src/page/differencePage.tsx diff --git a/packages/client/package.json b/packages/client/package.json index 36d89b2..0fdddb9 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -14,6 +14,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.7", "class-variance-authority": "^0.7.0", diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 9faa003..45a6fe5 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -14,6 +14,7 @@ import ProfilePage from "@/page/profilesPage.tsx"; import ContentInfoPage from "@/page/contentInfoPage.tsx"; import SettingPage from "@/page/settingPage.tsx"; import ComicPage from "@/page/reader/comicPage.tsx"; +import DifferencePage from "./page/differencePage.tsx"; const App = () => { const { isDarkMode } = useTernaryDarkMode(); @@ -38,9 +39,7 @@ const App = () => { - {/* - }/> - */} + diff --git a/packages/client/src/components/ui/separator.tsx b/packages/client/src/components/ui/separator.tsx new file mode 100644 index 0000000..6d7f122 --- /dev/null +++ b/packages/client/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/packages/client/src/hook/fetcher.ts b/packages/client/src/hook/fetcher.ts index 0d0ca38..67e6b9b 100644 --- a/packages/client/src/hook/fetcher.ts +++ b/packages/client/src/hook/fetcher.ts @@ -1,4 +1,4 @@ -export const BASE_API_URL = 'http://localhost:8080/'; +export const BASE_API_URL = 'http://localhost:5173/'; export async function fetcher(url: string) { const u = new URL(url, BASE_API_URL); diff --git a/packages/client/src/hook/useDifference.ts b/packages/client/src/hook/useDifference.ts new file mode 100644 index 0000000..37976a5 --- /dev/null +++ b/packages/client/src/hook/useDifference.ts @@ -0,0 +1,38 @@ +import useSWR, { mutate } from "swr"; +import { fetcher } from "./fetcher"; + +type FileDifference = { + type: string; + value: { + type: string; + path: string; + }[]; +}; + +export function useDifferenceDoc() { + return useSWR("/api/diff/list", fetcher); +} + +export async function commit(path: string, type: string) { + const res = await fetch("/api/diff/commit", { + method: "POST", + body: JSON.stringify([{ path, type }]), + headers: { + "Content-Type": "application/json", + }, + }); + mutate("/api/diff/list"); + return res.ok; +} + +export async function commitAll(type: string) { + const res = await fetch("/api/diff/commitall", { + method: "POST", + body: JSON.stringify({ type }), + headers: { + "Content-Type": "application/json", + }, + }); + mutate("/api/diff/list"); + return res.ok; +} \ No newline at end of file diff --git a/packages/client/src/page/difference.tsx b/packages/client/src/page/difference.tsx deleted file mode 100644 index a5ae699..0000000 --- a/packages/client/src/page/difference.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// 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; -// }[]; -// }; - -// 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 ( -// -// -// {x.type} -// -// -// {x.value.map((y) => ( -// -// -// {y.path} -// -// ))} -// -// ); -// } - -// export function DifferencePage() { -// const ctx = useContext(UserContext); -// // const classes = useStyles(); -// const [diffList, setDiffList] = useState([]); -// 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 ( -// -// -// {ctx.username == "admin" ? ( -//
-// {diffList.map((x) => ( -// -// ))} -//
-// ) : ( -// Not Allowed : please login as an admin -// )} -//
-//
-// ); -// } diff --git a/packages/client/src/page/differencePage.tsx b/packages/client/src/page/differencePage.tsx new file mode 100644 index 0000000..efe7ffc --- /dev/null +++ b/packages/client/src/page/differencePage.tsx @@ -0,0 +1,63 @@ +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { useDifferenceDoc, commit, commitAll } from "@/hook/useDifference"; +import { useLogin } from "@/state/user"; +import { Fragment } from "react/jsx-runtime"; + +export function DifferencePage() { + const { data, isLoading, error } = useDifferenceDoc(); + const userInfo = useLogin(); + + if (!userInfo) { + return
+

+ Not logged in +

+
+ } + + if (error) { + return
Error: {String(error)}
+ } + + return ( +
+ + + + Difference + Scanned Files List + + + + {isLoading &&
Loading...
} + {data?.map((c) => { + const x = c.value; + return ( + + {x.map((y) => ( +
+

{y.path}

+ +
+ ))} +
+ ) + })} +
+
+
+ ) +} + +export default DifferencePage; \ No newline at end of file diff --git a/packages/client/src/state/user.ts b/packages/client/src/state/user.ts index d610306..b2420ef 100644 --- a/packages/client/src/state/user.ts +++ b/packages/client/src/state/user.ts @@ -24,9 +24,10 @@ function getUserSessions() { } export async function refresh() { - const u = new URL("/user/refresh", BASE_API_URL); + const u = new URL("/api/user/refresh", BASE_API_URL); const res = await fetch(u, { method: "POST", + credentials: "include", }); if (res.status !== 200) throw new Error("Maybe Network Error"); const r = (await res.json()) as LoginLocalStorage & { refresh: boolean }; @@ -49,9 +50,10 @@ export async function refresh() { } export const doLogout = async () => { - const u = new URL("/user/refresh", BASE_API_URL); + const u = new URL("/api/user/logout", BASE_API_URL); const req = await fetch(u, { method: "POST", + credentials: "include", }); const setVal = setAtomValue(userLoginStateAtom); try { @@ -79,11 +81,12 @@ export const doLogin = async (userLoginInfo: { username: string; password: string; }): Promise => { - const u = new URL("/user/refresh", BASE_API_URL); + const u = new URL("/api/user/login", BASE_API_URL); const res = await fetch(u, { method: "POST", body: JSON.stringify(userLoginInfo), headers: { "content-type": "application/json" }, + credentials: "include", }); const b = await res.json(); if (res.status !== 200) { diff --git a/packages/server/comic_config.json b/packages/server/comic_config.json index e9692d3..130f581 100644 --- a/packages/server/comic_config.json +++ b/packages/server/comic_config.json @@ -1,3 +1,3 @@ { - "watch": [] + "watch": ["testdata"] } \ No newline at end of file diff --git a/packages/server/src/content/comic.ts b/packages/server/src/content/comic.ts index 9f5ab32..f42338e 100644 --- a/packages/server/src/content/comic.ts +++ b/packages/server/src/content/comic.ts @@ -1,5 +1,5 @@ import { extname } from "node:path"; -import type { DocumentBody } from "../model/doc"; +import type { DocumentBody } from "dbtype/api"; import { readZip } from "../util/zipwrap"; import { type ContentConstructOption, createDefaultClass, registerContentReferrer } from "./file"; import { TextWriter } from "@zip.js/zip.js"; @@ -29,19 +29,18 @@ export class ComicReferrer extends createDefaultClass("comic") { const zip = await readZip(this.path); const entries = await zip.reader.getEntries(); this.pagenum = entries.filter((x) => ImageExt.includes(extname(x.filename))).length; - const entry = entries.find(x=> x.filename === "desc.json"); - if (entry === undefined) { + const descEntry = entries.find(x=> x.filename === "desc.json"); + if (descEntry === undefined) { return; } - if (entry.getData === undefined) { + if (descEntry.getData === undefined) { throw new Error("entry.getData is undefined"); } const textWriter = new TextWriter(); - const data = (await entry.getData(textWriter)); + const data = (await descEntry.getData(textWriter)); this.desc = JSON.parse(data); - if (this.desc === undefined) { - throw new Error(`JSON.parse is returning undefined. ${this.path} desc.json format error`); - } + zip.reader.close() + .then(() => zip.handle.close()); } async createDocumentBody(): Promise { diff --git a/packages/server/src/content/file.ts b/packages/server/src/content/file.ts index 681a348..21f4397 100644 --- a/packages/server/src/content/file.ts +++ b/packages/server/src/content/file.ts @@ -1,9 +1,7 @@ import { createHash } from "node:crypto"; import { promises, type Stats } from "node:fs"; -import { Context, DefaultContext, DefaultState, Middleware, Next } from "koa"; -import Router from "koa-router"; import path, { extname } from "node:path"; -import type { DocumentBody } from "../model/mod"; +import type { DocumentBody } from "dbtype/api"; /** * content file or directory referrer */ @@ -40,8 +38,9 @@ export const createDefaultClass = (type: string): ContentFileConstructor => { this.stat = undefined; } async createDocumentBody(): Promise { + console.log(`createDocumentBody: ${this.path}`); const { base, dir, name } = path.parse(this.path); - + const ret = { title: name, basepath: dir, diff --git a/packages/server/src/db/doc.ts b/packages/server/src/db/doc.ts index 4464785..bbec4ee 100644 --- a/packages/server/src/db/doc.ts +++ b/packages/server/src/db/doc.ts @@ -73,11 +73,13 @@ class SqliteDocumentAccessor implements DocumentAccessor { await trx.insertInto("tags") .values(tags.map((x) => ({ name: x }))) // on conflict is supported in sqlite and postgresql. - .onConflict((oc) => oc.doNothing()); + .onConflict((oc) => oc.doNothing()) + .execute(); if (tags.length > 0) { await trx.insertInto("doc_tag_relation") - .values(tags.map((x) => ({ doc_id: id, tag_name: x }))); + .values(tags.map((x) => ({ doc_id: id, tag_name: x }))) + .execute(); } return id; }); diff --git a/packages/server/src/diff/diff.ts b/packages/server/src/diff/diff.ts index 5fd7c5a..3f69f50 100644 --- a/packages/server/src/diff/diff.ts +++ b/packages/server/src/diff/diff.ts @@ -24,6 +24,7 @@ export class DiffManager { throw new Error("path is not exist"); } await list.delete(c); + console.log(`commit: ${c.path} ${c.type}`); const body = await c.createDocumentBody(); const id = await this.doc_cntr.add(body); return id; diff --git a/packages/server/src/diff/router.ts b/packages/server/src/diff/router.ts index 725bb6e..41ab912 100644 --- a/packages/server/src/diff/router.ts +++ b/packages/server/src/diff/router.ts @@ -23,7 +23,7 @@ type PostAddedBody = { path: string; }[]; -function checkPostAddedBody(body: any): body is PostAddedBody { +function checkPostAddedBody(body: unknown): body is PostAddedBody { if (Array.isArray(body)) { return body.map((x) => "type" in x && "path" in x).every((x) => x); } @@ -43,6 +43,7 @@ export const postAdded = (diffmgr: DiffManager) => async (ctx: Router.IRouterCon docs: results, }; ctx.type = "json"; + await next(); }; export const postAddedAll = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { if (!ctx.is("json")) { @@ -64,6 +65,7 @@ export const postAddedAll = (diffmgr: DiffManager) => async (ctx: Router.IRouter ok: true, }; ctx.type = "json"; + await next(); }; /* export const getNotWatched = (diffmgr : DiffManager)=> (ctx:Router.IRouterContext,next:Koa.Next)=>{ diff --git a/packages/server/src/login.ts b/packages/server/src/login.ts index e10917a..1046d6f 100644 --- a/packages/server/src/login.ts +++ b/packages/server/src/login.ts @@ -1,9 +1,6 @@ -import { request } from "node:http"; -import { decode, sign, TokenExpiredError, verify } from "jsonwebtoken"; -import Knex from "knex"; +import { sign, TokenExpiredError, verify } from "jsonwebtoken"; import type Koa from "koa"; import Router from "koa-router"; -import { createSqliteUserController } from "./db/mod"; import type { IUser, UserAccessor } from "./model/mod"; import { sendError } from "./route/error_handler"; import { get_setting } from "./SettingConfig"; diff --git a/packages/server/src/util/zipwrap.ts b/packages/server/src/util/zipwrap.ts index 5410cd4..f32ab4f 100644 --- a/packages/server/src/util/zipwrap.ts +++ b/packages/server/src/util/zipwrap.ts @@ -34,7 +34,7 @@ export async function readZip(path: string): Promise<{ const fd = await open(path, "r"); const reader = new ZipReader(new FileReader(fd), { useCompressionStream: true, - preventClose: true, + preventClose: false, }); return { reader, handle: fd }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13ee424..243494a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@radix-ui/react-radio-group': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-separator': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.71)(react@18.2.0) @@ -1284,6 +1287,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.71 + '@types/react-dom': 18.2.22 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-slot@1.0.2(@types/react@18.2.71)(react@18.2.0): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: