From 0e611cf029584e870b2762097632f9f8556cd412 Mon Sep 17 00:00:00 2001 From: "Shaun A. Noordin" Date: Mon, 12 Feb 2024 17:17:55 +0000 Subject: [PATCH] Pages Editor: implement Edit Task Form (#6981) * pages-editor-pt12: TextTask can now commit updates * TextTask: fix 'required' toggle * TextTask: cleanup HTML. remove unused elements. * EditTaskForm: style up * EditTaskForm: style update * EditStepDialog: use proper Task names * EditStepDialog: design update. Add more visible Task Key * EditTaskForm: restyling fieldset. * EditStepDialog: add optional 'Done' footer to close dialog * EditStepDialog: restyle button * EditStepDialog: minor restyles * EditTaskForm: add SingleQuestionTask * SingleQuestionTask: add ability to list and add choices * SingleQuestionTask: implement ability to edit answers (choices) * SingleQuestionTask: implement ability to delete answers * Cleanup: remove eslint * SingleQuestionTask: add Plus and Minus icons * SingleQuestionTask: implement ability to delete choices --- .../components/TasksPage/TasksPage.jsx | 14 ++ .../EditStepDialog/EditStepDialog.jsx | 27 ++- .../EditStepDialog/EditTaskForm.jsx | 15 +- .../types/SingleQuestionTask.jsx | 157 ++++++++++++++++++ .../EditStepDialog/types/TextTask.jsx | 75 +++++++-- .../lab-pages-editor/icons/CloseIcon.jsx | 2 - app/pages/lab-pages-editor/icons/CopyIcon.jsx | 2 - .../lab-pages-editor/icons/DeleteIcon.jsx | 2 - app/pages/lab-pages-editor/icons/EditIcon.jsx | 2 - app/pages/lab-pages-editor/icons/GripIcon.jsx | 3 - .../lab-pages-editor/icons/MinusIcon.jsx | 5 + .../lab-pages-editor/icons/MoveDownIcon.jsx | 2 - .../lab-pages-editor/icons/MoveUpIcon.jsx | 2 - app/pages/lab-pages-editor/icons/PlusIcon.jsx | 5 + .../lab-pages-editor/icons/ReturnIcon.jsx | 2 - app/pages/lab-pages-editor/icons/TaskIcon.jsx | 2 - css/lab-pages-editor.styl | 72 +++++++- 17 files changed, 346 insertions(+), 43 deletions(-) create mode 100644 app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/SingleQuestionTask.jsx create mode 100644 app/pages/lab-pages-editor/icons/MinusIcon.jsx create mode 100644 app/pages/lab-pages-editor/icons/PlusIcon.jsx diff --git a/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx b/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx index 573cf1e031..0622d6a86b 100644 --- a/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx +++ b/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx @@ -62,6 +62,7 @@ export default function TasksPage() { update({ steps }); } + // aka openEditStepDialog function editStep(stepIndex) { setActiveStepIndex(stepIndex); editStepDialog.current?.openDialog(); @@ -71,6 +72,17 @@ export default function TasksPage() { newTaskDialog.current?.openDialog(); } + function deleteTask(taskKey) { + // TODO + } + + function updateTask(taskKey, task) { + if (!taskKey) return; + const tasks = JSON.parse(JSON.stringify(workflow?.tasks || {})); + tasks[taskKey] = task + update({tasks}); + } + if (!workflow) return null; return ( @@ -123,6 +135,8 @@ export default function TasksPage() { allTasks={workflow.tasks} step={workflow.steps[activeStepIndex]} stepIndex={activeStepIndex} + deleteTask={deleteTask} + updateTask={updateTask} /> {/* EXPERIMENTAL */} diff --git a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditStepDialog.jsx b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditStepDialog.jsx index 2496452eb2..0327a97ac8 100644 --- a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditStepDialog.jsx +++ b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditStepDialog.jsx @@ -4,10 +4,17 @@ import PropTypes from 'prop-types'; import EditTaskForm from './EditTaskForm.jsx'; import CloseIcon from '../../../../icons/CloseIcon.jsx'; +const taskNames = { + 'drawing': 'Drawing', + 'single': 'Single Question', + 'text': 'Text', +} + function EditStepDialog({ allTasks = {}, step = [], - stepIndex = -1 + stepIndex = -1, + updateTask }, forwardedRef) { const [ stepKey, stepBody ] = step ; const taskKeys = stepBody?.taskKeys || []; @@ -28,7 +35,9 @@ function EditStepDialog({ }; }); - const title = 'Create a (???) Task' + const firstTask = allTasks?.[taskKeys?.[0]] + const taskName = taskNames[firstTask?.type] || '???'; + const title = `Edit ${taskName} Task`; return ( ); })} -
- -
+
+
+ +
); } diff --git a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditTaskForm.jsx b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditTaskForm.jsx index bfb9a36f63..a769162bdd 100644 --- a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditTaskForm.jsx +++ b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/EditTaskForm.jsx @@ -1,24 +1,31 @@ +import SingleQuestionTask from './types/SingleQuestionTask.jsx'; import TextTask from './types/TextTask.jsx'; const taskTypes = { + 'single': SingleQuestionTask, 'text': TextTask }; export default function EditTaskForm({ // It's not actually a form, but a fieldset that's part of a form. task, - taskKey + taskKey, + updateTask }) { if (!task || !taskKey) return
  • ERROR: could not render Task
  • ; const TaskForm = taskTypes[task.type]; - + return (
    - {taskKey} + {taskKey} {(TaskForm) - ? + ? : null }
    diff --git a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/SingleQuestionTask.jsx b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/SingleQuestionTask.jsx new file mode 100644 index 0000000000..a5ded64bc7 --- /dev/null +++ b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/SingleQuestionTask.jsx @@ -0,0 +1,157 @@ +import { useEffect, useState } from 'react'; + +import MinusIcon from '../../../../../icons/MinusIcon.jsx'; +import PlusIcon from '../../../../../icons/PlusIcon.jsx'; + +export default function SingleQuestionTask({ + task, + taskKey, + updateTask = () => {} +}) { + const [ answers, setAnswers ] = useState(task?.answers || []); + const [ help, setHelp ] = useState(task?.help || ''); + const [ question, setQuestion ] = useState(task?.question || ''); // TODO: figure out if FEM is standardising Question vs Instructions + const [ required, setRequired ] = useState(!!task?.required); + + // Update is usually called manually onBlur, after user input is complete. + function update(optionalStateOverrides) { + const _answers = optionalStateOverrides?.answers || answers + const nonEmptyAnswers = _answers.filter(({ label }) => label.trim().length > 0); + + const newTask = { + ...task, + answers: nonEmptyAnswers, + help, + question, + required + }; + updateTask(taskKey, newTask); + } + + function addAnswer(e) { + const newAnswers = [ ...answers, { label: '', next: undefined }]; + setAnswers(newAnswers); + + e.preventDefault(); + return false; + } + + function editAnswer(e) { + const index = e?.target?.dataset?.index; + if (index === undefined || index < 0 || index >= answers.length) return; + + const answer = answers[index]; + const newLabel = e?.target?.value || ''; + + setAnswers(answers.with(index, { ...answer, label: newLabel })); + } + + function deleteAnswer(e) { + const index = e?.target?.dataset?.index; + if (index === undefined || index < 0 || index >= answers.length) return; + + const newAnswers = answers.slice() + newAnswers.splice(index, 1); + setAnswers(newAnswers); + update({ answer: newAnswers }); // Use optional state override, since setAnswers() won't reflect new values in this step of the lifecycle. + + e.preventDefault(); + return false; + } + + // For inputs that don't have onBlur, update triggers automagically. + // (You can't call update() in the onChange() right after setStateValue().) + useEffect(update, [required]); + + return ( +
    +
    + +
    + {taskKey} + { setQuestion(e?.target?.value) }} + /> +
    + {/* */} +
    +
    + +
    + + +
    +
    +
    +
      + {answers.map(({ label, next }, index) => ( +
    • + + +
    • + ))} +
    +
    +
    + +