diff --git a/smart-hut/src/components/HeaderController.js b/smart-hut/src/components/HeaderController.js index 08dc3af..d6ef4c5 100644 --- a/smart-hut/src/components/HeaderController.js +++ b/smart-hut/src/components/HeaderController.js @@ -20,9 +20,6 @@ const TitleImage = () => ; export class MyHeader extends React.Component { constructor(props) { super(props); - this.state = { - username: "", - }; this.getInfo(); this.logout = this.logout.bind(this); @@ -57,7 +54,7 @@ export class MyHeader extends React.Component { - {this.state.username} + {this.props.username} Logout @@ -79,10 +76,10 @@ export class MyHeader extends React.Component { - {this.state.username} + {this.props.username} - Logout + Logout diff --git a/smart-hut/src/components/RoomModal.js b/smart-hut/src/components/RoomModal.js index 350edb1..eabdb89 100644 --- a/smart-hut/src/components/RoomModal.js +++ b/smart-hut/src/components/RoomModal.js @@ -32,7 +32,7 @@ class RoomModal extends Component { get initialState() { return { - selectedIcon: "home", + selectedIcon: this.type === "new" ? "home" : this.props.room.icon, name: this.type === "new" ? "New Room" : this.props.room.name, img: this.type === "new" ? null : this.props.room.image, openModal: false, @@ -41,9 +41,11 @@ class RoomModal extends Component { removeImage(e) { e.preventDefault(); - this.setState(update(this.state, { - image: {$set: NO_IMAGE} - })); + this.setState( + update(this.state, { + image: { $set: null }, + }) + ); } setInitialState() { @@ -61,36 +63,38 @@ class RoomModal extends Component { image: this.state.img, }; - this.props.saveRoom(data, null) + this.props + .saveRoom(data, null) .then(() => { this.setInitialState(); this.closeModal(); }) - .catch((err) => console.error('error in creating room', err)); + .catch((err) => console.error("error in creating room", err)); }; modifyRoomModal = (e) => { let data = { - icon: - this.state.selectedIcon === "" - ? this.props.room.icon - : this.state.selectedIcon, - name: this.state.name === "" ? this.props.room.name : this.state.name, + icon: this.state.selectedIcon, + name: this.state.name, image: this.state.img, }; - this.props.saveRoom(data, this.props.id) + console.log("data", data); + + this.props + .saveRoom(data, this.props.id) .then(() => { this.setInitialState(); this.closeModal(); }) - .catch((err) => console.error('error in updating room', err)); + .catch((err) => console.error("error in updating room", err)); }; deleteRoom = (e) => { - // no need to close modal since this room modal instance will be deleted - this.props.deleteRoom(this.props.id) - .catch((err) => console.error('error in deleting room', err)); + this.props + .deleteRoom(this.props.id) + .then(() => this.closeModal()) + .catch((err) => console.error("error in deleting room", err)); }; changeSomething = (event) => { @@ -101,7 +105,6 @@ class RoomModal extends Component { closeModal = (e) => { this.setState({ openModal: false }); - this.updateIcon("home"); }; openModal = (e) => { @@ -172,7 +175,7 @@ class RoomModal extends Component { ) : null} - + {this.type === "new" ? "Add new room" : "Modify room"} @@ -192,7 +195,7 @@ class RoomModal extends Component { Insert an image of the room: this.fileInputRef.current.click()} /> @@ -207,9 +210,9 @@ class RoomModal extends Component { onChange={this.getBase64.bind(this)} /> - {this.state.img ? + {this.state.img ? ( Remove image - : null } + ) : null} @@ -244,9 +247,7 @@ class RoomModal extends Component { {" "} @@ -264,7 +265,7 @@ const setActiveRoom = (activeRoom) => { }; const mapStateToProps = (state, ownProps) => ({ - room: ownProps.id ? state.rooms[ownProps.id] : null + room: ownProps.id ? state.rooms[ownProps.id] : null, }); const RoomModalContainer = connect( mapStateToProps, diff --git a/smart-hut/src/components/dashboard/devices/DeviceSettingsModal.js b/smart-hut/src/components/dashboard/devices/DeviceSettingsModal.js index dd144f8..fcf29a0 100644 --- a/smart-hut/src/components/dashboard/devices/DeviceSettingsModal.js +++ b/smart-hut/src/components/dashboard/devices/DeviceSettingsModal.js @@ -81,10 +81,9 @@ class DeviceSettingsModal extends Component { } deleteDevice() { - // closing is not needed (and actually harmful due to memory leaks) - // since this component will be deleted anyways this.props .deleteDevice(this.props.device) + .then(() => this.setState({ open: false })) .catch((err) => console.error( `settings modal for device ${this.props.id} deletion error`, diff --git a/smart-hut/src/components/dashboard/devices/Dimmer.js b/smart-hut/src/components/dashboard/devices/Dimmer.js index 324e1e6..25ac89c 100644 --- a/smart-hut/src/components/dashboard/devices/Dimmer.js +++ b/smart-hut/src/components/dashboard/devices/Dimmer.js @@ -31,24 +31,23 @@ import { RemoteService } from "../../../remote"; import { connect } from "react-redux"; export class ButtonDimmerComponent extends Component { - increaseIntensity = () => { - this.props.device.buttonDimmerDim(this.props.id, "UP") - .catch((err) => console.error('button dimmer increase error', err)); + this.props + .buttonDimmerDim(this.props.id, "UP") + .catch((err) => console.error("button dimmer increase error", err)); }; decreaseIntensity = () => { - this.props.device.buttonDimmerDim(this.props.id, "DOWN") - .catch((err) => console.error('button dimmer decrease error', err)); + this.props + .buttonDimmerDim(this.props.id, "DOWN") + .catch((err) => console.error("button dimmer decrease error", err)); }; render() { return ( - - Button Dimmer - + Button Dimmer + @@ -61,19 +60,23 @@ export class ButtonDimmerComponent extends Component { } export class KnobDimmerComponent extends Component { - setIntensity = (newValue) => { const val = Math.round(newValue * 100); - this.props.device.knobDimmerDimTok(this.props.id, val) - .catch((err) => console.error('knob dimmer set intensity error', err)); + this.props + .knobDimmerDimTo(this.props.id, val) + .catch((err) => console.error("knob dimmer set intensity error", err)); }; + get intensity() { + return this.props.device.intensity || 0; + } + render() { return ( diff --git a/smart-hut/src/components/dashboard/devices/Light.js b/smart-hut/src/components/dashboard/devices/Light.js index 99e2ae1..0a62d8f 100644 --- a/smart-hut/src/components/dashboard/devices/Light.js +++ b/smart-hut/src/components/dashboard/devices/Light.js @@ -45,13 +45,14 @@ class Light extends Component { } get intensity() { - return this.props.device.intensity; + return this.props.device.intensity || 0; } onClickDevice = () => { - this.props.device.on = !this.turnedOn; - this.props.saveDevice({ ...this.props.device, on: !this.turnedOn }) - .catch((err) => console.error('regular light update error', err)) + const on = !this.turnedOn; + this.props + .saveDevice({ ...this.props.device, on }) + .catch((err) => console.error("regular light update error", err)); }; getIcon = () => { @@ -60,8 +61,9 @@ class Light extends Component { setIntensity = (newValue) => { const intensity = Math.round(newValue * 100); - this.props.saveDevice({ ...this.props.device, intensity }) - .catch((err) => console.error('intensity light update error', err)) + this.props + .saveDevice({ ...this.props.device, intensity }) + .catch((err) => console.error("intensity light update error", err)); }; render() { @@ -100,9 +102,7 @@ class Light extends Component { - - Light - + Light diff --git a/smart-hut/src/components/dashboard/devices/NewDevice.js b/smart-hut/src/components/dashboard/devices/NewDevice.js index b0fda2a..fc12782 100644 --- a/smart-hut/src/components/dashboard/devices/NewDevice.js +++ b/smart-hut/src/components/dashboard/devices/NewDevice.js @@ -88,65 +88,58 @@ class NewDevice extends Component { this.setState({ lightsAttached: d.value }); }; - createDevice() { + async createDevice() { // Connect to the backend and create device here. const data = { - params: { - name: this.state.deviceName, - }, - device: this.state.motion ? "motionSensor" : this.state.typeOfDevice, + id: null, + roomId: this.props.activeRoom, + name: this.state.deviceName, + kind: this.state.motion ? "motionSensor" : this.state.typeOfDevice, + }; + let outputs = null; + + const defaultNames = { + regularLight: "New regular light", + dimmableLight: "New intensity light", + smartPlug: "New smart Plug", + sensor: "New sensor", + switch: "New switch", + buttonDimmer: "New button dimmer", + knobDimmer: "New knob dimmer", }; + if (this.state.deviceName === "") { + data.name = defaultNames[this.state.typeOfDevice]; + } + switch (this.state.typeOfDevice) { - case "regularLight": - if (this.state.deviceName === "") { - data.params["name"] = "Regular Light"; - } - break; - case "smartPlug": - if (this.state.deviceName === "") { - data.params["name"] = "Smart Plug"; - } - break; case "dimmableLight": - if (this.state.deviceName === "") { - data.params["name"] = "Dimmable Light"; - } - data.params["intensity"] = 0; + data.intensity = 0; break; case "sensor": - if (this.state.deviceName === "") { - data.params["name"] = "Sensor"; - } if (!this.state.motion) { - data.params["sensor"] = this.state.typeOfSensor; - data.params["value"] = 0; + data.sensor = this.state.typeOfSensor; + data.value = 0; } break; case "switch": - if (this.state.deviceName === "") { - data.params["name"] = "Switch"; - } - data.params["lights"] = this.state.lightsAttached; - break; case "buttonDimmer": - if (this.state.deviceName === "") { - data.params["name"] = "Button Dimmer"; - } - data.params["lights"] = this.state.lightsAttached; - break; case "knobDimmer": - if (this.state.deviceName === "") { - data.params["name"] = "Knob Dimmer"; - } - data.params["lights"] = this.state.lightsAttached; + outputs = this.state.lightsAttached; break; default: break; } - this.props.addDevice(data); - this.resetState(); + try { + let newDevice = await this.props.saveDevice(data); + if (outputs) { + await this.props.connectOutputs(newDevice, outputs); + } + this.resetState(); + } catch (e) { + console.error("device creation error: ", e); + } } render() { @@ -380,10 +373,8 @@ class NewDevice extends Component { } const mapStateToProps = (state, _) => ({ - devices: Object.values(state.devices) + devices: Object.values(state.devices), + activeRoom: state.active.activeRoom, }); -const NewDeviceContainer = connect( - mapStateToProps, - RemoteService -)(NewDevice); -export default NewDeviceContainer; \ No newline at end of file +const NewDeviceContainer = connect(mapStateToProps, RemoteService)(NewDevice); +export default NewDeviceContainer; diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index 20ada12..ecd3074 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -11,7 +11,7 @@ class RemoteError extends Error { messages; constructor(messages) { - super("remote error"); + super(messages.join(" - ")); this.messages = messages; } } @@ -283,7 +283,7 @@ export const RemoteService = { * 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 void and rejects + * @returns {Promise} promise that resolves to the saved device and rejects * with user-fiendly errors as a RemoteError */ saveDevice: (data) => { @@ -294,7 +294,10 @@ export const RemoteService = { } return Endpoint[data.id ? "put" : "post"](url, {}, data) - .then((res) => void dispatch(actions.deviceSave(res.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"]); @@ -302,13 +305,51 @@ export const RemoteService = { }; }, + /** + * 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])); + dispatch( + actions.deviceOperationUpdate([...res.data, inputDevice.data]) + ); }) .catch((err) => { console.warn(`${url} error`, err); @@ -328,10 +369,14 @@ export const RemoteService = { * with user-fiendly errors as a RemoteError */ switchOperate: (switchId, type) => { - return RemoteService._operateInput("/switch/operate", `/switch/${switchId}`, { - type: type.toUpperCase(), - id: switchId, - }); + return RemoteService._operateInput( + "/switch/operate", + `/switch/${switchId}`, + { + type: type.toUpperCase(), + id: switchId, + } + ); }, /** @@ -343,10 +388,14 @@ export const RemoteService = { * with user-fiendly errors as a RemoteError */ knobDimmerDimTo: (dimmerId, intensity) => { - return RemoteService._operateInput("/knobDimmer/dimTo", `/knobDimmer/${dimmerId}`, { - intensity, - id: dimmerId, - }); + return RemoteService._operateInput( + "/knobDimmer/dimTo", + `/knobDimmer/${dimmerId}`, + { + intensity, + id: dimmerId, + } + ); }, /** diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 692cc7b..47fecc1 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -3,7 +3,8 @@ import thunk from "redux-thunk"; import update from "immutability-helper"; function reducer(previousState, action) { - let newState; + let newState, change; + const createOrUpdateRoom = (room) => { if (!newState.rooms[room.id]) { newState = update(newState, { @@ -35,13 +36,24 @@ function reducer(previousState, action) { } }; - let change; + const updateDeviceProps = (device) => { + // In some updates the information regarding a device is incomplete + // due to a fault in the type system and JPA repository management + // in the backend. Therefore to solve this avoid to delete existing + // attributes of this device in the previous state, but just update + // the new ones. + change.devices[device.id] = {}; + for (const key in device) { + change.devices[device.id][key] = { $set: device[key] }; + } + }; + switch (action.type) { case "LOGIN_UPDATE": newState = update(previousState, { login: { $set: action.login } }); break; case "USER_INFO_UPDATE": - newState = update(previousState, { userInfo: { $set: action.user } }); + newState = update(previousState, { userInfo: { $set: action.userInfo } }); break; case "ROOMS_UPDATE": newState = previousState; @@ -105,11 +117,15 @@ function reducer(previousState, action) { pendingJoins: { rooms: {} }, }; for (const device of action.devices) { - change.devices[device.id] = { $set: device }; + if (!newState.devices[device.id]) { + change.devices[device.id] = { $set: device }; + } else { + updateDeviceProps(device); + } if (device.roomId in newState.rooms) { change.rooms[device.roomId] = change.rooms[device.roomId] || {}; - change.rooms[device.roomId].devices = + change.rooms[device.roomId].devices = change.rooms[device.roomId].devices || {}; const devices = change.rooms[device.roomId].devices; devices.$add = devices.$add || []; @@ -135,9 +151,28 @@ function reducer(previousState, action) { createOrUpdateRoom(action.room); break; case "DEVICE_SAVE": - newState = update(previousState, { + change = { devices: { [action.device.id]: { $set: action.device } }, - }); + }; + + if (previousState.rooms[action.device.roomId]) { + change.rooms = { + [action.device.roomId]: { + devices: { + $add: [action.device.id], + }, + }, + }; + } else { + change.pendingJoins = { + rooms: { + [action.device.roomId]: { + $add: [action.device.id], + }, + }, + }; + } + newState = update(previousState, change); break; case "ROOM_DELETE": if (!(action.roomId in previousState.rooms)) { @@ -157,7 +192,7 @@ function reducer(previousState, action) { change.rooms = { $unset: [action.roomId] }; if (previousState.active.activeRoom === action.roomId) { - change.active = { activeRoom: {$set: -1}}; + change.active = { activeRoom: { $set: -1 } }; } newState = update(previousState, change); @@ -200,6 +235,7 @@ function reducer(previousState, action) { } console.log("new state: ", newState); + console.log("active room: ", newState.active.activeRoom); return newState; } diff --git a/smart-hut/src/views/Navbar.js b/smart-hut/src/views/Navbar.js index 3a0eeea..cfa7b0e 100644 --- a/smart-hut/src/views/Navbar.js +++ b/smart-hut/src/views/Navbar.js @@ -22,6 +22,7 @@ class Navbar extends Component { this.toggleEditMode = this.toggleEditMode.bind(this); this.selectRoom = this.selectRoom.bind(this); + this.openCurrentModalMobile = this.openCurrentModalMobile.bind(this); this.getRooms(); } @@ -40,7 +41,13 @@ class Navbar extends Component { get activeItemName() { if (this.props.activeRoom === -1) return "Home"; - return this.props.rooms[this.props.activeRoom]; + return this.props.rooms[this.props.activeRoom].name; + } + + openCurrentModalMobile() { + console.log(this.activeItem, this.props.roomModalRefs); + const currentModal = this.props.roomModalRefs[this.activeItem].current; + currentModal.openModal(); } toggleEditMode(e) { @@ -48,7 +55,7 @@ class Navbar extends Component { } selectRoom(e, { id }) { - this.activeItem = id; + this.activeItem = id || -1; } render() { @@ -65,9 +72,9 @@ class Navbar extends Component { @@ -86,7 +93,7 @@ class Navbar extends Component { id={e.id} key={i} name={e.name} - active={this.activeRoom === e.id} + active={this.activeItem === e.id} onClick={this.selectRoom} > @@ -124,9 +131,9 @@ class Navbar extends Component { @@ -145,7 +152,7 @@ class Navbar extends Component { id={e.id} key={i} name={e.name} - active={this.activeRoom === e.id} + active={this.activeItem === e.id} onClick={this.selectRoom} > @@ -156,7 +163,11 @@ class Navbar extends Component { {e.name} - + ); })} @@ -168,9 +179,14 @@ class Navbar extends Component { - {this.activeRoom !== -1 ? ( + {this.activeItem !== -1 ? ( - + EDIT ROOM @@ -188,7 +204,14 @@ const setActiveRoom = (activeRoom) => { return (dispatch) => dispatch(appActions.setActiveRoom(activeRoom)); }; -const mapStateToProps = (state, _) => ({ rooms: state.rooms }); +const mapStateToProps = (state, _) => ({ + rooms: state.rooms, + activeRoom: state.active.activeRoom, + roomModalRefs: Object.keys(state.rooms).reduce( + (acc, key) => ({ ...acc, [key]: React.createRef() }), + {} + ), +}); const NavbarContainer = connect(mapStateToProps, { ...RemoteService, setActiveRoom,
Insert an image of the room: