diff --git a/cbpi4ui/CBPI-ConfigForTesting/config.7z b/cbpi4ui/CBPI-ConfigForTesting/config.7z new file mode 100644 index 0000000..24b78ff Binary files /dev/null and b/cbpi4ui/CBPI-ConfigForTesting/config.7z differ diff --git a/cbpi4ui/src/components/dashboard/DashboardContext.js b/cbpi4ui/src/components/dashboard/DashboardContext.js index 223a9e4..676d75c 100644 --- a/cbpi4ui/src/components/dashboard/DashboardContext.js +++ b/cbpi4ui/src/components/dashboard/DashboardContext.js @@ -105,7 +105,6 @@ export const DashboardProvider = ({ children }) => { }; const update_prop = (id, key, value) => { - const data = [...elements2]; const index = data.findIndex((e) => e.id === selected.id); data[index].props[key] = value; @@ -114,10 +113,28 @@ export const DashboardProvider = ({ children }) => { }; - const update_path_condition = (id, data) => { + const update_path_condition = (id, data, direction) => { + const index = pathes.findIndex((e) => e.id === id); + const temp_pathes = [...pathes]; + if(direction === "left") + { + temp_pathes[index].condition.left = data; + } + if(direction === "right") + { + temp_pathes[index].condition.right = data; + } + setPathes([...temp_pathes]); + }; + + // New method for updating path animation condition based on boolean expression + const update_path_condition_exp = (id, direction, data) => { const index = pathes.findIndex((e) => e.id === id); const temp_pathes = [...pathes]; - temp_pathes[index].condition = data; + if(direction === "leftExpression") + temp_pathes[index].condition.leftExpression = data; + if(direction=== "rightExpression") + temp_pathes[index].condition.rightExpression = data; setPathes([...temp_pathes]); }; @@ -206,6 +223,7 @@ export const DashboardProvider = ({ children }) => { update_default_prop, update_prop, update_path_condition, + update_path_condition_exp, // New Method added for the boolean expression update_coordinates, setDraggable, update_path, diff --git a/cbpi4ui/src/components/dashboard/DashboardLayer.js b/cbpi4ui/src/components/dashboard/DashboardLayer.js index 8dbcfc7..4933581 100644 --- a/cbpi4ui/src/components/dashboard/DashboardLayer.js +++ b/cbpi4ui/src/components/dashboard/DashboardLayer.js @@ -152,9 +152,10 @@ const PathSettingsItem = ({ item, checked, handleToggle }) => { - ); + + }; const PathSettings = () => { @@ -176,14 +177,19 @@ const PathSettings = () => { const currentIndex = checked.indexOf(value); const newChecked = [...checked]; - if (currentIndex === -1) { + if (currentIndex === -1) + { newChecked.push(value); - } else { + } + else + { newChecked.splice(currentIndex, 1); } setChecked(newChecked); - actions.update_path_condition(selected_id, {left: newChecked, right: checkedRight}); - } else { + actions.update_path_condition(selected_id, {left: newChecked, right: checkedRight},direction); + } + else + { const currentIndex = checkedRight.indexOf(value); const newChecked = [...checkedRight]; @@ -193,7 +199,19 @@ const PathSettings = () => { newChecked.splice(currentIndex, 1); } setCheckedRight(newChecked); - actions.update_path_condition(selected_id, {left: checked, right: newChecked}); + actions.update_path_condition(selected_id, {left: checked, right: newChecked},direction); + } + }; + + // Handle change of the boolean expression for path animation. + const data = useModel(selected_id); + const handleChange = (e, direction) => { + if (direction === "left") + { + actions.update_path_condition_exp(selected_id, "leftExpression", e.target.value); + } + else{ + actions.update_path_condition_exp(selected_id, "rightExpression", e.target.value); } }; @@ -205,6 +223,11 @@ const PathSettings = () => { return ""; } + const item = state.pathes.find((e) => e.id === selected_id); + + +// Add a TextField for adding the booleanExpression + var helperTextExpression = "sample expression : (\"actor1\" && \"actor2\") || (\"actor2\" && \"actor3\") \n don't forget the quote"; return ( <>
{ ))} + handleChange(e, "left")} /> + Flow Right {actor.map((item) => ( handleToggle(id, "right")} /> ))} + handleChange(e, "right")} />
); diff --git a/cbpi4ui/src/components/dashboard/widgets/Path.js b/cbpi4ui/src/components/dashboard/widgets/Path.js index f7b3998..a5daec7 100644 --- a/cbpi4ui/src/components/dashboard/widgets/Path.js +++ b/cbpi4ui/src/components/dashboard/widgets/Path.js @@ -10,9 +10,10 @@ export const Path = ({ id, coordinates, condition = null, stroke = 10, max_x = 4 const [dragging, setDragging] = useState(false); const [origin, setOrigin] = useState({ x: 0, y: 0 }); const [active, setActive] = useState(false); - const [flowLeft, setFlowLeft] = useState(false) - const [flowRight, setFlowRight] = useState(false) - + const [flowLeft, setFlowLeft] = useState(false); + const [flowRight, setFlowRight] = useState(false) ; + const[rightExpression, SetFlowExpRight] = useState(false); // rightExpression and useState Hook for SetFlowRightExp + const[leftExpression, SetFlowExpLeft] = useState(false); // leftExpression and useState Hook for SetFlowleftExp useEffect(() => { @@ -21,18 +22,106 @@ export const Path = ({ id, coordinates, condition = null, stroke = 10, max_x = 4 return obj; }, {}); + // Add a cache of actor states by name, used for calculate the expression + const actor_cacheFromName = actor.reduce((obj, item) => { + obj[item.name] = item.state; + return obj; + }, {}); + const p = state.pathes.find((e) => e.id === id); - if (!p.condition?.left || p.condition?.left.length === 0) { - setFlowLeft(false) - } else { - setFlowLeft(p.condition?.left.reduce((sum, next) => sum && actor_cache[next], true)); - } - if (!p.condition?.right || p.condition?.right.length === 0) { - setFlowRight(false) - } else { + + // Add a control to check if a boolean expression is added in path properties + // If an expression is present in the left or right direction, we don't check the checked value for calculating animation state. + if((!p.condition?.leftExpression || p.condition?.leftExpression.length === 0) + && (!p.condition?.rightExpression || p.condition?.rightExpression.length === 0)) + { + if (!p.condition?.left || p.condition?.left.length === 0) + { + setFlowLeft(false) + } + else + { + setFlowLeft(p.condition?.left.reduce((sum, next) => sum && actor_cache[next], true)); + } + + if (!p.condition?.right || p.condition?.right.length === 0) + { + setFlowRight(false) + } + else + { setFlowRight(p.condition?.right.reduce((sum, next) => sum && actor_cache[next], true)); + } } + + // If the leftexpression value is entered in the path property. + // TODO : variable count optimisation + if(p.condition?.leftExpression) + { + // split the expression + var actorsId = p.condition.leftExpression.split("\""); + var bStateAction = false; + var boolExpressionLeft; + boolExpressionLeft = p.condition.leftExpression + + // For each part of the string, we check if this is an actor name, if so we replace the actor name by the resulting state. + for(var actorId of actorsId) + { + // Check if we process an actorId + if(typeof actor_cacheFromName[actorId] === "boolean") + { + boolExpressionLeft = boolExpressionLeft.replace('\"'+ actorId + '\"', actor_cacheFromName[actorId].toString()); + } + } + // Evaluation of the expression + // TODO : in order to be safer, plane to use something else than eval (in the craftbeerpi context, i'm not sur it can cause any security problem : TO BE CONFIRMED) + try + { + bStateAction = eval(boolExpressionLeft); + console.trace("Right, Eval de : " + boolExpressionLeft + " Result = " + bStateAction.toString()); + setFlowLeft(bStateAction, true); + } + catch (error) + { + console.error("Evaluation of boolean expression failure : check your boolean expression for path animation"); + } + } + + // If the rightExpression value is entered in the path property. + // TODO : variable count optimisation + if(p.condition?.rightExpression) + { + // split the expression + var actorsId =p.condition.rightExpression.split("\""); + var bStateAction = false; + var boolExpressionRight; + boolExpressionRight = p.condition.rightExpression + + // For each part of the string, we check if this is an actor name, if so we replace the actor name by the resulting state. + for(var actorId of actorsId) + { + // Check if we process an actorId + if(typeof actor_cacheFromName[actorId] === "boolean") // Check if this is an actor ID or just an operator. + { + boolExpressionRight.replace('\"' + actorId + '\"', actor_cacheFromName[actorId].toString()); + } + } + // Evaluation of the expression + // TODO : in order to be safer, plane to use something else than eval (in the craftbeerpi context, i'm not sur it can cause any security problem : TO BE CONFIRMED) + bStateAction = eval(boolExpressionRight); + if (bStateAction != undefined) + { + console.trace("Left, Eval de : " + boolExpressionLeft + " Result = " + bStateAction.toString()); + setFlowRight(bStateAction, true); + } + else + { + console.trace("Evaluation of boolean expression failure : check your boolean expression for path animation"); + } + } + + }, [actor]); const draggable = state.draggable; diff --git a/cbpi4ui/src/components/dashboard/widgets/config.js b/cbpi4ui/src/components/dashboard/widgets/config.js index 21e230a..26c3877 100644 --- a/cbpi4ui/src/components/dashboard/widgets/config.js +++ b/cbpi4ui/src/components/dashboard/widgets/config.js @@ -3,7 +3,7 @@ import QueryBuilderIcon from '@material-ui/icons/QueryBuilder'; import ShowChartIcon from '@material-ui/icons/ShowChart'; import TextFieldsIcon from '@material-ui/icons/TextFields'; import ToggleOffIcon from '@material-ui/icons/ToggleOff'; -import { CBPiCalculatorIcon, CBPiControlIcon, CBPiKettle, CBPiKettle2Icon, CBPiLedIcon, CBPiLiquidIcon, CBPiSensorIcon, CBPiSVGIcon, CBPiTankIcon, CBPiThermometerIcon } from '../../util/icons/CBPiSensorIcon'; +import { CBPiCalculatorIcon, CBPiControlIcon, CBPiKettle, CBPiKettle2Icon, CBPiLedIcon, CBPiLiquidIcon, CBPiPipeIcon, CBPiSensorIcon, CBPiSVGIcon, CBPiTankIcon, CBPiThermometerIcon } from '../../util/icons/CBPiSensorIcon'; import { DashboardButton } from "./Button"; import { Calculator } from "./Calculator"; import Chart from "./Chart"; @@ -12,6 +12,7 @@ import CustomSVG from "./CustomSVG"; import { KettleControl } from "./KettleControl"; import { Led } from "./Led"; import { Liquid } from './Liquid'; +import {Path} from './Path'; import { SensorData } from "./SensorData"; import Steps from "./Steps"; import KettleSVG from "./svg/kettle.svg"; @@ -163,9 +164,7 @@ export const widget_list = [ { name: "height", default: 40, type: "number" }, { name: "sensor", default: "", type: "sensor" } ], - } - - + }, ];