diff --git a/smart-hut/src/components/AutomationModal.js b/smart-hut/src/components/AutomationModal.js deleted file mode 100644 index 5f8fbd5..0000000 --- a/smart-hut/src/components/AutomationModal.js +++ /dev/null @@ -1,189 +0,0 @@ -import React, { Component } from "react"; -import { connect } from "react-redux"; -import { RemoteService } from "../remote"; -import { appActions } from "../storeActions"; - -class AutomationModal extends Component { - constructor(props) { - super(props); - this.state = this.initialState; - this.setInitialState(); - - this.addAutomationModal = this.addAutomationModal.bind(this); - this.modifyAutomationModal = this.modifyAutomationModal.bind(this); - this.deleteAutomation = this.deleteAutomation.bind(this); - } - - get initialState() { - return { - //INITIAL STATE HERE - }; - } - - setInitialState() { - this.setState(this.initialState); - } - - get type() { - return !this.props.id ? "new" : "modify"; - } - - addAutomationModal = (e) => { - /*let data = { - // DATA HERE - };*/ - // TODO CALL TO REMOTE SERVER TO ADD SCENE - /*this.props - .saveRoom(data, null) - .then(() => { - this.setInitialState(); - this.closeModal(); - }) - .catch((err) => console.error("error in creating room", err));*/ - }; - - modifyAutomationModal = (e) => { - /* let data = { - // DATA HERE - };*/ - // TODO CALL TO REMOTE SERVER TO MODIFY SCENE - /*this.props - .saveRoom(data, this.props.id) - .then(() => { - this.setInitialState(); - this.closeModal(); - }) - .catch((err) => console.error("error in updating room", err));*/ - }; - - deleteAutomation = (e) => { - // TODO CALL TO REMOTE SERVER TO DELETE SCENE - /* - this.props - .deleteRoom(this.props.id) - .then(() => this.closeModal()) - .catch((err) => console.error("error in deleting room", err));*/ - }; - - changeSomething = (event) => { - let nam = event.target.name; - let val = event.target.value; - this.setState({ [nam]: val }); - }; - - closeModal = (e) => { - this.setState({ openModal: false }); - }; - - openModal = (e) => { - this.setState({ openModal: true }); - }; - - render() { - return ( -
- {/* - {!this.props.nicolaStop ? ( -
- - {this.type === "new" ? ( - - ) : ( - - )} - - - {this.type === "new" ? ( - - ) : ( - - )} - -
- ) : null} - - -
- {this.type === "new" ? "Add new automation" : "Modify automation"} -
- - { - //TODO FORM TO ADD OR MODIFY SCENE - } - - {this.type === "modify" ? ( - - ) : null} - - - - - - -
*/} -
- ); - } -} - -const setActiveAutomation = (activeAutomation) => { - return (dispatch) => - dispatch(appActions.setActiveAutomation(activeAutomation)); -}; - -const mapStateToProps = (state, ownProps) => ({ - automations: ownProps.id ? state.automations[ownProps.id] : null, -}); -const AutomationModalContainer = connect( - mapStateToProps, - { ...RemoteService, setActiveAutomation }, - null, - { forwardRef: true } -)(AutomationModal); -export default AutomationModalContainer; diff --git a/smart-hut/src/components/HostModal.js b/smart-hut/src/components/HostModal.js new file mode 100644 index 0000000..789c484 --- /dev/null +++ b/smart-hut/src/components/HostModal.js @@ -0,0 +1,137 @@ +import React, { Component } from "react"; +import { + Button, + Header, + Modal, + Icon, + Form, + Responsive, + Dropdown, +} from "semantic-ui-react"; +import { connect } from "react-redux"; +import { RemoteService, Forms } from "../remote"; +import { appActions } from "../storeActions"; +//import { update } from "immutability-helper"; + +class HostModal extends Component { + constructor(props) { + super(props); + this.state = { guests: [], users: [] }; + + this.props + .fetchGuests() + .then(() => { + this.setState({ + ...this.state, + guests: this.props.guests.map((u) => u.id), + }); + }) + .catch(console.error); + + Forms.fetchAllUsers() + .then((users) => + this.setState({ + ...this.state, + users: users.map((u) => ({ + key: u.id, + text: `@${u.username} (${u.name})`, + value: u.id, + })), + }) + ) + .catch(console.error); + + this.saveGuestSettings = this.saveGuestSettings.bind(this); + this.closeModal = this.closeModal.bind(this); + this.openModal = this.openModal.bind(this); + this.setGuests = this.setGuests.bind(this); + this.saveGuestSettings = this.saveGuestSettings.bind(this); + } + + setGuests(_, guests) { + this.setState({ guests: guests.value }); + } + + closeModal() { + this.setState({ openModal: false }); + } + + openModal() { + this.setState({ openModal: true }); + } + + saveGuestSettings() { + this.props + .updateGuests(this.state.guests) + .then(this.closeModal) + .catch(console.error); + } + + render() { + return ( + + + + + + + + + +
Select guests
+ + +

Spaghetti!

+
+ +
+ + + + +
+
+
+ + + + + +
+
+ ); + } +} + +const setActiveHost = (activeHost) => { + return (dispatch) => dispatch(appActions.setActiveHost(activeHost)); +}; + +const mapStateToProps = (state) => ({ + guests: state.guests, +}); +const HostModalContainer = connect( + mapStateToProps, + { ...RemoteService, setActiveHost }, + null, + { forwardRef: true } +)(HostModal); +export default HostModalContainer; diff --git a/smart-hut/src/components/RoomModal.js b/smart-hut/src/components/RoomModal.js index eabdb89..2054b8a 100644 --- a/smart-hut/src/components/RoomModal.js +++ b/smart-hut/src/components/RoomModal.js @@ -21,7 +21,6 @@ class RoomModal extends Component { constructor(props) { super(props); this.state = this.initialState; - this.setInitialState(); this.fileInputRef = React.createRef(); diff --git a/smart-hut/src/components/dashboard/AutomationCreationModal.js b/smart-hut/src/components/dashboard/AutomationCreationModal.js index f32abde..867f677 100644 --- a/smart-hut/src/components/dashboard/AutomationCreationModal.js +++ b/smart-hut/src/components/dashboard/AutomationCreationModal.js @@ -255,8 +255,6 @@ class AutomationSaveModal extends Component { } _checkNewTrigger(trigger) { - const auxDevice = this.props.devices[trigger.device]; - // Check for missing fields for creation const error = { result: false, @@ -271,6 +269,8 @@ class AutomationSaveModal extends Component { case "rangeTrigger": if (!trigger.device || !trigger.operand || !trigger.value) return error; break; + default: + throw new Error("theoretically unreachable statement"); } const isNotDuplicate = !this.state.triggerList.some( @@ -345,6 +345,8 @@ class AutomationSaveModal extends Component { return "" + trigger.device + trigger.on; case "rangeTrigger": return "" + trigger.device + trigger.operand + trigger.value; + default: + throw new Error("theoretically unreachable statement"); } }; diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index d4d828e..9d6c972 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -1,4 +1,4 @@ -import React, { Component, useState, useEffect } from "react"; +import React, { Component } from "react"; import { connect } from "react-redux"; import { RemoteService } from "../../remote"; import "./Automations.css"; diff --git a/smart-hut/src/components/dashboard/DevicePanel.js b/smart-hut/src/components/dashboard/DevicePanel.js index 15206b8..73196df 100644 --- a/smart-hut/src/components/dashboard/DevicePanel.js +++ b/smart-hut/src/components/dashboard/DevicePanel.js @@ -29,7 +29,7 @@ class DevicePanel extends Component { return ; })} {!this.props.isActiveRoomHome ? ( - + diff --git a/smart-hut/src/components/dashboard/HostsPanel.js b/smart-hut/src/components/dashboard/HostsPanel.js new file mode 100644 index 0000000..ca63068 --- /dev/null +++ b/smart-hut/src/components/dashboard/HostsPanel.js @@ -0,0 +1,51 @@ +import React, { Component } from "react"; +import { connect } from "react-redux"; +import { RemoteService } from "../../remote"; +import { Card, Segment, Header, Icon } from "semantic-ui-react"; +import Device from "../../components/dashboard/devices/Device"; + +class HostsPanel extends Component { + componentDidUpdate(oldProps) { + if ( + oldProps.activeHost !== this.props.activeHost && + this.props.activeHost !== -1 + ) { + this.props.fetchDevices(null, this.props.activeHost).catch(console.error); + this.props.fetchAllRooms(this.props.activeHost).catch(console.error); + } + } + + render() { + return ( + + {this.props.isActiveDefaultHost && ( + +
+ + Please select a host to visit on the left. +
+
+ )} + {this.props.hostDeviceIds.map((id) => { + return ( + + ); + })} +
+ ); + } +} + +const mapStateToProps = (state, _) => ({ + isActiveDefaultHost: state.active.activeHost === -1, + activeHost: state.active.activeHost, + hostDevices: state.hostDevices, + hostDeviceIds: Object.keys(state.hostDevices[state.active.activeHost] || {}), +}); +const HostsPanelContainer = connect(mapStateToProps, RemoteService)(HostsPanel); +export default HostsPanelContainer; diff --git a/smart-hut/src/components/dashboard/NewSceneDevice.js b/smart-hut/src/components/dashboard/NewSceneDevice.js index d096bd8..8ce7321 100644 --- a/smart-hut/src/components/dashboard/NewSceneDevice.js +++ b/smart-hut/src/components/dashboard/NewSceneDevice.js @@ -48,6 +48,7 @@ class NewSceneDevice extends Component { handleOpen = () => { this.setState({ openModal: true }); }; + handleClose = () => { this.setState({ openModal: false }); }; diff --git a/smart-hut/src/components/dashboard/ScenesPanel.js b/smart-hut/src/components/dashboard/ScenesPanel.js index 1950f6b..a9b2210 100644 --- a/smart-hut/src/components/dashboard/ScenesPanel.js +++ b/smart-hut/src/components/dashboard/ScenesPanel.js @@ -3,7 +3,7 @@ import { connect } from "react-redux"; import { RemoteService } from "../../remote"; import Device from "./devices/Device"; import NewSceneDevice from "./NewSceneDevice"; -import { Grid, Button, Card, Segment, Header } from "semantic-ui-react"; +import { Button, Card, Segment, Header, Icon } from "semantic-ui-react"; class ScenesPanel extends Component { constructor(props) { @@ -42,7 +42,12 @@ class ScenesPanel extends Component {
) : ( - Welcome to the Scene View, you add a Scene + +
+ + Please select a scene on the left or add a new one. +
+
)} {!this.props.isActiveDefaultScene ? this.props.sceneStates.map((e, i) => { @@ -60,7 +65,6 @@ const mapStateToProps = (state, _) => ({ const stateArray = [ ...state.scenes[state.active.activeScene].sceneStates, ].sort(); - console.log("STATESCENE", stateArray); return stateArray.map((id) => state.sceneStates[id]); } else { return []; diff --git a/smart-hut/src/components/dashboard/devices/Curtain.js b/smart-hut/src/components/dashboard/devices/Curtain.js index d475455..45850f6 100644 --- a/smart-hut/src/components/dashboard/devices/Curtain.js +++ b/smart-hut/src/components/dashboard/devices/Curtain.js @@ -2,6 +2,7 @@ import React, { Component } from "react"; import "./Curtains.css"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; +import mapStateToProps from "../../../deviceProps"; class Curtain extends Component { constructor(props) { @@ -100,6 +101,7 @@ class Curtain extends Component { {Math.round(this.props.stateOrDevice.intensity)}% ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, - //device: state.devices[ownProps.id], -}); const CurtainContainer = connect(mapStateToProps, RemoteService)(Curtain); export default CurtainContainer; diff --git a/smart-hut/src/components/dashboard/devices/Device.js b/smart-hut/src/components/dashboard/devices/Device.js index dc2af8b..dc820fa 100644 --- a/smart-hut/src/components/dashboard/devices/Device.js +++ b/smart-hut/src/components/dashboard/devices/Device.js @@ -7,10 +7,11 @@ import Switcher from "./Switch"; import Videocam from "./Videocam"; import Curtains from "./Curtain"; import Thermostat from "./Thermostats"; -import { Segment, Grid, Header, Button, Icon, Card } from "semantic-ui-react"; +import { Header, Button, Icon, Card } from "semantic-ui-react"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; import DeviceSettingsModal from "./DeviceSettingsModal"; +import mapStateToProps from "../../../deviceProps"; const centerComponent = { marginLeft: "50%", @@ -41,75 +42,37 @@ class Device extends React.Component { } deleteState() { - console.log("alpaca ", this.props); this.props.deleteState(this.props.id, this.props.stateOrDevice.kind); } renderDeviceComponent() { - switch ( - this.props.tab === "Devices" - ? this.props.stateOrDevice.kind - : this.props.type - ) { - case "curtains": - return ( - - ); - case "thermostat": - return ( - - ); - case "regularLight": - return ( - - ); - case "sensor": - return ( - - ); - case "motionSensor": - return ; - case "buttonDimmer": - return ( - - ); - case "knobDimmer": - return ( - - ); - case "smartPlug": - return ( - - ); - case "switch": - return ( - - ); - case "dimmableLight": - return ; - case "securityCamera": - return ( - - ); - default: - throw new Error("Device type unknown"); + const mapKindToComponent = { + curtains: Curtains, + thermostat: Thermostat, + regularLight: Light, + sensor: Sensor, + motionSensor: Sensor, + buttonDimmer: ButtonDimmer, + knobDimmer: KnobDimmer, + smartPlug: SmartPlug, + switch: Switcher, + dimmableLight: Light, + securityCamera: Videocam, + }; + + if (!(this.props.type in mapKindToComponent)) { + throw new Error(`device kind ${this.props.type} not known`); } + + return React.createElement( + mapKindToComponent[this.props.type], + { + tab: this.props.tab, + id: this.props.id, + hostId: this.props.hostId, + }, + "" + ); } deviceDescription() { @@ -151,89 +114,40 @@ class Device extends React.Component { } get deviceName() { - return this.props.tab === "Devices" - ? this.props.stateOrDevice.name - : this.props.device.name; + return this.props.device.name; } render() { - { - return ( - - - -
{this.deviceName}
- {this.props.tab === "Scenes" ? ( -
{this.props.roomName}
- ) : ( - "" - )} -
+ return ( + + + +
{this.deviceName}
+
{this.props.roomName}
+
- - {this.renderDeviceComponent()} - -
- - {this.props.tab === "Devices" - ? this.deviceDescription() - : this.stateDescription()} - - {this.props.tab === "Devices" ? ( - - ) : null} -
- ); - } + + {this.renderDeviceComponent()} + +
+ + {this.props.tab === "Devices" + ? this.deviceDescription() + : this.props.tab === "Scenes" && this.stateDescription()} + + {this.props.tab === "Devices" ? ( + + ) : null} +
+ ); } } -const mapStateToProps = (state, ownProps) => ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, - get device() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.devices[state.sceneStates[ownProps.id].deviceId]; - } - }, - get roomName() { - if (state.active.activeTab === "Scenes") { - const device = state.devices[state.sceneStates[ownProps.id].deviceId]; - return state.rooms[device.roomId].name; - } else { - return ""; - } - }, - get type() { - if (state.active.activeTab === "Scenes") { - if (state.sceneStates[ownProps.id]) { - //console.log(state.sceneStates[ownProps.id], ownProps.id); - const id = state.sceneStates[ownProps.id].deviceId; - //console.log(id, state.devices[id].kind); - return state.devices[id].kind; - } else { - return ""; - } - } else { - return null; - } - }, -}); const DeviceContainer = connect(mapStateToProps, RemoteService)(Device); export default DeviceContainer; diff --git a/smart-hut/src/components/dashboard/devices/Dimmer.js b/smart-hut/src/components/dashboard/devices/Dimmer.js index d4762fd..60fdc32 100644 --- a/smart-hut/src/components/dashboard/devices/Dimmer.js +++ b/smart-hut/src/components/dashboard/devices/Dimmer.js @@ -29,6 +29,7 @@ import { } from "./DimmerStyle"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; +import mapStateToProps from "../../../deviceProps"; export class ButtonDimmerComponent extends Component { increaseIntensity = () => { @@ -104,7 +105,7 @@ export class KnobDimmerComponent extends Component { ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, -}); const conn = connect(mapStateToProps, RemoteService); export const KnobDimmer = conn(KnobDimmerComponent); diff --git a/smart-hut/src/components/dashboard/devices/Light.js b/smart-hut/src/components/dashboard/devices/Light.js index ebf0565..911f155 100644 --- a/smart-hut/src/components/dashboard/devices/Light.js +++ b/smart-hut/src/components/dashboard/devices/Light.js @@ -31,6 +31,7 @@ import { } from "./LightStyle"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; +import mapStateToProps from "../../../deviceProps"; class Light extends Component { constructor(props) { @@ -62,14 +63,17 @@ class Light extends Component { } get intensity() { - return this.props.stateOrDevice.intensity || 0; + return this.state.intensity || 0; } onClickDevice = () => { const on = !this.turnedOn; - if (this.props.tab === "Devices") { + if (this.props.tab !== "Scenes") { this.props - .saveDevice({ ...this.props.stateOrDevice, on }) + .saveDevice( + { ...this.props.stateOrDevice, on }, + this.props.tab === "Hosts" ? this.props.activeHost : null + ) .catch((err) => console.error("regular light update error", err)); } else { if (this.props.device.kind === "regularLight") { @@ -114,12 +118,14 @@ class Light extends Component { saveIntensity = () => { const intensity = Math.round(this.state.intensity); - if (this.props.tab === "Devices") { + if (this.props.tab !== "Scenes") { this.props - .saveDevice({ ...this.props.stateOrDevice, intensity }) - .catch((err) => console.error("regular light update error", err)); + .saveDevice( + { ...this.props.stateOrDevice, intensity }, + this.props.tab === "Hosts" ? this.props.activeHost : null + ) + .catch((err) => console.error("dimmable light update error", err)); } else { - console.log("CIAOOOOOOOOO", this.props.stateOrDevice); this.props .updateState( { id: this.props.stateOrDevice.id, intensity: intensity }, @@ -136,9 +142,9 @@ class Light extends Component {
@@ -184,22 +190,5 @@ class Light extends Component { } } -const mapStateToProps = (state, ownProps) => ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, - get device() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.devices[state.sceneStates[ownProps.id].deviceId]; - } - }, -}); - const LightContainer = connect(mapStateToProps, RemoteService)(Light); export default LightContainer; diff --git a/smart-hut/src/components/dashboard/devices/Sensor.js b/smart-hut/src/components/dashboard/devices/Sensor.js index ad80a4c..f171b6a 100644 --- a/smart-hut/src/components/dashboard/devices/Sensor.js +++ b/smart-hut/src/components/dashboard/devices/Sensor.js @@ -38,6 +38,7 @@ import { import { Image } from "semantic-ui-react"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; +import mapStateToProps from "../../../deviceProps"; class Sensor extends Component { constructor(props) { @@ -194,14 +195,5 @@ class Sensor extends Component { } } -const mapStateToProps = (state, ownProps) => ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, -}); const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor); export default SensorContainer; diff --git a/smart-hut/src/components/dashboard/devices/SmartPlug.js b/smart-hut/src/components/dashboard/devices/SmartPlug.js index 5074429..82bbd3d 100644 --- a/smart-hut/src/components/dashboard/devices/SmartPlug.js +++ b/smart-hut/src/components/dashboard/devices/SmartPlug.js @@ -15,6 +15,7 @@ import { } from "./SmartPlugStyle"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; +import mapStateToProps from "../../../deviceProps"; class SmartPlug extends Component { constructor(props) { @@ -51,7 +52,7 @@ class SmartPlug extends Component { render() { return ( - + {} : this.onClickDevice}> Smart Plug @@ -70,21 +71,5 @@ class SmartPlug extends Component { } } -const mapStateToProps = (state, ownProps) => ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, - get device() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.devices[state.sceneStates[ownProps.id].deviceId]; - } - }, -}); const SmartPlugContainer = connect(mapStateToProps, RemoteService)(SmartPlug); export default SmartPlugContainer; diff --git a/smart-hut/src/components/dashboard/devices/Switch.js b/smart-hut/src/components/dashboard/devices/Switch.js index 113c1f1..8c4e4d9 100644 --- a/smart-hut/src/components/dashboard/devices/Switch.js +++ b/smart-hut/src/components/dashboard/devices/Switch.js @@ -11,6 +11,7 @@ import { Image } from "semantic-ui-react"; import { imageStyle, nameStyle, turnedOnStyle } from "./SwitchStyle"; import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; +import mapStateToProps from "../../../deviceProps"; class Switch extends Component { constructor(props) { @@ -37,7 +38,7 @@ class Switch extends Component { render() { return ( - + {} : this.onClickDevice}> Switch @@ -55,8 +56,5 @@ class Switch extends Component { } } -const mapStateToProps = (state, ownProps) => ({ - device: state.devices[ownProps.id], -}); const SwitchContainer = connect(mapStateToProps, RemoteService)(Switch); export default SwitchContainer; diff --git a/smart-hut/src/components/dashboard/devices/Thermostat.css b/smart-hut/src/components/dashboard/devices/Thermostat.css index 15c4314..cf6fa0b 100644 --- a/smart-hut/src/components/dashboard/devices/Thermostat.css +++ b/smart-hut/src/components/dashboard/devices/Thermostat.css @@ -28,4 +28,4 @@ background: white; border-radius: 5px; } -*/ \ No newline at end of file +*/ diff --git a/smart-hut/src/components/dashboard/devices/Thermostats.js b/smart-hut/src/components/dashboard/devices/Thermostats.js index 73423b5..f159012 100644 --- a/smart-hut/src/components/dashboard/devices/Thermostats.js +++ b/smart-hut/src/components/dashboard/devices/Thermostats.js @@ -5,6 +5,7 @@ import { connect } from "react-redux"; import "./Thermostat.css"; import Slider from "react-rangeslider"; import "react-rangeslider/lib/index.css"; +import mapStateToProps from "../../../deviceProps"; import { stateTag, @@ -24,7 +25,6 @@ class Thermostats extends Component { measuredTemperature: this.props.device.measuredTemperature, useExternalSensors: this.props.device.useExternalSensors, }; - console.log(this.state); this.setMode = this.setMode.bind(this); this.setTargetTemperature = this.setTargetTemperature.bind(this); } @@ -33,13 +33,11 @@ class Thermostats extends Component { if (this.state.timeout) { clearTimeout(this.state.timeout); } - console.log(mode); //i came to the conclusion that is not possible to set mode. // Good job Jacob (Claudio) //this.mode = "HEATING"; const turnOn = mode; - console.log(turnOn); if (this.props.tab === "Devices") { this.props .saveDevice({ ...this.props.stateOrDevice, turnOn }) @@ -87,43 +85,6 @@ class Thermostats extends Component { } } - setTargetTemperature(newTemp) { - if (this.state.timeout) { - clearTimeout(this.state.timeout); - } - - this.setState({ - newTemp, - timeout: setTimeout(() => { - this.saveTargetTemperature(newTemp); - this.setState({ - targetTemperature: this.state.targetTemperature, - timeout: null, - }); - }, 100), - }); - } - - saveTargetTemperature(targetTemperature) { - if (this.props.tab === "Devices") { - this.props - .saveDevice({ - ...this.props.device, - turnOn: this.props.device.mode !== "OFF", - targetTemperature, - }) - .catch((err) => console.error("thermostat update error", err)); - } else { - this.props.updateState( - { - id: this.props.stateOrDevice.id, - targetTemperature: targetTemperature, - }, - this.props.stateOrDevice.kind - ); - } - } - setTargetTemperature() { this.saveTargetTemperature(this.state.targetTemperature); } @@ -138,6 +99,7 @@ class Thermostats extends Component {

Thermostat {this.props.tab === "Devices" ? ( - {this.props.tab === "Devices" + {this.props.tab !== "Scenes" ? this.props.device.mode : this.props.stateOrDevice.on ? "WILL TURN ON" @@ -181,24 +144,6 @@ class Thermostats extends Component { } } -const mapStateToProps = (state, ownProps) => ({ - get stateOrDevice() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.sceneStates[ownProps.id]; - } - }, - get device() { - if (state.active.activeTab === "Devices") { - return state.devices[ownProps.id]; - } else { - return state.devices[state.sceneStates[ownProps.id].deviceId]; - } - }, - activeTab: state.activeTab, -}); - const ThermostatContainer = connect( mapStateToProps, RemoteService diff --git a/smart-hut/src/components/dashboard/devices/Videocam.js b/smart-hut/src/components/dashboard/devices/Videocam.js index 4313088..610fb28 100644 --- a/smart-hut/src/components/dashboard/devices/Videocam.js +++ b/smart-hut/src/components/dashboard/devices/Videocam.js @@ -7,6 +7,7 @@ import { RemoteService } from "../../../remote"; import { endpointURL } from "../../../endpoint"; import { connect } from "react-redux"; import VideocamModal from "./VideocamModal"; +import mapStateToProps from "../../../deviceProps"; class Videocam extends Component { constructor(props) { @@ -86,12 +87,5 @@ class Videocam extends Component { } } -const mapStateToProps = (state, ownProps) => ({ - device: - ownProps.tab === "Devices" - ? state.devices[ownProps.id] - : state.devices[state.sceneStates[ownProps.id].deviceId], - state: state.sceneStates[ownProps.id], -}); const VideocamContainer = connect(mapStateToProps, RemoteService)(Videocam); export default VideocamContainer; diff --git a/smart-hut/src/deviceProps.js b/smart-hut/src/deviceProps.js new file mode 100644 index 0000000..352e017 --- /dev/null +++ b/smart-hut/src/deviceProps.js @@ -0,0 +1,69 @@ +function getStateOrDevice(state, ownProps) { + switch (state.active.activeTab) { + case "Devices": + return state.devices[ownProps.id]; + case "Scenes": + return state.sceneStates[ownProps.id]; + case "Hosts": + return state.hostDevices[ownProps.hostId][ownProps.id]; + default: + throw new Error( + `stateOrDevice has no value in tab "${state.active.activeTab}"` + ); + } +} + +function getDevice(state, ownProps) { + switch (state.active.activeTab) { + case "Scenes": + return state.devices[getStateOrDevice(state, ownProps).deviceId]; + case "Devices": + case "Hosts": + return getStateOrDevice(state, ownProps); + default: + throw new Error(`device has no value in tab "${state.active.activeTab}"`); + } +} + +function getRoomName(state, ownProps) { + switch (state.active.activeTab) { + case "Scenes": + case "Devices": + return (state.rooms[getDevice(state, ownProps).roomId] || {}).name; + case "Hosts": + const hostRooms = state.hostRooms[ownProps.hostId]; + if (!hostRooms) return ""; + const room = hostRooms[getDevice(state, ownProps).roomId]; + if (!room) return ""; + return room.name; + default: + throw new Error( + `room name has no value in tab "${state.active.activeTab}"` + ); + } +} + +export default function mapStateToProps(state, ownProps) { + return { + activeHost: state.active.activeHost, + get stateOrDevice() { + return getStateOrDevice(state, ownProps); + }, + get device() { + return getDevice(state, ownProps); + }, + get roomName() { + return getRoomName(state, ownProps); + }, + get type() { + return getDevice(state, ownProps).kind; + }, + get disabled() { + return ( + ownProps.tab === "Hosts" && + ["dimmableLight", "light"].indexOf(getDevice(state, ownProps).kind) === + -1 + ); + }, + }; +} diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index 66d47bd..5eae766 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -261,13 +261,22 @@ export const RemoteService = { /** * Fetches all rooms that belong to this user. This call does not * populate the devices attribute in rooms. + * @param {Number|null} hostId the user id of the host we need to fetch the rooms from. + * Null if we need to fetch our own rooms. * @returns {Promise} promise that resolves to void and rejects * with user-fiendly errors as a RemoteError */ - fetchAllRooms: () => { + fetchAllRooms: (hostId = null) => { return (dispatch) => { - return Endpoint.get("/room") - .then((res) => void dispatch(actions.roomsUpdate(res.data))) + return Endpoint.get("/room", hostId ? { hostId } : null) + .then( + (res) => + void dispatch( + hostId + ? actions.hostRoomsUpdate(hostId, res.data) + : actions.roomsUpdate(res.data) + ) + ) .catch((err) => { console.error("Fetch all rooms error", err); throw new RemoteError(["Network error"]); @@ -297,13 +306,25 @@ export const RemoteService = { * 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 + * @param {Number|null} hostId the user id of the owner of the devices to get + * (can be used for host view) * @returns {Promise} promise that resolves to void and rejects * with user-fiendly errors as a RemoteError */ - fetchDevices: (roomId = null) => { + fetchDevices: (roomId = null, hostId = null) => { return (dispatch) => { - return Endpoint.get(roomId ? `/room/${roomId}/device` : "/device") - .then((res) => void dispatch(actions.devicesUpdate(roomId, res.data))) + return Endpoint.get( + roomId ? `/room/${roomId}/device` : "/device", + hostId ? { hostId } : null + ) + .then( + (res) => + void dispatch( + !hostId + ? actions.devicesUpdate(roomId, res.data, hostId) + : actions.hostDevicesUpdate(hostId, res.data) + ) + ) .catch((err) => { console.error(`Fetch devices roomId=${roomId} error`, err); throw new RemoteError(["Network error"]); @@ -346,6 +367,56 @@ export const RemoteService = { }; }, + /** + * Fetches all hosts of a particular user. + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchHosts: () => { + return (dispatch) => { + return Endpoint.get(`/user/hosts`) + .then((res) => void dispatch(actions.hostsUpdate(res.data))) + .catch((err) => { + console.error(`Fetch hosts error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Fetches all guests of a particular user. + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + fetchGuests: () => { + return (dispatch) => { + return Endpoint.get(`/user/guests`) + .then((res) => void dispatch(actions.guestsUpdate(res.data))) + .catch((err) => { + console.error(`Fetch guests error`, err); + throw new RemoteError(["Network error"]); + }); + }; + }, + + /** + * Adds the current user as a guest to another user + * identified through a user id. + * @param {Number[]} userId the users to add. + * @returns {Promise} promise that resolves to void and rejects + * with user-fiendly errors as a RemoteError + */ + updateGuests: (userIds) => { + return (dispatch) => { + return Endpoint.put(`/user/guests`, {}, { ids: userIds }) + .then((res) => void dispatch(actions.guestsUpdate(res.data))) + .catch((err) => { + console.error(`Guest save error`, err); + throw new RemoteError(["Network Error"]); + }); + }; + }, + /** * Creates/Updates a room with the given data * @param {String} data.name the room's name, @@ -458,16 +529,24 @@ export const RemoteService = { * @returns {Promise} promise that resolves to the saved device and rejects * with user-fiendly errors as a RemoteError */ - saveDevice: (data) => { + saveDevice: (data, hostId = null) => { return (dispatch) => { let url = "/device"; if ((data.id && data.flowType === "OUTPUT") || !data.id) { url = "/" + data.kind; } - return Endpoint[data.id ? "put" : "post"](url, {}, data) + return Endpoint[data.id ? "put" : "post"]( + url, + hostId ? { hostId } : {}, + data + ) .then((res) => { - dispatch(actions.deviceSave(res.data)); + dispatch( + hostId + ? actions.hostDeviceSave(hostId, res.data) + : actions.deviceSave(res.data) + ); return res.data; }) .catch((err) => { @@ -793,6 +872,15 @@ for (const key in RemoteService) { } export class Forms { + static fetchAllUsers() { + return Endpoint.get(`/user`) + .then((res) => res.data) + .catch((err) => { + console.error(`Fetch users error`, err); + throw new RemoteError(["Network error"]); + }); + } + /** * Attempts to create a new user from the given data. * This method does not update the global state, diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 350cc7b..a8b4e66 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -3,6 +3,7 @@ import thunk from "redux-thunk"; import update from "immutability-helper"; import reduxWebSocket, { connect } from "@giantmachines/redux-websocket"; import { socketURL } from "./endpoint"; +import actions from "./storeActions"; function reducer(previousState, action) { let newState, change; @@ -99,6 +100,20 @@ function reducer(previousState, action) { createOrUpdateRoom(room); } break; + case "HOST_ROOMS_UPDATE": + change = { + hostRooms: { + [action.hostId]: { $set: {} }, + }, + }; + const rooms = change.hostRooms[action.hostId].$set; + + for (const room of action.rooms) { + rooms[room.id] = room; + } + + newState = update(previousState, change); + break; case "SCENES_UPDATE": newState = previousState; for (const scene of action.scenes) { @@ -255,7 +270,27 @@ function reducer(previousState, action) { newState = update(newState, change); break; + case "HOST_DEVICES_UPDATE": + newState = action.partial + ? previousState + : update(previousState, { + hostDevices: { [action.hostId]: { $set: {} } }, + }); + newState.hostDevices[action.hostId] = + newState.hostDevices[action.hostId] || {}; + change = { + hostDevices: { + [action.hostId]: {}, + }, + }; + const deviceMap = change.hostDevices[action.hostId]; + for (const device of action.devices) { + deviceMap[device.id] = { $set: device }; + } + + newState = update(newState, change); + break; case "AUTOMATION_UPDATE": const automations = {}; for (const automation of action.automations) { @@ -299,6 +334,28 @@ function reducer(previousState, action) { } newState = update(previousState, change); break; + case "HOST_DEVICE_SAVE": + change = { + hostDevices: { + [action.hostId]: { + [action.device.id]: { + $set: action.device, + }, + }, + }, + }; + newState = update(previousState, change); + break; + case "HOST_DEVICES_DELETE": + change = { + hostDevices: { + [action.hostId]: { + $unset: [action.deviceIds], + }, + }, + }; + newState = update(previousState, change); + break; case "AUTOMATION_SAVE": change = { @@ -433,51 +490,62 @@ function reducer(previousState, action) { case "LOGOUT": newState = update(initState, {}); break; - case "SET_ACTIVE_ROOM": + case "SET_ACTIVE": newState = update(previousState, { active: { - activeRoom: { - $set: action.activeRoom, - }, - }, - }); - break; - case "SET_ACTIVE_TAB": - newState = update(previousState, { - active: { - activeTab: { - $set: action.activeTab, - }, - }, - }); - break; - case "SET_ACTIVE_SCENE": - newState = update(previousState, { - active: { - activeScene: { - $set: action.activeScene, - }, - }, - }); - break; - case "SET_ACTIVE_AUTOMATION": - newState = update(previousState, { - active: { - activeAutomation: { - $set: action.activeAutomation, + [action.key]: { + $set: action.value, }, }, }); break; case "REDUX_WEBSOCKET::MESSAGE": - const devices = JSON.parse(action.payload.message); - //console.log("socket", JSON.stringify(devices, null, 2)); + const allDevices = JSON.parse(action.payload.message); + const devices = allDevices.filter( + (d) => + (d.fromHostId === null || d.fromHostId === undefined) && !d.deleted + ); + const hostDevicesMapByHostId = allDevices + .filter((d) => d.fromHostId) + .reduce((a, e) => { + const hostId = e.fromHostId; + //delete e.fromHostId; + a[hostId] = a[hostId] || { updated: [], deletedIds: [] }; + if (e.deleted) { + a[hostId].deletedIds.push(e.id); + } else { + a[hostId].updated.push(e); + } + return a; + }, {}); newState = reducer(previousState, { type: "DEVICES_UPDATE", partial: true, devices, }); + for (const hostId in hostDevicesMapByHostId) { + if (hostDevicesMapByHostId[hostId].updated.length > 0) + newState = reducer(newState, { + type: "HOST_DEVICES_UPDATE", + devices: hostDevicesMapByHostId[hostId].updated, + partial: true, + hostId, + }); + if (hostDevicesMapByHostId[hostId].deletedIds.length > 0) { + newState = reducer(newState, { + type: "HOST_DEVICES_DELETE", + deviceIds: hostDevicesMapByHostId[hostId].deletedIds, + partial: true, + hostId, + }); + } + } + break; + case "HG_UPDATE": + newState = update(previousState, { + [action.key]: { $set: action.value }, + }); break; default: console.warn(`Action type ${action.type} unknown`, action); @@ -497,6 +565,7 @@ const initState = { activeTab: "Devices", activeScene: -1, activeAutomation: -1, + activeHost: -1, }, login: { loggedIn: false, @@ -513,6 +582,14 @@ const initState = { devices: {}, /** @type {[integer]SceneState} */ sceneStates: {}, + /** @type {User[]} */ + guests: [], + /** @type {User[]} */ + hosts: [], + /** @type {[integer]Device} */ + hostDevices: {}, + /** @type {[integer]Eoom} */ + hostRooms: {}, }; function createSmartHutStore() { diff --git a/smart-hut/src/storeActions.js b/smart-hut/src/storeActions.js index e8ee30a..64c2a52 100644 --- a/smart-hut/src/storeActions.js +++ b/smart-hut/src/storeActions.js @@ -25,6 +25,11 @@ const actions = { type: "DEVICE_SAVE", device, }), + hostDeviceSave: (hostId, device) => ({ + type: "HOST_DEVICE_SAVE", + hostId, + device, + }), triggerSave: (automation) => ({ type: "TRIGGER_SAVE", automation, @@ -58,6 +63,11 @@ const actions = { devices, partial, }), + hostDevicesUpdate: (hostId, devices) => ({ + type: "HOST_DEVICES_UPDATE", + hostId, + devices, + }), stateDelete: (stateId) => ({ type: "STATE_DELETE", stateId, @@ -71,6 +81,11 @@ const actions = { type: "ROOMS_UPDATE", rooms, }), + hostRoomsUpdate: (hostId, rooms) => ({ + type: "HOST_ROOMS_UPDATE", + hostId, + rooms, + }), roomDelete: (roomId) => ({ type: "ROOM_DELETE", roomId, @@ -91,25 +106,48 @@ const actions = { type: "DEVICE_DELETE", deviceId, }), + hostsUpdate: (hosts) => ({ + type: "HG_UPDATE", + key: "hosts", + value: hosts, + }), + guestsUpdate: (hosts) => ({ + type: "HG_UPDATE", + key: "guests", + value: hosts, + }), + getHostDevices: (host) => ({ + type: "GET_HOST_DEVICES", + host, + }), + guestUpdate: (guests) => ({ + type: "HG_UPDATE", + key: "guests", + value: guests, + }), }; export const appActions = { // -1 for home view setActiveRoom: (activeRoom = -1) => ({ - type: "SET_ACTIVE_ROOM", - activeRoom, + type: "SET_ACTIVE", + key: "activeRoom", + value: activeRoom, }), setActiveTab: (activeTab) => ({ - type: "SET_ACTIVE_TAB", - activeTab, + type: "SET_ACTIVE", + key: "activeTab", + value: activeTab, }), setActiveScene: (activeScene = -1) => ({ - type: "SET_ACTIVE_SCENE", - activeScene, + type: "SET_ACTIVE", + key: "activeScene", + value: activeScene, }), - setActiveAutomations: (activeAutomation = -1) => ({ - type: "SET_ACTIVE_AUTOMATION", - activeAutomation, + setActiveHost: (activeHost = -1) => ({ + type: "SET_ACTIVE", + key: "activeHost", + value: activeHost, }), }; diff --git a/smart-hut/src/views/Dashboard.js b/smart-hut/src/views/Dashboard.js index bb92ea8..ab2c952 100644 --- a/smart-hut/src/views/Dashboard.js +++ b/smart-hut/src/views/Dashboard.js @@ -2,8 +2,10 @@ import React, { Component } from "react"; import DevicePanel from "../components/dashboard/DevicePanel"; import ScenesPanel from "../components/dashboard/ScenesPanel"; import AutomationsPanel from "../components/dashboard/AutomationsPanel"; +import HostsPanel from "../components/dashboard/HostsPanel"; import Navbar from "./Navbar"; import ScenesNavbar from "./ScenesNavbar"; +import HostsNavbar from "./HostsNavbar"; import MyHeader from "../components/HeaderController"; import { Grid, Responsive, Button, Menu } from "semantic-ui-react"; import { @@ -19,7 +21,6 @@ class Dashboard extends Component { constructor(props) { super(props); this.state = this.initialState; - this.setInitialState(); this.activeTab = "Devices"; this.selectTab = this.selectTab.bind(this); } @@ -55,6 +56,8 @@ class Dashboard extends Component { return ; case "Automations": return ; + case "Hosts": + return ; default: return

ERROR

; } @@ -66,6 +69,8 @@ class Dashboard extends Component { return ; case "Scenes": return ; + case "Hosts": + return ; default: return

ERROR

; } @@ -87,7 +92,7 @@ class Dashboard extends Component { - + + @@ -153,6 +164,16 @@ class Dashboard extends Component { color={this.activeTab === "Automations" ? "yellow" : "grey"} onClick={this.selectTab} /> +