diff --git a/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx b/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx index 9077a93905..2f421243b7 100644 --- a/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx +++ b/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx @@ -21,7 +21,6 @@ export default function TasksPage() { const newTaskDialog = useRef(null); const [ activeStepIndex, setActiveStepIndex ] = useState(-1); // Tracks which Step is being edited. const [ activeDragItem, setActiveDragItem ] = useState(-1); // Keeps track of active item being dragged (StepItem). This is because "dragOver" CAN'T read the data from dragEnter.dataTransfer.getData(). - const activeStepKey = workflow?.steps?.[activeStepIndex]?.[0]; const isActive = true; // TODO /* @@ -77,8 +76,39 @@ export default function TasksPage() { } function deleteTask(taskKey) { - if (!taskKey) return; - // TODO + // First check: does the task exist? + if (!workflow || !taskKey || !workflow?.tasks?.[taskKey]) return; + + // Second check: is this the only task in the step? + const activeStepTaskKeys = workflow.steps?.[activeStepIndex]?.[1]?.taskKeys || []; + const onlyTaskInStep = !!(activeStepTaskKeys.length === 1 && activeStepTaskKeys[0] === taskKey); + + // Third check: are you sure? + const confirmed = onlyTaskInStep + ? confirm(`Delete Task ${taskKey}? This will also delete the Page.`) + : confirm(`Delete Task ${taskKey}?`); + if (!confirmed) return; + + // Delete the task. + const newTasks = structuredClone(workflow.tasks || {}); + delete newTasks[taskKey]; + + // Delete the task reference in steps. + const newSteps = structuredClone(workflow.steps || {}); + newSteps.forEach(step => { + const stepBody = step[1] || {}; + stepBody.taskKeys = (stepBody?.taskKeys || []).filter(key => key !== taskKey); + }); + + // Close the Edit Step Dialog, if necessary. + // Note that this will also trigger handleCloseEditStepDialog() + if (onlyTaskInStep) { + editStepDialog.current?.closeDialog(); + } + + // Cleanup, then commit. + const cleanedTasksAndSteps = cleanupTasksAndSteps(newTasks, newSteps); + update(cleanedTasksAndSteps); } function moveStep(from, to) { @@ -92,16 +122,15 @@ export default function TasksPage() { function deleteStep(stepIndex) { if (!workflow) return; const { steps, tasks } = workflow; - const [ stepKey, stepBody ] = steps[stepIndex] || []; - const tasksToBeDeleted = stepBody?.taskKeys || []; + const [ stepKey ] = steps[stepIndex] || []; const confirmed = confirm(`Delete Page ${stepKey}?`); if (!confirmed) return; const newSteps = steps.toSpliced(stepIndex, 1); // Copy then delete Step at stepIndex const newTasks = tasks ? { ...tasks } : {}; // Copy tasks - tasksToBeDeleted.forEach(taskKey => delete newTasks[taskKey]); + // cleanedupTasksAndSteps() will also remove tasks not associated with any step. const cleanedTasksAndSteps = cleanupTasksAndSteps(newTasks, newSteps); update(cleanedTasksAndSteps); } 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 27b45f05fe..7a3de6a703 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 @@ -14,6 +14,7 @@ const DEFAULT_HANDLER = () => {}; function EditStepDialog({ allTasks = {}, + deleteTask, onClose = DEFAULT_HANDLER, openNewTaskDialog = DEFAULT_HANDLER, step = [], @@ -26,6 +27,7 @@ function EditStepDialog({ useImperativeHandle(forwardedRef, () => { return { + closeDialog, openDialog }; }); @@ -78,6 +80,7 @@ function EditStepDialog({ return ( - {/* */}
Choices diff --git a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/TextTask.jsx b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/TextTask.jsx index 751d6a70ef..a1298cc359 100644 --- a/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/TextTask.jsx +++ b/app/pages/lab-pages-editor/components/TasksPage/components/EditStepDialog/types/TextTask.jsx @@ -1,10 +1,12 @@ import { useEffect, useState } from 'react'; +import DeleteIcon from '../../../../../icons/DeleteIcon.jsx'; const DEFAULT_HANDLER = () => {}; export default function TextTask({ task, taskKey, + deleteTask = DEFAULT_HANDLER, updateTask = DEFAULT_HANDLER }) { const [ help, setHelp ] = useState(task?.help || ''); @@ -22,6 +24,10 @@ export default function TextTask({ updateTask(taskKey, newTask); } + function doDelete() { + deleteTask(taskKey); + } + // For inputs that don't have onBlur, update triggers automagically. // (You can't call update() in the onChange() right after setStateValue().) useEffect(update, [required]); @@ -45,8 +51,15 @@ export default function TextTask({ onBlur={update} onChange={(e) => { setInstruction(e?.target?.value) }} /> +
- {/* */}
diff --git a/app/pages/lab-pages-editor/helpers/cleanupTasksAndSteps.js b/app/pages/lab-pages-editor/helpers/cleanupTasksAndSteps.js index 039801be69..762558b506 100644 --- a/app/pages/lab-pages-editor/helpers/cleanupTasksAndSteps.js +++ b/app/pages/lab-pages-editor/helpers/cleanupTasksAndSteps.js @@ -1,7 +1,7 @@ /* Clean up tasks and steps. -- TODO: Remove steps without tasks. -- TODO: Remove tasks not associated with any step. +- Remove steps without tasks. +- Remove tasks not associated with any step. - Remove orphaned references in branching tasks. - Remove orphaned references in steps. @@ -10,32 +10,41 @@ Clean up tasks and steps. export default function cleanupTasksAndSteps(tasks = {}, steps = []) { const newTasks = structuredClone(tasks); // Copy tasks - let newSteps = steps.slice(); // Copy steps + let newSteps = structuredClone(steps); // Copy steps. This is a deep copy, compared to steps.slice() + + // Remove steps without tasks. + newSteps = newSteps.filter(step => step?.[1]?.taskKeys?.length > 0); + + // Remove tasks not associated with any step. + Object.keys(newTasks).forEach(taskKey => { + let existsInAnyStep = false; + newSteps.forEach(step => { + existsInAnyStep = existsInAnyStep || !!step?.[1]?.taskKeys?.includes(taskKey); + }); + if (!existsInAnyStep) delete newTasks[taskKey]; + }); const taskKeys = Object.keys(newTasks); const stepKeys = newSteps.map(step => step[0]); // Remove orphaned references in branching tasks. - Object.values(newTasks).forEach(taskBody => { - taskBody?.answers?.forEach(answer => { + Object.values(newTasks).forEach(task => { + task?.answers?.forEach(answer => { // If the branching answer points to a non-existent Task Key or Step Key, remove the 'next'. if (answer.next && !taskKeys.includes(answer.next) && !stepKeys.includes(answer.next)) { delete answer.next; } - }) + }); }); // Remove orphaned references in steps. newSteps = newSteps.map(step => { - const [stepKey, stepBody] = step; - const newStepBody = { ...stepBody }; - // If the stepBody points to a non-existent Task Key or Step Key, remove the 'next'. - if (newStepBody.next && !taskKeys.includes(newStepBody.next) && !stepKeys.includes(newStepBody.next)) { - delete newStepBody.next; + const [stepKey, stepBody] = step; + if (stepBody.next && !taskKeys.includes(stepBody.next) && !stepKeys.includes(stepBody.next)) { + delete stepBody.next; } - - return [ stepKey, newStepBody ] + return [ stepKey, stepBody ] }) return { tasks: newTasks, steps: newSteps }; diff --git a/css/lab-pages-editor.styl b/css/lab-pages-editor.styl index 4d60e0a021..434b36a383 100644 --- a/css/lab-pages-editor.styl +++ b/css/lab-pages-editor.styl @@ -308,12 +308,13 @@ $fontWeightBoldPlus = 700 button.big border: 3px solid $grey1 font-size: $fontSizeM - padding: $sizeS $sizeXL + padding: $sizeS $sizeM box-shadow: none text-transform: uppercase - &.teal-border + &.done border: 3px solid $teal + padding: $sizeS $sizeXL .dialog-header background: $teal