From 419fe6a6598afdd3b1a8a998ea5692e1fbf398e0 Mon Sep 17 00:00:00 2001 From: Amir Nakhaei <6696894+amirrr@users.noreply.github.com> Date: Thu, 21 Dec 2023 04:49:20 +0100 Subject: [PATCH] few components Signed-off-by: iripiri <ikoester@eonerc.rwth-aachen.de> --- src/widget/toolbox-item.js | 74 --------- src/widget/toolbox-item.jsx | 74 +++++++++ src/widget/widget-plot/plot-legend.js | 56 ------- src/widget/widget-plot/plot-legend.jsx | 62 +++++++ src/widget/widgets/{action.js => action.jsx} | 43 +++-- src/widget/widgets/{box.js => box.jsx} | 32 ++-- src/widget/widgets/button.js | 115 ------------- src/widget/widgets/button.jsx | 108 +++++++++++++ src/widget/widgets/custom-action.js | 66 -------- src/widget/widgets/custom-action.jsx | 118 ++++++++++++++ src/widget/widgets/{html.js => html.jsx} | 16 +- src/widget/widgets/icstatus.js | 79 --------- src/widget/widgets/icstatus.jsx | 71 ++++++++ src/widget/widgets/image.js | 89 ---------- src/widget/widgets/image.jsx | 78 +++++++++ src/widget/widgets/{label.js => label.jsx} | 29 ++-- src/widget/widgets/lamp.js | 86 ---------- src/widget/widgets/lamp.jsx | 72 +++++++++ src/widget/widgets/line.js | 83 ---------- src/widget/widgets/line.jsx | 70 ++++++++ src/widget/widgets/plot.js | 120 -------------- src/widget/widgets/plot.jsx | 96 +++++++++++ src/widget/widgets/slider.js | 161 ------------------- src/widget/widgets/slider.jsx | 148 +++++++++++++++++ src/widget/widgets/table.js | 136 ---------------- src/widget/widgets/table.jsx | 111 +++++++++++++ 26 files changed, 1066 insertions(+), 1127 deletions(-) delete mode 100644 src/widget/toolbox-item.js create mode 100644 src/widget/toolbox-item.jsx delete mode 100644 src/widget/widget-plot/plot-legend.js create mode 100644 src/widget/widget-plot/plot-legend.jsx rename src/widget/widgets/{action.js => action.jsx} (57%) rename src/widget/widgets/{box.js => box.jsx} (59%) delete mode 100644 src/widget/widgets/button.js create mode 100644 src/widget/widgets/button.jsx delete mode 100644 src/widget/widgets/custom-action.js create mode 100644 src/widget/widgets/custom-action.jsx rename src/widget/widgets/{html.js => html.jsx} (80%) delete mode 100644 src/widget/widgets/icstatus.js create mode 100644 src/widget/widgets/icstatus.jsx delete mode 100644 src/widget/widgets/image.js create mode 100644 src/widget/widgets/image.jsx rename src/widget/widgets/{label.js => label.jsx} (63%) delete mode 100644 src/widget/widgets/lamp.js create mode 100644 src/widget/widgets/lamp.jsx delete mode 100644 src/widget/widgets/line.js create mode 100644 src/widget/widgets/line.jsx delete mode 100644 src/widget/widgets/plot.js create mode 100644 src/widget/widgets/plot.jsx delete mode 100644 src/widget/widgets/slider.js create mode 100644 src/widget/widgets/slider.jsx delete mode 100644 src/widget/widgets/table.js create mode 100644 src/widget/widgets/table.jsx diff --git a/src/widget/toolbox-item.js b/src/widget/toolbox-item.js deleted file mode 100644 index 4c268c5..0000000 --- a/src/widget/toolbox-item.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React from 'react'; -import { DragSource } from 'react-dnd'; -import classNames from 'classnames'; -import Icon from '../common/icon'; - -const toolboxItemSource = { - beginDrag(props) { - return { - name: props.name - }; - } -}; - -function collect(connect, monitor) { - return { - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() - } -} - -class ToolboxItem extends React.Component { - static defaultProps = { - disabled: false - }; - - render() { - var itemClass = classNames({ - 'toolbox-item': true, - 'toolbox-item-dragging': this.props.isDragging, - 'toolbox-item-disabled': this.props.disabled - }); - var dropEffect = 'copy'; - - if (this.props.disabled === false) { - return this.props.connectDragSource( - <div className={itemClass}> - <span className="btn " style={{marginTop: '5px'}}> - {this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> } - {this.props.name} - </span> - </div> - , {dropEffect}); - } - else { - return ( - <div className={itemClass}> - <span className="btn btn-info" style={{marginTop: '5px'}}> - {this.props.icon && <Icon style={{marginRight: '5px'}} icon={this.props.icon} /> } - {this.props.name} - </span> - </div> - ); - } - } -} - -export default DragSource('widget', toolboxItemSource, collect)(ToolboxItem); diff --git a/src/widget/toolbox-item.jsx b/src/widget/toolbox-item.jsx new file mode 100644 index 0000000..ac5e834 --- /dev/null +++ b/src/widget/toolbox-item.jsx @@ -0,0 +1,74 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React from "react"; +import { DragSource } from "react-dnd"; +import classNames from "classnames"; +import Icon from "../common/icon"; + +// Drag source specification +const toolboxItemSource = { + beginDrag(props) { + return { + name: props.name, + }; + }, +}; + +// The collect function gathers props for the component related to dragging +function collect(connect, monitor) { + return { + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }; +} + +// The functional component for ToolboxItem +const ToolboxItem = ({ + connectDragSource, + isDragging, + disabled, + name, + icon, +}) => { + const itemClass = classNames({ + "toolbox-item": true, + "toolbox-item-dragging": isDragging, + "toolbox-item-disabled": disabled, + }); + + const content = ( + <span className="btn " style={{ marginTop: "5px" }}> + {icon && <Icon style={{ marginRight: "5px" }} icon={icon} />} + {name} + </span> + ); + + return disabled ? ( + <div className={itemClass}>{content}</div> + ) : ( + connectDragSource(<div className={itemClass}>{content}</div>, { + dropEffect: "copy", + }) + ); +}; + +ToolboxItem.defaultProps = { + disabled: false, +}; + +export default DragSource("widget", toolboxItemSource, collect)(ToolboxItem); diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js deleted file mode 100644 index 096c67b..0000000 --- a/src/widget/widget-plot/plot-legend.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React from 'react'; -import { scaleOrdinal} from 'd3-scale'; -import { schemeCategory10 } from 'd3-scale-chromatic' - -function Legend(props){ - - const signal = props.sig; - const hasScalingFactor = (signal.scalingFactor !== 1); - - let color = typeof props.lineColor === "undefined" ? schemeCategory10[props.index % 10] : props.lineColor; - - return ( - <li key={signal.id} className="signal-legend" style={{ color: color }}> - <span className="signal-legend-name">{signal.name}</span> - {props.showUnit ? <span style={{marginLeft: '0.3em'}} className="signal-unit">{signal.unit}</span> : <></> } - {hasScalingFactor ? <span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span> : <></>} - </li> - ) -} - -class PlotLegend extends React.Component { - render() { - - return <div className="plot-legend"> - <ul> - { this.props.lineColors !== undefined && this.props.lineColors != null ? ( - this.props.signals.map( (signal, idx) => - <Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={this.props.lineColors[idx]}/> - )) : ( - this.props.signals.map( (signal, idx) => - <Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={"undefined"}/> - )) - } - </ul> - </div>; - } -} - -export default PlotLegend; diff --git a/src/widget/widget-plot/plot-legend.jsx b/src/widget/widget-plot/plot-legend.jsx new file mode 100644 index 0000000..d054e18 --- /dev/null +++ b/src/widget/widget-plot/plot-legend.jsx @@ -0,0 +1,62 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React from "react"; +import { schemeCategory10 } from "d3-scale-chromatic"; + +function Legend(props) { + const { sig: signal, lineColor, index, showUnit } = props; + const hasScalingFactor = signal.scalingFactor !== 1; + const color = + typeof lineColor === "undefined" ? schemeCategory10[index % 10] : lineColor; + + return ( + <li key={signal.id} className="signal-legend" style={{ color: color }}> + <span className="signal-legend-name">{signal.name}</span> + {showUnit && ( + <span style={{ marginLeft: "0.3em" }} className="signal-unit"> + {signal.unit} + </span> + )} + {hasScalingFactor && ( + <span style={{ marginLeft: "0.3em" }} className="signal-scale"> + {signal.scalingFactor} + </span> + )} + </li> + ); +} + +const PlotLegend = ({ signals, lineColors, showUnit }) => { + return ( + <div className="plot-legend"> + <ul> + {signals.map((signal, idx) => ( + <Legend + key={signal.id} + sig={signal} + showUnit={showUnit} + index={idx} + lineColor={lineColors ? lineColors[idx] : undefined} + /> + ))} + </ul> + </div> + ); +}; + +export default PlotLegend; diff --git a/src/widget/widgets/action.js b/src/widget/widgets/action.jsx similarity index 57% rename from src/widget/widgets/action.js rename to src/widget/widgets/action.jsx index 5a69d32..7b0ecb0 100644 --- a/src/widget/widgets/action.js +++ b/src/widget/widgets/action.jsx @@ -15,29 +15,28 @@ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ -import React, { Component } from 'react'; -import { Button, ButtonGroup } from 'react-bootstrap'; -import Icon from '../../common/icon'; +import React from "react"; +import { Button, ButtonGroup } from "react-bootstrap"; +import Icon from "../../common/icon"; -class WidgetAction extends Component { - constructor(props) { - super(props); +const WidgetAction = (props) => { + const onClick = (e) => { + // Put your onClick logic here + }; - this.state = { - }; - } - - onClick(e) { - - } - - render() { - return <ButtonGroup> - <Button onClick={this.onClick} ><Icon icon="play" /> Start</Button> - <Button onClick={this.onClick} ><Icon icon="pause" /> Pause</Button> - <Button onClick={this.onClick} ><Icon icon="stop" /> Stop</Button> - </ButtonGroup>; - } -} + return ( + <ButtonGroup> + <Button onClick={onClick}> + <Icon icon="play" /> Start + </Button> + <Button onClick={onClick}> + <Icon icon="pause" /> Pause + </Button> + <Button onClick={onClick}> + <Icon icon="stop" /> Stop + </Button> + </ButtonGroup> + ); +}; export default WidgetAction; diff --git a/src/widget/widgets/box.js b/src/widget/widgets/box.jsx similarity index 59% rename from src/widget/widgets/box.js rename to src/widget/widgets/box.jsx index 3572267..3060610 100644 --- a/src/widget/widgets/box.js +++ b/src/widget/widgets/box.jsx @@ -15,25 +15,21 @@ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ -import React, { Component } from 'react'; +import React from "react"; +const WidgetBox = (props) => { + let colorStyle = { + borderColor: props.widget.customProperties.border_color, + backgroundColor: props.widget.customProperties.background_color, + opacity: props.widget.customProperties.background_color_opacity, + borderWidth: props.widget.customProperties.border_width + "px", + }; -class WidgetBox extends Component { - render() { - - let colorStyle = { - borderColor: this.props.widget.customProperties.border_color, - backgroundColor: this.props.widget.customProperties.background_color, - opacity: this.props.widget.customProperties.background_color_opacity, - borderWidth: this.props.widget.customProperties.border_width + 'px' - } - - return ( - <div className="box-widget full" style={colorStyle}> - { } - </div> - ); - } -} + return ( + <div className="box-widget full" style={colorStyle}> + {} + </div> + ); +}; export default WidgetBox; diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js deleted file mode 100644 index ed6e02d..0000000 --- a/src/widget/widgets/button.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React, { Component } from 'react'; -import { Button } from 'react-bootstrap'; -import AppDispatcher from '../../common/app-dispatcher'; - -class WidgetButton extends Component { - - constructor(props) { - super(props); - - this.state = { - pressed: props.widget.customProperties.pressed - } - } - - componentDidMount() { - let widget = this.props.widget - widget.customProperties.simStartedSendValue = false - widget.customProperties.pressed = false - - AppDispatcher.dispatch({ - type: 'widgets/start-edit', - token: this.props.token, - data: widget - }); - } - - componentDidUpdate() { - // a simulaton was started, make an update - if (this.props.widget.customProperties.simStartedSendValue) { - // update widget, 'unpress' button at each simulation start - let widget = this.props.widget - widget.customProperties.simStartedSendValue = false - widget.customProperties.pressed = false - AppDispatcher.dispatch({ - type: 'widgets/start-edit', - token: this.props.token, - data: widget - }); - - // send value without changing widget - this.props.onInputChanged(widget.customProperties.off_value, '', false, false); - } - } - - static getDerivedStateFromProps(props, state){ - return { - pressed: props.widget.customProperties.pressed - } - } - - onPress(e) { - if (e.button === 0 && !this.props.widget.customProperties.toggle) { - this.valueChanged(this.props.widget.customProperties.on_value, true); - } - } - - onRelease(e) { - if (e.button === 0) { - let nextState = false; - if (this.props.widget.customProperties.toggle) { - nextState = !this.state.pressed; - } - this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value, nextState); - } - } - - valueChanged(newValue, pressed) { - if (this.props.onInputChanged) { - this.props.onInputChanged(newValue, 'pressed', pressed, true); - } - } - - render() { - let opacity = this.props.widget.customProperties.background_color_opacity - const buttonStyle = { - backgroundColor: this.props.widget.customProperties.background_color, - borderColor: this.props.widget.customProperties.border_color, - color: this.props.widget.customProperties.font_color, - opacity: this.state.pressed ? (opacity + 1)/4 : opacity, - }; - - return ( - <div className="button-widget full"> - <Button - className="full" - style={buttonStyle} - active={ this.state.pressed } - disabled={ this.props.editing } - onMouseDown={ (e) => this.onPress(e) } - onMouseUp={ (e) => this.onRelease(e) }> - {this.props.widget.name} - </Button> - </div> - ); - } -} - -export default WidgetButton; diff --git a/src/widget/widgets/button.jsx b/src/widget/widgets/button.jsx new file mode 100644 index 0000000..7dced13 --- /dev/null +++ b/src/widget/widgets/button.jsx @@ -0,0 +1,108 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState, useEffect } from 'react'; +import { Button } from 'react-bootstrap'; +import AppDispatcher from '../../common/app-dispatcher'; + +const WidgetButton = (props) => { + const [pressed, setPressed] = useState(props.widget.customProperties.pressed); + + useEffect(() => { + let widget = props.widget; + widget.customProperties.simStartedSendValue = false; + widget.customProperties.pressed = false; + + AppDispatcher.dispatch({ + type: 'widgets/start-edit', + token: props.token, + data: widget + }); + + // Effect cleanup + return () => { + // Clean up if needed + }; + }, [props.token, props.widget]); + + useEffect(() => { + if (props.widget.customProperties.simStartedSendValue) { + let widget = props.widget; + widget.customProperties.simStartedSendValue = false; + widget.customProperties.pressed = false; + AppDispatcher.dispatch({ + type: 'widgets/start-edit', + token: props.token, + data: widget + }); + + props.onInputChanged(widget.customProperties.off_value, '', false, false); + } + }, [props, setPressed]); + + useEffect(() => { + setPressed(props.widget.customProperties.pressed); + }, [props.widget.customProperties.pressed]); + + const onPress = (e) => { + if (e.button === 0 && !props.widget.customProperties.toggle) { + valueChanged(props.widget.customProperties.on_value, true); + } + }; + + const onRelease = (e) => { + if (e.button === 0) { + let nextState = false; + if (props.widget.customProperties.toggle) { + nextState = !pressed; + } + valueChanged(nextState ? props.widget.customProperties.on_value : props.widget.customProperties.off_value, nextState); + } + }; + + const valueChanged = (newValue, newPressed) => { + if (props.onInputChanged) { + props.onInputChanged(newValue, 'pressed', newPressed, true); + } + setPressed(newPressed); + }; + + let opacity = props.widget.customProperties.background_color_opacity; + const buttonStyle = { + backgroundColor: props.widget.customProperties.background_color, + borderColor: props.widget.customProperties.border_color, + color: props.widget.customProperties.font_color, + opacity: pressed ? (opacity + 1) / 4 : opacity, + }; + + return ( + <div className="button-widget full"> + <Button + className="full" + style={buttonStyle} + active={pressed} + disabled={props.editing} + onMouseDown={(e) => onPress(e)} + onMouseUp={(e) => onRelease(e)} + > + {props.widget.name} + </Button> + </div> + ); +}; + +export default WidgetButton; diff --git a/src/widget/widgets/custom-action.js b/src/widget/widgets/custom-action.js deleted file mode 100644 index cdd8078..0000000 --- a/src/widget/widgets/custom-action.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React, { Component } from 'react'; -import { Button } from 'react-bootstrap'; -import Icon from '../../common/icon'; -import ICStore from '../../ic/ic-store'; -import AppDispatcher from '../../common/app-dispatcher'; - -class WidgetCustomAction extends Component { - constructor(props) { - super(props); - - this.state = { - ic: null - }; - } - - static getStores() { - return [ ICStore ]; - } - - static getDerivedStateFromProps(props, state){ - if(props.widget.signalIDs.length === 0){ - return null; - } - - return{ - ic: ICStore.getState().find(s => s.id === props.icIDs[0]), - sessionToken: localStorage.getItem("token") - }; - } - - onClick() { - AppDispatcher.dispatch({ - type: 'ics/start-action', - ic: this.state.ic, - data: this.props.widget.customProperties.actions, - token: this.state.sessionToken - }); - } - - render() { - return <div className="widget-custom-action full"> - <Button className="full" disabled={this.state.ic === null} onClick={(e) => this.onClick()}> - <Icon icon={this.props.widget.customProperties.icon} /> <span dangerouslySetInnerHTML={{ __html: this.props.widget.name }} /> - </Button> - </div>; - } -} - -export default WidgetCustomAction; diff --git a/src/widget/widgets/custom-action.jsx b/src/widget/widgets/custom-action.jsx new file mode 100644 index 0000000..1073249 --- /dev/null +++ b/src/widget/widgets/custom-action.jsx @@ -0,0 +1,118 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ +import React, { useState, useEffect } from "react"; +import { Button } from "react-bootstrap"; +import Icon from "../../common/icon"; +import ICStore from "../../ic/ic-store"; +import AppDispatcher from "../../common/app-dispatcher"; + +const WidgetCustomAction = (props) => { + const [ic, setIC] = useState(null); + const [sessionToken, setSessionToken] = useState( + localStorage.getItem("token") + ); + + useEffect(() => { + const handleStoresChanged = () => { + if (props.widget.signalIDs.length === 0) { + setIC(null); + return; + } + + const newIC = ICStore.getState().find((s) => s.id === props.icIDs[0]); + setIC(newIC); + }; + + // Subscribe to store changes + const unsubscribe = ICStore.subscribe(handleStoresChanged); + handleStoresChanged(); // Also call it to set initial state + + return () => { + unsubscribe(); // Clean up the subscription when the component is unmounted + }; + }, [props.widget.signalIDs, props.icIDs]); + + const onClick = () => { + AppDispatcher.dispatch({ + type: "ics/start-action", + ic: ic, + data: props.widget.customProperties.actions, + token: sessionToken, + }); + }; + + return ( + <div className="widget-custom-action full"> + <Button className="full" disabled={ic === null} onClick={onClick}> + <Icon icon={props.widget.customProperties.icon} /> + <span dangerouslySetInnerHTML={{ __html: props.widget.name }} /> + </Button> + </div> + ); +}; + +export default WidgetCustomAction; + +// import React, { Component } from 'react'; +// import { Button } from 'react-bootstrap'; +// import Icon from '../../common/icon'; +// import ICStore from '../../ic/ic-store'; +// import AppDispatcher from '../../common/app-dispatcher'; + +// class WidgetCustomAction extends Component { +// constructor(props) { +// super(props); + +// this.state = { +// ic: null +// }; +// } + +// static getStores() { +// return [ ICStore ]; +// } + +// static getDerivedStateFromProps(props, state){ +// if(props.widget.signalIDs.length === 0){ +// return null; +// } + +// return{ +// ic: ICStore.getState().find(s => s.id === props.icIDs[0]), +// sessionToken: localStorage.getItem("token") +// }; +// } + +// onClick() { +// AppDispatcher.dispatch({ +// type: 'ics/start-action', +// ic: this.state.ic, +// data: this.props.widget.customProperties.actions, +// token: this.state.sessionToken +// }); +// } + +// render() { +// return <div className="widget-custom-action full"> +// <Button className="full" disabled={this.state.ic === null} onClick={(e) => this.onClick()}> +// <Icon icon={this.props.widget.customProperties.icon} /> <span dangerouslySetInnerHTML={{ __html: this.props.widget.name }} /> +// </Button> +// </div>; +// } +// } + +// export default WidgetCustomAction; diff --git a/src/widget/widgets/html.js b/src/widget/widgets/html.jsx similarity index 80% rename from src/widget/widgets/html.js rename to src/widget/widgets/html.jsx index e5c18a0..1f3774f 100644 --- a/src/widget/widgets/html.js +++ b/src/widget/widgets/html.jsx @@ -15,12 +15,16 @@ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ -import React from 'react'; +import React from "react"; -class WidgetHTML extends React.Component { - render() { - return <div dangerouslySetInnerHTML={{__html: this.props.widget.customProperties.content }} /> - } -} +const WidgetHTML = (props) => { + return ( + <div + dangerouslySetInnerHTML={{ + __html: props.widget.customProperties.content, + }} + /> + ); +}; export default WidgetHTML; diff --git a/src/widget/widgets/icstatus.js b/src/widget/widgets/icstatus.js deleted file mode 100644 index 2bb9d9b..0000000 --- a/src/widget/widgets/icstatus.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React from 'react'; -import { Badge } from 'react-bootstrap'; -import { stateLabelStyle } from "../../ic/ics"; -import AppDispatcher from '../../common/app-dispatcher'; - - -class WidgetICstatus extends React.Component { - constructor(props) { - super(props); - - this.state = { - sessionToken: localStorage.getItem("token") - }; - } - - componentDidMount() { - // Start timer for periodic refresh - this.timer = window.setInterval(() => this.refresh(), 3000); - } - - componentWillUnmount() { - window.clearInterval(this.timer); - } - - refresh() { - if (this.props.ics) { - this.props.ics.forEach(ic => { - let icID = parseInt(ic.id, 10); - AppDispatcher.dispatch({ - type: 'ics/start-load', - data: icID, - token: this.state.sessionToken, - }); - }) - } - } - - render() { - let badges = [] - let checkedICs = [] - if (this.props.widget) { - checkedICs = this.props.widget.customProperties.checkedIDs - } - if (this.props.ics && checkedICs) { - this.props.ics.forEach(ic => { - if (!checkedICs.includes(ic.id)) { - return - } - let badgeStyle = stateLabelStyle(ic.state, ic) - badges.push(<Badge - key={ic.id} - bg={badgeStyle[0]} - className={badgeStyle[1]}> - {ic.name + ": " + ic.state}</Badge>) - }) - } - - return (<div>{badges}</div>); - } -} - -export default WidgetICstatus; diff --git a/src/widget/widgets/icstatus.jsx b/src/widget/widgets/icstatus.jsx new file mode 100644 index 0000000..19b7dba --- /dev/null +++ b/src/widget/widgets/icstatus.jsx @@ -0,0 +1,71 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState, useEffect } from "react"; +import { Badge } from "react-bootstrap"; +import { stateLabelStyle } from "../../ic/ics"; +import AppDispatcher from "../../common/app-dispatcher"; + +const WidgetICstatus = (props) => { + const [sessionToken, setSessionToken] = useState( + localStorage.getItem("token") + ); + + useEffect(() => { + // Function to refresh data + const refresh = () => { + if (props.ics) { + props.ics.forEach((ic) => { + let icID = parseInt(ic.id, 10); + AppDispatcher.dispatch({ + type: "ics/start-load", + data: icID, + token: sessionToken, + }); + }); + } + }; + + // Start timer for periodic refresh + const timer = window.setInterval(() => refresh(), 3000); + + // Cleanup function equivalent to componentWillUnmount + return () => { + window.clearInterval(timer); + }; + }, [props.ics, sessionToken]); + + let badges = []; + let checkedICs = props.widget ? props.widget.customProperties.checkedIDs : []; + + if (props.ics && checkedICs) { + badges = props.ics + .filter((ic) => checkedICs.includes(ic.id)) + .map((ic) => { + let badgeStyle = stateLabelStyle(ic.state, ic); + return ( + <Badge key={ic.id} bg={badgeStyle[0]} className={badgeStyle[1]}> + {ic.name + ": " + ic.state} + </Badge> + ); + }); + } + + return <div>{badges}</div>; +}; + +export default WidgetICstatus; diff --git a/src/widget/widgets/image.js b/src/widget/widgets/image.js deleted file mode 100644 index f289324..0000000 --- a/src/widget/widgets/image.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React from 'react'; - -import AppDispatcher from '../../common/app-dispatcher'; - -class WidgetImage extends React.Component { - - constructor(props) { - super(props); - - this.state = { - file: undefined, - } - } - - componentDidMount() { - // Query the image referenced by the widget - let widgetFile = this.props.widget.customProperties.file; - if (widgetFile !== -1 && this.state.file === undefined) { - AppDispatcher.dispatch({ - type: 'files/start-download', - data: widgetFile, - token: this.props.token - }); - } - } - - componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) { - - if(this.props.widget.customProperties.file === -1){ - this.props.widget.customProperties.update = false; - if(this.state.file !== undefined) this.setState({ file: undefined }) - } - - let file = this.props.files.find(file => file.id === parseInt(this.props.widget.customProperties.file, 10)); - - if (file !== undefined) { - if (this.props.widget.customProperties.update) { - this.props.widget.customProperties.update = false; - AppDispatcher.dispatch({ - type: 'files/start-download', - data: file.id, - token: this.props.token - }); - this.setState({ file: file }) - } - } - - } - - imageError(e){ - console.error("Image ", this.state.file.name, "cannot be displayed."); - } - - render() { - let objectURL='' - if(this.state.file !== undefined && this.state.file.objectURL !== undefined) { - objectURL = this.state.file.objectURL - } - - return ( - <div className="full"> - {objectURL !== '' ? ( - <img onError={(e) => this.imageError(e)} className="full" alt={this.state.file.name} src={objectURL} onDragStart={e => e.preventDefault()} /> - ) : ( - <img className="full" alt="No file selected." /> - )} - </div> - ); - } -} - -export default WidgetImage; diff --git a/src/widget/widgets/image.jsx b/src/widget/widgets/image.jsx new file mode 100644 index 0000000..8b658d8 --- /dev/null +++ b/src/widget/widgets/image.jsx @@ -0,0 +1,78 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState, useEffect } from "react"; +import AppDispatcher from "../../common/app-dispatcher"; + +const WidgetImage = (props) => { + const [file, setFile] = useState(undefined); + + useEffect(() => { + let widgetFile = props.widget.customProperties.file; + if (widgetFile !== -1 && file === undefined) { + AppDispatcher.dispatch({ + type: "files/start-download", + data: widgetFile, + token: props.token, + }); + } + }, [file, props.token, props.widget.customProperties.file]); + + useEffect(() => { + if (props.widget.customProperties.file === -1) { + props.widget.customProperties.update = false; + if (file !== undefined) setFile(undefined); + } else { + let foundFile = props.files.find( + (f) => f.id === parseInt(props.widget.customProperties.file, 10) + ); + if (foundFile && props.widget.customProperties.update) { + props.widget.customProperties.update = false; + AppDispatcher.dispatch({ + type: "files/start-download", + data: foundFile.id, + token: props.token, + }); + setFile(foundFile); + } + } + }, [props.widget.customProperties, props.files, props.token, file]); + + const imageError = (e) => { + console.error("Image error:", e); + }; + + let objectURL = file && file.objectURL ? file.objectURL : ""; + + return ( + <div className="full"> + {objectURL ? ( + <img + onError={imageError} + className="full" + alt={file.name} + src={objectURL} + onDragStart={(e) => e.preventDefault()} + /> + ) : ( + <img className="full" alt="No file selected." /> + )} + </div> + ); +}; + +export default WidgetImage; diff --git a/src/widget/widgets/label.js b/src/widget/widgets/label.jsx similarity index 63% rename from src/widget/widgets/label.js rename to src/widget/widgets/label.jsx index 2a29e1f..141d1c7 100644 --- a/src/widget/widgets/label.js +++ b/src/widget/widgets/label.jsx @@ -15,23 +15,20 @@ * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ -import React, { Component } from 'react'; +import React from "react"; +const WidgetLabel = (props) => { + const style = { + fontSize: props.widget.customProperties.textSize + "px", + color: props.widget.customProperties.fontColor, + opacity: props.widget.customProperties.fontColor_opacity, + }; -class WidgetLabel extends Component { - render() { - const style = { - fontSize: this.props.widget.customProperties.textSize + 'px', - color: this.props.widget.customProperties.fontColor, - opacity: this.props.widget.customProperties.fontColor_opacity, - }; - - return ( - <div className="label-widget"> - <h4 style={style}>{this.props.widget.name}</h4> - </div> - ); - } -} + return ( + <div className="label-widget"> + <h4 style={style}>{props.widget.name}</h4> + </div> + ); +}; export default WidgetLabel; diff --git a/src/widget/widgets/lamp.js b/src/widget/widgets/lamp.js deleted file mode 100644 index 43603e4..0000000 --- a/src/widget/widgets/lamp.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React, { Component } from 'react'; - - -class WidgetLamp extends Component { - constructor(props) { - super(props); - - this.state = { - value: '', - }; - } - - static getDerivedStateFromProps(props, state){ - if(props.widget.signalIDs.length === 0){ - return{ value: ''}; - } - - // get the signal with the selected signal ID - let signalID = props.widget.signalIDs[0]; - let signal = props.signals.filter(s => s.id === signalID) - - if(signal.length>0) { - // determine ID of infrastructure component related to signal[0] (there is only one signal for a lamp widget) - let icID = props.icIDs[signal[0].id]; - - // check if data available - if (props.data == null - || props.data[icID] == null - || props.data[icID].output == null - || props.data[icID].output.values == null) { - return {value: ''}; - } - - // check if value has changed - const data = props.data[icID].output.values[signal[0].index]; - if (data != null && Number(state.value) !== signal[0].scalingFactor * data[data.length - 1].y) { - return {value: signal[0].scalingFactor * data[data.length - 1].y}; - } - } - - return null; - } - - render() { - - let color; - let opacity; - - if (Number(this.state.value) > Number(this.props.widget.customProperties.threshold)){ - color = this.props.widget.customProperties.on_color; - opacity = this.props.widget.customProperties.on_color_opacity; - } - else{ - color = this.props.widget.customProperties.off_color; - opacity = this.props.widget.customProperties.off_color_opacity; - } - - let style = { - backgroundColor: color, - opacity: opacity - } - - return ( - <div className="lamp-widget" style={style} /> - ); - } -} - -export default WidgetLamp; diff --git a/src/widget/widgets/lamp.jsx b/src/widget/widgets/lamp.jsx new file mode 100644 index 0000000..c44cdd0 --- /dev/null +++ b/src/widget/widgets/lamp.jsx @@ -0,0 +1,72 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState, useEffect } from 'react'; + +const WidgetLamp = (props) => { + const [value, setValue] = useState(''); + + useEffect(() => { + if(props.widget.signalIDs.length === 0){ + setValue(''); + return; + } + + let signalID = props.widget.signalIDs[0]; + let signal = props.signals.find(s => s.id === signalID); + + if(signal) { + let icID = props.icIDs[signal.id]; + + if (!(props.data && + props.data[icID] && + props.data[icID].output && + props.data[icID].output.values)) { + setValue(''); + return; + } + + const data = props.data[icID].output.values[signal.index]; + if(data) { + const newValue = String(signal.scalingFactor * data[data.length - 1].y); + if (value !== newValue) { + setValue(newValue); + } + } + } + }, [props.widget.signalIDs, props.signals, props.icIDs, props.data, value]); + + let color, opacity; + if (Number(value) > Number(props.widget.customProperties.threshold)) { + color = props.widget.customProperties.on_color; + opacity = props.widget.customProperties.on_color_opacity; + } else { + color = props.widget.customProperties.off_color; + opacity = props.widget.customProperties.off_color_opacity; + } + + let style = { + backgroundColor: color, + opacity: opacity + }; + + return ( + <div className="lamp-widget" style={style} /> + ); +}; + +export default WidgetLamp; \ No newline at end of file diff --git a/src/widget/widgets/line.js b/src/widget/widgets/line.js deleted file mode 100644 index 2250907..0000000 --- a/src/widget/widgets/line.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React, { Component } from 'react'; - -class WidgetLine extends Component { - // WidgetLine is newly created when widget props are changed and saved - constructor(props) { - super(props); - this.illustrateDuringEdit = this.illustrateDuringEdit.bind(this); - - this.state = { - width: 0, - height: 0, - editing: false - } - } - - // needed to update the looks of the line in edit mode - illustrateDuringEdit(newwidth, newheight) { - this.setState({width: newwidth, height: newheight, editing: true}); - } - - render() { - let rotation = this.props.widget.customProperties.rotation; - let rad = rotation * (Math.PI / 180); - - // get the dimensions either from props (saved widget) - // or from the state (widget in edit mode) - let width = this.props.widget.width; - let height = this.props.widget.height; - - if (this.state.editing) { - width = this.state.width; - height = this.state.height; - } - - let length = width; - rotation = Math.abs(parseInt(rotation,10)); - if(rotation % 90 === 0 && (rotation/90) % 2 === 1){ - length = height; - } - - // calculate line coordinates (in percent) - const x1 = width * 0.5 - 0.5 * Math.cos(rad) * length; - const x1p = '' + Math.round(100 * x1 / width) + '%'; - - const x2 = width * 0.5 + 0.5 * Math.cos(rad) * length; - const x2p = '' + Math.round(100 * x2/width) + '%'; - - const y1 = height * 0.5 + 0.5 * Math.sin(rad) * length; - const y1p = '' + Math.round(100 * y1/height) + '%'; - - const y2 = height * 0.5 - 0.5 * Math.sin(rad) * length; - const y2p = '' + Math.round(100 * y2/height) + '%'; - - - const lineStyle = { - stroke: '' + this.props.widget.customProperties.border_color, - strokeWidth: '' + this.props.widget.customProperties.border_width + 'px' - }; - - return <svg height="100%" width="100%"> - <line x1={x1p} x2={x2p} y1={y1p} y2={y2p} style={lineStyle}/> - </svg>; - } -} - -export default WidgetLine; diff --git a/src/widget/widgets/line.jsx b/src/widget/widgets/line.jsx new file mode 100644 index 0000000..d1f6648 --- /dev/null +++ b/src/widget/widgets/line.jsx @@ -0,0 +1,70 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState } from "react"; + +const WidgetLine = (props) => { + const [dimensions, setDimensions] = useState({ + width: props.widget.width || 0, + height: props.widget.height || 0, + editing: false, + }); + + const illustrateDuringEdit = (newWidth, newHeight) => { + setDimensions({ width: newWidth, height: newHeight, editing: true }); + }; + + // Assuming illustrateDuringEdit may be called from outside to update the dimensions. + // If not, you can remove this line. + props.illustrateDuringEditRef && + props.illustrateDuringEditRef(illustrateDuringEdit); + + let { rotation } = props.widget.customProperties; + let rad = rotation * (Math.PI / 180); + let length = dimensions.editing ? dimensions.width : props.widget.width; + + rotation = Math.abs(parseInt(rotation, 10)); + if (rotation % 90 === 0 && (rotation / 90) % 2 === 1) { + length = dimensions.editing ? dimensions.height : props.widget.height; + } + + // calculate line coordinates (in percent) + const x1 = length * 0.5 - 0.5 * Math.cos(rad) * length; + const x1p = `${Math.round((100 * x1) / length)}%`; + + const x2 = length * 0.5 + 0.5 * Math.cos(rad) * length; + const x2p = `${Math.round((100 * x2) / length)}%`; + + const y1 = length * 0.5 + 0.5 * Math.sin(rad) * length; + const y1p = `${Math.round((100 * y1) / length)}%`; + + const y2 = length * 0.5 - 0.5 * Math.sin(rad) * length; + const y2p = `${Math.round((100 * y2) / length)}%`; + + const lineStyle = { + stroke: props.widget.customProperties.border_color, + strokeWidth: `${props.widget.customProperties.border_width}px`, + }; + + return ( + <svg height="100%" width="100%"> + <line x1={x1p} x2={x2p} y1={y1p} y2={y2p} style={lineStyle} /> + </svg> + ); +}; + +export default WidgetLine; diff --git a/src/widget/widgets/plot.js b/src/widget/widgets/plot.js deleted file mode 100644 index 27041b0..0000000 --- a/src/widget/widgets/plot.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React from 'react'; - -import Plot from '../widget-plot/plot'; -import PlotLegend from '../widget-plot/plot-legend'; - -class WidgetPlot extends React.Component { - constructor(props) { - super(props); - - this.state = { - data: [], - signals: [] - }; - } - - - static getDerivedStateFromProps(props, state){ - - let intersection = [] - let data = []; - let signalID, sig; - for (signalID of props.widget.signalIDs) { - for (sig of props.signals) { - if (signalID === sig.id) { - intersection.push(sig); - - // sig is a selected signal, get data - // determine ID of infrastructure component related to signal (via config) - let icID = props.icIDs[sig.id] - - // distinguish between input and output signals - if (sig.direction === "out") { - if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) { - if (props.data[icID].output.values[sig.index] !== undefined) { - let values = props.data[icID].output.values[sig.index]; - if(sig.scalingFactor !== 1) { - let scaledValues = JSON.parse(JSON.stringify(values)); - for (let i=0; i< scaledValues.length; i++){ - scaledValues[i].y = scaledValues[i].y * sig.scalingFactor; - } - data.push(scaledValues); - } else { - data.push(values); - } - } - } - } else if (sig.direction === "in") { - if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) { - if (props.data[icID].input.values[sig.index] !== undefined) { - let values = props.data[icID].output.values[sig.index]; - if(sig.scalingFactor !== 1) { - let scaledValues = JSON.parse(JSON.stringify(values)); - for (let i=0; i< scaledValues.length; i++){ - scaledValues[i].y = scaledValues[i].y * sig.scalingFactor; - } - data.push(scaledValues); - } else { - data.push(values); - } - } - } - } - } // sig is selected signal - } // loop over props.signals - } // loop over selected signals - - return {signals: intersection, data: data} - - } - - //do we need this function? - scaleData(data, scaleFactor){ - // data is an array of value pairs x,y - } - - render() { - return <div className="plot-widget" ref="wrapper"> - <div className="widget-plot"> - <Plot - data={this.state.data} - mode={this.props.widget.customProperties.mode || "auto time-scrolling"} - height={this.props.widget.height - 55} - width={this.props.widget.width - 20} - time={this.props.widget.customProperties.time} - samples={this.props.widget.customProperties.nbrSamples || 100} - yMin={this.props.widget.customProperties.yMin} - yMax={this.props.widget.customProperties.yMax} - yUseMinMax={this.props.widget.customProperties.yUseMinMax} - paused={this.props.paused} - yLabel={this.props.widget.customProperties.ylabel} - lineColors={this.props.widget.customProperties.lineColors} - signalIDs={this.props.widget.signalIDs} - /> - </div> - <PlotLegend - signals={this.state.signals} - lineColors={this.props.widget.customProperties.lineColors} - showUnit={this.props.widget.customProperties.showUnit} /> - </div>; - } -} - -export default WidgetPlot; diff --git a/src/widget/widgets/plot.jsx b/src/widget/widgets/plot.jsx new file mode 100644 index 0000000..74d5a16 --- /dev/null +++ b/src/widget/widgets/plot.jsx @@ -0,0 +1,96 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState, useEffect } from "react"; +import Plot from "../widget-plot/plot"; +import PlotLegend from "../widget-plot/plot-legend"; + +const WidgetPlot = (props) => { + const [data, setData] = useState([]); + const [signals, setSignals] = useState([]); + + useEffect(() => { + const intersection = []; + const plotData = []; + let signalID, sig; + for (signalID of props.widget.signalIDs) { + for (sig of props.signals) { + if (signalID === sig.id) { + intersection.push(sig); + + // Signal is a selected signal, get data + let icID = props.icIDs[sig.id]; + let values = null; + + if ( + sig.direction === "out" && + props.data[icID]?.output?.values?.[sig.index] !== undefined + ) { + values = props.data[icID].output.values[sig.index]; + } else if ( + sig.direction === "in" && + props.data[icID]?.input?.values?.[sig.index] !== undefined + ) { + values = props.data[icID].input.values[sig.index]; + } + + if (values) { + if (sig.scalingFactor !== 1) { + values = values.map((v) => ({ + ...v, + y: v.y * sig.scalingFactor, + })); + } + plotData.push(values); + } + } + } + } + + setData(plotData); + setSignals(intersection); + }, [props.widget.signalIDs, props.signals, props.icIDs, props.data]); + + return ( + <div className="plot-widget"> + <div className="widget-plot"> + <Plot + data={data} + mode={props.widget.customProperties.mode || "auto time-scrolling"} + height={props.widget.height - 55} + width={props.widget.width - 20} + time={props.widget.customProperties.time} + samples={props.widget.customProperties.nbrSamples || 100} + yMin={props.widget.customProperties.yMin} + yMax={props.widget.customProperties.yMax} + yUseMinMax={props.widget.customProperties.yUseMinMax} + paused={props.paused} + yLabel={props.widget.customProperties.ylabel} + lineColors={props.widget.customProperties.lineColors} + signalIDs={props.widget.signalIDs} + /> + </div> + <PlotLegend + signals={signals} + lineColors={props.widget.customProperties.lineColors} + showUnit={props.widget.customProperties.showUnit} + /> + </div> + ); +}; + +export default WidgetPlot; diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js deleted file mode 100644 index ea7be28..0000000 --- a/src/widget/widgets/slider.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React, { Component } from 'react'; -import { format } from 'd3'; -import classNames from 'classnames'; -import Slider from 'rc-slider'; -import 'rc-slider/assets/index.css'; -import AppDispatcher from '../../common/app-dispatcher'; - - -class WidgetSlider extends Component { - - static get OrientationTypes() { - return ({ - HORIZONTAL: {value: 0, name: 'Horizontal'}, - VERTICAL: {value: 1, name: 'Vertical'} - }) - } - - constructor(props) { - super(props); - - this.state = { - unit: '', - value: '', - }; - } - - componentDidMount() { - let widget = this.props.widget - widget.customProperties.simStartedSendValue = false - AppDispatcher.dispatch({ - type: 'widgets/start-edit', - token: this.props.token, - data: widget - }); - } - - componentDidUpdate() { - // a simulaton was started, make an update - if (this.props.widget.customProperties.simStartedSendValue) { - let widget = this.props.widget - widget.customProperties.simStartedSendValue = false - AppDispatcher.dispatch({ - type: 'widgets/start-edit', - token: this.props.token, - data: widget - }); - - // send value without changing widget - this.props.onInputChanged(widget.customProperties.value , '', '', false); - } - } - - static getDerivedStateFromProps(props, state){ - - let value = '' - let unit = '' - - if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){ - // set value to customProperties.value if this property exists and the value is different from current state - value = Number(props.widget.customProperties.value); - } else if (state.value === '') { - value = 0.0; - } - - // Update unit (assuming there is exactly one signal for this widget) - if (props.widget.signalIDs.length > 0) { - let signalID = props.widget.signalIDs[0]; - let signal = props.signals.find(sig => sig.id === signalID); - if (signal !== undefined) { - unit = signal.unit; - } - } - - if (unit !== '' && value !== ''){ - // unit and value have changed - return {unit: unit, value: value}; - } else if (unit !== ''){ - // only unit has changed - return {unit: unit} - } else if (value !== ''){ - // only value has changed - return {value: value} - } else { - // nothing has changed - return null - } - } - - valueIsChanging(newValue) { - this.props.widget.customProperties.value = newValue; - if (this.props.widget.customProperties.continous_update) - this.valueChanged(newValue, false); - - this.setState({ value: newValue }); - } - - valueChanged(newValue, isFinalChange) { - if (this.props.onInputChanged) { - this.props.onInputChanged(newValue, 'value', newValue, isFinalChange); - } - } - - render() { - - let isVertical = this.props.widget.customProperties.orientation === WidgetSlider.OrientationTypes.VERTICAL.value; - let fields = { - name: this.props.widget.name, - control: <Slider - min={ this.props.widget.customProperties.rangeMin } - max={ this.props.widget.customProperties.rangeMax } - step={ this.props.widget.customProperties.step } - value={ this.state.value } - disabled={ this.props.editing } - vertical={ isVertical } - onChange={ (v) => this.valueIsChanging(v) } - onAfterChange={ (v) => this.valueChanged(v, true) - }/>, - value: <span className="signal-value">{ format('.2f')(Number.parseFloat(this.state.value)) }</span>, - unit: <span className="signal-unit">{ this.state.unit }</span> - } - - let widgetClasses = classNames({ - 'slider-widget': true, - 'full': true, - 'vertical': isVertical, - 'horizontal': !isVertical - }); - - return ( - <> - <div > - { fields.name } - { fields.value } - {this.props.widget.customProperties.showUnit && fields.unit} - </div> - <div className={widgetClasses}> - { fields.control } - </div> - </> - ); - } -} - -export default WidgetSlider; diff --git a/src/widget/widgets/slider.jsx b/src/widget/widgets/slider.jsx new file mode 100644 index 0000000..89d33fd --- /dev/null +++ b/src/widget/widgets/slider.jsx @@ -0,0 +1,148 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ + +import React, { useState, useEffect } from "react"; +import { format } from "d3"; +import classNames from "classnames"; +import Slider from "rc-slider"; +import "rc-slider/assets/index.css"; +import AppDispatcher from "../../common/app-dispatcher"; + +const WidgetSlider = (props) => { + const [value, setValue] = useState(""); + const [unit, setUnit] = useState(""); + + useEffect(() => { + let widget = { ...props.widget }; + widget.customProperties.simStartedSendValue = false; + AppDispatcher.dispatch({ + type: "widgets/start-edit", + token: props.token, + data: widget, + }); + }, [props.token, props.widget]); + + useEffect(() => { + // A simulation was started, make an update + if (props.widget.customProperties.simStartedSendValue) { + let widget = { ...props.widget }; + widget.customProperties.simStartedSendValue = false; + AppDispatcher.dispatch({ + type: "widgets/start-edit", + token: props.token, + data: widget, + }); + + // Send value without changing widget + props.onInputChanged(widget.customProperties.value, "", "", false); + } + }, [props.token, props.widget, props.onInputChanged]); + + useEffect(() => { + let newValue = ""; + let newUnit = ""; + + if ( + props.widget.customProperties.hasOwnProperty("value") && + props.widget.customProperties.value !== value + ) { + newValue = Number(props.widget.customProperties.value); + } else if (value === "") { + newValue = 0.0; + } + + if (props.widget.signalIDs.length > 0) { + let signalID = props.widget.signalIDs[0]; + let signal = props.signals.find((sig) => sig.id === signalID); + if (signal !== undefined) { + newUnit = signal.unit; + } + } + + if (newUnit) { + setUnit(newUnit); + } + if (newValue !== "") { + setValue(newValue); + } + }, [props.signals, props.widget, value]); + + const valueIsChanging = (newValue) => { + props.widget.customProperties.value = newValue; + if (props.widget.customProperties.continous_update) { + valueChanged(newValue, false); + } + setValue(newValue); + }; + + const valueChanged = (newValue, isFinalChange) => { + if (props.onInputChanged) { + props.onInputChanged(newValue, "value", newValue, isFinalChange); + } + }; + + let isVertical = + props.widget.customProperties.orientation === + WidgetSlider.OrientationTypes.VERTICAL.value; + + const fields = { + name: props.widget.name, + control: ( + <Slider + min={props.widget.customProperties.rangeMin} + max={props.widget.customProperties.rangeMax} + step={props.widget.customProperties.step} + value={value} + disabled={props.editing} + vertical={isVertical} + onChange={valueIsChanging} + onAfterChange={(v) => valueChanged(v, true)} + /> + ), + value: ( + <span className="signal-value"> + {format(".2f")(Number.parseFloat(value))} + </span> + ), + unit: <span className="signal-unit">{unit}</span>, + }; + + const widgetClasses = classNames({ + "slider-widget": true, + full: true, + vertical: isVertical, + horizontal: !isVertical, + }); + + return ( + <> + <div> + {fields.name} + {fields.value} + {props.widget.customProperties.showUnit && fields.unit} + </div> + <div className={widgetClasses}>{fields.control}</div> + </> + ); +}; + +WidgetSlider.OrientationTypes = { + HORIZONTAL: { value: 0, name: "Horizontal" }, + VERTICAL: { value: 1, name: "Vertical" }, +}; + +export default WidgetSlider; diff --git a/src/widget/widgets/table.js b/src/widget/widgets/table.js deleted file mode 100644 index 7e0f8d0..0000000 --- a/src/widget/widgets/table.js +++ /dev/null @@ -1,136 +0,0 @@ -/** - * This file is part of VILLASweb. - * - * VILLASweb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * VILLASweb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. - ******************************************************************************/ - -import React, { Component } from 'react'; -import { format } from 'd3'; - -import { Table, DataColumn } from '../../common/table'; - -class WidgetTable extends Component { - constructor(props) { - super(props); - - this.state = { - rows: [], - }; - } - - static getDerivedStateFromProps(props, state){ - - let rows = []; - let signalID, sig; - for (signalID of props.widget.signalIDs) { - for (sig of props.signals) { - if (signalID === sig.id) { - // sig is a selected signal, get data - // determine ID of infrastructure component related to signal (via config) - let icID = props.icIDs[sig.id] - - // distinguish between input and output signals - if (sig.direction === "out") { - if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) { - if (props.data[icID].output.values[sig.index] !== undefined) { - let data = props.data[icID].output.values[sig.index]; - rows.push({ - name: sig.name, - unit: sig.unit, - value: data[data.length - 1].y * sig.scalingFactor, - scalingFactor: sig.scalingFactor - }); - - } else { - // no data available - rows.push({ - name: sig.name, - unit: sig.unit, - value: NaN, - scalingFactor: sig.scalingFactor - }) - } - } - } else if (sig.direction === "in") { - if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) { - if (props.data[icID].input.values[sig.index] !== undefined) { - let data = props.data[icID].input.values[sig.index]; - rows.push({ - name: sig.name, - unit: sig.unit, - value: data[data.length - 1].y * sig.scalingFactor, - scalingFactor: sig.scalingFactor - }); - } else { - // no data available - rows.push({ - name: sig.name, - unit: sig.unit, - value: NaN, - scalingFactor: sig.scalingFactor - }) - } - } - } - } // sig is selected signal - } // loop over props.signals - } // loop over selected signals - - return {rows: rows} - - } - - render() { - - - let showScalingFactor; - if (this.props.widget.customProperties.showScalingFactor !== undefined){ // this line ensures backwards compatibility with older versions of VILLASweb - showScalingFactor = this.props.widget.customProperties.showScalingFactor; - } else { - showScalingFactor = true; - } - - let rows = this.state.rows; - - if(rows.length === 0){ - rows.push({ - name: "no entries" - }) - } - - let columns = [ - <DataColumn key={1} title="Signal" dataKey="name" width={120} />, - <DataColumn key={2} title="Value" dataKey="value" modifier={format('.4f')} />, - ]; - - let nextKey = 3; - if (showScalingFactor) { - columns.push(<DataColumn key={nextKey} title="Scale" dataKey="scalingFactor" modifier={format('.2f')}/>); - nextKey++; - } - if (this.props.widget.customProperties.showUnit) { - columns.push(<DataColumn key={nextKey} title="Unit" dataKey="unit"/>); - } - - return ( - <div className="table-widget" style={{width: this.props.widget.width, height: this.props.widget.height, overflowY: 'auto'}}> - <Table data={rows}> - { columns } - </Table> - </div> - ); - } -} - -export default WidgetTable; diff --git a/src/widget/widgets/table.jsx b/src/widget/widgets/table.jsx new file mode 100644 index 0000000..e87fce7 --- /dev/null +++ b/src/widget/widgets/table.jsx @@ -0,0 +1,111 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see <http://www.gnu.org/licenses/>. + ******************************************************************************/ +import React, { useState, useEffect } from "react"; +import { format } from "d3"; +import { Table, DataColumn } from "../../common/table"; + +const WidgetTable = (props) => { + const [rows, setRows] = useState([]); + + useEffect(() => { + let newRows = []; + let signalID, sig; + for (signalID of props.widget.signalIDs) { + for (sig of props.signals) { + if (signalID === sig.id) { + let icID = props.icIDs[sig.id]; + + let direction = sig.direction === "out" ? "output" : "input"; + if ( + props.data[icID] && + props.data[icID][direction] && + props.data[icID][direction].values + ) { + if (props.data[icID][direction].values[sig.index] !== undefined) { + let data = props.data[icID][direction].values[sig.index]; + newRows.push({ + name: sig.name, + unit: sig.unit, + value: data[data.length - 1].y * sig.scalingFactor, + scalingFactor: sig.scalingFactor, + }); + } else { + newRows.push({ + name: sig.name, + unit: sig.unit, + value: NaN, + scalingFactor: sig.scalingFactor, + }); + } + } + } + } + } + + if (newRows.length === 0) { + newRows.push({ name: "no entries" }); + } + + setRows(newRows); + }, [props.widget.signalIDs, props.signals, props.icIDs, props.data]); + + let showScalingFactor = + props.widget.customProperties.showScalingFactor !== undefined + ? props.widget.customProperties.showScalingFactor + : true; + + let columns = [ + <DataColumn key={1} title="Signal" dataKey="name" width={120} />, + <DataColumn + key={2} + title="Value" + dataKey="value" + modifier={format(".4f")} + />, + ]; + + let nextKey = 3; + if (showScalingFactor) { + columns.push( + <DataColumn + key={nextKey} + title="Scale" + dataKey="scalingFactor" + modifier={format(".2f")} + /> + ); + nextKey++; + } + if (props.widget.customProperties.showUnit) { + columns.push(<DataColumn key={nextKey} title="Unit" dataKey="unit" />); + } + + return ( + <div + className="table-widget" + style={{ + width: props.widget.width, + height: props.widget.height, + overflowY: "auto", + }} + > + <Table data={rows}>{columns}</Table> + </div> + ); +}; + +export default WidgetTable;