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"; /** * the warning sidebar, shows all warnings * * @returns {React.JSX.Element} * @constructor */ export function WarningsSidebar() { const warnings = useFlowStore.getState().getWarnings(); const [hide, setHide] = useState(false); const [severityFilter, setSeverityFilter] = useState('ALL'); const [autoHide, setAutoHide] = useState(false); // let autohide change hide status only when autohide is toggled // and allow for user to change the hide state even if autohide is enabled const hasWarnings = warnings.length > 0; useEffect(() => { if (autoHide) { setHide(!hasWarnings); } }, [autoHide, hasWarnings]); const filtered = severityFilter === 'ALL' ? warnings : warnings.filter(w => w.severity === severityFilter); const summary = warningSummary(); // Finds the first key where the count > 0 const getHighestSeverity = () => { if (summary.error > 0) return styles.error; if (summary.warning > 0) return styles.warning; if (summary.info > 0) return styles.info; return ''; }; return ( ); } /** * the header of the warning sidebar, contains severity filtering buttons * * @param {WarningSeverity | "ALL"} severityFilter * @param {(severity: (WarningSeverity | "ALL")) => void} onChange * @returns {React.JSX.Element} * @constructor */ function WarningsHeader({ severityFilter, onChange, }: { severityFilter: WarningSeverity | 'ALL'; onChange: (severity: WarningSeverity | 'ALL') => void; }) { const summary = warningSummary(); return (

Warnings

{(['ALL', 'ERROR', 'WARNING', 'INFO'] as const).map(severity => ( ))}
); } /** * the list of warnings in the warning sidebar * * @param {{warnings: EditorWarning[]}} props * @returns {React.JSX.Element} * @constructor */ 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 (
No warnings!
) } return (
global:
{splitWarnings.global.map((warning) => ( ))} {splitWarnings.global.length === 0 && "No global warnings!"}
other:
{splitWarnings.other.map((warning) => ( ))} {splitWarnings.other.length === 0 && "No other warnings!"}
); } /** * a single warning in the warning sidebar * * @param {{warning: EditorWarning, key: string}} props * @returns {React.JSX.Element} * @constructor */ function WarningListItem(props: { warning: EditorWarning, key: string}) { const jumpToNode = useJumpToNode(); return (
jumpToNode(props.warning.scope.id)} >
{props.warning.type}
{props.warning.description}
); } /** * moves the editor to the provided node * @returns {(nodeId: string) => void} */ function useJumpToNode() { const { getNode, setCenter, getViewport } = useReactFlow(); const { addSelectedNodes } = useStoreApi().getState(); return (nodeId: string) => { // user can't jump to global warning, so prevent further logic from running if the warning is a globalWarning if (nodeId === globalWarning) return; const node = getNode(nodeId); if (!node) return; const nodeElement = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`) as HTMLElement; const { position } = node; const viewport = getViewport(); const { width, height } = nodeElement.getBoundingClientRect(); //move to node setCenter( position!.x + ((width / viewport.zoom) / 2), position!.y + ((height / viewport.zoom) / 2), {duration: 300, interpolate: "smooth" } ).then(() => { addSelectedNodes([nodeId]); }); }; }