From 96184c76c968f06014bb32d5922c97e4c68133f2 Mon Sep 17 00:00:00 2001 From: "Claudio Maggioni (maggicl)" Date: Fri, 1 May 2020 16:42:42 +0200 Subject: [PATCH 1/2] Fixing automations and ui in general --- .../dashboard/AutomationCreationModal.js | 472 ++++++++++++++++++ .../components/dashboard/AutomationsPanel.js | 397 +-------------- .../src/components/dashboard/DevicePanel.js | 11 +- .../components/dashboard/devices/Device.js | 2 +- smart-hut/src/views/AutomationsNavbar.js | 165 ------ smart-hut/src/views/Dashboard.js | 82 +-- smart-hut/src/views/Navbar.js | 4 +- smart-hut/src/views/ScenesNavbar.js | 4 +- 8 files changed, 523 insertions(+), 614 deletions(-) create mode 100644 smart-hut/src/components/dashboard/AutomationCreationModal.js delete mode 100644 smart-hut/src/views/AutomationsNavbar.js diff --git a/smart-hut/src/components/dashboard/AutomationCreationModal.js b/smart-hut/src/components/dashboard/AutomationCreationModal.js new file mode 100644 index 0000000..4280263 --- /dev/null +++ b/smart-hut/src/components/dashboard/AutomationCreationModal.js @@ -0,0 +1,472 @@ +import React, { Component, useState } from "react"; +import { connect } from "react-redux"; +import { RemoteService } from "../../remote"; +import update from "immutability-helper"; +import "./Automations.css"; + +import { + Segment, + Grid, + Icon, + Header, + Input, + Button, + List, + Divider, + Menu, + Form, + Dropdown, + Checkbox, +} from "semantic-ui-react"; + +export const operands = [ + { key: "EQUAL", text: "=", value: "EQUAL" }, + { + key: "GREATER_EQUAL", + text: "\u2265", + value: "GREATER_EQUAL", + }, + { + key: "GREATER", + text: ">", + value: "GREATER", + }, + { + key: "LESS_EQUAL", + text: "\u2264", + value: "LESS_EQUAL", + }, + { + key: "LESS", + text: "<", + value: "LESS", + }, +]; + +const deviceStateOptions = [ + { key: "off", text: "off", value: false }, + { key: "on", text: "on", value: true }, +]; + +const CreateTrigger = (props) => { + const [activeOperand, setActiveOperand] = useState(true); + const admitedDevices = ["sensor", "regularLight", "dimmableLight"]; // TODO: Complete this list + const deviceList = props.devices + .map((device) => { + return { + key: device.id, + text: device.name, + value: device.id, + kind: device.kind, + }; + }) + .filter((e) => admitedDevices.includes(e.kind)); + + const onChange = (e, val) => { + props.inputChange(val); + if ( + props.devices.filter((d) => d.id === val.value)[0].hasOwnProperty("on") + ) { + setActiveOperand(false); + } else { + setActiveOperand(true); + } + }; + + return ( + + +
+ + + + + {activeOperand ? ( + + + props.inputChange(val)} + name="operand" + compact + selection + options={operands} + /> + + + props.inputChange(val)} + name="value" + type="number" + placeholder="Value" + /> + + + ) : ( + + props.inputChange(val)} + placeholder="State" + name="on" + compact + selection + options={deviceStateOptions} + /> + + )} + +
+
+
+ ); +}; + +const SceneItem = (props) => { + let position = props.order.indexOf(props.scene.id); + return ( + + + + + + + props.orderScenes(props.scene.id, val.checked) + } + checked={position + 1 > 0} + /> + + +

{props.scene.name}

+
+ +

{position !== -1 ? "# " + (position + 1) : ""}

+
+
+
+
+
+ ); +}; + +const Trigger = ({ deviceName, trigger, onRemove, index }) => { + const { operand, value, on } = trigger; + let symbol; + if (operand) { + symbol = operands.filter((opt) => opt.key === operand)[0].text; + } + return ( + + + {deviceName} + {operand ? {symbol} : ""} + {operand ? value : on ? "on" : "off"} + + onRemove(index)} + className="remove-icon" + name="remove" + /> + + ); +}; + +class AutomationSaveModal extends Component { + constructor(props) { + super(props); + this.props = props; + this.state = { + triggerList: [], + order: [], + automationName: "New Automation", + editName: false, + newTrigger: {}, + scenesFilter: null, + }; + + this.setTrigger = this._setter("triggerList"); + this.setOrder = this._setter("order"); + this.setautomationName = this._setter("automationName"); + this.setEditName = this._setter("editName"); + this.setNewTrigger = this._setter("newTrigger"); + + this.addTrigger = this.addTrigger.bind(this); + this.removeTrigger = this.removeTrigger.bind(this); + this.onInputChange = this.onInputChange.bind(this); + this.searchScenes = this.searchScenes.bind(this); + this.orderScenes = this.orderScenes.bind(this); + } + + get deviceList() { + return Object.values(this.props.devices); + } + + _setter(property) { + return (value) => + this.setState(update(this.state, { [property]: { $set: value } })); + } + + triggerKind(trigger) { + if ("on" in trigger) { + return "BooleanTrigger"; + } else if ("operand" in trigger && "value" in trigger) { + return "RangeTrigger"; + } else { + throw new Error("Trigger kind not handled"); + } + } + + _checkNewTrigger(trigger) { + const auxDevice = this.props.devices[trigger.device]; + + // Check for missing fields for creation + const error = { + result: false, + message: "There are missing fields!", + }; + + switch (this.triggerKind(trigger)) { + case "BooleanTrigger": + if (!trigger.device || !trigger.on) return error; + break; + case "RangeTrigger": + if (!trigger.device || !trigger.operand || !trigger.value) return error; + break; + } + + const isNotDuplicate = !this.state.triggerList.some( + (t) => t.device === trigger.device && t.operand === trigger.operand + ); + + return { + result: isNotDuplicate, + message: isNotDuplicate + ? null + : "You have already created a trigger for this device with the same conditions", + }; + } + + addTrigger() { + const { result, message } = this._checkNewTrigger(this.state.newTrigger); + + if (result) { + this.setState( + update(this.state, { triggerList: { $push: [this.state.newTrigger] } }) + ); + } else { + alert(message); + } + } + + removeTrigger(index) { + this.setState( + update(this.state, { triggerList: { $splice: [[index, 1]] } }) + ); + } + + // This gets triggered when the devices dropdown changes the value. + onInputChange(val) { + this.setNewTrigger({ ...this.state.newTrigger, [val.name]: val.value }); + } + + onChangeName(_, val) { + this.setautomationName(val.value); + } + + orderScenes = (id, checked) => { + if (checked) { + this.setState(update(this.state, { order: { $push: [id] } })); + } else { + this.setState( + update(this.state, { + order: (prevList) => prevList.filter((e) => e !== id), + }) + ); + } + }; + + searchScenes(_, { value }) { + this.setState(update(this.state, { scenesFilter: { $set: value } })); + this.forceUpdate(); + } + + get sceneList() { + if (!this.scenesFilter) { + return this.props.scenes; + } else { + return this.props.scenes.filter((e) => + e.name.includes(this.scenesFilter) + ); + } + } + + _generateKey = (trigger) => { + switch (this.triggerKind(trigger)) { + case "BooleanTrigger": + return trigger.device + trigger.value; + case "RangeTrigger": + return trigger.device + trigger.operand + trigger.value; + } + }; + + checkBeforeSave = () => { + if (!this.state.automationName) { + alert("Give a name to the automation"); + return false; + } + if (this.state.triggerList.length <= 0) { + alert("You have to create a trigger"); + return false; + } + if (this.state.order.length <= 0) { + alert("You need at least one active scene"); + return false; + } + return true; + }; + + saveAutomation = () => { + if (this.checkBeforeSave()) { + const automation = { + name: this.state.automationName, + }; + this.props.save({ + automation, + triggerList: this.state.triggerList, + order: this.state.order, + }); + } + }; + + render() { + return ( + +
+ {this.state.editName ? ( + + ) : ( + this.state.automationName + )} +
+ + +
+ )} + + + + + + + + + + + + + ); + } +} + +const mapStateToProps = (state, _) => ({ + activeRoom: state.active.activeRoom, + activeTab: state.active.activeTab, + get scenes() { + return Object.values(state.scenes); + }, + devices: state.devices, + get automations() { + return Object.values(state.automations); + }, +}); +const AutomationSaveModalContainer = connect( + mapStateToProps, + RemoteService +)(AutomationSaveModal); +export default AutomationSaveModalContainer; diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 2a7b683..0119b62 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -6,402 +6,13 @@ import "./Automations.css"; import { Segment, Grid, - Icon, Header, - Input, Button, List, - Dropdown, - Form, Divider, - Checkbox, Menu, } from "semantic-ui-react"; - -const operands = [ - { key: "EQUAL", text: "=", value: "EQUAL" }, - { - key: "GREATER_EQUAL", - text: "\u2265", - value: "GREATER_EQUAL", - }, - { - key: "GREATER", - text: ">", - value: "GREATER", - }, - { - key: "LESS_EQUAL", - text: "\u2264", - value: "LESS_EQUAL", - }, - { - key: "LESS", - text: "<", - value: "LESS", - }, -]; - -const deviceStateOptions = [ - { key: "off", text: "off", value: false }, - { key: "on", text: "on", value: true }, -]; - -const CreateTrigger = (props) => { - const [activeOperand, setActiveOperand] = useState(true); - const admitedDevices = ["sensor", "regularLight", "dimmableLight"]; // TODO Complete this list - const deviceList = props.devices - .map((device) => { - return { - key: device.id, - text: device.name, - value: device.id, - kind: device.kind, - }; - }) - .filter((e) => admitedDevices.includes(e.kind)); - - const onChange = (e, val) => { - props.inputChange(val); - if ( - props.devices.filter((d) => d.id === val.value)[0].hasOwnProperty("on") - ) { - setActiveOperand(false); - } else { - setActiveOperand(true); - } - }; - - return ( - - -
- - - - - {activeOperand ? ( - - - props.inputChange(val)} - name="operand" - compact - selection - options={operands} - /> - - - props.inputChange(val)} - name="value" - type="number" - placeholder="Value" - /> - - - ) : ( - - props.inputChange(val)} - placeholder="State" - name="value" - compact - selection - options={deviceStateOptions} - /> - - )} - -
-
-
- ); -}; - -const SceneItem = (props) => { - let position = props.order.indexOf(props.scene.id); - return ( - - - - - - - props.orderScenes(props.scene.id, val.checked) - } - checked={position + 1 > 0} - /> - - -

{props.scene.name}

-
- -

{position !== -1 ? "# " + (position + 1) : ""}

-
-
-
-
-
- ); -}; - -const Trigger = ({ deviceName, trigger, onRemove, index }) => { - const { operand, value } = trigger; - let symbol; - if (operand) { - symbol = operands.filter((opt) => opt.key === operand)[0].text; - } - return ( - - - {deviceName} - {operand ? {symbol} : ""} - - {operand ? value : value ? "on" : "off"} - - - onRemove(index)} - className="remove-icon" - name="remove" - /> - - ); -}; - -export const CreateAutomation = (props) => { - const [triggerList, setTrigger] = useState([]); - const [order, setOrder] = useState([]); - const [stateScenes, setScenes] = useState(props.scenes); - const [automationName, setautomationName] = useState("New Automation"); - const [editName, setEditName] = useState(false); - const [newTrigger, setNewTrigger] = useState({}); - - useEffect(() => { - setScenes(props.scenes); - }, [props]); - - const _checkNewTrigger = (trigger) => { - const auxDevice = props.devices.filter((d) => d.id === trigger.device)[0]; - if (auxDevice && auxDevice.hasOwnProperty("on")) { - if (!trigger.device || !trigger.value == null) { - return { - result: false, - message: "There are missing fields!", - }; - } - } else { - if (!trigger.device || !trigger.operand || !trigger.value) { - return { - result: false, - message: "There are missing fields", - }; - } - } - const result = !triggerList.some( - (t) => t.device === trigger.device && t.operand === trigger.operand - ); - return { - result: result, - message: result - ? "" - : "You have already created a trigger for this device with the same conditions", - }; - }; - const addTrigger = () => { - const { result, message } = _checkNewTrigger(newTrigger); - const auxTrigger = newTrigger; - if (result) { - if ( - props.devices - .filter((d) => d.id === newTrigger.device)[0] - .hasOwnProperty("on") - ) { - delete auxTrigger.operand; - } - setTrigger((prevList) => [...prevList, auxTrigger]); - } else { - alert(message); - } - }; - - const removeTrigger = (index) => { - setTrigger((prevList) => prevList.filter((t, i) => i !== index)); - }; - - // This gets triggered when the devices dropdown changes the value. - const onInputChange = (val) => { - setNewTrigger({ ...newTrigger, [val.name]: val.value }); - }; - const onChangeName = (e, val) => setautomationName(val.value); - - const orderScenes = (id, checked) => { - if (checked) { - setOrder((prevList) => [...prevList, id]); - } else { - setOrder((prevList) => prevList.filter((e) => e !== id)); - } - }; - const searchScenes = (e, { value }) => { - if (value.length > 0) { - setScenes((prevScenes) => { - return stateScenes.filter((e) => { - return e.name.includes(value); - }); - }); - } else { - setScenes(props.scenes); - } - }; - - const _generateKey = (trigger) => { - if (trigger.hasOwnProperty("operand")) { - return trigger.device + trigger.operand + trigger.value; - } - return trigger.device + trigger.value; - }; - - /*const checkBeforeSave = () => { - if (automationName.length <= 0) { - alert("Give a name to the automation"); - return false; - } - if (triggerList.length <= 0) { - alert("You have to create a trigger"); - return false; - } - if (order.length <= 0) { - alert("You need at least one active scene"); - return false; - } - return true; - };*/ - - const saveAutomation = () => { - //if(checkBeforeSave()){ - const automation = { - name: automationName, - }; - props.save({ automation, triggerList, order }); - //} - }; - - return ( - -
- {editName ? ( - - ) : ( - automationName - )} -
- - -
- )} - - - - - - - - - - - - - ); -}; +import CreateAutomation, { operands } from "./AutomationCreationModal"; const Automation = ({ automation, devices, scenes, removeAutomation }) => { const { triggers } = automation; @@ -518,11 +129,7 @@ class AutomationsPanel extends Component { {!this.state.openModal ? ( - + ) : ( diff --git a/smart-hut/src/components/dashboard/DevicePanel.js b/smart-hut/src/components/dashboard/DevicePanel.js index 70f34f5..6923bcb 100644 --- a/smart-hut/src/components/dashboard/DevicePanel.js +++ b/smart-hut/src/components/dashboard/DevicePanel.js @@ -1,7 +1,7 @@ // vim: set ts=2 sw=2 et tw=80: import React, { Component } from "react"; -import { Grid } from "semantic-ui-react"; +import { Grid, Card } from "semantic-ui-react"; import Device from "./devices/Device"; import NewDevice from "./devices/NewDevice"; import { connect } from "react-redux"; @@ -24,12 +24,7 @@ class DevicePanel extends Component { render() { return ( - + {this.props.devices.map((e, i) => { return ( @@ -42,7 +37,7 @@ class DevicePanel extends Component { ) : null} - + ); } } diff --git a/smart-hut/src/components/dashboard/devices/Device.js b/smart-hut/src/components/dashboard/devices/Device.js index 954c67e..a1af1ee 100644 --- a/smart-hut/src/components/dashboard/devices/Device.js +++ b/smart-hut/src/components/dashboard/devices/Device.js @@ -159,7 +159,7 @@ class Device extends React.Component { render() { { return ( - +
{this.deviceName}
diff --git a/smart-hut/src/views/AutomationsNavbar.js b/smart-hut/src/views/AutomationsNavbar.js deleted file mode 100644 index 3378a90..0000000 --- a/smart-hut/src/views/AutomationsNavbar.js +++ /dev/null @@ -1,165 +0,0 @@ -import React, { Component } from "react"; -import { - Menu, - Button, - Grid, - Icon, - Responsive, - Dropdown, -} from "semantic-ui-react"; -import { editButtonStyle } from "../components/dashboard/devices/styleComponents"; -import AutomationModal from "../components/AutomationModal"; -import { RemoteService } from "../remote"; -import { connect } from "react-redux"; -import { appActions } from "../storeActions"; - -class AutomationsNavbar extends Component { - constructor(props) { - super(props); - this.state = { - editMode: false, - }; - - this.toggleEditMode = this.toggleEditMode.bind(this); - this.openCurrentModalMobile = this.openCurrentModalMobile.bind(this); - } - - get activeItemAutomation() { - return this.props.activeAutomation; - } - - set activeItemAutomation(item) { - this.props.setActiveAutomation(item); - } - - get activeItemAutomationsName() { - if (this.props.activeAutomation === -1) return "Home"; - return this.props.automations[this.props.activeAutomation].name; - } - - openCurrentModalMobile() { - console.log(this.activeItemAutomation, this.props.automationsModalRefs); - const currentModal = this.props.automationsModalRefs[ - this.activeItemAutomation - ].current; - currentModal.openModal(); - } - - toggleEditMode(e) { - this.setState((prevState) => ({ editMode: !prevState.editMode })); - } - - render() { - return ( -
- - - - - - - - - - - - - - AUTOMATIONS - - - - { - //INSERT LIST OF AUTOMATIONS HERE - } - - - - - - - - - - - - - - - - - - - - - - - Automations - - - - - { - //INSERT LIST OF AUTOMATIONS HERE - } - - - - - - - - - {this.activeItemAutomation !== -1 ? ( - - - - ) : null} - - - -
- ); - } -} - -const setActiveAutomation = (activeAutomation) => { - return (dispatch) => - dispatch(appActions.setActiveAutomation(activeAutomation)); -}; - -const mapStateToProps = (state, _) => ({ - automations: state.automations, - activeAutomation: state.active.activeAutomation, - automationModalRefs: Object.keys(state.automations).reduce( - (acc, key) => ({ ...acc, [key]: React.createRef() }), - {} - ), -}); -const AutomationsNavbarContainer = connect(mapStateToProps, { - ...RemoteService, - setActiveAutomation, -})(AutomationsNavbar); -export default AutomationsNavbarContainer; diff --git a/smart-hut/src/views/Dashboard.js b/smart-hut/src/views/Dashboard.js index 1985b23..8ee84c7 100644 --- a/smart-hut/src/views/Dashboard.js +++ b/smart-hut/src/views/Dashboard.js @@ -4,9 +4,8 @@ import ScenesPanel from "../components/dashboard/ScenesPanel"; import AutomationsPanel from "../components/dashboard/AutomationsPanel"; import Navbar from "./Navbar"; import ScenesNavbar from "./ScenesNavbar"; -import AutomationsNavbar from "./AutomationsNavbar"; import MyHeader from "../components/HeaderController"; -import { Grid, Responsive, Button } from "semantic-ui-react"; +import { Grid, Responsive, Button, Menu } from "semantic-ui-react"; import { panelStyle, mobilePanelStyle, @@ -67,13 +66,15 @@ class Dashboard extends Component { return ; case "Scenes": return ; - case "Automations": - return ; default: return

ERROR

; } } + get hasNavbar() { + return this.state.activeTab !== "Automations"; + } + render() { return (
@@ -85,40 +86,37 @@ class Dashboard extends Component { - - - + ); + } + render() { return ( - -
- {this.state.editName ? ( - - ) : ( - this.state.automationName - )} -
+ + +
+ {this.state.editName ? ( + + ) : ( + this.state.automationName + )} +
- -
- )} + + + + )} +
+
+ + + + + + - - - - - - - - + ); } } -const mapStateToProps = (state, _) => ({ - activeRoom: state.active.activeRoom, - activeTab: state.active.activeTab, - get scenes() { - return Object.values(state.scenes); - }, +const mapStateToProps = (state, ownProps) => ({ + scenes: Object.values(state.scenes), devices: state.devices, - get automations() { - return Object.values(state.automations); - }, + automation: ownProps.id ? state.automations[ownProps.id] : null, }); const AutomationSaveModalContainer = connect( mapStateToProps, diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 0119b62..d3eae66 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -25,12 +25,7 @@ const Automation = ({ automation, devices, scenes, removeAutomation }) => {
{automation.name}
- @@ -138,7 +133,6 @@ class AutomationsPanel extends Component { {this.props.automations.map((automation, i) => { - console.log(23, automation, i, this.props.automations); return ( + {this.props.devices.map((e, i) => { - return ( - - - - ); + return ; })} {!this.props.isActiveRoomHome ? ( - - - + + + + + ) : null} ); diff --git a/smart-hut/src/components/dashboard/devices/NewDevice.js b/smart-hut/src/components/dashboard/devices/NewDevice.js index 2120ba0..84a5b8c 100644 --- a/smart-hut/src/components/dashboard/devices/NewDevice.js +++ b/smart-hut/src/components/dashboard/devices/NewDevice.js @@ -341,7 +341,14 @@ class NewDevice extends Component { open={this.state.openModal} onClose={this.resetState} trigger={ - + } diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index c238c56..df6ed0e 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -315,36 +315,7 @@ export const RemoteService = { fetchAutomations: () => { return (dispatch) => { return Endpoint.get("/automation/") - .then((res) => { - const length = res.data.length; - const automations = []; - - res.data.forEach((a, index) => { - const { id, name } = a; - const automation = { - name, - id, - triggers: [], - scenes: [], - }; - - return Endpoint.get(`/booleanTrigger/${id}`).then((res) => { - automation.triggers.push(...res.data); - return Endpoint.get(`/rangeTrigger/${id}`).then((res) => { - automation.triggers.push(...res.data); - return Endpoint.get(`/scenePriority/${id}`).then((res) => { - automation.scenes.push(...res.data); - automations.push(automation); - if (index + 1 === length) { - return void dispatch( - actions.automationsUpdate(automations) - ); - } - }); - }); - }); - }); - }) + .then((res) => void dispatch(actions.automationsUpdate(res.data))) .catch((err) => { console.error(`Fetch automations error`, err); throw new RemoteError(["Network error"]); @@ -502,11 +473,22 @@ export const RemoteService = { }; }, + fastUpdateAutomation: (automation) => { + return (dispatch) => { + return Endpoint.put("/automation/fast", {}, automation) + .then((res) => dispatch(actions.automationSave(res.data))) + .catch((err) => { + console.warn("Update automation: ", automation, "error: ", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + /** * Creates/Updates an automation with the given data. If * data.id is truthy, then a update call is performed, * otherwise a create call is performed. - * @param {Automation} data the device to update. + * @param {Automation} data the automation to update. * @returns {Promise} promise that resolves to the saved device and rejects * with user-fiendly errors as a RemoteError */ diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 002c0db..fce1897 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -257,7 +257,6 @@ function reducer(previousState, action) { break; case "AUTOMATION_UPDATE": - newState = previousState; const automations = {}; for (const automation of action.automations) { automations[automation.id] = automation;