From d656e061931625545863ee089d33b71b025e0dc1 Mon Sep 17 00:00:00 2001 From: luisdralves Date: Tue, 7 May 2024 22:44:48 +0100 Subject: [PATCH] Add switches to static card --- bun.lockb | Bin 82366 -> 82760 bytes package.json | 1 + src/client/App.css | 13 ++++---- src/client/atoms/index.ts | 4 +++ .../chart-cards/common/chart/index.tsx | 5 ++- src/client/components/chart-cards/disks.tsx | 5 ++- src/client/components/chart-cards/memory.tsx | 8 +++-- src/client/components/chart-cards/network.tsx | 5 ++- src/client/components/chart-cards/static.css | 7 ++++ src/client/components/chart-cards/static.tsx | 31 ++++++++++++++---- src/client/hooks/use-animation-frame.ts | 10 ++++-- 11 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 src/client/atoms/index.ts create mode 100644 src/client/components/chart-cards/static.css diff --git a/bun.lockb b/bun.lockb index 60009f15469fd16770abb0102d7a89335f760c0b..379f46cb893a0838dab5062c178092b670910d65 100755 GIT binary patch delta 12294 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 c>DA-J;*gr6VdCp- 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 EUypE92mk;8 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]); };