feat: added rule based connection validation and connection limits to the editor
This commit is contained in:
committed by
Björn Otgaar
parent
6d1c17e77b
commit
9e7c192804
110
src/pages/VisProgPage/visualProgrammingUI/HandleRuleLogic.ts
Normal file
110
src/pages/VisProgPage/visualProgrammingUI/HandleRuleLogic.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
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);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user