diff --git a/app/src/ui/components/buttons/IconButton.tsx b/app/src/ui/components/buttons/IconButton.tsx index 30d0263..145b2f8 100644 --- a/app/src/ui/components/buttons/IconButton.tsx +++ b/app/src/ui/components/buttons/IconButton.tsx @@ -10,9 +10,9 @@ export type IconType = const styles = { xs: { className: "p-0.5", size: 13 }, - sm: { className: "p-0.5", size: 16 }, - md: { className: "p-1", size: 20 }, - lg: { className: "p-1.5", size: 24 } + sm: { className: "p-0.5", size: 15 }, + md: { className: "p-1", size: 18 }, + lg: { className: "p-1.5", size: 22 } } as const; interface IconButtonProps extends ComponentPropsWithoutRef<"button"> { diff --git a/app/src/ui/hooks/use-effect.ts b/app/src/ui/hooks/use-effect.ts new file mode 100644 index 0000000..539bc7b --- /dev/null +++ b/app/src/ui/hooks/use-effect.ts @@ -0,0 +1,32 @@ +import { useEffect, useRef } from "react"; + +export function useEffectOnce(effect: () => void | (() => void | undefined), deps: any[]): void { + const hasRunRef = useRef(false); + const savedDepsRef = useRef(deps); + + useEffect(() => { + const depsChanged = !hasRunRef.current || !areDepsEqual(savedDepsRef.current, deps); + + if (depsChanged) { + hasRunRef.current = true; + savedDepsRef.current = deps; + return effect(); + } + }, [deps]); +} + +function areDepsEqual(prevDeps: any[] | undefined, nextDeps: any[]): boolean { + if (prevDeps && prevDeps.length === 0 && nextDeps.length === 0) { + return true; + } + + if (!prevDeps && nextDeps.length === 0) { + return true; + } + + if (!prevDeps || !nextDeps || prevDeps.length !== nextDeps.length) { + return false; + } + + return prevDeps.every((dep, index) => Object.is(dep, nextDeps[index])); +} diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index b6862e0..d096731 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -40,8 +40,8 @@ export function DataEntityList({ params }) { useBrowserTitle(["Data", entity?.label ?? params.entity]); const [navigate] = useNavigate(); const search = useSearch(searchSchema, { - select: entity?.getSelect(undefined, "table") ?? [], - sort: entity?.getDefaultSort() + select: undefined, + sort: undefined }); const $q = useApiQuery( @@ -50,7 +50,7 @@ export function DataEntityList({ params }) { select: search.value.select, limit: search.value.perPage, offset: (search.value.page - 1) * search.value.perPage, - sort: search.value.sort + sort: `${search.value.sort.dir === "asc" ? "" : "-"}${search.value.sort.by}` }), { enabled: !!entity, @@ -131,7 +131,7 @@ export function DataEntityList({ params }) { { - const { verify } = useAuth(); + const { verify, user } = useAuth(); - useEffect(() => { + useEffectOnce(() => { verify(); - }, []); + }, [user?.id]); return ( diff --git a/app/vite.dev.ts b/app/vite.dev.ts index 244b84d..0d27e5a 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -7,12 +7,11 @@ import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAd registries.media.register("local", StorageLocalAdapter); -const run_example: string | boolean = false; -//run_example = "ex-admin-rich"; +const example = import.meta.env.VITE_EXAMPLE; -const credentials = run_example +const credentials = example ? { - url: `file:.configs/${run_example}.db` + url: `file:.configs/${example}.db` //url: ":memory:" } : { @@ -26,10 +25,8 @@ if (!credentials.url) { const connection = new LibsqlConnection(createClient(credentials)); let initialConfig: any = undefined; -if (run_example) { - const { version, ...config } = JSON.parse( - await readFile(`.configs/${run_example}.json`, "utf-8") - ); +if (example) { + const { version, ...config } = JSON.parse(await readFile(`.configs/${example}.json`, "utf-8")); initialConfig = config; }