1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

merged with development branch

This commit is contained in:
irismarie 2020-05-29 17:52:53 +02:00
commit 218e7fba4f
46 changed files with 1017 additions and 1109 deletions

298
package-lock.json generated
View file

@ -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",

View file

@ -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": {

View file

@ -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] } },

View file

@ -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();

View file

@ -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',

View file

@ -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);

View file

@ -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,
});
}
}
}

View file

@ -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 {
</FormControl>
</FormGroup>
<SelectFile type='config' name='Configuration File' onChange={(e) => this.handleSelectedFileChange(e)} value={this.state.selectedFileID} objectID={this.props.config.id}/>
<SelectFile
type='config'
name='Configuration File'
onChange={(e) => this.handleSelectedFileChange(e)}
files={this.props.files}
value={this.state.selectedFileID}
scenarioID={this.props.config.scenarioID}
sessionToken={this.props.sessionToken}
/>
<FormGroup controlId='startParameters'>
<FormLabel> Start Parameters </FormLabel>
<ParametersEditor content={this.props.config.startParameters} onChange={(data) => this.handleParameterChange(data)} />
<ParametersEditor content={this.state.startParameters} onChange={(data) => this.handleParameterChange(data)} />
</FormGroup>
</form>
</Dialog>

View file

@ -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 (
<Dialog show={this.props.show} title="Import Component Configuration" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={this.resetState} valid={this.imported}>
<Dialog
show={this.props.show}
title="Import Component Configuration"
buttonTitle="Import"
onClose={(c) => this.onClose(c)}
onReset={() => this.resetState()}
valid={this.valid} >
<form>
<FormGroup controlId='file'>
<FormLabel>Component Configuration File</FormLabel>
<FormControl type='file' onChange={this.loadFile} />
</FormGroup>
<FormGroup controlId='IC'>
<FormLabel>Infrastructure Component</FormLabel>
<FormControl disabled={this.imported === false} as='select' placeholder='Select infrastructure component' value={this.state.config.icID} onChange={this.handleICChange}>
{this.props.ics.map(ic => (
<option key={ic.id} value={ic.id}>{_.get(ic, 'properties.name') || _.get(ic, 'rawProperties.name')}</option>
))}
</FormControl>
<FormGroup controlId="name" >
<FormLabel>Name</FormLabel>
<FormControl
readOnly={!this.imported}
isValid={this.validateForm('name')}
type="text"
placeholder="Enter name"
value={this.state.name}
onChange={(e) => this.handleChange(e)}
/>
<FormControl.Feedback />
</FormGroup>
</form>
</Dialog>

View file

@ -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();

View file

@ -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}

View file

@ -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();

View file

@ -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 (
<Dialog show={this.props.show} title="Import Dashboard" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<Dialog
show={this.props.show}
title="Import Dashboard"
buttonTitle="Import"
onClose={(c) => this.onClose(c)}
onReset={() => this.resetState()}
valid={this.valid}>
<form>
<FormGroup controlId="file">
<FormLabel>Dashboard File</FormLabel>
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
</FormGroup>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<FormGroup controlId="name" >
<FormLabel>Name</FormLabel>
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl
readOnly={!this.imported}
isValid={this.validateForm('name')}
type="text"
placeholder="Enter name"
value={this.state.name}
onChange={(e) => this.handleChange(e)}
/>
<FormControl.Feedback />
</FormGroup>
</form>

View file

@ -69,7 +69,7 @@ class NewDashboardDialog extends React.Component {
return (
<Dialog show={this.props.show} title="New Dashboard" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
<form>
<FormGroup controlId="name" validationState={this.validateForm('name')}>
<FormGroup controlId="name" validationstate={this.validateForm('name')}>
<FormLabel>Name</FormLabel>
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />

View file

@ -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);

View file

@ -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();

View file

@ -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(
<option key = {0} value={-1}>Select file</option>
)
fileOptions.push(this.props.files.map(f =>
<option key={f.id} value={f.id}>{f.name}</option>
);
));
} else {
fileOptions = <option >No files for this component config</option>
fileOptions = <option >No files available</option>
}
/*const progressBarStyle = {
const progressBarStyle = {
marginLeft: '100px',
marginTop: '-25px'
};*/
marginTop: '-40px'
};
return <div>
<FormGroup>
@ -122,29 +94,44 @@ class SelectFile extends React.Component {
</FormLabel>
<FormGroup as={Col} sm={9} md={10}>
<FormControl as="select" disabled={this.props.disabled} placeholder='Select file' onChange={(event) => this.handleChange(event)}>
<FormControl
as="select"
value={this.props.value}
disabled={this.props.disabled}
placeholder='Select file'
onChange={(event) => this.props.onChange(event)}>
{fileOptions}
</FormControl>
</FormGroup>
</FormGroup>
<FormGroup as={Col} sm={{span: 9, offset: 3}} md={{span: 10, offset: 2}} >
<FormControl disabled={this.props.disabled} type='file' onChange={(event) => this.selectUploadFile(event)} />
<FormGroup as={Col} >
<FormControl
disabled={this.props.disabled}
type='file'
onChange={(event) => this.selectUploadFile(event)} />
</FormGroup>
<FormGroup as={Col} sm={{span: 9, offset: 3}} md={{span: 10, offset : 2}}>
<Button disabled={this.props.disabled} onClick={() => this.startFileUpload()}>
<Icon icon="plus" /> File
<FormGroup as={Col} >
<Button
disabled={this.state.uploadFile === null}
onClick={() => this.startFileUpload()}>
Upload
</Button>
</FormGroup>
{/*<FormGroup as={Col} sm={{span: 9, offset: 3}} md={{span: 10, offset: 2}}>
<ProgressBar striped animated now={this.state.uploadProgress} label={this.state.uploadProgress + '%'}
style={progressBarStyle}/>
<FormGroup as={Col} >
<ProgressBar
striped={true}
animated={true}
now={this.state.uploadProgress}
label={this.state.uploadProgress + '%'}
style={progressBarStyle}
/>
</FormGroup>
*/}
</div>;
}
}
let fluxContainerConverter = require('../common/FluxContainerConverter');
export default Container.create(fluxContainerConverter.convert(SelectFile), { withProps: true });
export default SelectFile;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View file

@ -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

View file

@ -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();
export default new ScenarioStore();

View file

@ -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 {
<div style={{ clear: 'both' }} />
<EditConfigDialog show={this.state.editConfigModal} onClose={data => this.closeEditConfigModal(data)} config={this.state.modalConfigData} ics={this.state.ics} />
<EditConfigDialog
show={this.state.editConfigModal}
onClose={data => this.closeEditConfigModal(data)}
config={this.state.modalConfigData}
ics={this.state.ics}
files={this.state.files}
sessionToken={this.state.sessionToken}
/>
<ImportConfigDialog show={this.state.importConfigModal} onClose={data => this.importConfig(data)} ics={this.state.ics} />
<DeleteDialog title="component configuration" name={this.state.modalConfigData.name} show={this.state.deleteConfigModal} onClose={(c) => this.closeDeleteConfigModal(c)} />

View file

@ -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();

View file

@ -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;

View file

@ -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',

View file

@ -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;
}

View file

@ -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(
<EditImageWidgetControl key={0} widget={widget} controlId={"customProperties.file"} sessionToken={sessionToken} files={files} handleChange={(e) => handleChange(e)} />,
<EditFileWidgetControl key={0} widget={widget} controlId={"customProperties.file"} files={files} type={'image'} handleChange={(e) => handleChange(e)} onUpload={(f,i) => onUpload(f,i)} />,
<EditWidgetAspectControl key={1} widget={widget} controlId={"customProperties.lockAspect"} handleChange={e => 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(
<EditImageWidgetControl key={0} widget={widget} controlId={"customProperties.file"} sessionToken={sessionToken} files={files} handleChange={(e) => handleChange(e)} />
<EditFileWidgetControl key={0} widget={widget} controlId={"customProperties.file"} files={files} type={'xml'} handleChange={(e) => handleChange(e) } onUpload={(f,i) => onUpload(f,i)} />
);
break;

View file

@ -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(
<option key = {0} default>Select image file</option>
)
fileOptions.push(this.state.files.map((file, index) => (
<option key={index+1} value={file.id}>{file.name}</option>
)))
} else {
fileOptions = <option disabled value style={{ display: 'none' }}>No files found, please upload one first.</option>
}
return <div>
<FormGroup controlId="file">
<FormLabel>Image</FormLabel>
<FormControl
as="select"
placeholder="Select image file"
value={isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId]}
onChange={(e) => this.handleFileChange(e)}>
{this.props.files.length === 0 ? (
<option disabled value style={{ display: 'none' }}>No images found, please upload one first.</option>
) : (
this.props.files.map((file, index) => (
<option key={index+1} value={file.id}>{file.name}</option>
))
)}
</FormControl>
onChange={(e) => this.handleFileChange(e)}>{fileOptions} </FormControl>
</FormGroup>
<FormGroup controlId="upload">
@ -111,4 +103,4 @@ class EditImageWidgetControl extends React.Component {
}
}
export default EditImageWidgetControl;
export default EditFileWidgetControl;

View file

@ -48,7 +48,8 @@ class EditWidgetSignalControl extends Component {
return (
<FormGroup controlId="signal">
<FormLabel>Select signal</FormLabel>
<FormControl as="select" placeholder="Select signal" value={this.props.widget.signalIDs[0]} onChange={(e) => this.handleSignalChange(e)}>
<FormControl as="select" value={this.props.widget.signalIDs[0] || ""} onChange={(e) => this.handleSignalChange(e)}>
<option default>Select signal</option>
{
this.props.signals.length === 0 ? (
<option disabled value style={{ display: 'none' }}>No signals available.</option>

View file

@ -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 (

View file

@ -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}

View file

@ -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:

View file

@ -26,7 +26,7 @@ class PlotLegend extends React.Component {
return <div className="plot-legend">
<ul>
{this.props.signals.map(signal =>
<li key={signal.index} className="signal-legend" style={{ color: colorScale(signal.index) }}>
<li key={signal.id} className="signal-legend" style={{ color: colorScale(signal.id) }}>
<span className="signal-legend-name">{signal.name}</span>
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
</li>

View file

@ -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);

View file

@ -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:

View file

@ -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 <WidgetValue widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} />
} else if (widget.type === 'Plot') {
return <WidgetPlot widget={widget} data={this.state.icData} dummy={this.state.sequence} paused={this.props.paused} />
return <WidgetPlot widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} paused={this.props.paused} />
} else if (widget.type === 'Table') {
return <WidgetTable widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} />
} else if (widget.type === 'Label') {
return <WidgetLabel widget={widget} />
} else if (widget.type === 'PlotTable') {
return <WidgetPlotTable widget={widget} data={this.state.icData} dummy={this.state.sequence} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
return <WidgetPlotTable widget={widget} data={this.state.icData} dummy={this.state.sequence} signals={this.state.signals} icIDs={this.state.icIDs} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
} else if (widget.type === 'Image') {
return <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
} else if (widget.type === 'Button') {
@ -154,7 +131,7 @@ class Widget extends React.Component {
} else if (widget.type === 'HTML') {
return <WidgetHTML widget={widget} editing={this.props.editing} />
} else if (widget.type === 'Topology') {
return <WidgetTopology widget={widget} files={this.state.files} />
return <WidgetTopology widget={widget} files={this.state.files} token={this.state.sessionToken} />
}
return null;

View file

@ -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()

View file

@ -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);
}

View file

@ -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<P>, prevState: Readonly<S>, 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 (
<div className="full">
{file ? (
<img className="full" alt={file.name} src={fileHasData ? objectURL : ''} onDragStart={e => e.preventDefault()} />
{objectURL !== '' ? (
<img onError={(e) => this.imageError(e)} className="full" alt={this.state.file.name} src={objectURL} onDragStart={e => e.preventDefault()} />
) : (
<img className="full" alt="No file selected." />
)}

View file

@ -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) {

View file

@ -66,8 +66,6 @@ class WidgetLamp extends Component {
let style = {
backgroundColor: color,
width: this.props.widget.width,
height: this.props.widget.height
}
return (

View file

@ -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<P>, prevState: Readonly<S>, 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 <FormCheck key={signal.index} className={chkBxClasses} checked={checked} disabled={ this.props.editing } onChange={(e) => this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } </FormCheck>
});
}
// 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 <FormCheck key={signal.index} className={chkBxClasses} checked={checked} disabled={this.props.editing}
// onChange={(e) => this.updateSignalSelection(signal, e.target.checked)}> {signal.name} </FormCheck>
// });
}
return (
@ -140,17 +97,17 @@ class WidgetPlotTable extends Component {
<div className="content">
<div className="table-plot-row">
<div className="widget-table">
{ checkBoxes.length > 0 ? (
{checkBoxes.length > 0 ? (
<FormGroup className="btn-group-vertical">
{ checkBoxes }
{checkBoxes}
</FormGroup>
) : ( <small>No signal has been pre-selected.</small> )
) : (<small>Use edit menu to change selected signals.</small>)
}
</div>
<div className="widget-plot">
<Plot
data={icData}
data={this.state.data}
time={this.props.widget.customProperties.time}
width={this.props.widget.width - 100}
height={this.props.widget.height - 55}
@ -162,13 +119,12 @@ class WidgetPlotTable extends Component {
/>
</div>
</div>
{showLegend? (
<PlotLegend signals={legendSignals} /> ) : (<div></div>)
{showLegend ? (
<PlotLegend signals={this.state.signals}/>) : (<div></div>)
}
</div>
</div>
);
}
}
export default WidgetPlotTable;

View file

@ -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}
/>
</div>
<PlotLegend signals={this.state.legend} />
<PlotLegend signals={this.state.signals} />
</div>;
}
}

View file

@ -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);

View file

@ -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(<TableColumn key={3} title="Unit" dataKey="unit" />)
return (
<div className="table-widget">
<div className="table-widget" style={{width: this.props.widget.width, height: this.props.widget.height, overflowY: 'auto'}}>
<Table data={rows}>
{ columns }
</Table>

View file

@ -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<P>, prevState: Readonly<S>, 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 = <div style={spinnerContainerStyle}><div className="loader" /></div>; break;
case 'show_message':
markup = <div style={msgContainerStyle}><div style={msgStyle}>{ this.state.message }</div></div>; break;
markup = <div style={msgContainerStyle}><div style={msgStyle}>{ this.message }</div></div>; break;
default:
markup = (<div>
<UncontrolledReactSVGPanZoom
ref={Viewer => 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} >
<svg width={this.props.widget.width} height={this.props.widget.height}>
<svg ref={ this.svgElem } width={this.props.widget.width} height={this.props.widget.height}>
<rect className="backing" style={pinturaBackingStyle} />
<g className="grid" style={pinturaGridStyle} />
<g className="diagrams"/>
</svg>
</svg>
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} >
<svg width={this.props.widget.width} height={this.props.widget.height}>
<svg ref={ this.svgElem } width={this.props.widget.width} height={this.props.widget.height}>
<rect className="backing" style={pinturaBackingStyle} />
<g className="grid" style={pinturaGridStyle} />
<g className="diagrams"/>
</svg>
</svg>
</UncontrolledReactSVGPanZoom>
</div>);
}

View file

@ -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 (
<div className="single-value-widget">
<strong style={{ fontSize: this.props.widget.customProperties.textSize + 'px'}}>{this.props.widget.name}</strong>
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px',width: value_width }}>{Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)}</span>
<strong style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: '1 1 auto'}}>{this.props.widget.name}</strong>
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', width: value_width }}>{Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)}</span>
{this.props.widget.customProperties.showUnit &&
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', width: unit_width}}>[{this.state.unit}]</span>
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', width: unit_width}}>[{this.state.unit}]</span>
}
</div>
);