// This program has been developed by students from the bachelor Computer Science at Utrecht // University within the Software Project course. // © Copyright Utrecht University (Department of Information and Computing Sciences) 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"; import { TextField } from "../../../../components/TextField"; import GestureValueEditor from "./GestureValueEditor"; type PlanEditorDialogProps = { plan?: Plan; onSave: (plan: Plan | undefined) => void; description? : string; }; export default function PlanEditorDialog({ plan, onSave, description, }: PlanEditorDialogProps) { // UseStates and references const dialogRef = useRef(null); const [draftPlan, setDraftPlan] = useState(null); const [newActionType, setNewActionType] = useState("speech"); const [newActionGestureType, setNewActionGestureType] = useState(true); const [newActionValue, setNewActionValue] = useState(""); const [hasInteractedWithPlan, setHasInteractedWithPlan] = useState(false) const { setScrollable } = useFlowStore(); const nodes = useFlowStore().nodes; //Button Actions const openCreate = () => { setScrollable(false); setDraftPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()}); 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(); setHasInteractedWithPlan(true) switch (newActionType) { case "speech": return { id, text: newActionValue, type: "speech" }; case "gesture": return { id, gesture: newActionValue, isTag: newActionGestureType, type: "gesture" }; case "llm": return { id, goal: newActionValue, type: "llm" }; } }; 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 && !hasInteractedWithPlan) && (
)} {/* Action value editor*/} {newActionType === "gesture" ? ( // Gesture get their own editor component ) : ( )} {/* Adding steps */}
{/* Right Side (Steps shown) */}

Steps

{/* 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}: { // This just tries to find the goals name, i know it looks ugly:( step.type === "goal" ? ((nodes.find(x => x.id === step.id)?.data.name as string) == "" ? "unnamed goal": (nodes.find(x => x.id === step.id)?.data.name as string)) : (GetActionValue(step) ?? "")}
))}
)} {/* Buttons */}
{/* Close button */} {/* Confirm/ Create button */} {/* Reset button */}
); }