From 0be89bfa23903c0768a1d77375736f4f4b3d0fc2 Mon Sep 17 00:00:00 2001 From: monoid Date: Wed, 1 Oct 2025 01:53:16 +0900 Subject: [PATCH] feat: enhance NavItem and NavItemButton components with optional className prop; refactor atom usage in user state management --- packages/client/src/components/layout/nav.tsx | 6 +- .../client/src/components/layout/navAtom.tsx | 21 +++--- packages/client/src/lib/atom.ts | 72 +------------------ packages/client/src/page/reader/comicPage.tsx | 18 +++-- packages/client/src/state/user.ts | 16 +++-- 5 files changed, 41 insertions(+), 92 deletions(-) diff --git a/packages/client/src/components/layout/nav.tsx b/packages/client/src/components/layout/nav.tsx index 1578b5c..a430846 100644 --- a/packages/client/src/components/layout/nav.tsx +++ b/packages/client/src/components/layout/nav.tsx @@ -11,18 +11,20 @@ interface NavItemProps { icon: React.ReactNode; to: string; name: string; + className?: string; } export function NavItem({ icon, to, - name + name, + className }: NavItemProps) { return {icon} {name} diff --git a/packages/client/src/components/layout/navAtom.tsx b/packages/client/src/components/layout/navAtom.tsx index f0b23a6..a05b2a8 100644 --- a/packages/client/src/components/layout/navAtom.tsx +++ b/packages/client/src/components/layout/navAtom.tsx @@ -1,7 +1,7 @@ -import { atom, useAtomValue, setAtomValue, getAtomState } from "@/lib/atom"; -import { useLayoutEffect } from "react"; +import { atom, useAtomValue, useSetAtom } from "@/lib/atom"; +import { useLayoutEffect, useRef } from "react"; -const NavItems = atom("NavItems", null); +const NavItems = atom(null); // eslint-disable-next-line react-refresh/only-export-components export function useNavItems() { @@ -9,14 +9,19 @@ export function useNavItems() { } export function PageNavItem({items, children}:{items: React.ReactNode, children: React.ReactNode}) { + const currentNavItems = useAtomValue(NavItems); + const setNavItems = useSetAtom(NavItems); + const prevValueRef = useRef(null); + useLayoutEffect(() => { - const prev = getAtomState(NavItems).value; - const setter = setAtomValue(NavItems); - setter(items); + // Store current value before setting new one + prevValueRef.current = currentNavItems; + setNavItems(items); + return () => { - setter(prev); + setNavItems(prevValueRef.current); }; - }, [items]); + }, [items, currentNavItems, setNavItems]); return children; } diff --git a/packages/client/src/lib/atom.ts b/packages/client/src/lib/atom.ts index 526e725..c543526 100644 --- a/packages/client/src/lib/atom.ts +++ b/packages/client/src/lib/atom.ts @@ -1,70 +1,2 @@ -import { useEffect, useReducer, useState } from "react"; - -interface AtomState { - value: T; - listeners: Set<() => void>; -} -interface Atom { - key: string; - default: T; -} - -const atomStateMap = new WeakMap, AtomState>(); - -export function atom(key: string, defaultVal: T): Atom { - return { key, default: defaultVal }; -} - -export function getAtomState(atom: Atom): AtomState { - let atomState = atomStateMap.get(atom); - if (!atomState) { - atomState = { - value: atom.default, - listeners: new Set(), - }; - atomStateMap.set(atom, atomState); - } - return atomState as AtomState; -} - -export function useAtom(atom: Atom): [T, (val: T) => void] { - const state = getAtomState(atom); - const [, setState] = useState(state.value); - useEffect(() => { - const listener = () => setState(state.value); - state.listeners.add(listener); - return () => { - state.listeners.delete(listener); - }; - }, [state]); - return [ - state.value as T, - (val: T) => { - state.value = val; - // biome-ignore lint/complexity/noForEach: forEach is used to call each listener - state.listeners.forEach((listener) => listener()); - }, - ]; -} - -export function useAtomValue(atom: Atom): T { - const state = getAtomState(atom); - const update = useReducer((x) => x + 1, 0)[1]; - useEffect(() => { - const listener = () => update(); - state.listeners.add(listener); - return () => { - state.listeners.delete(listener); - }; - }, [state, update]); - return state.value; -} - -export function setAtomValue(atom: Atom): (val: T) => void { - const state = getAtomState(atom); - return (val: T) => { - state.value = val; - // biome-ignore lint/complexity/noForEach: forEach is used to call each listener - state.listeners.forEach((listener) => listener()); - }; -} \ No newline at end of file +// Re-export jotai functions to maintain compatibility +export { atom, useAtom, useAtomValue, useSetAtom, useSetAtom as setAtomValue } from 'jotai'; \ No newline at end of file diff --git a/packages/client/src/page/reader/comicPage.tsx b/packages/client/src/page/reader/comicPage.tsx index 1257788..797ac53 100644 --- a/packages/client/src/page/reader/comicPage.tsx +++ b/packages/client/src/page/reader/comicPage.tsx @@ -146,12 +146,20 @@ export default function ComicPage({ return ( - } /> - : } onClick={() => { - toggleFullScreen(); - }} /> + } /> + : } + onClick={() => { + toggleFullScreen(); + }} /> - + {curPage + 1}/{data.additional.page as number} diff --git a/packages/client/src/state/user.ts b/packages/client/src/state/user.ts index 88cf097..69ad2bb 100644 --- a/packages/client/src/state/user.ts +++ b/packages/client/src/state/user.ts @@ -1,4 +1,5 @@ -import { atom, useAtomValue, setAtomValue } from "../lib/atom.ts"; +import { atom, useAtomValue } from "../lib/atom.ts"; +import { createStore } from 'jotai'; import { LoginRequest } from "dbtype/mod.ts"; import { ApiError, @@ -9,6 +10,9 @@ import { resetPasswordService, } from "./api.ts"; +// Create a store for setting atom values outside components +const store = createStore(); + let localObj: LoginResponse | null = null; function getUserSessions() { if (localObj === null) { @@ -55,7 +59,6 @@ export async function refresh() { } export const doLogout = async () => { - const setVal = setAtomValue(userLoginStateAtom); try { const res = await logoutService(); localObj = { @@ -64,7 +67,7 @@ export const doLogout = async () => { permission: res.permission, }; window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); - setVal(localObj); + store.set(userLoginStateAtom, localObj); return { username: localObj.username, permission: localObj.permission, @@ -74,7 +77,7 @@ export const doLogout = async () => { // Even if logout fails, clear client-side session localObj = { accessExpired: 0, username: "", permission: [] }; window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); - setVal(localObj); + store.set(userLoginStateAtom, localObj); return { username: "", permission: [], @@ -85,9 +88,8 @@ export const doLogout = async () => { export const doLogin = async (userLoginInfo: LoginRequest): Promise => { try { const b = await loginService(userLoginInfo); - const setVal = setAtomValue(userLoginStateAtom); localObj = b; - setVal(b); + store.set(userLoginStateAtom, b); window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); return b; } catch (e) { @@ -123,7 +125,7 @@ export async function getInitialValue() { return refresh(); } -export const userLoginStateAtom = atom("userLoginState", getUserSessions()); +export const userLoginStateAtom = atom(getUserSessions()); export function useLogin() { const val = useAtomValue(userLoginStateAtom);