diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.module.css b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.module.css index ed16c78..583c023 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.module.css +++ b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.module.css @@ -6,7 +6,6 @@ overscroll-behavior: contain; } - .planDialog::backdrop { background: rgba(0, 0, 0, 0.4); } @@ -68,4 +67,17 @@ font-style: italic; } +.dragHandle { + margin-left: auto; + cursor: grab; + opacity: 0.5; + user-select: none; +} +.dragHandle:active { + cursor: grabbing; +} + +.planStepDragging { + opacity: 0.4; +} \ No newline at end of file diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx index 2c2d098..04d2774 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx @@ -6,16 +6,26 @@ 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); @@ -24,10 +34,11 @@ export default function PlanEditorDialog({ 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 + // Button Actions const openCreate = () => { setScrollable(false); setDraftPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()}); @@ -89,9 +100,9 @@ export default function PlanEditorDialog({ data-testid={"PlanEditorDialogTestID"} >
-

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

+

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

{/* Plan name text field */} - {draftPlan && ( + {(draftPlan && !onlyEditPhasing) && ( @@ -104,12 +115,14 @@ export default function PlanEditorDialog({ {draftPlan && (
{/* Left Side (Action Adder) */} -

Add Action

+

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

{(!plan && description && draftPlan.steps.length === 0 && !hasInteractedWithPlan) && (
)} - )} - {/* Action value editor*/} - {newActionType === "gesture" ? ( + {/* Action value editor*/} + {!onlyEditPhasing && newActionType === "gesture" ? ( // Gesture get their own editor component - ) : ( - - )} - + />) + )} + {/* Adding steps */}
+ + + {(data.plan && data.plan.steps.length > 0) && (
+ { + updateNodeData(props.id, { + ...data, + plan, + }); + }} + description={props.data.label} + onlyEditPhasing={true} + /> +
)} + + + node.id === _sourceNodeId)! + switch (sourceNode.type) { case "phase": break; case "start": data.isFirstPhase = true; break; @@ -139,6 +163,18 @@ export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) { // endNodes cannot be the source of an outgoing connection // so we don't need to cover them with a special case // before handling the default behavior + case "goal": { + // First, let's see if we have a plan currently. If not, let's create a default plan with this goal inside.:) + if (!data.plan) { + data.plan = insertGoalInPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()} as Plan, sourceNode as GoalNode) + } + + // Else, lets just insert this goal into our current plan. + else { + data.plan = insertGoalInPlan(structuredClone(data.plan), sourceNode as GoalNode) + } + break; + } default: data.children.push(_sourceNodeId); break; } }