From 6c1e309ce4fa0b5607dfda3b7c057dc0872de0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Cape=C3=A1ns=20P=C3=A9rez?= Date: Wed, 6 May 2020 18:42:58 +0200 Subject: [PATCH 1/8] Color of the temperature sensor changes with the temperature value (up to 20 degrees is green, from 20 to 30 is yellow and from 30 on is red) --- .../components/dashboard/devices/Sensor.js | 359 ++++++++++-------- smart-hut/yarn.lock | 217 +---------- 2 files changed, 211 insertions(+), 365 deletions(-) diff --git a/smart-hut/src/components/dashboard/devices/Sensor.js b/smart-hut/src/components/dashboard/devices/Sensor.js index f171b6a..07cb440 100644 --- a/smart-hut/src/components/dashboard/devices/Sensor.js +++ b/smart-hut/src/components/dashboard/devices/Sensor.js @@ -22,18 +22,18 @@ import React, { Component } from "react"; import { CircularInput, CircularProgress } from "react-circular-input"; import { - container, - sensorText, - style, - valueStyle, - motionSensorInnerCircle, - motionSensorOuterCircle, - nameMotionStyle, - motionSensorIcon, - temperatureSensorColors, - lightSensorColors, - humiditySensorColors, - iconSensorStyle, + container, + sensorText, + style, + valueStyle, + motionSensorInnerCircle, + motionSensorOuterCircle, + nameMotionStyle, + motionSensorIcon, + temperatureSensorColors, + lightSensorColors, + humiditySensorColors, + iconSensorStyle, } from "./SensorStyle"; import { Image } from "semantic-ui-react"; import { RemoteService } from "../../../remote"; @@ -41,158 +41,197 @@ import { connect } from "react-redux"; import mapStateToProps from "../../../deviceProps"; class Sensor extends Component { - constructor(props) { - super(props); - this.state = { - value: 0, - motion: false, - }; - this.units = ""; - this.stateCallback = (e) => { - this.setState(Object.assign(this.state, e)); + constructor(props) { + super(props); + this.state = { + value: 0, + motion: false, + }; + this.units = ""; + this.stateCallback = (e) => { + this.setState(Object.assign(this.state, e)); + }; + + this.colors = temperatureSensorColors; + this.icon = "temperatureIcon.svg"; + this.name = "Sensor"; + } + + // setName = () => { + // if (this.props.device.name.length > 15) { + // return this.props.device.name.slice(0, 12) + "..."; + // } + // return this.props.device.name; + // }; + + componentDidUpdate(prevProps) { + if ( + this.props.stateOrDevice.kind === "sensor" && + this.props.stateOrDevice.value !== prevProps.stateOrDevice.value + ) { + this.setState({ value: this.props.stateOrDevice.value }); + } else if ( + this.props.stateOrDevice.kind === "motionSensor" && + this.props.stateOrDevice.detected !== + prevProps.stateOrDevice.detected + ) { + this.setState({ + motion: true, + detected: this.props.stateOrDevice.detected, + }); + } + } + + componentDidMount() { + if (this.props.stateOrDevice.kind === "sensor") { + switch (this.props.stateOrDevice.sensor) { + case "TEMPERATURE": + this.units = "ºC"; + this.colors = temperatureSensorColors; + this.icon = "temperatureIcon.svg"; + this.name = "Temperature Sensor"; + break; + case "HUMIDITY": + this.units = "%"; + this.colors = humiditySensorColors; + this.icon = "humidityIcon.svg"; + this.name = "Humidity Sensor"; + break; + case "LIGHT": + this.units = "lm"; + this.colors = lightSensorColors; + this.icon = "lightSensorIcon.svg"; + this.name = "Light Sensor"; + break; + default: + this.units = ""; + } + this.setState({ + value: this.props.stateOrDevice.value, + }); + } else { + this.setState({ + detected: this.props.stateOrDevice.detected, + motion: true, + }); + } + } + + getIcon = () => { + if (this.state.detected) { + return this.iconOn; + } + return this.iconOff; }; - this.colors = temperatureSensorColors; - this.icon = "temperatureIcon.svg"; - this.name = "Sensor"; - } - - // setName = () => { - // if (this.props.device.name.length > 15) { - // return this.props.device.name.slice(0, 12) + "..."; - // } - // return this.props.device.name; - // }; - - componentDidUpdate(prevProps) { - if ( - this.props.stateOrDevice.kind === "sensor" && - this.props.stateOrDevice.value !== prevProps.stateOrDevice.value - ) { - this.setState({ value: this.props.stateOrDevice.value }); - } else if ( - this.props.stateOrDevice.kind === "motionSensor" && - this.props.stateOrDevice.detected !== prevProps.stateOrDevice.detected - ) { - this.setState({ - motion: true, - detected: this.props.stateOrDevice.detected, - }); - } - } - - componentDidMount() { - if (this.props.stateOrDevice.kind === "sensor") { - switch (this.props.stateOrDevice.sensor) { - case "TEMPERATURE": - this.units = "ºC"; - this.colors = temperatureSensorColors; - this.icon = "temperatureIcon.svg"; - this.name = "Temperature Sensor"; - break; - case "HUMIDITY": - this.units = "%"; - this.colors = humiditySensorColors; - this.icon = "humidityIcon.svg"; - this.name = "Humidity Sensor"; - break; - case "LIGHT": - this.units = "lm"; - this.colors = lightSensorColors; - this.icon = "lightSensorIcon.svg"; - this.name = "Light Sensor"; - break; - default: - this.units = ""; - } - this.setState({ - value: this.props.stateOrDevice.value, - }); - } else { - this.setState({ - detected: this.props.stateOrDevice.detected, - motion: true, - }); - } - } - - getIcon = () => { - if (this.state.detected) { - return this.iconOn; - } - return this.iconOff; - }; - - render() { - const MotionSensor = (props) => { - return ( -
-
- - Motion Sensor -
-
- ); + temperatureColor = (value) => { + let hue = 100; + if (value >= 20 && value < 30) { + hue = 50; + } else if (value >= 30) { + hue = 0; + } + return `hsl(${hue}, 100%, 50%)`; }; - return ( -
- {this.state.motion ? ( - - ) : ( - - - - - {+(Math.round(this.state.value + "e+2") + "e-2")} - {this.units} - - - {this.name} - - - - - )} -
- ); - } + render() { + const MotionSensor = (props) => { + return ( +
+
+ + Motion Sensor +
+
+ ); + }; + + return ( +
+ {this.state.motion ? ( + + ) : ( + + + + + { + +( + Math.round(this.state.value + "e+2") + + "e-2" + ) + } + {this.units} + + + {this.name} + + + + + )} +
+ ); + } } const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor); diff --git a/smart-hut/yarn.lock b/smart-hut/yarn.lock index 68fe632..affdd46 100644 --- a/smart-hut/yarn.lock +++ b/smart-hut/yarn.lock @@ -1940,11 +1940,6 @@ abab@^2.0.0: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -2113,19 +2108,11 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3, aproba@^1.1.1: +aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -3252,11 +3239,6 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -3744,7 +3726,7 @@ debug@=3.1.0: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: +debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3780,11 +3762,6 @@ deep-equal@^1.0.1, deep-equal@^1.1.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -3845,11 +3822,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -3868,11 +3840,6 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" @@ -4967,13 +4934,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -5024,20 +4984,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gensync@^1.0.0-beta.1: version "1.0.0-beta.1" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" @@ -5229,11 +5175,6 @@ has-symbols@^1.0.0, has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -5491,7 +5432,7 @@ hyphenate-style-name@^1.0.3: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5522,13 +5463,6 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" @@ -5632,7 +5566,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.5, ini@~1.3.0: +ini@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -7235,14 +7169,6 @@ minipass-pipeline@^1.2.2: dependencies: minipass "^3.0.0" -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - minipass@^3.0.0, minipass@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" @@ -7250,13 +7176,6 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -7289,7 +7208,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -7368,15 +7287,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -needle@^2.2.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" - integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -7468,35 +7378,11 @@ node-notifier@^5.4.2: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-releases@^1.1.52, node-releases@^1.1.53: version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== -nopt@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -7539,27 +7425,6 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -7567,16 +7432,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nth-check@^1.0.2, nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -7786,11 +7641,6 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - os-locale@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" @@ -7800,19 +7650,11 @@ os-locale@^3.0.0: lcid "^2.0.0" mem "^4.0.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -9127,16 +8969,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-app-polyfill@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0" @@ -9421,7 +9253,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -9787,7 +9619,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -9945,7 +9777,7 @@ semantic-ui-react@^0.88.2: react-popper "^1.3.4" shallowequal "^1.1.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -10007,7 +9839,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -10419,7 +10251,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -10560,11 +10392,6 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - style-loader@0.23.1: version "0.23.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" @@ -10673,19 +10500,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - terser-webpack-plugin@2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.4.tgz#ac045703bd8da0936ce910d8fb6350d0e1dee5fe" @@ -11398,13 +11212,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -11631,7 +11438,7 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From 78f6e508ecdec3fd5e6be37ddfb73ca86fedbff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Cape=C3=A1ns=20P=C3=A9rez?= Date: Wed, 6 May 2020 21:36:32 +0200 Subject: [PATCH 2/8] Check values for different types of sensors when creating a trigger for an automation --- .../dashboard/AutomationCreationModal.js | 1114 ++++++++------- .../components/dashboard/AutomationsPanel.js | 301 ++-- smart-hut/src/store.js | 1236 +++++++++-------- 3 files changed, 1396 insertions(+), 1255 deletions(-) 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(); From d1cd93afe58f34728a4d9c3c8f4cd726836d1c2a Mon Sep 17 00:00:00 2001 From: britea Date: Fri, 8 May 2020 13:45:13 +0200 Subject: [PATCH 3/8] apply scene in host --- smart-hut/src/components/dashboard/HostsPanel.js | 9 ++++++++- smart-hut/src/remote.js | 5 ++++- smart-hut/src/store.js | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/smart-hut/src/components/dashboard/HostsPanel.js b/smart-hut/src/components/dashboard/HostsPanel.js index 5a6d380..0e8e13a 100644 --- a/smart-hut/src/components/dashboard/HostsPanel.js +++ b/smart-hut/src/components/dashboard/HostsPanel.js @@ -16,6 +16,13 @@ class HostsPanel extends Component { } } + applyHostScene(id) { + this.props + .sceneApply(id, this.props.activeHost) + .then(() => console.log("SCCUESS")) + .catch((err) => console.error("sceneApply update error", err)); + } + render() { if (this.props.isActiveDefaultHost) { return ( @@ -48,7 +55,7 @@ class HostsPanel extends Component {
- +
diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index f0cfcea..5164ced 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -549,9 +549,12 @@ export const RemoteService = { }; }, - sceneApply: (id) => { + sceneApply: (id, hostId = null) => { return (dispatch) => { let url = `/scene/${id}/apply`; + if (hostId) { + url = url + "?hostId=" + hostId; + } return Endpoint.post(url) .then((res) => dispatch(actions.deviceOperationUpdate(res.data))) diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index f256839..cbc8ba2 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -49,6 +49,7 @@ function reducer(previousState, action) { [scene.id]: { name: { $set: scene.name }, icon: { $set: scene.icon }, + guestAccessEnabled: { $set: scene.guestAccessEnabled }, }, }, }); From 4688995d12fe8799e427c87699d2f53ed70e0cea Mon Sep 17 00:00:00 2001 From: britea Date: Fri, 8 May 2020 14:55:26 +0200 Subject: [PATCH 4/8] fix apply scene --- smart-hut/src/components/dashboard/HostsPanel.js | 6 ++++-- smart-hut/src/components/dashboard/devices/Videocam.js | 3 ++- smart-hut/src/remote.js | 8 +++++++- smart-hut/src/storeActions.js | 3 ++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/smart-hut/src/components/dashboard/HostsPanel.js b/smart-hut/src/components/dashboard/HostsPanel.js index 0e8e13a..50f2097 100644 --- a/smart-hut/src/components/dashboard/HostsPanel.js +++ b/smart-hut/src/components/dashboard/HostsPanel.js @@ -53,9 +53,11 @@ class HostsPanel extends Component { {scene.name} - +
- +
diff --git a/smart-hut/src/components/dashboard/devices/Videocam.js b/smart-hut/src/components/dashboard/devices/Videocam.js index 409d467..75dee30 100644 --- a/smart-hut/src/components/dashboard/devices/Videocam.js +++ b/smart-hut/src/components/dashboard/devices/Videocam.js @@ -70,11 +70,12 @@ class Videocam extends Component { /> - + this.setOnOff(val.checked)} /> diff --git a/smart-hut/src/remote.js b/smart-hut/src/remote.js index 5164ced..42a5841 100644 --- a/smart-hut/src/remote.js +++ b/smart-hut/src/remote.js @@ -557,7 +557,13 @@ export const RemoteService = { } return Endpoint.post(url) - .then((res) => dispatch(actions.deviceOperationUpdate(res.data))) + .then((res) => + dispatch( + hostId + ? actions.hostDevicesUpdate(hostId, res.data, true) + : actions.deviceOperationUpdate(res.data) + ) + ) .catch((err) => { console.warn("scene apply error", err); throw new RemoteError(["Network error"]); diff --git a/smart-hut/src/storeActions.js b/smart-hut/src/storeActions.js index 218f4b9..6a4e4e4 100644 --- a/smart-hut/src/storeActions.js +++ b/smart-hut/src/storeActions.js @@ -63,9 +63,10 @@ const actions = { devices, partial, }), - hostDevicesUpdate: (hostId, devices) => ({ + hostDevicesUpdate: (hostId, devices, partial = false) => ({ type: "HOST_DEVICES_UPDATE", hostId, + partial, devices, }), stateDelete: (stateId) => ({ From e5426fa790dab6cecdb35054fbbb17f774a4d1b9 Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Fri, 8 May 2020 15:37:01 +0200 Subject: [PATCH 5/8] Added smooth temperature gradient --- .../components/dashboard/devices/Sensor.js | 381 +++++++++--------- smart-hut/src/views/Dashboard.js | 24 +- 2 files changed, 188 insertions(+), 217 deletions(-) diff --git a/smart-hut/src/components/dashboard/devices/Sensor.js b/smart-hut/src/components/dashboard/devices/Sensor.js index 07cb440..4206d8c 100644 --- a/smart-hut/src/components/dashboard/devices/Sensor.js +++ b/smart-hut/src/components/dashboard/devices/Sensor.js @@ -22,18 +22,18 @@ import React, { Component } from "react"; import { CircularInput, CircularProgress } from "react-circular-input"; import { - container, - sensorText, - style, - valueStyle, - motionSensorInnerCircle, - motionSensorOuterCircle, - nameMotionStyle, - motionSensorIcon, - temperatureSensorColors, - lightSensorColors, - humiditySensorColors, - iconSensorStyle, + container, + sensorText, + style, + valueStyle, + motionSensorInnerCircle, + motionSensorOuterCircle, + nameMotionStyle, + motionSensorIcon, + temperatureSensorColors, + lightSensorColors, + humiditySensorColors, + iconSensorStyle, } from "./SensorStyle"; import { Image } from "semantic-ui-react"; import { RemoteService } from "../../../remote"; @@ -41,197 +41,180 @@ import { connect } from "react-redux"; import mapStateToProps from "../../../deviceProps"; class Sensor extends Component { - constructor(props) { - super(props); - this.state = { - value: 0, - motion: false, - }; - this.units = ""; - this.stateCallback = (e) => { - this.setState(Object.assign(this.state, e)); - }; - - this.colors = temperatureSensorColors; - this.icon = "temperatureIcon.svg"; - this.name = "Sensor"; - } - - // setName = () => { - // if (this.props.device.name.length > 15) { - // return this.props.device.name.slice(0, 12) + "..."; - // } - // return this.props.device.name; - // }; - - componentDidUpdate(prevProps) { - if ( - this.props.stateOrDevice.kind === "sensor" && - this.props.stateOrDevice.value !== prevProps.stateOrDevice.value - ) { - this.setState({ value: this.props.stateOrDevice.value }); - } else if ( - this.props.stateOrDevice.kind === "motionSensor" && - this.props.stateOrDevice.detected !== - prevProps.stateOrDevice.detected - ) { - this.setState({ - motion: true, - detected: this.props.stateOrDevice.detected, - }); - } - } - - componentDidMount() { - if (this.props.stateOrDevice.kind === "sensor") { - switch (this.props.stateOrDevice.sensor) { - case "TEMPERATURE": - this.units = "ºC"; - this.colors = temperatureSensorColors; - this.icon = "temperatureIcon.svg"; - this.name = "Temperature Sensor"; - break; - case "HUMIDITY": - this.units = "%"; - this.colors = humiditySensorColors; - this.icon = "humidityIcon.svg"; - this.name = "Humidity Sensor"; - break; - case "LIGHT": - this.units = "lm"; - this.colors = lightSensorColors; - this.icon = "lightSensorIcon.svg"; - this.name = "Light Sensor"; - break; - default: - this.units = ""; - } - this.setState({ - value: this.props.stateOrDevice.value, - }); - } else { - this.setState({ - detected: this.props.stateOrDevice.detected, - motion: true, - }); - } - } - - getIcon = () => { - if (this.state.detected) { - return this.iconOn; - } - return this.iconOff; + constructor(props) { + super(props); + this.state = { + value: 0, + motion: false, + }; + this.units = ""; + this.stateCallback = (e) => { + this.setState(Object.assign(this.state, e)); }; - temperatureColor = (value) => { - let hue = 100; - if (value >= 20 && value < 30) { - hue = 50; - } else if (value >= 30) { - hue = 0; - } - return `hsl(${hue}, 100%, 50%)`; + this.colors = temperatureSensorColors; + this.icon = "temperatureIcon.svg"; + this.name = "Sensor"; + } + + // setName = () => { + // if (this.props.device.name.length > 15) { + // return this.props.device.name.slice(0, 12) + "..."; + // } + // return this.props.device.name; + // }; + + componentDidUpdate(prevProps) { + if ( + this.props.stateOrDevice.kind === "sensor" && + this.props.stateOrDevice.value !== prevProps.stateOrDevice.value + ) { + this.setState({ value: this.props.stateOrDevice.value }); + } else if ( + this.props.stateOrDevice.kind === "motionSensor" && + this.props.stateOrDevice.detected !== prevProps.stateOrDevice.detected + ) { + this.setState({ + motion: true, + detected: this.props.stateOrDevice.detected, + }); + } + } + + componentDidMount() { + if (this.props.stateOrDevice.kind === "sensor") { + switch (this.props.stateOrDevice.sensor) { + case "TEMPERATURE": + this.units = "ºC"; + this.colors = temperatureSensorColors; + this.icon = "temperatureIcon.svg"; + this.name = "Temperature Sensor"; + break; + case "HUMIDITY": + this.units = "%"; + this.colors = humiditySensorColors; + this.icon = "humidityIcon.svg"; + this.name = "Humidity Sensor"; + break; + case "LIGHT": + this.units = "lm"; + this.colors = lightSensorColors; + this.icon = "lightSensorIcon.svg"; + this.name = "Light Sensor"; + break; + default: + this.units = ""; + } + this.setState({ + value: this.props.stateOrDevice.value, + }); + } else { + this.setState({ + detected: this.props.stateOrDevice.detected, + motion: true, + }); + } + } + + getIcon = () => { + if (this.state.detected) { + return this.iconOn; + } + return this.iconOff; + }; + + temperatureColor = (value) => { + let hue = 100; + const min = 16; + const max = 20; + if (value >= min && value < max) { + hue = 100 - ((value - min) * 100) / (max - min); + } else if (value >= max) { + hue = 0; + } + return `hsl(${hue}, 100%, 50%)`; + }; + + render() { + const MotionSensor = (props) => { + return ( +
+
+ + Motion Sensor +
+
+ ); }; - render() { - const MotionSensor = (props) => { - return ( -
-
- - Motion Sensor -
-
- ); - }; - - return ( -
- {this.state.motion ? ( - - ) : ( - - - - - { - +( - Math.round(this.state.value + "e+2") + - "e-2" - ) - } - {this.units} - - - {this.name} - - - - - )} -
- ); - } + return ( +
+ {this.state.motion ? ( + + ) : ( + + + + + {+(Math.round(this.state.value + "e+2") + "e-2")} + {this.units} + + + {this.name} + + + + + )} +
+ ); + } } const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor); diff --git a/smart-hut/src/views/Dashboard.js b/smart-hut/src/views/Dashboard.js index 4501a8e..cac1298 100644 --- a/smart-hut/src/views/Dashboard.js +++ b/smart-hut/src/views/Dashboard.js @@ -8,10 +8,7 @@ import ScenesNavbar from "./ScenesNavbar"; import HostsNavbar from "./HostsNavbar"; import MyHeader from "../components/HeaderController"; import { Grid, Responsive, Button, Menu } from "semantic-ui-react"; -import { - panelStyle, - mobilePanelStyle, -} from "../components/dashboard/devices/styleComponents"; +import { mobilePanelStyle } from "../components/dashboard/devices/styleComponents"; import { RemoteService } from "../remote"; import { connect } from "react-redux"; @@ -85,7 +82,7 @@ class Dashboard extends Component { //in case a room has one. let backgroundImageHelper; if (this.activeTab === "Devices") { - backgroundImageHelper = this.props.allRooms; + backgroundImageHelper = this.props.backgroundImage; } else { backgroundImageHelper = null; } @@ -223,20 +220,11 @@ const mapStateToProps = (state, _) => ({ get currentRoom() { return state.active.activeRoom; }, - //this took me way longer to figure out than it should have - get allRooms() { - if (state.active.activeRoom == -1) { + get backgroundImage() { + if (state.active.activeRoom === -1) { return null; - } - for (let i in state.rooms) { - if (i == state.active.activeRoom) { - //console.log("check",state.rooms[i].image) - if (state.rooms[i].image === undefined) { - return null; - } else { - return state.rooms[i].image; - } - } + } else { + return state.rooms[state.active.activeRoom].image; } }, }); From e5bf0dbd83746cb4329097e432c00a26598c0edf Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Fri, 8 May 2020 15:45:08 +0200 Subject: [PATCH 6/8] Prettier reformat --- smart-hut/src/components/HeaderController.js | 44 +- smart-hut/src/components/SceneModal.js | 24 +- .../dashboard/AutomationCreationModal.js | 1171 ++++++++------- .../components/dashboard/AutomationsPanel.js | 300 ++-- .../components/dashboard/devices/Sensor.js | 7 - smart-hut/src/store.js | 1255 ++++++++--------- 6 files changed, 1366 insertions(+), 1435 deletions(-) diff --git a/smart-hut/src/components/HeaderController.js b/smart-hut/src/components/HeaderController.js index f67edfe..47954c9 100644 --- a/smart-hut/src/components/HeaderController.js +++ b/smart-hut/src/components/HeaderController.js @@ -77,14 +77,20 @@ export class MyHeader extends React.Component { - - Share cameras} - checked={this.props.cameraEnabled} - toggle - onChange={(e, val) => this.setCameraEnabled(val.checked)} - /> + + Share cameras} + checked={this.props.cameraEnabled} + toggle + onChange={(e, val) => this.setCameraEnabled(val.checked)} + />
@@ -108,14 +114,20 @@ export class MyHeader extends React.Component { - - Share cameras} - checked={this.props.cameraEnabled} - toggle - onChange={(e, val) => this.setCameraEnabled(val.checked)} - /> + + Share cameras} + checked={this.props.cameraEnabled} + toggle + onChange={(e, val) => this.setCameraEnabled(val.checked)} + />
diff --git a/smart-hut/src/components/SceneModal.js b/smart-hut/src/components/SceneModal.js index 8b935cc..d9cce65 100644 --- a/smart-hut/src/components/SceneModal.js +++ b/smart-hut/src/components/SceneModal.js @@ -115,11 +115,11 @@ class SceneModal extends Component { }; openModal = (e) => { - this.setState({ ...this.state, openModal: true }); + this.setState({ ...this.state, openModal: true }); }; updateIcon(e) { - this.setState({ ...this.state, selectedIcon: e }); + this.setState({ ...this.state, selectedIcon: e }); } setCopyFrom(_, copyFrom) { @@ -128,7 +128,7 @@ class SceneModal extends Component { setGuestAccessEnabled(val) { console.log(this.state, val); - this.setState({ ...this.state, guestAccessEnabled: val }); + this.setState({ ...this.state, guestAccessEnabled: val }); } render() { @@ -224,15 +224,15 @@ class SceneModal extends Component { )} {this.type === "modify" ? ( - - - this.setGuestAccessEnabled(val.checked) - } - /> + + + this.setGuestAccessEnabled(val.checked) + } + /> ) : null} diff --git a/smart-hut/src/components/dashboard/AutomationCreationModal.js b/smart-hut/src/components/dashboard/AutomationCreationModal.js index ac7e88d..722c09c 100644 --- a/smart-hut/src/components/dashboard/AutomationCreationModal.js +++ b/smart-hut/src/components/dashboard/AutomationCreationModal.js @@ -5,660 +5,613 @@ 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 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 [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)); - if (operandsRef.current) operandsRef.current.setValue(""); - if (valuesRef.current) - valuesRef.current.inputRef.current.valueAsNumber = undefined; - }; + 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} - /> - - )} - -
-
-
- ); + 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, - }; - - 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 }); + constructor(props) { + super(props); + this.state = { + triggerList: [], + order: [], + automationName: "New Automation", + editName: false, + newTrigger: {}, + scenesFilter: null, + openModal: false, }; - 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 (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, } - 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", - }; + } } - addTrigger() { - const { result, message } = this._checkNewTrigger( - this.state.newTrigger - ); + 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"); - if (result) { - this.setState( - update(this.state, { - triggerList: { $push: [this.state.newTrigger] }, - }) - ); - } else { - alert(message); - } + 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 ("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"); } + } - 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), - }) - ); - } + _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]; - searchScenes(_, { value }) { - this.setState(update(this.state, { scenesFilter: { $set: value } })); - this.forceUpdate(); + 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"); } - get sceneList() { - if (!this.scenesFilter) { - return this.props.scenes; - } else { - return this.props.scenes.filter((e) => - e.name.includes(this.scenesFilter) - ); - } + 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); } + } - _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"); - } - }; + removeTrigger(index) { + this.setState( + update(this.state, { triggerList: { $splice: [[index, 1]] } }) + ); + } - 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, - } - ) - ); - } - - 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 ? ( - - ); + // 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, + }); } + } - render() { - return ( - - -
- {this.state.editName ? ( - - ) : ( - this.state.automationName - )} -
+ onChangeName(_, val) { + this.setautomationName(val.value); + } - + ); + } + + 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, + 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 1df9d61..8530213 100644 --- a/smart-hut/src/components/dashboard/AutomationsPanel.js +++ b/smart-hut/src/components/dashboard/AutomationsPanel.js @@ -4,178 +4,168 @@ 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 ( + + + ); - }; - - 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() { - 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/components/dashboard/devices/Sensor.js b/smart-hut/src/components/dashboard/devices/Sensor.js index 4206d8c..2583df0 100644 --- a/smart-hut/src/components/dashboard/devices/Sensor.js +++ b/smart-hut/src/components/dashboard/devices/Sensor.js @@ -57,13 +57,6 @@ class Sensor extends Component { this.name = "Sensor"; } - // setName = () => { - // if (this.props.device.name.length > 15) { - // return this.props.device.name.slice(0, 12) + "..."; - // } - // return this.props.device.name; - // }; - componentDidUpdate(prevProps) { if ( this.props.stateOrDevice.kind === "sensor" && diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index b2d0acb..359604e 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -81,655 +81,638 @@ function reducer(previousState, action) { } }; - 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, - }; - } 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 - ); - 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; + 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 }, + }, + }, + }); } - return newState; + + 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, + }; + } 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 + ); + 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: { - rooms: {}, - scenes: {}, - automations: {}, - }, - active: { - activeRoom: -1, - activeTab: "Devices", - activeScene: -1, - activeAutomation: -1, - activeHost: -1, - }, - login: { - loggedIn: false, - token: null, - }, - userInfo: null, - /** @type {[integer]Room} */ + pendingJoins: { 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: {}, + }, + 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: {}, }; 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(); From dbc6223954758efde588f807c69c45ba1594dbb7 Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Fri, 8 May 2020 15:47:18 +0200 Subject: [PATCH 7/8] Fixed git merge sentience --- smart-hut/src/store.js | 75 ------------------------------------------ 1 file changed, 75 deletions(-) diff --git a/smart-hut/src/store.js b/smart-hut/src/store.js index 359604e..ecfa94a 100644 --- a/smart-hut/src/store.js +++ b/smart-hut/src/store.js @@ -81,81 +81,6 @@ function reducer(previousState, action) { } }; - 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) { From b27c6a3f5157ecb9e30b04e1b2edbac7d62afc2a Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Fri, 8 May 2020 16:03:42 +0200 Subject: [PATCH 8/8] Fixed bugs in automation fast update --- smart-hut/src/components/dashboard/AutomationCreationModal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smart-hut/src/components/dashboard/AutomationCreationModal.js b/smart-hut/src/components/dashboard/AutomationCreationModal.js index 722c09c..e0ef5f6 100644 --- a/smart-hut/src/components/dashboard/AutomationCreationModal.js +++ b/smart-hut/src/components/dashboard/AutomationCreationModal.js @@ -447,11 +447,11 @@ class AutomationSaveModal extends Component { deviceId: trigger.device, kind, }, - kind + kind === "booleanTrigger" ? { on: trigger.on } : { operator: trigger.operand, - value: trigger.value, + range: parseInt(trigger.value), } ) );