diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
index 70a0339..5489e3c 100644
--- a/src/pages/VisProgPage/VisProg.tsx
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -64,35 +64,34 @@ const VisProgUI = () => {
} = useFlowStore(useShallow(selector)); // instructs the editor to use the corresponding functions from the FlowStore
return (
-
-
-
-
- {/* contains the drag and drop panel for nodes */}
-
-
-
-
-
+
+
+
+ {/* contains the drag and drop panel for nodes */}
+
+
+
+
);
};
+
/**
* Places the VisProgUI component inside a ReactFlowProvider
*
@@ -112,6 +111,7 @@ function VisualProgrammingUI() {
function runProgram() {
const program = graphReducer();
console.log(program);
+ console.log(JSON.stringify(program, null, 2));
}
/**
diff --git a/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts b/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts
index 6a98c0a..e02f5f2 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts
@@ -6,12 +6,18 @@ import { EndNodeDefaults } from "./nodes/EndNode.default";
import { StartNodeDefaults } from "./nodes/StartNode.default";
import { PhaseNodeDefaults } from "./nodes/PhaseNode.default";
import { NormNodeDefaults } from "./nodes/NormNode.default";
+import GoalNode, { GoalConnects, GoalReduce } from "./nodes/GoalNode";
+import { GoalNodeDefaults } from "./nodes/GoalNode.default";
+import TriggerNode, { TriggerConnects, TriggerReduce } from "./nodes/TriggerNode";
+import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
export const NodeTypes = {
start: StartNode,
end: EndNode,
phase: PhaseNode,
norm: NormNode,
+ goal: GoalNode,
+ trigger: TriggerNode,
};
// Default node data for creation
@@ -20,6 +26,8 @@ export const NodeDefaults = {
end: EndNodeDefaults,
phase: PhaseNodeDefaults,
norm: NormNodeDefaults,
+ goal: GoalNodeDefaults,
+ trigger: TriggerNodeDefaults,
};
export const NodeReduces = {
@@ -27,6 +35,8 @@ export const NodeReduces = {
end: EndReduce,
phase: PhaseReduce,
norm: NormReduce,
+ goal: GoalReduce,
+ trigger: TriggerReduce,
}
export const NodeConnects = {
@@ -34,4 +44,13 @@ export const NodeConnects = {
end: EndConnects,
phase: PhaseConnects,
norm: NormConnects,
+ goal: GoalConnects,
+ trigger: TriggerConnects,
+}
+
+// Function to tell the visual program if we're allowed to delete them...
+// Right now it doesn't take in any values, but that could also be done later.
+export const NodeDeletes = {
+ start: () => false,
+ end: () => false,
}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
index f38013f..49c296b 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
@@ -9,7 +9,7 @@ import {
type XYPosition,
} from '@xyflow/react';
import type { FlowState } from './VisProgTypes';
-import { NodeDefaults, NodeConnects } from './NodeRegistry';
+import { NodeDefaults, NodeConnects, NodeDeletes } from './NodeRegistry';
/**
@@ -20,21 +20,22 @@ import { NodeDefaults, NodeConnects } from './NodeRegistry';
* @param data the data in the node to create
* @constructor
*/
-function createNode(id: string, type: string, position: XYPosition, data: Record
) {
+function createNode(id: string, type: string, position: XYPosition, data: Record, deletable? : boolean) {
const defaultData = NodeDefaults[type as keyof typeof NodeDefaults]
const newData = {
id: id,
type: type,
position: position,
data: data,
+ deletable: deletable,
}
return {...defaultData, ...newData}
}
//* Initial nodes, created by using createNode. */
const initialNodes : Node[] = [
- createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}),
- createNode('end', 'end', {x: 370, y: 100}, {label: "End"}),
+ createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}, false),
+ createNode('end', 'end', {x: 370, y: 100}, {label: "End"}, false),
createNode('phase-1', 'phase', {x:200, y:100}, {label: "Phase 1", children: ['end', 'start']}),
createNode('norms-1', 'norm', {x:-200, y:100}, {label: "Initial Norms", normList: ["Be a robot", "get good"]}),
];
@@ -92,12 +93,20 @@ const useFlowStore = create((set, get) => ({
set({ edgeReconnectSuccessful: true });
},
- deleteNode: (nodeId) =>
- set({
+ deleteNode: (nodeId) => {
+ // Let's find our node to check if they have a special deletion function
+ const ourNode = get().nodes.find((n)=>n.id==nodeId);
+ const ourFunction = Object.entries(NodeDeletes).find(([t])=>t==ourNode?.type)?.[1]
+
+ // If there's no function, OR, our function tells us we can delete it, let's do so...
+ if (ourFunction == undefined || ourFunction()) {
+ set({
nodes: get().nodes.filter((n) => n.id !== nodeId),
edges: get().edges.filter((e) => e.source !== nodeId && e.target !== nodeId),
- }),
+ })}
+ },
+
setNodes: (nodes) => set({ nodes }),
setEdges: (edges) => set({ edges }),
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
index c7007e6..b7159b6 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
@@ -18,7 +18,7 @@ export type EndNode = Node
export default function EndNode(props: NodeProps) {
return (
<>
-
+
End
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts
index a55832e..fc4d3aa 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts
@@ -6,6 +6,7 @@ import type { GoalNodeData } from "./GoalNode";
export const GoalNodeDefaults: GoalNodeData = {
label: "Goal Node",
droppable: true,
- GoalList: [],
+ description: "The robot will strive towards this goal",
+ achieved: false,
hasReduce: true,
};
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx
index 799c199..ce0b119 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx
@@ -8,6 +8,8 @@ import {
} from '@xyflow/react';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
+import { TextField } from '../../../../components/TextField';
+import useFlowStore from '../VisProgStores';
/**
* The default data dot a Goal node
@@ -17,8 +19,9 @@ import styles from '../../VisProg.module.css';
*/
export type GoalNodeData = {
label: string;
+ description: string;
droppable: boolean;
- GoalList: string[];
+ achieved: boolean;
hasReduce: boolean;
};
@@ -37,23 +40,47 @@ export function GoalNodeCanConnect(connection: Connection | Edge): boolean {
* @returns React.JSX.Element
*/
export default function GoalNode(props: NodeProps
) {
- const label_input_id = `Goal_${props.id}_label_input`;
const data = props.data as GoalNodeData;
- return (
- <>
-
-
-
-
- {props.data.label as string}
-
- {data.GoalList.map((Goal) => (
{Goal}
))}
-
+ const {updateNodeData} = useFlowStore();
+
+ const text_input_id = `goal_${props.id}_text_input`;
+ const checkbox_id = `goal_${props.id}_checkbox`;
+
+ const setDescription = (value: string) => {
+ updateNodeData(props.id, {...data, description: value});
+ }
+
+ const setAchieved = (value: boolean) => {
+ updateNodeData(props.id, {...data, achieved: value});
+ }
+
+ return <>
+
+
+
+
+ setDescription(val)}
+ placeholder={"To ..."}
+ />
- >
- );
+
+
+ setAchieved(e.target.checked)}
+ />
+
+
+
+ >;
}
+
/**
* Reduces each Goal, including its children down into its relevant data.
* @param props: The Node Properties of this node.
@@ -66,7 +93,7 @@ export function GoalReduce(node: Node, nodes: Node[]) {
const data = node.data as GoalNodeData;
return {
label: data.label,
- list: data.GoalList,
+ achieved: data.achieved,
}
}
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
index a3a3ce6..d99a6ef 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
@@ -20,7 +20,7 @@ export type StartNode = Node
export default function StartNode(props: NodeProps) {
return (
<>
-
+
Start
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts
index d3edeca..725f0d8 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts
@@ -6,6 +6,7 @@ import type { TriggerNodeData } from "./TriggerNode";
export const TriggerNodeDefaults: TriggerNodeData = {
label: "Trigger Node",
droppable: true,
- TriggerList: [],
+ triggers: [{id: "help-trigger", keyword:"help"}],
+ triggerType: "keywords",
hasReduce: true,
};
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx
index f9424ac..97a792e 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx
@@ -8,6 +8,10 @@ import {
} from '@xyflow/react';
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
+import useFlowStore from '../VisProgStores';
+import { useState } from 'react';
+import { RealtimeTextField, TextField } from '../../../../components/TextField';
+import duplicateIndices from '../../../../utils/duplicateIndices';
/**
* The default data dot a Trigger node
@@ -18,12 +22,12 @@ import styles from '../../VisProg.module.css';
export type TriggerNodeData = {
label: string;
droppable: boolean;
- TriggerList: string[];
+ triggerType: unknown;
+ triggers: [unknown];
hasReduce: boolean;
};
-
export type TriggerNode = Node
@@ -37,21 +41,28 @@ export function TriggerNodeCanConnect(connection: Connection | Edge): boolean {
* @returns React.JSX.Element
*/
export default function TriggerNode(props: NodeProps) {
- const label_input_id = `Trigger_${props.id}_label_input`;
- const data = props.data as TriggerNodeData;
- return (
- <>
-
-
-
-
- {props.data.label as string}
-
- {data.TriggerList.map((Trigger) => (
{Trigger}
))}
-
-
- >
- );
+ const data = props.data as TriggerNodeData
+ const {updateNodeData} = useFlowStore();
+
+ const setKeywords = (keywords: Keyword[]) => {
+ updateNodeData(props.id, {...data, triggers: keywords});
+ }
+
+ return <>
+
+
+ {data.triggerType === "emotion" && (
+
Emotion?
+ )}
+ {data.triggerType === "keywords" && (
+
+ )}
+
+
+ >;
}
/**
@@ -66,7 +77,7 @@ export function TriggerReduce(node: Node, nodes: Node[]) {
const data = node.data as TriggerNodeData;
return {
label: data.label,
- list: data.TriggerList,
+ list: data.triggers,
}
}
@@ -75,4 +86,91 @@ export function TriggerConnects(thisNode: Node, otherNode: Node, isThisSource: b
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
console.warn("Impossible node connection called in EndConnects")
}
+}
+
+
+export type EmotionTriggerNodeProps = {
+ type: "emotion";
+ value: string;
+}
+
+type Keyword = { id: string, keyword: string };
+
+export type KeywordTriggerNodeProps = {
+ type: "keywords";
+ value: Keyword[];
+}
+
+export type TriggerNodeProps = EmotionTriggerNodeProps | KeywordTriggerNodeProps;
+
+function KeywordAdder({ addKeyword }: { addKeyword: (keyword: string) => void }) {
+ const [input, setInput] = useState("");
+
+ const text_input_id = "keyword_adder_input";
+
+ return
+
+ {
+ if (!input) return;
+ addKeyword(input);
+ setInput("");
+ }}
+ placeholder={"..."}
+ className={"flex-1"}
+ />
+
;
+}
+
+function Keywords({
+ keywords,
+ setKeywords,
+}: {
+ keywords: Keyword[];
+ setKeywords: (keywords: Keyword[]) => void;
+}) {
+ type Interpolatable = string | number | boolean | bigint | null | undefined;
+
+ const inputElementId = (id: Interpolatable) => `keyword_${id}_input`;
+
+ /** Indices of duplicates in the keyword array. */
+ const [duplicates, setDuplicates] = useState([]);
+
+ function replace(id: string, value: string) {
+ value = value.trim();
+ const newKeywords = value === ""
+ ? keywords.filter((kw) => kw.id != id)
+ : keywords.map((kw) => kw.id === id ? {...kw, keyword: value} : kw);
+ setKeywords(newKeywords);
+ setDuplicates(duplicateIndices(newKeywords.map((kw) => kw.keyword)));
+ }
+
+ function add(value: string) {
+ value = value.trim();
+ if (value === "") return;
+ const newKeywords = [...keywords, {id: crypto.randomUUID(), keyword: value}];
+ setKeywords(newKeywords);
+ setDuplicates(duplicateIndices(newKeywords.map((kw) => kw.keyword)));
+ }
+
+ return <>
+ Triggers when {keywords.length <= 1 ? "the keyword is" : "all keywords are"} spoken.
+ {[...keywords].map(({id, keyword}, index) => {
+ return
+
+ replace(id, val)}
+ placeholder={"..."}
+ className={"flex-1"}
+ invalid={duplicates.includes(index)}
+ />
+
;
+ })}
+
+ >;
}
\ No newline at end of file