// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { useHeight } from "@/app/hook/useHeight"; import { useWidth } from "@/app/hook/useWidth"; import { WOS } from "@/store/global"; import { WshServer } from "@/store/wshserver"; import * as Plot from "@observablehq/plot"; import dayjs from "dayjs"; import * as htl from "htl"; import * as jotai from "jotai"; import * as React from "react"; import "./cpuplot.less"; type Point = { time: number; // note this is in seconds not milliseconds value: number; }; class CpuPlotViewModel { blockAtom: jotai.Atom; termMode: jotai.Atom; htmlElemFocusRef: React.RefObject; blockId: string; viewIcon: jotai.Atom; viewText: jotai.Atom; viewName: jotai.Atom; dataAtom: jotai.PrimitiveAtom>; addDataAtom: jotai.WritableAtom; width: number; incrementCount: jotai.WritableAtom>; constructor(blockId: string) { this.blockId = blockId; this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); this.width = 100; this.dataAtom = jotai.atom(this.getDefaultData()); this.addDataAtom = jotai.atom(null, (get, set, point) => { // not efficient but should be okay for a demo? const data = get(this.dataAtom); set(this.dataAtom, [...data.slice(1), point]); }); this.viewIcon = jotai.atom((get) => { return "chart-line"; // should not be hardcoded }); this.viewName = jotai.atom((get) => { return "CPU %"; // should not be hardcoded }); this.incrementCount = jotai.atom(null, async (get, set) => { const meta = get(this.blockAtom).meta; const count = meta.count ?? 0; await WshServer.SetMetaCommand({ oref: WOS.makeORef("block", this.blockId), meta: { count: count + 1 } }); }); } getDefaultData(): Array { // set it back one to avoid backwards line being possible const currentTime = Date.now() / 1000 - 1; const points = []; for (let i = this.width; i > -1; i--) { points.push({ time: currentTime - i, value: 0 }); } return points; } } function makeCpuPlotViewModel(blockId: string): CpuPlotViewModel { const cpuPlotViewModel = new CpuPlotViewModel(blockId); return cpuPlotViewModel; } function CpuPlotView({ model }: { model: CpuPlotViewModel; blockId: string }) { const containerRef = React.useRef(); const plotData = jotai.useAtomValue(model.dataAtom); const addPlotData = jotai.useSetAtom(model.addDataAtom); const parentHeight = useHeight(containerRef); const parentWidth = useWidth(containerRef); const block = jotai.useAtomValue(model.blockAtom); const incrementCount = jotai.useSetAtom(model.incrementCount); // temporary React.useEffect(() => { const temp = async () => { await incrementCount(); const dataGen = WshServer.StreamCpuDataCommand( { id: model.blockId, count: (block.meta?.count ?? 0) + 1 }, { timeout: 999999999, noresponse: false } ); try { for await (const datum of dataGen) { const ts = datum.ts; addPlotData({ time: ts / 1000, value: datum.values?.["cpu"] }); } } catch (e) { console.log(e); } }; temp(); }, []); React.useEffect(() => { const plot = Plot.plot({ x: { grid: true, label: "time", tickFormat: (d) => `${dayjs.unix(d).format("HH:mm:ss")}` }, y: { label: "%", domain: [0, 100] }, width: parentWidth, height: parentHeight, marks: [ () => htl.svg` `, Plot.lineY(plotData, { stroke: "#58C142", strokeWidth: 2, x: "time", y: "value", }), Plot.areaY(plotData, { fill: "url(#gradient)", x: "time", y: "value", }), ], }); if (plot !== undefined) { containerRef.current.append(plot); } return () => { if (plot !== undefined) { plot.remove(); } }; }, [plotData, parentHeight, parentWidth]); return
; } export { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel };