diff --git a/cypress/e2e/functional/tile_tests/dataflow_tool_spec.js b/cypress/e2e/functional/tile_tests/dataflow_tool_spec.js index cafff92ae9..984b1b3b6d 100644 --- a/cypress/e2e/functional/tile_tests/dataflow_tool_spec.js +++ b/cypress/e2e/functional/tile_tests/dataflow_tool_spec.js @@ -337,11 +337,11 @@ context('Dataflow Tool Tile', function () { cy.log("verify control operator types"); const controlOperator = "controlOperator"; - const controlOperatorTypes = ["Hold this", "Hold previous", "Hold 0"]; + const controlOperatorDisplayNames = ["Hold this", "Hold previous", "Hold 0"]; dataflowToolTile.getDropdown(controlNode, controlOperator).click(); dataflowToolTile.getDropdownOptions(controlNode, controlOperator).should("have.length", 3); dataflowToolTile.getDropdownOptions(controlNode, controlOperator).each(($tab, index, $typeList) => { - expect($tab.text()).to.contain(controlOperatorTypes[index]); + expect($tab.text()).to.contain(controlOperatorDisplayNames[index]); }); dataflowToolTile.getDropdownOptions(controlNode, controlOperator).last().click(); dataflowToolTile.getDropdownOptions(controlNode, controlOperator).should("have.length", 0); diff --git a/src/plugins/dataflow/model/dataflow-program-model.ts b/src/plugins/dataflow/model/dataflow-program-model.ts index 4fd3abe380..8c7a0c095f 100644 --- a/src/plugins/dataflow/model/dataflow-program-model.ts +++ b/src/plugins/dataflow/model/dataflow-program-model.ts @@ -80,6 +80,7 @@ const DataflowNodeDataModel = types. // Control controlOperator: types.maybe(types.string), + waitDuration: types.maybe(types.number), // Demo Output outputType: types.maybe(types.string), diff --git a/src/plugins/dataflow/model/utilities/node.ts b/src/plugins/dataflow/model/utilities/node.ts index f5fd37c287..711f2575eb 100644 --- a/src/plugins/dataflow/model/utilities/node.ts +++ b/src/plugins/dataflow/model/utilities/node.ts @@ -288,7 +288,25 @@ export const HoldFunctionOptions = [ displayName: "Hold 0", type: "control", icon: HoldZeroArrowIcon - } + }, + // { + // name: "Hold Current Wait", + // displayName: "Hold this, wait", + // type: "control", + // icon: HoldCurrentArrowIcon + // }, + // { + // name: "Hold Prior Wait", + // displayName: "Hold previous, wait", + // type: "control", + // icon: HoldPreviousArrowIcon + // }, + // { + // name: "Output Zero Wait", + // displayName: "Hold 0, wait", + // type: "control", + // icon: HoldZeroArrowIcon + // } ]; export const NodeSensorTypes = [ diff --git a/src/plugins/dataflow/nodes/controls/num-control.sass b/src/plugins/dataflow/nodes/controls/num-control.sass index 83e1b73a0b..356c38e811 100644 --- a/src/plugins/dataflow/nodes/controls/num-control.sass +++ b/src/plugins/dataflow/nodes/controls/num-control.sass @@ -12,7 +12,7 @@ .number-input border-radius: 3px - &.units + &.units.multiple border-top-right-radius: 0 border-bottom-right-radius: 0 @@ -48,3 +48,10 @@ margin: 0 box-sizing: border-box box-shadow: none + +.number-container.units.one + width: 97% + + .single-unit + text-align: center + padding-left: 5px diff --git a/src/plugins/dataflow/nodes/controls/num-control.tsx b/src/plugins/dataflow/nodes/controls/num-control.tsx index c8d4c10971..385bc491f1 100644 --- a/src/plugins/dataflow/nodes/controls/num-control.tsx +++ b/src/plugins/dataflow/nodes/controls/num-control.tsx @@ -1,6 +1,7 @@ // FIXME: ESLint is unhappy with these control components /* eslint-disable react-hooks/rules-of-hooks */ import React, { useRef } from "react"; +import classNames from "classnames"; import Rete, { NodeEditor, Node, Control } from "rete"; import { useStopEventPropagation } from "./custom-hooks"; import { NodePeriodUnits } from "../../model/utilities/node"; @@ -59,13 +60,45 @@ export class NumControl extends Rete.Control { tooltip: string}) => { const inputRef = useRef(null); useStopEventPropagation(inputRef, "pointerdown"); + + const unitsClasses = classNames({ + "units one": compProps.units && compProps.units.length === 1, + "units multiple": compProps.units && compProps.units.length > 1 + }); + + const renderUnits = () => { + if (!compProps.units || compProps.units.length === 0) { + return null; + } + + if (compProps.units.length === 1) { + return ( +
+ {compProps.units[0]} +
+ ); + } + + return ( +
+
+ +
+
+ ); + }; + return ( -
+
{ label ? : null } - - { compProps.units?.length - ?
-
- -
-
- : null - } + {renderUnits()}
); }; diff --git a/src/plugins/dataflow/nodes/dataflow-node.tsx b/src/plugins/dataflow/nodes/dataflow-node.tsx index 105d3249f1..b62e651945 100644 --- a/src/plugins/dataflow/nodes/dataflow-node.tsx +++ b/src/plugins/dataflow/nodes/dataflow-node.tsx @@ -25,6 +25,8 @@ export class DataflowNode extends Node { const dynamicClasses = classNames({ "gate-active": node.data.gateActive, + "wait-option-selected": node.data.hasWait, + "wait-active": node.data.waitActive, "has-flow-in": hasFlowIn(node), "uses-relays": outputsToAnyRelay(node), "uses-gripper": outputsToAnyGripper(node), diff --git a/src/plugins/dataflow/nodes/factories/control-rete-node-factory.tsx b/src/plugins/dataflow/nodes/factories/control-rete-node-factory.tsx index 8eb7364d9c..17f4350daa 100644 --- a/src/plugins/dataflow/nodes/factories/control-rete-node-factory.tsx +++ b/src/plugins/dataflow/nodes/factories/control-rete-node-factory.tsx @@ -3,16 +3,28 @@ import { NodeData } from "rete/types/core/data"; import { DataflowReteNodeFactory } from "./dataflow-rete-node-factory"; import { ValueControl } from "../controls/value-control"; import { DropdownListControl } from "../controls/dropdown-list-control"; +import { NumControl } from "../controls/num-control"; import { HoldFunctionOptions } from "../../model/utilities/node"; import { PlotButtonControl } from "../controls/plot-button-control"; -import { getNumDisplayStr } from "../utilities/view-utilities"; +import { determineGateAndTimerStates, getHoldNodeResultString } from "../utilities/view-utilities"; +interface HoldNodeData { + gateActive: boolean; + heldValue: number | null; + timerRunning: boolean; +} export class ControlReteNodeFactory extends DataflowReteNodeFactory { constructor(numSocket: Socket) { super("Control", numSocket); } - private heldValue: number | null = null; + private startTimer(node: NodeData, duration: number) { + if (node.data.timerRunning) return; + node.data.timerRunning = true; + setTimeout(() => { + node.data.timerRunning = false; + }, duration * 1000); + } public builder(node: Node) { super.defaultBuilder(node); @@ -21,7 +33,12 @@ export class ControlReteNodeFactory extends DataflowReteNodeFactory { const valueInput = new Rete.Input("num2", "Number2", this.numSocket); const out = new Rete.Output("num", "Number", this.numSocket); - node.data.gateActive = false; + node.data = { + ...node.data, + gateActive: false, + heldValue: null, + timerRunning: false, + } as NodeData['data'] & HoldNodeData; const dropdownOptions = HoldFunctionOptions .map((nodeOp) => { @@ -31,6 +48,7 @@ export class ControlReteNodeFactory extends DataflowReteNodeFactory { .addInput(valueInput) .addInput(binaryInput) .addControl(new DropdownListControl(this.editor, "controlOperator", node, dropdownOptions, true)) + .addControl(new NumControl(this.editor, "waitDuration", node, true, "wait", 0, 0, ["sec"], "wait")) .addControl(new PlotButtonControl(this.editor, "plot", node)) .addControl(new ValueControl(this.editor, "nodeValue", node)) .addOutput(out) as any; @@ -38,22 +56,23 @@ export class ControlReteNodeFactory extends DataflowReteNodeFactory { } public worker(node: NodeData, inputs: any, outputs: any) { + const signalValue :number = inputs.num2 ? (inputs.num2.length ? inputs.num2[0] : node.data.num2) : 0; const funcName = node.data.controlOperator as string; const recents: number[] | undefined = (node.data.recentValues as any)?.nodeValue; const lastRecentValue = recents?.[recents.length - 1]; const priorValue = lastRecentValue == null ? null : lastRecentValue; - const n1 :number = inputs.num1.length ? inputs.num1[0] : node.data.num1; - const n2 :number = inputs.num2 ? (inputs.num2.length ? inputs.num2[0] : node.data.num2) : 0; + const isTimerRunning = node.data.timerRunning as boolean; let result = 0; let cResult = 0; - // for setting classes on node - node.data.gateActive = n1 === 1; + const { activateGate, startTimer } = determineGateAndTimerStates(node, inputs, isTimerRunning); + startTimer && this.startTimer(node, node.data.waitDuration as number); + node.data.gateActive = activateGate; - // requires value in n2 (except for case of Output Zero) - if (isNaN(n2)) { - this.heldValue = null; + // requires value in signalValue (except for case of Output Zero) + if (isNaN(signalValue)) { + node.data.heldValue = null; result = NaN; cResult = NaN; } @@ -61,45 +80,40 @@ export class ControlReteNodeFactory extends DataflowReteNodeFactory { // For each function, evaluate given inputs and node state // TODO - check and see if this gets serialized, and if so, how to handle legacy funcNames on load if (funcName === "Hold 0" || funcName === "Output Zero"){ - this.heldValue = null; - result = node.data.gateActive ? 0 : n2; + node.data.heldValue = null; + result = node.data.gateActive ? 0 : signalValue; cResult = 0; } else if (funcName === "Hold Current"){ if (node.data.gateActive){ // Already a number here? Maintain. Otherwise set the new held value; - this.heldValue = typeof this.heldValue === "number" ? this.heldValue : n2; - result = this.heldValue; - cResult = this.heldValue; + node.data.heldValue = typeof node.data.heldValue === "number" ? node.data.heldValue : signalValue; + result = node.data.heldValue as number; + cResult = node.data.heldValue as number; } else { - this.heldValue = null; - result = n2; - cResult = n2; // still n2, since the value to be held would be the current + node.data.heldValue = null; + result = signalValue; + cResult = signalValue; // still signalValue, since the value to be held would be the current } } else if (funcName === "Hold Prior"){ if (node.data.gateActive){ // Already a number here? Maintain. Otherwise set the new held value; - this.heldValue = typeof this.heldValue === "number" ? this.heldValue : priorValue; - result = this.heldValue || 0; - cResult = this.heldValue || 0; + node.data.heldValue = typeof node.data.heldValue === "number" ? node.data.heldValue : priorValue; + result = node.data.heldValue as number || 0; + cResult = node.data.heldValue as number || 0; } else { - this.heldValue = null; - result = n2; + node.data.heldValue = null; + result = signalValue; cResult = priorValue || 0; } } - // prepare string to display on node - const resultString = getNumDisplayStr(result); - const cResultString = getNumDisplayStr(cResult); - const onString = `on → ${cResultString}`; - const offString = `off → ${resultString}`; - const resultSentence = node.data.gateActive ? onString : offString; + const resultSentence = getHoldNodeResultString(node, result, cResult, isTimerRunning) || ""; // operate rete if (this.editor) { diff --git a/src/plugins/dataflow/nodes/node-states.scss b/src/plugins/dataflow/nodes/node-states.scss index d26bc4567b..22f0a7d83d 100644 --- a/src/plugins/dataflow/nodes/node-states.scss +++ b/src/plugins/dataflow/nodes/node-states.scss @@ -45,7 +45,7 @@ .number2 .socket::before { content: ""; position: absolute; - top: 63px; + top: 93px; left: -14px; width: 20px; height: 20px; diff --git a/src/plugins/dataflow/nodes/utilities/view-utilities.ts b/src/plugins/dataflow/nodes/utilities/view-utilities.ts index f9146e8afd..56e6dc4aef 100644 --- a/src/plugins/dataflow/nodes/utilities/view-utilities.ts +++ b/src/plugins/dataflow/nodes/utilities/view-utilities.ts @@ -1,6 +1,7 @@ import { NodeEditor, Node, Input } from "rete"; import { Rect, scaleRect, unionRect } from "../../utilities/rect"; import { kEmptyValueString } from "../factories/dataflow-rete-node-factory"; +import { NodeData } from "rete/types/core/data"; function getBoundingRectOfNode(n: Node, editor: NodeEditor): Rect | undefined { const { k } = editor.view.area.transform; @@ -75,3 +76,21 @@ export function getInsertionOrder(editor: NodeEditor, id: number) { return index + 1; } +export function getHoldNodeResultString(node: NodeData, result: number, calcResult: number, timerRunning: boolean){ + const resultString = getNumDisplayStr(result); + const cResultString = getNumDisplayStr(calcResult); + const waitString = `waiting → ${cResultString}`; + const onString = `on → ${cResultString}`; + const offString = `off → ${resultString}`; + + if (node.data.gateActive) return timerRunning ? waitString : onString; + else return offString; +} + +export function determineGateAndTimerStates(node: NodeData, inputs: any, timerRunning: boolean){ + const timerIsOption = node.data.waitDuration as number > 0; + const switchIn = inputs.num1[0]; + const startTimer = timerIsOption && switchIn === 1 && !timerRunning; + const activateGate = timerRunning ? true : switchIn === 1; + return { activateGate, startTimer }; +}