diff --git a/package-lock.json b/package-lock.json index a670b70..4b0a113 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2865,9 +2865,9 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "bootstrap": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", - "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==" + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.0.tgz", + "integrity": "sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA==" }, "brace-expansion": { "version": "1.1.11", @@ -6081,11 +6081,6 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "gzip-size": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", @@ -8035,9 +8030,9 @@ } }, "jquery": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", - "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" }, "js-base64": { "version": "2.5.2", @@ -8767,13 +8762,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mini-create-react-context": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", - "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", "requires": { - "@babel/runtime": "^7.4.0", - "gud": "^1.0.0", - "tiny-warning": "^1.0.2" + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" } }, "mini-css-extract-plugin": { @@ -9140,9 +9134,9 @@ "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==" }, "node-sass": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.0.tgz", - "integrity": "sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -9158,7 +9152,7 @@ "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", + "sass-graph": "2.2.5", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, @@ -11595,9 +11589,9 @@ } }, "react-draggable": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.3.1.tgz", - "integrity": "sha512-m8QeV+eIi7LhD5mXoLqDzLbokc6Ncwa0T34fF6uJzWSs4vc4fdZI/XGqHYoEn91T8S6qO+BSXslONh7Jz9VPQQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.2.0.tgz", + "integrity": "sha512-5wFq//gEoeTYprnd4ze8GrFc+Rbnx+9RkOMR3vk4EbWxj02U6L6T3yrlKeiw4X5CtjD2ma2+b3WujghcXNRzkw==", "requires": { "classnames": "^2.2.5", "prop-types": "^15.6.0" @@ -11619,9 +11613,9 @@ } }, "react-grid-system": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-6.3.1.tgz", - "integrity": "sha512-TWJcAdICTseZ16ONTt5LhVYLWJPzpFl4U5LSbF4/mZ/pV7fK7W8lr2bvHOnBWLvYgP3QVcLAxdpeoXj4q2QzmA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/react-grid-system/-/react-grid-system-6.4.2.tgz", + "integrity": "sha512-zQxczLIAUyFfXJOySQlQEptwZj65hxyoEvS+siTupFW76a3AKUhqrhMJ90xKDcSCF9jb9D71zjoPw13Xe1+EKA==", "requires": { "prop-types": "^15.7.2" } @@ -11672,32 +11666,25 @@ } }, "react-rnd": { - "version": "10.1.9", - "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.1.9.tgz", - "integrity": "sha512-f6rkzEKehGRjKGK7XkdPQoHeUUawZidq3yzWdxeqFluupdbmvuM7Ygnmm6CgF6shAVrvfzxOSl/bejp9pNliUA==", + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.1.10.tgz", + "integrity": "sha512-xR+CasLBGXJUJQpds2CHocKp/Wze8/VKOf7KaaVDEy2MFzDJKcxPQ0J4QCAGSIaN20cTDmpfTyCzdTfwVGxN8A==", "requires": { "re-resizable": "6.3.2", - "react-draggable": "4.3.1", + "react-draggable": "4.2.0", "tslib": "1.11.1" - }, - "dependencies": { - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" - } } }, "react-router": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", - "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.3.0", + "mini-create-react-context": "^0.4.0", "path-to-regexp": "^1.7.0", "prop-types": "^15.6.2", "react-is": "^16.6.0", @@ -11706,15 +11693,15 @@ } }, "react-router-dom": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", - "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.1.2", + "react-router": "5.2.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -12438,219 +12425,14 @@ } }, "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", "requires": { "glob": "^7.0.0", "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "requires": { - "camelcase": "^3.0.0" - } - } + "yargs": "^13.3.2" } }, "sass-loader": { @@ -14127,9 +13909,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.2.tgz", + "integrity": "sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==" }, "ua-parser-js": { "version": "0.7.20", diff --git a/package.json b/package.json index 0e02e53..69cea14 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/react-fontawesome": "^0.1.9", "babel-runtime": "^6.26.0", - "bootstrap": "^4.4.1", + "bootstrap": "^4.5.0", "classnames": "^2.2.6", "d3-array": "^2.4.0", "d3-axis": "^1.0.12", @@ -21,11 +21,11 @@ "flux": "^3.1.3", "gaugeJS": "^1.3.7", "handlebars": "^4.7.6", - "jquery": "^3.5.0", + "jquery": "^3.5.1", "jszip": "^3.4.0", "libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git", "lodash": "^4.17.15", - "node-sass": "^4.14.0", + "node-sass": "^4.14.1", "popper.js": "^1.16.1", "prop-types": "^15.7.2", "rc-slider": "^9.2.4", @@ -37,17 +37,17 @@ "react-dnd-html5-backend": "^10.0.2", "react-dom": "^16.13.1", "react-fullscreenable": "^2.5.1-0", - "react-grid-system": "^6.3.1", + "react-grid-system": "^6.4.2", "react-json-view": "^1.19.1", "react-notification-system": "^0.3.0", - "react-rnd": "^10.1.9", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", + "react-rnd": "^10.1.10", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-scripts": "^3.4.1", "react-svg-pan-zoom": "^3.8.0", "sass": "^1.26.5", "superagent": "^5.2.2", - "typescript": "^3.8.3", + "typescript": "^3.9.2", "validator": "^12.2.0" }, "devDependencies": { diff --git a/src/__tests__/widget/edit-widget-control-creator.js b/src/__tests__/widget/edit-widget-control-creator.js index b338f7c..73efcd9 100644 --- a/src/__tests__/widget/edit-widget-control-creator.js +++ b/src/__tests__/widget/edit-widget-control-creator.js @@ -5,7 +5,7 @@ import createControls from '../../widget/edit-widget/edit-widget-control-creator import EditWidgetTextControl from '../../widget/edit-widget/edit-widget-text-control'; import EditWidgetColorControl from '../../widget/edit-widget/edit-widget-color-control'; import EditWidgetTimeControl from '../../widget/edit-widget/edit-widget-time-control'; -import EditImageWidgetControl from '../../widget/edit-widget/edit-widget-image-control'; +import EditFileWidgetControl from '../../widget/edit-widget/edit-widget-file-control'; import EditWidgetSignalControl from '../../widget/edit-widget/edit-widget-signal-control'; import EditWidgetSignalsControl from '../../widget/edit-widget/edit-widget-signals-control'; import EditWidgetOrientation from '../../widget/edit-widget/edit-widget-orientation'; @@ -28,7 +28,7 @@ describe('edit widget control creator', () => { { args: { widgetType: 'Value' }, result: { controlNumber: 5, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetTextSizeControl, EditWidgetCheckboxControl] } }, { args: { widgetType: 'Plot' }, result: { controlNumber: 5, controlTypes: [EditWidgetTimeControl, EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetMinMaxControl] } }, { args: { widgetType: 'Table' }, result: { controlNumber: 2, controlTypes: [EditWidgetCheckboxControl] } }, - { args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditImageWidgetControl, EditWidgetAspectControl] } }, + { args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditFileWidgetControl, EditWidgetAspectControl] } }, { args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } }, { args: { widgetType: 'PlotTable' }, result: { controlNumber: 5, controlTypes: [EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetTimeControl, EditWidgetMinMaxControl] } }, { args: { widgetType: 'Slider' }, result: { controlNumber: 9, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetCheckboxControl, EditWidgetMinMaxControl, EditWidgetNumberControl, EditWidgetNumberControl] } }, diff --git a/src/common/api/rest-api.js b/src/common/api/rest-api.js index 0ae4b03..3bb18d1 100644 --- a/src/common/api/rest-api.js +++ b/src/common/api/rest-api.js @@ -19,7 +19,6 @@ import request from 'superagent/lib/client'; import Promise from 'es6-promise'; import NotificationsDataManager from '../data-managers/notifications-data-manager'; - // TODO: Add this to a central pool of notifications const SERVER_NOT_REACHABLE_NOTIFICATION = { title: 'Server not reachable', @@ -55,23 +54,13 @@ class RestAPI { if (token != null) { req.set('Authorization', "Bearer " + token); - } req.end(function (error, res) { if (res == null || res.status !== 200) { reject(error); } else { - if (res.type ==="application/json"){ - resolve(JSON.parse(res.text)); - } else { - // if received data is not JSON it is a File - //create file name: - let parts = url.split("/"); - console.log("res.text has type: ", typeof res.text); - resolve({data: res.text, type: res.type, id: parts[parts.length-1]}) - } - + resolve(JSON.parse(res.text)); } }); }); @@ -134,9 +123,9 @@ class RestAPI { }); } - upload(url, data, token, progressCallback, objectType, objectID) { + upload(url, data, token, progressCallback, scenarioID) { return new Promise(function (resolve, reject) { - const req = request.post(url + "?objectType=" + objectType + "&objectID=" + objectID).send(data); //.on('progress', progressCallback); + const req = request.post(url + "?scenarioID=" + scenarioID).send(data).on('progress', progressCallback); if (token != null) { req.set('Authorization', "Bearer " + token); @@ -151,6 +140,28 @@ class RestAPI { }); }); } + + + download(url, token, fileID) { + return new Promise(function (resolve, reject) { + let req = request.get(url + "/" + fileID).buffer(true).responseType("blob") + // use blob response type and buffer + if (token != null) { + req.set('Authorization', "Bearer " + token); + } + + req.end(function (error, res) { + if (error !== null || res.status !== 200) { + reject(error); + } else { + // file data is contained in res.body (because of blob response type) + let parts = url.split("/"); + resolve({data: res.body, type: res.type, id: parts[parts.length-1]}) + } + }); + }); + } + } export default new RestAPI(); diff --git a/src/common/data-managers/rest-data-manager.js b/src/common/data-managers/rest-data-manager.js index 271f4c6..fb23eff 100644 --- a/src/common/data-managers/rest-data-manager.js +++ b/src/common/data-managers/rest-data-manager.js @@ -99,7 +99,7 @@ class RestDataManager { }); if (this.onLoad != null) { - this.onLoad(data); + this.onLoad(data, token); } }).catch(error => { AppDispatcher.dispatch({ @@ -121,7 +121,7 @@ class RestDataManager { }); if (this.onLoad != null) { - this.onLoad(data); + this.onLoad(data, token); } }).catch(error => { AppDispatcher.dispatch({ @@ -133,16 +133,50 @@ class RestDataManager { } - add(object, token = null, param = null) { + add(object, token = null, param = null, subObjects = null) { var obj = {}; obj[this.type] = this.filterKeys(object); - RestAPI.post(this.requestURL('load/add',null,param), obj, token).then(response => { AppDispatcher.dispatch({ type: this.type + 's/added', data: response[this.type], token: token }); + + // check if POST is done for import of object and issue dispatches of sub-objects + if (subObjects !== null){ + // there are sub-objects to be added for an import + for (let objectType of subObjects){ + let type = Object.keys(objectType) // type can be dashboards, configs, widgets, ... + type = type[0]; + for (let newObj of objectType[type]){ + + // set the ID of the object that the sub-object shall be associated with + if(type === "configs" || type === "dashboards"){ + // the main object is a scenario + newObj.scenarioID = response[this.type].id + } else if (type === "widgets") { + // the main object is a dashboard + newObj.dashboardID = response[this.type].id + } else if (type === "signals") { + // the main object is a component configuration + newObj.configID = response[this.type].id + } + + // iterate over all objects of type 'type' add issue add dispatch + AppDispatcher.dispatch({ + type: type + '/start-add', + data: newObj, + token: token + }) + + } + } + + + } + + }).catch(error => { AppDispatcher.dispatch({ type: this.type + 's/add-error', diff --git a/src/componentconfig/config-store.js b/src/componentconfig/config-store.js index b67377d..65f1d7c 100644 --- a/src/componentconfig/config-store.js +++ b/src/componentconfig/config-store.js @@ -28,11 +28,37 @@ class ConfigStore extends ArrayStore { switch (action.type) { case 'configs/loaded': - - ConfigsDataManager.loadSignals(action.token, action.data); - ConfigsDataManager.loadFiles(action.token, action.data); return super.reduce(state, action); + case 'configs/start-add': + // Check if this is a recursive component config import or not + if (action.data.hasOwnProperty("outputMapping") || action.data.hasOwnProperty("inputMapping")) { + // import + let subObjects = [] + let outputMapping = {} + let inputMapping = {} + + if (action.data.hasOwnProperty("outputMapping")){ + outputMapping["signals"] = action.data.outputMapping + subObjects.push(outputMapping) + delete action.data.outputMapping; // remove outputMapping signals from config object + } + if (action.data.hasOwnProperty("inputMapping")){ + inputMapping["signals"] = action.data.inputMapping + subObjects.push(inputMapping) + delete action.data.inputMapping; // remove inputMapping signals from config object + } + + // action.data should now contain the config and no sub-objects + // sub-objects are treated in add method of RestDataManager + this.dataManager.add(action.data, action.token,action.param, subObjects); + return state + + } else { + // no import + return super.reduce(state, action); + } + default: return super.reduce(state, action); diff --git a/src/componentconfig/configs-data-manager.js b/src/componentconfig/configs-data-manager.js index c0ac6c9..3fdca3f 100644 --- a/src/componentconfig/configs-data-manager.js +++ b/src/componentconfig/configs-data-manager.js @@ -17,7 +17,6 @@ import RestDataManager from '../common/data-managers/rest-data-manager'; import AppDispatcher from '../common/app-dispatcher'; -import RestAPI from "../common/api/rest-api"; class ConfigDataManager extends RestDataManager { constructor() { @@ -26,55 +25,35 @@ class ConfigDataManager extends RestDataManager { this.onLoad = this.onConfigsLoad; } - onConfigsLoad(data) { + onConfigsLoad(data, token) { if (!Array.isArray(data)) data = [ data ]; - for (let config of data) - this.loadICData(config); - } + for (let config of data) { - loadICData(config) { - AppDispatcher.dispatch({ - type: 'icData/prepare', - inputLength: parseInt(config.inputLength, 10), - outputLength: parseInt(config.outputLength, 10), - id: config.icID - }); - } + // prepare IC data + AppDispatcher.dispatch({ + type: 'icData/prepare', + inputLength: parseInt(config.inputLength, 10), + outputLength: parseInt(config.outputLength, 10), + id: config.icID + }); - loadSignals(token, configs){ - - for (let config of configs) { // request in signals - RestAPI.get(this.makeURL('/signals?direction=in&configID=' + config.id), token).then(response => { - AppDispatcher.dispatch({ - type: 'signals/loaded', - data: response.signals - }); + AppDispatcher.dispatch({ + type: 'signals/start-load', + token: token, + param: '?direction=in&configID=' + config.id, }); // request out signals - RestAPI.get(this.makeURL('/signals?direction=out&configID=' + config.id), token).then(response => { - AppDispatcher.dispatch({ - type: 'signals/loaded', - data: response.signals - }); - }); - - } - } - - loadFiles(token, configs){ - for (let config of configs) { - // request files of config - RestAPI.get(this.makeURL('/files?objectType=config&objectID=' + config.id), token).then(response => { - AppDispatcher.dispatch({ - type: 'files/loaded', - data: response.files - }); + AppDispatcher.dispatch({ + type: 'signals/start-load', + token: token, + param: '?direction=out&configID=' + config.id, }); } + } } diff --git a/src/componentconfig/edit-config.js b/src/componentconfig/edit-config.js index e629347..a69159d 100644 --- a/src/componentconfig/edit-config.js +++ b/src/componentconfig/edit-config.js @@ -32,8 +32,8 @@ class EditConfigDialog extends React.Component { name: '', icID: '', configuration: null, - startParameters: {}, - selectedFileID:0 + startParameters: this.props.config.startParameters, + selectedFileID: this.props.config.selectedFileID }; } @@ -49,7 +49,7 @@ class EditConfigDialog extends React.Component { if (this.state.icID !== '' && this.props.config.icID !== parseInt(this.state.icID)) { data.icID = parseInt(this.state.icID, 10); } - if(this.state.startParameters !== {} && this.props.config.startParameters !== this.state.startParameters){ + if(this.state.startParameters !== {} && JSON.stringify(this.props.config.startParameters) !== JSON.stringify(this.state.startParameters)){ data.startParameters = this.state.startParameters; } if (parseInt(this.state.selectedFileID, 10) !== 0 && @@ -79,9 +79,9 @@ class EditConfigDialog extends React.Component { this.valid = this.isValid() } - handleSelectedFileChange(newFileID){ - console.log("Config file change to: ", newFileID); - this.setState({selectedFileID: newFileID}) + handleSelectedFileChange(event){ + //console.log("Config file change to: ", event.target.value); + this.setState({selectedFileID: event.target.value}) this.valid = this.isValid() } @@ -121,11 +121,19 @@ class EditConfigDialog extends React.Component { - this.handleSelectedFileChange(e)} value={this.state.selectedFileID} objectID={this.props.config.id}/> + this.handleSelectedFileChange(e)} + files={this.props.files} + value={this.state.selectedFileID} + scenarioID={this.props.config.scenarioID} + sessionToken={this.props.sessionToken} + /> Start Parameters - this.handleParameterChange(data)} /> + this.handleParameterChange(data)} /> diff --git a/src/componentconfig/import-config.js b/src/componentconfig/import-config.js index 89c1986..b11a69e 100644 --- a/src/componentconfig/import-config.js +++ b/src/componentconfig/import-config.js @@ -17,18 +17,19 @@ import React from 'react'; import { FormGroup, FormControl, FormLabel } from 'react-bootstrap'; -import _ from 'lodash'; import Dialog from '../common/dialogs/dialog'; class ImportConfigDialog extends React.Component { imported = false; + valid = false; constructor(props) { super(props); this.state = { - config: {} + config: {}, + name: '', }; } @@ -39,12 +40,13 @@ class ImportConfigDialog extends React.Component { return; } - this.props.onClose(this.state.config); + this.props.onClose(this.state); } resetState = () => { this.setState({ - config: {} + config: {}, + name: '' }); this.imported = false; @@ -58,46 +60,65 @@ class ImportConfigDialog extends React.Component { } // create file reader - const reader = new FileReader(); - const self = this; + let reader = new FileReader(); + let self = this; reader.onload = event => { const config = JSON.parse(event.target.result); - config.icID = this.props.ics.length > 0 ? this.props.ics[0]._id : null; - self.imported = true; - - this.setState({ config: config }); + self.valid = true; + this.setState({name: config.name, config: config }); }; reader.readAsText(file); } - handleICChange = event => { - const config = this.state.config; + handleChange(e, index) { + this.setState({ [e.target.id]: e.target.value }); + } - config.icID = event.target.value; + validateForm(target) { + // check all controls + let name = true; - this.setState({ config: config }); + if (this.state.name === '') { + name = false; + } + this.valid = name; + + // return state to control + if (target === 'name'){ + return name; + } } render() { return ( - this.onClose(c)} onReset={this.resetState} valid={this.imported}> + this.onClose(c)} + onReset={() => this.resetState()} + valid={this.valid} >
Component Configuration File - - Infrastructure Component - - {this.props.ics.map(ic => ( - - ))} - + + Name + this.handleChange(e)} + /> +
diff --git a/src/dashboard/dashboard-store.js b/src/dashboard/dashboard-store.js index 4e850ab..5219135 100644 --- a/src/dashboard/dashboard-store.js +++ b/src/dashboard/dashboard-store.js @@ -18,4 +18,40 @@ import ArrayStore from '../common/array-store'; import DashboardsDataManager from './dashboards-data-manager'; -export default new ArrayStore('dashboards', DashboardsDataManager); +class DashboardStore extends ArrayStore { + constructor() { + super('dashboards', DashboardsDataManager); + } + + reduce(state, action) { + + switch (action.type) { + case 'dashboards/start-add': + + // Check if this is a recursive dashboard import or not + if (action.data.hasOwnProperty("widgets")) { + // import + let subObjects = [] + let widgets = {} + widgets["widgets"] = action.data.widgets + subObjects.push(widgets) + delete action.data.widgets; // remove widgets from dashboard object + + // action.data should now contain the dashboard and no sub-objects + // sub-objects are treated in add method of RestDataManager + this.dataManager.add(action.data, action.token,action.param, subObjects); + return state + + } else { + // no import + return super.reduce(state, action); + } + + default: + return super.reduce(state, action); + } + } + +} + +export default new DashboardStore(); diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 92484b3..489f366 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -32,6 +32,8 @@ import DashboardStore from './dashboard-store'; import SignalStore from '../signal/signal-store' import FileStore from '../file/file-store'; import WidgetStore from '../widget/widget-store'; +import ICStore from '../ic/ic-store' +import ConfigStore from '../componentconfig/config-store' import AppDispatcher from '../common/app-dispatcher'; import 'react-contexify/dist/ReactContexify.min.css'; @@ -40,7 +42,7 @@ class Dashboard extends Component { static lastWidgetKey = 0; static getStores() { - return [ DashboardStore, FileStore, LoginStore, WidgetStore, SignalStore ]; + return [ DashboardStore, LoginStore,FileStore, WidgetStore, SignalStore, ConfigStore, ICStore]; } static calculateState(prevState, props) { @@ -59,7 +61,7 @@ class Dashboard extends Component { }); } - // obtain all widgets of a dashboard + // obtain all widgets of this dashboard let widgets = WidgetStore.getState().filter(w => w.dashboardID === parseInt(props.match.params.dashboard, 10)); // compute max y coordinate @@ -71,29 +73,49 @@ class Dashboard extends Component { return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar; }, 0); - // TODO filter signals to the ones belonging to the scenario at hand! - let signals = SignalStore.getState(); + // filter component configurations to the ones that belong to this scenario + let configs = [] + let files = [] + if (dashboard !== null) { + configs = ConfigStore.getState().filter(config => config.scenarioID === dashboard.scenarioID); + files = FileStore.getState().filter(file => file.scenarioID === dashboard.scenarioID); + } - // get files of all widgets - let allFiles = FileStore.getState(); - let files = []; - let file, widget; - for (file of allFiles){ - for (widget of widgets){ - if (file.widgetID === widget.id){ - files.push(file); + // filter signals to the ones belonging to the scenario at hand + let signals = [] + let allSignals = SignalStore.getState(); + let sig, con; + for (sig of allSignals){ + for (con of configs){ + if (sig.configID === con.id){ + signals.push(sig); } } } - // TODO create list of infrastructure components in use + // filter ICs to the ones used by this scenario + let ics = [] + if (configs.length > 0){ + ics = ICStore.getState().filter(ic => { + let ICused = false; + for (let config of configs){ + if (ic.id === config.icID){ + ICused = true; + break; + } + } + return ICused; + }); + } return { dashboard, widgets, signals, - sessionToken: sessionToken, - files: files, + sessionToken, + files, + configs, + ics, editing: prevState.editing || false, paused: prevState.paused || false, @@ -117,7 +139,6 @@ class Dashboard extends Component { return widgetKey; } -//!!!won't work anymore componentDidMount() { // load widgets of dashboard @@ -127,9 +148,25 @@ class Dashboard extends Component { param: '?dashboardID=' + this.state.dashboard.id }); - // TODO open websockets in componentDidMount + // open web sockets if ICs are already known + if(this.state.ics.length > 0){ + console.log("Starting to open IC websockets:", this.state.ics); + AppDispatcher.dispatch({ + type: 'ics/open-sockets', + data: this.state.ics + }); + } else { + console.log("ICs unknown in componentDidMount", this.state.dashboard) + } - // TODO close websockets in componentWillUnmount + } + + componentWillUnmount() { + // close web sockets of ICs + console.log("Starting to close all web sockets"); + AppDispatcher.dispatch({ + type: 'ics/close-sockets', + }); } handleKeydown(e) { @@ -225,6 +262,15 @@ class Dashboard extends Component { this.setState({ editModal: true, modalData: widget, modalIndex: index }); }; + uploadFile(data,widget){ + AppDispatcher.dispatch({ + type: 'files/start-upload', + data: data, + token: this.state.sessionToken, + scenarioID: this.state.dashboard.scenarioID, + }); + + } closeEdit(data){ @@ -262,6 +308,15 @@ class Dashboard extends Component { startEditing(){ + this.state.widgets.forEach( widget => { + if(widget.type === 'Slider' || widget.type === 'NumberInput' || widget.type === 'Button'){ + AppDispatcher.dispatch({ + type: 'widgets/start-edit', + token: this.state.sessionToken, + data: widget + }); + } + }); this.setState({ editing: true }); }; @@ -307,14 +362,14 @@ class Dashboard extends Component { } }) }) - + temp.forEach( widget => { AppDispatcher.dispatch({ type: 'widgets/start-remove', data: widget, token: this.state.sessionToken }); - }); + }); AppDispatcher.dispatch({ type: 'widgets/start-load', token: this.state.sessionToken, @@ -412,6 +467,7 @@ class Dashboard extends Component { sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={this.closeEdit.bind(this)} + onUpload = {this.uploadFile.bind(this)} widget={this.state.modalData} signals={this.state.signals} files={this.state.files} diff --git a/src/dashboard/dashboards-data-manager.js b/src/dashboard/dashboards-data-manager.js index b35c58d..451d3bf 100644 --- a/src/dashboard/dashboards-data-manager.js +++ b/src/dashboard/dashboards-data-manager.js @@ -16,5 +16,29 @@ ******************************************************************************/ import RestDataManager from '../common/data-managers/rest-data-manager'; +import AppDispatcher from "../common/app-dispatcher"; -export default new RestDataManager('dashboard', '/dashboards'); +class DashboardsDataManager extends RestDataManager{ + constructor() { + super('dashboard', '/dashboards'); + this.onLoad = this.onDashboardsLoad + } + + onDashboardsLoad(data, token){ + + if (!Array.isArray(data)) { + data = [data]; + } + + for (let dashboard of data){ + AppDispatcher.dispatch({ + type: 'widgets/start-load', + token: token, + param: '?dashboardID=' + dashboard.id + }); + } + } + +} + +export default new DashboardsDataManager(); diff --git a/src/dashboard/import-dashboard.js b/src/dashboard/import-dashboard.js index b44e1f8..47e5216 100644 --- a/src/dashboard/import-dashboard.js +++ b/src/dashboard/import-dashboard.js @@ -67,27 +67,6 @@ class ImportDashboardDialog extends React.Component { // read IC const dashboard = JSON.parse(event.target.result); - /*let defaultIC = ""; - if (self.props.configs != null) { - defaultIC = self.props.configs[0].icID; - } - - dashboard.widgets.forEach(widget => { - switch (widget.type) { - case 'Value': - case 'Plot': - case 'Table': - case 'PlotTable': - case 'Gauge': - break; - - default: - break; - } - }); - - */ - self.imported = true; self.valid = true; self.setState({ name: dashboard.name, widgets: dashboard.widgets, grid: dashboard.grid }); @@ -107,21 +86,36 @@ class ImportDashboardDialog extends React.Component { this.valid = name; // return state to control - if (target === 'name') return name ? "success" : "error"; + if (target === 'name'){ + return name; + } } render() { return ( - this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}> + this.onClose(c)} + onReset={() => this.resetState()} + valid={this.valid}>
Dashboard File this.loadFile(e.target.files)} /> - + Name - this.handleChange(e)} /> + this.handleChange(e)} + /> diff --git a/src/dashboard/new-dashboard.js b/src/dashboard/new-dashboard.js index e71fdbc..6f4b9da 100644 --- a/src/dashboard/new-dashboard.js +++ b/src/dashboard/new-dashboard.js @@ -69,7 +69,7 @@ class NewDashboardDialog extends React.Component { return ( this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
- + Name this.handleChange(e)} /> diff --git a/src/file/file-store.js b/src/file/file-store.js index 7ab9f9b..ae6779d 100644 --- a/src/file/file-store.js +++ b/src/file/file-store.js @@ -25,23 +25,36 @@ class FileStore extends ArrayStore { saveFile(state, action){ - // save file data file - let fileID = parseInt(action.data.id) - console.log("Received file", action); - for (let f of state){ - if (f.id === fileID){ - f["data"] = action.data.data; - f.type = action.data.type; - console.log("Saving file data to file id", fileID); - } - } + let fileID = parseInt(action.id) + state.forEach((element, index, array) => { + if (element.id === fileID) { + // save blob object + array[index]["data"] = new Blob([action.data.data], {type: action.data.type}); + // update file type + array[index]["type"] = action.data.type; + if (array[index]["objectURL"] !== ''){ + // free memory of previously generated object URL + URL.revokeObjectURL(array[index]["objectURL"]); + } + // create an object URL for the file + array[index]["objectURL"] = URL.createObjectURL(array[index]["data"]) + } + }); + + // announce change to listeners + this.__emitChange(); + return state } reduce(state, action) { switch (action.type) { + case 'files/start-download': + FilesDataManager.download(action) + return state + case 'files/start-upload': - FilesDataManager.upload(action.data, action.token, action.progressCallback, action.finishedCallback, action.objectType, action.objectID); + FilesDataManager.upload(action.data, action.token, action.progressCallback, action.finishedCallback, action.scenarioID); return state; case 'files/uploaded': @@ -51,15 +64,10 @@ class FileStore extends ArrayStore { case 'files/upload-error': console.log(action.error); return state; - case 'files/loaded': - if (Array.isArray(action.data)) { - return super.reduce(state, action) - } else { - // in this case a file is contained in the response (no JSON) - // TODO we have to extract and process the file here (=save it somewhere?) - this.saveFile(state, action) - return super.reduce(state, action) - } + + case 'files/downloaded': + // in this case a file is contained in the response (no JSON) + return this.saveFile(state, action) default: return super.reduce(state, action); diff --git a/src/file/files-data-manager.js b/src/file/files-data-manager.js index 5758b62..cc5cf4b 100644 --- a/src/file/files-data-manager.js +++ b/src/file/files-data-manager.js @@ -24,8 +24,8 @@ class FilesDataManager extends RestDataManager { super('file', '/files'); } - upload(file, token = null, progressCallback = null, finishedCallback = null, objectType, objectID) { - RestAPI.upload(this.makeURL(this.url), file, token, progressCallback, objectType, objectID).then(response => { + upload(file, token = null, progressCallback = null, finishedCallback = null, scenarioID) { + RestAPI.upload(this.makeURL(this.url), file, token, progressCallback, scenarioID).then(response => { AppDispatcher.dispatch({ type: 'files/uploaded', @@ -34,13 +34,13 @@ class FilesDataManager extends RestDataManager { // Trigger a files reload AppDispatcher.dispatch({ type: 'files/start-load', - param: '?objectType=' + objectType + '&objectID=' + objectID, + param: '?scenarioID=' + scenarioID, token: token }); - /*if (finishedCallback) { - finishedCallback(); - }*/ + if (finishedCallback) { + finishedCallback(response.file.id); + } }).catch(error => { AppDispatcher.dispatch({ type: 'files/upload-error', @@ -48,6 +48,23 @@ class FilesDataManager extends RestDataManager { }); }); } + + download(action){ + RestAPI.download(this.makeURL(this.url), action.token, action.data).then(response => { + AppDispatcher.dispatch({ + type: 'files/downloaded', + data: response, + id: action.data, + token: action.token + }); + + }).catch(error => { + AppDispatcher.dispatch({ + type: 'files/load-error', + error: error + }); + }); + } } export default new FilesDataManager(); diff --git a/src/file/select-file.js b/src/file/select-file.js index b626ee3..bf7469a 100644 --- a/src/file/select-file.js +++ b/src/file/select-file.js @@ -16,58 +16,20 @@ ******************************************************************************/ import React from 'react'; -import { Container } from 'flux/utils'; -import { FormGroup, FormControl, FormLabel, Button, Col } from 'react-bootstrap'; - -import FileStore from './file-store'; -import LoginStore from '../user/login-store'; - +import { FormGroup, FormControl, FormLabel, Button, Col, ProgressBar } from 'react-bootstrap'; import AppDispatcher from '../common/app-dispatcher'; -import Icon from "../common/icon"; class SelectFile extends React.Component { - static getStores() { - return [ FileStore, LoginStore ]; + + constructor() { + super(); + + this.state = { + uploadFile: null, + uploadProgress: 0 + } } - - static calculateState(prevState, props) { - - let files = FileStore.getState().filter((file) => { - return (file.configID === props.objectID) - }); - - console.log("props.objectID=", props.objectID) - - return { - files: files, - sessionToken: LoginStore.getState().token, - selectedFile: '', - uploadFile: null, - uploadProgress: 0 - }; - } - - /*componentDidMount() { - AppDispatcher.dispatch({ - type: 'files/start-load', - token: this.state.sessionToken - }); - }*/ - - static getDerivedStateFromProps(props, state){ - - - } - - handleChange(event) { - - // send file ID to callback - if (this.props.onChange != null) { - this.props.onChange(event.target.value); - } - }; - selectUploadFile(event) { this.setState({ uploadFile: event.target.files[0] }); }; @@ -80,40 +42,50 @@ class SelectFile extends React.Component { AppDispatcher.dispatch({ type: 'files/start-upload', data: formData, - token: this.state.sessionToken, - //progressCallback: this.updateUploadProgress, - //finishedCallback: this.clearProgress, - objectType: this.props.type, - objectID: this.props.objectID, + token: this.props.sessionToken, + progressCallback: this.updateUploadProgress, + finishedCallback: this.clearProgress, + scenarioID: this.props.scenarioID, }); + + // TODO make sure that edit config dialog remains open after clicking "Upload" button }; - updateUploadProgress = (event) => {// TODO: this callback does not work properly (access to setState) + updateUploadProgress = (event) => { this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) }); }; - clearProgress = () => { // TODO this callback does not work properly (access to setState) - if (this.props.onChange != null) { - this.props.onChange(this.state.files[this.state.files.length - 1].id); + clearProgress = (newFileID) => { + /*if (this.props.onChange != null) { + let event = {} + event["target"] = {} + event.target["value"] = newFileID + this.props.onChange(event); } this.setState({ uploadProgress: 0 }); + */ + }; render() { - let fileOptions; - if (this.state.files.length > 0){ - fileOptions = this.state.files.map(f => + let fileOptions = []; + if (this.props.files.length > 0){ + fileOptions.push( + + ) + + fileOptions.push(this.props.files.map(f => - ); + )); } else { - fileOptions = + fileOptions = } - /*const progressBarStyle = { + const progressBarStyle = { marginLeft: '100px', - marginTop: '-25px' - };*/ + marginTop: '-40px' + }; return
@@ -122,29 +94,44 @@ class SelectFile extends React.Component { - this.handleChange(event)}> + this.props.onChange(event)}> {fileOptions} - - this.selectUploadFile(event)} /> + + this.selectUploadFile(event)} /> - - - {/* - + + + - */} +
; } } -let fluxContainerConverter = require('../common/FluxContainerConverter'); -export default Container.create(fluxContainerConverter.convert(SelectFile), { withProps: true }); +export default SelectFile; diff --git a/src/img/datamodel.png b/src/img/datamodel.png index e59cbc2..f98745f 100644 Binary files a/src/img/datamodel.png and b/src/img/datamodel.png differ diff --git a/src/scenario/import-scenario.js b/src/scenario/import-scenario.js index 90c33be..a6b9a23 100644 --- a/src/scenario/import-scenario.js +++ b/src/scenario/import-scenario.js @@ -52,15 +52,6 @@ class ImportScenarioDialog extends React.Component { } handleChange(e, index) { - /*if (e.target.id === 'icID') { - const configs = this.state.configs; - configs[index].icID = JSON.parse(e.target.value); - - this.setState({ configs: configs }); - - return; - }*/ - this.setState({ [e.target.id]: e.target.value }); // check all controls diff --git a/src/scenario/scenario-store.js b/src/scenario/scenario-store.js index be89e90..7ca1c17 100644 --- a/src/scenario/scenario-store.js +++ b/src/scenario/scenario-store.js @@ -19,14 +19,57 @@ import ScenariosDataManager from './scenarios-data-manager'; import ArrayStore from '../common/array-store'; -class ScenarioStore extends ArrayStore { - constructor() { - super('scenarios', ScenariosDataManager); - } - getUsers(token, id) { - ScenariosDataManager.getUsers(token, id); +class ScenarioStore extends ArrayStore{ + constructor() { + super('scenarios', ScenariosDataManager); + } + + getUsers(token, id) { + ScenariosDataManager.getUsers(token, id); + } + + reduce(state, action) { + switch (action.type) { + + case 'scenarios/start-add': + + // Check if this is a recursive scenario import or not + if (action.data.hasOwnProperty("configs") || action.data.hasOwnProperty("dashboards")) { + // import + let subObjects = [] + let configs = {} + let dashboards = {} + + if (action.data.hasOwnProperty("configs")){ + configs["configs"] = action.data.configs + subObjects.push(configs) + delete action.data.configs; // remove configs from scenario object + } + if (action.data.hasOwnProperty("dashboards")){ + dashboards["dashboards"] = action.data.dashboards + subObjects.push(dashboards) + delete action.data.dashboards; // remove dashboards from scenario object + } + + // action.data should now contain the scenario and no sub-objects + // sub-objects are treated in add method of RestDataManager + this.dataManager.add(action.data, action.token,action.param, subObjects); + return state + + } else { + // no import + return super.reduce(state, action); + } + +// case 'scenarios/users/start-load': + + default: + return super.reduce(state, action); } + } + + } -export default new ScenarioStore(); \ No newline at end of file +export default new ScenarioStore(); diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index b8e3f5a..33a1722 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -70,20 +70,11 @@ class Scenario extends React.Component { // obtain all component configurations of a scenario let configs = ConfigStore.getState().filter(config => config.scenarioID === parseInt(props.match.params.scenario, 10)); - let signals = SignalStore.getState(); - let files = FileStore.getState(); + // obtain all files of a scenario + let files = FileStore.getState().filter(file => file.scenarioID === parseInt(props.match.params.scenario, 10)); + + let signals = SignalStore.getState(); - // apply filter to contain only ICs that are used by configs - let icsUsed = ICStore.getState().filter(ic => { - let ICused = false; - for (let config of configs){ - if (ic.id === config.icID){ - ICused = true; - break; - } - } - return ICused; - }); return { @@ -95,7 +86,6 @@ class Scenario extends React.Component { signals, files, ics: ICStore.getState(), - icsUsed, deleteConfigModal: false, importConfigModal: false, @@ -124,20 +114,6 @@ class Scenario extends React.Component { token: this.state.sessionToken }); - // load component configurations for selected scenario - AppDispatcher.dispatch({ - type: 'configs/start-load', - token: this.state.sessionToken, - param: '?scenarioID='+this.state.scenario.id - }); - - // load dashboards of selected scenario - AppDispatcher.dispatch({ - type: 'dashboards/start-load', - token: this.state.sessionToken, - param: '?scenarioID='+this.state.scenario.id - }); - // load ICs to enable that component configs and dashboards work with them AppDispatcher.dispatch({ type: 'ics/start-load', @@ -145,39 +121,6 @@ class Scenario extends React.Component { }); } - componentDidUpdate(prevProps, prevState) { -// if (this.state.users) { -// this.getUsers = false; -// } - if (this.state.dashboards.length > prevState.dashboards.length) { - if (this.addWidgets) { // add widgets - // this can only be true after dashboard import, so there is only one dashboard - // (the newest) and this dashboards ID is used - let dashboardID = this.state.dashboards[this.state.dashboards.length - 1].id; - this.widgetsToAdd.forEach((widget) => { - widget.dashboardID = dashboardID; - AppDispatcher.dispatch({ - type: 'widgets/start-add', - data: widget, - token: this.state.sessionToken, - }) - }) - this.addWidgets = false; - this.widgetsToAdd = []; - } - else { // get widgets - let dashboards = Object.assign([], this.state.dashboards); - for (var i = prevState.dashboards.length; i < this.state.dashboards.length; i++) { - AppDispatcher.dispatch({ - type: 'widgets/start-load', - token: this.state.sessionToken, - param: '?dashboardID=' + dashboards[i].id - }) - } - } - } - } - /* ############################################## * Component Configuration modification methods @@ -197,13 +140,6 @@ class Scenario extends React.Component { token: this.state.sessionToken }); - this.setState({ scenario: {} }, () => { - AppDispatcher.dispatch({ - type: 'scenarios/start-load', - data: this.props.match.params.scenario, - token: this.state.sessionToken - }); - }); } closeEditConfigModal(data){ @@ -232,33 +168,47 @@ class Scenario extends React.Component { }); } - importConfig(config){ + importConfig(data){ this.setState({ importConfigModal: false }); - if (config == null) { + if (data == null) { return; } - config.scenario = this.state.scenario.id; + let newConfig = JSON.parse(JSON.stringify(data.config)) + + newConfig["scenarioID"] = this.state.scenario.id; + newConfig.name = data.name; AppDispatcher.dispatch({ type: 'configs/start-add', - data: config, + data: newConfig, token: this.state.sessionToken }); - - this.setState({ scenario: {} }, () => { - AppDispatcher.dispatch({ - type: 'scenarios/start-load', - data: this.props.match.params.scenario, - token: this.state.sessionToken - }); - }); } exportConfig(index) { // filter properties - const config = Object.assign({}, this.state.configs[index]); + let config = JSON.parse(JSON.stringify(this.state.configs[index])); + + let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10)))); + signals.forEach((signal) => { + delete signal.configID; + delete signal.id; + }) + + // two separate lists for inputMapping and outputMapping + let inputSignals = signals.filter(s => s.direction === 'in'); + let outputSignals = signals.filter(s => s.direction === 'out'); + + // add signal mappings to config + config["inputMapping"] = inputSignals; + config["outputMapping"] = outputSignals; + + delete config.id; + delete config.scenarioID; + delete config.inputLength; + delete config.outputLength; // show save dialog const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); @@ -331,12 +281,12 @@ class Scenario extends React.Component { closeNewDashboardModal(data) { this.setState({ newDashboardModal : false }); - let newDashboard = data; - // add default grid value and scenarioID - newDashboard["grid"] = 15; - newDashboard["scenarioID"] = this.state.scenario.id; - if (data) { + let newDashboard = data; + // add default grid value and scenarioID + newDashboard["grid"] = 15; + newDashboard["scenarioID"] = this.state.scenario.id; + AppDispatcher.dispatch({ type: 'dashboards/start-add', data, @@ -365,12 +315,7 @@ class Scenario extends React.Component { if (data) { let newDashboard = JSON.parse(JSON.stringify(data)); newDashboard["scenarioID"] = this.state.scenario.id; - // temporarily store widget data until dashboard is created - if (data.widgets) { - this.addWidgets = true; - this.widgetsToAdd = data.widgets; - } - delete newDashboard.widgets; + AppDispatcher.dispatch({ type: 'dashboards/start-add', data: newDashboard, @@ -381,16 +326,19 @@ class Scenario extends React.Component { exportDashboard(index) { // filter properties - const dashboard = Object.assign({}, this.state.dashboards[index]); + let dashboard = JSON.parse(JSON.stringify(this.state.dashboards[index])); - let widgets = WidgetStore.getState().filter(w => w.dashboardID === parseInt(dashboard.id, 10)); + let widgets = JSON.parse(JSON.stringify(WidgetStore.getState().filter(w => w.dashboardID === parseInt(dashboard.id, 10)))); + widgets.forEach((widget) => { + delete widget.dashboardID; + delete widget.id; + }) + dashboard["widgets"] = widgets; + delete dashboard.scenarioID; + delete dashboard.id; - - var jsonObj = dashboard; - jsonObj["widgets"] = widgets; - // show save dialog - const blob = new Blob([JSON.stringify(jsonObj, null, 2)], { type: 'application/json' }); + const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' }); FileSaver.saveAs(blob, 'dashboard - ' + dashboard.name + '.json'); } @@ -532,7 +480,15 @@ class Scenario extends React.Component {
- this.closeEditConfigModal(data)} config={this.state.modalConfigData} ics={this.state.ics} /> + this.closeEditConfigModal(data)} + config={this.state.modalConfigData} + ics={this.state.ics} + files={this.state.files} + sessionToken={this.state.sessionToken} + /> + this.importConfig(data)} ics={this.state.ics} /> this.closeDeleteConfigModal(c)} /> diff --git a/src/scenario/scenarios-data-manager.js b/src/scenario/scenarios-data-manager.js index b5e48fe..756816a 100644 --- a/src/scenario/scenarios-data-manager.js +++ b/src/scenario/scenarios-data-manager.js @@ -16,12 +16,15 @@ ******************************************************************************/ import RestDataManager from '../common/data-managers/rest-data-manager'; -import RestAPI from "../common/api/rest-api"; import AppDispatcher from "../common/app-dispatcher"; +import RestAPI from '../common/api/rest-api'; + class ScenariosDataManager extends RestDataManager { constructor() { super('scenario', '/scenarios'); + + this.onLoad = this.onScenariosLoad } getUsers(token, id) { @@ -38,33 +41,31 @@ class ScenariosDataManager extends RestDataManager { }) } - getComponentConfigs(token, id) { - RestAPI.get(this.makeURL('/scenarios/' + id + '/configs'), token).then(response => { - AppDispatcher.dispatch({ - type: 'scenarios/configs', - configs: response.configs - }); - }).catch(error => { - AppDispatcher.dispatch({ - type: 'scenarios/configs-error', - error: error - }); - }); - } + onScenariosLoad(data, token){ - getDashboards(token, id) { - RestAPI.get(this.makeURL('/scenarios/' + id + '/dashboards'), token).then(response => { - AppDispatcher.dispatch({ - type: 'scenarios/dashboards', - dashboards: response.dashboards - }); - }).catch(error => { - AppDispatcher.dispatch({ - type: 'scenarios/dashboards-error', - error: error - }); - }); - } + if (!Array.isArray(data)) { + data = [data]; + } + for (let scenario of data){ + AppDispatcher.dispatch({ + type: 'configs/start-load', + token: token, + param: '?scenarioID=' + scenario.id + }); + + AppDispatcher.dispatch({ + type: 'dashboards/start-load', + token: token, + param: '?scenarioID=' + scenario.id + }); + + AppDispatcher.dispatch({ + type: 'files/start-load', + token: token, + param: '?scenarioID='+scenario.id, + }); + } + } } export default new ScenariosDataManager(); diff --git a/src/scenario/scenarios.js b/src/scenario/scenarios.js index c00d8f6..22711a7 100644 --- a/src/scenario/scenarios.js +++ b/src/scenario/scenarios.js @@ -26,6 +26,7 @@ import LoginStore from '../user/login-store'; import DashboardStore from '../dashboard/dashboard-store'; import WidgetStore from "../widget/widget-store"; import ConfigStore from '../componentconfig/config-store'; +import SignalStore from '../signal/signal-store' import Icon from '../common/icon'; import Table from '../common/table'; @@ -40,21 +41,16 @@ import DeleteDialog from '../common/dialogs/delete-dialog'; class Scenarios extends Component { static getStores() { - return [ScenarioStore, LoginStore, DashboardStore, WidgetStore, ConfigStore]; + return [ScenarioStore, LoginStore, DashboardStore, WidgetStore, ConfigStore, SignalStore]; } static calculateState() { - const scenarios = ScenarioStore.getState(); - const sessionToken = LoginStore.getState().token; - - let dashboards = DashboardStore.getState(); - let configs = ConfigStore.getState(); return { - scenarios, - dashboards, - configs, - sessionToken, + scenarios: ScenarioStore.getState(), + dashboards: DashboardStore.getState(), + configs: ConfigStore.getState(), + sessionToken: LoginStore.getState().token, newModal: false, deleteModal: false, @@ -73,110 +69,20 @@ class Scenarios extends Component { }); } - componentDidUpdate(prevProps, prevState) { - // when length of scenarios array has increased, either add data (after import) - // or load data (after export) - if (this.state.scenarios.length > prevState.scenarios.length) { - if (this.addDashboards || this.addConfigs) { - let scenarioID = this.state.scenarios[this.state.scenarios.length - 1].id; - - if (this.addDashboards) { - this.dashboardsToAdd.forEach((dashboard) => { - if (dashboard.widgets) { - this.addWidgets = true; - } - dashboard.scenarioID = scenarioID; - AppDispatcher.dispatch({ - type: 'dashboards/start-add', - token: this.state.sessionToken, - data: dashboard - }); - }) - this.addDashboards = false; - } - - if (this.addConfigs) { - this.configsToAdd.forEach((config) => { - config.scenarioID = scenarioID; - AppDispatcher.dispatch({ - type: 'configs/start-add', - token: this.state.sessionToken, - data: config - }) - }) - delete this.configsToAdd; - this.addConfigs = false; - } - - } - else { - let scenarios = Object.assign([], this.state.scenarios); // copying neccessary? - for (var i = prevState.scenarios.length; i < scenarios.length; i++) { - AppDispatcher.dispatch({ - type: 'dashboards/start-load', - token: this.state.sessionToken, - param: '?scenarioID=' + scenarios[i].id - }); - AppDispatcher.dispatch({ - type: 'configs/start-load', - token: this.state.sessionToken, - param: '?scenarioID=' + scenarios[i].id - }); - } - } - } - - // when length of dashboards array has increased, either add widgets (after import) - // or load widgets (after export) - if (this.state.dashboards.length > prevState.dashboards.length) { - if (this.addWidgets && !this.addDashboards) { // add widget data - let dashboards = Object.assign([], this.state.dashboards); - for (var j = prevState.dashboards.length; j < dashboards.length; j++) { - let dboard = dashboards[j]; - let dboardID = dboard.id; - let dashboard = this.dashboardsToAdd.shift(); - if (dashboard.name !== dboard.name) { - console.log("Cannot add widgets, dashboard was not added as expected!"); - this.addWidgets = false; - return; - } - dashboard.widgets.forEach((widget) => { - widget.dashboardID = dboardID; - AppDispatcher.dispatch({ - type: 'widgets/start-add', - token: this.state.sessionToken, - data: widget - }); - }); - } - - if (this.dashboardsToAdd.length === 0) { - delete this.dashboardsToAdd; - this.addWidgets = false; - } - } - else { // load widget data - let dashboards = Object.assign([], this.state.dashboards); - for (var j = prevState.dashboards.length; j < dashboards.length; j++) { - AppDispatcher.dispatch({ - type: 'widgets/start-load', - token: this.state.sessionToken, - param: '?dashboardID=' + dashboards[j].id - }) - } - } - - } - } - - closeNewModal(data) { + if(data) { + AppDispatcher.dispatch({ + type: 'scenarios/start-add', + data: data, + token: this.state.sessionToken, + }); + } this.setState({ newModal: false }); } showDeleteModal(id) { // get scenario by id - var deleteScenario; + let deleteScenario; this.state.scenarios.forEach((scenario) => { if (scenario.id === id) { @@ -240,20 +146,9 @@ class Scenarios extends Component { this.setState({ importModal: false }); if (data) { - let newScenario = JSON.parse(JSON.stringify(data)); - // temporarily store dashboard data until scenario is created - if (data.dashboards) { - this.addDashboards = true; - this.dashboardsToAdd = data.dashboards; - } - if (data.configs) { - this.addConfigs = true; - this.configsToAdd = data.configs; - } - delete newScenario.dashboards; AppDispatcher.dispatch({ type: 'scenarios/start-add', - data: newScenario, + data: data, token: this.state.sessionToken, }); } @@ -279,8 +174,24 @@ class Scenarios extends Component { let jsonObj = scenario; configs.forEach((config) => { + let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10)))); + signals.forEach((signal) => { + delete signal.configID; + delete signal.id; + }) + + // two separate lists for inputMapping and outputMapping + let inputSignals = signals.filter(s => s.direction === 'in'); + let outputSignals = signals.filter(s => s.direction === 'out'); + + // add signal mappings to config + config["inputMapping"] = inputSignals; + config["outputMapping"] = outputSignals; + delete config.id; delete config.scenarioID; + delete config.inputLength; + delete config.outputLength; }) jsonObj["configs"] = configs; diff --git a/src/signal/signals-data-manager.js b/src/signal/signals-data-manager.js index 768bc7a..2e6cf98 100644 --- a/src/signal/signals-data-manager.js +++ b/src/signal/signals-data-manager.js @@ -27,7 +27,6 @@ class SignalsDataManager extends RestDataManager{ reloadConfig(token, data){ // request in signals - console.log("Reloading component config due to signal add/remove") RestAPI.get(this.makeURL('/configs/' + data.configID), token).then(response => { AppDispatcher.dispatch({ type: 'configs/edited', diff --git a/src/styles/widgets.css b/src/styles/widgets.css index 33e03f0..4e367a3 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -249,7 +249,7 @@ span.signal-unit::after { width: 100%; height: 100%; display: flex; - flex: none; + justify-content: center; word-wrap: break-word; } @@ -265,6 +265,15 @@ span.signal-unit::after { /* End button widget styling */ +/*Lamp Widget styling*/ + +.lamp-widget { + width: 100%; + height: 100%; +} + +/* End lamp widget styling*/ + .full { width: 100%; height: 100%; @@ -406,7 +415,13 @@ div[class*="-widget"] label { background-color: #fff; } -.table-widget td, .table-widget th { +.table-widget th { + position: sticky; + top: 0; + text-align: left; +} + +.table-widget td{ text-align: left; } diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index ae457b7..78bff0c 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -21,7 +21,7 @@ import EditWidgetTextControl from './edit-widget-text-control'; import EditWidgetNumberControl from './edit-widget-number-control'; import EditWidgetColorControl from './edit-widget-color-control'; import EditWidgetTimeControl from './edit-widget-time-control'; -import EditImageWidgetControl from './edit-widget-image-control'; +import EditFileWidgetControl from './edit-widget-file-control'; import EditWidgetSignalControl from './edit-widget-signal-control'; import EditWidgetSignalsControl from './edit-widget-signals-control'; import EditWidgetOrientation from './edit-widget-orientation'; @@ -33,7 +33,7 @@ import EditWidgetMinMaxControl from './edit-widget-min-max-control'; import EditWidgetHTMLContent from './edit-widget-html-content'; import EditWidgetParametersControl from './edit-widget-parameters-control'; -export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange) { +export default function CreateControls(widgetType = null, widget = null, sessionToken = null, files = null, signals, handleChange, onUpload) { // Use a list to concatenate the controls according to the widget type var DialogControls = []; @@ -84,7 +84,7 @@ export default function CreateControls(widgetType = null, widget = null, session // Restrict to only image file types (MIME) //let imageControlFiles = files == null? [] : files.filter(file => file.type.includes('image')); DialogControls.push( - handleChange(e)} />, + handleChange(e)} onUpload={(f,i) => onUpload(f,i)} />, handleChange(e)} /> ); break; @@ -149,7 +149,7 @@ export default function CreateControls(widgetType = null, widget = null, session // Restrict to only xml files (MIME) //let topologyControlFiles = files == null? [] : files.filter( file => file.type.includes('xml')); DialogControls.push( - handleChange(e)} /> + handleChange(e) } onUpload={(f,i) => onUpload(f,i)} /> ); break; diff --git a/src/widget/edit-widget/edit-widget-image-control.js b/src/widget/edit-widget/edit-widget-file-control.js similarity index 67% rename from src/widget/edit-widget/edit-widget-image-control.js rename to src/widget/edit-widget/edit-widget-file-control.js index 0f2c9e7..298ba01 100644 --- a/src/widget/edit-widget/edit-widget-image-control.js +++ b/src/widget/edit-widget/edit-widget-file-control.js @@ -18,14 +18,14 @@ import React from 'react'; import {FormGroup, FormControl, FormLabel, Button, ProgressBar} from 'react-bootstrap'; -import AppDispatcher from '../../common/app-dispatcher'; +class EditFileWidgetControl extends React.Component { -class EditImageWidgetControl extends React.Component { constructor(props) { super(props); this.state = { widget: { }, + files: [], fileList: null, progress: 0 }; @@ -33,7 +33,8 @@ class EditImageWidgetControl extends React.Component { static getDerivedStateFromProps(props, state){ return { - widget: props.widget + widget: props.widget, + files: props.files.filter(file => file.type.includes(props.type)) }; } @@ -43,20 +44,11 @@ class EditImageWidgetControl extends React.Component { for (let key in this.state.fileList) { if (this.state.fileList.hasOwnProperty(key) && this.state.fileList[key] instanceof File) { - formData.append(key, this.state.fileList[key]); + formData.append("file", this.state.fileList[key]); } } - // upload files - AppDispatcher.dispatch({ - type: 'files/start-upload', - data: formData, - token: this.props.sessionToken, - progressCallback: this.uploadProgress, - finishedCallback: this.clearProgress, - objectType: "widget", - objectID: this.props.widget.id, - }); + this.props.onUpload(formData,this.props.widget); } uploadProgress = (e) => { @@ -68,7 +60,6 @@ class EditImageWidgetControl extends React.Component { } handleFileChange(e){ - console.log("Changing image: ", this.props.controlId, "to", e.target.value) this.props.handleChange({ target: { id: this.props.controlId, value: e.target.value } }); } @@ -80,24 +71,25 @@ class EditImageWidgetControl extends React.Component { isCustomProperty = false; } - console.log("edit image: files: ", this.props.files, "widget", this.state.widget, "upload file list:", this.state.fileList); + let fileOptions = []; + if (this.state.files.length > 0){ + fileOptions.push( + + ) + fileOptions.push(this.state.files.map((file, index) => ( + + ))) + } else { + fileOptions = + } return
Image this.handleFileChange(e)}> - {this.props.files.length === 0 ? ( - - ) : ( - this.props.files.map((file, index) => ( - - )) - )} - + onChange={(e) => this.handleFileChange(e)}>{fileOptions} @@ -111,4 +103,4 @@ class EditImageWidgetControl extends React.Component { } } -export default EditImageWidgetControl; +export default EditFileWidgetControl; diff --git a/src/widget/edit-widget/edit-widget-signal-control.js b/src/widget/edit-widget/edit-widget-signal-control.js index b7d8da6..aaa9c24 100644 --- a/src/widget/edit-widget/edit-widget-signal-control.js +++ b/src/widget/edit-widget/edit-widget-signal-control.js @@ -48,7 +48,8 @@ class EditWidgetSignalControl extends Component { return ( Select signal - this.handleSignalChange(e)}> + this.handleSignalChange(e)}> + { this.props.signals.length === 0 ? ( diff --git a/src/widget/edit-widget/edit-widget.js b/src/widget/edit-widget/edit-widget.js index c8517bd..594acf0 100644 --- a/src/widget/edit-widget/edit-widget.js +++ b/src/widget/edit-widget/edit-widget.js @@ -16,10 +16,7 @@ ******************************************************************************/ import React from 'react'; -//import { FormGroup, FormControl, FormLabel } from 'react-bootstrap'; - import Dialog from '../../common/dialogs/dialog'; - import CreateControls from './edit-widget-control-creator'; class EditWidgetDialog extends React.Component { @@ -53,6 +50,7 @@ class EditWidgetDialog extends React.Component { } assignAspectRatio(changeObject, fileId) { + fileId = parseInt(fileId, 10) // get aspect ratio of file const file = this.props.files.find(element => element.id === fileId); @@ -65,12 +63,24 @@ class EditWidgetDialog extends React.Component { return changeObject; } + getTextWidth(text, fontSize) { + let font = fontSize + "px ariel"; + let canvas = this.getTextWidth.canvas || (this.getTextWidth.canvas = document.createElement("canvas")); + let context = canvas.getContext("2d"); + context.font = font; + let metrics = context.measureText(text); + return metrics.width; + } + setMaxWidth(changeObject){ if(changeObject.type === 'Label'){ - changeObject.customProperties.maxWidth = (changeObject.customProperties.textSize* 0.34) * changeObject.name.length; + changeObject.customProperties.maxWidth = Math.ceil(this.getTextWidth(changeObject.name, changeObject.customProperties.textSize)); } - else if (changeObject.type === 'Value'){ - // changeObject.customProperties.maxWidth = (changeObject.customProperties.textSize* 0.5) * (changeObject.name.length+13); + /*else if (changeObject.type === 'Value'){ + changeObject.customProperties.maxWidth = Math.ceil(this.getTextWidth(changeObject.name, changeObject.customProperties.textSize)); + }*/ + if(this.state.temporal.width > changeObject.customProperties.maxWidth){ + changeObject.width = changeObject.customProperties.maxWidth; } return changeObject; } @@ -97,13 +107,13 @@ class EditWidgetDialog extends React.Component { // not a customProperty customProperty = false; } - + if (parts[1] === 'lockAspect') { //not a customProperty customProperty ? changeObject[parts[0]][parts[1]] = e.target.checked : changeObject[e.target.id] = e.target.checked; // correct image aspect if turned on - if (e.target.checked && this.state.temporal.customProperties.file) { + if (e.target.checked && (this.state.temporal.customProperties.file !== -1)) { changeObject = this.assignAspectRatio(changeObject, this.state.temporal.customProperties.file); } } else if (e.target.id.includes('file')) { @@ -111,7 +121,7 @@ class EditWidgetDialog extends React.Component { customProperty ? changeObject[parts[0]][parts[1]] = e.target.value : changeObject[e.target.id] = e.target.value; // get file and update size (if it's an image) - if ('lockAspect' in this.state.temporal && this.state.temporal.lockAspect) { + if ((changeObject.customProperties.file !== -1)&&('lockAspect' in this.state.temporal && this.state.temporal.lockAspect)) { // TODO this if condition requires changes to work!!! changeObject = this.assignAspectRatio(changeObject, e.target.value); } @@ -122,7 +132,7 @@ class EditWidgetDialog extends React.Component { }else if(parts[1] === 'orientation'){ customProperty ? changeObject[parts[0]][parts[1]] = e.target.value : changeObject[e.target.id] = e.target.value ; changeObject = this.setNewLockRestrictions(changeObject); - } + } else if (e.target.type === 'number') { customProperty ? changeObject[parts[0]][parts[1]] = Number(e.target.value) : changeObject[e.target.id] = Number(e.target.value); } else if(e.target.id === 'name'){ @@ -167,7 +177,8 @@ class EditWidgetDialog extends React.Component { this.props.sessionToken, this.props.files, this.props.signals, - (e) => this.handleChange(e)); + (e) => this.handleChange(e), + (f,i) => this.props.onUpload(f,i)); } return ( diff --git a/src/widget/editable-widget-container.js b/src/widget/editable-widget-container.js index f96e02c..5ac2a55 100644 --- a/src/widget/editable-widget-container.js +++ b/src/widget/editable-widget-container.js @@ -114,7 +114,7 @@ class EditableWidgetContainer extends React.Component { minWidth={widget.minWidth} minHeight={widget.minHeight} maxWidth ={widget.customProperties.maxWidth || '100%' } - lockAspectRatio={Boolean(widget.isLocked)} + lockAspectRatio={Boolean(widget.customProperties.lockAspect)} bounds={'parent'} className={widgetClasses} onResizeStart={this.borderWasClicked} diff --git a/src/widget/widget-factory.js b/src/widget/widget-factory.js index 3673a63..4586464 100644 --- a/src/widget/widget-factory.js +++ b/src/widget/widget-factory.js @@ -70,7 +70,7 @@ class WidgetFactory { case 'Value': widget.minWidth = 70; widget.minHeight = 20; - widget.width = 150; + widget.width = 110; widget.height = 30; widget.customProperties.textSize = 16; widget.name = 'Value'; @@ -122,7 +122,7 @@ class WidgetFactory { widget.width = 200; widget.height = 200; widget.customProperties.lockAspect = true; - widget.customProperties.file = 2; // ID of image file, -1 means non selected + widget.customProperties.file = -1; // ID of image file, -1 means non selected break; case 'Button': widget.minWidth = 100; @@ -134,6 +134,7 @@ class WidgetFactory { widget.customProperties.on_value = 1; widget.customProperties.off_value = 0; widget.customProperties.toggle = false; + widget.customProperties.pressed = false; break; case 'NumberInput': widget.minWidth = 150; @@ -142,6 +143,7 @@ class WidgetFactory { widget.height = 50; widget.customProperties.showUnit = false; widget.customProperties.resizeTopBottomLock = true; + widget.customProperties.value = ''; break; case 'Slider': widget.minWidth = 380; @@ -154,7 +156,8 @@ class WidgetFactory { widget.customProperties.rangeUseMinMax = true; widget.customProperties.showUnit = true; widget.customProperties.continous_update = false; - widget.customProperties.default_value = 0; + widget.customProperties.default_value = '0'; + widget.customProperties.value = ''; widget.customProperties.resizeLeftRightLock = false; widget.customProperties.resizeTopBottomLock = true; @@ -185,6 +188,7 @@ class WidgetFactory { case 'Topology': widget.width = 600; widget.height = 400; + widget.customProperties.file = -1; // ID of file, -1 means non selected break; default: diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js index 22aba64..f3f4cd6 100644 --- a/src/widget/widget-plot/plot-legend.js +++ b/src/widget/widget-plot/plot-legend.js @@ -26,7 +26,7 @@ class PlotLegend extends React.Component { return
    {this.props.signals.map(signal => -
  • +
  • {signal.name} {signal.unit}
  • diff --git a/src/widget/widget-plot/plot.js b/src/widget/widget-plot/plot.js index 444d540..0b3d4a8 100644 --- a/src/widget/widget-plot/plot.js +++ b/src/widget/widget-plot/plot.js @@ -80,7 +80,7 @@ class Plot extends React.Component { } // check if data is invalid - if (props.data == null || props.data.length === 0 || props.data[0].length === 0) { + if (props.data == null || props.data.length === 0) { // create empty plot axes let xScale; let yScale; @@ -116,11 +116,14 @@ class Plot extends React.Component { // only show data in requested time let data = props.data; + let icDataset = data.find(function(element) { + return element !== undefined; + }) - const firstTimestamp = data[0][data[0].length - 1].x - (props.time + 1) * 1000; - if (data[0][0].x < firstTimestamp) { + const firstTimestamp = icDataset[icDataset.length - 1].x - (props.time + 1) * 1000; + if (icDataset[0].x < firstTimestamp) { // only show data in range (+100 ms) - const index = data[0].findIndex(value => value.x >= firstTimestamp - 100); + const index = icDataset.findIndex(value => value.x >= firstTimestamp - 100); data = data.map(values => values.slice(index)); } @@ -177,7 +180,11 @@ class Plot extends React.Component { if (this.props.yUseMinMax) { yRange = [this.props.yMin, this.props.yMax]; } else if (this.props.data.length > 0) { - yRange = [this.props.data[0][0].y, this.props.data[0][0].y]; + let icDataset = this.props.data.find(function(element) { + return element !== undefined; + }) + + yRange = [icDataset[0].y, icDataset[0].y]; this.props.data.forEach(values => { const range = extent(values, p => p.y); diff --git a/src/widget/widget-store.js b/src/widget/widget-store.js index f49ca27..ff1ad64 100644 --- a/src/widget/widget-store.js +++ b/src/widget/widget-store.js @@ -29,7 +29,8 @@ class WidgetStore extends ArrayStore { case 'widgets/loaded': - WidgetsDataManager.loadFiles(action.token, action.data); + //WidgetsDataManager.loadFiles(action.token, action.data); + // TODO make sure files of scenario are loaded return super.reduce(state, action); default: diff --git a/src/widget/widget.js b/src/widget/widget.js index 31a7be2..a2eb294 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -69,14 +69,12 @@ class Widget extends React.Component { // TODO make sure that the signals are only the signals that belong to the scenario at hand let signals = SignalStore.getState(); let icIDs = []; - if ( props.data.signalIDs.length > 0){ - for (let i in props.data.signalIDs.length){ - let signal = signals.find(s => s.id === props.data.signalIDs[i]); - let config = configs.find(m => m.id === signal.configID); - icIDs[i] = config.icID; - } - } + for (let id of props.data.signalIDs){ + let signal = signals.find(s => s.id === id); + let config = configs.find(m => m.id === signal.configID); + icIDs[signal.id] = config.icID; + } return { icData: icData, @@ -90,27 +88,6 @@ class Widget extends React.Component { }; } - componentDidMount() { - if (this.state.sessionToken == null) { - return; - } - - /*AppDispatcher.dispatch({ - type: 'files/start-load', - token: this.state.sessionToken, - param: '?objectID=1&objectType=widget' - });*/ - - // TODO no not load component congfigs here, since they are loaded via the scenario, pass them as props - /* - AppDispatcher.dispatch({ - type: 'configs/start-load', - token: this.state.sessionToken, - param: '?scenarioID=1' // TODO do not hardcode scenarioID! - }); - */ - } - inputDataChanged(widget, data) { // The following assumes that a widget modifies/ uses exactly one signal AppDispatcher.dispatch({ @@ -132,13 +109,13 @@ class Widget extends React.Component { } else if (widget.type === 'Value') { return } else if (widget.type === 'Plot') { - return + return } else if (widget.type === 'Table') { return } else if (widget.type === 'Label') { return } else if (widget.type === 'PlotTable') { - return this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} /> + return this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} /> } else if (widget.type === 'Image') { return } else if (widget.type === 'Button') { @@ -154,7 +131,7 @@ class Widget extends React.Component { } else if (widget.type === 'HTML') { return } else if (widget.type === 'Topology') { - return + return } return null; diff --git a/src/widget/widgets-data-manager.js b/src/widget/widgets-data-manager.js index c32417d..61d1cf0 100644 --- a/src/widget/widgets-data-manager.js +++ b/src/widget/widgets-data-manager.js @@ -17,8 +17,6 @@ import RestDataManager from '../common/data-managers/rest-data-manager'; -import RestAPI from "../common/api/rest-api"; -import AppDispatcher from "../common/app-dispatcher"; class WidgetsDataManager extends RestDataManager{ @@ -26,18 +24,6 @@ class WidgetsDataManager extends RestDataManager{ super('widget', '/widgets'); } - loadFiles(token, widgets){ - for (let widget of widgets) { - // request files of widget - RestAPI.get(this.makeURL('/files?objectType=widget&objectID=' + widget.id), token).then(response => { - AppDispatcher.dispatch({ - type: 'files/loaded', - data: response.files - }); - }); - } - } - } export default new WidgetsDataManager() diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js index c400b54..977a8ae 100644 --- a/src/widget/widgets/button.js +++ b/src/widget/widgets/button.js @@ -24,12 +24,12 @@ class WidgetButton extends Component { super(props); this.state = { - pressed: false + pressed: props.widget.customProperties.pressed } } onPress(e) { - console.log("button was pressed!"); + if (!this.props.widget.customProperties.toggle) { this.setState({ pressed: true }); this.valueChanged(this.props.widget.customProperties.on_value); @@ -37,12 +37,12 @@ class WidgetButton extends Component { } onRelease(e) { - console.log("button was released!"); + let nextState = false; if (this.props.widget.customProperties.toggle) { nextState = !this.state.pressed; } - + this.props.widget.customProperties.pressed = nextState; this.setState({ pressed: nextState }); this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value); } diff --git a/src/widget/widgets/image.js b/src/widget/widgets/image.js index 2fea54b..1f08fa3 100644 --- a/src/widget/widgets/image.js +++ b/src/widget/widgets/image.js @@ -21,39 +21,60 @@ import AppDispatcher from '../../common/app-dispatcher'; class WidgetImage extends React.Component { + constructor(props) { + super(props); + + this.state = { + file: undefined, + } + } + componentDidMount() { // Query the image referenced by the widget let widgetFile = this.props.widget.customProperties.file; - if (widgetFile !== -1 && !this.props.files.find(file => file.id === widgetFile)) { + if (widgetFile !== -1 && this.state.file === undefined) { AppDispatcher.dispatch({ - type: 'files/start-load', + type: 'files/start-download', data: widgetFile, token: this.props.token }); } } - render() { - const file = this.props.files.find(file => file.id === this.props.widget.customProperties.file); - let fileHasData = false; - let fileData, objectURL; - if (file){ - fileHasData = file.hasOwnProperty("data"); - if (fileHasData){ - //console.log("File data: ", file.data); + componentDidUpdate(prevProps: Readonly

    , prevState: Readonly, snapshot: SS) { - fileData = new Blob([file.data], {type: file.type}); - objectURL = window.URL.createObjectURL(fileData); - console.log("Image created new file", fileData, "and objectID", objectURL) + let file = this.props.files.find(file => file.id === parseInt(this.props.widget.customProperties.file, 10)); + + if (file !== undefined) { + if (this.state.file === undefined || (this.state.file.id !== file.id)) { + + AppDispatcher.dispatch({ + type: 'files/start-download', + data: file.id, + token: this.props.token + }); + + this.setState({ file: file }) } } - console.log("Image: has data:", fileHasData); + } + + imageError(e){ + console.error("Image ", this.state.file.name, "cannot be displayed."); + } + + render() { + + let objectURL='' + if(this.state.file !== undefined && this.state.file.objectURL !== undefined) { + objectURL = this.state.file.objectURL + } return (

    - {file ? ( - {file.name} e.preventDefault()} /> + {objectURL !== '' ? ( + this.imageError(e)} className="full" alt={this.state.file.name} src={objectURL} onDragStart={e => e.preventDefault()} /> ) : ( No file selected. )} diff --git a/src/widget/widgets/input.js b/src/widget/widgets/input.js index 59e2290..58273ad 100644 --- a/src/widget/widgets/input.js +++ b/src/widget/widgets/input.js @@ -31,14 +31,27 @@ class WidgetInput extends Component { static getDerivedStateFromProps(props, state){ - if(props.widget.signalIDs.length === 0){ - return null; - } - let returnState = {}; + if(props.widget.customProperties.value !== ''){ + returnState["value"] = props.widget.customProperties.value; + } + + if(props.widget.signalIDs.length === 0){ + if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') { + returnState["value"] = props.widget.customProperties.default_value; + } else { // if no default available + if (returnState !== {}){ + return returnState; + } + else{ + return null; + } + } + } + // Update value - if (props.widget.customProperties.default_value && this.state.value === undefined) { + if (props.widget.customProperties.default_value && this.state.value === undefined && props.widget.customProperties.value === '') { returnState["value"] = props.widget.customProperties.default_value; } @@ -59,6 +72,7 @@ class WidgetInput extends Component { valueIsChanging(newValue) { this.setState({ value: newValue }); + this.props.widget.customProperties.value = newValue; } valueChanged(newValue) { diff --git a/src/widget/widgets/lamp.js b/src/widget/widgets/lamp.js index b9a9605..7a89c04 100644 --- a/src/widget/widgets/lamp.js +++ b/src/widget/widgets/lamp.js @@ -66,8 +66,6 @@ class WidgetLamp extends Component { let style = { backgroundColor: color, - width: this.props.widget.width, - height: this.props.widget.height } return ( diff --git a/src/widget/widgets/plot-table.js b/src/widget/widgets/plot-table.js index e16b7b1..155131e 100644 --- a/src/widget/widgets/plot-table.js +++ b/src/widget/widgets/plot-table.js @@ -16,123 +16,80 @@ ******************************************************************************/ import React, { Component } from 'react'; -import classNames from 'classnames'; -import { FormGroup, FormCheck } from 'react-bootstrap'; - +import { FormGroup } from 'react-bootstrap'; import Plot from '../widget-plot/plot'; import PlotLegend from '../widget-plot/plot-legend'; class WidgetPlotTable extends Component { constructor(props) { super(props); - this.state = { - preselectedSignals: [], - signals: [] + signals: [], + data: [] }; } - componentDidUpdate(prevProps: Readonly

    , prevState: Readonly, snapshot: SS): void { - if (this.props.config == null) { - return; - } + static getDerivedStateFromProps(props, state){ + let intersection = [] + let data = []; + let signalID, sig; + for (signalID of props.widget.signalIDs) { + for (sig of props.signals) { + if (signalID === sig.id) { + intersection.push(sig); - // Update internal selected signals state with props (different array objects) - if (prevProps.widget.customProperties.signals !== this.props.widget.customProperties.signals) { - this.setState( {signals: this.props.widget.customProperties.signals}); - } + // sig is a selected signal, get data + // determine ID of infrastructure component related to signal (via config) + let icID = props.icIDs[sig.id] - // Identify if there was a change in the preselected signals - if (JSON.stringify(prevProps.widget.customProperties.preselectedSignals) !== JSON.stringify(this.props.widget.customProperties.preselectedSignals) - || this.state.preselectedSignals.length === 0) { - // Update the currently selected signals by intersecting with the preselected signalsWidget - // Do the same with the plot values - var intersection = this.computeIntersection(this.props.widget.customProperties.preselectedSignals, this.props.widget.customProperties.signals); - this.setState({ signals: intersection }); - - this.updatePreselectedSignalsState(this.props); - } - } - - // Perform the intersection of the lists, alternatively could be done with Sets ensuring unique values - computeIntersection(preselectedSignals, selectedSignals) { - return preselectedSignals.filter( s => selectedSignals.includes(s)); - } - - updatePreselectedSignalsState(props) { - // Create checkboxes using the signal indices from component config - if(props.config.outputMapping){ - const preselectedSignals = props.config.outputMapping.reduce( - // Loop through component config signals - (accum, signal, signal_index) => { - // Append them if they belong to the current selected type - if (props.widget.customProperties.preselectedSignals.indexOf(signal_index) > -1) { - accum.push( - { - index: signal_index, - name: signal.name, - type: signal.type + // distinguish between input and output signals + if (sig.direction === "out") { + if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) { + if (props.data[icID].output.values[sig.index-1] !== undefined) { + data.push(props.data[icID].output.values[sig.index-1]); } - ) + } + } else if (sig.direction === "in") { + if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) { + if (props.data[icID].input.values[sig.index-1] !== undefined) { + data.push(props.data[icID].input.values[sig.index-1]); + } + } } - return accum; - }, []); + } // sig is selected signal + } // loop over props.signals + } // loop over selected signals - this.setState({ preselectedSignals }); - } + return {signals: intersection, data: data} } - updateSignalSelection(signal_index, checked) { - // Update the selected signals and propagate to parent component - var new_widget = Object.assign({}, this.props.widget, { - signals: checked? this.state.signals.concat(signal_index) : this.state.signals.filter( (idx) => idx !== signal_index ) - }); - this.props.onWidgetChange(new_widget); - } + // updateSignalSelection(signal, checked) { + // // Update the selected signals and propagate to parent component + // var new_widget = Object.assign({}, this.props.widget, { + // checkedSignals: checked ? this.state.signals.concat(signal) : this.state.signals.filter((idx) => idx !== signal) + // }); + // this.props.onWidgetChange(new_widget); + // } render() { let checkBoxes = []; - let icData = []; - let legendSignals = []; - // Data passed to plot - if (this.props.config) { - - const ic = this.props.config.icID; - - if (this.props.data[ic] != null && this.props.data[ic].output != null && this.props.data[ic].output.values != null) { - icData = this.props.data[ic].output.values.filter((values, index) => ( - this.props.widget.customProperties.signals.findIndex(value => value === index) !== -1 - )); - } - - if (this.state.preselectedSignals && this.state.preselectedSignals.length > 0) { - // Create checkboxes using the signal indices from component config - checkBoxes = this.state.preselectedSignals.map( (signal) => { - var checked = this.state.signals.indexOf(signal.index) > -1; - var chkBxClasses = classNames({ - 'btn': true, - 'btn-default': true, - 'active': checked - }); - return this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } - }); - } - - // Prepare an array with the signals to show in the legend - legendSignals = this.state.preselectedSignals.reduce( (accum, signal, i) => { - if (this.state.signals.includes(signal.index)) { - accum.push({ - index: signal.index, - name: signal.name, - type: signal.type - }); - } - return accum; - }, []);} let showLegend = false; - if(legendSignals !== []){ + if (this.state.signals.length > 0) { + showLegend = true; + + // Create checkboxes using the signal indices from component config + // checkBoxes = this.state.signals.map((signal) => { + // let checked = this.state.signals.indexOf(signal) > -1; + // let chkBxClasses = classNames({ + // 'btn': true, + // 'btn-default': true, + // 'active': checked + // }); + // return this.updateSignalSelection(signal, e.target.checked)}> {signal.name} + // }); } return ( @@ -140,17 +97,17 @@ class WidgetPlotTable extends Component {

    - { checkBoxes.length > 0 ? ( + {checkBoxes.length > 0 ? ( - { checkBoxes } + {checkBoxes} - ) : ( No signal has been pre-selected. ) + ) : (Use edit menu to change selected signals.) }
    - {showLegend? ( - ) : (
    ) + {showLegend ? ( + ) : (
    ) }
    ); } } - export default WidgetPlotTable; diff --git a/src/widget/widgets/plot.js b/src/widget/widgets/plot.js index 15961a5..20245e2 100644 --- a/src/widget/widgets/plot.js +++ b/src/widget/widgets/plot.js @@ -26,49 +26,44 @@ class WidgetPlot extends React.Component { this.state = { data: [], - legend: [] + signals: [] }; } static getDerivedStateFromProps(props, state){ - if (props.config == null) { - return{ - data: [], - legend: [], - }; - } + let intersection = [] + let data = []; + let signalID, sig; + for (signalID of props.widget.signalIDs) { + for (sig of props.signals) { + if (signalID === sig.id) { + intersection.push(sig); - const ic = props.config.icID; + // sig is a selected signal, get data + // determine ID of infrastructure component related to signal (via config) + let icID = props.icIDs[sig.id] - // Proceed if a config and a IC are available - if (ic && props.data[ic] != null && props.data[ic] != null && props.data[ic].output != null && props.data[ic].output.values != null) { - const chosenSignals = props.widget.customProperties.signals; + // distinguish between input and output signals + if (sig.direction === "out") { + if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) { + if (props.data[icID].output.values[sig.index-1] !== undefined) { + data.push(props.data[icID].output.values[sig.index-1]); + } + } + } else if (sig.direction === "in") { + if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) { + if (props.data[icID].input.values[sig.index-1] !== undefined) { + data.push(props.data[icID].input.values[sig.index-1]); + } + } + } + } // sig is selected signal + } // loop over props.signals + } // loop over selected signals - const data = props.data[ic].output.values.filter((values, index) => ( - props.widget.customProperties.signals.findIndex(value => value === index) !== -1 - )); - - // Query the signals that will be displayed in the legend - const legend = props.config.outputMapping.reduce( (accum, signal, signal_index) => { - if (chosenSignals.includes(signal_index)) { - accum.push({ index: signal_index, name: signal.name, type: signal.unit }); - } - - return accum; - }, []); - - return{ - data: data, - legend: legend, - }; - } else { - return{ - data: [], - legend: [], - }; - } + return {signals: intersection, data: data} } @@ -87,7 +82,7 @@ class WidgetPlot extends React.Component { yLabel={this.props.widget.customProperties.ylabel} />
- +
; } } diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js index 4b9bba5..ce76fc6 100644 --- a/src/widget/widgets/slider.js +++ b/src/widget/widgets/slider.js @@ -42,19 +42,28 @@ class WidgetSlider extends Component { static getDerivedStateFromProps(props, state){ let returnState = {}; + if(props.widget.customProperties.value !== ''){ + returnState["value"] = props.widget.customProperties.value; + } + if(props.widget.signalIDs.length === 0){ // set value to default - if (props.widget.customProperties.default_value && state.value === undefined) { + if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') { returnState["value"] = props.widget.customProperties.default_value; } else { // if no default available - return null; + if (returnState !== {}){ + return returnState; + } + else{ + return null; + } } } // Update value - if (props.widget.customProperties.default_value && state.value === undefined) { + if (props.widget.customProperties.default_value && state.value === undefined && props.widget.customProperties.value === '') { returnState["value"] = props.widget.customProperties.default_value; } @@ -96,6 +105,7 @@ class WidgetSlider extends Component { } valueIsChanging(newValue) { + this.props.widget.customProperties.value = newValue; if (this.props.widget.continous_update) this.valueChanged(newValue); diff --git a/src/widget/widgets/table.js b/src/widget/widgets/table.js index a7ef37b..54383a8 100644 --- a/src/widget/widgets/table.js +++ b/src/widget/widgets/table.js @@ -94,6 +94,7 @@ class WidgetTable extends Component { render() { let rows = this.state.rows; + if(rows.length === 0){ rows.push({ name: "no entries" @@ -109,7 +110,7 @@ class WidgetTable extends Component { columns.push() return ( -
+
{ columns }
diff --git a/src/widget/widgets/topology.js b/src/widget/widgets/topology.js index d9e5d82..8aad399 100644 --- a/src/widget/widgets/topology.js +++ b/src/widget/widgets/topology.js @@ -17,9 +17,9 @@ import React from 'react'; import {UncontrolledReactSVGPanZoom} from 'react-svg-pan-zoom'; -import config from '../../config'; import '../../styles/simple-spinner.css'; import { cimsvg } from 'libcimsvg'; +import AppDispatcher from "../../common/app-dispatcher"; // Do not show Pintura's grid const pinturaGridStyle = { @@ -62,11 +62,22 @@ function textSibling(e) { } function show(element) { - element.style.visibility = 'inherit'; + if(element !== undefined) { + element.style.visibility = 'inherit'; + } + else{ + console.log("MouseOver, show, element undefined.") + } } function hide(element) { - element.style.visibility = 'hidden'; + if (element !== undefined) { + element.style.visibility = 'hidden'; + } else { + console.log("MouseLeave, hide, element undefined.") + } + + } // De-initialize functions @@ -80,12 +91,27 @@ class WidgetTopology extends React.Component { super(props); this.svgElem = React.createRef(); this.Viewer = null; + this.dashboardState = 'initial' + this.message = '' + let file = this.props.files.find(file => file.id === parseInt(this.props.widget.customProperties.file, 10)); + this.state = { - dashboardState: 'initial' + file: file }; } + static getDerivedStateFromProps(props, state){ + let file = props.files.find(file => file.id === parseInt(props.widget.customProperties.file, 10)); + + if (state.file === undefined || state.file.id !== file.id) { + return{ + file: file + }; + } + return null + } + componentDidMount() { if (this.svgElem) { window.onMouseLeave = function() {}; @@ -95,6 +121,19 @@ class WidgetTopology extends React.Component { window.onMouseDown = function() {}; window.onMouseMove = function() {}; } + + //this.Viewer.fitToViewer(); + + // Query the file referenced by the widget + let widgetFile = parseInt(this.props.widget.customProperties.file, 10); + if (widgetFile !== -1 && this.state.file === undefined) { + this.dashboardState = 'loading'; + AppDispatcher.dispatch({ + type: 'files/start-download', + data: widgetFile, + token: this.props.token + }); + } } componentWillUnmount() { @@ -102,84 +141,80 @@ class WidgetTopology extends React.Component { } componentDidUpdate(prevProps: Readonly

, prevState: Readonly, snapshot: SS): void { - const file = this.props.files.find(file => file._id === this.props.widget.customProperties.file); - // Ensure model is requested only once or a different was selected - if (prevProps.widget.customProperties.file !== this.props.widget.customProperties.file - || (prevState.dashboardState === 'initial' && file)) { - this.setState({'dashboardState': 'loading' }); - if (file) { - fetch(new Request('/' + config.publicPathBase + file.path)) - .then( response => { - if (response.status === 200) { - this.setState({'dashboardState': 'ready' }); - return response.text().then( contents => { - if (this.svgElem) { - let cimsvgInstance = new cimsvg(this.svgElem.current); - cimsvg.setCimsvg(cimsvgInstance); - cimsvgInstance.setFileCount(1); - cimsvgInstance.loadFile(contents); - //cimsvgInstance.fit(); - attachComponentEvents(); - } - else { - console.error("The svgElem variable is not initialized before the attempt to create the cimsvg instance."); - } - }); - } else { - throw new Error('Request failed'); - } - }) - .catch(error => { - console.error(error); - this.setState({ - 'dashboardState': 'show_message', - 'message': 'Topology could not be loaded.'}); + if(this.state.file === undefined) { + // No file has been selected + this.dashboardState = 'show_message'; + this.message = 'Select a topology model first.'; + return; + } + + if((prevState.file === undefined && this.state.file !== undefined) + || (this.state.file.id !== prevState.file.id && this.state.file.id !== -1)) { + // if file has changed, download new file + this.dashboardState = 'loading'; + AppDispatcher.dispatch({ + type: 'files/start-download', + data: this.state.file.id, + token: this.props.token + }); + } else if (this.state.file.hasOwnProperty("data") && this.dashboardState === 'loading') { + // data of file has been newly downloaded (did not exist in previous state) + this.dashboardState = 'ready'; + + } else if(this.state.file.hasOwnProperty("data") && this.dashboardState === 'ready'){ + if (this.svgElem) { + let cimsvgInstance = new cimsvg(this.svgElem.current); + cimsvg.setCimsvg(cimsvgInstance); + cimsvgInstance.setFileCount(1); + // transform data blob into string format + this.state.file.data.text().then(function(content) { + cimsvgInstance.loadFile(content); + cimsvgInstance.fit(); + attachComponentEvents(); }); } - } else { - // No file has been selected - if (!this.props.widget.customProperties.file&& this.state.message !== 'Select a topology model first.') { - this.setState({ - 'dashboardState': 'show_message', - 'message': 'Select a topology model first.'}); + else { + console.error("The svgElem variable is not initialized before the attempt to create the cimsvg instance."); } } + } render() { + var markup = null; const miniatureProps = { - miniaturePosition: "none", - } - - const toolbarProps = { - toolbarPosition: "none" + position: "none", } - switch(this.state.dashboardState) { + const toolbarProps = { + position: "right" + } + + switch(this.dashboardState) { case 'loading': markup =

; break; case 'show_message': - markup =
{ this.state.message }
; break; + markup =
{ this.message }
; break; default: markup = (
this.Viewer = Viewer} - style={{outline: "1px solid grey"}} - detectAutoPan={false} - toolbarProps={toolbarProps} - miniatureProps={miniatureProps} - background="white" - tool="pan" - width={this.props.widget.width-2} height={this.props.widget.height-2} > - - - - - - - + ref={Viewer => this.Viewer = Viewer} + style={{outline: "1px solid grey"}} + detectAutoPan={false} + toolbarProps={toolbarProps} + miniatureProps={miniatureProps} + background={"white"} + width={this.props.widget.width-2} + height={this.props.widget.height-2} > + + + + + + +
); } diff --git a/src/widget/widgets/value.js b/src/widget/widgets/value.js index 8951d80..f143cd0 100644 --- a/src/widget/widgets/value.js +++ b/src/widget/widgets/value.js @@ -69,14 +69,14 @@ class WidgetValue extends Component { render() { let value_to_render = Number(this.state.value); - let value_width = this.props.widget.customProperties.textSize*0.55* (this.state.value.length +2); - let unit_width = this.props.widget.customProperties.textSize*2; + let value_width = this.props.widget.customProperties.textSize*(value_to_render < 1000 ? (2):(3)); + let unit_width = this.props.widget.customProperties.textSize*(this.state.unit.length + 0.7); return (
- {this.props.widget.name} - {Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)} + {this.props.widget.name} + {Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)} {this.props.widget.customProperties.showUnit && - [{this.state.unit}] + [{this.state.unit}] }
);