diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
index a31665f..500a6c0 100644
--- a/src/pages/VisProgPage/VisProg.tsx
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -90,6 +90,8 @@ const VisProgUI = () => {
return () => window.removeEventListener('keydown', handler);
});
+
+
return (
{
+
@@ -187,16 +190,21 @@ function graphReducer() {
*/
function VisProgPage() {
const [programValidity, setProgramValidity] = useState(true);
- const sIndex = useFlowStore.getState().severityIndex;
+
+ const {isProgramValid, severityIndex} = useFlowStore();
+
useEffect(() => {
- setProgramValidity(useFlowStore.getState().isProgramValid)
- }, [sIndex]);
-
+ setProgramValidity(isProgramValid);
+ // the following eslint disable is required as it wants us to use all possible dependencies for the useEffect statement,
+ // however this would result in an infinite loop because it would change one of its own dependencies
+ // so we only use those dependencies that we don't change ourselves
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [severityIndex]);
return (
<>
-
+
>
)
}
diff --git a/src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx b/src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx
index 2ff7397..436fc21 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/EditorWarnings.tsx
@@ -1,12 +1,10 @@
/* contains all logic for the VisProgEditor warning system
*
* Missing but desirable features:
-* - Global Warnings, e.g. not node specific
-* - could be done by creating a global scope entry with GLOBAL as nodeId
* - Warning filtering:
* - if there is no completely connected chain of startNode-[PhaseNodes]-EndNode
* then hide any startNode, phaseNode, or endNode specific warnings
-* */
+*/
// --| Type definitions |--
@@ -42,13 +40,21 @@ export type EditorWarning = {
/**
* a scoped WarningKey,
- * `handleId` is `null` if the warning is not specific to one handle on the node
+ * the handleId scoping is only needed for handle specific errors
+ *
+ * "`WarningType`:`handleId`"
*/
-export type WarningKey = { type: WarningType, handleId: string | null }; // for warnings that can occur on a per-handle basis
+export type WarningKey = string; // for warnings that can occur on a per-handle basis
+/**
+ * a composite key used in the severityIndex
+ *
+ * "`WarningId`|`WarningKey`"
+ */
+export type CompositeWarningKey = string;
export type WarningRegistry = Map>;
-export type SeverityIndex = Map>;
+export type SeverityIndex = Map>;
type ZustandSet = (partial: Partial | ((state: FlowState) => Partial)) => void;
type ZustandGet = () => FlowState;
@@ -88,15 +94,14 @@ export type EditorWarningRegistry = {
// --| implemented logic |--
-export const globalWarningKey = (type: WarningType) : WarningKey => { return {type: type, handleId: null}};
export const globalWarning = "GLOBAL_WARNINGS";
-
export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : EditorWarningRegistry { return {
editorWarningRegistry: new Map>(),
severityIndex: new Map([
- ['INFO', new Set<{ id: WarningId, warningKey: WarningKey}>()],
- ['WARNING', new Set<{ id: WarningId, warningKey: WarningKey}>()]
+ ['INFO', new Set()],
+ ['WARNING', new Set()],
+ ['ERROR', new Set()],
]),
getWarningsBySeverity: (warningSeverity) => {
@@ -106,7 +111,8 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
const warnings: EditorWarning[] = [];
warningKeys?.forEach(
- ({id, warningKey}) => {
+ (compositeKey) => {
+ const [id, warningKey] = compositeKey.split('|');
const warning = wRegistry.get(id)?.get(warningKey);
if (warning) {
@@ -120,7 +126,7 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
isProgramValid: () => {
const sIndex = get().severityIndex;
- return sIndex.get("ERROR")!.size === 0;
+ return (sIndex.get("ERROR")!.size === 0);
},
getWarnings: () => Array.from(get().editorWarningRegistry.values())
@@ -129,18 +135,22 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
registerWarning: (warning) => {
const { scope: {id, handleId}, type, severity } = warning;
- const warningKey = handleId ? { type, handleId } : { type, handleId: null};
- const wRegistry = get().editorWarningRegistry;
- const sIndex = get().severityIndex;
-
+ const warningKey = handleId ? `${type}:${handleId}` : type;
+ const compositeKey = `${id}|${warningKey}`;
+ const wRegistry = structuredClone(get().editorWarningRegistry);
+ const sIndex = structuredClone(get().severityIndex);
+ console.log("register")
// add to warning registry
if (!wRegistry.has(id)) {
wRegistry.set(id, new Map());
}
wRegistry.get(id)!.set(warningKey, warning);
+
// add to severityIndex
- sIndex.get(severity)!.add({id,warningKey});
+ if (!sIndex.get(severity)!.has(compositeKey)) {
+ sIndex.get(severity)!.add(compositeKey);
+ }
set({
editorWarningRegistry: wRegistry,
@@ -149,17 +159,19 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
},
unregisterWarning: (id, warningKey) => {
+ const wRegistry = structuredClone(get().editorWarningRegistry);
+ const sIndex = structuredClone(get().severityIndex);
+ console.log("unregister")
+ // verify if the warning was created already
+ const warning = wRegistry.get(id)?.get(warningKey);
+ if (!warning) return;
- const wRegistry = get().editorWarningRegistry;
- const sIndex = get().severityIndex;
-
- const warning = wRegistry.get(id)!.get(warningKey);
// remove from warning registry
wRegistry.get(id)!.delete(warningKey);
// remove from severityIndex
- sIndex.get(warning!.severity)!.delete({id,warningKey});
+ sIndex.get(warning.severity)!.delete(`${id}|${warningKey}`);
set({
editorWarningRegistry: wRegistry,
@@ -168,15 +180,15 @@ export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : Editor
},
unregisterWarningsForId: (id) => {
- const wRegistry = get().editorWarningRegistry;
- const sIndex = get().severityIndex;
+ const wRegistry = structuredClone(get().editorWarningRegistry);
+ const sIndex = structuredClone(get().severityIndex);
const nodeWarnings = wRegistry.get(id);
// remove from severity index
if (nodeWarnings) {
nodeWarnings.forEach((warning, warningKey) => {
- sIndex.get(warning.severity)?.delete({id, warningKey});
+ sIndex.get(warning.severity)?.delete(`${id}|${warningKey}`);
});
}
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
index 741b190..d4bf0f6 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
@@ -1,12 +1,15 @@
import {
type NodeProps,
Position,
- type Node,
+ type Node, useNodeConnections
} from '@xyflow/react';
+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 {allowOnlyConnectionsFromHandle} from "../HandleRules.ts";
+import useFlowStore from "../VisProgStores.tsx";
export type StartNodeData = {
@@ -25,6 +28,29 @@ export type StartNode = Node
* @returns React.JSX.Element
*/
export default function StartNode(props: NodeProps) {
+ const {registerWarning, unregisterWarning} = useFlowStore.getState();
+ const connections = useNodeConnections({
+ id: props.id,
+ handleId: 'source'
+ })
+
+ useEffect(() => {
+ const noConnectionWarning : EditorWarning = {
+ scope: {
+ id: props.id,
+ handleId: 'source'
+ },
+ type: 'MISSING_OUTPUT',
+ severity: "ERROR",
+ description: "the startNode does not have an outgoing connection to a phaseNode"
+ }
+
+ if (connections.length === 0) { registerWarning(noConnectionWarning); }
+ else { unregisterWarning(props.id, `${noConnectionWarning.type}:source`); }
+ }, [connections.length, props.id, registerWarning, unregisterWarning]);
+
+
+
return (
<>