From 48c0988063bc6bf7a3550c17e16fb716ab83052a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Cape=C3=A1ns=20P=C3=A9rez?= Date: Fri, 24 Apr 2020 12:26:55 +0200 Subject: [PATCH 1/6] Version 1 for the automations view done --- .../src/components/dashboard/Automations.css | 7 + .../components/dashboard/AutomationsPanel.js | 266 +++++++++++++- smart-hut/src/views/Dashboard.js | 346 ++++++++++-------- 3 files changed, 448 insertions(+), 171 deletions(-) create mode 100644 smart-hut/src/components/dashboard/Automations.css diff --git a/smart-hut/src/components/dashboard/Automations.css b/smart-hut/src/components/dashboard/Automations.css new file mode 100644 index 0000000..69299e3 --- /dev/null +++ b/smart-hut/src/components/dashboard/Automations.css @@ -0,0 +1,7 @@ +.segment-automations { + top: 10%; +} + +.list-index { + font-size: 1.5rem; +} diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 4fbf809..297a5f0 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -1,23 +1,267 @@ -import React, { Component } from "react"; +import React, { Component, useState } from "react"; import { connect } from "react-redux"; import { RemoteService } from "../../remote"; +import "./Automations.css"; +import { + Segment, + Grid, + Icon, + Header, + Input, + Button, + List, + Dropdown, + Form, + Divider, + Checkbox, + Menu, + Label, +} from "semantic-ui-react"; + +const options = [ + { key: "equal", text: "=", value: "equal" }, + { + key: "greater-than-or-equal", + text: "\u2265", + value: "greater-than-or-equal", + }, + { + key: "greater-than", + text: ">", + value: "greater-than", + }, + { + key: "less-than-or-equal", + text: "\u2264", + value: "less-than-or-equal", + }, + { + key: "less-than", + text: "<", + value: "less-than", + }, +]; + +const CreateTrigger = (props) => { + const devices = [ + { key: "Light-1", text: "Light-1", value: "Light-1" }, + { key: "Light-2", text: "Light-2", value: "Light-2" }, + { key: "SmartPlug-1", text: "SmartPlug-1", value: "SmartPlug-1" }, + { key: "SmartPlug-2", text: "SmartPlug-2", value: "SmartPlug-2" }, + ]; + + return ( + + +
+ + + props.inputChange(val)} + name="device" + search + selection + options={devices} + placeholder="Device" + /> + + + + props.inputChange(val)} + name="operand" + compact + selection + options={options} + /> + + + props.inputChange(val)} + name="value" + type="number" + placeholder="Value" + /> + + +
+
+
+ ); +}; + +const SceneItem = (props) => { + return ( + + + + + + + + +

{props.sceneName}

+
+
+
+
+
+ ); +}; + +const Trigger = ({ trigger }) => { + console.log(trigger); + const { device, operand, value } = trigger; + const symbol = options.filter((opt) => opt.key === operand)[0].text; + return ( + + + {device} + {symbol} + {value} + + + ); +}; + +const Automation = (props) => { + const [triggerList, setTrigger] = useState([]); + const [newTrigger, setNewTrigger] = useState({ + device: null, + operand: null, + value: null, + }); + const scenes = ["scene-1", "scene-2", "scene-3"]; + const automationName = "Automation Name"; + + const checkNewTrigger = (trigger) => { + 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); + if (result) { + setTrigger((prevList) => [...prevList, newTrigger]); + } else { + alert(message); + } + }; + + const onInputChange = (val) => { + setNewTrigger({ ...newTrigger, [val.name]: val.value }); + }; + + return ( + +
+ {automationName} +
+ + +
+ )} + + + + + + + + + + + + + + + + ); +}; class AutomationsPanel extends Component { - constructor(props) { - super(props); - } + constructor(props) { + super(props); + this.state = {}; + } - render() { - return

AUTOMATIONS

; - } + render() { + return ( + + + + ); + } } const mapStateToProps = (state, _) => ({ - activeRoom: state.active.activeRoom, - activeTab: state.active.activeTab, + activeRoom: state.active.activeRoom, + activeTab: state.active.activeTab, }); const AutomationsPanelContainer = connect( - mapStateToProps, - RemoteService + mapStateToProps, + RemoteService )(AutomationsPanel); export default AutomationsPanelContainer; diff --git a/smart-hut/src/views/Dashboard.js b/smart-hut/src/views/Dashboard.js index 651a1e5..af0a3fa 100644 --- a/smart-hut/src/views/Dashboard.js +++ b/smart-hut/src/views/Dashboard.js @@ -8,8 +8,8 @@ import AutomationsNavbar from "./AutomationsNavbar"; import MyHeader from "../components/HeaderController"; import { Grid, Responsive, Button } from "semantic-ui-react"; import { - panelStyle, - mobilePanelStyle, + panelStyle, + mobilePanelStyle, } from "../components/dashboard/devices/styleComponents"; import { RemoteService } from "../remote"; @@ -17,177 +17,203 @@ import { connect } from "react-redux"; import { appActions } from "../storeActions"; class Dashboard extends Component { - constructor(props) { - super(props); - this.state = this.initialState; - this.setInitialState(); - - this.selectTab = this.selectTab.bind(this); - } - - get initialState() { - return { - activeTab: this.activeTab, - }; - } - - setInitialState() { - this.setState(this.initialState); - } - - get activeTab() { - return this.props.activeTab; - } - - set activeTab(tab) { - this.props.setActiveTab(tab); - } - - selectTab(e, { name }) { - this.setState({ activeTab: name }); - this.activeTab = name; - } - - renderTab(tab) { - switch (tab) { - case "Devices": - return ; - case "Scenes": - return ; - case "Automations": - return ; - default: - return

ERROR

; + constructor(props) { + super(props); + this.state = this.initialState; + this.setInitialState(); + this.activeTab = "Automations"; //TODO Remove this to not put automations first + this.selectTab = this.selectTab.bind(this); } - } - renderNavbar(tab) { - switch (tab) { - case "Devices": - return ; - case "Scenes": - return ; - case "Automations": - return ; - default: - return

ERROR

; + get initialState() { + return { + activeTab: this.activeTab, + }; } - } - render() { - return ( -
- - - - - - - - - - -
- ); - } + get activeTab() { + return this.props.activeTab; + } + + set activeTab(tab) { + this.props.setActiveTab(tab); + } + + selectTab(e, { name }) { + this.setState({ activeTab: name }); + this.activeTab = name; + } + + renderTab(tab) { + switch (tab) { + case "Devices": + return ; + case "Scenes": + return ; + case "Automations": + return ; + default: + return

ERROR

; + } + } + + renderNavbar(tab) { + switch (tab) { + case "Devices": + return ; + case "Scenes": + return ; + case "Automations": + return ; + default: + return

ERROR

; + } + } + + render() { + return ( +
+ + + + + + + + + + +
+ ); + } } const mapStateToProps = (state, _) => ({ - activeTab: state.active.activeTab, + activeTab: state.active.activeTab, }); const setActiveTab = (activeTab) => { - return (dispatch) => dispatch(appActions.setActiveTab(activeTab)); + return (dispatch) => dispatch(appActions.setActiveTab(activeTab)); }; const DashboardContainer = connect(mapStateToProps, { - ...RemoteService, - setActiveTab, + ...RemoteService, + setActiveTab, })(Dashboard); export default DashboardContainer; From cc23824a378650932ba40d774aa946191d8dc606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Cape=C3=A1ns=20P=C3=A9rez?= Date: Mon, 27 Apr 2020 17:00:46 +0200 Subject: [PATCH 2/6] Automations basic stuff is working: create, get --- .../src/components/dashboard/Automations.css | 11 + .../components/dashboard/AutomationsPanel.js | 451 ++++-- .../src/components/dashboard/DevicePanel.js | 90 +- smart-hut/src/remote.js | 1396 +++++++++-------- smart-hut/src/store.js | 19 +- smart-hut/src/storeActions.js | 18 + 6 files changed, 1221 insertions(+), 764 deletions(-) diff --git a/smart-hut/src/components/dashboard/Automations.css b/smart-hut/src/components/dashboard/Automations.css index 69299e3..0042cba 100644 --- a/smart-hut/src/components/dashboard/Automations.css +++ b/smart-hut/src/components/dashboard/Automations.css @@ -5,3 +5,14 @@ .list-index { font-size: 1.5rem; } + +.remove-icon { + display: inline !important; + margin-left: 1rem !important; +} + +.trigger-item { + display: flex !important; + justify-content: center !important; + align-items: center !important; +} diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 297a5f0..4e8ca37 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -1,7 +1,8 @@ -import React, { Component, useState } from "react"; -import { connect } from "react-redux"; -import { RemoteService } from "../../remote"; +import React, {Component, useState, useEffect} from "react"; +import {connect} from "react-redux"; +import {RemoteService} from "../../remote"; import "./Automations.css"; + import { Segment, Grid, @@ -15,40 +16,64 @@ import { Divider, Checkbox, Menu, - Label, + Label } from "semantic-ui-react"; -const options = [ - { key: "equal", text: "=", value: "equal" }, +const operands = [ + {key: "EQUAL", text: "=", value: "EQUAL"}, { - key: "greater-than-or-equal", + key: "GREATER_EQUAL", text: "\u2265", - value: "greater-than-or-equal", + value: "GREATER_EQUAL", }, { - key: "greater-than", + key: "GREATER", text: ">", - value: "greater-than", + value: "GREATER", }, { - key: "less-than-or-equal", + key: "LESS_EQUAL", text: "\u2264", - value: "less-than-or-equal", + value: "LESS_EQUAL", }, { - key: "less-than", + key: "LESS", text: "<", - value: "less-than", + value: "LESS", }, ]; +const deviceStateOptions = [ + {key: "off", text: "off", value: false}, + {key: "on", text: "on", value: true}, +]; + const CreateTrigger = (props) => { - const devices = [ - { key: "Light-1", text: "Light-1", value: "Light-1" }, - { key: "Light-2", text: "Light-2", value: "Light-2" }, - { key: "SmartPlug-1", text: "SmartPlug-1", value: "SmartPlug-1" }, - { key: "SmartPlug-2", text: "SmartPlug-2", value: "SmartPlug-2" }, - ]; + 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 ( @@ -57,32 +82,52 @@ const CreateTrigger = (props) => { props.inputChange(val)} + onChange={onChange} name="device" search selection - options={devices} + options={deviceList} placeholder="Device" /> - - - props.inputChange(val)} - name="operand" - compact - selection - options={options} - /> - - - props.inputChange(val)} - name="value" - type="number" - placeholder="Value" - /> - + {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} + /> + + )} @@ -91,16 +136,31 @@ const CreateTrigger = (props) => { }; const SceneItem = (props) => { + let position = props.order.indexOf(props.scene.id); return ( - + + props.orderScenes( + props.scene.id, + val.checked + ) + } + checked={position + 1 > 0} + /> -

{props.sceneName}

+

{props.scene.name}

+
+ +

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

@@ -109,37 +169,59 @@ const SceneItem = (props) => { ); }; -const Trigger = ({ trigger }) => { - console.log(trigger); - const { device, operand, value } = trigger; - const symbol = options.filter((opt) => opt.key === operand)[0].text; +const Trigger = ({deviceName, trigger, onRemove, index}) => { + const {device, operand, value} = trigger; + let symbol; + if (operand) { + symbol = operands.filter((opt) => opt.key === operand)[0].text; + } return ( - + - {device} - {symbol} - {value} + {deviceName} + {operand ? {symbol} : ""} + {value ? "on" : "off"} + onRemove(device, index)} + className="remove-icon" + name="remove" + /> ); }; -const Automation = (props) => { +const CreateAutomation = (props) => { const [triggerList, setTrigger] = useState([]); - const [newTrigger, setNewTrigger] = useState({ - device: null, - operand: null, - value: null, - }); - const scenes = ["scene-1", "scene-2", "scene-3"]; - const automationName = "Automation Name"; + 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({}); - const checkNewTrigger = (trigger) => { - if (!trigger.device || !trigger.operand || !trigger.value) { - return { - result: false, - message: "There are missing fields", - }; + 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 @@ -152,43 +234,135 @@ const Automation = (props) => { }; }; const addTrigger = () => { - const { result, message } = checkNewTrigger(newTrigger); + const {result, message} = _checkNewTrigger(newTrigger); + const auxTrigger = newTrigger; if (result) { - setTrigger((prevList) => [...prevList, newTrigger]); + if ( + props.devices + .filter((d) => d.id === newTrigger.device)[0] + .hasOwnProperty("on") + ) { + delete auxTrigger.operand; + } + setTrigger((prevList) => [...prevList, auxTrigger]); } else { alert(message); } }; - const onInputChange = (val) => { - setNewTrigger({ ...newTrigger, [val.name]: val.value }); + const removeTrigger = (trigger) => { + // TODO When this is connected to the backend each trigger will have an id which can be used to remove it }; + // 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) => { + console.log(e.name.includes(value)); + 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 = () => { + console.log("trigger list: ", triggerList); + //if(checkBeforeSave()){ + const automation = { + name: automationName + } + props.save({automation, triggerList, order}); + //} + } + return ( -
- {automationName} +
+ {editName ? ( + + ) : ( + automationName + )}
@@ -229,11 +408,11 @@ const Automation = (props) => { - - + + - + @@ -241,17 +420,99 @@ const Automation = (props) => { ); }; +const Automation = ({automation, devices}) => { + const {triggers, scenes} = automation; + const getOperator = (operand) => operands.filter(o => o.key == operand)[0].text; + + return ( + +
+ {automation.name} +
+ - + */} ); } diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 4e8ca37..c730332 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -16,7 +16,7 @@ import { Divider, Checkbox, Menu, - Label + Label, } from "semantic-ui-react"; const operands = [ @@ -154,7 +154,7 @@ const SceneItem = (props) => { checked={position + 1 > 0} /> - +

{props.scene.name}

@@ -180,11 +180,13 @@ const Trigger = ({deviceName, trigger, onRemove, index}) => { {deviceName} {operand ? {symbol} : ""} - {value ? "on" : "off"} + + {operand ? value : value ? "on" : "off"} + onRemove(device, index)} + onClick={() => onRemove(index)} className="remove-icon" name="remove" /> @@ -192,7 +194,7 @@ const Trigger = ({deviceName, trigger, onRemove, index}) => { ); }; -const CreateAutomation = (props) => { +export const CreateAutomation = (props) => { const [triggerList, setTrigger] = useState([]); const [order, setOrder] = useState([]); const [stateScenes, setScenes] = useState(props.scenes); @@ -250,8 +252,8 @@ const CreateAutomation = (props) => { } }; - const removeTrigger = (trigger) => { - // TODO When this is connected to the backend each trigger will have an id which can be used to remove it + const removeTrigger = (index) => { + setTrigger((prevList) => prevList.filter((t, i) => i !== index)); }; // This gets triggered when the devices dropdown changes the value. @@ -271,7 +273,6 @@ const CreateAutomation = (props) => { if (value.length > 0) { setScenes((prevScenes) => { return stateScenes.filter((e) => { - console.log(e.name.includes(value)); return e.name.includes(value); }); }); @@ -301,17 +302,16 @@ const CreateAutomation = (props) => { return false; } return true; - } + }; const saveAutomation = () => { - console.log("trigger list: ", triggerList); //if(checkBeforeSave()){ const automation = { - name: automationName - } + name: automationName, + }; props.save({automation, triggerList, order}); //} - } + }; return ( @@ -409,10 +409,9 @@ const CreateAutomation = (props) => { - - - - + @@ -420,9 +419,12 @@ const CreateAutomation = (props) => { ); }; -const Automation = ({automation, devices}) => { - const {triggers, scenes} = automation; - const getOperator = (operand) => operands.filter(o => o.key == operand)[0].text; +const Automation = ({automation, devices, scenes, removeAutomation}) => { + const {triggers} = automation; + const scenePriorities = automation.scenes; + const getOperator = (operand) => + operands.filter((o) => o.key == operand)[0].text; + return ( @@ -435,6 +437,14 @@ const Automation = ({automation, devices}) => { size="small" icon={"edit"} /> + + )} + - {this.props.automations.map((automation) => { + {this.props.automations.map((automation, i) => { return ( - - + + ); })} @@ -527,9 +587,8 @@ const mapStateToProps = (state, _) => ({ return Object.values(state.devices); }, get automations() { - console.log(Object.values(state.automations)); return Object.values(state.automations); - } + }, }); const AutomationsPanelContainer = connect( mapStateToProps, diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index 6891b88..a3c6b8c 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -348,7 +348,6 @@ export const RemoteService = { if ((index + 1) === length) { return void dispatch(actions.automationsUpdate(automations)); } - console.log("automation after trigger: ", automation); }) }) @@ -479,6 +478,7 @@ export const RemoteService = { */ saveAutomation: (data) => { const {automation, triggerList, order} = data; + console.log("automation: ", automation, triggerList, order); automation.triggers = []; automation.scenes = []; return (dispatch) => { @@ -525,8 +525,9 @@ export const RemoteService = { let resScenes = await Endpoint["post"](urlScenePriority, {}, scenePriority) automation.scenes.push(resScenes.data); } - console.log("This is an automtion: ", automation); + automation.id = id; dispatch(actions.automationSave(automation)); + }); } @@ -721,6 +722,19 @@ export const RemoteService = { }; }, + deleteAutomation: (id) => { + console.log("ID OF AUTO ", id); + return (dispatch) => { + return Endpoint.delete(`/automation/${id}`) + .then((_) => dispatch(actions.automationDelete(id))) + .catch((err) => { + console.warn("Automation deletion error", err); + throw new RemoteError(["Network error"]); + }); + }; + + }, + /** * Deletes a scene * @param {Number} sceneId the id of the scene to delete diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 2e0e20d..7c0507f 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -295,15 +295,18 @@ function reducer(previousState, action) { break; case "AUTOMATION_SAVE": + console.log("ID: ", action.automation.id); change = { automations : {[action.automation.id] : {$set : action.automation}} }; + + newState = update(previousState, change); + break; case "STATE_SAVE": - console.log("Store", action.sceneState); change = { sceneStates: { [action.sceneState.id]: { $set: action.sceneState } }, }; @@ -350,6 +353,18 @@ function reducer(previousState, action) { newState = update(previousState, change); break; + + case "AUTOMATION_DELETE": + + change = { + automations: { $unset: [action.id] }, + }; + + console.log("CHANGE ", change) + console.log("Action id: ", action.id); + newState = update(previousState, change); + console.log("NEW STATE ", newState) + break; case "SCENE_DELETE": console.log("SCENE", action.sceneId); if (!(action.sceneId in previousState.scenes)) { diff --git a/smart-hut/src/storeActions.js b/smart-hut/src/storeActions.js index e02bbd8..d1ec599 100644 --- a/smart-hut/src/storeActions.js +++ b/smart-hut/src/storeActions.js @@ -71,6 +71,10 @@ const actions = { type: "ROOM_DELETE", roomId, }), + automationDelete: (id) => ({ + type: "AUTOMATION_DELETE", + id, + }), sceneDelete: (sceneId) => ({ type: "SCENE_DELETE", sceneId, From b0b281263e8e73acdf13f9fe7b82da058b5dc8a5 Mon Sep 17 00:00:00 2001 From: "Claudio Maggioni (maggicl)" Date: Tue, 28 Apr 2020 11:56:34 +0200 Subject: [PATCH 4/6] fix --- .../components/dashboard/AutomationsPanel.js | 1076 ++++++++--------- smart-hut/src/store.js | 16 +- 2 files changed, 534 insertions(+), 558 deletions(-) diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index c730332..dd49666 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -1,597 +1,571 @@ -import React, {Component, useState, useEffect} from "react"; -import {connect} from "react-redux"; -import {RemoteService} from "../../remote"; +import React, { Component, useState, useEffect } from "react"; +import { connect } from "react-redux"; +import { RemoteService } from "../../remote"; import "./Automations.css"; import { - Segment, - Grid, - Icon, - Header, - Input, - Button, - List, - Dropdown, - Form, - Divider, - Checkbox, - Menu, - Label, + Segment, + Grid, + Icon, + Header, + Input, + Button, + List, + Dropdown, + Form, + Divider, + Checkbox, + Menu, + Label, } 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", - }, + { 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}, + { 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 [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); - } - }; + 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} - /> - - )} - -
-
-
- ); + 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) : ""} -

-
-
-
-
-
- ); + 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 {device, 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" - /> - - ); +const Trigger = ({ deviceName, trigger, onRemove, index }) => { + const { device, 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({}); + 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]); + 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 - ); + 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: result, - message: result - ? "" - : "You have already created a trigger for this device with the same conditions", + result: false, + message: "There are missing fields!", }; - }; - 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, + } + } else { + if (!trigger.device || !trigger.operand || !trigger.value) { + return { + result: false, + message: "There are missing fields", }; - props.save({automation, triggerList, order}); - //} - }; - - return ( - -
- {editName ? ( - - ) : ( - automationName - )} -
- - -
- )} -
-
- - - - - - - - - -
+ } + } + 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 + )} +
+ + +
+ )} + + + + + + + + + + + + + ); }; -const Automation = ({automation, devices, scenes, removeAutomation}) => { - const {triggers} = automation; - const scenePriorities = automation.scenes; - const getOperator = (operand) => - operands.filter((o) => o.key == operand)[0].text; +const Automation = ({ automation, devices, scenes, removeAutomation }) => { + const { triggers } = automation; + const scenePriorities = automation.scenes; + const getOperator = (operand) => + operands.filter((o) => o.key == operand)[0].text; - - return ( - -
- {automation.name} -
- - )} - - - - - {this.props.automations.map((automation, i) => { - return ( - - - - ); - })} - - - ); - } + render() { + return ( + + + + {!this.state.openModal ? ( + + + + ) : ( + + )} + + + + {this.props.automations.map((automation, i) => { + console.log(23, automation, i, this.props.automations); + return ( + + + + ); + })} + + + ); + } } const mapStateToProps = (state, _) => ({ - activeRoom: state.active.activeRoom, - activeTab: state.active.activeTab, - get scenes() { - return Object.values(state.scenes); - }, - get devices() { - return Object.values(state.devices); - }, - get automations() { - return Object.values(state.automations); - }, + activeRoom: state.active.activeRoom, + activeTab: state.active.activeTab, + get scenes() { + return Object.values(state.scenes); + }, + get devices() { + return Object.values(state.devices); + }, + get automations() { + console.log(state.automations); + return Object.values(state.automations); + }, }); const AutomationsPanelContainer = connect( - mapStateToProps, - RemoteService + mapStateToProps, + RemoteService )(AutomationsPanel); export default AutomationsPanelContainer; diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 7c0507f..0087e46 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -256,8 +256,13 @@ function reducer(previousState, action) { case "AUTOMATION_UPDATE": newState = previousState; + const automations = {}; + for (const automation of action.automations) { + automations[automation.id] = automation; + } + change = { - automations : {$set: action.automations} + automations: { $set: automations }, }; newState = update(previousState, change); break; @@ -297,15 +302,13 @@ function reducer(previousState, action) { case "AUTOMATION_SAVE": console.log("ID: ", action.automation.id); change = { - automations : {[action.automation.id] : {$set : action.automation}} + automations: { [action.automation.id]: { $set: action.automation } }, }; - newState = update(previousState, change); break; - case "STATE_SAVE": change = { sceneStates: { [action.sceneState.id]: { $set: action.sceneState } }, @@ -355,15 +358,14 @@ function reducer(previousState, action) { break; case "AUTOMATION_DELETE": - change = { automations: { $unset: [action.id] }, }; - console.log("CHANGE ", change) + console.log("CHANGE ", change); console.log("Action id: ", action.id); newState = update(previousState, change); - console.log("NEW STATE ", newState) + console.log("NEW STATE ", newState); break; case "SCENE_DELETE": console.log("SCENE", action.sceneId); From b9be38d467e83d7c1e21a64ff471a8979c13db7a Mon Sep 17 00:00:00 2001 From: "Claudio Maggioni (maggicl)" Date: Tue, 28 Apr 2020 12:02:46 +0200 Subject: [PATCH 5/6] removed warnings --- smart-hut/src/components/AutomationModal.js | 2 -- smart-hut/src/components/dashboard/AutomationsPanel.js | 9 ++++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/smart-hut/src/components/AutomationModal.js b/smart-hut/src/components/AutomationModal.js index 0cf1674..5f8fbd5 100644 --- a/smart-hut/src/components/AutomationModal.js +++ b/smart-hut/src/components/AutomationModal.js @@ -1,9 +1,7 @@ import React, { Component } from "react"; -import { Button, Header, Modal, Icon, Responsive } from "semantic-ui-react"; import { connect } from "react-redux"; import { RemoteService } from "../remote"; import { appActions } from "../storeActions"; -//import { update } from "immutability-helper"; class AutomationModal extends Component { constructor(props) { diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index dd49666..2a7b683 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -16,7 +16,6 @@ import { Divider, Checkbox, Menu, - Label, } from "semantic-ui-react"; const operands = [ @@ -157,7 +156,7 @@ const SceneItem = (props) => { }; const Trigger = ({ deviceName, trigger, onRemove, index }) => { - const { device, operand, value } = trigger; + const { operand, value } = trigger; let symbol; if (operand) { symbol = operands.filter((opt) => opt.key === operand)[0].text; @@ -273,7 +272,7 @@ export const CreateAutomation = (props) => { return trigger.device + trigger.value; }; - const checkBeforeSave = () => { + /*const checkBeforeSave = () => { if (automationName.length <= 0) { alert("Give a name to the automation"); return false; @@ -287,7 +286,7 @@ export const CreateAutomation = (props) => { return false; } return true; - }; + };*/ const saveAutomation = () => { //if(checkBeforeSave()){ @@ -408,7 +407,7 @@ const Automation = ({ automation, devices, scenes, removeAutomation }) => { const { triggers } = automation; const scenePriorities = automation.scenes; const getOperator = (operand) => - operands.filter((o) => o.key == operand)[0].text; + operands.filter((o) => o.key === operand)[0].text; return ( From 5b2bd7cc48ad6993a2bcb2c255eaa7c45ade5b05 Mon Sep 17 00:00:00 2001 From: "Claudio Maggioni (maggicl)" Date: Tue, 28 Apr 2020 12:04:01 +0200 Subject: [PATCH 6/6] runned beautifier --- smart-hut/src/App.js | 2 +- .../src/components/dashboard/Automations.css | 14 +- .../src/components/dashboard/DevicePanel.js | 88 +- .../components/dashboard/devices/NewDevice.js | 12 +- smart-hut/src/remote.js | 1560 ++++++++--------- smart-hut/src/views/Dashboard.js | 334 ++-- 6 files changed, 981 insertions(+), 1029 deletions(-) diff --git a/smart-hut/src/App.js b/smart-hut/src/App.js index 1365896..602e77f 100644 --- a/smart-hut/src/App.js +++ b/smart-hut/src/App.js @@ -69,7 +69,7 @@ class App extends Component { - + diff --git a/smart-hut/src/components/dashboard/Automations.css b/smart-hut/src/components/dashboard/Automations.css index 0042cba..508b97a 100644 --- a/smart-hut/src/components/dashboard/Automations.css +++ b/smart-hut/src/components/dashboard/Automations.css @@ -1,18 +1,18 @@ .segment-automations { - top: 10%; + top: 10%; } .list-index { - font-size: 1.5rem; + font-size: 1.5rem; } .remove-icon { - display: inline !important; - margin-left: 1rem !important; + display: inline !important; + margin-left: 1rem !important; } .trigger-item { - display: flex !important; - justify-content: center !important; - align-items: center !important; + display: flex !important; + justify-content: center !important; + align-items: center !important; } diff --git a/smart-hut/src/components/dashboard/DevicePanel.js b/smart-hut/src/components/dashboard/DevicePanel.js index 3108a48..449642e 100644 --- a/smart-hut/src/components/dashboard/DevicePanel.js +++ b/smart-hut/src/components/dashboard/DevicePanel.js @@ -8,58 +8,58 @@ import { connect } from "react-redux"; import { RemoteService } from "../../remote"; class DevicePanel extends Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.getDevices(); - } + this.getDevices(); + } - getDevices() { - if (this.props.tab === "Devices") { - this.props - .fetchDevices() - .catch((err) => console.error(`error fetching devices:`, err)); - } + getDevices() { + if (this.props.tab === "Devices") { + this.props + .fetchDevices() + .catch((err) => console.error(`error fetching devices:`, err)); } + } - render() { - return ( - - {this.props.devices.map((e, i) => { - return ( - - - - ); - })} - {!this.props.isActiveRoomHome ? ( - - - - ) : null} - - ); - } + render() { + return ( + + {this.props.devices.map((e, i) => { + return ( + + + + ); + })} + {!this.props.isActiveRoomHome ? ( + + + + ) : null} + + ); + } } const mapStateToProps = (state, _) => ({ - get devices() { - if (state.active.activeRoom === -1) { - return Object.values(state.devices); - } else { - const deviceArray = [ - ...state.rooms[state.active.activeRoom].devices, - ].sort(); - return deviceArray.map((id) => state.devices[id]); - } - }, - get isActiveRoomHome() { - return state.active.activeRoom === -1; - }, - activeRoom: state.active.activeRoom, + get devices() { + if (state.active.activeRoom === -1) { + return Object.values(state.devices); + } else { + const deviceArray = [ + ...state.rooms[state.active.activeRoom].devices, + ].sort(); + return deviceArray.map((id) => state.devices[id]); + } + }, + get isActiveRoomHome() { + return state.active.activeRoom === -1; + }, + activeRoom: state.active.activeRoom, }); const DevicePanelContainer = connect( - mapStateToProps, - RemoteService + mapStateToProps, + RemoteService )(DevicePanel); export default DevicePanelContainer; diff --git a/smart-hut/src/components/dashboard/devices/NewDevice.js b/smart-hut/src/components/dashboard/devices/NewDevice.js index ff0ef72..6dcace6 100644 --- a/smart-hut/src/components/dashboard/devices/NewDevice.js +++ b/smart-hut/src/components/dashboard/devices/NewDevice.js @@ -106,7 +106,7 @@ class NewDevice extends Component { switch: "New switch", buttonDimmer: "New button dimmer", knobDimmer: "New knob dimmer", - securityCamera: "New security camera" + securityCamera: "New security camera", }; if (this.state.deviceName === "") { @@ -188,11 +188,11 @@ class NewDevice extends Component { image: { avatar: true, src: "/img/plusMinus.svg" }, }, { - key: "securityCamera", - text: "Security Camera", - value: "securityCamera", - image: { avatar: true, src: "/img/plusMinus.svg" }, - } + key: "securityCamera", + text: "Security Camera", + value: "securityCamera", + image: { avatar: true, src: "/img/plusMinus.svg" }, + }, ]; const sensorOptions = [ { diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index a3c6b8c..67d0633 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -1,8 +1,8 @@ import smartHutStore from "./store"; import actions from "./storeActions"; import axios from "axios"; -import {endpointURL, socketURL} from "./endpoint"; -import {connect, disconnect} from "@giantmachines/redux-websocket"; +import { endpointURL, socketURL } from "./endpoint"; +import { connect, disconnect } from "@giantmachines/redux-websocket"; /** * An object returned by promise rejections in remoteservice @@ -10,176 +10,173 @@ import {connect, disconnect} from "@giantmachines/redux-websocket"; * @property {String[]} messages a list of user-friendly error messages to show; */ class RemoteError extends Error { - messages; + messages; - constructor(messages) { - super(messages.join(" - ")); - this.messages = messages; - } + constructor(messages) { + super(messages.join(" - ")); + this.messages = messages; + } } const Endpoint = { - axiosInstance: axios.create({ - baseURL: endpointURL(), - validateStatus: (status) => status >= 200 && status < 300, - }), + axiosInstance: axios.create({ + baseURL: endpointURL(), + validateStatus: (status) => status >= 200 && status < 300, + }), - /** - * Returns token for current session, null if logged out - * @returns {String|null} the token - */ - get token() { - return smartHutStore.getState().login.token; - }, + /** + * Returns token for current session, null if logged out + * @returns {String|null} the token + */ + get token() { + return smartHutStore.getState().login.token; + }, - /** - * Performs an authenticated request - * @param {get|post|put|delete} the desired method - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @param {any} body the JSON request body - */ - send: (method, route, query = {}, body = null) => { - if (!Endpoint.token) { - throw new Error("No token while performing authenticated request"); - } + /** + * Performs an authenticated request + * @param {get|post|put|delete} the desired method + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @param {any} body the JSON request body + */ + send: (method, route, query = {}, body = null) => { + if (!Endpoint.token) { + throw new Error("No token while performing authenticated request"); + } - return Endpoint.axiosInstance(route, { - method: method, - params: query, - data: ["put", "post"].indexOf(method) !== -1 ? body : null, - headers: { - Authorization: `Bearer ${Endpoint.token}`, - }, - }).then((res) => { - if (!res.data && method !== "delete") { - console.error("Response body is empty"); - return null; - } else { - return res; - } - }); - }, + return Endpoint.axiosInstance(route, { + method: method, + params: query, + data: ["put", "post"].indexOf(method) !== -1 ? body : null, + headers: { + Authorization: `Bearer ${Endpoint.token}`, + }, + }).then((res) => { + if (!res.data && method !== "delete") { + console.error("Response body is empty"); + return null; + } else { + return res; + } + }); + }, - /** - * Performs a non-authenticated post and put request for registration, reset-password - * @param {post} the desired method - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @param {any} body the JSON request body - */ - sendNA: (method, route, query = {}, body = null) => { - return Endpoint.axiosInstance(route, { - method: method, - params: query, - data: ["put", "post"].indexOf(method) !== -1 ? body : null, - }).then((res) => { - if (!res.data) { - console.error("Response body is empty"); - return null; - } else { - return res; - } - }); - }, + /** + * Performs a non-authenticated post and put request for registration, reset-password + * @param {post} the desired method + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @param {any} body the JSON request body + */ + sendNA: (method, route, query = {}, body = null) => { + return Endpoint.axiosInstance(route, { + method: method, + params: query, + data: ["put", "post"].indexOf(method) !== -1 ? body : null, + }).then((res) => { + if (!res.data) { + console.error("Response body is empty"); + return null; + } else { + return res; + } + }); + }, - /** - * Performs login - * @param {String} usernameOrEmail - * @param {String} password - * @returns {Promise} promise that resolves to the token string - * and rejects to the axios error. - */ - login: (usernameOrEmail, password) => { - return Endpoint.axiosInstance - .post(`/auth/login`, { - usernameOrEmail, - password, - }) - .then((res) => { - localStorage.setItem("token", res.data.jwttoken); - localStorage.setItem( - "exp", - new Date().getTime() + 5 * 60 * 60 * 1000 - ); - return res.data.jwttoken; - }); - }, + /** + * Performs login + * @param {String} usernameOrEmail + * @param {String} password + * @returns {Promise} promise that resolves to the token string + * and rejects to the axios error. + */ + login: (usernameOrEmail, password) => { + return Endpoint.axiosInstance + .post(`/auth/login`, { + usernameOrEmail, + password, + }) + .then((res) => { + localStorage.setItem("token", res.data.jwttoken); + localStorage.setItem("exp", new Date().getTime() + 5 * 60 * 60 * 1000); + return res.data.jwttoken; + }); + }, - /** - * Returns an immediately resolved promise for the socket logouts - * @return {Promise} An always-resolved promise - */ - logout: () => { - localStorage.removeItem("token"); - localStorage.removeItem("exp"); - return Promise.resolve(void 0); - }, + /** + * Returns an immediately resolved promise for the socket logouts + * @return {Promise} An always-resolved promise + */ + logout: () => { + localStorage.removeItem("token"); + localStorage.removeItem("exp"); + return Promise.resolve(void 0); + }, - /** - * Performs an authenticated GET request - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @returns {Promise<*, *>} The Axios-generated promise - */ - get(route, query = {}) { - return this.send("get", route, query); - }, + /** + * Performs an authenticated GET request + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @returns {Promise<*, *>} The Axios-generated promise + */ + get(route, query = {}) { + return this.send("get", route, query); + }, - /** - * Performs an authenticated POST request - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @param {any} body the JSON request body - * @returns {Promise<*, *>} The Axios-generated promise - */ - post(route, query, body) { - return this.send("post", route, query, body); - }, + /** + * Performs an authenticated POST request + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @param {any} body the JSON request body + * @returns {Promise<*, *>} The Axios-generated promise + */ + post(route, query, body) { + return this.send("post", route, query, body); + }, - /** - * Performs a non-authenticated POST request - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @param {any} body the JSON request body - * @returns {Promise<*, *>} The Axios-generated promise - */ - postNA(route, query, body) { - return this.sendNA("post", route, query, body); - }, + /** + * Performs a non-authenticated POST request + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @param {any} body the JSON request body + * @returns {Promise<*, *>} The Axios-generated promise + */ + postNA(route, query, body) { + return this.sendNA("post", route, query, body); + }, - /** - * Performs an authenticated PUT request - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @param {any} body the JSON request body - * @returns {Promise<*, *>} The Axios-generated promise - */ - put(route, query = {}, body = {}) { - return this.send("put", route, query, body); - }, + /** + * Performs an authenticated PUT request + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @param {any} body the JSON request body + * @returns {Promise<*, *>} The Axios-generated promise + */ + put(route, query = {}, body = {}) { + return this.send("put", route, query, body); + }, - /** - * Performs a non-authenticated PUT request - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @param {any} body the JSON request body - * @returns {Promise<*, *>} The Axios-generated promise - */ - putNA(route, query = {}, body = {}) { - return this.sendNA("put", route, query, body); - }, + /** + * Performs a non-authenticated PUT request + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @param {any} body the JSON request body + * @returns {Promise<*, *>} The Axios-generated promise + */ + putNA(route, query = {}, body = {}) { + return this.sendNA("put", route, query, body); + }, - /** - * Performs an authenticated DELETE request - * @param {get|post|put|delete} the desired method - * @param {String} route the desired route (e.g. "/rooms/1/devices") - * @param {[String]String} query query ('?') parameters (no params by default) - * @returns {Promise<*, *>} The Axios-generated promise - */ - delete(route, query = {}) { - return this.send("delete", route, query); - }, + /** + * Performs an authenticated DELETE request + * @param {get|post|put|delete} the desired method + * @param {String} route the desired route (e.g. "/rooms/1/devices") + * @param {[String]String} query query ('?') parameters (no params by default) + * @returns {Promise<*, *>} The Axios-generated promise + */ + delete(route, query = {}) { + return this.send("delete", route, query); + }, }; /** @@ -189,663 +186,644 @@ const Endpoint = { * @returns {RemoteError} user friendly error messages */ function parseValidationErrors(err) { - if ( - err.response && - err.response.status === 400 && - err.response.data && - Array.isArray(err.response.data.errors) - ) { - throw new RemoteError([ - ...new Set(err.response.data.errors.map((e) => e.defaultMessage)), - ]); - } else { - console.warn("Non validation error", err); - throw new RemoteError(["Network error"]); - } + if ( + err.response && + err.response.status === 400 && + err.response.data && + Array.isArray(err.response.data.errors) + ) { + throw new RemoteError([ + ...new Set(err.response.data.errors.map((e) => e.defaultMessage)), + ]); + } else { + console.warn("Non validation error", err); + throw new RemoteError(["Network error"]); + } } export const RemoteService = { - /** - * Performs login - * @param {String} usernameOrEmail - * @param {String} password - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - login: (usernameOrEmail, password) => { - return (dispatch) => { - return Endpoint.login(usernameOrEmail, password) - .then((token) => { - dispatch(actions.loginSuccess(token)); - dispatch(connect(socketURL(token))); - }) - .catch((err) => { - console.warn("login error", err); - throw new RemoteError([ - err.response && err.response.status === 401 - ? "Wrong credentials" - : "An error occurred while logging in", - ]); - }); - }; - }, + /** + * Performs login + * @param {String} usernameOrEmail + * @param {String} password + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + login: (usernameOrEmail, password) => { + return (dispatch) => { + return Endpoint.login(usernameOrEmail, password) + .then((token) => { + dispatch(actions.loginSuccess(token)); + dispatch(connect(socketURL(token))); + }) + .catch((err) => { + console.warn("login error", err); + throw new RemoteError([ + err.response && err.response.status === 401 + ? "Wrong credentials" + : "An error occurred while logging in", + ]); + }); + }; + }, - /** - * Performs logout - */ - logout: () => { - return (dispatch) => - Endpoint.logout().then(() => { - dispatch(disconnect()); - dispatch(actions.logout()); + /** + * Performs logout + */ + logout: () => { + return (dispatch) => + Endpoint.logout().then(() => { + dispatch(disconnect()); + dispatch(actions.logout()); + }); + }, + + /** + * Fetches user information via REST calls, if it is logged in + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchUserInfo: () => { + return (dispatch) => { + return Endpoint.get("/auth/profile") + .then((res) => void dispatch(actions.userInfoUpdate(res.data))) + .catch((err) => { + console.warn("Fetch user info error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Fetches all rooms that belong to this user. This call does not + * populate the devices attribute in rooms. + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchAllRooms: () => { + return (dispatch) => { + return Endpoint.get("/room") + .then((res) => void dispatch(actions.roomsUpdate(res.data))) + .catch((err) => { + console.error("Fetch all rooms error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Fetches all scenes that belong to this user. This call does not + * populate the devices attribute in scenes. + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchAllScenes: () => { + return (dispatch) => { + return Endpoint.get("/scene") + .then((res) => void dispatch(actions.scenesUpdate(res.data))) + .catch((err) => { + console.error("Fetch all scenes error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Fetches all devices in a particular room, or fetches all devices. + * This also updates the devices attribute on values in the map rooms. + * @param {Number|null} roomId the rsoom to which fetch devices + * from, null to fetch from all rooms + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchDevices: (roomId = null) => { + return (dispatch) => { + return Endpoint.get(roomId ? `/room/${roomId}/device` : "/device") + .then((res) => void dispatch(actions.devicesUpdate(roomId, res.data))) + .catch((err) => { + console.error(`Fetch devices roomId=${roomId} error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Fetches all the automations + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + 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) + ); + } }); - }, + }); + }); + }); + }) + .catch((err) => { + console.error(`Fetch automations error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, - /** - * Fetches user information via REST calls, if it is logged in - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - fetchUserInfo: () => { - return (dispatch) => { - return Endpoint.get("/auth/profile") - .then((res) => void dispatch(actions.userInfoUpdate(res.data))) - .catch((err) => { - console.warn("Fetch user info error", err); - throw new RemoteError(["Network error"]); - }); + /** + * Fetches all devices in a particular scene, or fetches all devices. + * This also updates the devices attribute on values in the map scenes. + * @param {Number} sceneId the scene to which fetch devices + * from, null to fetch from all scenes + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchStates: (sceneId) => { + return (dispatch) => { + return Endpoint.get(`/scene/${sceneId}/states`) + .then((res) => void dispatch(actions.statesUpdate(sceneId, res.data))) + .catch((err) => { + console.error(`Fetch devices sceneId=${sceneId} error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Creates/Updates a room with the given data + * @param {String} data.name the room's name, + * @param {String} data.icon the room's icon name in SemanticUI icons + * @param {String} data.image ths room's image, as base64 + * @param {Number|null} roomId the room's id if update, null for creation + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + saveRoom: (data, roomId = null) => { + return (dispatch) => { + data = { + name: data.name, + icon: data.icon, + image: data.image, + }; + + return (roomId + ? Endpoint.put(`/room/${roomId}`, {}, data) + : Endpoint.post(`/room`, {}, data) + ) + .then((res) => void dispatch(actions.roomSave(res.data))) + .catch(parseValidationErrors); + }; + }, + + /** + * Creates/Updates a scene with the given data + * @param {String} data.name the scene's name, + * @param {Number|null} sceneId the scene's id if update, null for creation + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + saveScene: (data, sceneId = null) => { + return (dispatch) => { + data = { + name: data.name, + }; + + return (sceneId + ? Endpoint.put(`/scene/${sceneId}`, {}, data) + : Endpoint.post(`/scene`, {}, data) + ) + .then((res) => void dispatch(actions.sceneSave(res.data))) + .catch(parseValidationErrors); + }; + }, + + /** + * Creates/Updates a device with the given data. If + * data.id is truthy, then a update call is performed, + * otherwise a create call is performed. The update URL + * is computed based on data.kind when data.flowType = + * 'OUTPUT', otherwise the PUT "/device" endpoint + * is used for updates and the POST "/" + * endpoints are used for creation. + * @param {Device} data the device to update. + * @returns {Promise} promise that resolves to the saved device and rejects + * with user-fiendly errors as a RemoteError + */ + saveDevice: (data) => { + return (dispatch) => { + let url = "/device"; + if ((data.id && data.flowType === "OUTPUT") || !data.id) { + url = "/" + data.kind; + } + + return Endpoint[data.id ? "put" : "post"](url, {}, data) + .then((res) => { + dispatch(actions.deviceSave(res.data)); + return res.data; + }) + .catch((err) => { + console.warn("Update device: ", data, "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. + * @returns {Promise} promise that resolves to the saved device and rejects + * with user-fiendly errors as a RemoteError + */ + saveAutomation: (data) => { + const { automation, triggerList, order } = data; + console.log("automation: ", automation, triggerList, order); + automation.triggers = []; + automation.scenes = []; + return (dispatch) => { + let urlAutomation = "/automation"; + let urlBooleanTrigger = "/booleanTrigger"; + let urlRangeTrigger = "/rangeTrigger"; + let urlScenePriority = "/scenePriority"; + + let rangeTriggerList = triggerList.filter((trigger) => + trigger.hasOwnProperty("operand") + ); + let booleanTriggerList = triggerList.filter( + (trigger) => !trigger.hasOwnProperty("operand") + ); + + return Endpoint["post"](urlAutomation, {}, automation).then( + async (automationRes) => { + const { id } = automationRes.data; + // Introduce the range triggers in the automation + for (let t of rangeTriggerList) { + const trigger = { + automationId: id, + deviceId: t.device, + operator: t.operand, + range: t.value, }; - }, + let resRange = await Endpoint.post(urlRangeTrigger, {}, trigger); + automation.triggers.push(resRange.data); + } - /** - * Fetches all rooms that belong to this user. This call does not - * populate the devices attribute in rooms. - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - fetchAllRooms: () => { - return (dispatch) => { - return Endpoint.get("/room") - .then((res) => void dispatch(actions.roomsUpdate(res.data))) - .catch((err) => { - console.error("Fetch all rooms error", err); - throw new RemoteError(["Network error"]); - }); + for (let t of booleanTriggerList) { + const trigger = { + automationId: id, + deviceId: t.device, + on: t.value, }; - }, - - /** - * Fetches all scenes that belong to this user. This call does not - * populate the devices attribute in scenes. - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - fetchAllScenes: () => { - return (dispatch) => { - return Endpoint.get("/scene") - .then((res) => void dispatch(actions.scenesUpdate(res.data))) - .catch((err) => { - console.error("Fetch all scenes error", err); - throw new RemoteError(["Network error"]); - }); - }; - }, - - /** - * Fetches all devices in a particular room, or fetches all devices. - * This also updates the devices attribute on values in the map rooms. - * @param {Number|null} roomId the rsoom to which fetch devices - * from, null to fetch from all rooms - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - fetchDevices: (roomId = null) => { - return (dispatch) => { - return Endpoint.get(roomId ? `/room/${roomId}/device` : "/device") - .then( - (res) => - void dispatch(actions.devicesUpdate(roomId, res.data)) - ) - .catch((err) => { - console.error(`Fetch devices roomId=${roomId} error`, err); - throw new RemoteError(["Network error"]); - }); - }; - }, - - /** - * Fetches all the automations - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - 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)); - } - }) - - }) - - }); - }); - } - ) - .catch((err) => { - console.error(`Fetch automations error`, err); - throw new RemoteError(["Network error"]); - }); - }; - }, - - /** - * Fetches all devices in a particular scene, or fetches all devices. - * This also updates the devices attribute on values in the map scenes. - * @param {Number} sceneId the scene to which fetch devices - * from, null to fetch from all scenes - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - fetchStates: (sceneId) => { - return (dispatch) => { - return Endpoint.get(`/scene/${sceneId}/states`) - .then( - (res) => - void dispatch(actions.statesUpdate(sceneId, res.data)) - ) - .catch((err) => { - console.error( - `Fetch devices sceneId=${sceneId} error`, - err - ); - throw new RemoteError(["Network error"]); - }); - }; - }, - - /** - * Creates/Updates a room with the given data - * @param {String} data.name the room's name, - * @param {String} data.icon the room's icon name in SemanticUI icons - * @param {String} data.image ths room's image, as base64 - * @param {Number|null} roomId the room's id if update, null for creation - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - saveRoom: (data, roomId = null) => { - return (dispatch) => { - data = { - name: data.name, - icon: data.icon, - image: data.image, - }; - - return (roomId - ? Endpoint.put(`/room/${roomId}`, {}, data) - : Endpoint.post(`/room`, {}, data) - ) - .then((res) => void dispatch(actions.roomSave(res.data))) - .catch(parseValidationErrors); - }; - }, - - /** - * Creates/Updates a scene with the given data - * @param {String} data.name the scene's name, - * @param {Number|null} sceneId the scene's id if update, null for creation - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - saveScene: (data, sceneId = null) => { - return (dispatch) => { - data = { - name: data.name, - }; - - return (sceneId - ? Endpoint.put(`/scene/${sceneId}`, {}, data) - : Endpoint.post(`/scene`, {}, data) - ) - .then((res) => void dispatch(actions.sceneSave(res.data))) - .catch(parseValidationErrors); - }; - }, - - /** - * Creates/Updates a device with the given data. If - * data.id is truthy, then a update call is performed, - * otherwise a create call is performed. The update URL - * is computed based on data.kind when data.flowType = - * 'OUTPUT', otherwise the PUT "/device" endpoint - * is used for updates and the POST "/" - * endpoints are used for creation. - * @param {Device} data the device to update. - * @returns {Promise} promise that resolves to the saved device and rejects - * with user-fiendly errors as a RemoteError - */ - saveDevice: (data) => { - return (dispatch) => { - let url = "/device"; - if ((data.id && data.flowType === "OUTPUT") || !data.id) { - url = "/" + data.kind; - } - - return Endpoint[data.id ? "put" : "post"](url, {}, data) - .then((res) => { - dispatch(actions.deviceSave(res.data)); - return res.data; - }) - .catch((err) => { - console.warn("Update device: ", data, "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. - * @returns {Promise} promise that resolves to the saved device and rejects - * with user-fiendly errors as a RemoteError - */ - saveAutomation: (data) => { - const {automation, triggerList, order} = data; - console.log("automation: ", automation, triggerList, order); - automation.triggers = []; - automation.scenes = []; - return (dispatch) => { - let urlAutomation = "/automation"; - let urlBooleanTrigger = "/booleanTrigger"; - let urlRangeTrigger = "/rangeTrigger"; - let urlScenePriority = "/scenePriority"; - - let rangeTriggerList = triggerList.filter(trigger => trigger.hasOwnProperty("operand")); - let booleanTriggerList = triggerList.filter(trigger => !trigger.hasOwnProperty("operand")); - - return Endpoint["post"](urlAutomation, {}, automation) - .then(async automationRes => { - const {id} = automationRes.data; - // Introduce the range triggers in the automation - for (let t of rangeTriggerList) { - const trigger = { - automationId: id, - deviceId: t.device, - operator: t.operand, - range: t.value - }; - let resRange = await Endpoint.post(urlRangeTrigger, {}, trigger) - automation.triggers.push(resRange.data); - } - - for (let t of booleanTriggerList) { - const trigger = { - automationId: id, - deviceId: t.device, - on: t.value - }; - let resBoolean = await Endpoint.post(urlBooleanTrigger, {}, trigger) - automation.triggers.push(resBoolean.data); - console.log("TRIGGERS: ", automation); - } - - for (let [priority, sceneId] of order.entries()) { - const scenePriority = { - automationId: id, - priority, - sceneId, - } - let resScenes = await Endpoint["post"](urlScenePriority, {}, scenePriority) - automation.scenes.push(resScenes.data); - } - automation.id = id; - dispatch(actions.automationSave(automation)); - - }); - - } - }, - - /** - * Creates/Updates a state with the given data. If - * data.id is truthy, then a update call is performed, - * otherwise a create call is performed. The update URL - * is computed based on data.kind when data.flowType = - * 'OUTPUT', otherwise the PUT "/device" endpoint - * is used for updates and the POST "/" - * endpoints are used for creation. - * @param {State} data the device to update. - * @returns {Promise} promise that resolves to the saved device and rejects - * with user-fiendly errors as a RemoteError - */ - saveState: (data) => { - return (dispatch) => { - let url = - "/" + - data.kind + - "/" + - data.id + - "/state?sceneId=" + - data.sceneId; - - return Endpoint["post"](url, {}, data) - .then((res) => { - dispatch(actions.stateSave(res.data)); - return res.data; - }) - .catch((err) => { - console.warn("Update device: ", data, "error: ", err); - throw new RemoteError(["Network error"]); - }); - }; - }, - - /** - * Connetcs a series of output devices to an input device. - * Output devices for Switch input can be: Normal Light, Dimmable Light, Smart Plug. - * Output devices for Dimmers input can be: Dimmable Light. - * - * @typedef {"switch" | "buttonDimmer" | "knobDimmer"} ConnectableInput - * - * @param {ConnectableInput} newDevice.kind kind of the input device - * @param {Integer} newDevice.id id of the input device - * @param {Integer[]} outputs ids of the output device - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - connectOutputs: (newDevice, outputs) => { - return (dispatch) => { - let url = `/${newDevice.kind}/${newDevice.id}/lights`; - - return Endpoint.post(url, {}, outputs) - .then((res) => { - dispatch(actions.deviceOperationUpdate(res.data)); - return res.data; - }) - .catch((err) => { - console.warn( - "ConnectOutputs of ", - newDevice.id, - " with outputs: ", - outputs, - "error: ", - err - ); - throw new RemoteError(["Network error"]); - }); - }; - }, - - _operateInput: (url, getUrl, action) => { - return (dispatch) => { - return Endpoint.put(url, {}, action) - .then(async (res) => { - const inputDevice = await Endpoint.get(getUrl); - delete inputDevice.outputs; - dispatch( - actions.deviceOperationUpdate([ - ...res.data, - inputDevice.data, - ]) - ); - }) - .catch((err) => { - console.warn(`${url} error`, err); - throw new RemoteError(["Network error"]); - }); - }; - }, - - /** - * Changes the state of a switch, by turning it on, off or toggling it. - * - * @typedef {"ON" | "OFF" | "TOGGLE"} SwitchOperation - * - * @param {Number} switchId the switch device id - * @param {SwitchOperation} type the operation to perform on the switch - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - switchOperate: (switchId, type) => { - return RemoteService._operateInput( - "/switch/operate", - `/switch/${switchId}`, - { - type: type.toUpperCase(), - id: switchId, - } + let resBoolean = await Endpoint.post( + urlBooleanTrigger, + {}, + trigger ); - }, + automation.triggers.push(resBoolean.data); + console.log("TRIGGERS: ", automation); + } - /** - * Turns a knob dimmer to a specific amount - * - * @param {Number} dimmerId the knob dimmer id - * @param {number} intensity the absolute intensity to dim to. Must be >=0 and <= 100. - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - knobDimmerDimTo: (dimmerId, intensity) => { - return RemoteService._operateInput( - "/knobDimmer/dimTo", - `/knobDimmer/${dimmerId}`, - { - intensity, - id: dimmerId, - } - ); - }, - - /** - * Turns a button dimmer up or down - * - * @typedef {"UP" | "DOWN"} ButtonDimmerDimType - * - * @param {Number} dimmerId the button dimmer id - * @param {ButtonDimmerDimType} dimType the type of dim to perform - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - buttonDimmerDim: (dimmerId, dimType) => { - return RemoteService._operateInput( - "/buttonDimmer/dim", - `/buttonDimmer/${dimmerId}`, - { - dimType, - id: dimmerId, - } - ); - }, - - /** - * Resets the meter on a smart plug - * - * @param {Number} smartPlugId the smart plug to reset - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - smartPlugReset(smartPlugId) { - return (dispatch) => { - return Endpoint.delete(`/smartPlug/${smartPlugId}/meter`) - .then((res) => - dispatch(actions.deviceOperationUpdate([res.data])) - ) - .catch((err) => { - console.warn(`Smartplug reset error`, err); - throw new RemoteError(["Network error"]); - }); + for (let [priority, sceneId] of order.entries()) { + const scenePriority = { + automationId: id, + priority, + sceneId, }; + let resScenes = await Endpoint["post"]( + urlScenePriority, + {}, + scenePriority + ); + automation.scenes.push(resScenes.data); + } + automation.id = id; + dispatch(actions.automationSave(automation)); } - , + ); + }; + }, - /** - * Deletes a room - * @param {Number} roomId the id of the room to delete - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - deleteRoom: (roomId) => { - return (dispatch) => { - return Endpoint.delete(`/room/${roomId}`) - .then((_) => dispatch(actions.roomDelete(roomId))) - .catch((err) => { - console.warn("Room deletion error", err); - throw new RemoteError(["Network error"]); - }); - }; - }, + /** + * Creates/Updates a state with the given data. If + * data.id is truthy, then a update call is performed, + * otherwise a create call is performed. The update URL + * is computed based on data.kind when data.flowType = + * 'OUTPUT', otherwise the PUT "/device" endpoint + * is used for updates and the POST "/" + * endpoints are used for creation. + * @param {State} data the device to update. + * @returns {Promise} promise that resolves to the saved device and rejects + * with user-fiendly errors as a RemoteError + */ + saveState: (data) => { + return (dispatch) => { + let url = + "/" + data.kind + "/" + data.id + "/state?sceneId=" + data.sceneId; - deleteAutomation: (id) => { - console.log("ID OF AUTO ", id); - return (dispatch) => { - return Endpoint.delete(`/automation/${id}`) - .then((_) => dispatch(actions.automationDelete(id))) - .catch((err) => { - console.warn("Automation deletion error", err); - throw new RemoteError(["Network error"]); - }); - }; + return Endpoint["post"](url, {}, data) + .then((res) => { + dispatch(actions.stateSave(res.data)); + return res.data; + }) + .catch((err) => { + console.warn("Update device: ", data, "error: ", err); + throw new RemoteError(["Network error"]); + }); + }; + }, - }, + /** + * Connetcs a series of output devices to an input device. + * Output devices for Switch input can be: Normal Light, Dimmable Light, Smart Plug. + * Output devices for Dimmers input can be: Dimmable Light. + * + * @typedef {"switch" | "buttonDimmer" | "knobDimmer"} ConnectableInput + * + * @param {ConnectableInput} newDevice.kind kind of the input device + * @param {Integer} newDevice.id id of the input device + * @param {Integer[]} outputs ids of the output device + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + connectOutputs: (newDevice, outputs) => { + return (dispatch) => { + let url = `/${newDevice.kind}/${newDevice.id}/lights`; - /** - * Deletes a scene - * @param {Number} sceneId the id of the scene to delete - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - deleteScene: (sceneId) => { - return (dispatch) => { - return Endpoint.delete(`/scene/${sceneId}`) - .then((_) => dispatch(actions.sceneDelete(sceneId))) - .catch((err) => { - console.warn("Scene deletion error", err); - throw new RemoteError(["Network error"]); - }); - }; - }, + return Endpoint.post(url, {}, outputs) + .then((res) => { + dispatch(actions.deviceOperationUpdate(res.data)); + return res.data; + }) + .catch((err) => { + console.warn( + "ConnectOutputs of ", + newDevice.id, + " with outputs: ", + outputs, + "error: ", + err + ); + throw new RemoteError(["Network error"]); + }); + }; + }, - /** - * Deletes a device - * @param {Device} device the device to delete - * @returns {Promise} promise that resolves to void and rejects - * with user-fiendly errors as a RemoteError - */ - deleteDevice: (device) => { - return (dispatch) => { - return Endpoint.delete(`/${device.kind}/${device.id}`) - .then((_) => dispatch(actions.deviceDelete(device.id))) - .catch((err) => { - console.warn("Device deletion error", err); - throw new RemoteError(["Network error"]); - }); - }; - }, - } -; + _operateInput: (url, getUrl, action) => { + return (dispatch) => { + return Endpoint.put(url, {}, action) + .then(async (res) => { + const inputDevice = await Endpoint.get(getUrl); + delete inputDevice.outputs; + dispatch( + actions.deviceOperationUpdate([...res.data, inputDevice.data]) + ); + }) + .catch((err) => { + console.warn(`${url} error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, -for (const key in RemoteService - ) { - RemoteService[key] = RemoteService[key].bind(RemoteService); + /** + * Changes the state of a switch, by turning it on, off or toggling it. + * + * @typedef {"ON" | "OFF" | "TOGGLE"} SwitchOperation + * + * @param {Number} switchId the switch device id + * @param {SwitchOperation} type the operation to perform on the switch + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + switchOperate: (switchId, type) => { + return RemoteService._operateInput( + "/switch/operate", + `/switch/${switchId}`, + { + type: type.toUpperCase(), + id: switchId, + } + ); + }, + + /** + * Turns a knob dimmer to a specific amount + * + * @param {Number} dimmerId the knob dimmer id + * @param {number} intensity the absolute intensity to dim to. Must be >=0 and <= 100. + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + knobDimmerDimTo: (dimmerId, intensity) => { + return RemoteService._operateInput( + "/knobDimmer/dimTo", + `/knobDimmer/${dimmerId}`, + { + intensity, + id: dimmerId, + } + ); + }, + + /** + * Turns a button dimmer up or down + * + * @typedef {"UP" | "DOWN"} ButtonDimmerDimType + * + * @param {Number} dimmerId the button dimmer id + * @param {ButtonDimmerDimType} dimType the type of dim to perform + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + buttonDimmerDim: (dimmerId, dimType) => { + return RemoteService._operateInput( + "/buttonDimmer/dim", + `/buttonDimmer/${dimmerId}`, + { + dimType, + id: dimmerId, + } + ); + }, + + /** + * Resets the meter on a smart plug + * + * @param {Number} smartPlugId the smart plug to reset + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + smartPlugReset(smartPlugId) { + return (dispatch) => { + return Endpoint.delete(`/smartPlug/${smartPlugId}/meter`) + .then((res) => dispatch(actions.deviceOperationUpdate([res.data]))) + .catch((err) => { + console.warn(`Smartplug reset error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, + /** + * Deletes a room + * @param {Number} roomId the id of the room to delete + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + deleteRoom: (roomId) => { + return (dispatch) => { + return Endpoint.delete(`/room/${roomId}`) + .then((_) => dispatch(actions.roomDelete(roomId))) + .catch((err) => { + console.warn("Room deletion error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + deleteAutomation: (id) => { + console.log("ID OF AUTO ", id); + return (dispatch) => { + return Endpoint.delete(`/automation/${id}`) + .then((_) => dispatch(actions.automationDelete(id))) + .catch((err) => { + console.warn("Automation deletion error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Deletes a scene + * @param {Number} sceneId the id of the scene to delete + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + deleteScene: (sceneId) => { + return (dispatch) => { + return Endpoint.delete(`/scene/${sceneId}`) + .then((_) => dispatch(actions.sceneDelete(sceneId))) + .catch((err) => { + console.warn("Scene deletion error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Deletes a device + * @param {Device} device the device to delete + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + deleteDevice: (device) => { + return (dispatch) => { + return Endpoint.delete(`/${device.kind}/${device.id}`) + .then((_) => dispatch(actions.deviceDelete(device.id))) + .catch((err) => { + console.warn("Device deletion error", err); + throw new RemoteError(["Network error"]); + }); + }; + }, +}; + +for (const key in RemoteService) { + RemoteService[key] = RemoteService[key].bind(RemoteService); } export class Forms { - /** - * Attempts to create a new user from the given data. - * This method does not update the global state, - * please check its return value. - * @param {String} data.username the chosen username - * @param {String} data.password the chosen password - * @param {String} data.email the chosen email - * @param {String} data.name the chosen full name - * @returns {Promise} promise that resolves to void and rejects - * with validation errors as a String array - */ - static submitRegistration(data) { - return Endpoint.postNA( - "/register", - {}, - { - username: data.username, - password: data.password, - name: data.name, - email: data.email, - } - ) - .then((_) => void 0) - .catch(parseValidationErrors); - } + /** + * Attempts to create a new user from the given data. + * This method does not update the global state, + * please check its return value. + * @param {String} data.username the chosen username + * @param {String} data.password the chosen password + * @param {String} data.email the chosen email + * @param {String} data.name the chosen full name + * @returns {Promise} promise that resolves to void and rejects + * with validation errors as a String array + */ + static submitRegistration(data) { + return Endpoint.postNA( + "/register", + {}, + { + username: data.username, + password: data.password, + name: data.name, + email: data.email, + } + ) + .then((_) => void 0) + .catch(parseValidationErrors); + } - /** - * Sends a request to perform a password reset. - * This method does not update the global state, - * please check its return value. - * @param {String} email the email to which perform the reset - * @returns {Promise} promise that resolves to void and rejects - * with validation errors as a String array - */ - static submitInitResetPassword(email) { - return Endpoint.postNA( - "/register/init-reset-password", - {}, - { - email: email, - } - ) - .then((_) => void 0) - .catch((err) => { - console.warn("Init reset password failed", err); - throw new RemoteError(["Network error"]); - }); - } + /** + * Sends a request to perform a password reset. + * This method does not update the global state, + * please check its return value. + * @param {String} email the email to which perform the reset + * @returns {Promise} promise that resolves to void and rejects + * with validation errors as a String array + */ + static submitInitResetPassword(email) { + return Endpoint.postNA( + "/register/init-reset-password", + {}, + { + email: email, + } + ) + .then((_) => void 0) + .catch((err) => { + console.warn("Init reset password failed", err); + throw new RemoteError(["Network error"]); + }); + } - /** - * Sends the password for the actual password reset, haviug already - * performed email verification - * This method does not update the global state, - * please check its return value. - * @param {String} confirmationToken the confirmation token got from the email - * @param {String} password the new password - * @returns {Promise} promise that resolves to void and rejects - * with validation errors as a String array - */ - static submitResetPassword(confirmationToken, password) { - return Endpoint.putNA( - "/register/reset-password", - {}, - { - confirmationToken, - password, - } - ) - .then((_) => void 0) - .catch(parseValidationErrors); - } + /** + * Sends the password for the actual password reset, haviug already + * performed email verification + * This method does not update the global state, + * please check its return value. + * @param {String} confirmationToken the confirmation token got from the email + * @param {String} password the new password + * @returns {Promise} promise that resolves to void and rejects + * with validation errors as a String array + */ + static submitResetPassword(confirmationToken, password) { + return Endpoint.putNA( + "/register/reset-password", + {}, + { + confirmationToken, + password, + } + ) + .then((_) => void 0) + .catch(parseValidationErrors); + } } diff --git a/smart-hut/src/views/Dashboard.js b/smart-hut/src/views/Dashboard.js index af0a3fa..1d97806 100644 --- a/smart-hut/src/views/Dashboard.js +++ b/smart-hut/src/views/Dashboard.js @@ -8,8 +8,8 @@ import AutomationsNavbar from "./AutomationsNavbar"; import MyHeader from "../components/HeaderController"; import { Grid, Responsive, Button } from "semantic-ui-react"; import { - panelStyle, - mobilePanelStyle, + panelStyle, + mobilePanelStyle, } from "../components/dashboard/devices/styleComponents"; import { RemoteService } from "../remote"; @@ -17,203 +17,177 @@ import { connect } from "react-redux"; import { appActions } from "../storeActions"; class Dashboard extends Component { - constructor(props) { - super(props); - this.state = this.initialState; - this.setInitialState(); - this.activeTab = "Automations"; //TODO Remove this to not put automations first - this.selectTab = this.selectTab.bind(this); - } + constructor(props) { + super(props); + this.state = this.initialState; + this.setInitialState(); + this.activeTab = "Automations"; //TODO Remove this to not put automations first + this.selectTab = this.selectTab.bind(this); + } - get initialState() { - return { - activeTab: this.activeTab, - }; - } + get initialState() { + return { + activeTab: this.activeTab, + }; + } - setInitialState() { - this.setState(this.initialState); - } + setInitialState() { + this.setState(this.initialState); + } - get activeTab() { - return this.props.activeTab; - } + get activeTab() { + return this.props.activeTab; + } - set activeTab(tab) { - this.props.setActiveTab(tab); - } + set activeTab(tab) { + this.props.setActiveTab(tab); + } - selectTab(e, { name }) { - this.setState({ activeTab: name }); - this.activeTab = name; - } + selectTab(e, { name }) { + this.setState({ activeTab: name }); + this.activeTab = name; + } - renderTab(tab) { - switch (tab) { - case "Devices": - return ; - case "Scenes": - return ; - case "Automations": - return ; - default: - return

ERROR

; - } + renderTab(tab) { + switch (tab) { + case "Devices": + return ; + case "Scenes": + return ; + case "Automations": + return ; + default: + return

ERROR

; } + } - renderNavbar(tab) { - switch (tab) { - case "Devices": - return ; - case "Scenes": - return ; - case "Automations": - return ; - default: - return

ERROR

; - } + renderNavbar(tab) { + switch (tab) { + case "Devices": + return ; + case "Scenes": + return ; + case "Automations": + return ; + default: + return

ERROR

; } + } - render() { - return ( -
- - - - - - - - - - -
- ); - } + +
{this.renderTab(this.activeTab)}
+
+ + + + + + + + + + + + +