All files / src/pages/VisProgPage/visualProgrammingUI HandleRuleLogic.ts

0% Statements 0/21
0% Branches 0/8
0% Functions 0/9
0% Lines 0/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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);
  };
}