Files
pepperplus-ui/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx

172 lines
4.5 KiB
TypeScript

import {useReactFlow, useStoreApi} from "@xyflow/react";
import clsx from "clsx";
import {useEffect, useState} from "react";
import useFlowStore from "../VisProgStores.tsx";
import {
warningSummary,
type WarningSeverity,
type EditorWarning, globalWarning
} from "./EditorWarnings.tsx";
import styles from "./WarningSidebar.module.css";
export function WarningsSidebar() {
const warnings = useFlowStore.getState().getWarnings();
const [severityFilter, setSeverityFilter] = useState<WarningSeverity | 'ALL'>('ALL');
useEffect(() => {}, [warnings]);
const filtered = severityFilter === 'ALL'
? warnings
: warnings.filter(w => w.severity === severityFilter);
return (
<aside className={styles.warningsSidebar}>
<WarningsHeader
severityFilter={severityFilter}
onChange={setSeverityFilter}
/>
<WarningsList warnings={filtered} />
</aside>
);
}
function WarningsHeader({
severityFilter,
onChange,
}: {
severityFilter: WarningSeverity | 'ALL';
onChange: (severity: WarningSeverity | 'ALL') => void;
}) {
const summary = warningSummary();
return (
<div className={styles.warningsHeader}>
<h3>Warnings</h3>
<div className={styles.severityTabs}>
{(['ALL', 'ERROR', 'WARNING', 'INFO'] as const).map(severity => (
<button
key={severity}
className={clsx(styles.severityTab, severityFilter === severity && styles.active)}
onClick={() => onChange(severity)}
>
{severity}
{severity !== 'ALL' && (
<span className={styles.count}>
{summary[severity.toLowerCase() as keyof typeof summary]}
</span>
)}
</button>
))}
</div>
</div>
);
}
function WarningsList(props: { warnings: EditorWarning[] }) {
const splitWarnings = {
global: props.warnings.filter(w => w.scope.id === globalWarning),
other: props.warnings.filter(w => w.scope.id !== globalWarning),
}
if (props.warnings.length === 0) {
return (
<div className={styles.warningsEmpty}>
No warnings!
</div>
)
}
if (splitWarnings.global.length === 0) {
return (
<div>
<div className={"warningGroup"}>
<div className={styles.warningGroupHeader}>global:</div>
<div className={styles.warningsEmpty}>
No global warnings!
</div>
</div>
<div className={"warningGroup"}>
<div className={styles.warningGroupHeader}>other:</div>
<div className={styles.warningsList}>
{splitWarnings.other.map((warning) => (
<WarningListItem warning={warning} />
))}
</div>
</div>
</div>
);
}
return (
<div>
<div className={"warningGroup"}>
<div className={styles.warningGroupHeader}>global:</div>
<div className={styles.warningsList}>
{splitWarnings.global.map((warning) => (
<WarningListItem warning={warning} />
))}
</div>
</div>
<div className={"warningGroup"}>
<div className={styles.warningGroupHeader}>other:</div>
<div className={styles.warningsList}>
{splitWarnings.other.map((warning) => (
<WarningListItem warning={warning} />
))}
</div>
</div>
</div>
);
}
function WarningListItem(props: { warning: EditorWarning }) {
const jumpToNode = useJumpToNode();
return (
<div
className={clsx(styles.warningItem, styles[`warning-item--${props.warning.severity.toLowerCase()}`],)}
onClick={() => jumpToNode(props.warning.scope.id)}
>
<div className={styles.description}>
{props.warning.description}
</div>
<div className={styles.meta}>
{props.warning.scope.id}
{props.warning.scope.handleId && (
<span className={styles.handle}>@{props.warning.scope.handleId}</span>
)}
</div>
</div>
);
}
function useJumpToNode() {
const { getNode, setCenter } = useReactFlow();
const { addSelectedNodes } = useStoreApi().getState();
return (nodeId: string) => {
// user can't jump to global warning, so prevent further logic from running
if (nodeId === globalWarning) return;
const node = getNode(nodeId);
if (!node) return;
const { position, width = 0, height = 0} = node;
// move to node
setCenter(
position!.x + width / 2,
position!.y + height / 2,
{ zoom: 2, duration: 300 }
).then(() => {
// select the node
addSelectedNodes([nodeId]);
});
};
}