diff --git a/bun.lockb b/bun.lockb index 60009f1..379f46c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 43213d1..48a7b26 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@tanstack/react-query": "^5.32.0", + "jotai": "^2.8.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/src/client/App.css b/src/client/App.css index 53bf59d..fb0928e 100644 --- a/src/client/App.css +++ b/src/client/App.css @@ -10,7 +10,7 @@ background-color: var(--color-background0); padding: 16px; width: 100%; - height: max-content; + min-height: max-content; overflow: hidden; } @@ -20,18 +20,17 @@ @media (min-width: 640px) { @media (min-height: 640px) { - height: 100vh; - - > div { - height: 100%; - } - .chart { min-height: unset; } @media (orientation: landscape) { grid-template-columns: repeat(3, 1fr); + height: 100vh; + + > div { + height: 100%; + } } @media (orientation: portrait) { diff --git a/src/client/atoms/index.ts b/src/client/atoms/index.ts new file mode 100644 index 0000000..f2686c7 --- /dev/null +++ b/src/client/atoms/index.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai'; + +export const highFpsAtom = atom(true); +export const siAtom = atom(false); diff --git a/src/client/components/chart-cards/common/chart/index.tsx b/src/client/components/chart-cards/common/chart/index.tsx index db967c5..a4d3c1e 100644 --- a/src/client/components/chart-cards/common/chart/index.tsx +++ b/src/client/components/chart-cards/common/chart/index.tsx @@ -8,7 +8,6 @@ import { YAxis } from './y-axis'; const stepWindow = Number(import.meta.env.CLIENT_GRAPH_STEPS); const stepPeriod = Number(import.meta.env.CLIENT_REFETCH_INTERVAL); const xMargin = 4; -const fps = 30; type Props = { total: number; @@ -33,7 +32,7 @@ export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, data, fo } if (!domain || historyMax > domain[1]) { - return 1.25 * historyMax; + return historyMax; } return domain[1]; @@ -129,7 +128,7 @@ export const CanvasChart = ({ total, hueOffset = 0, domain, hardDomain, data, fo drawSeries('fill'); drawSeries('stroke'); - }, fps); + }); return (
diff --git a/src/client/components/chart-cards/disks.tsx b/src/client/components/chart-cards/disks.tsx index 1469ed0..cf3dfd3 100644 --- a/src/client/components/chart-cards/disks.tsx +++ b/src/client/components/chart-cards/disks.tsx @@ -1,8 +1,11 @@ +import { siAtom } from '@/atoms'; import { useQuery } from '@tanstack/react-query'; +import { useAtomValue } from 'jotai'; import { ChartCard } from './common/card'; export const Disks = () => { const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const isSi = useAtomValue(siAtom); if (!dynamicData) { return
; @@ -15,7 +18,7 @@ export const Disks = () => { labels: ['Read', 'Write'], }} hueOffset={120} - formatOptions={{ units: 'B/s' }} + formatOptions={{ units: 'B/s', ...(isSi && { si: true }) }} data={[dynamicData.disks.read, dynamicData.disks.write]} total={2} /> diff --git a/src/client/components/chart-cards/memory.tsx b/src/client/components/chart-cards/memory.tsx index ed3e48b..967f2b5 100644 --- a/src/client/components/chart-cards/memory.tsx +++ b/src/client/components/chart-cards/memory.tsx @@ -1,20 +1,22 @@ +import { siAtom } from '@/atoms'; import { formatValue } from '@/utils/format'; import { useQuery } from '@tanstack/react-query'; +import { useAtomValue } from 'jotai'; import { useMemo } from 'react'; import { ChartCard } from './common/card'; -const formatOptions = { units: 'B' }; - export const Memory = () => { const { data: staticData } = useQuery({ queryKey: ['static'] }); const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const isSi = useAtomValue(siAtom); + const formatOptions = { units: 'B', ...(isSi && { si: true }) }; const formatedTotals = useMemo(() => { if (!staticData) { return []; } return [formatValue(staticData.total_memory, formatOptions), formatValue(staticData.total_swap, formatOptions)]; - }, [staticData]); + }, [staticData, formatOptions]); if (!staticData || !dynamicData) { return
; diff --git a/src/client/components/chart-cards/network.tsx b/src/client/components/chart-cards/network.tsx index 349802a..566653b 100644 --- a/src/client/components/chart-cards/network.tsx +++ b/src/client/components/chart-cards/network.tsx @@ -1,8 +1,11 @@ +import { siAtom } from '@/atoms'; import { useQuery } from '@tanstack/react-query'; +import { useAtomValue } from 'jotai'; import { ChartCard } from './common/card'; export const Network = () => { const { data: dynamicData } = useQuery({ queryKey: ['dynamic'] }); + const isSi = useAtomValue(siAtom); if (!dynamicData) { return
; @@ -15,7 +18,7 @@ export const Network = () => { labels: ['Down', 'Up'], }} hueOffset={60} - formatOptions={{ units: 'B/s' }} + formatOptions={{ units: 'B/s', ...(isSi && { si: true }) }} data={[dynamicData.network.down, dynamicData.network.up]} total={2} /> diff --git a/src/client/components/chart-cards/static.css b/src/client/components/chart-cards/static.css new file mode 100644 index 0000000..63e9ec4 --- /dev/null +++ b/src/client/components/chart-cards/static.css @@ -0,0 +1,7 @@ +.switches { + display: flex; + flex-direction: column; + flex-wrap: wrap; + gap: 8px; + padding: 4px 0px; +} \ No newline at end of file diff --git a/src/client/components/chart-cards/static.tsx b/src/client/components/chart-cards/static.tsx index 20e5b15..f3737c8 100644 --- a/src/client/components/chart-cards/static.tsx +++ b/src/client/components/chart-cards/static.tsx @@ -1,7 +1,10 @@ +import { highFpsAtom, siAtom } from '@/atoms'; +import { Switch } from '@/components/switch'; import { useAnimationFrame } from '@/hooks/use-animation-frame'; import { useQuery } from '@tanstack/react-query'; +import { useAtom } from 'jotai'; import { useEffect, useRef, useState } from 'react'; -import { Switch } from '../switch'; +import './static.css'; const formatUptime = (value: number) => { const seconds = String(Math.floor(value % 60)).padStart(2, '0'); @@ -40,6 +43,8 @@ export const Static = () => { const { data: staticData } = useQuery({ queryKey: ['static'] }); const root = useRef(document.getElementById('root')!); const [dark, setDark] = useState(window.matchMedia('(prefers-color-scheme: dark)').matches); + const [highFps, setHighFps] = useAtom(highFpsAtom); + const [isSi, setIsSi] = useAtom(siAtom); useEffect(() => { root.current.setAttribute('data-theme', dark ? 'dark' : 'light'); @@ -59,11 +64,25 @@ export const Static = () => {

{staticData.kernel_version}

{staticData.boot_time && } - setDark(target.checked)} - /> +
+ setDark(target.checked)} + /> + + setHighFps(target.checked)} + /> + + setIsSi(target.checked)} + /> +
) ); diff --git a/src/client/hooks/use-animation-frame.ts b/src/client/hooks/use-animation-frame.ts index 97957a6..ea06899 100644 --- a/src/client/hooks/use-animation-frame.ts +++ b/src/client/hooks/use-animation-frame.ts @@ -1,9 +1,13 @@ +import { highFpsAtom } from '@/atoms'; +import { useAtomValue } from 'jotai'; import { useEffect, useRef } from 'react'; -export const useAnimationFrame = (callback: (dt: number) => void, fps = 60) => { +export const useAnimationFrame = (callback: (dt: number) => void, fps?: number) => { const ignored = useRef(0); const requestRef = useRef(); const previousTimeRef = useRef(); + const highFps = useAtomValue(highFpsAtom); + const autoFps = fps ?? (highFps ? 30 : 4); useEffect(() => { const animate: FrameRequestCallback = time => { @@ -11,7 +15,7 @@ export const useAnimationFrame = (callback: (dt: number) => void, fps = 60) => { const deltaTime = time - previousTimeRef.current; ignored.current += deltaTime; - if (ignored.current > 1000 / fps) { + if (ignored.current > 1000 / autoFps) { ignored.current = 0; callback(deltaTime); } @@ -26,5 +30,5 @@ export const useAnimationFrame = (callback: (dt: number) => void, fps = 60) => { cancelAnimationFrame(requestRef.current); } }; - }, [callback, fps]); + }, [callback, autoFps]); };