"use client"; import { useEffect, useState } from "react"; import { usePathname } from "next/navigation"; import { TanStackDevtools } from "@tanstack/react-devtools"; import type { TanStackDevtoolsReactPlugin } from "@tanstack/react-devtools"; import type { QueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query"; import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; const valueStyle = { fontFamily: "monospace", overflowWrap: "anywhere" } as const; const metricStyle = { background: "color-mix(in srgb, currentColor 4%, transparent)", border: "1px solid color-mix(in srgb, currentColor 18%, transparent)", borderRadius: 8, padding: 12, } as const; const readDiagnostics = (queryClient: QueryClient) => { const queries = queryClient.getQueryCache().getAll(); const mutations = queryClient.getMutationCache().getAll(); const board = document.querySelector("[data-homarr-dev-benchmark-board]"); const widgetItems = board?.querySelectorAll('[data-type="item"]').length ?? 0; const readyWidgets = board?.querySelectorAll("[data-homarr-widget-ready]").length ?? 0; const resources = performance.getEntriesByType("resource") as PerformanceResourceTiming[]; return { active: queries.filter((query) => query.getObserversCount() > 0).length, errors: queries.filter((query) => query.state.status === "error").length, fetching: queries.filter((query) => query.state.fetchStatus === "fetching").length, pendingMutations: mutations.filter((mutation) => mutation.state.status === "pending").length, stale: queries.filter((query) => query.isStale()).length, total: queries.length, readyWidgets, widgetItems, scriptKiB: Math.round( resources .filter((resource) => resource.initiatorType === "script") .reduce((total, resource) => total + resource.decodedBodySize, 0) / 1024, ), }; }; const Metric = ({ label, value }: { label: string; value: number | string }) => (
{label}
{value}
); const HomarrDevtoolsPanel = ({ active }: { active: boolean }) => { const pathname = usePathname(); const queryClient = useQueryClient(); const [diagnostics, setDiagnostics] = useState(() => readDiagnostics(queryClient)); useEffect(() => { if (!active) return; const update = () => setDiagnostics(readDiagnostics(queryClient)); let frame: number | undefined; const scheduleUpdate = () => { if (frame !== undefined) return; frame = window.requestAnimationFrame(() => { frame = undefined; update(); }); }; const unsubscribeQueries = queryClient.getQueryCache().subscribe(scheduleUpdate); const unsubscribeMutations = queryClient.getMutationCache().subscribe(scheduleUpdate); const observer = new MutationObserver(scheduleUpdate); observer.observe(document.body, { childList: true, subtree: true }); update(); return () => { unsubscribeQueries(); unsubscribeMutations(); observer.disconnect(); if (frame !== undefined) window.cancelAnimationFrame(frame); }; }, [active, pathname, queryClient]); if (!active) return null; return (

Homarr performance

Live state for {pathname}

); }; const developmentToolsPlugins = [ { id: "tanstack-query", name: "TanStack Query", render: () => , }, { id: "homarr-performance", name: "Homarr", defaultOpen: true, render: (_element, { devtoolsOpen }) => , }, ] satisfies TanStackDevtoolsReactPlugin[]; export const DevelopmentTools = () => ( );