Files
pepperplus-ui/src/pages/VisProgPage/visualProgrammingUI/HandleRuleLogic.ts
2026-01-28 10:34:36 +00:00

125 lines
4.2 KiB
TypeScript

// This program has been developed by students from the bachelor Computer Science at Utrecht
// University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences)
import {type Connection} from "@xyflow/react";
import {useEffect} from "react";
import useFlowStore from "./VisProgStores.tsx";
export type ConnectionContext = {
connectionCount: number;
source: {
id: string;
handleId: string;
}
target: {
id: string;
handleId: string;
}
}
export type HandleRule = (
connection: Connection,
context: ConnectionContext
) => RuleResult;
/**
* A RuleResult describes the outcome of validating a HandleRule
*
* if a rule is not satisfied, the RuleResult includes a message that is used inside a tooltip
* that tells the user why their attempted connection is not possible
*/
export type RuleResult =
| { isSatisfied: true }
| { isSatisfied: false, message: string };
/**
* default RuleResults, can be used to create more readable handleRule definitions
*/
export const ruleResult = {
satisfied: { isSatisfied: true } as RuleResult,
unknownError: {isSatisfied: false, message: "Unknown Error" } as RuleResult,
notSatisfied: (message: string) : RuleResult => { return {isSatisfied: false, message: message } }
}
const evaluateRules = (
rules: HandleRule[],
connection: Connection,
context: ConnectionContext
) : RuleResult => {
// evaluate the rules and check if there is at least one unsatisfied rule
const failedRule = rules
.map(rule => rule(connection, context))
.find(result => !result.isSatisfied);
return failedRule ? ruleResult.notSatisfied(failedRule.message) : ruleResult.satisfied;
}
/**
* !DOCUMENTATION NOT FINISHED!
*
* - The output is a single RuleResult, meaning we only show one error message.
* Error messages are prioritised by listOrder; Thus, if multiple HandleRules evaluate to false,
* we only send the error message of the first failed rule in the target's registered list of rules.
*
* @param {string} nodeId
* @param {string} handleId
* @param type
* @param {HandleRule[]} rules
* @returns {(c: Connection) => RuleResult} a function that validates an attempted connection
*/
export function useHandleRules(
nodeId: string,
handleId: string,
type: "source" | "target",
rules: HandleRule[],
) : (c: Connection) => RuleResult {
const edges = useFlowStore.getState().edges;
const registerRules = useFlowStore((state) => state.registerRules);
useEffect(() => {
registerRules(nodeId, handleId, rules);
// 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
}, [handleId, nodeId, registerRules]);
return (connection: Connection) => {
// inside this function we consider the target to be the target of the isValidConnection event
// and not the target in the actual connection
const { target, targetHandle } = type === "source"
? connection
: { target: connection.source, targetHandle: connection.sourceHandle };
if (!targetHandle) {throw new Error("No target handle was provided");}
const targetConnections = edges.filter(edge => edge.target === target && edge.targetHandle === targetHandle);
// we construct the connectionContext
const context: ConnectionContext = {
connectionCount: targetConnections.length,
source: {id: nodeId, handleId: handleId},
target: {id: target, handleId: targetHandle},
};
const targetRules = useFlowStore.getState().getTargetRules(target, targetHandle);
// finally we return a function that evaluates all rules using the created context
return evaluateRules(targetRules, connection, context);
};
}
export function validateConnectionWithRules(
connection: Connection,
context: ConnectionContext
): RuleResult {
const rules = useFlowStore.getState().getTargetRules(
connection.target!,
connection.targetHandle!
);
return evaluateRules(rules,connection, context);
}