diff --git a/smart-hut/src/components/dashboard/AutomationCreationModal.js b/smart-hut/src/components/dashboard/AutomationCreationModal.js index 867f677..ac7e88d 100644 --- a/smart-hut/src/components/dashboard/AutomationCreationModal.js +++ b/smart-hut/src/components/dashboard/AutomationCreationModal.js @@ -1,560 +1,664 @@ -import React, { Component, useState } from "react"; +import React, { Component, useState, useRef } from "react"; import { connect } from "react-redux"; import { RemoteService } from "../../remote"; import update from "immutability-helper"; import "./Automations.css"; import { - Segment, - Grid, - Icon, - Header, - Input, - Button, - Modal, - List, - Divider, - Menu, - Form, - Dropdown, - Checkbox, + Segment, + Grid, + Icon, + Header, + Input, + Button, + Modal, + List, + Divider, + Menu, + Form, + Dropdown, + Checkbox, } from "semantic-ui-react"; export const operands = [ - { key: "EQUAL", text: "=", value: "EQUAL" }, - { - key: "GREATER_EQUAL", - text: "\u2265", - value: "GREATER_EQUAL", - }, - { - key: "GREATER", - text: ">", - value: "GREATER", - }, - { - key: "LESS_EQUAL", - text: "\u2264", - value: "LESS_EQUAL", - }, - { - key: "LESS", - text: "<", - value: "LESS", - }, + { 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 notAdmitedDevices = ["buttonDimmer"]; - const hasOperand = new Set([ - "knobDimmer", - "dimmableLight", - "curtains", - "sensor", - ]); - const deviceList = Object.values(props.devices) - .map((device) => { - return { - key: device.id, - text: device.name, - value: device.id, - kind: device.kind, - }; - }) - .filter((e) => !notAdmitedDevices.includes(e.kind)); + const [activeOperand, setActiveOperand] = useState(true); + const operandsRef = useRef(null); + const valuesRef = useRef(null); + const notAdmitedDevices = ["buttonDimmer"]; + const hasOperand = new Set([ + "knobDimmer", + "dimmableLight", + "curtains", + "sensor", + ]); + const deviceList = Object.values(props.devices) + .map((device) => { + return { + key: device.id, + text: device.name, + value: device.id, + kind: device.kind, + }; + }) + .filter((e) => !notAdmitedDevices.includes(e.kind)); - const onChange = (e, val) => { - props.inputChange(val); - setActiveOperand(hasOperand.has(props.devices[val.value].kind)); - }; + const onChange = (e, val) => { + props.inputChange(val); + setActiveOperand(hasOperand.has(props.devices[val.value].kind)); - return ( - - -
- - - - - {activeOperand ? ( - - - props.inputChange(val)} - name="operand" - compact - selection - options={operands} - /> - - - props.inputChange(val)} - name="value" - type="number" - placeholder="Value" - /> - - - ) : ( - - props.inputChange(val)} - placeholder="State" - name="on" - compact - selection - options={deviceStateOptions} - /> - - )} - -
-
-
- ); + if (operandsRef.current) operandsRef.current.setValue(""); + if (valuesRef.current) + valuesRef.current.inputRef.current.valueAsNumber = undefined; + }; + + return ( + + +
+ + + + + {activeOperand ? ( + + + + props.inputChange(val) + } + ref={operandsRef} + name="operand" + compact + selection + options={operands} + /> + + + { + props.inputChange(val); + }} + ref={valuesRef} + name="value" + type="number" + placeholder="Value" + /> + + + ) : ( + + + props.inputChange(val) + } + placeholder="State" + name="on" + compact + selection + options={deviceStateOptions} + /> + + )} + +
+
+
+ ); }; const SceneItem = (props) => { - let position = props.order.indexOf(props.scene.id); - return ( - - - - - - - props.orderScenes(props.scene.id, val.checked) - } - checked={position + 1 > 0} - /> - - -

{props.scene.name}

-
- -

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

-
-
-
-
-
- ); + let position = props.order.indexOf(props.scene.id); + return ( + + + + + + + props.orderScenes( + props.scene.id, + val.checked + ) + } + checked={position + 1 > 0} + /> + + +

{props.scene.name}

+
+ +

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

+
+
+
+
+
+ ); }; const Trigger = ({ deviceName, trigger, onRemove, index }) => { - const { operand, value, on } = trigger; - let symbol; - if (operand) { - symbol = operands.filter((opt) => opt.key === operand)[0].text; - } - return ( - - - {deviceName} - {operand ? {symbol} : ""} - {operand ? value : on ? "on" : "off"} - - onRemove(index)} - className="remove-icon" - name="remove" - /> - - ); + const { operand, value, on } = trigger; + let symbol; + if (operand) { + symbol = operands.filter((opt) => opt.key === operand)[0].text; + } + return ( + + + {deviceName} + {operand ? {symbol} : ""} + + {operand ? value : on ? "on" : "off"} + + + onRemove(index)} + className="remove-icon" + name="remove" + /> + + ); }; class AutomationSaveModal extends Component { - constructor(props) { - super(props); - this.state = { - triggerList: [], - order: [], - automationName: "New Automation", - editName: false, - newTrigger: {}, - scenesFilter: null, - openModal: false, + constructor(props) { + super(props); + this.state = { + triggerList: [], + order: [], + automationName: "New Automation", + editName: false, + newTrigger: {}, + scenesFilter: null, + openModal: false, + }; + + if (this.props.automation) { + this.state.automationName = this.props.automation.name; + for (const scenePriority of this.props.automation.scenes) { + this.state.order[scenePriority.priority] = + scenePriority.sceneId; + } + for (const trigger of this.props.automation.triggers) { + this.state.triggerList.push( + Object.assign( + { + device: trigger.deviceId, + kind: trigger.kind, + }, + trigger.kind === "booleanTrigger" + ? { on: trigger.on } + : { + operand: trigger.operator, + value: trigger.value, + } + ) + ); + } + } + + this.setTrigger = this._setter("triggerList"); + this.setOrder = this._setter("order"); + this.setautomationName = this._setter("automationName"); + this.setEditName = this._setter("editName"); + this.setNewTrigger = this._setter("newTrigger"); + + this.addTrigger = this.addTrigger.bind(this); + this.removeTrigger = this.removeTrigger.bind(this); + this.onInputChange = this.onInputChange.bind(this); + this.searchScenes = this.searchScenes.bind(this); + this.orderScenes = this.orderScenes.bind(this); + this.onChangeName = this.onChangeName.bind(this); + } + + openModal = (e) => { + this.setState({ openModal: true }); }; - if (this.props.automation) { - this.state.automationName = this.props.automation.name; - for (const scenePriority of this.props.automation.scenes) { - this.state.order[scenePriority.priority] = scenePriority.sceneId; - } - for (const trigger of this.props.automation.triggers) { - this.state.triggerList.push( - Object.assign( - { - device: trigger.deviceId, - kind: trigger.kind, - }, - trigger.kind === "booleanTrigger" - ? { on: trigger.on } - : { operand: trigger.operator, value: trigger.value } - ) + closeModal = (e) => { + this.setState({ openModal: false }); + }; + + get deviceList() { + return Object.values(this.props.devices); + } + + _setter(property) { + return (value) => + this.setState(update(this.state, { [property]: { $set: value } })); + } + + triggerKind(trigger) { + if ("operand" in trigger && "value" in trigger) { + return "rangeTrigger"; + } else if ("on" in trigger) { + return "booleanTrigger"; + } else { + return false; + //throw new Error("Trigger kind not handled"); + } + } + + _checkNewTrigger(trigger) { + const error = { + result: false, + message: "There are missing fields!", + }; + let device = Object.values(this.props.devices).filter( + (d) => d.id === trigger.device + )[0]; + + let triggerKind = this.triggerKind(trigger); + + if (!device || !triggerKind) { + error.message = "There are missing fields"; + return error; + } + let deviceKind = device.kind; + const devicesWithPercentage = [ + "dimmableLight", + "curtains", + "knobDimmer", + ]; + + switch (triggerKind) { + case "booleanTrigger": + if ( + !trigger.device || + trigger.on === null || + trigger.on === undefined + ) + return error; + break; + case "rangeTrigger": + if (!trigger.device || !trigger.operand || !trigger.value) { + return error; + } + if (trigger.value < 0) { + error.message = "Values cannot be negative"; + return error; + } + // If the device's range is a percentage, values cannot exceed 100 + else if ( + devicesWithPercentage.includes(deviceKind) && + trigger.value > 100 + ) { + error.message = + "The value can't exceed 100, as it's a percentage"; + return error; + } else if ( + deviceKind === "sensor" && + device.sensor === "HUMIDITY" && + trigger.value > 100 + ) { + error.message = + "The value can't exceed 100, as it's a percentage"; + return error; + } + break; + default: + throw new Error("theoretically unreachable statement"); + } + + const isNotDuplicate = !this.state.triggerList.some( + (t) => t.device === trigger.device && t.operand === trigger.operand ); - } + + return { + result: isNotDuplicate, + message: isNotDuplicate + ? null + : "You have already created a trigger for this device with the same conditions", + }; } - this.setTrigger = this._setter("triggerList"); - this.setOrder = this._setter("order"); - this.setautomationName = this._setter("automationName"); - this.setEditName = this._setter("editName"); - this.setNewTrigger = this._setter("newTrigger"); + addTrigger() { + const { result, message } = this._checkNewTrigger( + this.state.newTrigger + ); - this.addTrigger = this.addTrigger.bind(this); - this.removeTrigger = this.removeTrigger.bind(this); - this.onInputChange = this.onInputChange.bind(this); - this.searchScenes = this.searchScenes.bind(this); - this.orderScenes = this.orderScenes.bind(this); - this.onChangeName = this.onChangeName.bind(this); - } - - openModal = (e) => { - this.setState({ openModal: true }); - }; - - closeModal = (e) => { - this.setState({ openModal: false }); - }; - - get deviceList() { - return Object.values(this.props.devices); - } - - _setter(property) { - return (value) => - this.setState(update(this.state, { [property]: { $set: value } })); - } - - triggerKind(trigger) { - if ("on" in trigger) { - return "booleanTrigger"; - } else if ("operand" in trigger && "value" in trigger) { - return "rangeTrigger"; - } else { - throw new Error("Trigger kind not handled"); + if (result) { + this.setState( + update(this.state, { + triggerList: { $push: [this.state.newTrigger] }, + }) + ); + } else { + alert(message); + } } - } - _checkNewTrigger(trigger) { - // Check for missing fields for creation - const error = { - result: false, - message: "There are missing fields!", + removeTrigger(index) { + this.setState( + update(this.state, { triggerList: { $splice: [[index, 1]] } }) + ); + } + + // This gets triggered when the devices dropdown changes the value. + onInputChange(val) { + if (val.name === "device") { + this.setNewTrigger({ [val.name]: val.value }); + } else { + this.setNewTrigger({ + ...this.state.newTrigger, + [val.name]: val.value, + }); + } + } + + onChangeName(_, val) { + this.setautomationName(val.value); + } + + orderScenes = (id, checked) => { + if (checked) { + this.setState(update(this.state, { order: { $push: [id] } })); + } else { + this.setState( + update(this.state, { + order: (prevList) => prevList.filter((e) => e !== id), + }) + ); + } }; - switch (this.triggerKind(trigger)) { - case "booleanTrigger": - if (!trigger.device || trigger.on === null || trigger.on === undefined) - return error; - break; - case "rangeTrigger": - if (!trigger.device || !trigger.operand || !trigger.value) return error; - break; - default: - throw new Error("theoretically unreachable statement"); + searchScenes(_, { value }) { + this.setState(update(this.state, { scenesFilter: { $set: value } })); + this.forceUpdate(); } - const isNotDuplicate = !this.state.triggerList.some( - (t) => t.device === trigger.device && t.operand === trigger.operand - ); + get sceneList() { + if (!this.scenesFilter) { + return this.props.scenes; + } else { + return this.props.scenes.filter((e) => + e.name.includes(this.scenesFilter) + ); + } + } - return { - result: isNotDuplicate, - message: isNotDuplicate - ? null - : "You have already created a trigger for this device with the same conditions", + _generateKey = (trigger) => { + switch (this.triggerKind(trigger)) { + case "booleanTrigger": + return "" + trigger.device + trigger.on; + case "rangeTrigger": + return "" + trigger.device + trigger.operand + trigger.value; + default: + throw new Error("theoretically unreachable statement"); + } }; - } - addTrigger() { - const { result, message } = this._checkNewTrigger(this.state.newTrigger); - - if (result) { - this.setState( - update(this.state, { triggerList: { $push: [this.state.newTrigger] } }) - ); - } else { - alert(message); - } - } - - removeTrigger(index) { - this.setState( - update(this.state, { triggerList: { $splice: [[index, 1]] } }) - ); - } - - // This gets triggered when the devices dropdown changes the value. - onInputChange(val) { - this.setNewTrigger({ ...this.state.newTrigger, [val.name]: val.value }); - } - - onChangeName(_, val) { - this.setautomationName(val.value); - } - - orderScenes = (id, checked) => { - if (checked) { - this.setState(update(this.state, { order: { $push: [id] } })); - } else { - this.setState( - update(this.state, { - order: (prevList) => prevList.filter((e) => e !== id), - }) - ); - } - }; - - searchScenes(_, { value }) { - this.setState(update(this.state, { scenesFilter: { $set: value } })); - this.forceUpdate(); - } - - get sceneList() { - if (!this.scenesFilter) { - return this.props.scenes; - } else { - return this.props.scenes.filter((e) => - e.name.includes(this.scenesFilter) - ); - } - } - - _generateKey = (trigger) => { - switch (this.triggerKind(trigger)) { - case "booleanTrigger": - return "" + trigger.device + trigger.on; - case "rangeTrigger": - return "" + trigger.device + trigger.operand + trigger.value; - default: - throw new Error("theoretically unreachable statement"); - } - }; - - checkBeforeSave = () => { - if (!this.state.automationName) { - alert("Give a name to the automation"); - return false; - } - if (this.state.triggerList.length <= 0) { - alert("You have to create a trigger"); - return false; - } - if (this.state.order.length <= 0) { - alert("You need at least one active scene"); - return false; - } - return true; - }; - - saveAutomation = () => { - if (this.checkBeforeSave()) { - const automation = { - name: this.state.automationName, - }; - - if (this.props.id) { - automation.id = this.props.id; - automation.triggers = []; - automation.scenes = []; - - for (let i = 0; i < this.state.order.length; i++) { - automation.scenes.push({ - priority: i, - sceneId: this.state.order[i], - }); + checkBeforeSave = () => { + if (!this.state.automationName) { + alert("Give a name to the automation"); + return false; } - - for (const trigger of this.state.triggerList) { - const kind = trigger.kind || this.triggerKind(trigger); - automation.triggers.push( - Object.assign( - { - deviceId: trigger.device, - kind, - }, - kind - ? { on: trigger.on } - : { operator: trigger.operand, value: trigger.value } - ) - ); + if (this.state.triggerList.length <= 0) { + alert("You have to create a trigger"); + return false; } + if (this.state.order.length <= 0) { + alert("You need at least one active scene"); + return false; + } + return true; + }; - console.log(automation); - this.props - .fastUpdateAutomation(automation) - .then(this.closeModal) - .catch(console.error); - } else { - this.props - .saveAutomation({ - automation, - triggerList: this.state.triggerList, - order: this.state.order, - }) - .then(this.closeModal) - .catch(console.error); - } + saveAutomation = () => { + if (this.checkBeforeSave()) { + const automation = { + name: this.state.automationName, + }; + + if (this.props.id) { + automation.id = this.props.id; + automation.triggers = []; + automation.scenes = []; + + for (let i = 0; i < this.state.order.length; i++) { + automation.scenes.push({ + priority: i, + sceneId: this.state.order[i], + }); + } + + for (const trigger of this.state.triggerList) { + const kind = trigger.kind || this.triggerKind(trigger); + automation.triggers.push( + Object.assign( + { + deviceId: trigger.device, + kind, + }, + kind + ? { on: trigger.on } + : { + operator: trigger.operand, + value: trigger.value, + } + ) + ); + } + + this.props + .fastUpdateAutomation(automation) + .then(this.closeModal) + .catch(console.error); + } else { + this.props + .saveAutomation({ + automation, + triggerList: this.state.triggerList, + order: this.state.order, + }) + .then(this.closeModal) + .catch(console.error); + } + } + }; + + get trigger() { + return this.props.id ? ( + + ); } - }; - get trigger() { - return this.props.id ? ( - - ); - } + render() { + return ( + + +
+ {this.state.editName ? ( + + ) : ( + this.state.automationName + )} +
- render() { - return ( - - -
- {this.state.editName ? ( - - ) : ( - this.state.automationName - )} -
- - - - )} - - - -
- - - - - - - -
-
- ); - } + + + + + + +
Create Triggers
+ + {this.state.triggerList.length > 0 && + this.state.triggerList.map( + (trigger, i) => { + const deviceName = this.deviceList.filter( + (d) => + d.id === + trigger.device + )[0].name; + const key = this._generateKey( + trigger + ); + return ( + + ); + } + )} + + + + + )} +
+
+
+
+ + + + + + + + + + ); + } } const mapStateToProps = (state, ownProps) => ({ - scenes: Object.values(state.scenes), - devices: state.devices, - automation: ownProps.id ? state.automations[ownProps.id] : null, + scenes: Object.values(state.scenes), + devices: state.devices, + automation: ownProps.id ? state.automations[ownProps.id] : null, }); const AutomationSaveModalContainer = connect( - mapStateToProps, - RemoteService + mapStateToProps, + RemoteService )(AutomationSaveModal); export default AutomationSaveModalContainer; diff --git a/smart-hut/src/components/dashboard/AutomationsPanel.js b/smart-hut/src/components/dashboard/AutomationsPanel.js index 9d6c972..1df9d61 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -4,169 +4,178 @@ import { RemoteService } from "../../remote"; import "./Automations.css"; import { - Segment, - Grid, - Header, - Button, - List, - Divider, - Menu, + Segment, + Grid, + Header, + Button, + List, + Divider, + Menu, } from "semantic-ui-react"; import CreateAutomation, { operands } from "./AutomationCreationModal"; 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 { 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 ( - - - + removeAutomation = (id) => { + this.props + .deleteAutomation(id) + .catch((err) => + console.error(`error removing automation ${id}:`, err) ); - })} - - - ); - } + }; + + render() { + return ( + + + + {!this.state.openModal ? ( + + + + ) : ( + + )} + + + + {this.props.automations.map((automation, i) => { + 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() { - console.log(state.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() { + 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 f256839..e39a022 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -5,629 +5,657 @@ import reduxWebSocket, { connect } from "@giantmachines/redux-websocket"; import { socketURL } from "./endpoint"; function reducer(previousState, action) { - let newState, change; + let newState, change; - const createOrUpdateRoom = (room) => { - if (!newState.rooms[room.id]) { - newState = update(newState, { - rooms: { [room.id]: { $set: { ...room, devices: new Set() } } }, - }); - } else { - newState = update(newState, { - rooms: { - [room.id]: { - name: { $set: room.name }, - image: { $set: room.image }, - icon: { $set: room.icon }, - }, - }, - }); - } - - if (newState.pendingJoins.rooms[room.id]) { - newState = update(newState, { - pendingJoins: { rooms: { $unset: [room.id] } }, - rooms: { - [room.id]: { - devices: { - $add: [...newState.pendingJoins.rooms[room.id]], - }, - }, - }, - }); - } - }; - - const createOrUpdateScene = (scene) => { - if (!newState.scenes[scene.id]) { - newState = update(newState, { - scenes: { [scene.id]: { $set: { ...scene, sceneStates: new Set() } } }, - }); - } else { - newState = update(newState, { - scenes: { - [scene.id]: { - name: { $set: scene.name }, - icon: { $set: scene.icon }, - }, - }, - }); - } - - if (newState.pendingJoins.scenes[scene.id]) { - newState = update(newState, { - pendingJoins: { scenes: { $unset: [scene.id] } }, - scenes: { - [scene.id]: { - sceneStates: { - $add: [...newState.pendingJoins.scenes[scene.id]], - }, - }, - }, - }); - } - }; - - 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] }; - } - }; - - const updateSceneStateProps = (state) => { - change.sceneStates[state.id] = {}; - for (const key in state) { - change.sceneStates[state.id][key] = { $set: state[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.userInfo } }); - break; - case "ROOMS_UPDATE": - newState = previousState; - for (const room of action.rooms) { - 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) { - createOrUpdateScene(scene); - } - break; - case "HOST_SCENES_UPDATE": - change = { - hostScenes: { - [action.hostId]: { $set: action.scenes }, // stored as array - }, - }; - - newState = update(previousState, change); - break; - case "STATES_UPDATE": - //console.log(action.sceneStates); - change = null; - - // if scene is given, delete all sceneStates in that scene - // and remove any join between that scene and deleted - // sceneStates - change = { - scenes: { [action.sceneId]: { sceneStates: { $set: new Set() } } }, - sceneStates: { $unset: [] }, - }; - - const scene = previousState.scenes[action.sceneId]; - for (const stateId of scene.sceneStates) { - change.sceneStates.$unset.push(stateId); - } - - newState = update(previousState, change); - - change = { - sceneStates: {}, - scenes: {}, - pendingJoins: { scenes: {} }, - }; - - for (const sceneState of action.sceneStates) { - if (!newState.sceneStates[sceneState.id]) { - change.sceneStates[sceneState.id] = { - $set: sceneState, - }; + const createOrUpdateRoom = (room) => { + if (!newState.rooms[room.id]) { + newState = update(newState, { + rooms: { [room.id]: { $set: { ...room, devices: new Set() } } }, + }); } else { - updateSceneStateProps(sceneState); + newState = update(newState, { + rooms: { + [room.id]: { + name: { $set: room.name }, + image: { $set: room.image }, + icon: { $set: room.icon }, + }, + }, + }); } - if (sceneState.sceneId in newState.scenes) { - change.scenes[sceneState.sceneId] = - change.scenes[sceneState.sceneId] || {}; - change.scenes[sceneState.sceneId].sceneStates = - change.scenes[sceneState.sceneId].sceneStates || {}; - const sceneStates = change.scenes[sceneState.sceneId].sceneStates; - sceneStates.$add = sceneStates.$add || []; - sceneStates.$add.push(sceneState.id); - } else { - // room does not exist yet, so add to the list of pending - // joins + if (newState.pendingJoins.rooms[room.id]) { + newState = update(newState, { + pendingJoins: { rooms: { $unset: [room.id] } }, + rooms: { + [room.id]: { + devices: { + $add: [...newState.pendingJoins.rooms[room.id]], + }, + }, + }, + }); + } + }; - if (!change.pendingJoins.scenes[sceneState.sceneId]) { - change.pendingJoins.scenes[sceneState.sceneId] = { - $set: new Set([sceneState.id]), + const createOrUpdateScene = (scene) => { + if (!newState.scenes[scene.id]) { + newState = update(newState, { + scenes: { + [scene.id]: { $set: { ...scene, sceneStates: new Set() } }, + }, + }); + } else { + newState = update(newState, { + scenes: { + [scene.id]: { + name: { $set: scene.name }, + icon: { $set: scene.icon }, + }, + }, + }); + } + + if (newState.pendingJoins.scenes[scene.id]) { + newState = update(newState, { + pendingJoins: { scenes: { $unset: [scene.id] } }, + scenes: { + [scene.id]: { + sceneStates: { + $add: [...newState.pendingJoins.scenes[scene.id]], + }, + }, + }, + }); + } + }; + + 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] }; + } + }; + + const updateSceneStateProps = (state) => { + change.sceneStates[state.id] = {}; + for (const key in state) { + change.sceneStates[state.id][key] = { $set: state[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.userInfo }, + }); + break; + case "ROOMS_UPDATE": + newState = previousState; + for (const room of action.rooms) { + createOrUpdateRoom(room); + } + break; + case "HOST_ROOMS_UPDATE": + change = { + hostRooms: { + [action.hostId]: { $set: {} }, + }, }; - } else { - change.pendingJoins.scenes[sceneState.sceneId].$set.add( - sceneState.id + 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) { + createOrUpdateScene(scene); + } + break; + case "HOST_SCENES_UPDATE": + change = { + hostScenes: { + [action.hostId]: { $set: action.scenes }, // stored as array + }, + }; + + newState = update(previousState, change); + break; + case "STATES_UPDATE": + //console.log(action.sceneStates); + change = null; + + // if scene is given, delete all sceneStates in that scene + // and remove any join between that scene and deleted + // sceneStates + change = { + scenes: { + [action.sceneId]: { sceneStates: { $set: new Set() } }, + }, + sceneStates: { $unset: [] }, + }; + + const scene = previousState.scenes[action.sceneId]; + for (const stateId of scene.sceneStates) { + change.sceneStates.$unset.push(stateId); + } + + newState = update(previousState, change); + + change = { + sceneStates: {}, + scenes: {}, + pendingJoins: { scenes: {} }, + }; + + for (const sceneState of action.sceneStates) { + if (!newState.sceneStates[sceneState.id]) { + change.sceneStates[sceneState.id] = { + $set: sceneState, + }; + } else { + updateSceneStateProps(sceneState); + } + + if (sceneState.sceneId in newState.scenes) { + change.scenes[sceneState.sceneId] = + change.scenes[sceneState.sceneId] || {}; + change.scenes[sceneState.sceneId].sceneStates = + change.scenes[sceneState.sceneId].sceneStates || {}; + const sceneStates = + change.scenes[sceneState.sceneId].sceneStates; + sceneStates.$add = sceneStates.$add || []; + sceneStates.$add.push(sceneState.id); + } else { + // room does not exist yet, so add to the list of pending + // joins + + if (!change.pendingJoins.scenes[sceneState.sceneId]) { + change.pendingJoins.scenes[sceneState.sceneId] = { + $set: new Set([sceneState.id]), + }; + } else { + change.pendingJoins.scenes[sceneState.sceneId].$set.add( + sceneState.id + ); + } + } + } + + newState = update(newState, change); + + break; + case "DEVICES_UPDATE": + change = null; + + // if room is given, delete all devices in that room + // and remove any join between that room and deleted + // devices + if (action.roomId) { + change = { + rooms: { + [action.roomId]: { devices: { $set: new Set() } }, + }, + devices: { $unset: [] }, + }; + + const room = newState.rooms[action.roomId]; + for (const deviceId of room.devices) { + change.devices.$unset.push(deviceId); + } + } else if (action.partial) { + // if the update is partial and caused by an operation on an input + // device (like /switch/operate), iteratively remove deleted + // devices and their join with their corresponding room. + change = { + devices: { $unset: [] }, + rooms: {}, + }; + + for (const device of action.devices) { + if (!previousState.devices[device.id]) continue; + change.devices.$unset.push(device.id); + const roomId = previousState.devices[device.id].roomId; + + if (roomId in previousState.rooms) { + change.rooms[roomId] = change.rooms[roomId] || { + devices: { $remove: [] }, + }; + change.rooms[roomId].devices.$remove.push(device.id); + } + } + } else { + // otherwise, just delete all devices and all joins + // between rooms and devices + change = { + devices: { $set: {} }, + rooms: {}, + }; + + for (const room of Object.values(previousState.rooms)) { + if (change.rooms[room.id]) { + change.rooms[room.id].devices = { $set: new Set() }; + } + } + } + + newState = update(previousState, change); + + change = { + devices: {}, + rooms: {}, + pendingJoins: { rooms: {} }, + }; + for (const device of action.devices) { + 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 || {}; + const devices = change.rooms[device.roomId].devices; + devices.$add = devices.$add || []; + devices.$add.push(device.id); + } else { + // room does not exist yet, so add to the list of pending + // joins + + if (!change.pendingJoins.rooms[device.roomId]) { + change.pendingJoins.rooms[device.roomId] = { + $set: new Set([device.id]), + }; + } else { + change.pendingJoins.rooms[device.roomId].$set.add( + device.id + ); + } + } + } + + 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) { + automations[automation.id] = automation; + } + + change = { + automations: { $set: automations }, + }; + newState = update(previousState, change); + break; + case "ROOM_SAVE": + newState = previousState; + createOrUpdateRoom(action.room); + break; + case "SCENE_SAVE": + newState = previousState; + createOrUpdateScene(action.scene); + break; + case "DEVICE_SAVE": + 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 "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 = { + automations: { + [action.automation.id]: { $set: action.automation }, + }, + }; + + newState = update(previousState, change); + + break; + + case "STATE_SAVE": + change = { + sceneStates: { + [action.sceneState.id]: { $set: action.sceneState }, + }, + }; + + if (previousState.scenes[action.sceneState.sceneId]) { + change.scenes = { + [action.sceneState.sceneId]: { + sceneStates: { + $add: [action.sceneState.id], + }, + }, + }; + } else { + change.pendingJoins = { + scenes: { + [action.sceneState.sceneId]: { + $add: [action.sceneState.id], + }, + }, + }; + } + newState = update(previousState, change); + break; + case "ROOM_DELETE": + if (!(action.roomId in previousState.rooms)) { + console.warn(`Room to delete ${action.roomId} does not exist`); + break; + } + + // This update does not ensure the consistent update of switchId/dimmerId properties + // on output devices connected to an input device in this room. Please manually request + // all devices again if consistent update is desired + change = { devices: { $unset: [] } }; + + for (const id of previousState.rooms[action.roomId].devices) { + change.devices.$unset.push(id); + } + + change.rooms = { $unset: [action.roomId] }; + + if (previousState.active.activeRoom === action.roomId) { + change.active = { activeRoom: { $set: -1 } }; + } + + newState = update(previousState, change); + break; + + case "AUTOMATION_DELETE": + change = { + automations: { $unset: [action.id] }, + }; + + newState = update(previousState, change); + break; + case "SCENE_DELETE": + if (!(action.sceneId in previousState.scenes)) { + console.warn( + `Scene to delete ${action.sceneId} does not exist` + ); + break; + } + + // This update does not ensure the consistent update of switchId/dimmerId properties + // on output devices connected to an input device in this room. Please manually request + // all devices again if consistent update is desired + change = { sceneStates: { $unset: [] } }; + + for (const id of previousState.scenes[action.sceneId].sceneStates) { + change.sceneStates.$unset.push(id); + } + + change.scenes = { $unset: [action.sceneId] }; + + if (previousState.active.activeScene === action.sceneId) { + change.active = { activeScene: { $set: -1 } }; + } + + newState = update(previousState, change); + break; + case "STATE_DELETE": + if (!(action.stateId in previousState.sceneStates)) { + console.warn( + `State to delete ${action.stateId} does not exist` + ); + break; + } + + change = { + sceneStates: { $unset: [action.stateId] }, + }; + + if ( + previousState.scenes[ + previousState.sceneStates[action.stateId].sceneId + ] + ) { + change.scenes = { + [previousState.sceneStates[action.stateId].sceneId]: { + sceneStates: { $remove: [action.stateId] }, + }, + }; + } + + newState = update(previousState, change); + break; + + case "DEVICE_DELETE": + if (!(action.deviceId in previousState.devices)) { + console.warn( + `Device to delete ${action.deviceId} does not exist` + ); + break; + } + + change = { + devices: { $unset: [action.deviceId] }, + }; + + if ( + previousState.rooms[ + previousState.devices[action.deviceId].roomId + ] + ) { + change.rooms = { + [previousState.devices[action.deviceId].roomId]: { + devices: { $remove: [action.deviceId] }, + }, + }; + } + + newState = update(previousState, change); + break; + case "LOGOUT": + newState = update(initState, {}); + break; + case "SET_ACTIVE": + newState = update(previousState, { + active: { + [action.key]: { + $set: action.value, + }, + }, + }); + break; + case "REDUX_WEBSOCKET::MESSAGE": + const allDevices = JSON.parse(action.payload.message); + const devices = allDevices.filter( + (d) => + (d.fromHostId === null || d.fromHostId === undefined) && + !d.deleted ); - } - } - } - - newState = update(newState, change); - - break; - case "DEVICES_UPDATE": - change = null; - - // if room is given, delete all devices in that room - // and remove any join between that room and deleted - // devices - if (action.roomId) { - change = { - rooms: { [action.roomId]: { devices: { $set: new Set() } } }, - devices: { $unset: [] }, - }; - - const room = newState.rooms[action.roomId]; - for (const deviceId of room.devices) { - change.devices.$unset.push(deviceId); - } - } else if (action.partial) { - // if the update is partial and caused by an operation on an input - // device (like /switch/operate), iteratively remove deleted - // devices and their join with their corresponding room. - change = { - devices: { $unset: [] }, - rooms: {}, - }; - - for (const device of action.devices) { - if (!previousState.devices[device.id]) continue; - change.devices.$unset.push(device.id); - const roomId = previousState.devices[device.id].roomId; - - if (roomId in previousState.rooms) { - change.rooms[roomId] = change.rooms[roomId] || { - devices: { $remove: [] }, - }; - change.rooms[roomId].devices.$remove.push(device.id); - } - } - } else { - // otherwise, just delete all devices and all joins - // between rooms and devices - change = { - devices: { $set: {} }, - rooms: {}, - }; - - for (const room of Object.values(previousState.rooms)) { - if (change.rooms[room.id]) { - change.rooms[room.id].devices = { $set: new Set() }; - } - } - } - - newState = update(previousState, change); - - change = { - devices: {}, - rooms: {}, - pendingJoins: { rooms: {} }, - }; - for (const device of action.devices) { - 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 || {}; - const devices = change.rooms[device.roomId].devices; - devices.$add = devices.$add || []; - devices.$add.push(device.id); - } else { - // room does not exist yet, so add to the list of pending - // joins - - if (!change.pendingJoins.rooms[device.roomId]) { - change.pendingJoins.rooms[device.roomId] = { - $set: new Set([device.id]), - }; - } else { - change.pendingJoins.rooms[device.roomId].$set.add(device.id); - } - } - } - - 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) { - automations[automation.id] = automation; - } - - change = { - automations: { $set: automations }, - }; - newState = update(previousState, change); - break; - case "ROOM_SAVE": - newState = previousState; - createOrUpdateRoom(action.room); - break; - case "SCENE_SAVE": - newState = previousState; - createOrUpdateScene(action.scene); - break; - case "DEVICE_SAVE": - 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 "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 = { - automations: { [action.automation.id]: { $set: action.automation } }, - }; - - newState = update(previousState, change); - - break; - - case "STATE_SAVE": - change = { - sceneStates: { [action.sceneState.id]: { $set: action.sceneState } }, - }; - - if (previousState.scenes[action.sceneState.sceneId]) { - change.scenes = { - [action.sceneState.sceneId]: { - sceneStates: { - $add: [action.sceneState.id], - }, - }, - }; - } else { - change.pendingJoins = { - scenes: { - [action.sceneState.sceneId]: { - $add: [action.sceneState.id], - }, - }, - }; - } - newState = update(previousState, change); - break; - case "ROOM_DELETE": - if (!(action.roomId in previousState.rooms)) { - console.warn(`Room to delete ${action.roomId} does not exist`); - break; - } - - // This update does not ensure the consistent update of switchId/dimmerId properties - // on output devices connected to an input device in this room. Please manually request - // all devices again if consistent update is desired - change = { devices: { $unset: [] } }; - - for (const id of previousState.rooms[action.roomId].devices) { - change.devices.$unset.push(id); - } - - change.rooms = { $unset: [action.roomId] }; - - if (previousState.active.activeRoom === action.roomId) { - change.active = { activeRoom: { $set: -1 } }; - } - - newState = update(previousState, change); - break; - - case "AUTOMATION_DELETE": - change = { - automations: { $unset: [action.id] }, - }; - - newState = update(previousState, change); - break; - case "SCENE_DELETE": - if (!(action.sceneId in previousState.scenes)) { - console.warn(`Scene to delete ${action.sceneId} does not exist`); - break; - } - - // This update does not ensure the consistent update of switchId/dimmerId properties - // on output devices connected to an input device in this room. Please manually request - // all devices again if consistent update is desired - change = { sceneStates: { $unset: [] } }; - - for (const id of previousState.scenes[action.sceneId].sceneStates) { - change.sceneStates.$unset.push(id); - } - - change.scenes = { $unset: [action.sceneId] }; - - if (previousState.active.activeScene === action.sceneId) { - change.active = { activeScene: { $set: -1 } }; - } - - newState = update(previousState, change); - break; - case "STATE_DELETE": - if (!(action.stateId in previousState.sceneStates)) { - console.warn(`State to delete ${action.stateId} does not exist`); - break; - } - - change = { - sceneStates: { $unset: [action.stateId] }, - }; - - if ( - previousState.scenes[previousState.sceneStates[action.stateId].sceneId] - ) { - change.scenes = { - [previousState.sceneStates[action.stateId].sceneId]: { - sceneStates: { $remove: [action.stateId] }, - }, - }; - } - - newState = update(previousState, change); - break; - - case "DEVICE_DELETE": - if (!(action.deviceId in previousState.devices)) { - console.warn(`Device to delete ${action.deviceId} does not exist`); - break; - } - - change = { - devices: { $unset: [action.deviceId] }, - }; - - if (previousState.rooms[previousState.devices[action.deviceId].roomId]) { - change.rooms = { - [previousState.devices[action.deviceId].roomId]: { - devices: { $remove: [action.deviceId] }, - }, - }; - } - - newState = update(previousState, change); - break; - case "LOGOUT": - newState = update(initState, {}); - break; - case "SET_ACTIVE": - newState = update(previousState, { - active: { - [action.key]: { - $set: action.value, - }, - }, - }); - break; - case "REDUX_WEBSOCKET::MESSAGE": - 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; - }, {}); - console.log(devices); - 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); - return previousState; - } - return newState; + 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); + return previousState; + } + return newState; } const initState = { - pendingJoins: { + pendingJoins: { + rooms: {}, + scenes: {}, + automations: {}, + }, + active: { + activeRoom: -1, + activeTab: "Devices", + activeScene: -1, + activeAutomation: -1, + activeHost: -1, + }, + login: { + loggedIn: false, + token: null, + }, + userInfo: null, + /** @type {[integer]Room} */ rooms: {}, + /** @type {[integer]Scene} */ scenes: {}, + hostScenes: {}, + /** @type {[integer]Automation} */ automations: {}, - }, - active: { - activeRoom: -1, - activeTab: "Devices", - activeScene: -1, - activeAutomation: -1, - activeHost: -1, - }, - login: { - loggedIn: false, - token: null, - }, - userInfo: null, - /** @type {[integer]Room} */ - rooms: {}, - /** @type {[integer]Scene} */ - scenes: {}, - hostScenes: {}, - /** @type {[integer]Automation} */ - automations: {}, - /** @type {[integer]Device} */ - devices: {}, - /** @type {[integer]SceneState} */ - sceneStates: {}, - /** @type {User[]} */ - guests: [], - /** @type {User[]} */ - hosts: [], - /** @type {[integer]Device} */ - hostDevices: {}, - /** @type {[integer]Eoom} */ - hostRooms: {}, + /** @type {[integer]Device} */ + devices: {}, + /** @type {[integer]SceneState} */ + sceneStates: {}, + /** @type {User[]} */ + guests: [], + /** @type {User[]} */ + hosts: [], + /** @type {[integer]Device} */ + hostDevices: {}, + /** @type {[integer]Eoom} */ + hostRooms: {}, }; function createSmartHutStore() { - const token = localStorage.getItem("token"); - const exp = localStorage.getItem("exp"); + const token = localStorage.getItem("token"); + const exp = localStorage.getItem("exp"); - const initialState = update(initState, { - login: { - token: { $set: token }, - loggedIn: { $set: !!(token && exp > new Date().getTime()) }, - }, - }); + const initialState = update(initState, { + login: { + token: { $set: token }, + loggedIn: { $set: !!(token && exp > new Date().getTime()) }, + }, + }); - if (!initialState.login.loggedIn) { - localStorage.removeItem("token"); - localStorage.removeItem("exp"); - initialState.login.token = null; - } + if (!initialState.login.loggedIn) { + localStorage.removeItem("token"); + localStorage.removeItem("exp"); + initialState.login.token = null; + } - const store = createStore( - reducer, - initialState, - compose(applyMiddleware(thunk), applyMiddleware(reduxWebSocket())) - ); - if (initialState.login.loggedIn) { - store.dispatch(connect(socketURL(token))); - } - return store; + const store = createStore( + reducer, + initialState, + compose(applyMiddleware(thunk), applyMiddleware(reduxWebSocket())) + ); + if (initialState.login.loggedIn) { + store.dispatch(connect(socketURL(token))); + } + return store; } const smartHutStore = createSmartHutStore();