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"; /** * The properties of a plan editor. * @property plan: The optional plan loaded into this editor. * @property onSave: The function that will be called upon save. * @property description: Optional description which is already set. * @property onlyEditPhasing: Optional boolean to toggle * whether or not this editor is part of the phase editing. */ type PlanEditorDialogProps = { plan?: Plan; onSave: (plan: Plan | undefined) => void; description? : string; onlyEditPhasing? : boolean; }; export default function PlanEditorDialog({ plan, onSave, description, onlyEditPhasing = false, }: 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 [draggedIndex, setDraggedIndex] = useState(null); 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"} >

{onlyEditPhasing ? "Editing Phase Ordering" : (draftPlan?.id === plan?.id ? "Edit Plan" : "Create Plan")}

{/* Plan name text field */} {(draftPlan && !onlyEditPhasing) && ( 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) */}

{onlyEditPhasing ? "You can't add any actions, only rearrange the steps." : "Add Action"}

{(!plan && description && draftPlan.steps.length === 0 && !hasInteractedWithPlan) && (
)} {(!onlyEditPhasing) && ()} {/* Action value editor*/} {!onlyEditPhasing && newActionType === "gesture" ? ( // Gesture get their own editor component ) : // Only show the text field if we're not just rearranging. (!onlyEditPhasing && () )} {/* 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 */}
); }