refactor: update the goal node to have a description for plans that need to be checked, and correctly give the value to the CB.
ref: N25B-412
This commit is contained in:
75
src/components/MultilineTextField.tsx
Normal file
75
src/components/MultilineTextField.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import styles from "./TextField.module.css";
|
||||
|
||||
export function MultilineTextField({
|
||||
value = "",
|
||||
setValue,
|
||||
placeholder,
|
||||
className,
|
||||
id,
|
||||
ariaLabel,
|
||||
invalid = false,
|
||||
minRows = 3,
|
||||
}: {
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
id?: string;
|
||||
ariaLabel?: string;
|
||||
invalid?: boolean;
|
||||
minRows?: number;
|
||||
}) {
|
||||
const [readOnly, setReadOnly] = useState(true);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value);
|
||||
}, [value]);
|
||||
|
||||
// Auto-grow logic
|
||||
useEffect(() => {
|
||||
const el = textareaRef.current;
|
||||
if (!el) return;
|
||||
|
||||
el.style.height = "auto";
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
}, [inputValue]);
|
||||
|
||||
const onCommit = () => {
|
||||
setReadOnly(true);
|
||||
setValue(inputValue);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
(e.target as HTMLTextAreaElement).blur();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={minRows}
|
||||
placeholder={placeholder}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onFocus={() => setReadOnly(false)}
|
||||
onBlur={onCommit}
|
||||
onKeyDown={onKeyDown}
|
||||
readOnly={readOnly}
|
||||
id={id}
|
||||
aria-label={ariaLabel}
|
||||
className={`
|
||||
${readOnly ? "drag" : "nodrag"}
|
||||
flex-1
|
||||
${styles.textField}
|
||||
${styles.multiline}
|
||||
${invalid ? styles.invalid : ""}
|
||||
${className ?? ""}
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -27,3 +27,13 @@
|
||||
.text-field:read-only:hover:not(.invalid) {
|
||||
border-color: color-mix(in srgb, canvas, #777 10%);
|
||||
}
|
||||
|
||||
.multiline {
|
||||
resize: none; /* no manual resizing */
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden; /* needed for auto-grow */
|
||||
max-width: 100%;
|
||||
width: 95%;
|
||||
min-width: 95%;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import type { GoalNodeData } from "./GoalNode";
|
||||
*/
|
||||
export const GoalNodeDefaults: GoalNodeData = {
|
||||
label: "Goal Node",
|
||||
name: "",
|
||||
droppable: true,
|
||||
description: "",
|
||||
achieved: false,
|
||||
|
||||
@@ -10,18 +10,20 @@ import { TextField } from '../../../../components/TextField';
|
||||
import useFlowStore from '../VisProgStores';
|
||||
import { DoesPlanIterate, PlanReduce, type Plan } from '../components/Plan';
|
||||
import PlanEditorDialog from '../components/PlanEditor';
|
||||
import { MultilineTextField } from '../../../../components/MultilineTextField';
|
||||
|
||||
/**
|
||||
* The default data dot a phase node
|
||||
* @param label: the label of this phase
|
||||
* @param droppable: whether this node is droppable from the drop bar (initialized as true)
|
||||
* @param desciption: description of the goal
|
||||
* @param desciption: description of the goal - this will be checked for completion
|
||||
* @param hasReduce: whether this node has reducing functionality (true by default)
|
||||
* @param can_fail: whether this plan should be checked- this plan could possible fail
|
||||
* @param plan: The (possible) attached plan to this goal
|
||||
*/
|
||||
export type GoalNodeData = {
|
||||
label: string;
|
||||
name: string;
|
||||
description: string;
|
||||
droppable: boolean;
|
||||
achieved: boolean;
|
||||
@@ -49,6 +51,10 @@ export default function GoalNode({id, data}: NodeProps<GoalNode>) {
|
||||
updateNodeData(id, {...data, description: value});
|
||||
}
|
||||
|
||||
const setName= (value: string) => {
|
||||
updateNodeData(id, {...data, name: value})
|
||||
}
|
||||
|
||||
const setFailable = (value: boolean) => {
|
||||
updateNodeData(id, {...data, can_fail: value});
|
||||
}
|
||||
@@ -60,18 +66,29 @@ export default function GoalNode({id, data}: NodeProps<GoalNode>) {
|
||||
<label htmlFor={text_input_id}>Goal:</label>
|
||||
<TextField
|
||||
id={text_input_id}
|
||||
value={data.description}
|
||||
setValue={(val) => setDescription(val)}
|
||||
value={data.name}
|
||||
setValue={(val) => setName(val)}
|
||||
placeholder={"To ..."}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{data.can_fail && (<div>
|
||||
<label htmlFor={text_input_id}>Description/ Condition of goal:</label>
|
||||
<div className={"flex-wrap"}>
|
||||
<MultilineTextField
|
||||
id={text_input_id}
|
||||
value={data.description}
|
||||
setValue={setDescription}
|
||||
placeholder={"Describe the condition of this goal..."}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div>
|
||||
<label> {!data.plan ? "No plan set to execute while goal is not reached. 🔴" : "Will follow plan '" + data.plan.name + "' until all steps complete. 🟢"} </label>
|
||||
</div>
|
||||
{data.plan && (<div className={"flex-row gap-md align-center " + (planIterate ? "" : styles.planNoIterate)}>
|
||||
{planIterate ? "" : <s></s>}
|
||||
<label htmlFor={checkbox_id}>{!planIterate ? "This plan always succeeds!" : ("We " + (data.can_fail ? "will " : "wont ") + "check if this plan fails")}:</label>
|
||||
<label htmlFor={checkbox_id}>{!planIterate ? "This plan always succeeds!" : "Check if this plan fails"}:</label>
|
||||
<input
|
||||
id={checkbox_id}
|
||||
type={"checkbox"}
|
||||
@@ -91,7 +108,7 @@ export default function GoalNode({id, data}: NodeProps<GoalNode>) {
|
||||
plan,
|
||||
});
|
||||
}}
|
||||
description={data.description}
|
||||
description={data.name}
|
||||
/>
|
||||
</div>
|
||||
<Handle type="source" position={Position.Right} id="GoalSource"/>
|
||||
@@ -109,7 +126,8 @@ export function GoalReduce(node: Node, _nodes: Node[]) {
|
||||
const data = node.data as GoalNodeData;
|
||||
return {
|
||||
id: node.id,
|
||||
name: data.description,
|
||||
name: data.label,
|
||||
description: data.description,
|
||||
can_fail: data.can_fail,
|
||||
plan: data.plan ? PlanReduce(data.plan) : "",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user