diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 11e33690c8..a0cdec9ce7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,7 +21,7 @@ We use github to host code, to track issues and feature requests, as well as acc ## Before making a Pull Request -Make sure you discussed with the team on [Gladys Community](https://community.gladysassistant.com/) to ensure your pull request goes in the same direction as current developements 🙂 +Make sure you discussed with the team on our Gladys [french forum](https://community.gladysassistant.com/) or [english forum](https://en-community.gladysassistant.com/) to ensure your pull request goes in the same direction as current developements 🙂 Then, create a GitHub Issue to indicate that you're working on the topic. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e9091573ba..00ad834d7f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,7 +5,7 @@ about: Report a bug to help us improve Gladys Assistant First, are you sure that you found a Gladys bug? -If you are not sure, you can come discuss with us on [Gladys Community](http://community.gladysassistant.com/) 🙂 +If you are not sure, you can come discuss with us on our community, in [french](http://community.gladysassistant.com/) or [english](https://en-community.gladysassistant.com/) 🙂 **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index aa8415c15d..94244216d5 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,14 @@ blank_issues_enabled: true contact_links: - - name: Gladys Assistant Forum + - name: Gladys Assistant english forum + url: https://en-community.gladysassistant.com/ + about: We use this forum for questions & support in english. + - name: Gladys Assistant french Forum url: https://community.gladysassistant.com/ - about: We use the forum for questions & support. - - name: Feature Requests + about: We use the forum for questions & support in french. + - name: Feature Requests (english) + url: https://en-community.gladysassistant.com/c/feature-requests/7/l/latest?order=votes + about: We use our forum as feature requests. You can create feature requests there, and vote for them. + - name: Feature Requests (french) url: https://community.gladysassistant.com/c/international/feature-requests/53 about: We use our forum as feature requests. You can create feature requests there, and vote for them. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6eb096bb57..1b557df5d7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ To ensure your Pull Request can be accepted as fast as possible, make sure to re - [ ] Is the linter passing? (`npm run eslint` on both front/server) - [ ] Did you run prettier? (`npm run prettier` on both front/server) - [ ] If you are adding a new features/services, did you run integration comparator? (`npm run compare-translations` on front) -- [ ] Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to [the community](https://community.gladysassistant.com/) for testing before merging. +- [ ] Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to the community ([french forum](https://community.gladysassistant.com/)/[english forum](https://en-community.gladysassistant.com/)) for testing before merging. - [ ] If your changes modify the API (REST or Node.js), did you modify the API documentation? (Documentation is based on comments in code) - [ ] If you are adding a new features/services which needs explanation, did you modify the user documentation? See [the GitHub repo](https://github.com/GladysAssistant/v4-website) and the [website](https://gladysassistant.com). - [ ] Did you add fake requests data for the demo mode (`front/src/config/demo.js`) so that the demo website is working without a backend? (if needed) See [https://demo.gladysassistant.com](https://demo.gladysassistant.com). diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 38faf1683f..3f23caa862 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -2,7 +2,7 @@ First, thanks for trying out Gladys! -The best place to ask for help is our [Gladys Community Forum](https://community.gladysassistant.com/). +The best place to ask for help is our forum, in [french](https://community.gladysassistant.com/) or [english](https://en-community.gladysassistant.com/). Please **_do not_** raise an issue on GitHub if it's a support problem! diff --git a/.github/workflows/docker-release-build.yml b/.github/workflows/docker-release-build.yml index c51d60ec23..a1408f29e5 100644 --- a/.github/workflows/docker-release-build.yml +++ b/.github/workflows/docker-release-build.yml @@ -168,9 +168,6 @@ jobs: cache-to: type=inline - name: 🐳 Legacy Tags run: | - echo '{"experimental": true}' | sudo tee -a /etc/docker/daemon.json - export DOCKER_CLI_EXPERIMENTAL=enabled - sudo systemctl restart docker export DIGESTARM=$(docker manifest inspect ${{ env.DOCKERHUB_REPO }}:latest | jq -r '.manifests | to_entries[] | select(.value.platform.architecture == "arm" and .value.platform.variant == "v6").value | .digest') docker pull ${{ env.DOCKERHUB_REPO }}@$DIGESTARM docker tag ${{ env.DOCKERHUB_REPO }}@$DIGESTARM ${{ env.DOCKERHUB_REPO }}:v4-arm diff --git a/SECURITY.md b/SECURITY.md index 5336fcfdcd..4cd6f3cf18 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -To report a vulnerability, you can contact us on [Gladys Assistant forum](https://community.gladysassistant.com/) in private, or on the contact form [on our website](https://gladysassistant.com/contact/). +To report a vulnerability, you can contact us on Gladys Assistant [french forum](https://community.gladysassistant.com/) or [english forum](https://en-community.gladysassistant.com/) in private, or on the contact form [on our website](https://gladysassistant.com/contact/). diff --git a/front/package-lock.json b/front/package-lock.json index 14ab2b1eea..6174a5d769 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -9,8 +9,8 @@ "@gladysassistant/gladys-gateway-js": "^4.13.1", "@gladysassistant/theme-optimized": "^1.0.3", "@jaames/iro": "^5.5.2", - "@yaireo/tagify": "^4.5.0", - "apexcharts": "^3.29.0", + "@yaireo/tagify": "4.5.0", + "apexcharts": "^3.41.1", "axios": "^0.21.1", "classnames": "^2.3.1", "cropperjs": "^1.5.12", @@ -31,7 +31,7 @@ "preact-router": "^3.2.1", "qrcode": "^1.4.2", "react-big-calendar": "^1.6.9", - "react-clock": "^3.1.0", + "react-clock": "^4.5.0", "react-datepicker": "^3.8.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", @@ -5000,6 +5000,19 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.197", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", + "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==" + }, + "node_modules/@types/lodash.memoize": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz", + "integrity": "sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -5640,9 +5653,9 @@ } }, "node_modules/@wojtekmaj/date-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz", - "integrity": "sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.0.tgz", + "integrity": "sha512-0mq88lCND6QiffnSDWp+TbOxzJSwy2V/3XN+HwWZ7S2n19QAgR5dy5hRVhlECXvQIq2r+VcblBu+S9V+yMcxXw==", "funding": { "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" } @@ -6280,9 +6293,9 @@ } }, "node_modules/apexcharts": { - "version": "3.29.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.29.0.tgz", - "integrity": "sha512-PhI17VayidYAbLb5/g+7WOeirgFrVopzt0qGwLq8V+cd6NXx4CeHYq3S0pDZiUGO7UFQ4YIrT8+ie9/Fnler+w==", + "version": "3.41.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.41.1.tgz", + "integrity": "sha512-kta8fhXrfZYqW7K9kF7FqZ6imQaC6moyRgcUZjwIky/oeHVVISSN/2rjUIvZXnwxWHiSdDHMqLy+TqJhB4DXFA==", "dependencies": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -14793,10 +14806,11 @@ } }, "node_modules/get-user-locale": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-1.5.1.tgz", - "integrity": "sha512-WiNpoFRcHn1qxP9VabQljzGwkAQDrcpqUtaP0rNBEkFxJdh4f3tik6MfZsMYZc+UgQJdGCxWEjL9wnCUlRQXag==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.0.tgz", + "integrity": "sha512-I3rQvAUwu2nauRD9YyQBSXVFJZixNouwA+eZld51Sn4Pn0N1qFbgcgOi/nPigJPQlNY519mT95fiSPRgflQiTA==", "dependencies": { + "@types/lodash.memoize": "^4.1.7", "lodash.memoize": "^4.1.1" }, "funding": { @@ -19313,14 +19327,6 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, - "node_modules/merge-class-names": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.4.2.tgz", - "integrity": "sha512-bOl98VzwCGi25Gcn3xKxnR5p/WrhWFQB59MS/aGENcmUc6iSm96yrFDF0XSNurX9qN4LbJm0R9kfvsQ17i8zCw==", - "funding": { - "url": "https://github.com/wojtekmaj/merge-class-names?sponsor=1" - } - }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -24366,21 +24372,35 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-clock": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-clock/-/react-clock-3.1.0.tgz", - "integrity": "sha512-KLV3pDBcETc7lHPPqK6EpRaPS8NA3STAes+zIdfr7IY67vYgYc3brOAnGC9IcgA4X4xNPnLZwwaLJXmHrQ/MnQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-clock/-/react-clock-4.5.0.tgz", + "integrity": "sha512-0LnNG1NbEOOl0M4v3Ul1D/4U1898S3JIc5GMunR6PGMv+ntGqOV/flV9MbIIXoFu8TEgS6xC/RVHZWCmaKJXhA==", "dependencies": { - "@wojtekmaj/date-utils": "^1.0.0", - "get-user-locale": "^1.4.0", - "merge-class-names": "^1.1.1", + "@wojtekmaj/date-utils": "^1.5.0", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", "prop-types": "^15.6.0" }, "funding": { "url": "https://github.com/wojtekmaj/react-clock?sponsor=1" }, "peerDependencies": { - "react": "^15.5.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.5.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-clock/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" } }, "node_modules/react-datepicker": { @@ -35130,6 +35150,19 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.197", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", + "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==" + }, + "@types/lodash.memoize": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz", + "integrity": "sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -35682,9 +35715,9 @@ } }, "@wojtekmaj/date-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz", - "integrity": "sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.0.tgz", + "integrity": "sha512-0mq88lCND6QiffnSDWp+TbOxzJSwy2V/3XN+HwWZ7S2n19QAgR5dy5hRVhlECXvQIq2r+VcblBu+S9V+yMcxXw==" }, "@xtuc/ieee754": { "version": "1.2.0", @@ -36191,9 +36224,9 @@ } }, "apexcharts": { - "version": "3.29.0", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.29.0.tgz", - "integrity": "sha512-PhI17VayidYAbLb5/g+7WOeirgFrVopzt0qGwLq8V+cd6NXx4CeHYq3S0pDZiUGO7UFQ4YIrT8+ie9/Fnler+w==", + "version": "3.41.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.41.1.tgz", + "integrity": "sha512-kta8fhXrfZYqW7K9kF7FqZ6imQaC6moyRgcUZjwIky/oeHVVISSN/2rjUIvZXnwxWHiSdDHMqLy+TqJhB4DXFA==", "requires": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -42907,10 +42940,11 @@ } }, "get-user-locale": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-1.5.1.tgz", - "integrity": "sha512-WiNpoFRcHn1qxP9VabQljzGwkAQDrcpqUtaP0rNBEkFxJdh4f3tik6MfZsMYZc+UgQJdGCxWEjL9wnCUlRQXag==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.0.tgz", + "integrity": "sha512-I3rQvAUwu2nauRD9YyQBSXVFJZixNouwA+eZld51Sn4Pn0N1qFbgcgOi/nPigJPQlNY519mT95fiSPRgflQiTA==", "requires": { + "@types/lodash.memoize": "^4.1.7", "lodash.memoize": "^4.1.1" } }, @@ -46562,11 +46596,6 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, - "merge-class-names": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.4.2.tgz", - "integrity": "sha512-bOl98VzwCGi25Gcn3xKxnR5p/WrhWFQB59MS/aGENcmUc6iSm96yrFDF0XSNurX9qN4LbJm0R9kfvsQ17i8zCw==" - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -50423,14 +50452,21 @@ } }, "react-clock": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-clock/-/react-clock-3.1.0.tgz", - "integrity": "sha512-KLV3pDBcETc7lHPPqK6EpRaPS8NA3STAes+zIdfr7IY67vYgYc3brOAnGC9IcgA4X4xNPnLZwwaLJXmHrQ/MnQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-clock/-/react-clock-4.5.0.tgz", + "integrity": "sha512-0LnNG1NbEOOl0M4v3Ul1D/4U1898S3JIc5GMunR6PGMv+ntGqOV/flV9MbIIXoFu8TEgS6xC/RVHZWCmaKJXhA==", "requires": { - "@wojtekmaj/date-utils": "^1.0.0", - "get-user-locale": "^1.4.0", - "merge-class-names": "^1.1.1", + "@wojtekmaj/date-utils": "^1.5.0", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", "prop-types": "^15.6.0" + }, + "dependencies": { + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + } } }, "react-datepicker": { diff --git a/front/package.json b/front/package.json index 495d94cd81..1697828014 100644 --- a/front/package.json +++ b/front/package.json @@ -47,8 +47,8 @@ "@gladysassistant/gladys-gateway-js": "^4.13.1", "@gladysassistant/theme-optimized": "^1.0.3", "@jaames/iro": "^5.5.2", - "@yaireo/tagify": "^4.5.0", - "apexcharts": "^3.29.0", + "@yaireo/tagify": "4.5.0", + "apexcharts": "^3.41.1", "axios": "^0.21.1", "classnames": "^2.3.1", "cropperjs": "^1.5.12", @@ -69,7 +69,7 @@ "preact-router": "^3.2.1", "qrcode": "^1.4.2", "react-big-calendar": "^1.6.9", - "react-clock": "^3.1.0", + "react-clock": "^4.5.0", "react-datepicker": "^3.8.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", diff --git a/front/src/actions/dashboard/boxes/devicesInRoom.js b/front/src/actions/dashboard/boxes/devicesInRoom.js deleted file mode 100644 index 31f8e304f4..0000000000 --- a/front/src/actions/dashboard/boxes/devicesInRoom.js +++ /dev/null @@ -1,220 +0,0 @@ -import { RequestStatus } from '../../../utils/consts'; -import createBoxActions from '../boxActions'; -import createDeviceActions from '../../device'; -import update from 'immutability-helper'; -import get from 'get-value'; -import debounce from 'debounce'; -const { DEVICE_FEATURE_TYPES, DEVICE_FEATURE_CATEGORIES } = require('../../../../../server/utils/constants'); - -const BOX_KEY = 'DevicesInRoom'; - -const getLightStatus = room => { - let roomLightStatus = 0; - room.devices.forEach(device => { - device.features.forEach(feature => { - // if it's a light - const isLight = - feature.category === DEVICE_FEATURE_CATEGORIES.LIGHT && - feature.type === DEVICE_FEATURE_TYPES.LIGHT.BINARY && - feature.read_only === false; - // if it's a light and it's turned on, we consider that the light - // is on in the room - if (isLight && feature.last_value === 1) { - roomLightStatus = 1; - } - }); - }); - return { - roomLightStatus - }; -}; - -function createActions(store) { - const boxActions = createBoxActions(store); - const deviceActions = createDeviceActions(store); - - const actions = { - async getDevicesInRoom(state, box, x, y) { - boxActions.updateBoxStatus(state, BOX_KEY, x, y, RequestStatus.Getting); - try { - const room = await state.httpClient.get(`/api/v1/room/${box.room}?expand=devices`); - // we test if there are lights ON/OFF device features to control in this room - const { roomLightStatus } = getLightStatus(room); - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - room, - roomLightStatus - }); - boxActions.updateBoxStatus(state, BOX_KEY, x, y, RequestStatus.Success); - } catch (e) { - boxActions.updateBoxStatus(state, BOX_KEY, x, y, RequestStatus.Error); - } - }, - async changeAllLightsStatusRoom(state, x, y, action) { - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - roomLightStatus: action - }); - const data = boxActions.getBoxData(state, BOX_KEY, x, y); - const promises = []; - data.room.devices.forEach(device => { - device.features.forEach(feature => { - const isLightBinary = - feature.category === DEVICE_FEATURE_CATEGORIES.LIGHT && feature.type === DEVICE_FEATURE_TYPES.LIGHT.BINARY; - if (isLightBinary) { - promises.push(deviceActions.setValue(state, feature.selector, action)); - feature.last_value = action; - feature.last_value_changed = new Date(); - } - }); - }); - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - room: data.room - }); - await Promise.all(promises); - }, - async setValueDevice(state, deviceFeatureSelector, action) { - await deviceActions.setValue(state, deviceFeatureSelector, action); - }, - async updateValueWithDebounce(state, x, y, device, deviceFeature, deviceIndex, featureIndex, action) { - const data = boxActions.getBoxData(state, BOX_KEY, x, y); - const newData = update(data, { - room: { - devices: { - [deviceIndex]: { - features: { - [featureIndex]: { - last_value: { - $set: action - }, - last_value_changed: { - $set: new Date() - } - } - } - } - } - } - }); - const { roomLightStatus } = getLightStatus(newData.room); - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - room: newData.room, - roomLightStatus - }); - await actions.setValueDeviceDebounce(state, deviceFeature.selector, action); - }, - async updateValue(state, x, y, device, deviceFeature, deviceIndex, featureIndex, action) { - const data = boxActions.getBoxData(state, BOX_KEY, x, y); - const newData = update(data, { - room: { - devices: { - [deviceIndex]: { - features: { - [featureIndex]: { - last_value: { - $set: action - }, - last_value_changed: { - $set: new Date() - } - } - } - } - } - } - }); - const { roomLightStatus } = getLightStatus(newData.room); - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - room: newData.room, - roomLightStatus - }); - await deviceActions.setValue(state, deviceFeature.selector, action); - }, - deviceFeatureWebsocketEvent(state, x, y, payload) { - const data = boxActions.getBoxData(state, BOX_KEY, x, y); - const devices = get(data, 'room.devices'); - if (devices) { - let found = false; - let currentDeviceIndex = 0; - let currentFeatureIndex = 0; - while (!found && currentDeviceIndex < devices.length) { - while (!found && currentFeatureIndex < devices[currentDeviceIndex].features.length) { - if ( - devices[currentDeviceIndex].features[currentFeatureIndex].selector === payload.device_feature_selector - ) { - found = true; - const newData = update(data, { - room: { - devices: { - [currentDeviceIndex]: { - features: { - [currentFeatureIndex]: { - last_value: { - $set: payload.last_value - }, - last_value_changed: { - $set: payload.last_value_changed - } - } - } - } - } - } - }); - const { roomLightStatus } = getLightStatus(newData.room); - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - room: newData.room, - roomLightStatus - }); - } - currentFeatureIndex += 1; - } - currentDeviceIndex += 1; - currentFeatureIndex = 0; - } - } - }, - deviceFeatureStringStateWebsocketEvent(state, x, y, payload) { - const data = boxActions.getBoxData(state, BOX_KEY, x, y); - const devices = get(data, 'room.devices'); - if (devices) { - let found = false; - let currentDeviceIndex = 0; - let currentFeatureIndex = 0; - while (!found && currentDeviceIndex < devices.length) { - while (!found && currentFeatureIndex < devices[currentDeviceIndex].features.length) { - if (devices[currentDeviceIndex].features[currentFeatureIndex].selector === payload.device_feature) { - found = true; - const newData = update(data, { - room: { - devices: { - [currentDeviceIndex]: { - features: { - [currentFeatureIndex]: { - last_value_string: { - $set: payload.last_value_string - }, - last_value_changed: { - $set: payload.last_value_changed - } - } - } - } - } - } - }); - boxActions.mergeBoxData(state, BOX_KEY, x, y, { - room: newData.room - }); - } - currentFeatureIndex += 1; - } - currentDeviceIndex += 1; - currentFeatureIndex = 0; - } - } - } - }; - actions.setValueDeviceDebounce = debounce(actions.setValueDevice, 500); - return Object.assign({}, actions, boxActions); -} - -export default createActions; diff --git a/front/src/components/boxs/clock/Clock.jsx b/front/src/components/boxs/clock/Clock.jsx index 388b72688d..c440dfc656 100644 --- a/front/src/components/boxs/clock/Clock.jsx +++ b/front/src/components/boxs/clock/Clock.jsx @@ -22,8 +22,10 @@ const Clock = ({ children, ...props }) => (
{props.year}
-
- +
+
+ +
)} diff --git a/front/src/components/boxs/clock/style.css b/front/src/components/boxs/clock/style.css index a1e4b6db9e..973ad34aa6 100644 --- a/front/src/components/boxs/clock/style.css +++ b/front/src/components/boxs/clock/style.css @@ -12,22 +12,24 @@ .analogCol { display: flex; flex-direction: column; - flex: auto; + flex: 1 0 50%; } .analogSmallText { - font-size: 18px; + font-size: 1rem; text-align: left; } .analogBigText { - font-size: 30px; + font-size: 1.3rem; text-align: left; } .analogClock { - align-items: center; - justify-content: center; + display: flex; + margin-left: auto; + margin-right: 15%; + width: 100px; } .digitalTime { @@ -42,4 +44,7 @@ text-align: center; } - +.reactClock { + width: inherit !important; + padding-top: 100%; +} diff --git a/front/src/components/boxs/device-in-room/DeviceCard.jsx b/front/src/components/boxs/device-in-room/DeviceCard.jsx index e207471847..cec313b3c8 100644 --- a/front/src/components/boxs/device-in-room/DeviceCard.jsx +++ b/front/src/components/boxs/device-in-room/DeviceCard.jsx @@ -2,8 +2,8 @@ import cx from 'classnames'; import DeviceRow from './DeviceRow'; import { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } from '../../../../../server/utils/constants'; -const hasSwitchFeature = (device, featureSelectors) => { - return device.features.find(feature => isSwitchFeature(feature, featureSelectors)); +const hasSwitchFeature = (features, featureSelectors) => { + return features.find(feature => isSwitchFeature(feature, featureSelectors)); }; const isSwitchFeature = (feature, featureSelectors) => { @@ -15,16 +15,11 @@ const isSwitchFeature = (feature, featureSelectors) => { ); }; -const changeAllLightsStatusRoom = (props, roomLightStatus) => () => { - const newStatus = roomLightStatus === 1 ? 0 : 1; - props.changeAllLightsStatusRoom(props.x, props.y, newStatus); -}; - const DeviceCard = ({ children, ...props }) => { - const { boxTitle, roomLightStatus, loading, devices = [], box = {} } = props; + const { boxTitle, roomLightStatus, loading, deviceFeatures = [], box = {} } = props; const { device_features: featureSelectors = [] } = box; - const hasBinaryLightDeviceFeature = devices.find(device => hasSwitchFeature(device, featureSelectors)) !== undefined; + const hasBinaryLightDeviceFeature = hasSwitchFeature(deviceFeatures, featureSelectors) !== undefined; return (
@@ -39,7 +34,7 @@ const DeviceCard = ({ children, ...props }) => { value="1" class="custom-switch-input" checked={roomLightStatus === 1} - onClick={changeAllLightsStatusRoom(props, roomLightStatus)} + onClick={props.changeAllLightsStatusRoom} /> @@ -56,25 +51,19 @@ const DeviceCard = ({ children, ...props }) => {
- {devices.map((device, deviceIndex) => - device.features.map( - (deviceFeature, deviceFeatureIndex) => - featureSelectors.indexOf(deviceFeature.selector) !== -1 && ( - - ) - ) - )} + {deviceFeatures.map((deviceFeature, deviceFeatureIndex) => ( + + ))}
diff --git a/front/src/components/boxs/device-in-room/DeviceListWithDragAndDrop.jsx b/front/src/components/boxs/device-in-room/DeviceListWithDragAndDrop.jsx new file mode 100644 index 0000000000..b722f0e97e --- /dev/null +++ b/front/src/components/boxs/device-in-room/DeviceListWithDragAndDrop.jsx @@ -0,0 +1,97 @@ +import { DndProvider, useDrag, useDrop } from 'react-dnd'; +import { TouchBackend } from 'react-dnd-touch-backend'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { useRef } from 'preact/hooks'; +import cx from 'classnames'; +import style from './style.css'; + +const DEVICE_TYPE = 'DEVICE_TYPE'; + +const DeviceRow = ({ selectedDeviceFeature, moveDevice, index, removeDevice, updateDeviceFeatureName }) => { + const ref = useRef(null); + const [{ isDragging }, drag, preview] = useDrag(() => ({ + type: DEVICE_TYPE, + item: () => { + return { index }; + }, + collect: monitor => ({ + isDragging: !!monitor.isDragging() + }) + })); + const [{ isActive }, drop] = useDrop({ + accept: DEVICE_TYPE, + collect: monitor => ({ + isActive: monitor.canDrop() && monitor.isOver() + }), + drop(item) { + if (!ref.current) { + return; + } + moveDevice(item.index, index); + } + }); + preview(drop(ref)); + const removeThisDevice = () => { + removeDevice(index); + }; + + const updateThisDeviceFeatureName = e => { + updateDeviceFeatureName(index, e.target.value); + }; + + return ( +
+ +
+
+ +
+ +
+ +
+
+
+ ); +}; + +const DeviceListWithDragAndDrop = ({ + selectedDeviceFeaturesOptions, + isTouchDevice, + moveDevice, + removeDevice, + updateDeviceFeatureName +}) => ( + + {selectedDeviceFeaturesOptions.map((selectedDeviceFeature, index) => ( + + ))} + +); + +export { DeviceListWithDragAndDrop }; diff --git a/front/src/components/boxs/device-in-room/DeviceRow.jsx b/front/src/components/boxs/device-in-room/DeviceRow.jsx index 2f0e87431f..c490cf2a1e 100644 --- a/front/src/components/boxs/device-in-room/DeviceRow.jsx +++ b/front/src/components/boxs/device-in-room/DeviceRow.jsx @@ -1,6 +1,8 @@ import { createElement } from 'preact'; import { DEVICE_FEATURE_TYPES } from '../../../../../server/utils/constants'; +import { getDeviceName } from '../../../utils/device'; + import BinaryDeviceFeature from './device-features/BinaryDeviceFeature'; import ColorDeviceFeature from './device-features/ColorDeviceFeature'; import SensorDeviceFeature from './device-features/sensor-value/SensorDeviceFeature'; @@ -29,9 +31,12 @@ const ROW_TYPE_BY_FEATURE_TYPE = { }; const DeviceRow = ({ children, ...props }) => { + const { device, deviceFeature } = props; + const rowName = deviceFeature.new_label || getDeviceName(device, deviceFeature); + // if device is a sensor, we display the sensor deviceFeature if (props.deviceFeature.read_only) { - return ; + return ; } const elementType = ROW_TYPE_BY_FEATURE_TYPE[props.deviceFeature.type]; @@ -41,7 +46,7 @@ const DeviceRow = ({ children, ...props }) => { return null; } - return createElement(elementType, props); + return createElement(elementType, { ...props, rowName }); }; export default DeviceRow; diff --git a/front/src/components/boxs/device-in-room/DevicesBox.jsx b/front/src/components/boxs/device-in-room/DevicesBox.jsx index 61ea6a8ae1..28bcd56a5a 100644 --- a/front/src/components/boxs/device-in-room/DevicesBox.jsx +++ b/front/src/components/boxs/device-in-room/DevicesBox.jsx @@ -1,26 +1,39 @@ import { Component } from 'preact'; import { connect } from 'unistore/preact'; +import Promise from 'bluebird'; import get from 'get-value'; import { RequestStatus } from '../../../utils/consts'; -import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../server/utils/constants'; +import { + WEBSOCKET_MESSAGE_TYPES, + DEVICE_FEATURE_CATEGORIES, + DEVICE_FEATURE_TYPES +} from '../../../../../server/utils/constants'; import DeviceCard from './DeviceCard'; import debounce from 'debounce'; -const updateDevices = (devices, deviceFeatureSelector, lastValue, lastValueChange) => { - return devices.map(device => { - return { - ...device, - features: device.features.map(feature => { - if (feature.selector === deviceFeatureSelector) { - return { - ...feature, - last_value: lastValue, - last_value_changed: lastValueChange - }; - } - return feature; - }) - }; +const updateDeviceFeatures = (deviceFeatures, deviceFeatureSelector, lastValue, lastValueChange) => { + return deviceFeatures.map(feature => { + if (feature.selector === deviceFeatureSelector) { + return { + ...feature, + last_value: lastValue, + last_value_changed: lastValueChange + }; + } + return feature; + }); +}; + +const updateDeviceFeaturesString = (deviceFeatures, deviceFeatureSelector, lastValueString, lastValueChange) => { + return deviceFeatures.map(feature => { + if (feature.selector === deviceFeatureSelector) { + return { + ...feature, + last_value_string: lastValueString, + last_value_changed: lastValueChange + }; + } + return feature; }); }; @@ -28,23 +41,39 @@ class DevicesComponent extends Component { constructor(props) { super(props); this.state = { - devices: [], + deviceFeatures: [], status: RequestStatus.Getting }; } refreshData = () => { - this.getDevices(); + this.getDeviceFeatures(); }; - getDevices = async () => { + getDeviceFeatures = async () => { this.setState({ status: RequestStatus.Getting }); try { + const deviceFeatureSelectors = this.props.box.device_features; const devices = await this.props.httpClient.get(`/api/v1/device`, { - device_feature_selectors: this.props.box.device_features.join(',') + device_feature_selectors: deviceFeatureSelectors.join(',') + }); + const deviceFeaturesFlat = []; + devices.forEach(device => { + device.features.forEach(feature => { + deviceFeaturesFlat.push({ ...feature, device }); + }); }); + const deviceFeaturesSorted = deviceFeaturesFlat.sort( + (a, b) => deviceFeatureSelectors.indexOf(a.selector) - deviceFeatureSelectors.indexOf(b.selector) + ); + const deviceFeaturesNewNames = this.props.box.device_feature_names; + if (deviceFeaturesNewNames) { + deviceFeaturesSorted.forEach((deviceFeature, index) => { + deviceFeature.new_label = deviceFeaturesNewNames[index]; + }); + } this.setState({ - devices, + deviceFeatures: deviceFeaturesSorted, status: RequestStatus.Success }); } catch (e) { @@ -55,20 +84,30 @@ class DevicesComponent extends Component { }; updateDeviceStateWebsocket = payload => { - let devices = this.state.devices; - if (devices) { - devices = updateDevices(devices, payload.device_feature_selector, payload.last_value, payload.last_value_changed); + let { deviceFeatures } = this.state; + if (deviceFeatures) { + deviceFeatures = updateDeviceFeatures( + deviceFeatures, + payload.device_feature_selector, + payload.last_value, + payload.last_value_changed + ); this.setState({ - devices + deviceFeatures }); } }; updateDeviceTextWebsocket = payload => { - let devices = this.state.devices; - if (devices) { - devices = updateDevices(devices, payload.device_feature, payload.last_value, payload.last_value_changed); + let { deviceFeatures } = this.state; + if (deviceFeatures) { + deviceFeatures = updateDeviceFeaturesString( + deviceFeatures, + payload.device_feature, + payload.last_value_string, + payload.last_value_changed + ); this.setState({ - devices + deviceFeatures }); } }; @@ -79,24 +118,56 @@ class DevicesComponent extends Component { }); }; - //Remove x, y when DeviceInRoom is rewrite without action - updateValue = async (x, y, device, deviceFeature, deviceIndex, featureIndex, value) => { - const devices = updateDevices(this.state.devices, deviceFeature.selector, value, new Date()); - this.setState({ - devices + changeAllLightsStatusRoom = async () => { + const newValue = this.getLightStatus() === 0 ? 1 : 0; + // Foreach device features + await Promise.map(this.state.deviceFeatures, async feature => { + const isLightBinary = + feature.category === DEVICE_FEATURE_CATEGORIES.LIGHT && feature.type === DEVICE_FEATURE_TYPES.LIGHT.BINARY; + // if device feature is a light, we control it + if (isLightBinary) { + return this.updateValue(feature, newValue); + } }); - await this.setValueDevice(deviceFeature, value); + }; + + updateValue = async (deviceFeature, value) => { + const deviceFeatures = updateDeviceFeatures(this.state.deviceFeatures, deviceFeature.selector, value, new Date()); + await this.setState({ + deviceFeatures + }); + try { + await this.setValueDevice(deviceFeature, value); + } catch (e) { + console.error(e); + } }; setValueDeviceDebounce = debounce(this.updateValue.bind(this), 200); - //Remove x, y when DeviceInRoom is rewrite without action - updateValueWithDebounce = async (x, y, device, deviceFeature, deviceIndex, featureIndex, value) => { - const devices = updateDevices(this.state.devices, deviceFeature.selector, value, new Date()); + updateValueWithDebounce = async (deviceFeature, value) => { + const deviceFeatures = updateDeviceFeatures(this.state.deviceFeatures, deviceFeature.selector, value, new Date()); this.setState({ - devices + deviceFeatures + }); + await this.setValueDeviceDebounce(deviceFeature, value); + }; + + getLightStatus = () => { + let roomLightStatus = 0; + this.state.deviceFeatures.forEach(feature => { + // if it's a light + const isLight = + feature.category === DEVICE_FEATURE_CATEGORIES.LIGHT && + feature.type === DEVICE_FEATURE_TYPES.LIGHT.BINARY && + feature.read_only === false; + // if it's a light and it's turned on, we consider that the light + // is on in the room + if (isLight && feature.last_value === 1) { + roomLightStatus = 1; + } }); - await this.setValueDeviceDebounce(x, y, device, deviceFeature, deviceIndex, featureIndex, value); + return roomLightStatus; }; componentDidMount() { @@ -129,21 +200,24 @@ class DevicesComponent extends Component { ); } - render(props, { devices, status }) { + render(props, { deviceFeatures, status }) { const boxTitle = props.box.name; const loading = status === RequestStatus.Getting && !status; + const roomLightStatus = this.getLightStatus(); return ( ); } } -export default connect('session,httpClient', {})(DevicesComponent); +export default connect('session,httpClient,user', {})(DevicesComponent); diff --git a/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx b/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx index 969b77d0cf..724661809c 100644 --- a/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx +++ b/front/src/components/boxs/device-in-room/DevicesInRoomsBox.jsx @@ -1,73 +1,26 @@ import { Component } from 'preact'; import { connect } from 'unistore/preact'; -import get from 'get-value'; -import actions from '../../../actions/dashboard/boxes/devicesInRoom'; -import { RequestStatus, DASHBOARD_BOX_STATUS_KEY, DASHBOARD_BOX_DATA_KEY } from '../../../utils/consts'; -import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../server/utils/constants'; -import DeviceCard from './DeviceCard'; + +import DeviceBox from './DevicesBox'; class DevicesInRoomComponent extends Component { - refreshData = () => { - this.props.getDevicesInRoom(this.props.box, this.props.x, this.props.y); + getRoom = async () => { + try { + const room = await this.props.httpClient.get(`/api/v1/room/${this.props.box.room}`); + this.setState({ + room + }); + } catch (e) { + console.error(e); + } }; - updateDeviceStateWebsocket = payload => this.props.deviceFeatureWebsocketEvent(this.props.x, this.props.y, payload); - updateDeviceTextWebsocket = payload => - this.props.deviceFeatureStringStateWebsocketEvent(this.props.x, this.props.y, payload); - componentDidMount() { - this.refreshData(); - this.props.session.dispatcher.addListener( - WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE, - this.updateDeviceStateWebsocket - ); - this.props.session.dispatcher.addListener( - WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STRING_STATE, - this.updateDeviceTextWebsocket - ); - } - - componentDidUpdate(previousProps) { - const roomChanged = get(previousProps, 'box.room') !== get(this.props, 'box.room'); - const deviceFeaturesChanged = get(previousProps, 'box.device_features') !== get(this.props, 'box.device_features'); - if (roomChanged || deviceFeaturesChanged) { - this.refreshData(); - } + this.getRoom(); } - - componentWillUnmount() { - this.props.session.dispatcher.removeListener( - WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE, - this.updateDeviceStateWebsocket - ); - this.props.session.dispatcher.removeListener( - WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STRING_STATE, - this.updateDeviceTextWebsocket - ); - } - - render(props, {}) { - // safely get all data - const boxData = get(props, `${DASHBOARD_BOX_DATA_KEY}DevicesInRoom.${props.x}_${props.y}`); - const boxStatus = get(props, `${DASHBOARD_BOX_STATUS_KEY}DevicesInRoom.${props.x}_${props.y}`); - const roomName = get(boxData, `room.name`); - const devices = get(boxData, `room.devices`); - - const roomLightStatus = get(boxData, `roomLightStatus`); - const loading = boxStatus === RequestStatus.Getting && !boxData; - - return ( - - ); + render({ box, x, y }, { room }) { + const boxModified = { ...box, name: room && room.name }; + return ; } } -export default connect( - 'session,user,DashboardBoxDataDevicesInRoom,DashboardBoxStatusDevicesInRoom', - actions -)(DevicesInRoomComponent); +export default connect('httpClient', {})(DevicesInRoomComponent); diff --git a/front/src/components/boxs/device-in-room/EditDeviceInRoom.jsx b/front/src/components/boxs/device-in-room/EditDeviceInRoom.jsx index 0bb732c9c1..b7f2313722 100644 --- a/front/src/components/boxs/device-in-room/EditDeviceInRoom.jsx +++ b/front/src/components/boxs/device-in-room/EditDeviceInRoom.jsx @@ -74,8 +74,12 @@ class EditDeviceInRoom extends Component { } componentDidUpdate(prevProps) { - if (prevProps.box.room !== this.props.box.room && this.props.box.room) { - this.getDeviceFeatures(); + if (prevProps.box && this.props.box && this.props.box.room) { + const deviceFeaturesChanged = prevProps.box.device_features !== this.props.box.device_features; + const roomChanged = prevProps.box.room !== this.props.box.room; + if (deviceFeaturesChanged || roomChanged) { + this.getDeviceFeatures(); + } } } diff --git a/front/src/components/boxs/device-in-room/EditDevices.jsx b/front/src/components/boxs/device-in-room/EditDevices.jsx index 39fe467b94..1ed1c42dc5 100644 --- a/front/src/components/boxs/device-in-room/EditDevices.jsx +++ b/front/src/components/boxs/device-in-room/EditDevices.jsx @@ -2,19 +2,18 @@ import { Component } from 'preact'; import { Localizer, Text } from 'preact-i18n'; import { connect } from 'unistore/preact'; import Select from 'react-select'; +import update from 'immutability-helper'; import BaseEditBox from '../baseEditBox'; import { getDeviceFeatureName } from '../../../utils/device'; +import { DeviceListWithDragAndDrop } from './DeviceListWithDragAndDrop'; import withIntlAsProp from '../../../utils/withIntlAsProp'; import SUPPORTED_FEATURE_TYPES from './SupportedFeatureTypes'; class EditDevices extends Component { - updateDeviceFeatures = selectedDeviceFeaturesOptions => { - selectedDeviceFeaturesOptions = selectedDeviceFeaturesOptions || []; - const deviceFeatures = selectedDeviceFeaturesOptions.map(option => option.value); - this.props.updateBoxConfig(this.props.x, this.props.y, { - device_features: deviceFeatures - }); - this.setState({ selectedDeviceFeaturesOptions }); + addDeviceFeature = async selectedDeviceFeatureOption => { + const newSelectedDeviceFeaturesOptions = [...this.state.selectedDeviceFeaturesOptions, selectedDeviceFeatureOption]; + await this.setState({ selectedDeviceFeaturesOptions: newSelectedDeviceFeaturesOptions }); + this.refreshDeviceFeaturesNames(); }; updateName = e => { @@ -23,54 +22,149 @@ class EditDevices extends Component { }); }; - getDeviceFeatures = async () => { - try { - this.setState({ loading: true }); - // we get the rooms with the devices - const devices = await this.props.httpClient.get(`/api/v1/device`); - const deviceOptions = []; - const selectedDeviceFeaturesOptions = []; + refreshDeviceFeaturesNames = () => { + const newDeviceFeatureNames = this.state.selectedDeviceFeaturesOptions.map(o => { + return o.new_label !== undefined ? o.new_label : o.label; + }); + const newDeviceFeature = this.state.selectedDeviceFeaturesOptions.map(o => { + return o.value; + }); + this.props.updateBoxConfig(this.props.x, this.props.y, { + device_feature_names: newDeviceFeatureNames, + device_features: newDeviceFeature + }); + }; + + refreshDisplayForNewProps = async () => { + if (!this.state.devices) { + return; + } + if (!this.props.box || !this.props.box.device_features) { + return; + } + if (!this.state.deviceOptions) { + return; + } + const { selectedDeviceFeaturesOptions } = this.getSelectedDeviceFeaturesAndOptions(this.state.devices); + await this.setState({ selectedDeviceFeaturesOptions }); + }; - devices.forEach(device => { - const deviceFeatures = []; - device.features.forEach(feature => { - const featureOption = { - value: feature.selector, - label: getDeviceFeatureName(this.props.intl.dictionary, device, feature) - }; - if (feature.read_only || SUPPORTED_FEATURE_TYPES.includes(feature.type)) { - deviceFeatures.push(featureOption); + updateDeviceFeatureName = async (index, name) => { + const newState = update(this.state, { + selectedDeviceFeaturesOptions: { + [index]: { + new_label: { + $set: name } - if (this.props.box.device_features && this.props.box.device_features.indexOf(feature.selector) !== -1) { + } + } + }); + await this.setState(newState); + this.refreshDeviceFeaturesNames(); + }; + + getSelectedDeviceFeaturesAndOptions = devices => { + const deviceOptions = []; + let selectedDeviceFeaturesOptions = []; + + devices.forEach(device => { + const deviceFeatures = []; + device.features.forEach(feature => { + const featureOption = { + value: feature.selector, + label: getDeviceFeatureName(this.props.intl.dictionary, device, feature) + }; + if (feature.read_only || SUPPORTED_FEATURE_TYPES.includes(feature.type)) { + deviceFeatures.push(featureOption); + } + // If the feature is already selected + if (this.props.box.device_features) { + const featureIndex = this.props.box.device_features.indexOf(feature.selector); + if (this.props.box.device_features && featureIndex !== -1) { + // and there is a name associated to it + if (this.props.box.device_feature_names && this.props.box.device_feature_names[featureIndex]) { + // We set the new_label in the object + featureOption.new_label = this.props.box.device_feature_names[featureIndex]; + } + // And we push this to the list of selected feature selectedDeviceFeaturesOptions.push(featureOption); } - }); - if (deviceFeatures.length > 0) { - deviceFeatures.sort((a, b) => { - if (a.label < b.label) { - return -1; - } else if (a.label > b.label) { - return 1; - } - return 0; - }); - deviceOptions.push({ - label: device.name, - options: deviceFeatures - }); } }); - await this.setState({ deviceOptions, selectedDeviceFeaturesOptions, loading: false }); + if (deviceFeatures.length > 0) { + deviceFeatures.sort((a, b) => { + if (a.label < b.label) { + return -1; + } else if (a.label > b.label) { + return 1; + } + return 0; + }); + deviceOptions.push({ + label: device.name, + options: deviceFeatures + }); + } + }); + if (this.props.box.device_features) { + selectedDeviceFeaturesOptions = selectedDeviceFeaturesOptions.sort( + (a, b) => this.props.box.device_features.indexOf(a.value) - this.props.box.device_features.indexOf(b.value) + ); + } + return { deviceOptions, selectedDeviceFeaturesOptions }; + }; + + getDeviceFeatures = async () => { + try { + this.setState({ loading: true }); + // we get the rooms with the devices + const devices = await this.props.httpClient.get(`/api/v1/device`); + const { deviceOptions, selectedDeviceFeaturesOptions } = this.getSelectedDeviceFeaturesAndOptions(devices); + await this.setState({ devices, deviceOptions, selectedDeviceFeaturesOptions, loading: false }); + this.refreshDeviceFeaturesNames(); } catch (e) { console.error(e); this.setState({ loading: false }); } }; + moveDevice = async (currentIndex, newIndex) => { + const element = this.state.selectedDeviceFeaturesOptions[currentIndex]; + + const newStateWithoutElement = update(this.state, { + selectedDeviceFeaturesOptions: { + $splice: [[currentIndex, 1]] + } + }); + const newState = update(newStateWithoutElement, { + selectedDeviceFeaturesOptions: { + $splice: [[newIndex, 0, element]] + } + }); + await this.setState(newState); + this.refreshDeviceFeaturesNames(); + }; + + removeDevice = async index => { + const newStateWithoutElement = update(this.state, { + selectedDeviceFeaturesOptions: { + $splice: [[index, 1]] + } + }); + await this.setState(newStateWithoutElement); + this.refreshDeviceFeaturesNames(); + }; + componentDidMount() { this.getDeviceFeatures(); } + componentDidUpdate(prevProps) { + if (prevProps.box.device_features !== this.props.box.device_features) { + this.refreshDisplayForNewProps(); + } + } + render(props, { selectedDeviceFeaturesOptions, deviceOptions, loading }) { return ( @@ -91,19 +185,26 @@ class EditDevices extends Component { />
+
+ + {selectedDeviceFeaturesOptions && ( + + )} +
{deviceOptions && (
-
)} diff --git a/front/src/components/boxs/device-in-room/device-features/AirConditioningModeDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/AirConditioningModeDeviceFeature.jsx index 9edc443c11..043b53b9f3 100644 --- a/front/src/components/boxs/device-in-room/device-features/AirConditioningModeDeviceFeature.jsx +++ b/front/src/components/boxs/device-in-room/device-features/AirConditioningModeDeviceFeature.jsx @@ -2,25 +2,15 @@ import get from 'get-value'; import { Text } from 'preact-i18n'; import cx from 'classnames'; -import { getDeviceName } from '../../../../utils/device'; import { DeviceFeatureCategoriesIcon } from '../../../../utils/consts'; import { AC_MODE } from '../../../../../../server/utils/constants'; const AirConditioningModeDeviceFeature = ({ children, ...props }) => { - const { device, deviceFeature } = props; + const { deviceFeature } = props; const { category, type, last_value: lastValue } = deviceFeature; function updateValue(value) { - props.updateValueWithDebounce( - props.x, - props.y, - device, - deviceFeature, - props.deviceIndex, - props.deviceFeatureIndex, - value, - lastValue - ); + props.updateValueWithDebounce(deviceFeature, value); } function auto() { @@ -40,7 +30,7 @@ const AirConditioningModeDeviceFeature = ({ children, ...props }) => { - {getDeviceName(device, deviceFeature)} + {props.rowName}
diff --git a/front/src/components/boxs/device-in-room/device-features/BinaryDeviceFeature.jsx b/front/src/components/boxs/device-in-room/device-features/BinaryDeviceFeature.jsx index 705d9d7a9d..a9dbd65a18 100644 --- a/front/src/components/boxs/device-in-room/device-features/BinaryDeviceFeature.jsx +++ b/front/src/components/boxs/device-in-room/device-features/BinaryDeviceFeature.jsx @@ -1,16 +1,6 @@ -import { getDeviceName } from '../../../../utils/device'; - const BinaryDeviceType = ({ children, ...props }) => { function updateValue() { - props.updateValue( - props.x, - props.y, - props.device, - props.deviceFeature, - props.deviceIndex, - props.deviceFeatureIndex, - props.deviceFeature.last_value === 0 ? 1 : 0 - ); + props.updateValue(props.deviceFeature, props.deviceFeature.last_value === 0 ? 1 : 0); } return ( @@ -18,12 +8,12 @@ const BinaryDeviceType = ({ children, ...props }) => { - {getDeviceName(props.device, props.deviceFeature)} + {props.rowName}
- - - diff --git a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx index 08051246b0..beab584b57 100644 --- a/front/src/routes/settings/settings-system/SettingsSystemPage.jsx +++ b/front/src/routes/settings/settings-system/SettingsSystemPage.jsx @@ -200,6 +200,31 @@ const SystemPage = ({ children, ...props }) => ( +
+ +

+ + + +

+
+ +
+ + + +
+
+