diff --git a/package-lock.json b/package-lock.json
index a52343e..cb39172 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@neodrag/react": "^2.3.1",
"@xyflow/react": "^12.8.6",
+ "clsx": "^2.1.1",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router": "^7.9.3",
@@ -3971,6 +3972,15 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -6945,9 +6955,9 @@
}
},
"node_modules/react-router": {
- "version": "7.9.3",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz",
- "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==",
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
+ "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
diff --git a/package.json b/package.json
index 45f66df..ea362b7 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"dependencies": {
"@neodrag/react": "^2.3.1",
"@xyflow/react": "^12.8.6",
+ "clsx": "^2.1.1",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router": "^7.9.3",
diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
index ef759c1..032dfb3 100644
--- a/src/pages/VisProgPage/VisProg.tsx
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -12,6 +12,7 @@ import {useShallow} from 'zustand/react/shallow';
import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts";
import useProgramStore from "../../utils/programStore.ts";
import {DndToolbar} from './visualProgrammingUI/components/DragDropSidebar.tsx';
+import {WarningsSidebar} from "./visualProgrammingUI/components/WarningSidebar.tsx";
import type {PhaseNode} from "./visualProgrammingUI/nodes/PhaseNode.tsx";
import useFlowStore from './visualProgrammingUI/VisProgStores.tsx';
import type {FlowState} from './visualProgrammingUI/VisProgTypes.tsx';
@@ -93,7 +94,7 @@ const VisProgUI = () => {
return (
-
+
{
snapToGrid
fitView
proOptions={{hideAttribution: true}}
+ style={{flexGrow: 3}}
>
{/* contains the drag and drop panel for nodes */}
@@ -128,6 +130,7 @@ const VisProgUI = () => {
+
);
};
@@ -190,10 +193,8 @@ function graphReducer() {
*/
function VisProgPage() {
const [programValidity, setProgramValidity] = useState
(true);
-
const {isProgramValid, severityIndex} = useFlowStore();
-
useEffect(() => {
setProgramValidity(isProgramValid);
// the following eslint disable is required as it wants us to use all possible dependencies for the useEffect statement,
diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
index 570d0df..8305bb2 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
@@ -9,7 +9,7 @@ import {
type XYPosition,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
-import { editorWarningRegistry } from "./EditorWarnings.tsx";
+import { editorWarningRegistry } from "./components/EditorWarnings.tsx";
import type { FlowState } from './VisProgTypes';
import {
NodeDefaults,
diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx
index bf1bffd..afb1024 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx
@@ -9,7 +9,7 @@ import type {
OnEdgesDelete,
OnNodesDelete
} from '@xyflow/react';
-import type {EditorWarningRegistry} from "./EditorWarnings.tsx";
+import type {EditorWarningRegistry} from "./components/EditorWarnings.tsx";
import type {HandleRule} from "./HandleRuleLogic.ts";
import type { NodeTypes } from './NodeRegistry';
import type {FlowSnapshot} from "./EditorUndoRedo.ts";
diff --git a/src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/EditorWarnings.tsx
similarity index 92%
rename from src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx
rename to src/pages/VisProgPage/visualProgrammingUI/components/EditorWarnings.tsx
index 436fc21..a0c90d6 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/components/EditorWarnings.tsx
@@ -5,12 +5,11 @@
* - if there is no completely connected chain of startNode-[PhaseNodes]-EndNode
* then hide any startNode, phaseNode, or endNode specific warnings
*/
+import useFlowStore from "../VisProgStores.tsx";
+import type {FlowState} from "../VisProgTypes.tsx";
// --| Type definitions |--
-import type {FlowState} from "./VisProgTypes.tsx";
-
-
export type WarningId = NodeId | "GLOBAL_WARNINGS";
export type NodeId = string;
@@ -202,3 +201,16 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
},
}}
+
+// returns a summary of the warningRegistry
+export function warningSummary() {
+ const {severityIndex, isProgramValid} = useFlowStore.getState();
+ return {
+ info: severityIndex.get('INFO')!.size,
+ warning: severityIndex.get('WARNING')!.size,
+ error: severityIndex.get('ERROR')!.size,
+ isValid: isProgramValid(),
+ };
+}
+
+
diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.module.css b/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.module.css
new file mode 100644
index 0000000..182d99e
--- /dev/null
+++ b/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.module.css
@@ -0,0 +1,73 @@
+.warnings-sidebar {
+ width: 320px;
+ height: 100%;
+ background: canvas;
+ border-left: 2px solid black;
+ display: flex;
+ flex-direction: column;
+}
+
+.warnings-header {
+ padding: 12px;
+ border-bottom: 1px solid #2a2a2e;
+}
+
+.severity-tabs {
+ display: flex;
+ gap: 4px;
+}
+
+.severity-tab {
+ flex: 1;
+ padding: 4px;
+ background: ButtonFace;
+ color: GrayText;
+ border: none;
+ cursor: pointer;
+}
+
+.count {
+ padding: 4px;
+ color: GrayText;
+ border: none;
+ cursor: pointer;
+}
+
+.severity-tab.active {
+ color: ButtonText;
+ border: 2px solid currentColor;
+}
+
+.warnings-list {
+ flex: 1;
+ overflow-y: auto;
+}
+
+.warnings-empty {
+ margin: auto;
+}
+
+.warning-item {
+ display: flex;
+ margin: 5px;
+ gap: 8px;
+ padding: 8px 12px;
+ border-radius: 5px;
+}
+
+.warning-item--error {
+ border: 2px solid red;
+}
+
+.warning-item--warning {
+ border-left: 3px solid orange;
+}
+
+.warning-item--info {
+ border-left: 3px solid steelblue;
+}
+
+.warning-item .meta {
+ font-size: 11px;
+ opacity: 0.6;
+}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx
new file mode 100644
index 0000000..b91a17a
--- /dev/null
+++ b/src/pages/VisProgPage/visualProgrammingUI/components/WarningSidebar.tsx
@@ -0,0 +1,99 @@
+import clsx from "clsx";
+import {useEffect, useState} from "react";
+import useFlowStore from "../VisProgStores.tsx";
+import {
+ warningSummary,
+ type WarningSeverity, type EditorWarning
+} from "./EditorWarnings.tsx";
+import styles from "./WarningSidebar.module.css";
+
+export function WarningsSidebar() {
+ const warnings = useFlowStore.getState().getWarnings();
+
+ const [severityFilter, setSeverityFilter] = useState('ALL');
+
+ useEffect(() => {}, [warnings]);
+ const filtered = severityFilter === 'ALL'
+ ? warnings
+ : warnings.filter(w => w.severity === severityFilter);
+
+ return (
+
+ );
+}
+
+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 => (
+ onChange(severity)}
+ >
+ {severity}
+ {severity !== 'ALL' && (
+
+ {summary[severity.toLowerCase() as keyof typeof summary]}
+
+ )}
+
+ ))}
+
+
+ );
+}
+
+
+
+function WarningsList(props: { warnings: EditorWarning[] }) {
+ if (props.warnings.length === 0) {
+ return (
+
+ No warnings!
+
+ )
+ }
+ return (
+
+ {props.warnings.map((warning) => (
+
+ ))}
+
+ );
+}
+
+function WarningListItem(props: { warning: EditorWarning }) {
+ return (
+
+
+ {props.warning.description}
+
+
+
+ {props.warning.scope.id}
+ {props.warning.scope.handleId && (
+ @{props.warning.scope.handleId}
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
index d4bf0f6..3d6c2b2 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
@@ -7,7 +7,7 @@ import {useEffect} from "react";
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import {SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
-import type {EditorWarning} from "../EditorWarnings.tsx";
+import type {EditorWarning} from "../components/EditorWarnings.tsx";
import {allowOnlyConnectionsFromHandle} from "../HandleRules.ts";
import useFlowStore from "../VisProgStores.tsx";