mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-21 21:32:13 +01:00
put some styles in mixins
This commit is contained in:
parent
06c04c0f24
commit
02f8c02a44
@ -1,6 +1,8 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@import "@/app/mixins.less";
|
||||
|
||||
.button {
|
||||
// override default button appearance
|
||||
border: 1px solid transparent;
|
||||
@ -155,150 +157,10 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
// customs styles here
|
||||
&.border-radius-2 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.border-radius-3 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.border-radius-4 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.border-radius-5 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.border-radius-6 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.border-radius-10 {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&.vertical-padding-0 {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
&.vertical-padding-1 {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
&.vertical-padding-2 {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
&.vertical-padding-3 {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
&.vertical-padding-4 {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&.vertical-padding-5 {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
&.vertical-padding-6 {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
&.vertical-padding-7 {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
&.vertical-padding-8 {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
&.vertical-padding-9 {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
&.vertical-padding-10 {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-0 {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-1 {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-2 {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-3 {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-4 {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-5 {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-6 {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-7 {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-8 {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-9 {
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
}
|
||||
|
||||
&.horizontal-padding-10 {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&.font-size-11 {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&.font-weight-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.font-weight-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
// Include mixins
|
||||
.border-radius-mixin();
|
||||
.vertical-padding-mixin();
|
||||
.horizontal-padding-mixin();
|
||||
.font-size-mixin();
|
||||
.font-weight-mixin();
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useLongClick } from "@/app/hook/useLongClick";
|
||||
import { makeIconClass } from "@/util/util";
|
||||
import clsx from "clsx";
|
||||
|
@ -51,8 +51,7 @@
|
||||
color: var(--main-text-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--button-text-color);
|
||||
background-color: var(--button-grey-hover-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
import { Avatar } from "./avatar";
|
||||
import { List } from "./list";
|
||||
|
||||
import "./list.less";
|
||||
|
||||
const meta: Meta<typeof List> = {
|
||||
@ -60,6 +62,57 @@ const nestedItems = [
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Important clicked"),
|
||||
},
|
||||
{
|
||||
text: "Inbox",
|
||||
icon: <i className="fa-sharp fa-solid fa-inbox"></i>,
|
||||
onClick: () => console.log("Inbox clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Starred",
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Starred clicked"),
|
||||
},
|
||||
{
|
||||
text: "Important",
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Important clicked"),
|
||||
},
|
||||
{
|
||||
text: "Inbox",
|
||||
icon: <i className="fa-sharp fa-solid fa-inbox"></i>,
|
||||
onClick: () => console.log("Inbox clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Starred",
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Starred clicked"),
|
||||
},
|
||||
{
|
||||
text: "Important",
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Important clicked"),
|
||||
},
|
||||
{
|
||||
text: "Inbox",
|
||||
icon: <i className="fa-sharp fa-solid fa-inbox"></i>,
|
||||
onClick: () => console.log("Inbox clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Starred",
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Starred clicked"),
|
||||
},
|
||||
{
|
||||
text: "Important",
|
||||
icon: <i className="fa-sharp fa-solid fa-star"></i>,
|
||||
onClick: () => console.log("Important clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -141,3 +194,32 @@ export const NestedWithClickHandlers: Story = {
|
||||
</Container>
|
||||
),
|
||||
};
|
||||
|
||||
const avatarItems = [
|
||||
{
|
||||
text: "John Doe",
|
||||
icon: <Avatar name="John Doe" status="online" />,
|
||||
onClick: () => console.log("John Doe clicked"),
|
||||
},
|
||||
{
|
||||
text: "Jane Smith",
|
||||
icon: <Avatar name="Jane Smith" status="busy" />,
|
||||
onClick: () => console.log("Jane Smith clicked"),
|
||||
},
|
||||
{
|
||||
text: "Robert Brown",
|
||||
icon: <Avatar name="Robert Brown" status="away" />,
|
||||
onClick: () => console.log("Robert Brown clicked"),
|
||||
},
|
||||
];
|
||||
|
||||
export const WithAvatars: Story = {
|
||||
args: {
|
||||
items: avatarItems,
|
||||
},
|
||||
render: (args) => (
|
||||
<Container>
|
||||
<List {...args} />
|
||||
</Container>
|
||||
),
|
||||
};
|
||||
|
@ -21,23 +21,27 @@ interface ListProps {
|
||||
const List = memo(({ items, className, renderItem }: ListProps) => {
|
||||
const [open, setOpen] = useState<{ [key: string]: boolean }>({});
|
||||
|
||||
const handleClick = (item: ListItem) => {
|
||||
setOpen((prevState) => ({ ...prevState, [item.text]: !prevState[item.text] }));
|
||||
// Helper function to generate a unique key for each item based on its path in the hierarchy
|
||||
const getItemKey = (item: ListItem, path: string) => `${path}-${item.text}`;
|
||||
|
||||
const handleClick = (item: ListItem, itemKey: string) => {
|
||||
setOpen((prevState) => ({ ...prevState, [itemKey]: !prevState[itemKey] }));
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
const renderListItem = (item: ListItem, index: number) => {
|
||||
const isOpen = open[item.text] === true;
|
||||
const renderListItem = (item: ListItem, index: number, path: string) => {
|
||||
const itemKey = getItemKey(item, path); // Generate unique key based on the path
|
||||
const isOpen = open[itemKey] === true;
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
|
||||
return (
|
||||
<li key={index} className={clsx("list-item", className)}>
|
||||
<li key={itemKey} className={clsx("list-item", className)}>
|
||||
{renderItem ? (
|
||||
renderItem(item, isOpen, () => handleClick(item))
|
||||
renderItem(item, isOpen, () => handleClick(item, itemKey))
|
||||
) : (
|
||||
<div className="list-item-button" onClick={() => handleClick(item)}>
|
||||
<div className="list-item-button" onClick={() => handleClick(item, itemKey)}>
|
||||
<div className="list-item-content">
|
||||
<div className="list-item-icon">{item.icon}</div>
|
||||
<div className="list-item-text">{item.text}</div>
|
||||
@ -49,14 +53,16 @@ const List = memo(({ items, className, renderItem }: ListProps) => {
|
||||
)}
|
||||
{hasChildren && (
|
||||
<ul className={`nested-list ${isOpen ? "open" : "closed"}`}>
|
||||
{item.children.map((child, childIndex) => renderListItem(child, childIndex))}
|
||||
{item.children.map((child, childIndex) =>
|
||||
renderListItem(child, childIndex, `${path}-${index}`)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
return <ul className="list">{items.map((item, index) => renderListItem(item, index))}</ul>;
|
||||
return <ul className="list">{items.map((item, index) => renderListItem(item, index, "root"))}</ul>;
|
||||
});
|
||||
|
||||
List.displayName = "List";
|
||||
|
@ -7,3 +7,205 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.border-radius-mixin() {
|
||||
&.border-radius-2 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
&.border-radius-3 {
|
||||
border-radius: 3px;
|
||||
}
|
||||
&.border-radius-4 {
|
||||
border-radius: 4px;
|
||||
}
|
||||
&.border-radius-5 {
|
||||
border-radius: 5px;
|
||||
}
|
||||
&.border-radius-6 {
|
||||
border-radius: 6px;
|
||||
}
|
||||
&.border-radius-7 {
|
||||
border-radius: 7px;
|
||||
}
|
||||
&.border-radius-8 {
|
||||
border-radius: 8px;
|
||||
}
|
||||
&.border-radius-9 {
|
||||
border-radius: 9px;
|
||||
}
|
||||
&.border-radius-10 {
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-padding-mixin() {
|
||||
&.vertical-padding-0 {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
&.vertical-padding-1 {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
&.vertical-padding-2 {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
&.vertical-padding-3 {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
&.vertical-padding-4 {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
&.vertical-padding-5 {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
&.vertical-padding-6 {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
&.vertical-padding-7 {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
&.vertical-padding-8 {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
&.vertical-padding-9 {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
&.vertical-padding-10 {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-padding-mixin() {
|
||||
&.horizontal-padding-0 {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
&.horizontal-padding-1 {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
}
|
||||
&.horizontal-padding-2 {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
&.horizontal-padding-3 {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
}
|
||||
&.horizontal-padding-4 {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
&.horizontal-padding-5 {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
&.horizontal-padding-6 {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
&.horizontal-padding-7 {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
&.horizontal-padding-8 {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
&.horizontal-padding-9 {
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
}
|
||||
&.horizontal-padding-10 {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.font-size-mixin() {
|
||||
&.font-size-10 {
|
||||
font-size: 10px;
|
||||
}
|
||||
&.font-size-11 {
|
||||
font-size: 11px;
|
||||
}
|
||||
&.font-size-12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
&.font-size-13 {
|
||||
font-size: 13px;
|
||||
}
|
||||
&.font-size-14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
&.font-size-15 {
|
||||
font-size: 15px;
|
||||
}
|
||||
&.font-size-16 {
|
||||
font-size: 16px;
|
||||
}
|
||||
&.font-size-17 {
|
||||
font-size: 17px;
|
||||
}
|
||||
&.font-size-18 {
|
||||
font-size: 18px;
|
||||
}
|
||||
&.font-size-19 {
|
||||
font-size: 19px;
|
||||
}
|
||||
&.font-size-20 {
|
||||
font-size: 20px;
|
||||
}
|
||||
&.font-size-21 {
|
||||
font-size: 21px;
|
||||
}
|
||||
&.font-size-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
&.font-size-23 {
|
||||
font-size: 23px;
|
||||
}
|
||||
&.font-size-24 {
|
||||
font-size: 24px;
|
||||
}
|
||||
&.font-size-25 {
|
||||
font-size: 25px;
|
||||
}
|
||||
&.font-size-26 {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.font-weight-mixin() {
|
||||
&.font-weight-100 {
|
||||
font-weight: 100;
|
||||
}
|
||||
&.font-weight-200 {
|
||||
font-weight: 200;
|
||||
}
|
||||
&.font-weight-300 {
|
||||
font-weight: 300;
|
||||
}
|
||||
&.font-weight-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
&.font-weight-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
&.font-weight-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
&.font-weight-700 {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
@ -1,302 +1,263 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { getConnStatusAtom, globalStore, WOS } from "@/store/global";
|
||||
import * as util from "@/util/util";
|
||||
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 "./Layout.css";
|
||||
|
||||
import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions";
|
||||
import { waveEventSubscribe } from "@/app/store/wps";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { WindowRpcClient } from "@/app/store/wshrpcutil";
|
||||
import "./cpuplot.less";
|
||||
|
||||
const DefaultNumPoints = 120;
|
||||
|
||||
type DataItem = {
|
||||
ts: number;
|
||||
[k: string]: number;
|
||||
};
|
||||
|
||||
const SysInfoMetricNames = {
|
||||
cpu: "CPU %",
|
||||
"mem:total": "Memory Total",
|
||||
"mem:used": "Memory Used",
|
||||
"mem:free": "Memory Free",
|
||||
"mem:available": "Memory Available",
|
||||
};
|
||||
for (let i = 0; i < 32; i++) {
|
||||
SysInfoMetricNames[`cpu:${i}`] = `CPU[${i}] %`;
|
||||
}
|
||||
|
||||
function convertWaveEventToDataItem(event: WaveEvent): DataItem {
|
||||
const eventData: TimeSeriesData = event.data;
|
||||
if (eventData == null || eventData.ts == null || eventData.values == null) {
|
||||
return null;
|
||||
}
|
||||
const dataItem = { ts: eventData.ts };
|
||||
for (const key in eventData.values) {
|
||||
dataItem[key] = eventData.values[key];
|
||||
}
|
||||
return dataItem;
|
||||
}
|
||||
|
||||
class ChatViewModel {
|
||||
viewType: string;
|
||||
blockAtom: jotai.Atom<Block>;
|
||||
termMode: jotai.Atom<string>;
|
||||
htmlElemFocusRef: React.RefObject<HTMLInputElement>;
|
||||
blockId: string;
|
||||
viewIcon: jotai.Atom<string>;
|
||||
viewText: jotai.Atom<string>;
|
||||
viewName: jotai.Atom<string>;
|
||||
dataAtom: jotai.PrimitiveAtom<Array<DataItem>>;
|
||||
addDataAtom: jotai.WritableAtom<unknown, [DataItem[]], void>;
|
||||
incrementCount: jotai.WritableAtom<unknown, [], Promise<void>>;
|
||||
loadingAtom: jotai.PrimitiveAtom<boolean>;
|
||||
numPoints: jotai.Atom<number>;
|
||||
metrics: jotai.Atom<string[]>;
|
||||
connection: jotai.Atom<string>;
|
||||
manageConnection: jotai.Atom<boolean>;
|
||||
connStatus: jotai.Atom<ConnStatus>;
|
||||
|
||||
constructor(blockId: string) {
|
||||
this.viewType = "cpuplot";
|
||||
this.blockId = blockId;
|
||||
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
||||
this.addDataAtom = jotai.atom(null, (get, set, points) => {
|
||||
const targetLen = get(this.numPoints) + 1;
|
||||
let data = get(this.dataAtom);
|
||||
try {
|
||||
if (data.length > targetLen) {
|
||||
data = data.slice(data.length - targetLen);
|
||||
}
|
||||
if (data.length < targetLen) {
|
||||
const defaultData = this.getDefaultData();
|
||||
data = [...defaultData.slice(defaultData.length - targetLen + data.length), ...data];
|
||||
}
|
||||
const newData = [...data.slice(points.length), ...points];
|
||||
set(this.dataAtom, newData);
|
||||
} catch (e) {
|
||||
console.log("Error adding data to cpuplot", e);
|
||||
}
|
||||
});
|
||||
this.manageConnection = jotai.atom(true);
|
||||
this.loadingAtom = jotai.atom(true);
|
||||
this.numPoints = jotai.atom((get) => {
|
||||
const blockData = get(this.blockAtom);
|
||||
const metaNumPoints = blockData?.meta?.["graph:numpoints"];
|
||||
if (metaNumPoints == null || metaNumPoints <= 0) {
|
||||
return DefaultNumPoints;
|
||||
}
|
||||
return metaNumPoints;
|
||||
});
|
||||
this.metrics = jotai.atom((get) => {
|
||||
const blockData = get(this.blockAtom);
|
||||
const metrics = blockData?.meta?.["graph:metrics"];
|
||||
if (metrics == null || !Array.isArray(metrics)) {
|
||||
return ["cpu"];
|
||||
}
|
||||
return metrics;
|
||||
});
|
||||
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 RpcApi.SetMetaCommand(WindowRpcClient, {
|
||||
oref: WOS.makeORef("block", this.blockId),
|
||||
meta: { count: count + 1 },
|
||||
});
|
||||
});
|
||||
this.connection = jotai.atom((get) => {
|
||||
const blockData = get(this.blockAtom);
|
||||
const connValue = blockData?.meta?.connection;
|
||||
if (util.isBlank(connValue)) {
|
||||
return "local";
|
||||
}
|
||||
return connValue;
|
||||
});
|
||||
this.dataAtom = jotai.atom(this.getDefaultData());
|
||||
this.loadInitialData();
|
||||
this.connStatus = jotai.atom((get) => {
|
||||
const blockData = get(this.blockAtom);
|
||||
const connName = blockData?.meta?.connection;
|
||||
const connAtom = getConnStatusAtom(connName);
|
||||
return get(connAtom);
|
||||
});
|
||||
}
|
||||
|
||||
async loadInitialData() {
|
||||
globalStore.set(this.loadingAtom, true);
|
||||
try {
|
||||
const numPoints = globalStore.get(this.numPoints);
|
||||
const connName = globalStore.get(this.connection);
|
||||
const initialData = await RpcApi.EventReadHistoryCommand(WindowRpcClient, {
|
||||
event: "sysinfo",
|
||||
scope: connName,
|
||||
maxitems: numPoints,
|
||||
});
|
||||
if (initialData == null) {
|
||||
return;
|
||||
}
|
||||
const newData = this.getDefaultData();
|
||||
const initialDataItems: DataItem[] = initialData.map(convertWaveEventToDataItem);
|
||||
// splice the initial data into the default data (replacing the newest points)
|
||||
newData.splice(newData.length - initialDataItems.length, initialDataItems.length, ...initialDataItems);
|
||||
globalStore.set(this.addDataAtom, newData);
|
||||
} catch (e) {
|
||||
console.log("Error loading initial data for cpuplot", e);
|
||||
} finally {
|
||||
globalStore.set(this.loadingAtom, false);
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultData(): DataItem[] {
|
||||
// set it back one to avoid backwards line being possible
|
||||
const numPoints = globalStore.get(this.numPoints);
|
||||
const currentTime = Date.now() - 1000;
|
||||
const points: DataItem[] = [];
|
||||
for (let i = numPoints; i > -1; i--) {
|
||||
points.push({ ts: currentTime - i * 1000 });
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
function makeChatViewModel(blockId: string): ChatViewModel {
|
||||
const cpuPlotViewModel = new ChatViewModel(blockId);
|
||||
return cpuPlotViewModel;
|
||||
}
|
||||
|
||||
const plotColors = ["#58C142", "#FFC107", "#FF5722", "#2196F3", "#9C27B0", "#00BCD4", "#FFEB3B", "#795548"];
|
||||
|
||||
type ChatViewProps = {
|
||||
blockId: string;
|
||||
model: ChatViewModel;
|
||||
};
|
||||
|
||||
function ChatView({ model, blockId }: ChatViewProps) {
|
||||
const connName = jotai.useAtomValue(model.connection);
|
||||
const lastConnName = React.useRef(connName);
|
||||
const connStatus = jotai.useAtomValue(model.connStatus);
|
||||
const addPlotData = jotai.useSetAtom(model.addDataAtom);
|
||||
const loading = jotai.useAtomValue(model.loadingAtom);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (connStatus?.status != "connected") {
|
||||
return;
|
||||
}
|
||||
if (lastConnName.current !== connName) {
|
||||
lastConnName.current = connName;
|
||||
model.loadInitialData();
|
||||
}
|
||||
}, [connStatus.status, connName]);
|
||||
React.useEffect(() => {
|
||||
const unsubFn = waveEventSubscribe({
|
||||
eventType: "sysinfo",
|
||||
scope: connName,
|
||||
handler: (event) => {
|
||||
const loading = globalStore.get(model.loadingAtom);
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
const dataItem = convertWaveEventToDataItem(event);
|
||||
addPlotData([dataItem]);
|
||||
const channels = [
|
||||
{
|
||||
text: "Channel 1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 1 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 2",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 2 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 2.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 2.1 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 2.1.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 2.1.1 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 2.1.2",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 2.1.2 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
console.log("subscribe to sysinfo", connName);
|
||||
return () => {
|
||||
unsubFn();
|
||||
};
|
||||
}, [connName]);
|
||||
if (connStatus?.status != "connected") {
|
||||
return null;
|
||||
}
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
return <ChatViewInner key={connStatus?.connection ?? "local"} blockId={blockId} model={model} />;
|
||||
}
|
||||
{
|
||||
text: "Channel 2.2",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 2.2 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 3",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 3 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 3.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 3.1 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 4",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 4 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 5",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 5 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 5.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 5.1 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 5.1.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 5.1.1 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 5.1.2",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 5.1.2 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 5.1.2.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 5.1.2.1 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 6",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 6 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 7",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 7 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 7.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 7.1 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 8",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 8 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 9",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 9 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 9.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 9.1 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 9.1.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 9.1.1 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 9.1.2",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 9.1.2 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 10",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 10 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 11",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 11 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 12",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 12 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 13",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 13 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 14",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 14 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 14.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 14.1 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 15",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 15 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 16",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 16 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 17",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 17 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 17.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 17.1 clicked"),
|
||||
children: [
|
||||
{
|
||||
text: "Channel 17.1.1",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 17.1.1 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 17.1.2",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 17.1.2 clicked"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Channel 18",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 18 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 19",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 19 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 20",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 20 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 21",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 21 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 22",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 22 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 23",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 23 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 24",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 24 clicked"),
|
||||
},
|
||||
{
|
||||
text: "Channel 25",
|
||||
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
|
||||
onClick: () => console.log("Channel 25 clicked"),
|
||||
},
|
||||
];
|
||||
|
||||
const ChatViewInner = React.memo(({ model }: ChatViewProps) => {
|
||||
const containerRef = React.useRef<HTMLInputElement>();
|
||||
const plotData = jotai.useAtomValue(model.dataAtom);
|
||||
const domRect = useDimensionsWithExistingRef(containerRef, 30);
|
||||
const parentHeight = domRect?.height ?? 0;
|
||||
const parentWidth = domRect?.width ?? 0;
|
||||
const yvals = jotai.useAtomValue(model.metrics);
|
||||
const Layout = ({ columns }) => {
|
||||
return (
|
||||
<div className="layout">
|
||||
{columns.map((column, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="layout-column"
|
||||
style={{
|
||||
flexBasis: column.width === "fluid" ? "auto" : `${column.width}px`,
|
||||
flexGrow: column.width === "fluid" ? 1 : 0,
|
||||
flexShrink: column.width === "fluid" ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
{column.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const marks: Plot.Markish[] = [];
|
||||
marks.push(
|
||||
() => htl.svg`<defs>
|
||||
<linearGradient id="gradient" gradientTransform="rotate(90)">
|
||||
<stop offset="0%" stop-color="#58C142" stop-opacity="0.7" />
|
||||
<stop offset="100%" stop-color="#58C142" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
</defs>`
|
||||
);
|
||||
if (yvals.length == 0) {
|
||||
// nothing
|
||||
} else if (yvals.length == 1) {
|
||||
marks.push(
|
||||
Plot.lineY(plotData, {
|
||||
stroke: plotColors[0],
|
||||
strokeWidth: 2,
|
||||
x: "ts",
|
||||
y: yvals[0],
|
||||
})
|
||||
);
|
||||
marks.push(
|
||||
Plot.areaY(plotData, {
|
||||
fill: "url(#gradient)",
|
||||
x: "ts",
|
||||
y: yvals[0],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
let idx = 0;
|
||||
for (const yval of yvals) {
|
||||
marks.push(
|
||||
Plot.lineY(plotData, {
|
||||
stroke: plotColors[idx % plotColors.length],
|
||||
strokeWidth: 1,
|
||||
x: "ts",
|
||||
y: yval,
|
||||
})
|
||||
);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
const plot = Plot.plot({
|
||||
x: { grid: true, label: "time", tickFormat: (d) => `${dayjs.unix(d / 1000).format("HH:mm:ss")}` },
|
||||
y: { label: "%", domain: [0, 100] },
|
||||
width: parentWidth,
|
||||
height: parentHeight,
|
||||
marks: marks,
|
||||
});
|
||||
|
||||
if (plot !== undefined) {
|
||||
containerRef.current.append(plot);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (plot !== undefined) {
|
||||
plot.remove();
|
||||
}
|
||||
};
|
||||
}, [plotData, parentHeight, parentWidth]);
|
||||
|
||||
return <div className="plot-view" ref={containerRef} />;
|
||||
});
|
||||
|
||||
export { ChatView, ChatViewModel, makeChatViewModel };
|
||||
export default Layout;
|
||||
|
Loading…
Reference in New Issue
Block a user