diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx index 06e072c..c54666a 100644 --- a/src/pages/VisProgPage/VisProg.tsx +++ b/src/pages/VisProgPage/VisProg.tsx @@ -48,7 +48,8 @@ const selector = (state: FlowState) => ({ undo: state.undo, redo: state.redo, beginBatchAction: state.beginBatchAction, - endBatchAction: state.endBatchAction + endBatchAction: state.endBatchAction, + scrollable: state.scrollable }); // --| define ReactFlow editor |-- @@ -72,7 +73,8 @@ const VisProgUI = () => { undo, redo, beginBatchAction, - endBatchAction + endBatchAction, + scrollable } = useFlowStore(useShallow(selector)); // instructs the editor to use the corresponding functions from the FlowStore // adds ctrl+z and ctrl+y support to respectively undo and redo actions @@ -101,6 +103,7 @@ const VisProgUI = () => { onConnect={onConnect} onNodeDragStart={beginBatchAction} onNodeDragStop={endBatchAction} + preventScrolling={scrollable} snapToGrid fitView proOptions={{hideAttribution: true}} diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx index 676019a..ec85a97 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx @@ -72,6 +72,15 @@ const useFlowStore = create(UndoRedo((set, get) => ({ nodes: initialNodes, edges: initialEdges, edgeReconnectSuccessful: true, + scrollable: true, + + /** + * handles changing the scrollable state of the editor, + * this is used to control if scrolling is captured by the editor + * or if it's available to other components within the reactFlowProvider + * @param {boolean} val - the desired state + */ + setScrollable: (val) => set({scrollable: val}), /** * Handles changes to nodes triggered by ReactFlow. diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx index d5d8c06..ab6674b 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx @@ -23,6 +23,10 @@ export type FlowState = { nodes: Node[]; edges: Edge[]; edgeReconnectSuccessful: boolean; + scrollable: boolean; + + /** Handler for managing scrollable state */ + setScrollable: (value: boolean) => void; /** Handler for changes to nodes triggered by ReactFlow */ onNodesChange: OnNodesChange; diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx index abfdf84..ac41241 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx @@ -1,4 +1,5 @@ -import { useRef, useState } from "react"; +import {useRef, useState} from "react"; +import useFlowStore from "../VisProgStores.tsx"; import styles from './PlanEditor.module.css'; import { GetActionValue, type Action, type ActionTypes, type Plan } from "../components/Plan"; import { defaultPlan } from "../components/Plan.default"; @@ -16,220 +17,225 @@ export default function PlanEditorDialog({ onSave, description, }: PlanEditorDialogProps) { - // UseStates and references - const dialogRef = useRef(null); - const [draftPlan, setDraftPlan] = useState(null); - const [newActionType, setNewActionType] = useState("speech"); - const [newActionValue, setNewActionValue] = useState(""); + // UseStates and references + const dialogRef = useRef(null); + const [draftPlan, setDraftPlan] = useState(null); + const [newActionType, setNewActionType] = useState("speech"); + const [newActionValue, setNewActionValue] = useState(""); + const { setScrollable } = useFlowStore(); - //Button Actions - const openCreate = () => { - setDraftPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()}); - dialogRef.current?.showModal(); - }; + //Button Actions + const openCreate = () => { + setScrollable(false); + setDraftPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()}); + dialogRef.current?.showModal(); + }; - const openCreateWithDescription = () => { - setDraftPlan({...structuredClone(defaultPlan), id: crypto.randomUUID(), name: description!}); - setNewActionType("llm") - setNewActionValue(description!) - dialogRef.current?.showModal(); + const openCreateWithDescription = () => { + setScrollable(false); + setDraftPlan({...structuredClone(defaultPlan), id: crypto.randomUUID(), name: description!}); + setNewActionType("llm") + setNewActionValue(description!) + dialogRef.current?.showModal(); + } + + const openEdit = () => { + setScrollable(false); + if (!plan) return; + setDraftPlan(structuredClone(plan)); + dialogRef.current?.showModal(); + }; + + const close = () => { + setScrollable(true); + dialogRef.current?.close(); + setDraftPlan(null); + }; + + const buildAction = (): Action => { + const id = crypto.randomUUID(); + switch (newActionType) { + case "speech": + return { id, text: newActionValue, type: "speech" }; + case "gesture": + return { id, gesture: newActionValue, type: "gesture" }; + case "llm": + return { id, goal: newActionValue, type: "llm" }; } + }; - const openEdit = () => { - if (!plan) return; - setDraftPlan(structuredClone(plan)); - dialogRef.current?.showModal(); - }; + return (<> + {/* Create and edit buttons */} + {!plan && ( + + )} + {plan && ( + + )} - const close = () => { - dialogRef.current?.close(); - setDraftPlan(null); - }; + {/* Start of dialog (plan editor) */} + e.stopPropagation()} + data-testid={"PlanEditorDialogTestID"} + > +
+

{draftPlan?.id === plan?.id ? "Edit Plan" : "Create Plan"}

+ {/* Plan name text field */} + {draftPlan && ( + + setDraftPlan({ ...draftPlan, name })} + placeholder="Plan name" + data-testid="name_text_field"/> + )} - const buildAction = (): Action => { - const id = crypto.randomUUID(); - switch (newActionType) { - case "speech": - return { id, text: newActionValue, type: "speech" }; - case "gesture": - return { id, gesture: newActionValue, type: "gesture" }; - case "llm": - return { id, goal: newActionValue, type: "llm" }; - } - }; + {/* Entire "bottom" part (adder and steps) without cancel, confirm and reset */} + {draftPlan && (
+
+ {/* Left Side (Action Adder) */} +

Add Action

+ {(!plan && description && draftPlan.steps.length === 0) && (
+ + +
)} + - return (<> - {/* Create and edit buttons */} - {!plan && ( - - )} - {plan && ( - - )} - - {/* Start of dialog (plan editor) */} - e.stopPropagation()} - data-testid={"PlanEditorDialogTestID"} - > - -

{draftPlan?.id === plan?.id ? "Edit Plan" : "Create Plan"}

- {/* Plan name text field */} - {draftPlan && ( - - setDraftPlan({ ...draftPlan, name })} - placeholder="Plan name" - data-testid="name_text_field"/> - )} - - {/* Entire "bottom" part (adder and steps) without cancel, confirm and reset */} - {draftPlan && (
-
- {/* Left Side (Action Adder) */} -

Add Action

- {(!plan && description && draftPlan.steps.length === 0) && (
- - -
)} - - - {/* Action value editor*/} - {newActionType === "gesture" ? ( - // Gesture get their own editor component - + ) : ( + - ) : ( - - )} - - {/* Adding steps */} - -
- - {/* Right Side (Steps shown) */} -
-

Steps

+ placeholder={ + newActionType === "speech" ? "Speech text" + : "LLM goal" + } + /> + )} - {/* Show if there are no steps yet */} - {draftPlan.steps.length === 0 && ( -
- No steps yet -
- )} - - - {/* Map over all steps */} - {draftPlan.steps.map((step, index) => ( -
{ - if (e.key === "Enter" || e.key === " ") { - setDraftPlan({ - ...draftPlan, - steps: draftPlan.steps.filter((s) => s.id !== step.id),}); - }}} - onClick={() => { - setDraftPlan({ - ...draftPlan, - steps: draftPlan.steps.filter((s) => s.id !== step.id),}); - }}> - - {index + 1}. - {step.type}: - { - step.type == "goal" ? ""/* TODO: Add support for goals */ - : GetActionValue(step)} - -
- ))} -
+ {/* Adding steps */} +
- )} - {/* Buttons */} -
- {/* Close button */} - + {/* Right Side (Steps shown) */} +
+

Steps

- {/* Confirm/ Create button */} - + {/* Show if there are no steps yet */} + {draftPlan.steps.length === 0 && ( +
+ No steps yet +
+ )} - {/* Reset button */} - -
- -
- - ); + + {/* Map over all steps */} + {draftPlan.steps.map((step, index) => ( +
{ + if (e.key === "Enter" || e.key === " ") { + setDraftPlan({ + ...draftPlan, + steps: draftPlan.steps.filter((s) => s.id !== step.id),}); + }}} + onClick={() => { + setDraftPlan({ + ...draftPlan, + steps: draftPlan.steps.filter((s) => s.id !== step.id),}); + }}> + + {index + 1}. + {step.type}: + { + step.type == "goal" ? ""/* TODO: Add support for goals */ + : GetActionValue(step)} + +
+ ))} +
+
+ )} + + {/* Buttons */} +
+ {/* Close button */} + + + {/* Confirm/ Create button */} + + + {/* Reset button */} + +
+ +
+ +); } \ No newline at end of file