From 62ec80565eb153bf33ac409e9a76b99b6d5955b3 Mon Sep 17 00:00:00 2001 From: monoid Date: Thu, 4 Apr 2024 23:12:31 +0900 Subject: [PATCH] layout --- packages/client/package.json | 7 +- packages/client/src/App.css | 13 + packages/client/src/App.tsx | 85 ++-- .../client/src/components/layout/layout.tsx | 50 ++ packages/client/src/components/layout/nav.tsx | 43 ++ packages/client/src/components/ui/button.tsx | 57 +++ .../client/src/components/ui/resizable.tsx | 43 ++ packages/client/src/components/ui/tooltip.tsx | 28 ++ packages/client/src/lib/atom.ts | 70 +++ packages/client/src/page/404.tsx | 18 +- packages/client/src/page/galleryPage.tsx | 6 + packages/client/src/state.tsx | 2 +- packages/client/src/state/user.ts | 109 +++++ pnpm-lock.yaml | 451 +++++++++++++++++- 14 files changed, 913 insertions(+), 69 deletions(-) create mode 100644 packages/client/src/components/layout/layout.tsx create mode 100644 packages/client/src/components/layout/nav.tsx create mode 100644 packages/client/src/components/ui/button.tsx create mode 100644 packages/client/src/components/ui/resizable.tsx create mode 100644 packages/client/src/components/ui/tooltip.tsx create mode 100644 packages/client/src/lib/atom.ts create mode 100644 packages/client/src/page/galleryPage.tsx create mode 100644 packages/client/src/state/user.ts diff --git a/packages/client/package.json b/packages/client/package.json index e1363dd..cb28194 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -12,12 +12,17 @@ }, "dependencies": { "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tooltip": "^1.0.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-resizable-panels": "^2.0.16", + "swr": "^2.2.5", "tailwind-merge": "^2.2.2", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "wouter": "^3.1.0" }, "devDependencies": { "@types/node": ">=20.0.0", diff --git a/packages/client/src/App.css b/packages/client/src/App.css index e69de29..1ff976b 100644 --- a/packages/client/src/App.css +++ b/packages/client/src/App.css @@ -0,0 +1,13 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap'); + +body { + margin: 0; + font-family: "Noto Sans KR", sans-serif; + font-optical-sizing: auto; + min-height: 100vh; +} + +#root { + margin: 0; + min-height: 100vh; +} \ No newline at end of file diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 04403cb..790f223 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -1,59 +1,44 @@ +import { Route, Switch, Redirect } from "wouter"; import './App.css' -// import React, { createContext, useEffect, useRef, useState } from "react"; -// import ReactDom from "react-dom"; -// import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; + // import { -// DifferencePage, -// DocumentAbout, -// Gallery, -// LoginPage, -// NotFoundPage, -// ProfilePage, -// ReaderPage, -// SettingPage, -// TagsPage, +// // DifferencePage, +// // DocumentAbout, +// // Gallery, +// // LoginPage, +// // NotFoundPage, +// // ProfilePage, +// // ReaderPage, +// // SettingPage, +// // TagsPage, // } from "./page/mod"; -// import { getInitialValue, UserContext } from "./state"; + +import { TooltipProvider } from "./components/ui/tooltip"; + +import Gallery from "./page/galleryPage"; +import Layout from "./components/layout/layout"; +import NotFoundPage from "./page/404"; const App = () => { - // const [user, setUser] = useState(""); - // const [userPermission, setUserPermission] = useState([]); - // (async () => { - // const { username, permission } = await getInitialValue(); - // if (username !== user) { - // setUser(username); - // setUserPermission(permission); - // } - // })(); - // useEffect(()=>{}); - return (

- Hello world! -

- // - // - // - // } /> - // } /> - // }> - // }> - // } /> - // }> - // }> - // }> - // }> - // } /> - // - // - // - ); + return ( + + + + + } /> + + {/* }> + }> + } /> + }> + }> + }> + }>*/} + + + + ); }; export default App diff --git a/packages/client/src/components/layout/layout.tsx b/packages/client/src/components/layout/layout.tsx new file mode 100644 index 0000000..fe1f1dc --- /dev/null +++ b/packages/client/src/components/layout/layout.tsx @@ -0,0 +1,50 @@ +import { useLayoutEffect, useState } from "react"; +import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "../ui/resizable"; +import { NavList } from "./nav"; + + +interface LayoutProps { + children?: React.ReactNode; +} + +export default function Layout({ children }: LayoutProps) { + const MIN_SIZE_IN_PIXELS = 70; + const [minSize, setMinSize] = useState(MIN_SIZE_IN_PIXELS); + + useLayoutEffect(() => { + const panelGroup = document.querySelector('[data-panel-group-id="main"]'); + const resizeHandles = document.querySelectorAll( + "[data-panel-resize-handle-id]" + ); + if (!panelGroup || !resizeHandles) return; + console.log(panelGroup, resizeHandles); + const observer = new ResizeObserver(() => { + let width = panelGroup?.clientWidth; + if (!width) return; + width -= [...resizeHandles].reduce((acc, resizeHandle) => acc + resizeHandle.clientWidth, 0); + // Minimum size in pixels is a percentage of the PanelGroup's height, + // less the (fixed) height of the resize handles. + setMinSize((MIN_SIZE_IN_PIXELS / width) * 100); + }); + observer.observe(panelGroup); + for (const resizeHandle of resizeHandles) { + observer.observe(resizeHandle); + } + + return () => { + observer.disconnect(); + }; + }, []); + + return ( + + + + + + + {children} + + + ); +} \ No newline at end of file diff --git a/packages/client/src/components/layout/nav.tsx b/packages/client/src/components/layout/nav.tsx new file mode 100644 index 0000000..4ddb9be --- /dev/null +++ b/packages/client/src/components/layout/nav.tsx @@ -0,0 +1,43 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip" +import { Link } from "wouter" +import { MagnifyingGlassIcon, GearIcon, ActivityLogIcon, ArchiveIcon, PersonIcon } from "@radix-ui/react-icons" +import { buttonVariants } from "../ui/button" + +interface NavItemProps { + icon: React.ReactNode; + to: string; + name: string; +} + +export function NavItem({ + icon, + to, + name +}: NavItemProps) { + return + + + {icon} + {name} + + + {name} + +} + +export function NavList() { + return +} \ No newline at end of file diff --git a/packages/client/src/components/ui/button.tsx b/packages/client/src/components/ui/button.tsx new file mode 100644 index 0000000..0270f64 --- /dev/null +++ b/packages/client/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/packages/client/src/components/ui/resizable.tsx b/packages/client/src/components/ui/resizable.tsx new file mode 100644 index 0000000..35784da --- /dev/null +++ b/packages/client/src/components/ui/resizable.tsx @@ -0,0 +1,43 @@ +import { DragHandleDots2Icon } from "@radix-ui/react-icons" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/packages/client/src/components/ui/tooltip.tsx b/packages/client/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..a9c71ba --- /dev/null +++ b/packages/client/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/packages/client/src/lib/atom.ts b/packages/client/src/lib/atom.ts new file mode 100644 index 0000000..157b013 --- /dev/null +++ b/packages/client/src/lib/atom.ts @@ -0,0 +1,70 @@ +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 }; +} + +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 diff --git a/packages/client/src/page/404.tsx b/packages/client/src/page/404.tsx index beecbb4..af25627 100644 --- a/packages/client/src/page/404.tsx +++ b/packages/client/src/page/404.tsx @@ -1,15 +1,9 @@ -import { Typography } from "@mui/material"; -import React from "react"; -import { CommonMenuList, Headline } from "../component/mod"; -import { PagePad } from "../component/pagepad"; - export const NotFoundPage = () => { - const menu = CommonMenuList(); - return ( - - - 404 Not Found - - + return (
+

404 Not Found

+

찾을 수 없음

+
); }; + +export default NotFoundPage; \ No newline at end of file diff --git a/packages/client/src/page/galleryPage.tsx b/packages/client/src/page/galleryPage.tsx new file mode 100644 index 0000000..429a4b9 --- /dev/null +++ b/packages/client/src/page/galleryPage.tsx @@ -0,0 +1,6 @@ +export default function Gallery() { + return (
+ a +
+ ); +} \ No newline at end of file diff --git a/packages/client/src/state.tsx b/packages/client/src/state.tsx index 130780e..da6c0fb 100644 --- a/packages/client/src/state.tsx +++ b/packages/client/src/state.tsx @@ -1,5 +1,5 @@ import React, { createContext, useRef, useState } from "react"; -export const BackLinkContext = createContext({ backLink: "", setBackLink: (s: string) => {} }); + export const UserContext = createContext({ username: "", permission: [] as string[], diff --git a/packages/client/src/state/user.ts b/packages/client/src/state/user.ts new file mode 100644 index 0000000..0b82a5f --- /dev/null +++ b/packages/client/src/state/user.ts @@ -0,0 +1,109 @@ +import { atom, useAtomValue, setAtomValue } from "../lib/atom"; + +type LoginLocalStorage = { + username: string; + permission: string[]; + accessExpired: number; +}; + +let localObj: LoginLocalStorage | null = null; +function getUserSessions() { + if (localObj === null) { + const storagestr = localStorage.getItem("UserLoginContext") as string | null; + const storage = storagestr !== null ? (JSON.parse(storagestr) as LoginLocalStorage | null) : null; + localObj = storage; + } + if (localObj !== null && localObj.accessExpired > Math.floor(Date.now() / 1000)) { + return { + username: localObj.username, + permission: localObj.permission, + }; + } + return null; +} + +async function refresh() { + const res = await fetch("/user/refresh", { + method: "POST", + }); + if (res.status !== 200) throw new Error("Maybe Network Error"); + const r = (await res.json()) as LoginLocalStorage & { refresh: boolean }; + if (r.refresh) { + localObj = { + ...r + }; + } else { + localObj = { + accessExpired: 0, + username: "", + permission: r.permission, + }; + } + localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); + return { + username: r.username, + permission: r.permission, + }; +} + +export const doLogout = async () => { + const req = await fetch("/user/logout", { + method: "POST", + }); + const setVal = setAtomValue(userLoginStateAtom); + try { + const res = await req.json(); + localObj = { + accessExpired: 0, + username: "", + permission: res.permission, + }; + window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); + setVal(localObj); + return { + username: localObj.username, + permission: localObj.permission, + }; + } catch (error) { + console.error(`Server Error ${error}`); + return { + username: "", + permission: [], + }; + } +}; +export const doLogin = async (userLoginInfo: { + username: string; + password: string; +}): Promise => { + const res = await fetch("/user/login", { + method: "POST", + body: JSON.stringify(userLoginInfo), + headers: { "content-type": "application/json" }, + }); + const b = await res.json(); + if (res.status !== 200) { + return b.detail as string; + } + const setVal = setAtomValue(userLoginStateAtom); + localObj = b; + setVal(b); + window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); + return b; +}; + + +export async function getInitialValue() { + const user = getUserSessions(); + if (user) { + return user; + } + return refresh(); +} + +export const userLoginStateAtom = atom("userLoginState", getUserSessions()); + +export function useLogin() { + const val = useAtomValue(userLoginStateAtom); + return val; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfac0c9..25e9a14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,12 @@ importers: '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.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) + '@radix-ui/react-tooltip': + specifier: ^1.0.7 + version: 1.0.7(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -29,12 +35,21 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-resizable-panels: + specifier: ^2.0.16 + version: 2.0.16(react-dom@18.2.0)(react@18.2.0) + swr: + specifier: ^2.2.5 + version: 2.2.5(react@18.2.0) tailwind-merge: specifier: ^2.2.2 version: 2.2.2 tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.3) + wouter: + specifier: ^3.1.0 + version: 3.1.0(react@18.2.0) devDependencies: '@types/node': specifier: '>=20.0.0' @@ -817,6 +832,34 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@floating-ui/core@1.6.0: + resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} + dependencies: + '@floating-ui/utils': 0.2.1 + dev: false + + /@floating-ui/dom@1.6.3: + resolution: {integrity: sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==} + dependencies: + '@floating-ui/core': 1.6.0 + '@floating-ui/utils': 0.2.1 + dev: false + + /@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.6.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@floating-ui/utils@0.2.1: + resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -911,6 +954,86 @@ packages: requiresBuild: true optional: true + /@radix-ui/primitive@1.0.1: + resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + dependencies: + '@babel/runtime': 7.24.1 + dev: false + + /@radix-ui/react-arrow@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-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} + 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-compose-refs@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-context@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + 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/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@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) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.71)(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-icons@1.3.0(react@18.2.0): resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} peerDependencies: @@ -919,6 +1042,277 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + 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 + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 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-compose-refs': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@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) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/rect': 1.0.1 + '@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-portal@1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + 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-presence@1.0.1(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + 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-compose-refs': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.71)(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-primitive@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-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + 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-slot': 1.0.2(@types/react@18.2.71)(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: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} + 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/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-popper': 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-portal': 1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.22)(@types/react@18.2.71)(react-dom@18.2.0)(react@18.2.0) + '@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) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@radix-ui/react-visually-hidden': 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-use-callback-ref@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-rect@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.71)(react@18.2.0): + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.71)(react@18.2.0) + '@types/react': 18.2.71 + react: 18.2.0 + dev: false + + /@radix-ui/react-visually-hidden@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-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} + 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/rect@1.0.1: + resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} + dependencies: + '@babel/runtime': 7.24.1 + dev: false + /@rollup/rollup-android-arm-eabi@4.13.0: resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} cpu: [arm] @@ -1382,7 +1776,6 @@ packages: /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - dev: true /@types/qs@6.9.14: resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==} @@ -1396,7 +1789,6 @@ packages: resolution: {integrity: sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==} dependencies: '@types/react': 18.2.71 - dev: true /@types/react@18.2.71: resolution: {integrity: sha512-PxEsB9OjmQeYGffoWnYAd/r5FiJuUw2niFQHPc2v2idwh8wGPkkYzOHuinNJJY6NZqfoTCiOIizDOz38gYNsyw==} @@ -1404,7 +1796,6 @@ packages: '@types/prop-types': 15.7.12 '@types/scheduler': 0.23.0 csstype: 3.1.3 - dev: true /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -1414,7 +1805,6 @@ packages: /@types/scheduler@0.23.0: resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} - dev: true /@types/semver@7.5.8: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -1933,6 +2323,10 @@ packages: engines: {node: '>=6'} dev: true + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + /clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} dependencies: @@ -2079,7 +2473,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true /data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} @@ -3432,6 +3825,10 @@ packages: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} + /mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + dev: false + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -3934,6 +4331,16 @@ packages: scheduler: 0.23.0 dev: false + /react-resizable-panels@2.0.16(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UrnxmTZaTnbCl/xIOX38ig35RicqGfLuqt2x5fytpNlQvCRuxyXZwIBEhmF+pmrEGxfajyXFBoCplNxLvhF0CQ==} + peerDependencies: + react: ^16.14.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -3989,6 +4396,11 @@ packages: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} dev: false + /regexparam@3.0.0: + resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==} + engines: {node: '>=8'} + dev: false + /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} dev: true @@ -4384,6 +4796,16 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /swr@2.2.5(react@18.2.0): + resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + client-only: 0.0.1 + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /tailwind-merge@2.2.2: resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==} dependencies: @@ -4612,6 +5034,14 @@ packages: punycode: 2.3.1 dev: true + /use-sync-external-store@1.2.0(react@18.2.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4681,6 +5111,17 @@ packages: dependencies: isexe: 2.0.0 + /wouter@3.1.0(react@18.2.0): + resolution: {integrity: sha512-hou3w+12BMTBckdWdyJp/z7+kKcbdLDWfz6omSyrO6bbx4irNuQQyLDQkfSGXXJCxmglea3c8On9XFUkBSU8+Q==} + peerDependencies: + react: '>=16.8.0' + dependencies: + mitt: 3.0.1 + react: 18.2.0 + regexparam: 3.0.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'}