feat: enhance NavItem and NavItemButton components with optional className prop; refactor atom usage in user state management
This commit is contained in:
parent
cb6d03458f
commit
0be89bfa23
5 changed files with 41 additions and 92 deletions
|
@ -11,18 +11,20 @@ interface NavItemProps {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
to: string;
|
to: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NavItem({
|
export function NavItem({
|
||||||
icon,
|
icon,
|
||||||
to,
|
to,
|
||||||
name
|
name,
|
||||||
|
className
|
||||||
}: NavItemProps) {
|
}: NavItemProps) {
|
||||||
return <Tooltip>
|
return <Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Link
|
<Link
|
||||||
href={to}
|
href={to}
|
||||||
className={buttonVariants({ variant: "ghost" })}
|
className={buttonVariants({ variant: "ghost", className })}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="sr-only">{name}</span>
|
<span className="sr-only">{name}</span>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { atom, useAtomValue, setAtomValue, getAtomState } from "@/lib/atom";
|
import { atom, useAtomValue, useSetAtom } from "@/lib/atom";
|
||||||
import { useLayoutEffect } from "react";
|
import { useLayoutEffect, useRef } from "react";
|
||||||
|
|
||||||
const NavItems = atom<React.ReactNode>("NavItems", null);
|
const NavItems = atom<React.ReactNode>(null);
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export function useNavItems() {
|
export function useNavItems() {
|
||||||
|
@ -9,14 +9,19 @@ export function useNavItems() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PageNavItem({items, children}:{items: React.ReactNode, children: React.ReactNode}) {
|
export function PageNavItem({items, children}:{items: React.ReactNode, children: React.ReactNode}) {
|
||||||
|
const currentNavItems = useAtomValue(NavItems);
|
||||||
|
const setNavItems = useSetAtom(NavItems);
|
||||||
|
const prevValueRef = useRef<React.ReactNode>(null);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const prev = getAtomState(NavItems).value;
|
// Store current value before setting new one
|
||||||
const setter = setAtomValue(NavItems);
|
prevValueRef.current = currentNavItems;
|
||||||
setter(items);
|
setNavItems(items);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setter(prev);
|
setNavItems(prevValueRef.current);
|
||||||
};
|
};
|
||||||
}, [items]);
|
}, [items, currentNavItems, setNavItems]);
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,2 @@
|
||||||
import { useEffect, useReducer, useState } from "react";
|
// Re-export jotai functions to maintain compatibility
|
||||||
|
export { atom, useAtom, useAtomValue, useSetAtom, useSetAtom as setAtomValue } from 'jotai';
|
||||||
interface AtomState<T> {
|
|
||||||
value: T;
|
|
||||||
listeners: Set<() => void>;
|
|
||||||
}
|
|
||||||
interface Atom<T> {
|
|
||||||
key: string;
|
|
||||||
default: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
const atomStateMap = new WeakMap<Atom<unknown>, AtomState<unknown>>();
|
|
||||||
|
|
||||||
export function atom<T>(key: string, defaultVal: T): Atom<T> {
|
|
||||||
return { key, default: defaultVal };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAtomState<T>(atom: Atom<T>): AtomState<T> {
|
|
||||||
let atomState = atomStateMap.get(atom);
|
|
||||||
if (!atomState) {
|
|
||||||
atomState = {
|
|
||||||
value: atom.default,
|
|
||||||
listeners: new Set(),
|
|
||||||
};
|
|
||||||
atomStateMap.set(atom, atomState);
|
|
||||||
}
|
|
||||||
return atomState as AtomState<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAtom<T>(atom: Atom<T>): [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<T>(atom: Atom<T>): 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<T>(atom: Atom<T>): (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());
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -146,12 +146,20 @@ export default function ComicPage({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageNavItem items={<>
|
<PageNavItem items={<>
|
||||||
<NavItem to={`/doc/${params.id}`} name="Back" icon={<ExitIcon />} />
|
<NavItem
|
||||||
<NavItemButton name={isFullScreen ? "Exit Fullscreen" : "Enter Fullscreen"} icon={isFullScreen ? <ExitFullScreenIcon /> : <EnterFullScreenIcon />} onClick={() => {
|
className="flex-1"
|
||||||
toggleFullScreen();
|
to={`/doc/${params.id}`} name="Back" icon={<ExitIcon />} />
|
||||||
}} />
|
<NavItemButton
|
||||||
|
className="flex-1"
|
||||||
|
name={isFullScreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
||||||
|
icon={isFullScreen ? <ExitFullScreenIcon /> : <EnterFullScreenIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
toggleFullScreen();
|
||||||
|
}} />
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
<span className="text-sm text-ellipsis" >{curPage + 1}/{data.additional.page as number}</span>
|
<span className="text-sm text-ellipsis" >{curPage + 1}/{data.additional.page as number}</span>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-28">
|
<PopoverContent className="w-28">
|
||||||
|
|
|
@ -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 { LoginRequest } from "dbtype/mod.ts";
|
||||||
import {
|
import {
|
||||||
ApiError,
|
ApiError,
|
||||||
|
@ -9,6 +10,9 @@ import {
|
||||||
resetPasswordService,
|
resetPasswordService,
|
||||||
} from "./api.ts";
|
} from "./api.ts";
|
||||||
|
|
||||||
|
// Create a store for setting atom values outside components
|
||||||
|
const store = createStore();
|
||||||
|
|
||||||
let localObj: LoginResponse | null = null;
|
let localObj: LoginResponse | null = null;
|
||||||
function getUserSessions() {
|
function getUserSessions() {
|
||||||
if (localObj === null) {
|
if (localObj === null) {
|
||||||
|
@ -55,7 +59,6 @@ export async function refresh() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doLogout = async () => {
|
export const doLogout = async () => {
|
||||||
const setVal = setAtomValue(userLoginStateAtom);
|
|
||||||
try {
|
try {
|
||||||
const res = await logoutService();
|
const res = await logoutService();
|
||||||
localObj = {
|
localObj = {
|
||||||
|
@ -64,7 +67,7 @@ export const doLogout = async () => {
|
||||||
permission: res.permission,
|
permission: res.permission,
|
||||||
};
|
};
|
||||||
window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj));
|
window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj));
|
||||||
setVal(localObj);
|
store.set(userLoginStateAtom, localObj);
|
||||||
return {
|
return {
|
||||||
username: localObj.username,
|
username: localObj.username,
|
||||||
permission: localObj.permission,
|
permission: localObj.permission,
|
||||||
|
@ -74,7 +77,7 @@ export const doLogout = async () => {
|
||||||
// Even if logout fails, clear client-side session
|
// Even if logout fails, clear client-side session
|
||||||
localObj = { accessExpired: 0, username: "", permission: [] };
|
localObj = { accessExpired: 0, username: "", permission: [] };
|
||||||
window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj));
|
window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj));
|
||||||
setVal(localObj);
|
store.set(userLoginStateAtom, localObj);
|
||||||
return {
|
return {
|
||||||
username: "",
|
username: "",
|
||||||
permission: [],
|
permission: [],
|
||||||
|
@ -85,9 +88,8 @@ export const doLogout = async () => {
|
||||||
export const doLogin = async (userLoginInfo: LoginRequest): Promise<string | LoginResponse> => {
|
export const doLogin = async (userLoginInfo: LoginRequest): Promise<string | LoginResponse> => {
|
||||||
try {
|
try {
|
||||||
const b = await loginService(userLoginInfo);
|
const b = await loginService(userLoginInfo);
|
||||||
const setVal = setAtomValue(userLoginStateAtom);
|
|
||||||
localObj = b;
|
localObj = b;
|
||||||
setVal(b);
|
store.set(userLoginStateAtom, b);
|
||||||
window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj));
|
window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj));
|
||||||
return b;
|
return b;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -123,7 +125,7 @@ export async function getInitialValue() {
|
||||||
return refresh();
|
return refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userLoginStateAtom = atom("userLoginState", getUserSessions());
|
export const userLoginStateAtom = atom(getUserSessions());
|
||||||
|
|
||||||
export function useLogin() {
|
export function useLogin() {
|
||||||
const val = useAtomValue(userLoginStateAtom);
|
const val = useAtomValue(userLoginStateAtom);
|
||||||
|
|
Loading…
Add table
Reference in a new issue