diff --git a/app/pages/lab-pages-editor/DataManager.jsx b/app/pages/lab-pages-editor/DataManager.jsx
index 5c64ddd2be..b851584765 100644
--- a/app/pages/lab-pages-editor/DataManager.jsx
+++ b/app/pages/lab-pages-editor/DataManager.jsx
@@ -110,10 +110,11 @@ function DataManager({
return {
project: apiData.project,
+ status: apiData.status,
workflow: apiData.workflow,
update
};
- }, [apiData.project, apiData.workflow, updateCounter]);
+ }, [apiData.project, apiData.workflow, apiData.status, updateCounter]);
if (!workflowId) return (
ERROR: no Workflow ID specified
);
// if (!workflow) return null
diff --git a/app/pages/lab-pages-editor/PagesEditor.jsx b/app/pages/lab-pages-editor/PagesEditor.jsx
index e15641e1e5..2ddfc0225e 100644
--- a/app/pages/lab-pages-editor/PagesEditor.jsx
+++ b/app/pages/lab-pages-editor/PagesEditor.jsx
@@ -17,9 +17,17 @@ import TasksPage from './components/TasksPage';
import WorkflowSettingsPage from './components/WorkflowSettingsPage';
import strings from './strings.json';
+function getDefaultTab() { // Use ?tab=1 or tab='settings' to link directly to Workflow Settings
+ const params = new URLSearchParams(window?.location?.search);
+ const tab = params.get('tab');
+
+ if ([1, '1', 'settings', 'workflowsettings'].includes(tab)) return 1;
+ return 0;
+}
+
function PagesEditor({ params }) {
const { workflowID: workflowId, projectID: projectId } = params;
- const [currentTab, setCurrentTab] = useState(0);
+ const [currentTab, setCurrentTab] = useState(getDefaultTab()); // Default tab is 0
const tabs = [
{
id: 'pages-editor_workflow-header-tab-button_task',
diff --git a/app/pages/lab-pages-editor/components/TasksPage/ExperimentalPanel.jsx b/app/pages/lab-pages-editor/components/TasksPage/ExperimentalPanel.jsx
index 6d18b28da5..021800fb79 100644
--- a/app/pages/lab-pages-editor/components/TasksPage/ExperimentalPanel.jsx
+++ b/app/pages/lab-pages-editor/components/TasksPage/ExperimentalPanel.jsx
@@ -5,6 +5,7 @@ export default function ExperimentalPanel({
}) {
function experimentalReset() {
update({
+ configuration: {},
first_task: '',
tasks: {},
steps: []
diff --git a/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx b/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx
index d040477d0a..97ca412db8 100644
--- a/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx
+++ b/app/pages/lab-pages-editor/components/TasksPage/TasksPage.jsx
@@ -5,6 +5,7 @@ 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 strings from '../../strings.json'; // TODO: move all text into strings
@@ -13,6 +14,17 @@ import ExperimentalPanel from './ExperimentalPanel.jsx';
import EditStepDialog from './components/EditStepDialog';
import NewTaskDialog from './components/NewTaskDialog.jsx';
import StepItem from './components/StepItem';
+import WorkflowVersion from '../WorkflowVersion.jsx';
+import WrenchIcon from '../../icons/WrenchIcon.jsx';
+
+// Use ?advanced=true to enable advanced mode.
+// - switches from simpler "linear workflow" to "manual workflow".
+// - enables Experimental Panel.
+// - shows hidden options in workflow settings.
+function getAdvancedMode() {
+ const params = new URLSearchParams(window?.location?.search);
+ return !!params.get('advanced');
+}
export default function TasksPage() {
const { workflow, update } = useWorkflowContext();
@@ -22,6 +34,13 @@ export default function TasksPage() {
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 firstStepKey = workflow?.steps?.[0]?.[0] || '';
const isActive = true; // TODO
+ const advancedMode = getAdvancedMode();
+
+ // A linear workflow means every step (except branching steps) will move into
+ // the next step in the workflow.steps array. e.g. step0.next = step1
+ // A manual (i.e. non-linear) workflow asks the user to explicity spell out
+ // the next step of each step.
+ const isLinearWorkflow = !advancedMode;
/*
Adds a new Task of a specified type (with default settings) to a Step.
@@ -32,7 +51,7 @@ export default function TasksPage() {
if (!workflow) return;
const newTaskKey = getNewTaskKey(workflow.tasks);
const newTask = createTask(taskType);
- const steps = workflow.steps?.slice() || [];
+ let steps = workflow.steps?.slice() || [];
let step
if (stepIndex < 0) {
@@ -62,6 +81,10 @@ export default function TasksPage() {
[newTaskKey]: newTask
};
+ if (linkStepsInWorkflow) {
+ steps = linkStepsInWorkflow(steps, tasks);
+ }
+
await update({ tasks, steps });
return (stepIndex < 0) ? steps.length - 1 : stepIndex;
}
@@ -117,7 +140,10 @@ export default function TasksPage() {
const oldSteps = workflow.steps || [];
if (from < 0 || to < 0 || from >= oldSteps.length || to >= oldSteps.length) return;
- const steps = moveItemInArray(oldSteps, from, to);
+ let steps = moveItemInArray(oldSteps, from, to);
+ if (linkStepsInWorkflow) {
+ steps = linkStepsInWorkflow(steps, workflow.tasks);
+ }
update({ steps });
}
@@ -134,6 +160,9 @@ export default function TasksPage() {
// cleanedupTasksAndSteps() will also remove tasks not associated with any step.
const cleanedTasksAndSteps = cleanupTasksAndSteps(newTasks, newSteps);
+ if (linkStepsInWorkflow) {
+ cleanedTasksAndSteps.steps = linkStepsInWorkflow(cleanedTasksAndSteps.steps, cleanedTasksAndSteps.tasks);
+ }
update(cleanedTasksAndSteps);
}
@@ -214,11 +243,13 @@ export default function TasksPage() {
{workflow.display_name}
- {`#${workflow.id}`}
{(isActive) ? Active : Inactive}
- Tasks
+
+
Tasks
+
+
+ {!(workflow.steps?.length > 0) && (
+
+
+
Start by adding tasks to build your Task Funnel here.
+
+ )}
{workflow.steps?.map((step, index) => (
{/* EXPERIMENTAL */}
-
+ {advancedMode && (
+
+ )}
);
diff --git a/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/SimpleNextControls.jsx b/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/SimpleNextControls.jsx
index 673ce34e51..c35df0a16e 100644
--- a/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/SimpleNextControls.jsx
+++ b/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/SimpleNextControls.jsx
@@ -5,12 +5,17 @@ const DEFAULT_HANDLER = () => {};
export default function SimpleNextControls({
allSteps = [],
+ isLastItem = false,
+ isLinearWorkflow = false,
step,
updateNextStepForStep = DEFAULT_HANDLER
}) {
if (!step) return null;
const [ stepKey, stepBody ] = step;
-
+ const isLinearItem = isLinearWorkflow && !isLastItem;
+ const showFakeSubmit = isLinearWorkflow && isLastItem;
+ const showNextPageDropdown = !isLinearWorkflow && !showFakeSubmit;
+
function onChange(e) {
const next = e.target?.value;
updateNextStepForStep(stepKey, next);
@@ -18,35 +23,45 @@ export default function SimpleNextControls({
return (
-
-
+ )}
+ {showFakeSubmit && (
+
Submit
+ )}
);
}
SimpleNextControls.propTypes = {
allSteps: PropTypes.arrayOf(PropTypes.array),
+ isLastItem: PropTypes.bool,
+ isLinearWorkflow: PropTypes.bool,
step: PropTypes.array,
updateNextStepForStep: PropTypes.func
};
\ No newline at end of file
diff --git a/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/StepItem.jsx b/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/StepItem.jsx
index f093961445..7830230713 100644
--- a/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/StepItem.jsx
+++ b/app/pages/lab-pages-editor/components/TasksPage/components/StepItem/StepItem.jsx
@@ -24,6 +24,7 @@ function StepItem({
deleteStep = DEFAULT_HANDLER,
moveStep = DEFAULT_HANDLER,
openEditStepDialog = DEFAULT_HANDLER,
+ isLinearWorkflow = false,
setActiveDragItem = DEFAULT_HANDLER,
step,
stepIndex,
@@ -33,6 +34,7 @@ function StepItem({
const [stepKey, stepBody] = step || [];
if (!stepKey || !stepBody || !allSteps || !allTasks) return ERROR: could not render Step;
+ const isLastItem = stepIndex === allSteps.length - 1;
const taskKeys = stepBody.taskKeys || [];
function doDelete() {
@@ -142,6 +144,8 @@ function StepItem({
{!branchingTaskKey && (
@@ -161,6 +165,7 @@ StepItem.propTypes = {
allSteps: PropTypes.array,
allTasks: PropTypes.object,
deleteStep: PropTypes.func,
+ isLinearWorkflow: PropTypes.bool,
moveStep: PropTypes.func,
openEditStepDialog: PropTypes.func,
setActiveDragItem: PropTypes.func,
diff --git a/app/pages/lab-pages-editor/components/WorkflowSettingsPage/WorkflowSettingsPage.jsx b/app/pages/lab-pages-editor/components/WorkflowSettingsPage/WorkflowSettingsPage.jsx
index 0385ef85ec..fbb84889d1 100644
--- a/app/pages/lab-pages-editor/components/WorkflowSettingsPage/WorkflowSettingsPage.jsx
+++ b/app/pages/lab-pages-editor/components/WorkflowSettingsPage/WorkflowSettingsPage.jsx
@@ -1,9 +1,21 @@
import { useWorkflowContext } from '../../context.js';
import AssociatedSubjectSets from './components/AssociatedSubjectSets.jsx';
import AssociatedTutorial from './components/AssociatedTutorial.jsx';
+import WorkflowVersion from '../WorkflowVersion.jsx';
+
+// Use ?advanced=true to enable advanced mode.
+// - switches from simpler "linear workflow" to "manual workflow".
+// - enables Experimental Panel.
+// - shows hidden options in workflow settings.
+function getAdvancedMode() {
+ const params = new URLSearchParams(window?.location?.search);
+ return !!params.get('advanced');
+}
export default function WorkflowSettingsPage() {
const { workflow, update, project } = useWorkflowContext();
+ const advancedMode = getAdvancedMode();
+ const showSeparateFramesOptions = !!workflow?.configuration?.enable_switching_flipbook_and_separate;
function onSubmit(e) {
e.preventDefault();
@@ -18,6 +30,7 @@ export default function WorkflowSettingsPage() {
const { updaterule } = e?.target?.dataset || {};
if (!key) return;
+ if (updaterule === 'checkbox') value = !!e?.target?.checked;
if (updaterule === 'convert_to_number') value = parseInt(value);
if (updaterule === 'undefined_if_empty') value = value || undefined;
@@ -37,15 +50,24 @@ export default function WorkflowSettingsPage() {
className="workflow-settings-page"
onSubmit={onSubmit}
>
-
+
+
+
+
+
+ #{workflow.id}
+
+
+
+
If you'd like more complex retirement rules such as conditional
@@ -104,54 +131,201 @@ export default function WorkflowSettingsPage() {
+
-
-
diff --git a/app/pages/lab-pages-editor/components/WorkflowVersion.jsx b/app/pages/lab-pages-editor/components/WorkflowVersion.jsx
new file mode 100644
index 0000000000..767bf931f3
--- /dev/null
+++ b/app/pages/lab-pages-editor/components/WorkflowVersion.jsx
@@ -0,0 +1,16 @@
+import { useWorkflowContext } from '../context.js';
+import LoadingIcon from '../icons/LoadingIcon.jsx'
+
+export default function WorkflowVersion({}) {
+ const { workflow, status } = useWorkflowContext();
+ const isBusy = status === 'fetching' || status === 'updating';
+ if (!workflow) return;
+
+ return (
+
+ V. {workflow.version}
+ {isBusy && }
+
+ );
+
+}
\ No newline at end of file
diff --git a/app/pages/lab-pages-editor/icons/LoadingIcon.jsx b/app/pages/lab-pages-editor/icons/LoadingIcon.jsx
new file mode 100644
index 0000000000..415673c2df
--- /dev/null
+++ b/app/pages/lab-pages-editor/icons/LoadingIcon.jsx
@@ -0,0 +1,5 @@
+export default function LoadingIcon({ alt, ...rest }) {
+ return (
+
+ );
+}
diff --git a/app/pages/lab-pages-editor/icons/WrenchIcon.jsx b/app/pages/lab-pages-editor/icons/WrenchIcon.jsx
new file mode 100644
index 0000000000..26cf990b48
--- /dev/null
+++ b/app/pages/lab-pages-editor/icons/WrenchIcon.jsx
@@ -0,0 +1,5 @@
+export default function WrenchIcon({ alt, ...rest }) {
+ return (
+
+ );
+}
diff --git a/css/lab-pages-editor.styl b/css/lab-pages-editor.styl
index f41437a675..1ae24347eb 100644
--- a/css/lab-pages-editor.styl
+++ b/css/lab-pages-editor.styl
@@ -80,6 +80,7 @@ $fontWeightBoldPlus = 700
select
border: 1.5px solid $grey1
+ border-radius: $sizeXS
color: $black
font-size: $fontSizeM
padding: $sizeS
@@ -118,7 +119,7 @@ $fontWeightBoldPlus = 700
hr
border-top: 1px solid $grey2
- .disabled, .disabled *
+ .disabled, .disabled *, [disabled]
color: $grey1
.decoration-plus
@@ -160,11 +161,17 @@ $fontWeightBoldPlus = 700
.justify-around
justify-content: space-around
+ .position-relative
+ position: relative
+
.spacing-bottom-XS
margin-bottom: $sizeXS
.spacing-bottom-M
margin-bottom: $sizeM
+
+ .workflow-version
+ color: $teal
// Component: Workflow Header
// ---------------------------------------------------------------------------
@@ -212,25 +219,33 @@ $fontWeightBoldPlus = 700
.workflow-settings-page
display: grid
gap: $sizeM
- grid-template-columns: auto auto
+ grid-template-columns: 50% auto
grid-template-rows: auto auto
margin-top: $sizeS
// Workflow Title
- label[for=display_name]
- display: block
- font-size: $fontSizeL
- font-weight: $fontWeightBoldPlus
+ .workflow-title
grid-column: 1 / span 2
grid-row: 1
- text-transform: uppercase
- input
+ label[for=display_name]
+ display: block
+ font-size: $fontSizeL
+ font-weight: $fontWeightBoldPlus
+ margin-bottom: $sizeXS
+ text-transform: uppercase
+
+ input[name=display_name]
border-radius: $sizeXS
display: block
font-size: $fontSizeL
- margin-top: $sizeXS
width: 100%
+
+ .workflow-id
+ color: $grey2
+ background: $white
+ position: absolute
+ right: $sizeS
// Group of Config Options/Controls
fieldset
@@ -247,9 +262,12 @@ $fontWeightBoldPlus = 700
p
font-size: $fontSizeS
- .small-info
+ p.small-info
font-size: $fontSizeS
font-style: italic
+
+ span.small-info
+ font-size: $fontSizeS
.col-1
grid-column: 1
@@ -290,10 +308,6 @@ $fontWeightBoldPlus = 700
font-weight: $fontWeightBoldPlus
text-transform: uppercase
- .workflow-id
- color: $grey4
- font-size: $fontSizeS
-
.status-active, .status-inactive
font-size: $fontSizeS
margin-left: $sizeS
@@ -310,6 +324,17 @@ $fontWeightBoldPlus = 700
border-radius: $sizeXS
color: $grey2
+ .no-tasks-notice
+ padding: $sizeL
+ text-align: center
+
+ .icon
+ color: $yellow
+ font-size: $sizeXXL
+
+ p
+ color: $grey1
+
dialog
border: 1px solid $grey1
border-radius: $sizeS
@@ -526,6 +551,7 @@ $fontWeightBoldPlus = 700
&.next-is-submit
background: $white
border: 2px solid $yellow
+ color: $black
&.simple-next-controls
display: flex
@@ -569,6 +595,14 @@ $fontWeightBoldPlus = 700
padding: $sizeS $sizeM
text-align: center
+ .fake-submit
+ background: $white
+ border-radius: $sizeM
+ border: 2px solid $yellow
+ color: $black
+ font-size: $fontSizeXS
+ padding: $sizeS $sizeM
+
.fake-text-input
background: $white
border: 1px solid $grey1