import React, { Component, useState } 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, } 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", }, ]; const deviceStateOptions = [ { 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 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} /> )}
); }; 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) : ""}

); }; 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" /> ); }; class AutomationSaveModal extends Component { 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 }); }; 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"); } } _checkNewTrigger(trigger) { const auxDevice = this.props.devices[trigger.device]; // Check for missing fields for creation const error = { result: false, message: "There are missing fields!", }; 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; } 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", }; } 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; } }; 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], }); } 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 } ) ); } 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); } } }; get trigger() { return this.props.id ? ( ); } render() { return (
{this.state.editName ? ( ) : ( this.state.automationName )}
)}
); } } const mapStateToProps = (state, ownProps) => ({ scenes: Object.values(state.scenes), devices: state.devices, automation: ownProps.id ? state.automations[ownProps.id] : null, }); const AutomationSaveModalContainer = connect( mapStateToProps, RemoteService )(AutomationSaveModal); export default AutomationSaveModalContainer;