Compare commits

6 Commits

Author SHA1 Message Date
Björn Otgaar
26894cc2d2 Merge branch 'dev' into chore/clean-visprog-code 2026-01-28 12:27:27 +01:00
Björn Otgaar
dcf4f01e68 chore: replace plan editor functionality into own folder with own files. 2026-01-28 12:26:57 +01:00
Gerla, J. (Justin)
c84088dd9d Merge branch 'chore/editing-css-strings' into 'dev'
chore: updated css comments

See merge request ics/sp/2025/n25b/pepperplus-ui!51
2026-01-28 11:19:16 +00:00
JGerla
dfe793e04a chore: updated css comments 2026-01-28 12:07:33 +01:00
Björn Otgaar
1876138fe2 Merge branch 'dev' into chore/clean-visprog-code 2026-01-28 11:36:21 +01:00
Björn Otgaar
5930e24bf4 chore: intitial commit, starting seperating concerns for plan editor. 2026-01-28 11:35:34 +01:00
35 changed files with 416 additions and 295 deletions

View File

@@ -1,3 +1,6 @@
// 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)
export default function Next({ fill }: { fill?: string }) { export default function Next({ fill }: { fill?: string }) {
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}> return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
<path d="M664.07-224.93v-510.14h91v510.14h-91Zm-459.14 0v-510.14L587.65-480 204.93-224.93Z"/> <path d="M664.07-224.93v-510.14h91v510.14h-91Zm-459.14 0v-510.14L587.65-480 204.93-224.93Z"/>

View File

@@ -1,3 +1,6 @@
// 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)
export default function Pause({ fill }: { fill?: string }) { export default function Pause({ fill }: { fill?: string }) {
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}> return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
<path d="M556.17-185.41v-589.18h182v589.18h-182Zm-334.34 0v-589.18h182v589.18h-182Z"/> <path d="M556.17-185.41v-589.18h182v589.18h-182Zm-334.34 0v-589.18h182v589.18h-182Z"/>

View File

@@ -1,3 +1,6 @@
// 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)
export default function Play({ fill }: { fill?: string }) { export default function Play({ fill }: { fill?: string }) {
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}> return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
<path d="M311.87-185.41v-589.18L775.07-480l-463.2 294.59Z"/> <path d="M311.87-185.41v-589.18L775.07-480l-463.2 294.59Z"/>

View File

@@ -1,3 +1,6 @@
// 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)
export default function Redo({ fill }: { fill?: string }) { export default function Redo({ fill }: { fill?: string }) {
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}> return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
<path d="M390.98-191.87q-98.44 0-168.77-65.27-70.34-65.27-70.34-161.43 0-96.15 70.34-161.54 70.33-65.39 168.77-65.39h244.11l-98.98-98.98 63.65-63.65L808.13-600 599.76-391.87l-63.65-63.65 98.98-98.98H390.98q-60.13 0-104.12 38.92-43.99 38.93-43.99 96.78 0 57.84 43.99 96.89 43.99 39.04 104.12 39.04h286.15v91H390.98Z"/> <path d="M390.98-191.87q-98.44 0-168.77-65.27-70.34-65.27-70.34-161.43 0-96.15 70.34-161.54 70.33-65.39 168.77-65.39h244.11l-98.98-98.98 63.65-63.65L808.13-600 599.76-391.87l-63.65-63.65 98.98-98.98H390.98q-60.13 0-104.12 38.92-43.99 38.93-43.99 96.78 0 57.84 43.99 96.89 43.99 39.04 104.12 39.04h286.15v91H390.98Z"/>

View File

@@ -1,3 +1,6 @@
// 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)
export default function Replay({ fill }: { fill?: string }) { export default function Replay({ fill }: { fill?: string }) {
return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}> return <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill={fill ?? "canvas"}>
<path d="M480.05-70.43q-76.72 0-143.78-29.1-67.05-29.1-116.75-78.8-49.69-49.69-78.79-116.75-29.1-67.05-29.1-143.49h91q0 115.81 80.73 196.47Q364.1-161.43 480-161.43q115.8 0 196.47-80.74 80.66-80.73 80.66-196.63 0-115.81-80.73-196.47-80.74-80.66-196.64-80.66h-6.24l60.09 60.08-58.63 60.63-166.22-166.21 166.22-166.22 58.63 60.87-59.85 59.85h6q76.74 0 143.76 29.09 67.02 29.1 116.84 78.8 49.81 49.69 78.91 116.64 29.1 66.95 29.1 143.61 0 76.66-29.1 143.71-29.1 67.06-78.79 116.75-49.7 49.7-116.7 78.8-67.01 29.1-143.73 29.1Z"/> <path d="M480.05-70.43q-76.72 0-143.78-29.1-67.05-29.1-116.75-78.8-49.69-49.69-78.79-116.75-29.1-67.05-29.1-143.49h91q0 115.81 80.73 196.47Q364.1-161.43 480-161.43q115.8 0 196.47-80.74 80.66-80.73 80.66-196.63 0-115.81-80.73-196.47-80.74-80.66-196.64-80.66h-6.24l60.09 60.08-58.63 60.63-166.22-166.21 166.22-166.22 58.63 60.87-59.85 59.85h6q76.74 0 143.76 29.09 67.02 29.1 116.84 78.8 49.81 49.69 78.91 116.64 29.1 66.95 29.1 143.61 0 76.66-29.1 143.71-29.1 67.06-78.79 116.75-49.7 49.7-116.7 78.8-67.01 29.1-143.73 29.1Z"/>

View File

@@ -1,3 +1,6 @@
// 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 type {Cell} from "../../utils/cellStore.ts"; import type {Cell} from "../../utils/cellStore.ts";
import type {LogRecord} from "./useLogs.ts"; import type {LogRecord} from "./useLogs.ts";

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.filter-root { .filter-root {
position: relative; position: relative;
display: flex; display: flex;

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.logging-container { .logging-container {
box-sizing: border-box; box-sizing: border-box;

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.text-field { .text-field {
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 5pt; border-radius: 5pt;

View File

@@ -4,6 +4,11 @@ University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */}
/*
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)
*/
:root { :root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5; line-height: 1.5;

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.read_the_docs { .read_the_docs {
color: #888; color: #888;
} }

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.dashboardContainer { .dashboardContainer {
display: grid; display: grid;
grid-template-columns: 2fr 1fr; /* Left = content, Right = logs */ grid-template-columns: 2fr 1fr; /* Left = content, Right = logs */

View File

@@ -1,3 +1,8 @@
/*
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)
*/
.logs { .logs {
/* grid-area used in MonitoringPage.module.css */ /* grid-area used in MonitoringPage.module.css */
grid-area: logs; grid-area: logs;

View File

@@ -1,3 +1,6 @@
// 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 styles from "./ExperimentLogs.module.css"; import styles from "./ExperimentLogs.module.css";
import {LogMessages} from "../../../components/Logging/Logging.tsx"; import {LogMessages} from "../../../components/Logging/Logging.tsx";
import {useEffect, useMemo, useState} from "react"; import {useEffect, useMemo, useState} from "react";

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
/* editor UI */ /* editor UI */
.inner-editor-container { .inner-editor-container {

View File

@@ -1,253 +0,0 @@
// 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<HTMLDialogElement | null>(null);
const [draftPlan, setDraftPlan] = useState<Plan | null>(null);
const [newActionType, setNewActionType] = useState<ActionTypes>("speech");
const [newActionGestureType, setNewActionGestureType] = useState<boolean>(true);
const [newActionValue, setNewActionValue] = useState("");
const [hasInteractedWithPlan, setHasInteractedWithPlan] = useState<boolean>(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 && (
<button className={styles.nodeButton} onClick={description ? openCreateWithDescription : openCreate}>
Create Plan
</button>
)}
{plan && (
<button className={styles.nodeButton} onClick={openEdit}>
Edit Plan
</button>
)}
{/* Start of dialog (plan editor) */}
<dialog
ref={dialogRef}
className={`${styles.planDialog}`}
//onWheel={(e) => e.stopPropagation()}
data-testid={"PlanEditorDialogTestID"}
>
<form method="dialog" className="flex-col gap-md">
<h3> {draftPlan?.id === plan?.id ? "Edit Plan" : "Create Plan"} </h3>
{/* Plan name text field */}
{draftPlan && (
<TextField
value={draftPlan.name}
setValue={(name) =>
setDraftPlan({ ...draftPlan, name })}
placeholder="Plan name"
data-testid="name_text_field"/>
)}
{/* Entire "bottom" part (adder and steps) without cancel, confirm and reset */}
{draftPlan && (<div className={styles.planEditor}>
<div className={styles.planEditorLeft}>
{/* Left Side (Action Adder) */}
<h4>Add Action</h4>
{(!plan && description && draftPlan.steps.length === 0 && !hasInteractedWithPlan) && (<div className={styles.stepSuggestion}>
<label> Filled in as a suggestion! </label>
<label> Feel free to change! </label>
</div>)}
<label>
Action Type <wbr />
{/* Type selection */}
<select
value={newActionType}
onChange={(e) => {
setNewActionType(e.target.value as ActionTypes);
// Reset value when action type changes
setNewActionValue("");
}}>
<option value="speech">Speech Action</option>
<option value="gesture">Gesture Action</option>
<option value="llm">LLM Action</option>
</select>
</label>
{/* Action value editor*/}
{newActionType === "gesture" ? (
// Gesture get their own editor component
<GestureValueEditor
value={newActionValue}
setValue={setNewActionValue}
setType={setNewActionGestureType}
placeholder="Gesture name"
/>
) : (
<TextField
value={newActionValue}
setValue={setNewActionValue}
placeholder={
newActionType === "speech" ? "Speech text"
: "LLM goal"
}
/>
)}
{/* Adding steps */}
<button
type="button"
disabled={!newActionValue}
onClick={() => {
if (!draftPlan) return;
// Add action to steps
const action = buildAction();
setDraftPlan({
...draftPlan,
steps: [...draftPlan.steps, action],});
// Reset current action building
setNewActionValue("");
setNewActionType("speech");
}}>
Add Step
</button>
</div>
{/* Right Side (Steps shown) */}
<div className={styles.planEditorRight}>
<h4>Steps</h4>
{/* Show if there are no steps yet */}
{draftPlan.steps.length === 0 && (
<div className={styles.emptySteps}>
No steps yet
</div>
)}
{/* Map over all steps */}
{draftPlan.steps.map((step, index) => (
<div
role="button"
tabIndex={0}
key={step.id}
className={styles.planStep}
// Extra logic for screen readers to access using keyboard
onKeyDown={(e) => {
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),});
}}>
<span className={styles.stepIndex}>{index + 1}.</span>
<span className={styles.stepType}>{step.type}:</span>
<span className={styles.stepName}>
{
// 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) ?? "")}
</span>
</div>
))}
</div>
</div>
)}
{/* Buttons */}
<div className="flex-row gap-md">
{/* Close button */}
<button type="button" onClick={close}>
Cancel
</button>
{/* Confirm/ Create button */}
<button
type="button"
disabled={!draftPlan}
onClick={() => {
if (!draftPlan) return;
onSave(draftPlan);
close();
}}>
{draftPlan?.id === plan?.id ? "Confirm" : "Create"}
</button>
{/* Reset button */}
<button
type="button"
disabled={!draftPlan}
onClick={() => {
onSave(undefined);
close();
}}>
Reset
</button>
</div>
</form>
</dialog>
</>
);
}

View File

@@ -0,0 +1,72 @@
import { TextField } from "../../../../../components/TextField";
import GestureValueEditor from "./GestureValueEditor";
import type { ActionTypes } from "./Plan";
import styles from './PlanEditor.module.css';
type ActionAdderProps = {
newActionType: ActionTypes;
setNewActionType: (t: ActionTypes) => void;
newActionValue: string;
setNewActionValue: (v: string) => void;
setNewActionGestureType: (b: boolean) => void;
onAdd: () => void;
showSuggestion: boolean;
};
export function ActionAdder({
newActionType,
setNewActionType,
newActionValue,
setNewActionValue,
setNewActionGestureType,
onAdd,
showSuggestion,
}: ActionAdderProps) {
return (
<div className={styles.planEditorLeft}>
<h4>Add Action</h4>
{showSuggestion && (
<div className={styles.stepSuggestion}>
<label>Filled in as a suggestion!</label>
<label>Feel free to change!</label>
</div>
)}
<label>
Action Type <wbr />
<select
value={newActionType}
onChange={(e) => {
setNewActionType(e.target.value as ActionTypes);
setNewActionValue("");
}}
>
<option value="speech">Speech Action</option>
<option value="gesture">Gesture Action</option>
<option value="llm">LLM Action</option>
</select>
</label>
{newActionType === "gesture" ? (
<GestureValueEditor
value={newActionValue}
setValue={setNewActionValue}
setType={setNewActionGestureType}
placeholder="Gesture name"
/>
) : (
<TextField
value={newActionValue}
setValue={setNewActionValue}
placeholder={newActionType === "speech" ? "Speech text" : "LLM goal"}
/>
)}
<button type="button" disabled={!newActionValue} onClick={onAdd}>
Add Step
</button>
</div>
);
}

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.gestureEditor { .gestureEditor {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -2,7 +2,7 @@
// University within the Software Project course. // University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences) // © Copyright Utrecht University (Department of Information and Computing Sciences)
import { type Node } from "@xyflow/react" import { type Node } from "@xyflow/react"
import { GoalReduce } from "../nodes/GoalNode" import { GoalReduce } from "../../nodes/GoalNode"
export type Plan = { export type Plan = {
@@ -125,3 +125,5 @@ export function GetActionValue(action: Action) {
default: default:
} }
} }

View File

@@ -2,7 +2,7 @@
// University within the Software Project course. // University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences) // © Copyright Utrecht University (Department of Information and Computing Sciences)
// This file is to avoid sharing both functions and components which eslint dislikes. :) // This file is to avoid sharing both functions and components which eslint dislikes. :)
import type { GoalNode } from "../nodes/GoalNode" import type { GoalNode } from "../../nodes/GoalNode"
import type { Goal, Plan } from "./Plan" import type { Goal, Plan } from "./Plan"
/** /**
@@ -36,3 +36,4 @@ export function deleteGoalInPlanByID(plan: Plan, goalID: string) {
} }
return updatedPlan.steps.length == 0 ? undefined : updatedPlan return updatedPlan.steps.length == 0 ? undefined : updatedPlan
} }

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.planDialog { .planDialog {
overflow:visible; overflow:visible;
width: 80vw; width: 80vw;

View File

@@ -0,0 +1,214 @@
// 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 { type Action, type ActionTypes, type Plan } from "./Plan.tsx";
import { defaultPlan } from "./Plan.default.ts";
import { TextField } from "../../../../../components/TextField.tsx";
import { StepsList } from "./StepList.tsx";
import { ActionAdder } from "./ActionAdder.tsx";
type PlanEditorDialogProps = {
plan?: Plan;
onSave: (plan: Plan | undefined) => void;
description? : string;
};
/**
* Creates an action, as a step for a plan.
* @param type the type of action to build
* @param value the value of this action to build
* @param isGestureTag whether or not this action, restricted to gestures, is a tag.
* @returns An action
*/
function buildAction(
type: ActionTypes,
value: string,
isGestureTag: boolean
): Action {
const id = crypto.randomUUID();
switch (type) {
case "speech":
return { id, text: value, type: "speech" };
case "gesture":
return { id, gesture: value, isTag: isGestureTag, type: "gesture" };
case "llm":
return { id, goal: value, type: "llm" };
}
}
export default function PlanEditorDialog({
plan,
onSave,
description,
}: PlanEditorDialogProps) {
// UseStates and references
const dialogRef = useRef<HTMLDialogElement | null>(null);
const [draftPlan, setDraftPlan] = useState<Plan | null>(null);
const [newActionType, setNewActionType] = useState<ActionTypes>("speech");
const [newActionGestureType, setNewActionGestureType] = useState<boolean>(true);
const [newActionValue, setNewActionValue] = useState("");
const [hasInteractedWithPlan, setHasInteractedWithPlan] = useState<boolean>(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 addAction = () => {
if (!draftPlan) return;
// Add action to steps
const action = buildAction(newActionType, newActionValue, newActionGestureType);
setDraftPlan({
...draftPlan,
steps: [...draftPlan.steps, action],});
// Reset current action building
setNewActionValue("");
setNewActionType("speech");
setHasInteractedWithPlan(true);
}
const showSuggestion : boolean = (
!plan &&
!!description &&
draftPlan !== null &&
draftPlan.steps.length === 0 &&
!hasInteractedWithPlan)
return (<>
{/* Create and edit buttons */}
{!plan && (
<button className={styles.nodeButton} onClick={description ? openCreateWithDescription : openCreate}>
Create Plan
</button>
)}
{plan && (
<button className={styles.nodeButton} onClick={openEdit}>
Edit Plan
</button>
)}
{/* Start of dialog (plan editor) */}
<dialog
ref={dialogRef}
className={`${styles.planDialog}`}
//onWheel={(e) => e.stopPropagation()}
data-testid={"PlanEditorDialogTestID"}
>
<form method="dialog" className="flex-col gap-md">
<h3> {draftPlan?.id === plan?.id ? "Edit Plan" : "Create Plan"} </h3>
{/* Plan name text field */}
{draftPlan && (
<TextField
value={draftPlan.name}
setValue={(name) =>
setDraftPlan({ ...draftPlan, name })}
placeholder="Plan name"
data-testid="name_text_field"/>
)}
{/* Entire "bottom" part (adder and steps) without cancel, confirm and reset */}
{draftPlan && (<div className={styles.planEditor}>
<div className={styles.planEditorLeft}>
{/* Left Side (Action Adder) */}
<ActionAdder
newActionType={newActionType}
setNewActionType={setNewActionType}
newActionValue={newActionValue}
setNewActionValue={setNewActionValue}
setNewActionGestureType={setNewActionGestureType}
onAdd={addAction}
showSuggestion={showSuggestion}
/>
</div>
{/* Right Side (Steps shown) */}
<div className={styles.planEditorRight}>
<h4>Steps</h4>
{/* Map over all steps */}
<StepsList
steps={draftPlan.steps}
nodes={nodes}
onRemove={(id) =>
setDraftPlan({
...draftPlan,
steps: draftPlan.steps.filter(s => s.id !== id),
})
}
/>
</div>
</div>
)}
{/* Buttons */}
<div className="flex-row gap-md">
{/* Close button */}
<button type="button" onClick={close}>
Cancel
</button>
{/* Confirm/ Create button */}
<button
type="button"
disabled={!draftPlan}
onClick={() => {
if (!draftPlan) return;
onSave(draftPlan);
close();
}}>
{draftPlan?.id === plan?.id ? "Confirm" : "Create"}
</button>
{/* Reset button */}
<button
type="button"
disabled={!draftPlan}
onClick={() => {
onSave(undefined);
close();
}}>
Reset
</button>
</div>
</form>
</dialog>
</>
);
}

View File

@@ -0,0 +1,54 @@
import { GetActionValue, type PlanElement } from "./Plan";
import styles from './PlanEditor.module.css';
import { type Node} from "@xyflow/react"
type StepsListProps = {
steps: PlanElement[];
onRemove: (id: string) => void;
nodes: Node[];
};
function getStepLabel(
step: PlanElement,
nodes: Node[],
): string {
if (step.type === "goal") {
// For goals, we lookup the value through the nodes in the diagram
const node = nodes.find(n => n.id === step.id);
return (node?.data?.name as string)?.trim() || "unnamed goal";
}
// Not a goal, we lookup the correct action value of the action
return GetActionValue(step) ?? "";
}
export function StepsList({ steps, onRemove, nodes }: StepsListProps) {
if (steps.length === 0) {
return <div className={styles.emptySteps}>No steps yet</div>;
}
return (
<>
{steps.map((step, index) => (
<div
key={step.id}
role="button"
tabIndex={0}
className={styles.planStep}
onClick={() => onRemove(step.id)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
onRemove(step.id);
}
}}
>
<span className={styles.stepIndex}>{index + 1}.</span>
<span className={styles.stepType}>{step.type}:</span>
<span className={styles.stepName}>
{getStepLabel(step, nodes)}
</span>
</div>
))}
</>
);
}

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
:global(.react-flow__handle.source){ :global(.react-flow__handle.source){
border-radius: 100%; border-radius: 100%;
} }

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.save-load-panel { .save-load-panel {
border-radius: 0 0 5pt 5pt; border-radius: 0 0 5pt 5pt;
background-color: canvas; background-color: canvas;

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.warnings-sidebar { .warnings-sidebar {
min-width: auto; min-width: auto;
max-width: 340px; max-width: 340px;

View File

@@ -14,11 +14,11 @@ import { TextField } from '../../../../components/TextField';
import {MultiConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {MultiConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts"; import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores'; import useFlowStore from '../VisProgStores';
import {DoesPlanIterate, HasCheckingSubGoal, PlanReduce, type Plan } from '../components/Plan'; import {DoesPlanIterate, HasCheckingSubGoal, PlanReduce, type Plan } from '../components/PlanEditor/Plan.tsx';
import PlanEditorDialog from '../components/PlanEditor'; import PlanEditorDialog from '../components/PlanEditor/PlanEditor.tsx';
import { MultilineTextField } from '../../../../components/MultilineTextField'; import { MultilineTextField } from '../../../../components/MultilineTextField';
import { defaultPlan } from '../components/Plan.default.ts'; import { defaultPlan } from '../components/PlanEditor/Plan.default.ts';
import { deleteGoalInPlanByID, insertGoalInPlan } from '../components/PlanEditingFunctions.tsx'; import { deleteGoalInPlanByID, insertGoalInPlan } from '../components/PlanEditor/PlanEditingFunctions.tsx';
/** /**
* The default data dot a phase node * The default data dot a phase node

View File

@@ -1,8 +1,8 @@
{/* /*
This program has been developed by students from the bachelor Computer Science at Utrecht This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course. University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences) © Copyright Utrecht University (Department of Information and Computing Sciences)
*/} */
.operator-switch { .operator-switch {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View File

@@ -13,12 +13,12 @@ import styles from '../../VisProg.module.css';
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts"; import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores'; import useFlowStore from '../VisProgStores';
import {PlanReduce, type Plan } from '../components/Plan'; import {PlanReduce, type Plan } from '../components/PlanEditor/Plan.tsx';
import PlanEditorDialog from '../components/PlanEditor'; import PlanEditorDialog from '../components/PlanEditor/PlanEditor.tsx';
import {BeliefGlobalReduce} from "./BeliefGlobals.ts"; import {BeliefGlobalReduce} from "./BeliefGlobals.ts";
import type { GoalNode } from './GoalNode.tsx'; import type { GoalNode } from './GoalNode.tsx';
import { defaultPlan } from '../components/Plan.default.ts'; import { defaultPlan } from '../components/PlanEditor/Plan.default.ts';
import { deleteGoalInPlanByID, insertGoalInPlan } from '../components/PlanEditingFunctions.tsx'; import { deleteGoalInPlanByID, insertGoalInPlan } from '../components/PlanEditor/PlanEditingFunctions.tsx';
import { TextField } from '../../../../components/TextField.tsx'; import { TextField } from '../../../../components/TextField.tsx';
/** /**

View File

@@ -4,7 +4,7 @@
import { useState } from 'react'; import { useState } from 'react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { renderWithProviders, screen } from '../../../../test-utils/test-utils.tsx'; import { renderWithProviders, screen } from '../../../../test-utils/test-utils.tsx';
import GestureValueEditor from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/GestureValueEditor'; import GestureValueEditor from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/GestureValueEditor.tsx';
function TestHarness({ initialValue = '', initialType=true, placeholder = 'Gesture name' } : { initialValue?: string, initialType?: boolean, placeholder?: string }) { function TestHarness({ initialValue = '', initialType=true, placeholder = 'Gesture name' } : { initialValue?: string, initialType?: boolean, placeholder?: string }) {
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);

View File

@@ -6,12 +6,12 @@ import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import type { Node } from '@xyflow/react'; import type { Node } from '@xyflow/react';
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx'; import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
import PlanEditorDialog from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor'; import PlanEditorDialog from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/PlanEditor.tsx';
import { PlanReduce, type Plan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/Plan'; import { PlanReduce, type Plan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/Plan.tsx';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { GoalReduce, type GoalNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx'; import { GoalReduce, type GoalNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx';
import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts'; import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts';
import { insertGoalInPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditingFunctions.tsx'; import { insertGoalInPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/PlanEditingFunctions.tsx';
// Mock structuredClone // Mock structuredClone

View File

@@ -10,7 +10,7 @@ import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgramming
import type { Node } from '@xyflow/react'; import type { Node } from '@xyflow/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts'; import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts';
import { defaultPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/Plan.default.ts'; import { defaultPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/Plan.default.ts';
describe('GoalNode', () => { describe('GoalNode', () => {
let user: ReturnType<typeof userEvent.setup>; let user: ReturnType<typeof userEvent.setup>;

View File

@@ -14,7 +14,7 @@ import type { Node } from '@xyflow/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { TriggerNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts'; import { TriggerNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.default.ts';
import { BasicBeliefNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts'; import { BasicBeliefNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts';
import { defaultPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/Plan.default.ts'; import { defaultPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/Plan.default.ts';
import { NormNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts'; import { NormNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts';
import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts'; import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts';
import { act } from '@testing-library/react'; import { act } from '@testing-library/react';