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 (
-
-
- 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 (
+
+
+ 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 (
+
+
+
- render() {
- return (
-
-
-
-
-
-
- );
- }
+
+
+
+
+
+
+
+
+ {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 (
+
+ );
+ }
+ )}
+
+
+
+
+
+ {this.props.scenes.length > 0 ? (
+
+
+
+
+
+ {this.sceneList.map((scene) => (
+
+ ))}
+
+
+ ) : (
+
+
+
+ Create Scene
+
+
+ )}
+
+
+
+
+
+
+
+ this.saveAutomation()}
+ color="green"
+ >
+ {this.props.id ? "Save" : "Create"}
+
+
+
+
+
+
+ );
+ }
}
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 (
-
-
-
- removeAutomation(automation.id)}
- color="red"
- size="small"
- icon={"trash alternate outline"}
- />
-
-
-
-
-
-
-
- {triggers !== undefined &&
- triggers.map((trigger) => {
- const device = devices.filter(
- (d) => d.id === trigger.deviceId
- )[0];
- return (
-
- );
- })}
-
-
-
-
-
- {scenePriorities !== undefined &&
- scenePriorities.map((sp) => {
- const sceneData = scenes.filter(
- (s) => s.id === sp.sceneId
- )[0];
- if (!sceneData) return "";
- return (
-
- );
- })}
-
-
-
-
-
-
- );
+ return (
+
+
+
+ removeAutomation(automation.id)}
+ color="red"
+ size="small"
+ icon={"trash alternate outline"}
+ />
+
+
+
+
+
+
+
+ {triggers !== undefined &&
+ triggers.map((trigger) => {
+ const device = devices.filter(
+ (d) => d.id === trigger.deviceId
+ )[0];
+ return (
+
+ );
+ })}
+
+
+
+
+
+ {scenePriorities !== undefined &&
+ scenePriorities.map((sp) => {
+ const sceneData = scenes.filter(
+ (s) => s.id === sp.sceneId
+ )[0];
+ if (!sceneData) return "";
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+ );
};
class AutomationsPanel extends Component {
- constructor(props) {
- super(props);
- this.state = { openModal: false };
- this.getDevices();
- this.getScenes();
- this.getAutomations();
- }
+ constructor(props) {
+ super(props);
+ this.state = { openModal: false };
+ this.getDevices();
+ this.getScenes();
+ this.getAutomations();
+ }
- getScenes() {
- this.props.fetchAllScenes().catch(console.error);
- }
+ getScenes() {
+ this.props.fetchAllScenes().catch(console.error);
+ }
- getDevices() {
- this.props
- .fetchDevices()
- .catch((err) => console.error(`error fetching devices:`, err));
- }
+ getDevices() {
+ this.props
+ .fetchDevices()
+ .catch((err) => console.error(`error fetching devices:`, err));
+ }
- getAutomations() {
- this.props
- .fetchAutomations()
- .catch((err) => console.error(`error fetching automations:`, err));
- }
+ getAutomations() {
+ this.props
+ .fetchAutomations()
+ .catch((err) => console.error(`error fetching automations:`, err));
+ }
- removeAutomation = (id) => {
- this.props
- .deleteAutomation(id)
- .catch((err) => console.error(`error removing automation ${id}:`, err));
- };
-
- render() {
- return (
-
-
-
- {!this.state.openModal ? (
-
-
-
- ) : (
- CREATE AUTOMATION
- )}
-
-
-
- {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 ? (
+
+
+
+ ) : (
+ CREATE AUTOMATION
+ )}
+
+
+
+ {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();