Skip to content

Commit

Permalink
Pages Editor: rebuild "Next Page" logic. Add placeholder answers. (zo…
Browse files Browse the repository at this point in the history
…oniverse#7055)

* pages-editor-pt17: debug, show step.next

* Add SimpleNextControls. Rename BranchingControls.

* Refactor: separate NextStepArrow. SimpleNextControls: test functionality

* SimpleNextControls: add and populate 'next page' dropdown

* TasksPage: add second QuickSetup debug feature

* BranchingNextControls: move from Step to Task

* TaskItem: add PlaceholderAnswers subcomponent

* TasksPage: rename updateAnswerNext()

* TasksPage: implement updateNextStepForStep()

* Remove automatic linking of steps/pages

* cleanupTasksAndSteps: now also removes orphaned references in step.next
  • Loading branch information
shaunanoordin authored Apr 8, 2024
1 parent a2a2ad4 commit 04a4bbe
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 87 deletions.
79 changes: 69 additions & 10 deletions app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import createStep from '../../helpers/createStep.js';
import createTask from '../../helpers/createTask.js';
import getNewStepKey from '../../helpers/getNewStepKey.js';
import getNewTaskKey from '../../helpers/getNewTaskKey.js';
import linkStepsInWorkflow from '../../helpers/linkStepsInWorkflow.js';
import moveItemInArray from '../../helpers/moveItemInArray.js';
import cleanupTasksAndSteps from '../../helpers/cleanupTasksAndSteps.js';
import getPreviewEnv from '../../helpers/getPreviewEnv.js';
Expand Down Expand Up @@ -39,7 +38,7 @@ export default function TasksPage() {
...workflow.tasks,
[newTaskKey]: newTask
};
const steps = linkStepsInWorkflow([...workflow.steps, newStep]);
const steps = [...workflow.steps, newStep];

await update({ tasks, steps });
return steps.length - 1;
Expand All @@ -57,9 +56,9 @@ export default function TasksPage() {
tasks: {
'T0': {
answers: [
{next: "P1", label: "Animals"},
{next: "P2", label: "Fruits"},
{label: "Neither"}
{next: 'P1', label: 'Animals'},
{next: 'P2', label: 'Fruits'},
{label: 'Neither'}
],
help: '',
question: 'Do you like Animals or Fruits?',
Expand All @@ -70,18 +69,56 @@ export default function TasksPage() {
'T2': { help: '', type: 'text', required: false, instruction: 'Which fruit?' }
},
steps: [
['P0', { next: 'P1', stepKey: 'P0', taskKeys: ["T0"] }],
['P0', { stepKey: 'P0', taskKeys: ["T0"] }],
['P1', { next: 'P2', stepKey: 'P1', taskKeys: ["T1"] }],
['P2', { stepKey: 'P2', taskKeys: ["T2"] }]
]
});
}

function experimentalQuickSetupBranching() {
update({
tasks: {
'T1.1': {
answers: [
{next: 'P2', label: 'Go to the 🔴 RED page'},
{next: 'P3', label: 'Go to the 🔵 BLUE page'},
],
help: '',
question: 'Oh dear, this page has multiple branching tasks. Let\'s see what happens',
required: false,
type: 'single'
},
'T1.2': {
answers: [
{next: 'P4', label: 'Go to the 🟡 YELLOW page'},
{next: 'P5', label: 'Go to the 🟢 GREEN page'},
],
help: '',
question: 'This is the second branching task. If you answer both on the page, where do you branch to?',
required: false,
type: 'single'
},
'T2': { help: '', type: 'text', required: false, instruction: 'Welcome to the 🔴 RED page! How do you feel?' },
'T3': { help: '', type: 'text', required: false, instruction: 'Welcome to the 🔵 BLUE page! How do you feel?' },
'T4': { help: '', type: 'text', required: false, instruction: 'Welcome to the 🟡 YELLOW page! How do you feel?' },
'T5': { help: '', type: 'text', required: false, instruction: 'Welcome to the 🟢 GREEN page! How do you feel?' },
},
steps: [
['P1', { stepKey: 'P1', taskKeys: ['T1.1', 'T1.2'] }],
['P2', { stepKey: 'P2', taskKeys: ['T2'] }],
['P3', { stepKey: 'P3', taskKeys: ['T3'] }],
['P4', { stepKey: 'P4', taskKeys: ['T4'] }],
['P5', { stepKey: 'P5', taskKeys: ['T5'] }],
]
});
}

function moveStep(from, to) {
const oldSteps = workflow?.steps || [];
if (from < 0 || to < 0 || from >= oldSteps.length || to >= oldSteps.length) return;

const steps = linkStepsInWorkflow(moveItemInArray(oldSteps, from, to));
const steps = moveItemInArray(oldSteps, from, to);
update({ steps });
}

Expand Down Expand Up @@ -124,8 +161,21 @@ export default function TasksPage() {
update({tasks});
}

// Changes the optional "next page" of a step/page
function updateNextStepForStep(stepKey, next = undefined) {
// Check if input is valid
const stepIndex = workflow?.steps?.findIndex(step => step[0] === stepKey);
const stepBody = workflow?.steps?.[stepIndex]?.[1];
if (!stepBody) return;

const newSteps = workflow.steps.slice();
newSteps[stepIndex] = [stepKey, { ...stepBody, next }];

update({ steps: newSteps });
}

// Changes the optional "next page" of a branching answer/choice
function updateAnswerNext(taskKey, answerIndex, next = undefined) {
function updateNextStepForTaskAnswer(taskKey, answerIndex, next = undefined) {
// Check if input is valid
const task = workflow?.tasks?.[taskKey];
const answer = task?.answers[answerIndex];
Expand Down Expand Up @@ -185,7 +235,8 @@ export default function TasksPage() {
step={step}
stepKey={step[0]}
stepIndex={index}
updateAnswerNext={updateAnswerNext}
updateNextStepForStep={updateNextStepForStep}
updateNextStepForTaskAnswer={updateNextStepForTaskAnswer}
/>
))}
</ul>
Expand Down Expand Up @@ -225,7 +276,15 @@ export default function TasksPage() {
type="button"
style={{ margin: '0 4px' }}
>
QUICK SETUP
QUICK SETUP (simple)
</button>
<button
className="big"
onClick={experimentalQuickSetupBranching}
type="button"
style={{ margin: '0 4px' }}
>
QUICK SETUP (advanced, branching)
</button>
</div>
</section>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import PropTypes from 'prop-types';
import NextStepArrow from './NextStepArrow.jsx';

const DEFAULT_HANDLER = () => {};

export default function BranchingControls({
export default function BranchingNextControls({
allSteps = [],
task,
taskKey,
updateAnswerNext = DEFAULT_HANDLER
updateNextStepForTaskAnswer = DEFAULT_HANDLER
}) {
if (!task || !taskKey) return null;

Expand All @@ -13,13 +16,13 @@ export default function BranchingControls({
function onChange(e) {
const next = e.target?.value;
const index = e?.target?.dataset.index;
updateAnswerNext(taskKey, index, next);
updateNextStepForTaskAnswer(taskKey, index, next);
}

return (
<ul className="branching-controls">
<ul className="next-controls horizontal-list">
{answers.map((answer, index) => (
<li key={`branching-controls-answer-${index}`}>
<li key={`branching-next-controls-answer-${index}`}>
<div className="fake-button">{answer.label}</div>
<NextStepArrow className="next-arrow" />
<select
Expand All @@ -37,7 +40,7 @@ export default function BranchingControls({
const taskKeys = stepBody?.taskKeys?.toString() || '(none)';
return (
<option
key={`branching-controls-answer-${index}-option-${stepKey}`}
key={`branching-next-controls-answer-${index}-option-${stepKey}`}
value={stepKey}
>
{taskKeys}
Expand All @@ -51,29 +54,9 @@ export default function BranchingControls({
);
}

function NextStepArrow({
alt,
className = 'icon',
color = 'currentColor',
height = 48,
pad = 4,
strokeWidth = 2,
width = 16
}) {
const xA = 0 + pad;
const xB = width * 0.5;
const xC = width - pad;
const yA = 0 + pad;
const yB = height - (width / 2);
const yC = height - pad;

return (
<svg aria-label={alt} width={width} height={height} className={className}>
<g stroke={color} strokeWidth={strokeWidth}>
<line x1={xB} y1={yA} x2={xB} y2={yC} />
<line x1={xA} y1={yB} x2={xB} y2={yC} />
<line x1={xC} y1={yB} x2={xB} y2={yC} />
</g>
</svg>
);
}
BranchingNextControls.propTypes = {
allSteps: PropTypes.arrayOf(PropTypes.array),
task: PropTypes.object,
taskKey: PropTypes.string,
updateNextStepForTaskAnswer: PropTypes.func
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default function NextStepArrow({
alt,
className = 'icon',
color = 'currentColor',
height = 48,
pad = 4,
strokeWidth = 2,
width = 16
}) {
const xA = 0 + pad;
const xB = width * 0.5;
const xC = width - pad;
const yA = 0 + pad;
const yB = height - (width / 2);
const yC = height - pad;

return (
<svg aria-label={alt} width={width} height={height} className={className}>
<g stroke={color} strokeWidth={strokeWidth}>
<line x1={xB} y1={yA} x2={xB} y2={yC} />
<line x1={xA} y1={yB} x2={xB} y2={yC} />
<line x1={xC} y1={yB} x2={xB} y2={yC} />
</g>
</svg>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import PropTypes from 'prop-types';
import NextStepArrow from './NextStepArrow.jsx';

const DEFAULT_HANDLER = () => {};

export default function SimpleNextControls({
allSteps = [],
step,
updateNextStepForStep = DEFAULT_HANDLER
}) {
if (!step) return null;
const [ stepKey, stepBody ] = step;

function onChange(e) {
const next = e.target?.value;
updateNextStepForStep(stepKey, next);
}

return (
<div className="next-controls vertical-layout">
<NextStepArrow className="next-arrow" />
<select
className={(!stepBody?.next) ? 'next-is-submit' : ''}
onChange={onChange}
value={stepBody?.next || ''}
>
<option
value={''}
>
Submit
</option>
{allSteps.map(([otherStepKey, otherStepBody]) => {
const taskKeys = otherStepBody?.taskKeys?.toString() || '(none)';
return (
<option
key={`simple-next-controls-option-${otherStepKey}`}
value={otherStepKey}
>
{taskKeys}
</option>
);
})}
</select>
</div>
);
}

SimpleNextControls.propTypes = {
allSteps: PropTypes.arrayOf(PropTypes.array),
step: PropTypes.array,
updateNextStepForStep: PropTypes.func
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import TaskItem from './TaskItem.jsx';

import canStepBranch from '../../../../helpers/canStepBranch.js';

import BranchingControls from './BranchingControls.jsx';
import SimpleNextControls from './SimpleNextControls.jsx';

import CopyIcon from '../../../../icons/CopyIcon.jsx';
import DeleteIcon from '../../../../icons/DeleteIcon.jsx';
import EditIcon from '../../../../icons/EditIcon.jsx';
Expand All @@ -26,7 +27,8 @@ function StepItem({
setActiveDragItem = DEFAULT_HANDLER,
step,
stepIndex,
updateAnswerNext = DEFAULT_HANDLER
updateNextStepForStep = DEFAULT_HANDLER,
updateNextStepForTaskAnswer = DEFAULT_HANDLER
}) {
const [stepKey, stepBody] = step || [];
if (!stepKey || !stepBody || !allSteps || !allTasks) return <li className="step-item">ERROR: could not render Step</li>;
Expand Down Expand Up @@ -56,8 +58,7 @@ function StepItem({
}

const branchingTaskKey = canStepBranch(step, allTasks);
const branchingTask = allTasks?.[branchingTaskKey];


return (
<li className="step-item">
{(stepIndex === 0)
Expand Down Expand Up @@ -127,19 +128,21 @@ function StepItem({
const task = allTasks[taskKey];
return (
<TaskItem
allSteps={allSteps}
isBranchingTask={branchingTaskKey === taskKey}
key={`taskItem-${taskKey}`}
task={task}
taskKey={taskKey}
updateNextStepForTaskAnswer={updateNextStepForTaskAnswer}
/>
);
})}
</ul>
{branchingTask && (
<BranchingControls
{!branchingTaskKey && (
<SimpleNextControls
allSteps={allSteps}
task={branchingTask}
taskKey={branchingTaskKey}
updateAnswerNext={updateAnswerNext}
step={step}
updateNextStepForStep={updateNextStepForStep}
/>
)}
</div>
Expand All @@ -163,7 +166,8 @@ StepItem.propTypes = {
setActiveDragItem: PropTypes.func,
step: PropTypes.array,
stepIndex: PropTypes.number,
updateAnswerNext: PropTypes.func
updateNextStepForStep: PropTypes.func,
updateNextStepForTaskAnswer: PropTypes.func
};

export default StepItem;
Loading

0 comments on commit 04a4bbe

Please sign in to comment.