Merging dev into main #49
@@ -139,3 +139,67 @@
|
|||||||
outline: plum solid 2pt;
|
outline: plum solid 2pt;
|
||||||
filter: drop-shadow(0 0 0.25rem plum);
|
filter: drop-shadow(0 0 0.25rem plum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.planDialog {
|
||||||
|
width: 80vw;
|
||||||
|
max-width: 900px;
|
||||||
|
padding: 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planDialog::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.planEditor {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planEditorLeft {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planEditorRight {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-left: 1px solid var(--border-color, #ccc);
|
||||||
|
padding-left: 1rem;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planStep {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: text-decoration 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.planStep:hover {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepType {
|
||||||
|
margin-left: auto;
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepIndex {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.emptySteps {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Plan } from "./Plan";
|
||||||
|
|
||||||
|
export const defaultPlan: Plan = {
|
||||||
|
name: "Default Plan",
|
||||||
|
id: "-1",
|
||||||
|
steps: [],
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export type Plan = {
|
||||||
|
name: string,
|
||||||
|
id: string,
|
||||||
|
steps: PlanElement[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlanElement = Goal | Action
|
||||||
|
|
||||||
|
export type Goal = {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
plan: Plan,
|
||||||
|
can_fail: boolean,
|
||||||
|
type: "goal"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
export type Action = SpeechAction | GestureAction | LLMAction
|
||||||
|
export type SpeechAction = { name: string, id: string, text: string, type:"speech" }
|
||||||
|
export type GestureAction = { name: string, id: string, gesture: string, type:"gesture" }
|
||||||
|
export type LLMAction = { name: string, id: string, goal: string, type:"llm" }
|
||||||
|
|
||||||
|
export type ActionTypes = "speech" | "gesture" | "llm";
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ import { BasicBeliefReduce } from './BasicBeliefNode';
|
|||||||
export type NormNodeData = {
|
export type NormNodeData = {
|
||||||
label: string;
|
label: string;
|
||||||
droppable: boolean;
|
droppable: boolean;
|
||||||
conditions: string[]; // List of (basic) belief nodes' ids.
|
condition?: string; // id of this node's belief.
|
||||||
norm: string;
|
norm: string;
|
||||||
hasReduce: boolean;
|
hasReduce: boolean;
|
||||||
critical: boolean;
|
critical: boolean;
|
||||||
@@ -70,13 +70,12 @@ export default function NormNode(props: NodeProps<NormNode>) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.conditions.length > 0 && (<div className={"flex-row gap-md align-center"} data-testid="norm-condition-information">
|
{data.condition && (<div className={"flex-row gap-md align-center"} data-testid="norm-condition-information">
|
||||||
<label htmlFor={checkbox_id}>{data.conditions.length} condition{data.conditions.length > 1 ? "s" : ""}/ belief{data.conditions.length > 1 ? "s" : ""} attached.</label>
|
<label htmlFor={checkbox_id}>Condition/ Belief attached.</label>
|
||||||
</div>)}
|
</div>)}
|
||||||
|
|
||||||
|
<Handle type="source" position={Position.Right} id="phase"/>
|
||||||
<Handle type="source" position={Position.Right} id="norms"/>
|
<Handle type="target" position={Position.Bottom} id="belief"/>
|
||||||
<Handle type="target" position={Position.Bottom} id="norms"/>
|
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
@@ -90,11 +89,6 @@ export default function NormNode(props: NodeProps<NormNode>) {
|
|||||||
export function NormReduce(node: Node, nodes: Node[]) {
|
export function NormReduce(node: Node, nodes: Node[]) {
|
||||||
const data = node.data as NormNodeData;
|
const data = node.data as NormNodeData;
|
||||||
|
|
||||||
// conditions nodes - make sure to check for empty arrays
|
|
||||||
let conditionNodes: Node[] = [];
|
|
||||||
if (data.conditions)
|
|
||||||
conditionNodes = nodes.filter((node) => data.conditions.includes(node.id));
|
|
||||||
|
|
||||||
// Build the result object
|
// Build the result object
|
||||||
const result: Record<string, unknown> = {
|
const result: Record<string, unknown> = {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
@@ -103,12 +97,14 @@ export function NormReduce(node: Node, nodes: Node[]) {
|
|||||||
critical: data.critical,
|
critical: data.critical,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Go over our conditionNodes. They should either be Basic (OR TODO: Inferred)
|
if (data.condition) {
|
||||||
const reducer = BasicBeliefReduce;
|
const reducer = BasicBeliefReduce; // TODO: also add inferred.
|
||||||
result["basic_beliefs"] = conditionNodes.map((condition) => reducer(condition, nodes))
|
const conditionNode = nodes.find((node) => node.id === data.condition);
|
||||||
|
// In case something went wrong, and our condition doesn't actually exist;
|
||||||
|
if (conditionNode == undefined) return result;
|
||||||
|
result["belief"] = reducer(conditionNode, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
// When the Inferred is being implemented, you should follow the same kind of structure that PhaseNode has,
|
|
||||||
// dividing the conditions into basic and inferred, then calling the correct reducer on them.
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +115,9 @@ export function NormReduce(node: Node, nodes: Node[]) {
|
|||||||
*/
|
*/
|
||||||
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
const data = _thisNode.data as NormNodeData;
|
const data = _thisNode.data as NormNodeData;
|
||||||
// If we got a belief connected, this is a condition for the norm.
|
// If we got a belief connected, this is the condition for the norm.
|
||||||
if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
|
if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
|
||||||
data.conditions.push(_sourceNodeId);
|
data.condition = _sourceNodeId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,10 +137,8 @@ export function NormConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
|||||||
*/
|
*/
|
||||||
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
const data = _thisNode.data as NormNodeData;
|
const data = _thisNode.data as NormNodeData;
|
||||||
// If we got a belief connected, this is a condition for the norm.
|
// remove if the target of disconnection was our condition
|
||||||
if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
|
if (_sourceNodeId == data.condition) data.condition = undefined
|
||||||
data.conditions = data.conditions.filter(id => id != _sourceNodeId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import {
|
|||||||
import { Toolbar } from '../components/NodeComponents';
|
import { Toolbar } from '../components/NodeComponents';
|
||||||
import styles from '../../VisProg.module.css';
|
import styles from '../../VisProg.module.css';
|
||||||
import useFlowStore from '../VisProgStores';
|
import useFlowStore from '../VisProgStores';
|
||||||
import { useState } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
import { RealtimeTextField, TextField } from '../../../../components/TextField';
|
import { RealtimeTextField, TextField } from '../../../../components/TextField';
|
||||||
import duplicateIndices from '../../../../utils/duplicateIndices';
|
import duplicateIndices from '../../../../utils/duplicateIndices';
|
||||||
|
import type { Action, ActionTypes, Plan } from '../components/Plan';
|
||||||
|
import { defaultPlan } from '../components/Plan.default';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default data structure for a Trigger node
|
* The default data structure for a Trigger node
|
||||||
@@ -28,8 +30,8 @@ import duplicateIndices from '../../../../utils/duplicateIndices';
|
|||||||
export type TriggerNodeData = {
|
export type TriggerNodeData = {
|
||||||
label: string;
|
label: string;
|
||||||
droppable: boolean;
|
droppable: boolean;
|
||||||
triggerType: "keywords" | string;
|
condition?: string; // id of the belief
|
||||||
triggers: Keyword[] | never;
|
plan?: Plan;
|
||||||
hasReduce: boolean;
|
hasReduce: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,25 +57,265 @@ export function TriggerNodeCanConnect(connection: Connection | Edge): boolean {
|
|||||||
export default function TriggerNode(props: NodeProps<TriggerNode>) {
|
export default function TriggerNode(props: NodeProps<TriggerNode>) {
|
||||||
const data = props.data;
|
const data = props.data;
|
||||||
const {updateNodeData} = useFlowStore();
|
const {updateNodeData} = useFlowStore();
|
||||||
|
const dialogRef = useRef<HTMLDialogElement | null>(null);
|
||||||
|
const [draftPlan, setDraftPlan] = useState<Plan | null>(null);
|
||||||
|
|
||||||
const setKeywords = (keywords: Keyword[]) => {
|
// Helpers for inside plan creation
|
||||||
updateNodeData(props.id, {...data, triggers: keywords});
|
const [newActionType, setNewActionType] = useState<ActionTypes>("speech");
|
||||||
|
const [newActionName, setNewActionName] = useState("");
|
||||||
|
const [newActionValue, setNewActionValue] = useState("");
|
||||||
|
|
||||||
|
|
||||||
|
// Create a new Plan
|
||||||
|
const openCreateDialog = () => {
|
||||||
|
setDraftPlan(JSON.parse(JSON.stringify(defaultPlan)));
|
||||||
|
dialogRef.current?.showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Edit our current plan
|
||||||
|
const openEditDialog = () => {
|
||||||
|
if (!data.plan) return;
|
||||||
|
setDraftPlan(JSON.parse(JSON.stringify(data.plan)));
|
||||||
|
dialogRef.current?.showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close the creating/editing of our plan
|
||||||
|
const closeDialog = () => {
|
||||||
|
dialogRef.current?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Define the function for creating actions
|
||||||
|
const buildAction = (): Action => {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
|
switch (newActionType) {
|
||||||
|
case "speech":
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: newActionName,
|
||||||
|
text: newActionValue,
|
||||||
|
type: "speech"
|
||||||
|
};
|
||||||
|
case "gesture":
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: newActionName,
|
||||||
|
gesture: newActionValue,
|
||||||
|
type: "gesture"
|
||||||
|
};
|
||||||
|
case "llm":
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: newActionName,
|
||||||
|
goal: newActionValue,
|
||||||
|
type: "llm"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Toolbar nodeId={props.id} allowDelete={true}/>
|
<Toolbar nodeId={props.id} allowDelete={true}/>
|
||||||
<div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}>
|
<div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}>
|
||||||
{data.triggerType === "emotion" && (
|
<div className={"flex-row gap-md"}>Triggers when the condition is met.</div>
|
||||||
<div className={"flex-row gap-md"}>Emotion?</div>
|
<div className={"flex-row gap-md"}>Condition/ Belief is currently {data.condition ? "" : "not"} set. {data.condition ? "🟢" : "🔴"}</div>
|
||||||
)}
|
<div className={"flex-row gap-md"}>Plan{data.plan ? (": " + data.plan.name) : ""} is currently {data.plan ? "" : "not"} set. {data.plan ? "🟢" : "🔴"}</div>
|
||||||
{data.triggerType === "keywords" && (
|
|
||||||
<Keywords
|
|
||||||
keywords={data.triggers}
|
|
||||||
setKeywords={setKeywords}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Handle type="source" position={Position.Right} id="TriggerSource"/>
|
<Handle type="source" position={Position.Right} id="TriggerSource"/>
|
||||||
|
<Handle type="target" position={Position.Bottom} id="ConditionTarget"/>
|
||||||
|
|
||||||
|
{/* We don't have a plan yet, show our create plan button */}
|
||||||
|
{!data.plan && (
|
||||||
|
<button
|
||||||
|
className={styles.nodeButton}
|
||||||
|
onClick={openCreateDialog}
|
||||||
|
>
|
||||||
|
Create Plan
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* We have a plan, show our edit plan button */}
|
||||||
|
{data.plan && (
|
||||||
|
<button
|
||||||
|
className={styles.nodeButton}
|
||||||
|
onClick={openEditDialog}
|
||||||
|
>
|
||||||
|
Edit Plan
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Define how our dialog should work */}
|
||||||
|
<dialog ref={dialogRef}
|
||||||
|
className={styles.planDialog}
|
||||||
|
onCancel={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<form method="dialog" className="flex-col gap-md">
|
||||||
|
<h3>{draftPlan?.id === data.plan?.id ? "Edit Plan" : "Create Plan"}</h3>
|
||||||
|
|
||||||
|
{/*Text field to edit the name of our draft plan*/}
|
||||||
|
<div className={styles.planEditor}>
|
||||||
|
{/* LEFT: plan info + add action */}
|
||||||
|
<div className={styles.planEditorLeft}>
|
||||||
|
{/* Plan name */}
|
||||||
|
{draftPlan && (
|
||||||
|
<div className="flex-col gap-sm">
|
||||||
|
<label htmlFor="plan-name">Plan Name</label>
|
||||||
|
<TextField
|
||||||
|
id="plan-name"
|
||||||
|
value={draftPlan.name}
|
||||||
|
setValue={(name) =>
|
||||||
|
setDraftPlan({
|
||||||
|
...draftPlan,
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Add action UI */}
|
||||||
|
{draftPlan && (
|
||||||
|
<div className="flex-col gap-sm">
|
||||||
|
<h4>Add Action</h4>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Action Type <wbr></wbr> {/* Just moves the selector over a bit :) */}
|
||||||
|
<select
|
||||||
|
value={newActionType}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewActionType(e.target.value as ActionTypes)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="speech">Speech Action</option>
|
||||||
|
<option value="gesture">Gesture Action</option>
|
||||||
|
<option value="llm">LLM Action</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
value={newActionName}
|
||||||
|
setValue={setNewActionName}
|
||||||
|
placeholder="Action name"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
value={newActionValue}
|
||||||
|
setValue={setNewActionValue}
|
||||||
|
placeholder={
|
||||||
|
newActionType === "speech"
|
||||||
|
? "Speech text"
|
||||||
|
: newActionType === "gesture"
|
||||||
|
? "Gesture name"
|
||||||
|
: "LLM goal"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!newActionName || !newActionValue}
|
||||||
|
onClick={() => {
|
||||||
|
if (!draftPlan) return;
|
||||||
|
|
||||||
|
const action = buildAction();
|
||||||
|
|
||||||
|
setDraftPlan({
|
||||||
|
...draftPlan,
|
||||||
|
steps: [...draftPlan.steps, action],
|
||||||
|
});
|
||||||
|
|
||||||
|
setNewActionName("");
|
||||||
|
setNewActionValue("");
|
||||||
|
setNewActionType("speech");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Step
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT: steps list */}
|
||||||
|
<div className={styles.planEditorRight}>
|
||||||
|
<h4>Steps</h4>
|
||||||
|
|
||||||
|
{draftPlan && draftPlan.steps.length === 0 && (
|
||||||
|
<div className={styles.emptySteps}>
|
||||||
|
No steps yet
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{draftPlan?.steps.map((step, index) => (
|
||||||
|
<div
|
||||||
|
key={step.id}
|
||||||
|
className={styles.planStep}
|
||||||
|
onClick={() => {
|
||||||
|
if (!draftPlan) return;
|
||||||
|
setDraftPlan({
|
||||||
|
...draftPlan,
|
||||||
|
steps: draftPlan.steps.filter((s) => s.id !== step.id),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={styles.stepIndex}>{index + 1}.</span>
|
||||||
|
<span className={styles.stepName}>{step.name}</span>
|
||||||
|
<span className={styles.stepType}>{step.type}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-row gap-md">
|
||||||
|
{/*Button to close the plan editor.*/}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setDraftPlan(null);
|
||||||
|
closeDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/*Button to save the draftPlan to the plan in the Node.*/}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!draftPlan}
|
||||||
|
onClick={() => {
|
||||||
|
if (!draftPlan) return;
|
||||||
|
|
||||||
|
updateNodeData(props.id, {
|
||||||
|
...data,
|
||||||
|
plan: draftPlan,
|
||||||
|
});
|
||||||
|
setDraftPlan(null);
|
||||||
|
closeDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{draftPlan?.id === data.plan?.id ? "Confirm" : "Create"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/*Button to reset the plan*/}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!draftPlan}
|
||||||
|
onClick={() => {
|
||||||
|
if (!draftPlan) return;
|
||||||
|
updateNodeData(props.id, {
|
||||||
|
...data,
|
||||||
|
plan: undefined,
|
||||||
|
});
|
||||||
|
setNewActionName("")
|
||||||
|
setNewActionType("speech")
|
||||||
|
setNewActionValue("")
|
||||||
|
setDraftPlan(defaultPlan)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +350,11 @@ export function TriggerReduce(node: Node, _nodes: Node[]) {
|
|||||||
*/
|
*/
|
||||||
export function TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
export function TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
// no additional connection logic exists yet
|
// no additional connection logic exists yet
|
||||||
|
const data = _thisNode.data as TriggerNodeData;
|
||||||
|
// If we got a belief connected, this is the condition for the norm.
|
||||||
|
if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
|
||||||
|
data.condition = _sourceNodeId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,6 +373,9 @@ export function TriggerConnectionSource(_thisNode: Node, _targetNodeId: string)
|
|||||||
*/
|
*/
|
||||||
export function TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
export function TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
// no additional connection logic exists yet
|
// no additional connection logic exists yet
|
||||||
|
const data = _thisNode.data as TriggerNodeData;
|
||||||
|
// remove if the target of disconnection was our condition
|
||||||
|
if (_sourceNodeId == data.condition) data.condition = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user