Commit 700293d5f9c51f5062cb28bd9d1d37afbc83cb4c
1 parent
d29a8731
Implement Widget Editor. Dashboard page initial implementation.
Showing
74 changed files
with
4959 additions
and
184 deletions
@@ -47,6 +47,10 @@ | @@ -47,6 +47,10 @@ | ||
47 | "node_modules/flot/src/plugins/jquery.flot.stack.js", | 47 | "node_modules/flot/src/plugins/jquery.flot.stack.js", |
48 | "node_modules/flot.curvedlines/curvedLines.js", | 48 | "node_modules/flot.curvedlines/curvedLines.js", |
49 | "node_modules/tinycolor2/dist/tinycolor-min.js", | 49 | "node_modules/tinycolor2/dist/tinycolor-min.js", |
50 | + "node_modules/split.js/dist/split.js", | ||
51 | + "node_modules/js-beautify/js/lib/beautify.js", | ||
52 | + "node_modules/js-beautify/js/lib/beautify-css.js", | ||
53 | + "node_modules/js-beautify/js/lib/beautify-html.js", | ||
50 | "node_modules/ace-builds/src-min/ace.js", | 54 | "node_modules/ace-builds/src-min/ace.js", |
51 | "node_modules/ace-builds/src-min/ext-language_tools.js", | 55 | "node_modules/ace-builds/src-min/ext-language_tools.js", |
52 | "node_modules/ace-builds/src-min/ext-searchbox.js", | 56 | "node_modules/ace-builds/src-min/ext-searchbox.js", |
@@ -1170,12 +1170,23 @@ | @@ -1170,12 +1170,23 @@ | ||
1170 | "@types/sizzle": "*" | 1170 | "@types/sizzle": "*" |
1171 | } | 1171 | } |
1172 | }, | 1172 | }, |
1173 | + "@types/js-beautify": { | ||
1174 | + "version": "1.8.1", | ||
1175 | + "resolved": "https://registry.npmjs.org/@types/js-beautify/-/js-beautify-1.8.1.tgz", | ||
1176 | + "integrity": "sha512-B1Br8yE27obcYvFx5ECZswT/947aAFNb9lHqnkUOhtOfvJqaa6Axibo4T+5G6iQlUfjgSd8am9R/9j9UBfRlrw==", | ||
1177 | + "dev": true | ||
1178 | + }, | ||
1173 | "@types/minimatch": { | 1179 | "@types/minimatch": { |
1174 | "version": "3.0.3", | 1180 | "version": "3.0.3", |
1175 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", | 1181 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", |
1176 | "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", | 1182 | "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", |
1177 | "dev": true | 1183 | "dev": true |
1178 | }, | 1184 | }, |
1185 | + "@types/mousetrap": { | ||
1186 | + "version": "1.6.3", | ||
1187 | + "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.3.tgz", | ||
1188 | + "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" | ||
1189 | + }, | ||
1179 | "@types/node": { | 1190 | "@types/node": { |
1180 | "version": "10.14.15", | 1191 | "version": "10.14.15", |
1181 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.15.tgz", | 1192 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.15.tgz", |
@@ -1434,6 +1445,11 @@ | @@ -1434,6 +1445,11 @@ | ||
1434 | "through": ">=2.2.7 <3" | 1445 | "through": ">=2.2.7 <3" |
1435 | } | 1446 | } |
1436 | }, | 1447 | }, |
1448 | + "abbrev": { | ||
1449 | + "version": "1.1.1", | ||
1450 | + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", | ||
1451 | + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" | ||
1452 | + }, | ||
1437 | "accepts": { | 1453 | "accepts": { |
1438 | "version": "1.3.7", | 1454 | "version": "1.3.7", |
1439 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", | 1455 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", |
@@ -1523,6 +1539,15 @@ | @@ -1523,6 +1539,15 @@ | ||
1523 | "tslib": "^1.9.0" | 1539 | "tslib": "^1.9.0" |
1524 | } | 1540 | } |
1525 | }, | 1541 | }, |
1542 | + "angular2-hotkeys": { | ||
1543 | + "version": "2.1.5", | ||
1544 | + "resolved": "https://registry.npmjs.org/angular2-hotkeys/-/angular2-hotkeys-2.1.5.tgz", | ||
1545 | + "integrity": "sha512-HiAnK1pW7lns5LpxtRsdkRRb5iVa7fv8Cf69Jye6l9gI6/IyvaVDptRtsWmdIG7VAr2Ngz6Yeehkym39O/LdgA==", | ||
1546 | + "requires": { | ||
1547 | + "@types/mousetrap": "^1.6.0", | ||
1548 | + "mousetrap": "^1.6.0" | ||
1549 | + } | ||
1550 | + }, | ||
1526 | "ansi-colors": { | 1551 | "ansi-colors": { |
1527 | "version": "3.2.4", | 1552 | "version": "3.2.4", |
1528 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", | 1553 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", |
@@ -1955,8 +1980,7 @@ | @@ -1955,8 +1980,7 @@ | ||
1955 | "balanced-match": { | 1980 | "balanced-match": { |
1956 | "version": "1.0.0", | 1981 | "version": "1.0.0", |
1957 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", | 1982 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", |
1958 | - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", | ||
1959 | - "dev": true | 1983 | + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" |
1960 | }, | 1984 | }, |
1961 | "base": { | 1985 | "base": { |
1962 | "version": "0.11.2", | 1986 | "version": "0.11.2", |
@@ -2143,7 +2167,6 @@ | @@ -2143,7 +2167,6 @@ | ||
2143 | "version": "1.1.11", | 2167 | "version": "1.1.11", |
2144 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", | 2168 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", |
2145 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", | 2169 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", |
2146 | - "dev": true, | ||
2147 | "requires": { | 2170 | "requires": { |
2148 | "balanced-match": "^1.0.0", | 2171 | "balanced-match": "^1.0.0", |
2149 | "concat-map": "0.0.1" | 2172 | "concat-map": "0.0.1" |
@@ -2854,8 +2877,7 @@ | @@ -2854,8 +2877,7 @@ | ||
2854 | "commander": { | 2877 | "commander": { |
2855 | "version": "2.20.0", | 2878 | "version": "2.20.0", |
2856 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", | 2879 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", |
2857 | - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", | ||
2858 | - "dev": true | 2880 | + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" |
2859 | }, | 2881 | }, |
2860 | "commondir": { | 2882 | "commondir": { |
2861 | "version": "1.0.1", | 2883 | "version": "1.0.1", |
@@ -3107,8 +3129,7 @@ | @@ -3107,8 +3129,7 @@ | ||
3107 | "concat-map": { | 3129 | "concat-map": { |
3108 | "version": "0.0.1", | 3130 | "version": "0.0.1", |
3109 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | 3131 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", |
3110 | - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", | ||
3111 | - "dev": true | 3132 | + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" |
3112 | }, | 3133 | }, |
3113 | "concat-stream": { | 3134 | "concat-stream": { |
3114 | "version": "1.6.2", | 3135 | "version": "1.6.2", |
@@ -3122,6 +3143,15 @@ | @@ -3122,6 +3143,15 @@ | ||
3122 | "typedarray": "^0.0.6" | 3143 | "typedarray": "^0.0.6" |
3123 | } | 3144 | } |
3124 | }, | 3145 | }, |
3146 | + "config-chain": { | ||
3147 | + "version": "1.1.12", | ||
3148 | + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", | ||
3149 | + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", | ||
3150 | + "requires": { | ||
3151 | + "ini": "^1.3.4", | ||
3152 | + "proto-list": "~1.2.1" | ||
3153 | + } | ||
3154 | + }, | ||
3125 | "connect": { | 3155 | "connect": { |
3126 | "version": "3.7.0", | 3156 | "version": "3.7.0", |
3127 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", | 3157 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", |
@@ -3762,6 +3792,17 @@ | @@ -3762,6 +3792,17 @@ | ||
3762 | "safer-buffer": "^2.1.0" | 3792 | "safer-buffer": "^2.1.0" |
3763 | } | 3793 | } |
3764 | }, | 3794 | }, |
3795 | + "editorconfig": { | ||
3796 | + "version": "0.15.3", | ||
3797 | + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", | ||
3798 | + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", | ||
3799 | + "requires": { | ||
3800 | + "commander": "^2.19.0", | ||
3801 | + "lru-cache": "^4.1.5", | ||
3802 | + "semver": "^5.6.0", | ||
3803 | + "sigmund": "^1.0.1" | ||
3804 | + } | ||
3805 | + }, | ||
3765 | "ee-first": { | 3806 | "ee-first": { |
3766 | "version": "1.1.1", | 3807 | "version": "1.1.1", |
3767 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | 3808 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", |
@@ -4574,8 +4615,7 @@ | @@ -4574,8 +4615,7 @@ | ||
4574 | "fs.realpath": { | 4615 | "fs.realpath": { |
4575 | "version": "1.0.0", | 4616 | "version": "1.0.0", |
4576 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | 4617 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", |
4577 | - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", | ||
4578 | - "dev": true | 4618 | + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" |
4579 | }, | 4619 | }, |
4580 | "fsevents": { | 4620 | "fsevents": { |
4581 | "version": "1.2.9", | 4621 | "version": "1.2.9", |
@@ -5183,7 +5223,6 @@ | @@ -5183,7 +5223,6 @@ | ||
5183 | "version": "7.1.3", | 5223 | "version": "7.1.3", |
5184 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", | 5224 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", |
5185 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", | 5225 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", |
5186 | - "dev": true, | ||
5187 | "requires": { | 5226 | "requires": { |
5188 | "fs.realpath": "^1.0.0", | 5227 | "fs.realpath": "^1.0.0", |
5189 | "inflight": "^1.0.4", | 5228 | "inflight": "^1.0.4", |
@@ -5709,7 +5748,6 @@ | @@ -5709,7 +5748,6 @@ | ||
5709 | "version": "1.0.6", | 5748 | "version": "1.0.6", |
5710 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", | 5749 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", |
5711 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", | 5750 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", |
5712 | - "dev": true, | ||
5713 | "requires": { | 5751 | "requires": { |
5714 | "once": "^1.3.0", | 5752 | "once": "^1.3.0", |
5715 | "wrappy": "1" | 5753 | "wrappy": "1" |
@@ -5718,14 +5756,12 @@ | @@ -5718,14 +5756,12 @@ | ||
5718 | "inherits": { | 5756 | "inherits": { |
5719 | "version": "2.0.4", | 5757 | "version": "2.0.4", |
5720 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", | 5758 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", |
5721 | - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", | ||
5722 | - "dev": true | 5759 | + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" |
5723 | }, | 5760 | }, |
5724 | "ini": { | 5761 | "ini": { |
5725 | "version": "1.3.5", | 5762 | "version": "1.3.5", |
5726 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", | 5763 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", |
5727 | - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", | ||
5728 | - "dev": true | 5764 | + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" |
5729 | }, | 5765 | }, |
5730 | "inquirer": { | 5766 | "inquirer": { |
5731 | "version": "6.5.0", | 5767 | "version": "6.5.0", |
@@ -6444,6 +6480,18 @@ | @@ -6444,6 +6480,18 @@ | ||
6444 | } | 6480 | } |
6445 | } | 6481 | } |
6446 | }, | 6482 | }, |
6483 | + "js-beautify": { | ||
6484 | + "version": "1.10.2", | ||
6485 | + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.2.tgz", | ||
6486 | + "integrity": "sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==", | ||
6487 | + "requires": { | ||
6488 | + "config-chain": "^1.1.12", | ||
6489 | + "editorconfig": "^0.15.3", | ||
6490 | + "glob": "^7.1.3", | ||
6491 | + "mkdirp": "~0.5.1", | ||
6492 | + "nopt": "~4.0.1" | ||
6493 | + } | ||
6494 | + }, | ||
6447 | "js-tokens": { | 6495 | "js-tokens": { |
6448 | "version": "3.0.2", | 6496 | "version": "3.0.2", |
6449 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", | 6497 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", |
@@ -6918,7 +6966,6 @@ | @@ -6918,7 +6966,6 @@ | ||
6918 | "version": "4.1.5", | 6966 | "version": "4.1.5", |
6919 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", | 6967 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", |
6920 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", | 6968 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", |
6921 | - "dev": true, | ||
6922 | "requires": { | 6969 | "requires": { |
6923 | "pseudomap": "^1.0.2", | 6970 | "pseudomap": "^1.0.2", |
6924 | "yallist": "^2.1.2" | 6971 | "yallist": "^2.1.2" |
@@ -7258,7 +7305,6 @@ | @@ -7258,7 +7305,6 @@ | ||
7258 | "version": "3.0.4", | 7305 | "version": "3.0.4", |
7259 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", | 7306 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", |
7260 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", | 7307 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", |
7261 | - "dev": true, | ||
7262 | "requires": { | 7308 | "requires": { |
7263 | "brace-expansion": "^1.1.7" | 7309 | "brace-expansion": "^1.1.7" |
7264 | } | 7310 | } |
@@ -7368,7 +7414,6 @@ | @@ -7368,7 +7414,6 @@ | ||
7368 | "version": "0.5.1", | 7414 | "version": "0.5.1", |
7369 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", | 7415 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", |
7370 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", | 7416 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", |
7371 | - "dev": true, | ||
7372 | "requires": { | 7417 | "requires": { |
7373 | "minimist": "0.0.8" | 7418 | "minimist": "0.0.8" |
7374 | }, | 7419 | }, |
@@ -7376,8 +7421,7 @@ | @@ -7376,8 +7421,7 @@ | ||
7376 | "minimist": { | 7421 | "minimist": { |
7377 | "version": "0.0.8", | 7422 | "version": "0.0.8", |
7378 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", | 7423 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", |
7379 | - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", | ||
7380 | - "dev": true | 7424 | + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" |
7381 | } | 7425 | } |
7382 | } | 7426 | } |
7383 | }, | 7427 | }, |
@@ -7386,6 +7430,11 @@ | @@ -7386,6 +7430,11 @@ | ||
7386 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", | 7430 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", |
7387 | "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" | 7431 | "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" |
7388 | }, | 7432 | }, |
7433 | + "mousetrap": { | ||
7434 | + "version": "1.6.3", | ||
7435 | + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.3.tgz", | ||
7436 | + "integrity": "sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==" | ||
7437 | + }, | ||
7389 | "move-concurrently": { | 7438 | "move-concurrently": { |
7390 | "version": "1.0.1", | 7439 | "version": "1.0.1", |
7391 | "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", | 7440 | "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", |
@@ -7563,6 +7612,15 @@ | @@ -7563,6 +7612,15 @@ | ||
7563 | "semver": "^5.3.0" | 7612 | "semver": "^5.3.0" |
7564 | } | 7613 | } |
7565 | }, | 7614 | }, |
7615 | + "nopt": { | ||
7616 | + "version": "4.0.1", | ||
7617 | + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", | ||
7618 | + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", | ||
7619 | + "requires": { | ||
7620 | + "abbrev": "1", | ||
7621 | + "osenv": "^0.1.4" | ||
7622 | + } | ||
7623 | + }, | ||
7566 | "normalize-package-data": { | 7624 | "normalize-package-data": { |
7567 | "version": "2.5.0", | 7625 | "version": "2.5.0", |
7568 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", | 7626 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", |
@@ -7801,7 +7859,6 @@ | @@ -7801,7 +7859,6 @@ | ||
7801 | "version": "1.4.0", | 7859 | "version": "1.4.0", |
7802 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | 7860 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", |
7803 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | 7861 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", |
7804 | - "dev": true, | ||
7805 | "requires": { | 7862 | "requires": { |
7806 | "wrappy": "1" | 7863 | "wrappy": "1" |
7807 | } | 7864 | } |
@@ -7877,8 +7934,7 @@ | @@ -7877,8 +7934,7 @@ | ||
7877 | "os-homedir": { | 7934 | "os-homedir": { |
7878 | "version": "1.0.2", | 7935 | "version": "1.0.2", |
7879 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", | 7936 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", |
7880 | - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", | ||
7881 | - "dev": true | 7937 | + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" |
7882 | }, | 7938 | }, |
7883 | "os-locale": { | 7939 | "os-locale": { |
7884 | "version": "3.1.0", | 7940 | "version": "3.1.0", |
@@ -7894,14 +7950,12 @@ | @@ -7894,14 +7950,12 @@ | ||
7894 | "os-tmpdir": { | 7950 | "os-tmpdir": { |
7895 | "version": "1.0.2", | 7951 | "version": "1.0.2", |
7896 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", | 7952 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", |
7897 | - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", | ||
7898 | - "dev": true | 7953 | + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" |
7899 | }, | 7954 | }, |
7900 | "osenv": { | 7955 | "osenv": { |
7901 | "version": "0.1.5", | 7956 | "version": "0.1.5", |
7902 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", | 7957 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", |
7903 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", | 7958 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", |
7904 | - "dev": true, | ||
7905 | "requires": { | 7959 | "requires": { |
7906 | "os-homedir": "^1.0.0", | 7960 | "os-homedir": "^1.0.0", |
7907 | "os-tmpdir": "^1.0.0" | 7961 | "os-tmpdir": "^1.0.0" |
@@ -8222,8 +8276,7 @@ | @@ -8222,8 +8276,7 @@ | ||
8222 | "path-is-absolute": { | 8276 | "path-is-absolute": { |
8223 | "version": "1.0.1", | 8277 | "version": "1.0.1", |
8224 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | 8278 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", |
8225 | - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", | ||
8226 | - "dev": true | 8279 | + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" |
8227 | }, | 8280 | }, |
8228 | "path-is-inside": { | 8281 | "path-is-inside": { |
8229 | "version": "1.0.2", | 8282 | "version": "1.0.2", |
@@ -8465,6 +8518,11 @@ | @@ -8465,6 +8518,11 @@ | ||
8465 | "retry": "^0.10.0" | 8518 | "retry": "^0.10.0" |
8466 | } | 8519 | } |
8467 | }, | 8520 | }, |
8521 | + "proto-list": { | ||
8522 | + "version": "1.2.4", | ||
8523 | + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", | ||
8524 | + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" | ||
8525 | + }, | ||
8468 | "protoduck": { | 8526 | "protoduck": { |
8469 | "version": "5.0.1", | 8527 | "version": "5.0.1", |
8470 | "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", | 8528 | "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", |
@@ -8612,8 +8670,7 @@ | @@ -8612,8 +8670,7 @@ | ||
8612 | "pseudomap": { | 8670 | "pseudomap": { |
8613 | "version": "1.0.2", | 8671 | "version": "1.0.2", |
8614 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", | 8672 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", |
8615 | - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", | ||
8616 | - "dev": true | 8673 | + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" |
8617 | }, | 8674 | }, |
8618 | "psl": { | 8675 | "psl": { |
8619 | "version": "1.3.0", | 8676 | "version": "1.3.0", |
@@ -9208,8 +9265,7 @@ | @@ -9208,8 +9265,7 @@ | ||
9208 | "semver": { | 9265 | "semver": { |
9209 | "version": "5.6.0", | 9266 | "version": "5.6.0", |
9210 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", | 9267 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", |
9211 | - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", | ||
9212 | - "dev": true | 9268 | + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" |
9213 | }, | 9269 | }, |
9214 | "semver-dsl": { | 9270 | "semver-dsl": { |
9215 | "version": "1.0.1", | 9271 | "version": "1.0.1", |
@@ -9408,6 +9464,11 @@ | @@ -9408,6 +9464,11 @@ | ||
9408 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", | 9464 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", |
9409 | "dev": true | 9465 | "dev": true |
9410 | }, | 9466 | }, |
9467 | + "sigmund": { | ||
9468 | + "version": "1.0.1", | ||
9469 | + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", | ||
9470 | + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" | ||
9471 | + }, | ||
9411 | "signal-exit": { | 9472 | "signal-exit": { |
9412 | "version": "3.0.2", | 9473 | "version": "3.0.2", |
9413 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", | 9474 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", |
@@ -9920,6 +9981,11 @@ | @@ -9920,6 +9981,11 @@ | ||
9920 | "extend-shallow": "^3.0.0" | 9981 | "extend-shallow": "^3.0.0" |
9921 | } | 9982 | } |
9922 | }, | 9983 | }, |
9984 | + "split.js": { | ||
9985 | + "version": "1.5.11", | ||
9986 | + "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.5.11.tgz", | ||
9987 | + "integrity": "sha512-ec0sAbWnaMGpNHWo1ZgIlF3Mx7GzSyaO0GlcEBZGIFZQwYPPkbDV6JRpDmpzIshVig7USREuEPudy0ygQaskXg==" | ||
9988 | + }, | ||
9923 | "sprintf-js": { | 9989 | "sprintf-js": { |
9924 | "version": "1.0.3", | 9990 | "version": "1.0.3", |
9925 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", | 9991 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", |
@@ -11121,8 +11187,7 @@ | @@ -11121,8 +11187,7 @@ | ||
11121 | "wrappy": { | 11187 | "wrappy": { |
11122 | "version": "1.0.2", | 11188 | "version": "1.0.2", |
11123 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | 11189 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", |
11124 | - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", | ||
11125 | - "dev": true | 11190 | + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" |
11126 | }, | 11191 | }, |
11127 | "ws": { | 11192 | "ws": { |
11128 | "version": "3.3.3", | 11193 | "version": "3.3.3", |
@@ -11180,8 +11245,7 @@ | @@ -11180,8 +11245,7 @@ | ||
11180 | "yallist": { | 11245 | "yallist": { |
11181 | "version": "2.1.2", | 11246 | "version": "2.1.2", |
11182 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", | 11247 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", |
11183 | - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", | ||
11184 | - "dev": true | 11248 | + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" |
11185 | }, | 11249 | }, |
11186 | "yargs": { | 11250 | "yargs": { |
11187 | "version": "12.0.5", | 11251 | "version": "12.0.5", |
@@ -33,6 +33,7 @@ | @@ -33,6 +33,7 @@ | ||
33 | "@ngx-translate/http-loader": "^4.0.0", | 33 | "@ngx-translate/http-loader": "^4.0.0", |
34 | "ace-builds": "^1.4.5", | 34 | "ace-builds": "^1.4.5", |
35 | "angular-gridster2": "^8.1.0", | 35 | "angular-gridster2": "^8.1.0", |
36 | + "angular2-hotkeys": "^2.1.5", | ||
36 | "base64-js": "^1.3.1", | 37 | "base64-js": "^1.3.1", |
37 | "compass-sass-mixins": "^0.12.7", | 38 | "compass-sass-mixins": "^0.12.7", |
38 | "core-js": "^3.1.4", | 39 | "core-js": "^3.1.4", |
@@ -44,6 +45,7 @@ | @@ -44,6 +45,7 @@ | ||
44 | "javascript-detect-element-resize": "^0.5.3", | 45 | "javascript-detect-element-resize": "^0.5.3", |
45 | "jquery": "^3.4.1", | 46 | "jquery": "^3.4.1", |
46 | "jquery.terminal": "^2.8.0", | 47 | "jquery.terminal": "^2.8.0", |
48 | + "js-beautify": "^1.10.2", | ||
47 | "material-design-icons": "^3.0.1", | 49 | "material-design-icons": "^3.0.1", |
48 | "messageformat": "^2.3.0", | 50 | "messageformat": "^2.3.0", |
49 | "moment": "^2.24.0", | 51 | "moment": "^2.24.0", |
@@ -51,6 +53,7 @@ | @@ -51,6 +53,7 @@ | ||
51 | "ngx-translate-messageformat-compiler": "^4.5.0", | 53 | "ngx-translate-messageformat-compiler": "^4.5.0", |
52 | "rxjs": "~6.5.2", | 54 | "rxjs": "~6.5.2", |
53 | "screenfull": "^4.2.1", | 55 | "screenfull": "^4.2.1", |
56 | + "split.js": "^1.5.11", | ||
54 | "tinycolor2": "^1.4.1", | 57 | "tinycolor2": "^1.4.1", |
55 | "tslib": "^1.10.0", | 58 | "tslib": "^1.10.0", |
56 | "typeface-roboto": "^0.0.75", | 59 | "typeface-roboto": "^0.0.75", |
@@ -66,6 +69,7 @@ | @@ -66,6 +69,7 @@ | ||
66 | "@types/jasmine": "~3.4.0", | 69 | "@types/jasmine": "~3.4.0", |
67 | "@types/jasminewd2": "~2.0.6", | 70 | "@types/jasminewd2": "~2.0.6", |
68 | "@types/jquery": "^3.3.31", | 71 | "@types/jquery": "^3.3.31", |
72 | + "@types/js-beautify": "^1.8.1", | ||
69 | "@types/node": "~10.14.15", | 73 | "@types/node": "~10.14.15", |
70 | "@types/tinycolor2": "^1.4.2", | 74 | "@types/tinycolor2": "^1.4.2", |
71 | "codelyzer": "~5.1.0", | 75 | "codelyzer": "~5.1.0", |
@@ -24,6 +24,7 @@ import { LoginModule } from './modules/login/login.module'; | @@ -24,6 +24,7 @@ import { LoginModule } from './modules/login/login.module'; | ||
24 | import { HomeModule } from './modules/home/home.module'; | 24 | import { HomeModule } from './modules/home/home.module'; |
25 | 25 | ||
26 | import { AppComponent } from './app.component'; | 26 | import { AppComponent } from './app.component'; |
27 | +import { DashboardRoutingModule } from './modules/dashboard/dashboard-routing.module'; | ||
27 | 28 | ||
28 | @NgModule({ | 29 | @NgModule({ |
29 | declarations: [ | 30 | declarations: [ |
@@ -35,7 +36,8 @@ import { AppComponent } from './app.component'; | @@ -35,7 +36,8 @@ import { AppComponent } from './app.component'; | ||
35 | AppRoutingModule, | 36 | AppRoutingModule, |
36 | CoreModule, | 37 | CoreModule, |
37 | LoginModule, | 38 | LoginModule, |
38 | - HomeModule | 39 | + HomeModule, |
40 | + DashboardRoutingModule | ||
39 | ], | 41 | ], |
40 | providers: [], | 42 | providers: [], |
41 | bootstrap: [AppComponent] | 43 | bootstrap: [AppComponent] |
@@ -14,19 +14,26 @@ | @@ -14,19 +14,26 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { IAliasController, AliasInfo } from '@core/api/widget-api.models'; | 17 | +import { IAliasController, AliasInfo, IStateController } from '@core/api/widget-api.models'; |
18 | import { Observable, of, Subject } from 'rxjs'; | 18 | import { Observable, of, Subject } from 'rxjs'; |
19 | import { Datasource } from '@app/shared/models/widget.models'; | 19 | import { Datasource } from '@app/shared/models/widget.models'; |
20 | import { deepClone } from '@core/utils'; | 20 | import { deepClone } from '@core/utils'; |
21 | +import { EntityService } from '@core/http/entity.service'; | ||
22 | +import { UtilsService } from '@core/services/utils.service'; | ||
23 | +import { EntityAliases } from '@shared/models/alias.models'; | ||
24 | +import { EntityInfo } from '@shared/models/entity.models'; | ||
25 | +import * as equal from 'deep-equal'; | ||
21 | 26 | ||
22 | export class DummyAliasController implements IAliasController { | 27 | export class DummyAliasController implements IAliasController { |
23 | 28 | ||
24 | entityAliasesChanged: Observable<Array<string>>; | 29 | entityAliasesChanged: Observable<Array<string>>; |
30 | + entityAliasResolved: Observable<string>; | ||
25 | 31 | ||
26 | [key: string]: any | null; | 32 | [key: string]: any | null; |
27 | 33 | ||
28 | constructor() { | 34 | constructor() { |
29 | this.entityAliasesChanged = new Subject<Array<string>>().asObservable(); | 35 | this.entityAliasesChanged = new Subject<Array<string>>().asObservable(); |
36 | + this.entityAliasResolved = new Subject<string>().asObservable(); | ||
30 | } | 37 | } |
31 | 38 | ||
32 | getAliasInfo(aliasId): Observable<AliasInfo> { | 39 | getAliasInfo(aliasId): Observable<AliasInfo> { |
@@ -36,4 +43,72 @@ export class DummyAliasController implements IAliasController { | @@ -36,4 +43,72 @@ export class DummyAliasController implements IAliasController { | ||
36 | resolveDatasources(datasources: Array<Datasource>): Observable<Array<Datasource>> { | 43 | resolveDatasources(datasources: Array<Datasource>): Observable<Array<Datasource>> { |
37 | return of(deepClone(datasources)); | 44 | return of(deepClone(datasources)); |
38 | } | 45 | } |
46 | + | ||
47 | + getEntityAliases(): EntityAliases { | ||
48 | + return undefined; | ||
49 | + } | ||
50 | + | ||
51 | + getInstantAliasInfo(aliasId: string): AliasInfo { | ||
52 | + return undefined; | ||
53 | + } | ||
54 | + | ||
55 | + updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo) { | ||
56 | + } | ||
57 | + | ||
58 | + updateEntityAliases(entityAliases: EntityAliases) { | ||
59 | + } | ||
60 | + | ||
61 | +} | ||
62 | + | ||
63 | +export class AliasController implements IAliasController { | ||
64 | + | ||
65 | + private entityAliasesChangedSubject = new Subject<Array<string>>(); | ||
66 | + entityAliasesChanged: Observable<Array<string>> = this.entityAliasesChangedSubject.asObservable(); | ||
67 | + | ||
68 | + private entityAliasResolvedSubject = new Subject<string>(); | ||
69 | + entityAliasResolved: Observable<string> = this.entityAliasResolvedSubject.asObservable(); | ||
70 | + | ||
71 | + entityAliases: EntityAliases; | ||
72 | + | ||
73 | + resolvedAliases: {[aliasId: string]: AliasInfo} = {}; | ||
74 | + | ||
75 | + [key: string]: any | null; | ||
76 | + | ||
77 | + constructor(private utils: UtilsService, | ||
78 | + private entityService: EntityService, | ||
79 | + private stateController: IStateController, | ||
80 | + private origEntityAliases: EntityAliases) { | ||
81 | + this.entityAliases = deepClone(this.origEntityAliases); | ||
82 | + } | ||
83 | + | ||
84 | + getAliasInfo(aliasId: string): Observable<AliasInfo> { | ||
85 | + return of(null); | ||
86 | + } | ||
87 | + | ||
88 | + resolveDatasources(datasources: Array<Datasource>): Observable<Array<Datasource>> { | ||
89 | + return of(deepClone(datasources)); | ||
90 | + } | ||
91 | + | ||
92 | + getEntityAliases(): EntityAliases { | ||
93 | + return this.entityAliases; | ||
94 | + } | ||
95 | + | ||
96 | + getInstantAliasInfo(aliasId: string): AliasInfo { | ||
97 | + return this.resolvedAliases[aliasId]; | ||
98 | + } | ||
99 | + | ||
100 | + updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo) { | ||
101 | + const aliasInfo = this.resolvedAliases[aliasId]; | ||
102 | + if (aliasInfo) { | ||
103 | + const prevCurrentEntity = aliasInfo.currentEntity; | ||
104 | + if (!equal(currentEntity, prevCurrentEntity)) { | ||
105 | + aliasInfo.currentEntity = currentEntity; | ||
106 | + this.entityAliasesChangedSubject.next([aliasId]); | ||
107 | + } | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + updateEntityAliases(entityAliases: EntityAliases) { | ||
112 | + } | ||
113 | + | ||
39 | } | 114 | } |
@@ -34,6 +34,8 @@ import { AlarmSearchStatus } from '@shared/models/alarm.models'; | @@ -34,6 +34,8 @@ import { AlarmSearchStatus } from '@shared/models/alarm.models'; | ||
34 | import { HttpErrorResponse } from '@angular/common/http'; | 34 | import { HttpErrorResponse } from '@angular/common/http'; |
35 | import { DatasourceService } from '@core/api/datasource.service'; | 35 | import { DatasourceService } from '@core/api/datasource.service'; |
36 | import { RafService } from '@core/services/raf.service'; | 36 | import { RafService } from '@core/services/raf.service'; |
37 | +import { EntityAliases } from '@shared/models/alias.models'; | ||
38 | +import { EntityInfo } from '@app/shared/models/entity.models'; | ||
37 | 39 | ||
38 | export interface TimewindowFunctions { | 40 | export interface TimewindowFunctions { |
39 | onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; | 41 | onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; |
@@ -66,20 +68,24 @@ export interface WidgetActionsApi { | @@ -66,20 +68,24 @@ export interface WidgetActionsApi { | ||
66 | } | 68 | } |
67 | 69 | ||
68 | export interface AliasInfo { | 70 | export interface AliasInfo { |
71 | + alias?: string; | ||
69 | stateEntity?: boolean; | 72 | stateEntity?: boolean; |
70 | - currentEntity?: { | ||
71 | - id: string; | ||
72 | - entityType: EntityType; | ||
73 | - name?: string; | ||
74 | - }; | 73 | + currentEntity?: EntityInfo; |
74 | + selectedId?: string; | ||
75 | + resolvedEntities?: Array<EntityInfo>; | ||
75 | [key: string]: any | null; | 76 | [key: string]: any | null; |
76 | // TODO: | 77 | // TODO: |
77 | } | 78 | } |
78 | 79 | ||
79 | export interface IAliasController { | 80 | export interface IAliasController { |
80 | entityAliasesChanged: Observable<Array<string>>; | 81 | entityAliasesChanged: Observable<Array<string>>; |
81 | - getAliasInfo(aliasId): Observable<AliasInfo>; | 82 | + entityAliasResolved: Observable<string>; |
83 | + getAliasInfo(aliasId: string): Observable<AliasInfo>; | ||
84 | + getInstantAliasInfo(aliasId: string): AliasInfo; | ||
82 | resolveDatasources(datasources: Array<Datasource>): Observable<Array<Datasource>>; | 85 | resolveDatasources(datasources: Array<Datasource>): Observable<Array<Datasource>>; |
86 | + getEntityAliases(): EntityAliases; | ||
87 | + updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo); | ||
88 | + updateEntityAliases(entityAliases: EntityAliases); | ||
83 | [key: string]: any | null; | 89 | [key: string]: any | null; |
84 | // TODO: | 90 | // TODO: |
85 | } | 91 | } |
@@ -97,17 +103,14 @@ export interface StateParams { | @@ -97,17 +103,14 @@ export interface StateParams { | ||
97 | } | 103 | } |
98 | 104 | ||
99 | export interface IStateController { | 105 | export interface IStateController { |
100 | - getStateParams: () => StateParams; | ||
101 | - openState: (id: string, params?: StateParams, openRightLayout?: boolean) => void; | ||
102 | - updateState: (id?: string, params?: StateParams, openRightLayout?: boolean) => void; | 106 | + getStateParams?: () => StateParams; |
107 | + openState?: (id: string, params?: StateParams, openRightLayout?: boolean) => void; | ||
108 | + updateState?: (id?: string, params?: StateParams, openRightLayout?: boolean) => void; | ||
109 | + openRightLayout: () => void; | ||
110 | + preserveState?: () => void; | ||
103 | // TODO: | 111 | // TODO: |
104 | } | 112 | } |
105 | 113 | ||
106 | -export interface EntityInfo { | ||
107 | - entityId: EntityId; | ||
108 | - entityName: string; | ||
109 | -} | ||
110 | - | ||
111 | export interface SubscriptionInfo { | 114 | export interface SubscriptionInfo { |
112 | type: DatasourceType; | 115 | type: DatasourceType; |
113 | name?: string; | 116 | name?: string; |
@@ -171,6 +174,11 @@ export interface WidgetSubscriptionOptions { | @@ -171,6 +174,11 @@ export interface WidgetSubscriptionOptions { | ||
171 | // TODO: | 174 | // TODO: |
172 | } | 175 | } |
173 | 176 | ||
177 | +export interface SubscriptionEntityInfo { | ||
178 | + entityId: EntityId; | ||
179 | + entityName: string; | ||
180 | +} | ||
181 | + | ||
174 | export interface IWidgetSubscription { | 182 | export interface IWidgetSubscription { |
175 | 183 | ||
176 | id: string; | 184 | id: string; |
@@ -201,7 +209,7 @@ export interface IWidgetSubscription { | @@ -201,7 +209,7 @@ export interface IWidgetSubscription { | ||
201 | rpcErrorText?: string; | 209 | rpcErrorText?: string; |
202 | rpcRejection?: HttpErrorResponse; | 210 | rpcRejection?: HttpErrorResponse; |
203 | 211 | ||
204 | - getFirstEntityInfo(): EntityInfo; | 212 | + getFirstEntityInfo(): SubscriptionEntityInfo; |
205 | 213 | ||
206 | onAliasesChanged(aliasIds: Array<string>): boolean; | 214 | onAliasesChanged(aliasIds: Array<string>): boolean; |
207 | 215 |
@@ -15,8 +15,7 @@ | @@ -15,8 +15,7 @@ | ||
15 | /// | 15 | /// |
16 | 16 | ||
17 | import { | 17 | import { |
18 | - EntityInfo, | ||
19 | - IWidgetSubscription, | 18 | + IWidgetSubscription, SubscriptionEntityInfo, |
20 | WidgetSubscriptionCallbacks, | 19 | WidgetSubscriptionCallbacks, |
21 | WidgetSubscriptionContext, | 20 | WidgetSubscriptionContext, |
22 | WidgetSubscriptionOptions | 21 | WidgetSubscriptionOptions |
@@ -339,7 +338,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -339,7 +338,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
339 | this.onDataUpdated(); | 338 | this.onDataUpdated(); |
340 | } | 339 | } |
341 | 340 | ||
342 | - getFirstEntityInfo(): EntityInfo { | 341 | + getFirstEntityInfo(): SubscriptionEntityInfo { |
343 | return undefined; | 342 | return undefined; |
344 | } | 343 | } |
345 | 344 |
@@ -42,6 +42,8 @@ import {TimeService} from '@core/services/time.service'; | @@ -42,6 +42,8 @@ import {TimeService} from '@core/services/time.service'; | ||
42 | }) | 42 | }) |
43 | export class AuthService { | 43 | export class AuthService { |
44 | 44 | ||
45 | + forceFullscreen = false; // TODO: | ||
46 | + | ||
45 | constructor( | 47 | constructor( |
46 | private store: Store<AppState>, | 48 | private store: Store<AppState>, |
47 | private http: HttpClient, | 49 | private http: HttpClient, |
@@ -39,6 +39,7 @@ import { TranslateDefaultCompiler } from '@core/translate/translate-default-comp | @@ -39,6 +39,7 @@ import { TranslateDefaultCompiler } from '@core/translate/translate-default-comp | ||
39 | import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; | 39 | import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; |
40 | import { WINDOW_PROVIDERS } from '@core/services/window.service'; | 40 | import { WINDOW_PROVIDERS } from '@core/services/window.service'; |
41 | import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; | 41 | import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; |
42 | +import { HotkeyModule } from 'angular2-hotkeys'; | ||
42 | 43 | ||
43 | export function HttpLoaderFactory(http: HttpClient) { | 44 | export function HttpLoaderFactory(http: HttpClient) { |
44 | return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); | 45 | return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); |
@@ -79,6 +80,7 @@ export function HttpLoaderFactory(http: HttpClient) { | @@ -79,6 +80,7 @@ export function HttpLoaderFactory(http: HttpClient) { | ||
79 | useClass: TranslateDefaultCompiler | 80 | useClass: TranslateDefaultCompiler |
80 | } | 81 | } |
81 | }), | 82 | }), |
83 | + HotkeyModule.forRoot(), | ||
82 | 84 | ||
83 | // ngrx | 85 | // ngrx |
84 | StoreModule.forRoot(reducers, { metaReducers }), | 86 | StoreModule.forRoot(reducers, { metaReducers }), |
@@ -28,21 +28,26 @@ import { selectAuth } from '@core/auth/auth.selectors'; | @@ -28,21 +28,26 @@ import { selectAuth } from '@core/auth/auth.selectors'; | ||
28 | import { take } from 'rxjs/operators'; | 28 | import { take } from 'rxjs/operators'; |
29 | import { DialogService } from '@core/services/dialog.service'; | 29 | import { DialogService } from '@core/services/dialog.service'; |
30 | import { TranslateService } from '@ngx-translate/core'; | 30 | import { TranslateService } from '@ngx-translate/core'; |
31 | +import { isDefined } from '../utils'; | ||
31 | 32 | ||
32 | export interface HasConfirmForm { | 33 | export interface HasConfirmForm { |
33 | confirmForm(): FormGroup; | 34 | confirmForm(): FormGroup; |
34 | } | 35 | } |
35 | 36 | ||
37 | +export interface HasDirtyFlag { | ||
38 | + isDirty: boolean; | ||
39 | +} | ||
40 | + | ||
36 | @Injectable({ | 41 | @Injectable({ |
37 | providedIn: 'root' | 42 | providedIn: 'root' |
38 | }) | 43 | }) |
39 | -export class ConfirmOnExitGuard implements CanDeactivate<HasConfirmForm> { | 44 | +export class ConfirmOnExitGuard implements CanDeactivate<HasConfirmForm & HasDirtyFlag> { |
40 | 45 | ||
41 | constructor(private store: Store<AppState>, | 46 | constructor(private store: Store<AppState>, |
42 | private dialogService: DialogService, | 47 | private dialogService: DialogService, |
43 | private translate: TranslateService) { } | 48 | private translate: TranslateService) { } |
44 | 49 | ||
45 | - canDeactivate(component: HasConfirmForm, | 50 | + canDeactivate(component: HasConfirmForm & HasDirtyFlag, |
46 | route: ActivatedRouteSnapshot, | 51 | route: ActivatedRouteSnapshot, |
47 | state: RouterStateSnapshot) { | 52 | state: RouterStateSnapshot) { |
48 | 53 | ||
@@ -54,9 +59,17 @@ export class ConfirmOnExitGuard implements CanDeactivate<HasConfirmForm> { | @@ -54,9 +59,17 @@ export class ConfirmOnExitGuard implements CanDeactivate<HasConfirmForm> { | ||
54 | } | 59 | } |
55 | ); | 60 | ); |
56 | 61 | ||
57 | - if (component.confirmForm && auth && auth.isAuthenticated) { | ||
58 | - const confirmForm = component.confirmForm(); | ||
59 | - if (confirmForm && confirmForm.dirty) { | 62 | + if (auth && auth.isAuthenticated) { |
63 | + let isDirty = false; | ||
64 | + if (component.confirmForm) { | ||
65 | + const confirmForm = component.confirmForm(); | ||
66 | + if (confirmForm) { | ||
67 | + isDirty = confirmForm.dirty; | ||
68 | + } | ||
69 | + } else if (isDefined(component.isDirty)) { | ||
70 | + isDirty = component.isDirty; | ||
71 | + } | ||
72 | + if (isDirty) { | ||
60 | return this.dialogService.confirm( | 73 | return this.dialogService.confirm( |
61 | this.translate.instant('confirm-on-exit.title'), | 74 | this.translate.instant('confirm-on-exit.title'), |
62 | this.translate.instant('confirm-on-exit.html-message') | 75 | this.translate.instant('confirm-on-exit.html-message') |
@@ -21,10 +21,12 @@ import { HttpClient } from '@angular/common/http'; | @@ -21,10 +21,12 @@ import { HttpClient } from '@angular/common/http'; | ||
21 | import { PageLink } from '@shared/models/page/page-link'; | 21 | import { PageLink } from '@shared/models/page/page-link'; |
22 | import { PageData } from '@shared/models/page/page-data'; | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | 23 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
24 | -import { WidgetType } from '@shared/models/widget.models'; | 24 | +import { WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared/models/widget.models'; |
25 | import { UtilsService } from '@core/services/utils.service'; | 25 | import { UtilsService } from '@core/services/utils.service'; |
26 | import { TranslateService } from '@ngx-translate/core'; | 26 | import { TranslateService } from '@ngx-translate/core'; |
27 | import { ResourcesService } from '../services/resources.service'; | 27 | import { ResourcesService } from '../services/resources.service'; |
28 | +import { toWidgetInfo, WidgetInfo } from '@app/modules/home/models/widget-component.models'; | ||
29 | +import { map } from 'rxjs/operators'; | ||
28 | 30 | ||
29 | @Injectable({ | 31 | @Injectable({ |
30 | providedIn: 'root' | 32 | providedIn: 'root' |
@@ -70,4 +72,23 @@ export class WidgetService { | @@ -70,4 +72,23 @@ export class WidgetService { | ||
70 | return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, | 72 | return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, |
71 | defaultHttpOptions(ignoreLoading, ignoreErrors)); | 73 | defaultHttpOptions(ignoreLoading, ignoreErrors)); |
72 | } | 74 | } |
75 | + | ||
76 | + public getWidgetTypeById(widgetTypeId: string, | ||
77 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> { | ||
78 | + return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`, | ||
79 | + defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
80 | + } | ||
81 | + | ||
82 | + public getWidgetTemplate(widgetTypeParam: widgetType, | ||
83 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetInfo> { | ||
84 | + const templateWidgetType = widgetTypesData.get(widgetTypeParam); | ||
85 | + return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true, | ||
86 | + ignoreErrors, ignoreLoading).pipe( | ||
87 | + map((result) => { | ||
88 | + const widgetInfo = toWidgetInfo(result); | ||
89 | + widgetInfo.alias = undefined; | ||
90 | + return widgetInfo; | ||
91 | + }) | ||
92 | + ); | ||
93 | + } | ||
73 | } | 94 | } |
@@ -14,10 +14,13 @@ | @@ -14,10 +14,13 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { createFeatureSelector, createSelector } from '@ngrx/store'; | 17 | +import { createFeatureSelector, createSelector, select, Store } from '@ngrx/store'; |
18 | 18 | ||
19 | import { AppState } from '../core.state'; | 19 | import { AppState } from '../core.state'; |
20 | import { LoadState } from './load.models'; | 20 | import { LoadState } from './load.models'; |
21 | +import { AuthUser } from '@shared/models/user.model'; | ||
22 | +import { take } from 'rxjs/operators'; | ||
23 | +import { selectAuthUser } from '@core/auth/auth.selectors'; | ||
21 | 24 | ||
22 | export const selectLoadState = createFeatureSelector<AppState, LoadState>( | 25 | export const selectLoadState = createFeatureSelector<AppState, LoadState>( |
23 | 'load' | 26 | 'load' |
@@ -32,3 +35,11 @@ export const selectIsLoading = createSelector( | @@ -32,3 +35,11 @@ export const selectIsLoading = createSelector( | ||
32 | selectLoadState, | 35 | selectLoadState, |
33 | (state: LoadState) => state.isLoading | 36 | (state: LoadState) => state.isLoading |
34 | ); | 37 | ); |
38 | + | ||
39 | +export function getCurrentIsLoading(store: Store<AppState>): boolean { | ||
40 | + let isLoading: boolean; | ||
41 | + store.pipe(select(selectIsLoading), take(1)).subscribe( | ||
42 | + val => isLoading = val | ||
43 | + ); | ||
44 | + return isLoading; | ||
45 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Injectable } from '@angular/core'; | ||
18 | +import { UtilsService } from '@core/services/utils.service'; | ||
19 | +import { TimeService } from '@core/services/time.service'; | ||
20 | +import { | ||
21 | + Dashboard, | ||
22 | + DashboardLayout, | ||
23 | + DashboardStateLayouts, | ||
24 | + DashboardState, | ||
25 | + DashboardConfiguration | ||
26 | +} from '@shared/models/dashboard.models'; | ||
27 | +import { isUndefined, isDefined, isString } from '@core/utils'; | ||
28 | +import { DatasourceType, Widget, Datasource } from '@app/shared/models/widget.models'; | ||
29 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
30 | +import { EntityAlias, AliasFilterType } from '@app/shared/models/alias.models'; | ||
31 | + | ||
32 | +@Injectable({ | ||
33 | + providedIn: 'root' | ||
34 | +}) | ||
35 | +export class DashboardUtilsService { | ||
36 | + | ||
37 | + constructor(private utils: UtilsService, | ||
38 | + private timeService: TimeService) { | ||
39 | + } | ||
40 | + | ||
41 | + public validateAndUpdateDashboard(dashboard: Dashboard): Dashboard { | ||
42 | + if (!dashboard.configuration) { | ||
43 | + dashboard.configuration = {}; | ||
44 | + } | ||
45 | + if (isUndefined(dashboard.configuration.widgets)) { | ||
46 | + dashboard.configuration.widgets = {}; | ||
47 | + } else if (Array.isArray(dashboard.configuration.widgets)) { | ||
48 | + const widgetsMap: {[id: string]: Widget} = {}; | ||
49 | + dashboard.configuration.widgets.forEach((widget) => { | ||
50 | + if (!widget.id) { | ||
51 | + widget.id = this.utils.guid(); | ||
52 | + } | ||
53 | + widgetsMap[widget.id] = widget; | ||
54 | + }); | ||
55 | + dashboard.configuration.widgets = widgetsMap; | ||
56 | + } | ||
57 | + for (const id of Object.keys(dashboard.configuration.widgets)) { | ||
58 | + const widget = dashboard.configuration.widgets[id]; | ||
59 | + dashboard.configuration.widgets[id] = this.validateAndUpdateWidget(widget); | ||
60 | + } | ||
61 | + if (isUndefined(dashboard.configuration.states)) { | ||
62 | + dashboard.configuration.states = { | ||
63 | + default: this.createDefaultState(dashboard.title, true) | ||
64 | + }; | ||
65 | + | ||
66 | + const mainLayout = dashboard.configuration.states.default.layouts.main; | ||
67 | + for (const id of Object.keys(dashboard.configuration.widgets)) { | ||
68 | + const widget = dashboard.configuration.widgets[id]; | ||
69 | + mainLayout.widgets[id] = { | ||
70 | + sizeX: widget.sizeX, | ||
71 | + sizeY: widget.sizeY, | ||
72 | + row: widget.row, | ||
73 | + col: widget.col | ||
74 | + }; | ||
75 | + if (isDefined(widget.config.mobileHeight)) { | ||
76 | + mainLayout.widgets[id].mobileHeight = widget.config.mobileHeight; | ||
77 | + } | ||
78 | + if (isDefined(widget.config.mobileOrder)) { | ||
79 | + mainLayout.widgets[id].mobileOrder = widget.config.mobileOrder; | ||
80 | + } | ||
81 | + } | ||
82 | + } else { | ||
83 | + const states = dashboard.configuration.states; | ||
84 | + let rootFound = false; | ||
85 | + for (const stateId of Object.keys(states)) { | ||
86 | + const state = states[stateId]; | ||
87 | + if (isUndefined(state.root)) { | ||
88 | + state.root = false; | ||
89 | + } else if (state.root) { | ||
90 | + rootFound = true; | ||
91 | + } | ||
92 | + } | ||
93 | + if (!rootFound) { | ||
94 | + const firstStateId = Object.keys(states)[0]; | ||
95 | + states[firstStateId].root = true; | ||
96 | + } | ||
97 | + } | ||
98 | + const datasourcesByAliasId: {[aliasId: string]: Array<Datasource>} = {}; | ||
99 | + const targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>} = {}; | ||
100 | + for (const widgetId of Object.keys(dashboard.configuration.widgets)) { | ||
101 | + const widget = dashboard.configuration.widgets[widgetId]; | ||
102 | + widget.config.datasources.forEach((datasource) => { | ||
103 | + if (datasource.entityAliasId) { | ||
104 | + const aliasId = datasource.entityAliasId; | ||
105 | + let aliasDatasources = datasourcesByAliasId[aliasId]; | ||
106 | + if (!aliasDatasources) { | ||
107 | + aliasDatasources = []; | ||
108 | + datasourcesByAliasId[aliasId] = aliasDatasources; | ||
109 | + } | ||
110 | + aliasDatasources.push(datasource); | ||
111 | + } | ||
112 | + }); | ||
113 | + if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) { | ||
114 | + const aliasId = widget.config.targetDeviceAliasIds[0]; | ||
115 | + let targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; | ||
116 | + if (!targetDeviceAliasIdsList) { | ||
117 | + targetDeviceAliasIdsList = []; | ||
118 | + targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList; | ||
119 | + } | ||
120 | + targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds); | ||
121 | + } | ||
122 | + } | ||
123 | + | ||
124 | + dashboard.configuration = this.validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId); | ||
125 | + | ||
126 | + if (isUndefined(dashboard.configuration.timewindow)) { | ||
127 | + dashboard.configuration.timewindow = this.timeService.defaultTimewindow(); | ||
128 | + } | ||
129 | + if (isUndefined(dashboard.configuration.settings)) { | ||
130 | + dashboard.configuration.settings = {}; | ||
131 | + dashboard.configuration.settings.stateControllerId = 'entity'; | ||
132 | + dashboard.configuration.settings.showTitle = false; | ||
133 | + dashboard.configuration.settings.showDashboardsSelect = true; | ||
134 | + dashboard.configuration.settings.showEntitiesSelect = true; | ||
135 | + dashboard.configuration.settings.showDashboardTimewindow = true; | ||
136 | + dashboard.configuration.settings.showDashboardExport = true; | ||
137 | + dashboard.configuration.settings.toolbarAlwaysOpen = true; | ||
138 | + } else { | ||
139 | + if (isUndefined(dashboard.configuration.settings.stateControllerId)) { | ||
140 | + dashboard.configuration.settings.stateControllerId = 'entity'; | ||
141 | + } | ||
142 | + } | ||
143 | + if (isDefined(dashboard.configuration.gridSettings)) { | ||
144 | + const gridSettings = dashboard.configuration.gridSettings; | ||
145 | + if (isDefined(gridSettings.showTitle)) { | ||
146 | + dashboard.configuration.settings.showTitle = gridSettings.showTitle; | ||
147 | + delete gridSettings.showTitle; | ||
148 | + } | ||
149 | + if (isDefined(gridSettings.titleColor)) { | ||
150 | + dashboard.configuration.settings.titleColor = gridSettings.titleColor; | ||
151 | + delete gridSettings.titleColor; | ||
152 | + } | ||
153 | + if (isDefined(gridSettings.showDevicesSelect)) { | ||
154 | + dashboard.configuration.settings.showEntitiesSelect = gridSettings.showDevicesSelect; | ||
155 | + delete gridSettings.showDevicesSelect; | ||
156 | + } | ||
157 | + if (isDefined(gridSettings.showEntitiesSelect)) { | ||
158 | + dashboard.configuration.settings.showEntitiesSelect = gridSettings.showEntitiesSelect; | ||
159 | + delete gridSettings.showEntitiesSelect; | ||
160 | + } | ||
161 | + if (isDefined(gridSettings.showDashboardTimewindow)) { | ||
162 | + dashboard.configuration.settings.showDashboardTimewindow = gridSettings.showDashboardTimewindow; | ||
163 | + delete gridSettings.showDashboardTimewindow; | ||
164 | + } | ||
165 | + if (isDefined(gridSettings.showDashboardExport)) { | ||
166 | + dashboard.configuration.settings.showDashboardExport = gridSettings.showDashboardExport; | ||
167 | + delete gridSettings.showDashboardExport; | ||
168 | + } | ||
169 | + dashboard.configuration.states.default.layouts.main.gridSettings = gridSettings; | ||
170 | + delete dashboard.configuration.gridSettings; | ||
171 | + } | ||
172 | + return dashboard; | ||
173 | + } | ||
174 | + | ||
175 | + public createSingleWidgetDashboard(widget: Widget): Dashboard { | ||
176 | + if (!widget.id) { | ||
177 | + widget.id = this.utils.guid(); | ||
178 | + } | ||
179 | + let dashboard: Dashboard = {}; | ||
180 | + dashboard = this.validateAndUpdateDashboard(dashboard); | ||
181 | + dashboard.configuration.widgets[widget.id] = widget; | ||
182 | + dashboard.configuration.states.default.layouts.main.widgets[widget.id] = { | ||
183 | + sizeX: widget.sizeX, | ||
184 | + sizeY: widget.sizeY, | ||
185 | + row: widget.row, | ||
186 | + col: widget.col, | ||
187 | + }; | ||
188 | + return dashboard; | ||
189 | + } | ||
190 | + | ||
191 | + public validateAndUpdateWidget(widget: Widget): Widget { | ||
192 | + if (!widget.config) { | ||
193 | + widget.config = {}; | ||
194 | + } | ||
195 | + if (!widget.config.datasources) { | ||
196 | + widget.config.datasources = []; | ||
197 | + } | ||
198 | + widget.config.datasources.forEach((datasource) => { | ||
199 | + if (datasource.type === 'device') { | ||
200 | + datasource.type = DatasourceType.entity; | ||
201 | + } | ||
202 | + if (datasource.deviceAliasId) { | ||
203 | + datasource.entityAliasId = datasource.deviceAliasId; | ||
204 | + delete datasource.deviceAliasId; | ||
205 | + } | ||
206 | + }); | ||
207 | + // TODO: Temp workaround | ||
208 | + if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { | ||
209 | + widget.typeAlias = 'basic_timeseries'; | ||
210 | + } | ||
211 | + return widget; | ||
212 | + } | ||
213 | + | ||
214 | + public createDefaultLayoutData(): DashboardLayout { | ||
215 | + return { | ||
216 | + widgets: {}, | ||
217 | + gridSettings: { | ||
218 | + backgroundColor: '#eeeeee', | ||
219 | + color: 'rgba(0,0,0,0.870588)', | ||
220 | + columns: 24, | ||
221 | + margins: [10, 10], | ||
222 | + backgroundSizeMode: '100%' | ||
223 | + } | ||
224 | + }; | ||
225 | + } | ||
226 | + | ||
227 | + public createDefaultLayouts(): DashboardStateLayouts { | ||
228 | + return { | ||
229 | + main: this.createDefaultLayoutData() | ||
230 | + }; | ||
231 | + } | ||
232 | + | ||
233 | + public createDefaultState(name: string, root: boolean): DashboardState { | ||
234 | + return { | ||
235 | + name, | ||
236 | + root, | ||
237 | + layouts: this.createDefaultLayouts() | ||
238 | + }; | ||
239 | + } | ||
240 | + | ||
241 | + private validateAndUpdateEntityAliases(configuration: DashboardConfiguration, | ||
242 | + datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, | ||
243 | + targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): DashboardConfiguration { | ||
244 | + let entityAlias: EntityAlias; | ||
245 | + if (isUndefined(configuration.entityAliases)) { | ||
246 | + configuration.entityAliases = {}; | ||
247 | + if (configuration.deviceAliases) { | ||
248 | + const deviceAliases = configuration.deviceAliases; | ||
249 | + for (const aliasId of Object.keys(deviceAliases)) { | ||
250 | + const deviceAlias = deviceAliases[aliasId]; | ||
251 | + entityAlias = this.validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId); | ||
252 | + configuration.entityAliases[entityAlias.id] = entityAlias; | ||
253 | + } | ||
254 | + delete configuration.deviceAliases; | ||
255 | + } | ||
256 | + } else { | ||
257 | + const entityAliases = configuration.entityAliases; | ||
258 | + for (const aliasId of Object.keys(entityAliases)) { | ||
259 | + entityAlias = entityAliases[aliasId]; | ||
260 | + entityAlias = this.validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId); | ||
261 | + if (aliasId !== entityAlias.id) { | ||
262 | + delete entityAliases[aliasId]; | ||
263 | + } | ||
264 | + entityAliases[entityAlias.id] = entityAlias; | ||
265 | + } | ||
266 | + } | ||
267 | + return configuration; | ||
268 | + } | ||
269 | + | ||
270 | + private validateAndUpdateDeviceAlias(aliasId: string, | ||
271 | + deviceAlias: any, | ||
272 | + datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, | ||
273 | + targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): EntityAlias { | ||
274 | + aliasId = this.validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); | ||
275 | + const alias = deviceAlias.alias; | ||
276 | + const entityAlias: EntityAlias = { | ||
277 | + id: aliasId, | ||
278 | + alias, | ||
279 | + filter: { | ||
280 | + type: null, | ||
281 | + entityType: EntityType.DEVICE, | ||
282 | + resolveMultiple: false | ||
283 | + }, | ||
284 | + }; | ||
285 | + if (deviceAlias.deviceFilter) { | ||
286 | + entityAlias.filter.type = | ||
287 | + deviceAlias.deviceFilter.useFilter ? AliasFilterType.entityName : AliasFilterType.entityList; | ||
288 | + if (entityAlias.filter.type === AliasFilterType.entityList) { | ||
289 | + entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList; | ||
290 | + } else { | ||
291 | + entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; | ||
292 | + } | ||
293 | + } else { | ||
294 | + entityAlias.filter.type = AliasFilterType.entityList; | ||
295 | + entityAlias.filter.entityList = [deviceAlias.deviceId]; | ||
296 | + } | ||
297 | + return entityAlias; | ||
298 | + } | ||
299 | + | ||
300 | + private validateAndUpdateEntityAlias(aliasId: string, entityAlias: EntityAlias, | ||
301 | + datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, | ||
302 | + targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): EntityAlias { | ||
303 | + entityAlias.id = this.validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); | ||
304 | + if (!entityAlias.filter) { | ||
305 | + entityAlias.filter = { | ||
306 | + type: entityAlias.entityFilter.useFilter ? AliasFilterType.entityName : AliasFilterType.entityList, | ||
307 | + entityType: entityAlias.entityType, | ||
308 | + resolveMultiple: false | ||
309 | + }; | ||
310 | + if (entityAlias.filter.type === AliasFilterType.entityList) { | ||
311 | + entityAlias.filter.entityList = entityAlias.entityFilter.entityList; | ||
312 | + } else { | ||
313 | + entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter; | ||
314 | + } | ||
315 | + delete entityAlias.entityType; | ||
316 | + delete entityAlias.entityFilter; | ||
317 | + } | ||
318 | + return entityAlias; | ||
319 | + } | ||
320 | + | ||
321 | + private validateAliasId(aliasId: string, | ||
322 | + datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, | ||
323 | + targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): string { | ||
324 | + if (!aliasId || !isString(aliasId) || aliasId.length !== 36) { | ||
325 | + const newAliasId = this.utils.guid(); | ||
326 | + const aliasDatasources = datasourcesByAliasId[aliasId]; | ||
327 | + if (aliasDatasources) { | ||
328 | + aliasDatasources.forEach( | ||
329 | + (datasource) => { | ||
330 | + datasource.entityAliasId = newAliasId; | ||
331 | + } | ||
332 | + ); | ||
333 | + } | ||
334 | + const targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; | ||
335 | + if (targetDeviceAliasIdsList) { | ||
336 | + targetDeviceAliasIdsList.forEach( | ||
337 | + (targetDeviceAliasIds) => { | ||
338 | + targetDeviceAliasIds[0] = newAliasId; | ||
339 | + } | ||
340 | + ); | ||
341 | + } | ||
342 | + return newAliasId; | ||
343 | + } else { | ||
344 | + return aliasId; | ||
345 | + } | ||
346 | + } | ||
347 | + | ||
348 | +} |
@@ -21,11 +21,12 @@ import { isUndefined, isDefined } from '@core/utils'; | @@ -21,11 +21,12 @@ import { isUndefined, isDefined } from '@core/utils'; | ||
21 | import { WindowMessage } from '@shared/models/window-message.model'; | 21 | import { WindowMessage } from '@shared/models/window-message.model'; |
22 | import { TranslateService } from '@ngx-translate/core'; | 22 | import { TranslateService } from '@ngx-translate/core'; |
23 | import { customTranslationsPrefix } from '@app/shared/models/constants'; | 23 | import { customTranslationsPrefix } from '@app/shared/models/constants'; |
24 | -import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; | 24 | +import { DataKey, Datasource, DatasourceType, KeyInfo, Widget } from '@shared/models/widget.models'; |
25 | import { EntityType } from '@shared/models/entity-type.models'; | 25 | import { EntityType } from '@shared/models/entity-type.models'; |
26 | import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; | 26 | import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; |
27 | import { alarmFields } from '@shared/models/alarm.models'; | 27 | import { alarmFields } from '@shared/models/alarm.models'; |
28 | import { materialColors } from '@app/shared/models/material.models'; | 28 | import { materialColors } from '@app/shared/models/material.models'; |
29 | +import { WidgetInfo } from '@home/models/widget-component.models'; | ||
29 | 30 | ||
30 | @Injectable({ | 31 | @Injectable({ |
31 | providedIn: 'root' | 32 | providedIn: 'root' |
@@ -34,7 +35,7 @@ export class UtilsService { | @@ -34,7 +35,7 @@ export class UtilsService { | ||
34 | 35 | ||
35 | iframeMode = false; | 36 | iframeMode = false; |
36 | widgetEditMode = false; | 37 | widgetEditMode = false; |
37 | - editWidgetInfo: any = null; | 38 | + editWidgetInfo: WidgetInfo = null; |
38 | 39 | ||
39 | constructor(@Inject(WINDOW) private window: Window, | 40 | constructor(@Inject(WINDOW) private window: Window, |
40 | private translate: TranslateService) { | 41 | private translate: TranslateService) { |
@@ -87,7 +88,7 @@ export class UtilsService { | @@ -87,7 +88,7 @@ export class UtilsService { | ||
87 | type: 'widgetException', | 88 | type: 'widgetException', |
88 | data | 89 | data |
89 | }; | 90 | }; |
90 | - this.window.parent.postMessage(message, '*'); | 91 | + this.window.parent.postMessage(JSON.stringify(message), '*'); |
91 | } | 92 | } |
92 | return data; | 93 | return data; |
93 | } | 94 | } |
@@ -95,6 +95,10 @@ export function isNumber(value: any): boolean { | @@ -95,6 +95,10 @@ export function isNumber(value: any): boolean { | ||
95 | return typeof value === 'number'; | 95 | return typeof value === 'number'; |
96 | } | 96 | } |
97 | 97 | ||
98 | +export function isString(value: any): boolean { | ||
99 | + return typeof value === 'string'; | ||
100 | +} | ||
101 | + | ||
98 | export function objToBase64(obj: any): string { | 102 | export function objToBase64(obj: any): string { |
99 | const json = JSON.stringify(obj); | 103 | const json = JSON.stringify(obj); |
100 | const encoded = utf8Encode(json); | 104 | const encoded = utf8Encode(json); |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { NgModule } from '@angular/core'; | ||
18 | +import { CommonModule } from '@angular/common'; | ||
19 | +import { SharedModule } from '@shared/shared.module'; | ||
20 | +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; | ||
21 | +import { HomeDialogsModule } from '@app/modules/home/dialogs/home-dialogs.module'; | ||
22 | +import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; | ||
23 | +import { DashboardPagesRoutingModule } from './dashboard-pages.routing.module'; | ||
24 | + | ||
25 | +@NgModule({ | ||
26 | + entryComponents: [], | ||
27 | + imports: [ | ||
28 | + CommonModule, | ||
29 | + SharedModule, | ||
30 | + HomeComponentsModule, | ||
31 | + HomeDialogsModule, | ||
32 | + DashboardModule, | ||
33 | + DashboardPagesRoutingModule | ||
34 | + ] | ||
35 | +}) | ||
36 | +export class DashboardPagesModule { } |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Injectable, NgModule } from '@angular/core'; | ||
18 | +import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; | ||
19 | + | ||
20 | +import {Authority} from '@shared/models/authority.enum'; | ||
21 | +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; | ||
22 | +import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; | ||
23 | +import { widgetTypesBreadcumbLabelFunction } from '@home/pages/widget/widget-library-routing.module'; | ||
24 | +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | ||
25 | +import { WidgetService } from '@core/http/widget.service'; | ||
26 | +import { Observable } from 'rxjs'; | ||
27 | +import { Dashboard } from '@app/shared/models/dashboard.models'; | ||
28 | +import { DashboardService } from '@core/http/dashboard.service'; | ||
29 | +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; | ||
30 | +import { map } from 'rxjs/operators'; | ||
31 | +import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@app/modules/home/pages/dashboard/dashboard-routing.module'; | ||
32 | +import { UtilsService } from '@core/services/utils.service'; | ||
33 | +import { Widget } from '@app/shared/models/widget.models'; | ||
34 | + | ||
35 | +@Injectable() | ||
36 | +export class WidgetEditorDashboardResolver implements Resolve<Dashboard> { | ||
37 | + | ||
38 | + constructor(private dashboardService: DashboardService, | ||
39 | + private dashboardUtils: DashboardUtilsService, | ||
40 | + private utils: UtilsService) { | ||
41 | + } | ||
42 | + | ||
43 | + resolve(route: ActivatedRouteSnapshot): Dashboard { | ||
44 | + const editWidgetInfo = this.utils.editWidgetInfo; | ||
45 | + const widget: Widget = { | ||
46 | + isSystemType: true, | ||
47 | + bundleAlias: 'customWidgetBundle', | ||
48 | + typeAlias: 'customWidget', | ||
49 | + type: editWidgetInfo.type, | ||
50 | + title: 'My widget', | ||
51 | + sizeX: editWidgetInfo.sizeX * 2, | ||
52 | + sizeY: editWidgetInfo.sizeY * 2, | ||
53 | + row: 2, | ||
54 | + col: 4, | ||
55 | + config: JSON.parse(editWidgetInfo.defaultConfig) | ||
56 | + }; | ||
57 | + widget.config.title = widget.config.title || editWidgetInfo.widgetName; | ||
58 | + return this.dashboardUtils.createSingleWidgetDashboard(widget); | ||
59 | + } | ||
60 | +} | ||
61 | + | ||
62 | +const routes: Routes = [ | ||
63 | + { | ||
64 | + path: 'dashboard/:dashboardId', | ||
65 | + component: DashboardPageComponent, | ||
66 | + data: { | ||
67 | + breadcrumb: { | ||
68 | + skip: true | ||
69 | + }, | ||
70 | + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], | ||
71 | + title: 'dashboard.dashboard', | ||
72 | + widgetEditMode: false, | ||
73 | + singlePageMode: true | ||
74 | + }, | ||
75 | + resolve: { | ||
76 | + dashboard: DashboardResolver | ||
77 | + } | ||
78 | + }, | ||
79 | + { | ||
80 | + path: 'widget-editor', | ||
81 | + component: DashboardPageComponent, | ||
82 | + data: { | ||
83 | + breadcrumb: { | ||
84 | + skip: true | ||
85 | + }, | ||
86 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | ||
87 | + title: 'widget.editor', | ||
88 | + widgetEditMode: true, | ||
89 | + singlePageMode: true | ||
90 | + }, | ||
91 | + resolve: { | ||
92 | + dashboard: WidgetEditorDashboardResolver | ||
93 | + } | ||
94 | + } | ||
95 | +]; | ||
96 | + | ||
97 | +@NgModule({ | ||
98 | + imports: [RouterModule.forChild(routes)], | ||
99 | + exports: [RouterModule], | ||
100 | + providers: [ | ||
101 | + WidgetEditorDashboardResolver | ||
102 | + ] | ||
103 | +}) | ||
104 | +export class DashboardPagesRoutingModule { } |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { NgModule } from '@angular/core'; | ||
18 | +import { Routes, RouterModule } from '@angular/router'; | ||
19 | + | ||
20 | +import { AuthGuard } from '@core/guards/auth.guard'; | ||
21 | +import { StoreModule } from '@ngrx/store'; | ||
22 | + | ||
23 | +const routes: Routes = [ | ||
24 | + { path: '', | ||
25 | + data: { | ||
26 | + title: 'dashboard.dashboard', | ||
27 | + breadcrumb: { | ||
28 | + skip: true | ||
29 | + } | ||
30 | + }, | ||
31 | + canActivate: [AuthGuard], | ||
32 | + canActivateChild: [AuthGuard], | ||
33 | + loadChildren: './dashboard-pages.module#DashboardPagesModule' | ||
34 | + } | ||
35 | +]; | ||
36 | + | ||
37 | +@NgModule({ | ||
38 | + imports: [ | ||
39 | + StoreModule, | ||
40 | + RouterModule.forChild(routes)], | ||
41 | + exports: [RouterModule] | ||
42 | +}) | ||
43 | +export class DashboardRoutingModule { } |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div fxLayout="column" class="mat-content mat-padding"> | ||
19 | + <div fxLayout="row" *ngFor="let alias of entityAliasesInfo | keyvalue"> | ||
20 | + <mat-form-field> | ||
21 | + <mat-label>{{alias.value.alias}}</mat-label> | ||
22 | + <mat-select matInput [(ngModel)]="alias.value.selectedId" | ||
23 | + (ngModelChange)="currentAliasEntityChanged(alias.key, alias.value.selectedId)"> | ||
24 | + <mat-option *ngFor="let resolvedEntity of alias.value.resolvedEntities" [value]="resolvedEntity.id"> | ||
25 | + {{resolvedEntity.name}} | ||
26 | + </mat-option> | ||
27 | + </mat-select> | ||
28 | + </mat-form-field> | ||
29 | + </div> | ||
30 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host { | ||
17 | + min-width: 300px; | ||
18 | + max-height: 150px; | ||
19 | + overflow-x: hidden; | ||
20 | + overflow-y: auto; | ||
21 | + background: #fff; | ||
22 | + border-radius: 4px; | ||
23 | + box-shadow: | ||
24 | + 0 7px 8px -4px rgba(0, 0, 0, .2), | ||
25 | + 0 13px 19px 2px rgba(0, 0, 0, .14), | ||
26 | + 0 5px 24px 4px rgba(0, 0, 0, .12); | ||
27 | + | ||
28 | + @media (min-height: 350px) { | ||
29 | + max-height: 250px; | ||
30 | + } | ||
31 | + | ||
32 | + .mat-content { | ||
33 | + background-color: #fff; | ||
34 | + } | ||
35 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, InjectionToken, OnInit } from '@angular/core'; | ||
18 | +import { Timewindow } from '@shared/models/time/time.models'; | ||
19 | +import { AliasInfo, IAliasController } from '@core/api/widget-api.models'; | ||
20 | +import { PageComponent } from '@shared/components/page.component'; | ||
21 | +import { TIMEWINDOW_PANEL_DATA, TimewindowPanelData } from '@shared/components/time/timewindow-panel.component'; | ||
22 | +import { deepClone } from '@core/utils'; | ||
23 | + | ||
24 | +export const ALIASES_ENTITY_SELECT_PANEL_DATA = new InjectionToken<any>('AliasesEntitySelectPanelData'); | ||
25 | + | ||
26 | +export interface AliasesEntitySelectPanelData { | ||
27 | + aliasController: IAliasController; | ||
28 | +} | ||
29 | + | ||
30 | +@Component({ | ||
31 | + selector: 'tb-aliases-entity-select-panel', | ||
32 | + templateUrl: './aliases-entity-select-panel.component.html', | ||
33 | + styleUrls: ['./aliases-entity-select-panel.component.scss'] | ||
34 | +}) | ||
35 | +export class AliasesEntitySelectPanelComponent { | ||
36 | + | ||
37 | + entityAliasesInfo: {[aliasId: string]: AliasInfo} = {}; | ||
38 | + | ||
39 | + constructor(@Inject(ALIASES_ENTITY_SELECT_PANEL_DATA) public data: AliasesEntitySelectPanelData) { | ||
40 | + const allEntityAliases = this.data.aliasController.getEntityAliases(); | ||
41 | + for (const aliasId of Object.keys(allEntityAliases)) { | ||
42 | + const aliasInfo = this.data.aliasController.getInstantAliasInfo(aliasId); | ||
43 | + if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity | ||
44 | + && aliasInfo.resolvedEntities.length > 1) { | ||
45 | + this.entityAliasesInfo[aliasId] = deepClone(aliasInfo); | ||
46 | + this.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id; | ||
47 | + } | ||
48 | + } | ||
49 | + } | ||
50 | + | ||
51 | + public currentAliasEntityChanged(aliasId: string, selectedId: string) { | ||
52 | + const resolvedEntities = this.entityAliasesInfo[aliasId].resolvedEntities; | ||
53 | + const selected = resolvedEntities.find((entity) => entity.id === selectedId); | ||
54 | + if (selected) { | ||
55 | + this.data.aliasController.updateCurrentAliasEntity(aliasId, selected[0]); | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + | ||
60 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<section class="tb-aliases-entity-select" fxLayout="row" fxLayoutAlign="start center"> | ||
19 | + <button mat-button mat-icon-button | ||
20 | + cdkOverlayOrigin #aliasEntitySelectPanelOrigin="cdkOverlayOrigin" | ||
21 | + (click)="openEditMode()" | ||
22 | + matTooltip="{{ 'entity.select-entities' | translate }}" | ||
23 | + [matTooltipPosition]="tooltipPosition"> | ||
24 | + <mat-icon class="material-icons">devices_other</mat-icon> | ||
25 | + </button> | ||
26 | + <span fxHide.xs fxHide.sm fxHide.md | ||
27 | + (click)="openEditMode()" | ||
28 | + matTooltip="{{ 'entity.select-entities' | translate }}" | ||
29 | + [matTooltipPosition]="tooltipPosition"> | ||
30 | + {{displayValue}} | ||
31 | + </span> | ||
32 | +</section> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +@import "../../../../../scss/constants"; | ||
17 | + | ||
18 | +:host { | ||
19 | + min-width: 52px; | ||
20 | + | ||
21 | + section.tb-aliases-entity-select { | ||
22 | + min-height: 32px; | ||
23 | + padding: 0 6px; | ||
24 | + | ||
25 | + @media #{$mat-lt-md} { | ||
26 | + padding: 0; | ||
27 | + } | ||
28 | + | ||
29 | + span { | ||
30 | + max-width: 200px; | ||
31 | + overflow: hidden; | ||
32 | + text-overflow: ellipsis; | ||
33 | + white-space: nowrap; | ||
34 | + pointer-events: all; | ||
35 | + cursor: pointer; | ||
36 | + } | ||
37 | + } | ||
38 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | ||
18 | +import { TooltipPosition } from '@angular/material'; | ||
19 | +import { IAliasController } from '@core/api/widget-api.models'; | ||
20 | +import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | ||
21 | +import { TranslateService } from '@ngx-translate/core'; | ||
22 | +import { Subscription } from 'rxjs'; | ||
23 | +import { BreakpointObserver } from '@angular/cdk/layout'; | ||
24 | +import { DOCUMENT } from '@angular/common'; | ||
25 | +import { WINDOW } from '@core/services/window.service'; | ||
26 | +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | ||
27 | +import { | ||
28 | + ALIASES_ENTITY_SELECT_PANEL_DATA, | ||
29 | + AliasesEntitySelectPanelComponent, | ||
30 | + AliasesEntitySelectPanelData | ||
31 | +} from './aliases-entity-select-panel.component'; | ||
32 | + | ||
33 | +@Component({ | ||
34 | + selector: 'tb-aliases-entity-select', | ||
35 | + templateUrl: './aliases-entity-select.component.html', | ||
36 | + styleUrls: ['./aliases-entity-select.component.scss'] | ||
37 | +}) | ||
38 | +export class AliasesEntitySelectComponent implements OnInit, OnDestroy { | ||
39 | + | ||
40 | + @Input() | ||
41 | + aliasController: IAliasController; | ||
42 | + | ||
43 | + @Input() | ||
44 | + tooltipPosition: TooltipPosition = 'above'; | ||
45 | + | ||
46 | + @Input() disabled: boolean; | ||
47 | + | ||
48 | + @ViewChild('aliasEntitySelectPanelOrigin', {static: false}) aliasEntitySelectPanelOrigin: CdkOverlayOrigin; | ||
49 | + | ||
50 | + displayValue: string; | ||
51 | + | ||
52 | + private rxSubscriptions = new Array<Subscription>(); | ||
53 | + | ||
54 | + constructor(private translate: TranslateService, | ||
55 | + private overlay: Overlay, | ||
56 | + private breakpointObserver: BreakpointObserver, | ||
57 | + private viewContainerRef: ViewContainerRef, | ||
58 | + @Inject(DOCUMENT) private document: Document, | ||
59 | + @Inject(WINDOW) private window: Window) { | ||
60 | + } | ||
61 | + | ||
62 | + ngOnInit(): void { | ||
63 | + this.rxSubscriptions.push(this.aliasController.entityAliasesChanged.subscribe( | ||
64 | + () => { | ||
65 | + this.updateDisplayValue(); | ||
66 | + } | ||
67 | + )); | ||
68 | + this.rxSubscriptions.push(this.aliasController.entityAliasResolved.subscribe( | ||
69 | + () => { | ||
70 | + this.updateDisplayValue(); | ||
71 | + } | ||
72 | + )); | ||
73 | + } | ||
74 | + | ||
75 | + ngOnDestroy(): void { | ||
76 | + this.rxSubscriptions.forEach((subscription) => { | ||
77 | + subscription.unsubscribe(); | ||
78 | + }); | ||
79 | + this.rxSubscriptions.length = 0; | ||
80 | + } | ||
81 | + | ||
82 | + openEditMode() { | ||
83 | + if (this.disabled) { | ||
84 | + return; | ||
85 | + } | ||
86 | + const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150; | ||
87 | + const panelWidth = 300; | ||
88 | + const position = this.overlay.position(); | ||
89 | + const config = new OverlayConfig({ | ||
90 | + panelClass: 'tb-aliases-entity-select-panel', | ||
91 | + backdropClass: 'cdk-overlay-transparent-backdrop', | ||
92 | + hasBackdrop: true, | ||
93 | + }); | ||
94 | + const el = this.aliasEntitySelectPanelOrigin.elementRef.nativeElement; | ||
95 | + const offset = el.getBoundingClientRect(); | ||
96 | + const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; | ||
97 | + const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; | ||
98 | + const bottomY = offset.bottom - scrollTop; | ||
99 | + const leftX = offset.left - scrollLeft; | ||
100 | + let originX; | ||
101 | + let originY; | ||
102 | + let overlayX; | ||
103 | + let overlayY; | ||
104 | + const wHeight = this.document.documentElement.clientHeight; | ||
105 | + const wWidth = this.document.documentElement.clientWidth; | ||
106 | + if (bottomY + panelHeight > wHeight) { | ||
107 | + originY = 'top'; | ||
108 | + overlayY = 'bottom'; | ||
109 | + } else { | ||
110 | + originY = 'bottom'; | ||
111 | + overlayY = 'top'; | ||
112 | + } | ||
113 | + if (leftX + panelWidth > wWidth) { | ||
114 | + originX = 'end'; | ||
115 | + overlayX = 'end'; | ||
116 | + } else { | ||
117 | + originX = 'start'; | ||
118 | + overlayX = 'start'; | ||
119 | + } | ||
120 | + const connectedPosition: ConnectedPosition = { | ||
121 | + originX, | ||
122 | + originY, | ||
123 | + overlayX, | ||
124 | + overlayY | ||
125 | + }; | ||
126 | + config.positionStrategy = position.flexibleConnectedTo(this.aliasEntitySelectPanelOrigin.elementRef) | ||
127 | + .withPositions([connectedPosition]); | ||
128 | + const overlayRef = this.overlay.create(config); | ||
129 | + overlayRef.backdropClick().subscribe(() => { | ||
130 | + overlayRef.dispose(); | ||
131 | + }); | ||
132 | + | ||
133 | + const injector = this._createAliasesEntitySelectPanelInjector( | ||
134 | + overlayRef, | ||
135 | + { | ||
136 | + aliasController: this.aliasController | ||
137 | + } | ||
138 | + ); | ||
139 | + overlayRef.attach(new ComponentPortal(AliasesEntitySelectPanelComponent, this.viewContainerRef, injector)); | ||
140 | + } | ||
141 | + | ||
142 | + private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): PortalInjector { | ||
143 | + const injectionTokens = new WeakMap<any, any>([ | ||
144 | + [ALIASES_ENTITY_SELECT_PANEL_DATA, data], | ||
145 | + [OverlayRef, overlayRef] | ||
146 | + ]); | ||
147 | + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | ||
148 | + } | ||
149 | + | ||
150 | + private updateDisplayValue() { | ||
151 | + let displayValue; | ||
152 | + let singleValue = true; | ||
153 | + let currentAliasId; | ||
154 | + const entityAliases = this.aliasController.getEntityAliases(); | ||
155 | + for (const aliasId of Object.keys(entityAliases)) { | ||
156 | + const entityAlias = entityAliases[aliasId]; | ||
157 | + if (!entityAlias.filter.resolveMultiple) { | ||
158 | + const resolvedAlias = this.aliasController.getInstantAliasInfo(aliasId); | ||
159 | + if (resolvedAlias && resolvedAlias.currentEntity) { | ||
160 | + if (!currentAliasId) { | ||
161 | + currentAliasId = aliasId; | ||
162 | + } else { | ||
163 | + singleValue = false; | ||
164 | + break; | ||
165 | + } | ||
166 | + } | ||
167 | + } | ||
168 | + } | ||
169 | + if (singleValue && currentAliasId) { | ||
170 | + const aliasInfo = this.aliasController.getInstantAliasInfo(currentAliasId); | ||
171 | + displayValue = aliasInfo.currentEntity.name; | ||
172 | + } else { | ||
173 | + displayValue = this.translate.instant('entity.entities'); | ||
174 | + } | ||
175 | + this.displayValue = displayValue; | ||
176 | + } | ||
177 | + | ||
178 | +} |
@@ -38,6 +38,8 @@ import { DashboardComponent } from '@home/components/dashboard/dashboard.compone | @@ -38,6 +38,8 @@ import { DashboardComponent } from '@home/components/dashboard/dashboard.compone | ||
38 | import { WidgetComponent } from '@home/components/widget/widget.component'; | 38 | import { WidgetComponent } from '@home/components/widget/widget.component'; |
39 | import { WidgetComponentService } from './widget/widget-component.service'; | 39 | import { WidgetComponentService } from './widget/widget-component.service'; |
40 | import { LegendComponent } from '@home/components/widget/legend.component'; | 40 | import { LegendComponent } from '@home/components/widget/legend.component'; |
41 | +import { AliasesEntitySelectPanelComponent } from '@home/components/alias/aliases-entity-select-panel.component'; | ||
42 | +import { AliasesEntitySelectComponent } from '@home/components/alias/aliases-entity-select.component'; | ||
41 | 43 | ||
42 | @NgModule({ | 44 | @NgModule({ |
43 | entryComponents: [ | 45 | entryComponents: [ |
@@ -48,7 +50,8 @@ import { LegendComponent } from '@home/components/widget/legend.component'; | @@ -48,7 +50,8 @@ import { LegendComponent } from '@home/components/widget/legend.component'; | ||
48 | AlarmTableHeaderComponent, | 50 | AlarmTableHeaderComponent, |
49 | AlarmDetailsDialogComponent, | 51 | AlarmDetailsDialogComponent, |
50 | AddAttributeDialogComponent, | 52 | AddAttributeDialogComponent, |
51 | - EditAttributeValuePanelComponent | 53 | + EditAttributeValuePanelComponent, |
54 | + AliasesEntitySelectPanelComponent | ||
52 | ], | 55 | ], |
53 | declarations: | 56 | declarations: |
54 | [ | 57 | [ |
@@ -69,6 +72,8 @@ import { LegendComponent } from '@home/components/widget/legend.component'; | @@ -69,6 +72,8 @@ import { LegendComponent } from '@home/components/widget/legend.component'; | ||
69 | AttributeTableComponent, | 72 | AttributeTableComponent, |
70 | AddAttributeDialogComponent, | 73 | AddAttributeDialogComponent, |
71 | EditAttributeValuePanelComponent, | 74 | EditAttributeValuePanelComponent, |
75 | + AliasesEntitySelectPanelComponent, | ||
76 | + AliasesEntitySelectComponent, | ||
72 | DashboardComponent, | 77 | DashboardComponent, |
73 | WidgetComponent, | 78 | WidgetComponent, |
74 | LegendComponent | 79 | LegendComponent |
@@ -89,6 +94,7 @@ import { LegendComponent } from '@home/components/widget/legend.component'; | @@ -89,6 +94,7 @@ import { LegendComponent } from '@home/components/widget/legend.component'; | ||
89 | AlarmTableComponent, | 94 | AlarmTableComponent, |
90 | AlarmDetailsDialogComponent, | 95 | AlarmDetailsDialogComponent, |
91 | AttributeTableComponent, | 96 | AttributeTableComponent, |
97 | + AliasesEntitySelectComponent, | ||
92 | DashboardComponent, | 98 | DashboardComponent, |
93 | WidgetComponent, | 99 | WidgetComponent, |
94 | LegendComponent | 100 | LegendComponent |
@@ -61,10 +61,9 @@ import { | @@ -61,10 +61,9 @@ import { | ||
61 | WidgetTypeInstance | 61 | WidgetTypeInstance |
62 | } from '@home/models/widget-component.models'; | 62 | } from '@home/models/widget-component.models'; |
63 | import { | 63 | import { |
64 | - EntityInfo, | ||
65 | IWidgetSubscription, | 64 | IWidgetSubscription, |
66 | StateObject, | 65 | StateObject, |
67 | - StateParams, | 66 | + StateParams, SubscriptionEntityInfo, |
68 | SubscriptionInfo, | 67 | SubscriptionInfo, |
69 | WidgetSubscriptionContext, | 68 | WidgetSubscriptionContext, |
70 | WidgetSubscriptionOptions | 69 | WidgetSubscriptionOptions |
@@ -1065,7 +1064,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI | @@ -1065,7 +1064,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI | ||
1065 | this.store.dispatch(new ActionNotificationShow({message: messageToShow, type: 'error'})); | 1064 | this.store.dispatch(new ActionNotificationShow({message: messageToShow, type: 'error'})); |
1066 | } | 1065 | } |
1067 | 1066 | ||
1068 | - private getActiveEntityInfo(): EntityInfo { | 1067 | + private getActiveEntityInfo(): SubscriptionEntityInfo { |
1069 | let entityInfo = this.widgetContext.activeEntityInfo; | 1068 | let entityInfo = this.widgetContext.activeEntityInfo; |
1070 | if (!entityInfo) { | 1069 | if (!entityInfo) { |
1071 | for (const id of Object.keys(this.widgetContext.subscriptions)) { | 1070 | for (const id of Object.keys(this.widgetContext.subscriptions)) { |
@@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
38 | <button mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()"> | 38 | <button mat-button mat-icon-button id="main" fxHide.gt-sm (click)="sidenav.toggle()"> |
39 | <mat-icon class="material-icons">menu</mat-icon> | 39 | <mat-icon class="material-icons">menu</mat-icon> |
40 | </button> | 40 | </button> |
41 | - <div fxFlex tb-breadcrumb class="mat-toolbar-tools"> | 41 | + <div fxFlex tb-breadcrumb [activeComponent]="activeComponent" class="mat-toolbar-tools"> |
42 | </div> | 42 | </div> |
43 | <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()"> | 43 | <button *ngIf="fullscreenEnabled" mat-button mat-icon-button fxHide.xs fxHide.sm (click)="toggleFullscreen()"> |
44 | <mat-icon class="material-icons">{{ isFullscreen() ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | 44 | <mat-icon class="material-icons">{{ isFullscreen() ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
@@ -49,7 +49,7 @@ | @@ -49,7 +49,7 @@ | ||
49 | *ngIf="isLoading$ | async"> | 49 | *ngIf="isLoading$ | async"> |
50 | </mat-progress-bar> | 50 | </mat-progress-bar> |
51 | <div fxFlex fxLayout="column" tb-toast class="tb-main-content"> | 51 | <div fxFlex fxLayout="column" tb-toast class="tb-main-content"> |
52 | - <router-outlet></router-outlet> | 52 | + <router-outlet (activate)="activeComponent = $event;"></router-outlet> |
53 | </div> | 53 | </div> |
54 | </div> | 54 | </div> |
55 | </mat-sidenav-content> | 55 | </mat-sidenav-content> |
@@ -40,6 +40,8 @@ import { MatSidenav } from '@angular/material'; | @@ -40,6 +40,8 @@ import { MatSidenav } from '@angular/material'; | ||
40 | }) | 40 | }) |
41 | export class HomeComponent extends PageComponent implements OnInit { | 41 | export class HomeComponent extends PageComponent implements OnInit { |
42 | 42 | ||
43 | + activeComponent: any; | ||
44 | + | ||
43 | sidenavMode = 'side'; | 45 | sidenavMode = 'side'; |
44 | sidenavOpened = true; | 46 | sidenavOpened = true; |
45 | 47 |
@@ -31,12 +31,11 @@ import { | @@ -31,12 +31,11 @@ import { | ||
31 | } from '@shared/models/widget.models'; | 31 | } from '@shared/models/widget.models'; |
32 | import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; | 32 | import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; |
33 | import { | 33 | import { |
34 | - EntityInfo, | ||
35 | IAliasController, | 34 | IAliasController, |
36 | IStateController, | 35 | IStateController, |
37 | IWidgetSubscription, | 36 | IWidgetSubscription, |
38 | IWidgetUtils, | 37 | IWidgetUtils, |
39 | - RpcApi, | 38 | + RpcApi, SubscriptionEntityInfo, |
40 | TimewindowFunctions, | 39 | TimewindowFunctions, |
41 | WidgetActionsApi, | 40 | WidgetActionsApi, |
42 | WidgetSubscriptionApi | 41 | WidgetSubscriptionApi |
@@ -85,7 +84,7 @@ export interface WidgetContext { | @@ -85,7 +84,7 @@ export interface WidgetContext { | ||
85 | actionsApi?: WidgetActionsApi; | 84 | actionsApi?: WidgetActionsApi; |
86 | stateController?: IStateController; | 85 | stateController?: IStateController; |
87 | aliasController?: IAliasController; | 86 | aliasController?: IAliasController; |
88 | - activeEntityInfo?: EntityInfo; | 87 | + activeEntityInfo?: SubscriptionEntityInfo; |
89 | widgetTitleTemplate?: string; | 88 | widgetTitleTemplate?: string; |
90 | widgetTitle?: string; | 89 | widgetTitle?: string; |
91 | customHeaderActions?: Array<WidgetHeaderAction>; | 90 | customHeaderActions?: Array<WidgetHeaderAction>; |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div class="tb-dashboard-page mat-content" style="padding-top: 150px;" | ||
19 | + fxFlex tb-fullscreen [fullscreen]="widgetEditMode || iframeMode || forceFullscreen || isFullscreen"> | ||
20 | + <section class="tb-dashboard-toolbar" | ||
21 | + [ngClass]="{ 'tb-dashboard-toolbar-opened': toolbarOpened, 'tb-dashboard-toolbar-closed': !toolbarOpened }"> | ||
22 | + <tb-dashboard-toolbar [fxShow]="!widgetEditMode" [forceFullscreen]="forceFullscreen" | ||
23 | + [toolbarOpened]="toolbarOpened" (triggerClick)="openToolbar()"> | ||
24 | + <div class="tb-dashboard-action-panels" fxLayout="column-reverse" fxLayout.gt-sm="row-reverse" | ||
25 | + fxLayoutAlign="center stretch" fxLayoutAlign.gt-sm="space-between center"> | ||
26 | + <div class="tb-dashboard-action-panel" fxFlex.md="30" fxLayout="row-reverse" | ||
27 | + fxLayoutAlign.gt-sm="start center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> | ||
28 | + <button [fxShow]="showCloseToolbar()" mat-button mat-icon-button class="close-action" | ||
29 | + matTooltip="{{ 'dashboard.close-toolbar' | translate }}" | ||
30 | + matTooltipPosition="below" | ||
31 | + (click)="closeToolbar()"> | ||
32 | + <mat-icon>arrow_forward</mat-icon> | ||
33 | + </button> | ||
34 | + <tb-user-menu *ngIf="isPublicUser() && forceFullscreen" fxHide.xs fxHide.sm displayUserInfo="true"> | ||
35 | + </tb-user-menu> | ||
36 | + <button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button | ||
37 | + matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}" | ||
38 | + matTooltipPosition="below" | ||
39 | + (click)="toggleLayouts()"> | ||
40 | + <mat-icon>{{isRightLayoutOpened ? 'arrow_back' : 'menu'}}</mat-icon> | ||
41 | + </button> | ||
42 | + <button [fxShow]="!hideFullscreenButton()" mat-button mat-icon-button | ||
43 | + matTooltip="{{(isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
44 | + matTooltipPosition="below" | ||
45 | + (click)="isFullscreen = !isFullscreen"> | ||
46 | + <mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
47 | + </button> | ||
48 | + <button [fxShow]="isEdit || displayExport()" mat-button mat-icon-button | ||
49 | + matTooltip="{{'dashboard.export' | translate}}" | ||
50 | + matTooltipPosition="below" | ||
51 | + (click)="exportDashboard($event)"> | ||
52 | + <mat-icon>file_download</mat-icon> | ||
53 | + </button> | ||
54 | + <tb-timewindow [fxShow]="isEdit || displayDashboardTimewindow()" | ||
55 | + isToolbar="true" | ||
56 | + direction="left" | ||
57 | + tooltipPosition="below" | ||
58 | + aggregation="true" | ||
59 | + [(ngModel)]="dashboardCtx.dashboardTimewindow"> | ||
60 | + </tb-timewindow> | ||
61 | + <tb-aliases-entity-select [fxShow]="!isEdit && displayEntitiesSelect()" | ||
62 | + tooltipPosition="below" | ||
63 | + [aliasController]="dashboardCtx.aliasController"> | ||
64 | + </tb-aliases-entity-select> | ||
65 | + <button [fxShow]="isEdit" mat-button mat-icon-button | ||
66 | + matTooltip="{{ 'entity.aliases' | translate }}" | ||
67 | + matTooltipPosition="below" | ||
68 | + (click)="openEntityAliases($event)"> | ||
69 | + <mat-icon>devices_other</mat-icon> | ||
70 | + </button> | ||
71 | + <button [fxShow]="isEdit" mat-button mat-icon-button | ||
72 | + matTooltip="{{ 'dashboard.settings' | translate }}" | ||
73 | + matTooltipPosition="below" | ||
74 | + (click)="openDashboardSettings($event)"> | ||
75 | + <mat-icon>settings</mat-icon> | ||
76 | + </button> | ||
77 | + <tb-dashboard-select [fxShow]="!isEdit && !widgetEditMode && displayDashboardsSelect()" | ||
78 | + [(ngModel)]="currentDashboardId" | ||
79 | + (ngModelChange)="currentDashboardIdChanged(currentDashboardId)" | ||
80 | + [customerId]="currentCustomerId" | ||
81 | + [dashboardsScope]="currentDashboardScope"> | ||
82 | + </tb-dashboard-select> | ||
83 | + </div> | ||
84 | + <div class="tb-dashboard-action-panel" fxFlex.md="70" fxLayout="row-reverse" | ||
85 | + fxLayoutAlign.gt-sm="end center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> | ||
86 | + <tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true"> | ||
87 | + </tb-user-menu> | ||
88 | + <!-- TODO --> | ||
89 | + </div> | ||
90 | + </div> | ||
91 | + </tb-dashboard-toolbar> | ||
92 | + </section> | ||
93 | + <section class="tb-dashboard-container tb-absolute-fill" | ||
94 | + [ngClass]="{ 'is-fullscreen': forceFullscreen, | ||
95 | + 'tb-dashboard-toolbar-opened': toolbarOpened, | ||
96 | + 'tb-dashboard-toolbar-closed': !toolbarOpened }"> | ||
97 | + <section *ngIf="!widgetEditMode" class="tb-dashboard-title" fxLayout="row" fxLayoutAlign="center center" | ||
98 | + [ngStyle]="{'color': dashboard.configuration.settings.titleColor}"> | ||
99 | + <h3 [fxShow]="!isEdit && displayTitle()">{{ dashboard.title }}</h3> | ||
100 | + <mat-form-field [fxShow]="isEdit" class="mat-block" style="height: 30px;"> | ||
101 | + <mat-label translate [ngStyle]="{'color': dashboard.configuration.settings.titleColor}">dashboard.title</mat-label> | ||
102 | + <input matInput class="tb-dashboard-title" | ||
103 | + [ngStyle]="{'color': dashboard.configuration.settings.titleColor}" | ||
104 | + required name="title" [(ngModel)]="dashboard.title"> | ||
105 | + </mat-form-field> | ||
106 | + </section> | ||
107 | + <div class="tb-absolute-fill tb-dashboard-layouts" fxLayout="{{forceDashboardMobileMode ? 'column' : 'row'}}" | ||
108 | + [ngClass]="{ 'tb-padded' : !widgetEditMode && (isEdit || displayTitle()), 'tb-shrinked' : isEditingWidget }"> | ||
109 | + <div [fxShow]="layouts.main.show" | ||
110 | + id="tb-main-layout" | ||
111 | + [ngStyle]="{width: mainLayoutWidth(), | ||
112 | + height: mainLayoutHeight()}"> | ||
113 | + TODO: MAIN LAYOUT tb-dashboard-layout | ||
114 | + </div> | ||
115 | + <mat-sidenav-container *ngIf="layouts.right.show" | ||
116 | + id="tb-right-layout"> | ||
117 | + <mat-sidenav | ||
118 | + [ngStyle]="{minWidth: rightLayoutWidth(), | ||
119 | + maxWidth: rightLayoutWidth(), | ||
120 | + height: rightLayoutHeight(), | ||
121 | + zIndex: 25}" | ||
122 | + disableClose="true" | ||
123 | + position="end" | ||
124 | + [mode]="isMobile ? 'over' : 'side'" | ||
125 | + [(opened)]="rightLayoutOpened"> | ||
126 | + TODO: RIGHT LAYOUT tb-dashboard-layout | ||
127 | + </mat-sidenav> | ||
128 | + </mat-sidenav-container> | ||
129 | + </div> | ||
130 | + <!--tb-details-sidenav TODO --> | ||
131 | + <!--tb-details-sidenav TODO --> | ||
132 | + <section fxLayout="row" class="layout-wrap tb-footer-buttons" fxLayoutAlign="start end"> | ||
133 | + <!--md-fab-speed-dial TODO --> | ||
134 | + <button *ngIf="(isTenantAdmin() || isSystemAdmin()) && !forceFullscreen" | ||
135 | + mat-fab color="accent" class="tb-btn-footer" | ||
136 | + [ngClass]="{'tb-hide': !isEdit || isAddingWidget}" | ||
137 | + [disabled]="isLoading$ | async" | ||
138 | + (click)="saveDashboard()" | ||
139 | + matTooltip="{{ 'action.apply-changes' | translate }}" | ||
140 | + matTooltipPosition="above"> | ||
141 | + <mat-icon>done</mat-icon> | ||
142 | + </button> | ||
143 | + <button *ngIf="(isTenantAdmin() || isSystemAdmin()) && !forceFullscreen" | ||
144 | + mat-fab color="accent" class="tb-btn-footer" | ||
145 | + [ngClass]="{'tb-hide': isAddingWidget || (isLoading$ | async)}" | ||
146 | + [disabled]="isLoading$ | async" | ||
147 | + (click)="toggleDashboardEditMode()" | ||
148 | + matTooltip="{{ (isEdit ? 'action.decline-changes': 'action.enter-edit-mode') | translate }}" | ||
149 | + matTooltipPosition="above"> | ||
150 | + <mat-icon>{{ isEdit ? 'close' : 'edit' }}</mat-icon> | ||
151 | + </button> | ||
152 | + </section> | ||
153 | + </section> | ||
154 | + <section class="tb-powered-by-footer" [ngStyle]="{'color': dashboard.configuration.settings.titleColor}"> | ||
155 | + <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ thingsboardVersion }}</a></span> | ||
156 | + </section> | ||
157 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +@import "../../../../../scss/constants"; | ||
18 | + | ||
19 | +$toolbar-height: 50px !default; | ||
20 | +$fullscreen-toolbar-height: 64px !default; | ||
21 | +$mobile-toolbar-height: 84px !default; | ||
22 | + | ||
23 | +tb-dashboard-page { | ||
24 | + display: flex; | ||
25 | + width: 100%; | ||
26 | + height: 100%; | ||
27 | +} | ||
28 | + | ||
29 | +div.tb-dashboard-page { | ||
30 | + &.mat-content { | ||
31 | + background-color: #eee; | ||
32 | + } | ||
33 | + section.tb-dashboard-title { | ||
34 | + position: absolute; | ||
35 | + top: 0; | ||
36 | + left: 20px; | ||
37 | + mat-form-field { | ||
38 | + .mat-form-field-infix { | ||
39 | + width: 100%; | ||
40 | + } | ||
41 | + } | ||
42 | + input.tb-dashboard-title { | ||
43 | + height: 38px; | ||
44 | + font-size: 2rem; | ||
45 | + font-weight: 500; | ||
46 | + letter-spacing: .005em; | ||
47 | + } | ||
48 | + } | ||
49 | + div.tb-padded { | ||
50 | + top: 60px; | ||
51 | + } | ||
52 | + | ||
53 | + section.tb-padded { | ||
54 | + top: 60px; | ||
55 | + } | ||
56 | + | ||
57 | + div.tb-shrinked { | ||
58 | + width: 40%; | ||
59 | + } | ||
60 | + | ||
61 | + section.tb-dashboard-toolbar { | ||
62 | + position: absolute; | ||
63 | + top: 0; | ||
64 | + left: 0; | ||
65 | + z-index: 13; | ||
66 | + pointer-events: none; | ||
67 | + | ||
68 | + &.tb-dashboard-toolbar-opened { | ||
69 | + right: 0; | ||
70 | + // transition: right .3s cubic-bezier(.55, 0, .55, .2); | ||
71 | + } | ||
72 | + | ||
73 | + &.tb-dashboard-toolbar-closed { | ||
74 | + right: 18px; | ||
75 | + transition: right .3s cubic-bezier(.55, 0, .55, .2) .2s; | ||
76 | + } | ||
77 | + } | ||
78 | + | ||
79 | + .tb-dashboard-container { | ||
80 | + &.tb-dashboard-toolbar-opened { | ||
81 | + &.is-fullscreen { | ||
82 | + margin-top: $mobile-toolbar-height; | ||
83 | + | ||
84 | + @media #{$mat-gt-sm} { | ||
85 | + margin-top: $fullscreen-toolbar-height; | ||
86 | + } | ||
87 | + } | ||
88 | + | ||
89 | + &:not(.is-fullscreen) { | ||
90 | + margin-top: $mobile-toolbar-height; | ||
91 | + | ||
92 | + @media #{$mat-gt-sm} { | ||
93 | + margin-top: $toolbar-height; | ||
94 | + } | ||
95 | + | ||
96 | + transition: margin-top .3s cubic-bezier(.55, 0, .55, .2); | ||
97 | + } | ||
98 | + } | ||
99 | + | ||
100 | + &.tb-dashboard-toolbar-closed { | ||
101 | + margin-top: 0; | ||
102 | + | ||
103 | + transition: margin-top .3s cubic-bezier(.55, 0, .55, .2) .2s; | ||
104 | + } | ||
105 | + | ||
106 | + .tb-dashboard-layouts { | ||
107 | + /*md-backdrop { | ||
108 | + z-index: 1; | ||
109 | + }*/ | ||
110 | + #tb-right-layout { | ||
111 | + mat-sidenav { | ||
112 | + z-index: 1; | ||
113 | + } | ||
114 | + } | ||
115 | + } | ||
116 | + } | ||
117 | + | ||
118 | + section.tb-powered-by-footer { | ||
119 | + position: absolute; | ||
120 | + right: 25px; | ||
121 | + bottom: 5px; | ||
122 | + z-index: 30; | ||
123 | + pointer-events: none; | ||
124 | + | ||
125 | + span { | ||
126 | + font-size: 12px; | ||
127 | + | ||
128 | + a { | ||
129 | + font-weight: 700; | ||
130 | + text-decoration: none; | ||
131 | + pointer-events: all; | ||
132 | + border: none; | ||
133 | + } | ||
134 | + } | ||
135 | + } | ||
136 | + | ||
137 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; | ||
18 | +import { PageComponent } from '@shared/components/page.component'; | ||
19 | +import { Store } from '@ngrx/store'; | ||
20 | +import { AppState } from '@core/core.state'; | ||
21 | +import { ActivatedRoute, Router } from '@angular/router'; | ||
22 | +import { UtilsService } from '@core/services/utils.service'; | ||
23 | +import { AuthService } from '@core/auth/auth.service'; | ||
24 | +import { Dashboard, DashboardConfiguration, WidgetLayout } from '@app/shared/models/dashboard.models'; | ||
25 | +import { WINDOW } from '@core/services/window.service'; | ||
26 | +import { WindowMessage } from '@shared/models/window-message.model'; | ||
27 | +import { deepClone, isDefined } from '@app/core/utils'; | ||
28 | +import { | ||
29 | + DashboardContext, | ||
30 | + DashboardPageLayoutContext, | ||
31 | + DashboardPageLayouts, | ||
32 | + DashboardPageScope | ||
33 | +} from './dashboard-page.models'; | ||
34 | +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; | ||
35 | +import { MediaBreakpoints } from '@shared/models/constants'; | ||
36 | +import { AuthUser } from '@shared/models/user.model'; | ||
37 | +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; | ||
38 | +import { Widget } from '@app/shared/models/widget.models'; | ||
39 | +import { environment as env } from '@env/environment'; | ||
40 | +import { Authority } from '@shared/models/authority.enum'; | ||
41 | +import { DialogService } from '@core/services/dialog.service'; | ||
42 | +import { EntityService } from '@core/http/entity.service'; | ||
43 | +import { AliasController } from '@core/api/alias-controller'; | ||
44 | +import { Subscription } from 'rxjs'; | ||
45 | + | ||
46 | +@Component({ | ||
47 | + selector: 'tb-dashboard-page', | ||
48 | + templateUrl: './dashboard-page.component.html', | ||
49 | + styleUrls: ['./dashboard-page.component.scss'], | ||
50 | + encapsulation: ViewEncapsulation.None | ||
51 | +}) | ||
52 | +export class DashboardPageComponent extends PageComponent implements OnDestroy { | ||
53 | + | ||
54 | + authUser: AuthUser = getCurrentAuthUser(this.store); | ||
55 | + | ||
56 | + dashboard: Dashboard; | ||
57 | + dashboardConfiguration: DashboardConfiguration; | ||
58 | + | ||
59 | + prevDashboard: Dashboard; | ||
60 | + | ||
61 | + iframeMode = this.utils.iframeMode; | ||
62 | + widgetEditMode: boolean; | ||
63 | + singlePageMode: boolean; | ||
64 | + forceFullscreen = this.authService.forceFullscreen; | ||
65 | + | ||
66 | + isFullscreen = false; | ||
67 | + isEdit = false; | ||
68 | + isEditingWidget = false; | ||
69 | + isMobile = !this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); | ||
70 | + forceDashboardMobileMode = false; | ||
71 | + isAddingWidget = false; | ||
72 | + | ||
73 | + isToolbarOpened = false; | ||
74 | + isRightLayoutOpened = false; | ||
75 | + | ||
76 | + editingWidget: Widget = null; | ||
77 | + editingWidgetLayout: WidgetLayout = null; | ||
78 | + editingWidgetOriginal: Widget = null; | ||
79 | + editingWidgetLayoutOriginal: WidgetLayout = null; | ||
80 | + editingWidgetSubtitle: string = null; | ||
81 | + editingLayoutCtx: DashboardPageLayoutContext = null; | ||
82 | + | ||
83 | + thingsboardVersion: string = env.tbVersion; | ||
84 | + | ||
85 | + currentDashboardId: string; | ||
86 | + currentCustomerId: string; | ||
87 | + currentDashboardScope: DashboardPageScope; | ||
88 | + | ||
89 | + layouts: DashboardPageLayouts = { | ||
90 | + main: { | ||
91 | + show: false, | ||
92 | + layoutCtx: { | ||
93 | + id: 'main', | ||
94 | + widgets: [], | ||
95 | + widgetLayouts: {}, | ||
96 | + gridSettings: {}, | ||
97 | + ignoreLoading: false | ||
98 | + } | ||
99 | + }, | ||
100 | + right: { | ||
101 | + show: false, | ||
102 | + layoutCtx: { | ||
103 | + id: 'right', | ||
104 | + widgets: [], | ||
105 | + widgetLayouts: {}, | ||
106 | + gridSettings: {}, | ||
107 | + ignoreLoading: false | ||
108 | + } | ||
109 | + } | ||
110 | + }; | ||
111 | + | ||
112 | + dashboardCtx: DashboardContext = { | ||
113 | + dashboard: null, | ||
114 | + dashboardTimewindow: null, | ||
115 | + state: null, | ||
116 | + stateController: { | ||
117 | + openRightLayout: this.openRightLayout.bind(this) | ||
118 | + }, | ||
119 | + aliasController: null | ||
120 | + }; | ||
121 | + | ||
122 | + private rxSubscriptions = new Array<Subscription>(); | ||
123 | + | ||
124 | + get toolbarOpened(): boolean { | ||
125 | + return !this.widgetEditMode && | ||
126 | + (this.toolbarAlwaysOpen() || this.isToolbarOpened || this.isEdit || this.showRightLayoutSwitch()); | ||
127 | + } | ||
128 | + set toolbarOpened(toolbarOpened: boolean) { | ||
129 | + } | ||
130 | + | ||
131 | + get rightLayoutOpened(): boolean { | ||
132 | + return !this.isMobile || this.isRightLayoutOpened; | ||
133 | + } | ||
134 | + set rightLayoutOpened(rightLayoutOpened: boolean) { | ||
135 | + } | ||
136 | + | ||
137 | + constructor(protected store: Store<AppState>, | ||
138 | + @Inject(WINDOW) private window: Window, | ||
139 | + private breakpointObserver: BreakpointObserver, | ||
140 | + private route: ActivatedRoute, | ||
141 | + private router: Router, | ||
142 | + private utils: UtilsService, | ||
143 | + private authService: AuthService, | ||
144 | + private entityService: EntityService, | ||
145 | + private dialogService: DialogService) { | ||
146 | + super(store); | ||
147 | + | ||
148 | + this.rxSubscriptions.push(this.route.data.subscribe( | ||
149 | + (data) => { | ||
150 | + this.init(data); | ||
151 | + } | ||
152 | + )); | ||
153 | + | ||
154 | + this.rxSubscriptions.push(this.breakpointObserver | ||
155 | + .observe(MediaBreakpoints['gt-sm']) | ||
156 | + .subscribe((state: BreakpointState) => { | ||
157 | + this.isMobile = !state.matches; | ||
158 | + } | ||
159 | + )); | ||
160 | + } | ||
161 | + | ||
162 | + private init(data: any) { | ||
163 | + | ||
164 | + this.reset(); | ||
165 | + | ||
166 | + this.currentDashboardId = this.route.snapshot.params.dashboardId; | ||
167 | + | ||
168 | + if (this.route.snapshot.params.customerId) { | ||
169 | + this.currentCustomerId = this.route.snapshot.params.customerId; | ||
170 | + this.currentDashboardScope = 'customer'; | ||
171 | + } else { | ||
172 | + this.currentDashboardScope = this.authUser.authority === Authority.TENANT_ADMIN ? 'tenant' : 'customer'; | ||
173 | + this.currentCustomerId = this.authUser.customerId; | ||
174 | + } | ||
175 | + | ||
176 | + this.dashboard = data.dashboard; | ||
177 | + this.dashboardConfiguration = this.dashboard.configuration; | ||
178 | + this.widgetEditMode = data.widgetEditMode; | ||
179 | + this.singlePageMode = data.singlePageMode; | ||
180 | + | ||
181 | + this.dashboardCtx.dashboard = this.dashboard; | ||
182 | + this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; | ||
183 | + this.dashboardCtx.aliasController = new AliasController(this.utils, | ||
184 | + this.entityService, | ||
185 | + this.dashboardCtx.stateController, | ||
186 | + this.dashboardConfiguration.entityAliases); | ||
187 | + | ||
188 | + if (this.widgetEditMode) { | ||
189 | + const message: WindowMessage = { | ||
190 | + type: 'widgetEditModeInited' | ||
191 | + }; | ||
192 | + this.window.parent.postMessage(JSON.stringify(message), '*'); | ||
193 | + } | ||
194 | + } | ||
195 | + | ||
196 | + private reset() { | ||
197 | + this.dashboard = null; | ||
198 | + this.dashboardConfiguration = null; | ||
199 | + this.prevDashboard = null; | ||
200 | + | ||
201 | + this.widgetEditMode = false; | ||
202 | + this.singlePageMode = false; | ||
203 | + | ||
204 | + this.isFullscreen = false; | ||
205 | + this.isEdit = false; | ||
206 | + this.isEditingWidget = false; | ||
207 | + this.forceDashboardMobileMode = false; | ||
208 | + this.isAddingWidget = false; | ||
209 | + | ||
210 | + this.isToolbarOpened = false; | ||
211 | + this.isRightLayoutOpened = false; | ||
212 | + | ||
213 | + this.editingWidget = null; | ||
214 | + this.editingWidgetLayout = null; | ||
215 | + this.editingWidgetOriginal = null; | ||
216 | + this.editingWidgetLayoutOriginal = null; | ||
217 | + this.editingWidgetSubtitle = null; | ||
218 | + this.editingLayoutCtx = null; | ||
219 | + | ||
220 | + this.currentDashboardId = null; | ||
221 | + this.currentCustomerId = null; | ||
222 | + this.currentDashboardScope = null; | ||
223 | + } | ||
224 | + | ||
225 | + ngOnDestroy(): void { | ||
226 | + this.rxSubscriptions.forEach((subscription) => { | ||
227 | + subscription.unsubscribe(); | ||
228 | + }); | ||
229 | + this.rxSubscriptions.length = 0; | ||
230 | + } | ||
231 | + | ||
232 | + public openToolbar() { | ||
233 | + this.isToolbarOpened = true; | ||
234 | + } | ||
235 | + | ||
236 | + public closeToolbar() { | ||
237 | + this.isToolbarOpened = false; | ||
238 | + } | ||
239 | + | ||
240 | + public showCloseToolbar() { | ||
241 | + return !this.toolbarAlwaysOpen() && !this.isEdit && !this.showRightLayoutSwitch(); | ||
242 | + } | ||
243 | + | ||
244 | + public hideFullscreenButton(): boolean { | ||
245 | + return this.widgetEditMode || this.iframeMode || this.forceFullscreen || this.singlePageMode; | ||
246 | + } | ||
247 | + | ||
248 | + public toolbarAlwaysOpen(): boolean { | ||
249 | + if (this.dashboard.configuration.settings && | ||
250 | + isDefined(this.dashboard.configuration.settings.toolbarAlwaysOpen)) { | ||
251 | + return this.dashboard.configuration.settings.toolbarAlwaysOpen; | ||
252 | + } else { | ||
253 | + return true; | ||
254 | + } | ||
255 | + } | ||
256 | + | ||
257 | + public displayTitle(): boolean { | ||
258 | + if (this.dashboard.configuration.settings && | ||
259 | + isDefined(this.dashboard.configuration.settings.showTitle)) { | ||
260 | + return this.dashboard.configuration.settings.showTitle; | ||
261 | + } else { | ||
262 | + return false; | ||
263 | + } | ||
264 | + } | ||
265 | + | ||
266 | + public displayExport(): boolean { | ||
267 | + if (this.dashboard.configuration.settings && | ||
268 | + isDefined(this.dashboard.configuration.settings.showDashboardExport)) { | ||
269 | + return this.dashboard.configuration.settings.showDashboardExport; | ||
270 | + } else { | ||
271 | + return true; | ||
272 | + } | ||
273 | + } | ||
274 | + | ||
275 | + public displayDashboardTimewindow(): boolean { | ||
276 | + if (this.dashboard.configuration.settings && | ||
277 | + isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) { | ||
278 | + return this.dashboard.configuration.settings.showDashboardTimewindow; | ||
279 | + } else { | ||
280 | + return true; | ||
281 | + } | ||
282 | + } | ||
283 | + | ||
284 | + public displayDashboardsSelect(): boolean { | ||
285 | + if (this.dashboard.configuration.settings && | ||
286 | + isDefined(this.dashboard.configuration.settings.showDashboardsSelect)) { | ||
287 | + return this.dashboard.configuration.settings.showDashboardsSelect; | ||
288 | + } else { | ||
289 | + return true; | ||
290 | + } | ||
291 | + } | ||
292 | + | ||
293 | + public displayEntitiesSelect(): boolean { | ||
294 | + if (this.dashboard.configuration.settings && | ||
295 | + isDefined(this.dashboard.configuration.settings.showEntitiesSelect)) { | ||
296 | + return this.dashboard.configuration.settings.showEntitiesSelect; | ||
297 | + } else { | ||
298 | + return true; | ||
299 | + } | ||
300 | + } | ||
301 | + | ||
302 | + public showRightLayoutSwitch(): boolean { | ||
303 | + return this.isMobile && this.layouts.right.show; | ||
304 | + } | ||
305 | + | ||
306 | + public toggleLayouts() { | ||
307 | + this.isRightLayoutOpened = !this.isRightLayoutOpened; | ||
308 | + } | ||
309 | + | ||
310 | + public openRightLayout() { | ||
311 | + this.isRightLayoutOpened = true; | ||
312 | + } | ||
313 | + | ||
314 | + public mainLayoutWidth(): string { | ||
315 | + if (this.isEditingWidget && this.editingLayoutCtx.id === 'main') { | ||
316 | + return '100%'; | ||
317 | + } else { | ||
318 | + return this.layouts.right.show && !this.isMobile ? '50%' : '100%'; | ||
319 | + } | ||
320 | + } | ||
321 | + | ||
322 | + public mainLayoutHeight(): string { | ||
323 | + if (!this.isEditingWidget || this.editingLayoutCtx.id === 'main') { | ||
324 | + return '100%'; | ||
325 | + } else { | ||
326 | + return '0px'; | ||
327 | + } | ||
328 | + } | ||
329 | + | ||
330 | + public rightLayoutWidth(): string { | ||
331 | + if (this.isEditingWidget && this.editingLayoutCtx.id === 'right') { | ||
332 | + return '100%'; | ||
333 | + } else { | ||
334 | + return this.isMobile ? '100%' : '50%'; | ||
335 | + } | ||
336 | + } | ||
337 | + | ||
338 | + public rightLayoutHeight(): string { | ||
339 | + if (!this.isEditingWidget || this.editingLayoutCtx.id === 'right') { | ||
340 | + return '100%'; | ||
341 | + } else { | ||
342 | + return '0px'; | ||
343 | + } | ||
344 | + } | ||
345 | + | ||
346 | + public isPublicUser(): boolean { | ||
347 | + return this.authUser.isPublic; | ||
348 | + } | ||
349 | + | ||
350 | + public isTenantAdmin(): boolean { | ||
351 | + return this.authUser.authority === Authority.TENANT_ADMIN; | ||
352 | + } | ||
353 | + | ||
354 | + public isSystemAdmin(): boolean { | ||
355 | + return this.authUser.authority === Authority.SYS_ADMIN; | ||
356 | + } | ||
357 | + | ||
358 | + public exportDashboard($event: Event) { | ||
359 | + if ($event) { | ||
360 | + $event.stopPropagation(); | ||
361 | + } | ||
362 | + // TODO: | ||
363 | + this.dialogService.todo(); | ||
364 | + } | ||
365 | + | ||
366 | + public openEntityAliases($event: Event) { | ||
367 | + if ($event) { | ||
368 | + $event.stopPropagation(); | ||
369 | + } | ||
370 | + // TODO: | ||
371 | + this.dialogService.todo(); | ||
372 | + } | ||
373 | + | ||
374 | + public openDashboardSettings($event: Event) { | ||
375 | + if ($event) { | ||
376 | + $event.stopPropagation(); | ||
377 | + } | ||
378 | + // TODO: | ||
379 | + this.dialogService.todo(); | ||
380 | + } | ||
381 | + | ||
382 | + public currentDashboardIdChanged(dashboardId: string) { | ||
383 | + if (!this.widgetEditMode) { | ||
384 | + if (this.currentDashboardScope === 'customer' && this.authUser.authority === Authority.TENANT_ADMIN) { | ||
385 | + this.router.navigateByUrl(`customers/${this.currentCustomerId}/dashboards/${dashboardId}`); | ||
386 | + } else { | ||
387 | + if (this.singlePageMode) { | ||
388 | + this.router.navigateByUrl(`dashboard/${dashboardId}`); | ||
389 | + } else { | ||
390 | + this.router.navigateByUrl(`dashboards/${dashboardId}`); | ||
391 | + } | ||
392 | + } | ||
393 | + } | ||
394 | + } | ||
395 | + | ||
396 | + public toggleDashboardEditMode() { | ||
397 | + this.setEditMode(!this.isEdit, true); | ||
398 | + } | ||
399 | + | ||
400 | + private setEditMode(isEdit: boolean, revert: boolean) { | ||
401 | + this.isEdit = isEdit; | ||
402 | + if (this.isEdit) { | ||
403 | + // TODO: | ||
404 | + // this.dashboardCtx.stateController.preserveState(); | ||
405 | + this.prevDashboard = deepClone(this.dashboard); | ||
406 | + } else { | ||
407 | + if (this.widgetEditMode) { | ||
408 | + if (revert) { | ||
409 | + this.dashboard = this.prevDashboard; | ||
410 | + } | ||
411 | + } else { | ||
412 | + this.resetHighlight(); | ||
413 | + if (revert) { | ||
414 | + this.dashboard = this.prevDashboard; | ||
415 | + this.dashboardConfiguration = this.dashboard.configuration; | ||
416 | + this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; | ||
417 | + this.entityAliasesUpdated(); | ||
418 | + } else { | ||
419 | + this.dashboard.configuration.timewindow = this.dashboardCtx.dashboardTimewindow; | ||
420 | + } | ||
421 | + } | ||
422 | + } | ||
423 | + } | ||
424 | + | ||
425 | + private resetHighlight() { | ||
426 | + for (const l of Object.keys(this.layouts)) { | ||
427 | + if (this.layouts[l].layoutCtx) { | ||
428 | + if (this.layouts[l].layoutCtx.ctrl) { | ||
429 | + this.layouts[l].layoutCtx.ctrl.resetHighlight(); | ||
430 | + } | ||
431 | + } | ||
432 | + } | ||
433 | + } | ||
434 | + | ||
435 | + private entityAliasesUpdated() { | ||
436 | + this.dashboardCtx.aliasController.updateEntityAliases(this.dashboard.configuration.entityAliases); | ||
437 | + } | ||
438 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { DashboardLayoutId, GridSettings, WidgetLayout, Dashboard } from '@app/shared/models/dashboard.models'; | ||
18 | +import { Widget } from '@app/shared/models/widget.models'; | ||
19 | +import { Timewindow } from '@shared/models/time/time.models'; | ||
20 | +import { IAliasController, IStateController } from '@core/api/widget-api.models'; | ||
21 | + | ||
22 | +export declare type DashboardPageScope = 'tenant' | 'customer'; | ||
23 | + | ||
24 | +export interface DashboardContext { | ||
25 | + state: string; | ||
26 | + dashboard: Dashboard; | ||
27 | + dashboardTimewindow: Timewindow; | ||
28 | + aliasController: IAliasController; | ||
29 | + stateController: IStateController; | ||
30 | +} | ||
31 | + | ||
32 | +export interface DashboardPageLayoutContext { | ||
33 | + id: DashboardLayoutId; | ||
34 | + widgets: Array<Widget>; | ||
35 | + widgetLayouts: {[id: string]: WidgetLayout}; | ||
36 | + gridSettings: GridSettings; | ||
37 | + ignoreLoading: boolean; | ||
38 | +} | ||
39 | + | ||
40 | +export interface DashboardPageLayout { | ||
41 | + show: boolean; | ||
42 | + layoutCtx: DashboardPageLayoutContext; | ||
43 | +} | ||
44 | + | ||
45 | +export interface DashboardPageLayouts { | ||
46 | + main: DashboardPageLayout; | ||
47 | + right: DashboardPageLayout; | ||
48 | +} |
@@ -14,12 +14,39 @@ | @@ -14,12 +14,39 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import {NgModule} from '@angular/core'; | ||
18 | -import {RouterModule, Routes} from '@angular/router'; | 17 | +import { Injectable, NgModule } from '@angular/core'; |
18 | +import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; | ||
19 | 19 | ||
20 | import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; | 20 | import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; |
21 | import {Authority} from '@shared/models/authority.enum'; | 21 | import {Authority} from '@shared/models/authority.enum'; |
22 | import {DashboardsTableConfigResolver} from './dashboards-table-config.resolver'; | 22 | import {DashboardsTableConfigResolver} from './dashboards-table-config.resolver'; |
23 | +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; | ||
24 | +import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; | ||
25 | +import { widgetTypesBreadcumbLabelFunction } from '@home/pages/widget/widget-library-routing.module'; | ||
26 | +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | ||
27 | +import { WidgetService } from '@core/http/widget.service'; | ||
28 | +import { Observable } from 'rxjs'; | ||
29 | +import { Dashboard } from '@app/shared/models/dashboard.models'; | ||
30 | +import { DashboardService } from '@core/http/dashboard.service'; | ||
31 | +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; | ||
32 | +import { map } from 'rxjs/operators'; | ||
33 | + | ||
34 | +@Injectable() | ||
35 | +export class DashboardResolver implements Resolve<Dashboard> { | ||
36 | + | ||
37 | + constructor(private dashboardService: DashboardService, | ||
38 | + private dashboardUtils: DashboardUtilsService) { | ||
39 | + } | ||
40 | + | ||
41 | + resolve(route: ActivatedRouteSnapshot): Observable<Dashboard> { | ||
42 | + const dashboardId = route.params.dashboardId; | ||
43 | + return this.dashboardService.getDashboard(dashboardId).pipe( | ||
44 | + map((dashboard) => this.dashboardUtils.validateAndUpdateDashboard(dashboard)) | ||
45 | + ); | ||
46 | + } | ||
47 | +} | ||
48 | + | ||
49 | +export const dashboardBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => component.dashboard.title); | ||
23 | 50 | ||
24 | const routes: Routes = [ | 51 | const routes: Routes = [ |
25 | { | 52 | { |
@@ -42,6 +69,22 @@ const routes: Routes = [ | @@ -42,6 +69,22 @@ const routes: Routes = [ | ||
42 | resolve: { | 69 | resolve: { |
43 | entitiesTableConfig: DashboardsTableConfigResolver | 70 | entitiesTableConfig: DashboardsTableConfigResolver |
44 | } | 71 | } |
72 | + }, | ||
73 | + { | ||
74 | + path: ':dashboardId', | ||
75 | + component: DashboardPageComponent, | ||
76 | + data: { | ||
77 | + breadcrumb: { | ||
78 | + labelFunction: dashboardBreadcumbLabelFunction, | ||
79 | + icon: 'dashboard' | ||
80 | + } as BreadCrumbConfig, | ||
81 | + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], | ||
82 | + title: 'dashboard.dashboard', | ||
83 | + widgetEditMode: false | ||
84 | + }, | ||
85 | + resolve: { | ||
86 | + dashboard: DashboardResolver | ||
87 | + } | ||
45 | } | 88 | } |
46 | ] | 89 | ] |
47 | } | 90 | } |
@@ -51,7 +94,8 @@ const routes: Routes = [ | @@ -51,7 +94,8 @@ const routes: Routes = [ | ||
51 | imports: [RouterModule.forChild(routes)], | 94 | imports: [RouterModule.forChild(routes)], |
52 | exports: [RouterModule], | 95 | exports: [RouterModule], |
53 | providers: [ | 96 | providers: [ |
54 | - DashboardsTableConfigResolver | 97 | + DashboardsTableConfigResolver, |
98 | + DashboardResolver | ||
55 | ] | 99 | ] |
56 | }) | 100 | }) |
57 | export class DashboardRoutingModule { } | 101 | export class DashboardRoutingModule { } |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<mat-fab-toolbar [isOpen]="toolbarOpened" | ||
19 | + color="primary" | ||
20 | + direction="left" | ||
21 | + [ngClass]="{'is-fullscreen': forceFullscreen, 'mat-elevation-z1': forceFullscreen && toolbarOpened }"> | ||
22 | + <mat-fab-trigger> | ||
23 | + <button mat-button mat-icon-button mat-fab color="primary" | ||
24 | + [matTooltip]="!toolbarOpened ? ('dashboard.open-toolbar' | translate) : ''" | ||
25 | + matTooltipPosition="below" | ||
26 | + (click)="onTriggerClick()"> | ||
27 | + <mat-icon>more_horiz</mat-icon> | ||
28 | + </button> | ||
29 | + </mat-fab-trigger> | ||
30 | + <mat-toolbar color="primary"> | ||
31 | + <mat-fab-actions class="mat-toolbar-tools"> | ||
32 | + <ng-content></ng-content> | ||
33 | + </mat-fab-actions> | ||
34 | + </mat-toolbar> | ||
35 | +</mat-fab-toolbar> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +@import "../../../../../scss/constants"; | ||
17 | + | ||
18 | +$toolbar-height: 50px !default; | ||
19 | +$fullscreen-toolbar-height: 64px !default; | ||
20 | +$mobile-toolbar-height: 80px !default; | ||
21 | +$half-mobile-toolbar-height: 40px !default; | ||
22 | +$mobile-toolbar-height-total: 84px !default; | ||
23 | + | ||
24 | +tb-dashboard-toolbar { | ||
25 | + mat-fab-toolbar { | ||
26 | + mat-fab-trigger { | ||
27 | + .mat-button { | ||
28 | + &.mat-fab { | ||
29 | + width: 36px; | ||
30 | + height: 36px; | ||
31 | + margin: 4px 0 0 4px; | ||
32 | + line-height: 36px; | ||
33 | + .mat-button-wrapper { | ||
34 | + width: 36px; | ||
35 | + height: 36px; | ||
36 | + padding: 0 6px; | ||
37 | + box-sizing: border-box; | ||
38 | + } | ||
39 | + mat-icon { | ||
40 | + display: block; | ||
41 | + position: absolute; | ||
42 | + top: 25%; | ||
43 | + width: 18px; | ||
44 | + min-width: 18px; | ||
45 | + height: 18px; | ||
46 | + min-height: 18px; | ||
47 | + margin: 0; | ||
48 | + line-height: 18px; | ||
49 | + } | ||
50 | + } | ||
51 | + } | ||
52 | + } | ||
53 | + | ||
54 | + &.is-fullscreen { | ||
55 | + | ||
56 | + .mat-fab-toolbar-wrapper { | ||
57 | + height: $mobile-toolbar-height-total; | ||
58 | + | ||
59 | + @media #{$mat-gt-sm} { | ||
60 | + height: $fullscreen-toolbar-height; | ||
61 | + } | ||
62 | + | ||
63 | + mat-toolbar { | ||
64 | + height: $mobile-toolbar-height; | ||
65 | + min-height: $mobile-toolbar-height; | ||
66 | + .mat-toolbar-tools { | ||
67 | + height: $mobile-toolbar-height; | ||
68 | + min-height: $mobile-toolbar-height; | ||
69 | + } | ||
70 | + @media #{$mat-gt-sm} { | ||
71 | + height: $fullscreen-toolbar-height; | ||
72 | + min-height: $fullscreen-toolbar-height; | ||
73 | + .mat-toolbar-tools { | ||
74 | + height: $fullscreen-toolbar-height; | ||
75 | + min-height: $fullscreen-toolbar-height; | ||
76 | + } | ||
77 | + } | ||
78 | + } | ||
79 | + } | ||
80 | + } | ||
81 | + | ||
82 | + .mat-fab-toolbar-wrapper { | ||
83 | + height: $mobile-toolbar-height-total; | ||
84 | + | ||
85 | + @media #{$mat-gt-sm} { | ||
86 | + height: $toolbar-height; | ||
87 | + } | ||
88 | + | ||
89 | + mat-toolbar { | ||
90 | + height: $mobile-toolbar-height; | ||
91 | + min-height: $mobile-toolbar-height; | ||
92 | + .mat-toolbar-tools { | ||
93 | + height: $mobile-toolbar-height; | ||
94 | + min-height: $mobile-toolbar-height; | ||
95 | + } | ||
96 | + | ||
97 | + @media #{$mat-gt-sm} { | ||
98 | + height: $toolbar-height; | ||
99 | + min-height: $toolbar-height; | ||
100 | + .mat-toolbar-tools { | ||
101 | + height: $toolbar-height; | ||
102 | + min-height: $toolbar-height; | ||
103 | + } | ||
104 | + } | ||
105 | + | ||
106 | + mat-fab-actions { | ||
107 | + margin-top: 0; | ||
108 | + font-size: 16px; | ||
109 | + | ||
110 | + @media #{$mat-lt-md} { | ||
111 | + height: $mobile-toolbar-height; | ||
112 | + max-height: $mobile-toolbar-height; | ||
113 | + } | ||
114 | + | ||
115 | + .close-action { | ||
116 | + margin-right: -18px; | ||
117 | + } | ||
118 | + | ||
119 | + .mat-fab-action-item { | ||
120 | + width: 100%; | ||
121 | + height: $mobile-toolbar-height; | ||
122 | + | ||
123 | + @media #{$mat-gt-sm} { | ||
124 | + height: 46px; | ||
125 | + } | ||
126 | + | ||
127 | + .tb-dashboard-action-panels { | ||
128 | + height: $mobile-toolbar-height; | ||
129 | + | ||
130 | + @media #{$mat-gt-sm} { | ||
131 | + height: 46px; | ||
132 | + } | ||
133 | + | ||
134 | + .tb-dashboard-action-panel { | ||
135 | + min-width: 0; | ||
136 | + height: $half-mobile-toolbar-height; | ||
137 | + | ||
138 | + @media #{$mat-lt-md} { | ||
139 | + mat-menu{ | ||
140 | + margin: 0; | ||
141 | + } | ||
142 | + } | ||
143 | + | ||
144 | + @media #{$mat-gt-sm} { | ||
145 | + height: 46px; | ||
146 | + } | ||
147 | + | ||
148 | + > div { | ||
149 | + height: $half-mobile-toolbar-height; | ||
150 | + | ||
151 | + @media #{$mat-gt-sm} { | ||
152 | + height: 46px; | ||
153 | + } | ||
154 | + } | ||
155 | + | ||
156 | + mat-select { | ||
157 | + margin: 0; | ||
158 | + pointer-events: all; | ||
159 | + } | ||
160 | + | ||
161 | + tb-states-component { | ||
162 | + pointer-events: all; | ||
163 | + } | ||
164 | + | ||
165 | + button.mat-icon-button:not(.tb-mat-32) { | ||
166 | + min-width: 40px; | ||
167 | + | ||
168 | + @media #{$mat-lt-md} { | ||
169 | + min-width: 28px; | ||
170 | + padding: 2px; | ||
171 | + margin: 0; | ||
172 | + .mat-button-wrapper { | ||
173 | + display: block; | ||
174 | + line-height: 24px; | ||
175 | + } | ||
176 | + } | ||
177 | + } | ||
178 | + } | ||
179 | + } | ||
180 | + } | ||
181 | + } | ||
182 | + } | ||
183 | + } | ||
184 | + } | ||
185 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, OnDestroy, OnInit, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core'; | ||
18 | +import { PageComponent } from '@shared/components/page.component'; | ||
19 | + | ||
20 | +@Component({ | ||
21 | + selector: 'tb-dashboard-toolbar', | ||
22 | + templateUrl: './dashboard-toolbar.component.html', | ||
23 | + styleUrls: ['./dashboard-toolbar.component.scss'], | ||
24 | + encapsulation: ViewEncapsulation.None | ||
25 | +}) | ||
26 | +export class DashboardToolbarComponent implements OnInit { | ||
27 | + | ||
28 | + @Input() | ||
29 | + toolbarOpened: boolean; | ||
30 | + | ||
31 | + @Input() | ||
32 | + forceFullscreen: boolean; | ||
33 | + | ||
34 | + @Output() | ||
35 | + triggerClick = new EventEmitter<void>(); | ||
36 | + | ||
37 | + constructor() { | ||
38 | + } | ||
39 | + | ||
40 | + ngOnInit(): void { | ||
41 | + } | ||
42 | + | ||
43 | + onTriggerClick() { | ||
44 | + this.triggerClick.emit(); | ||
45 | + } | ||
46 | + | ||
47 | +} |
@@ -14,16 +14,18 @@ | @@ -14,16 +14,18 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import {NgModule} from '@angular/core'; | ||
18 | -import {CommonModule} from '@angular/common'; | ||
19 | -import {SharedModule} from '@shared/shared.module'; | ||
20 | -import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; | ||
21 | -import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component'; | ||
22 | -import {ManageDashboardCustomersDialogComponent} from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; | ||
23 | -import {DashboardRoutingModule} from './dashboard-routing.module'; | ||
24 | -import {MakeDashboardPublicDialogComponent} from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; | ||
25 | -import {HomeComponentsModule} from '@modules/home/components/home-components.module'; | 17 | +import { NgModule } from '@angular/core'; |
18 | +import { CommonModule } from '@angular/common'; | ||
19 | +import { SharedModule } from '@shared/shared.module'; | ||
20 | +import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; | ||
21 | +import { DashboardFormComponent } from '@modules/home/pages/dashboard/dashboard-form.component'; | ||
22 | +import { ManageDashboardCustomersDialogComponent } from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; | ||
23 | +import { DashboardRoutingModule } from './dashboard-routing.module'; | ||
24 | +import { MakeDashboardPublicDialogComponent } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; | ||
25 | +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; | ||
26 | import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component'; | 26 | import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component'; |
27 | +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; | ||
28 | +import { DashboardToolbarComponent } from './dashboard-toolbar.component'; | ||
27 | 29 | ||
28 | @NgModule({ | 30 | @NgModule({ |
29 | entryComponents: [ | 31 | entryComponents: [ |
@@ -36,7 +38,9 @@ import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.com | @@ -36,7 +38,9 @@ import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.com | ||
36 | DashboardFormComponent, | 38 | DashboardFormComponent, |
37 | DashboardTabsComponent, | 39 | DashboardTabsComponent, |
38 | ManageDashboardCustomersDialogComponent, | 40 | ManageDashboardCustomersDialogComponent, |
39 | - MakeDashboardPublicDialogComponent | 41 | + MakeDashboardPublicDialogComponent, |
42 | + DashboardToolbarComponent, | ||
43 | + DashboardPageComponent | ||
40 | ], | 44 | ], |
41 | imports: [ | 45 | imports: [ |
42 | CommonModule, | 46 | CommonModule, |
@@ -303,9 +303,11 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig< | @@ -303,9 +303,11 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig< | ||
303 | if ($event) { | 303 | if ($event) { |
304 | $event.stopPropagation(); | 304 | $event.stopPropagation(); |
305 | } | 305 | } |
306 | - // TODO: | ||
307 | - // this.router.navigateByUrl(`customers/${customer.id.id}/users`); | ||
308 | - this.dialogService.todo(); | 306 | + if (this.config.componentsData.dashboardScope === 'customer') { |
307 | + this.router.navigateByUrl(`customers/${this.config.componentsData.customerId}/dashboards/${dashboard.id.id}`); | ||
308 | + } else { | ||
309 | + this.router.navigateByUrl(`dashboards/${dashboard.id.id}`); | ||
310 | + } | ||
309 | } | 311 | } |
310 | 312 | ||
311 | importDashboard($event: Event) { | 313 | importDashboard($event: Event) { |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<form style="width: 800px;"> | ||
19 | + <mat-toolbar fxLayout="row" color="primary"> | ||
20 | + <h2 translate>widget.select-widget-type</h2> | ||
21 | + <span fxFlex></span> | ||
22 | + <button mat-button mat-icon-button | ||
23 | + (click)="cancel()" | ||
24 | + type="button"> | ||
25 | + <mat-icon class="material-icons">close</mat-icon> | ||
26 | + </button> | ||
27 | + </mat-toolbar> | ||
28 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
29 | + </mat-progress-bar> | ||
30 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
31 | + <div mat-dialog-content> | ||
32 | + <fieldset [disabled]="(isLoading$ | async)"> | ||
33 | + <div fxLayout="column" fxLayoutGap="16px" fxLayout.gt-sm="row" fxLayoutAlign="center center"> | ||
34 | + <button *ngFor="let type of allWidgetTypes;" class="tb-card-button" mat-button mat-raised-button color="primary" | ||
35 | + type="button" | ||
36 | + (click)="typeSelected(type)"> | ||
37 | + <mat-icon *ngIf="!widgetTypesDataMap.get(type).isMdiIcon; else mdiIconBlock" class="tb-mat-96"> | ||
38 | + {{ widgetTypesDataMap.get(type).icon }} | ||
39 | + </mat-icon> | ||
40 | + <ng-template #mdiIconBlock> | ||
41 | + <mat-icon class="tb-mat-96" [svgIcon]="widgetTypesDataMap.get(type).icon"> | ||
42 | + </mat-icon> | ||
43 | + </ng-template> | ||
44 | + <span translate>{{ widgetTypesDataMap.get(type).name }}</span> | ||
45 | + </button> | ||
46 | + </div> | ||
47 | + </fieldset> | ||
48 | + </div> | ||
49 | + <div mat-dialog-actions fxLayout="row"> | ||
50 | + <span fxFlex></span> | ||
51 | + <button mat-button color="primary" | ||
52 | + style="margin-right: 20px;" | ||
53 | + type="button" | ||
54 | + [disabled]="(isLoading$ | async)" | ||
55 | + (click)="cancel()" cdkFocusInitial> | ||
56 | + {{ 'action.cancel' | translate }} | ||
57 | + </button> | ||
58 | + </div> | ||
59 | +</form> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +:host ::ng-deep { | ||
18 | + button.tb-card-button { | ||
19 | + width: 100%; | ||
20 | + height: 100%; | ||
21 | + max-width: 240px; | ||
22 | + .mat-button-wrapper { | ||
23 | + width: 100%; | ||
24 | + height: 100%; | ||
25 | + display: flex; | ||
26 | + flex-direction: column; | ||
27 | + align-items: center; | ||
28 | + mat-icon { | ||
29 | + margin: auto; | ||
30 | + } | ||
31 | + span { | ||
32 | + height: 18px; | ||
33 | + min-height: 18px; | ||
34 | + max-height: 18px; | ||
35 | + padding: 0 0 20px 0; | ||
36 | + margin: auto; | ||
37 | + font-size: 18px; | ||
38 | + font-weight: 400; | ||
39 | + line-height: 18px; | ||
40 | + white-space: normal; | ||
41 | + } | ||
42 | + } | ||
43 | + &.mat-raised-button.mat-primary { | ||
44 | + .mat-ripple-element { | ||
45 | + opacity: 0.3; | ||
46 | + background-color: rgba(255, 255, 255, 0.3); | ||
47 | + } | ||
48 | + } | ||
49 | + } | ||
50 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component } from '@angular/core'; | ||
18 | +import { MatDialogRef } from '@angular/material'; | ||
19 | +import { Store } from '@ngrx/store'; | ||
20 | +import { AppState } from '@core/core.state'; | ||
21 | +import { DialogComponent } from '@shared/components/dialog.component'; | ||
22 | +import { Router } from '@angular/router'; | ||
23 | +import { widgetType, widgetTypesData } from '@shared/models/widget.models'; | ||
24 | + | ||
25 | +@Component({ | ||
26 | + selector: 'tb-select-widget-type-dialog', | ||
27 | + templateUrl: './select-widget-type-dialog.component.html', | ||
28 | + styleUrls: ['./select-widget-type-dialog.component.scss'] | ||
29 | +}) | ||
30 | +export class SelectWidgetTypeDialogComponent extends | ||
31 | + DialogComponent<SelectWidgetTypeDialogComponent, widgetType> { | ||
32 | + | ||
33 | + widgetTypes = widgetType; | ||
34 | + | ||
35 | + allWidgetTypes = Object.keys(widgetType); | ||
36 | + | ||
37 | + widgetTypesDataMap = widgetTypesData; | ||
38 | + | ||
39 | + constructor(protected store: Store<AppState>, | ||
40 | + protected router: Router, | ||
41 | + public dialogRef: MatDialogRef<SelectWidgetTypeDialogComponent, widgetType>) { | ||
42 | + super(store, router, dialogRef); | ||
43 | + } | ||
44 | + | ||
45 | + cancel(): void { | ||
46 | + this.dialogRef.close(null); | ||
47 | + } | ||
48 | + | ||
49 | + typeSelected(type: widgetType) { | ||
50 | + this.dialogRef.close(type); | ||
51 | + } | ||
52 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<hotkeys-cheatsheet></hotkeys-cheatsheet> | ||
19 | +<div fxFlex fxLayout="column"> | ||
20 | + <div fxFlex fxLayout="column" tb-fullscreen [fullscreen]="fullscreen"> | ||
21 | + <mat-toolbar class="mat-elevation-z1 tb-edit-toolbar mat-hue-3" fxLayoutGap="16px"> | ||
22 | + <mat-form-field floatLabel="always" class="tb-widget-title"> | ||
23 | + <mat-label></mat-label> | ||
24 | + <input [disabled]="isReadOnly" matInput [(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true" | ||
25 | + placeholder="{{ 'widget.title' | translate }}"/> | ||
26 | + </mat-form-field> | ||
27 | + <mat-form-field> | ||
28 | + <mat-select [disabled]="isReadOnly" matInput placeholder="{{ 'widget.type' | translate }}" | ||
29 | + [(ngModel)]="widget.type" (ngModelChange)="widetTypeChanged()"> | ||
30 | + <mat-option *ngFor="let type of allWidgetTypes" [value]="type"> | ||
31 | + {{ widgetTypesDataMap.get(type).name | translate }} | ||
32 | + </mat-option> | ||
33 | + </mat-select> | ||
34 | + </mat-form-field> | ||
35 | + <span fxFlex></span> | ||
36 | + <button mat-button fxHide.xs fxHide.sm [disabled]="!iframeWidgetEditModeInited" | ||
37 | + (click)="applyWidgetScript()" | ||
38 | + matTooltip="{{ 'widget.run' | translate }} (CTRL + Return)" | ||
39 | + matTooltipPosition="below"> | ||
40 | + <mat-icon>play_arrow</mat-icon> | ||
41 | + <span translate>action.run</span> | ||
42 | + </button> | ||
43 | + <button mat-button mat-raised-button | ||
44 | + fxHide.xs fxHide.sm [disabled]="(isLoading$ | async) || undoDisabled()" | ||
45 | + (click)="undoWidget()" | ||
46 | + matTooltip="{{ 'widget.undo' | translate }} (CTRL + Q)" | ||
47 | + matTooltipPosition="below"> | ||
48 | + <mat-icon>undo</mat-icon> | ||
49 | + <span translate>action.undo</span> | ||
50 | + </button> | ||
51 | + <button *ngIf="!isReadOnly" mat-button mat-raised-button | ||
52 | + fxHide.xs fxHide.sm [disabled]="(isLoading$ | async) || saveDisabled()" | ||
53 | + (click)="saveWidget()" | ||
54 | + [tb-circular-progress]="saveWidgetPending" | ||
55 | + matTooltip="{{ 'widget.save' | translate }} (CTRL + S)" | ||
56 | + matTooltipPosition="below"> | ||
57 | + <mat-icon>save</mat-icon> | ||
58 | + <span translate>action.save</span> | ||
59 | + </button> | ||
60 | + <button mat-button mat-raised-button | ||
61 | + fxHide.xs fxHide.sm [disabled]="(isLoading$ | async) || saveAsDisabled()" | ||
62 | + (click)="saveWidgetAs()" | ||
63 | + [tb-circular-progress]="saveWidgetAsPending" | ||
64 | + matTooltip="{{ 'widget.saveAs' | translate }} (Shift + CTRL + S)" | ||
65 | + matTooltipPosition="below"> | ||
66 | + <mat-icon>save</mat-icon> | ||
67 | + <span translate>action.saveAs</span> | ||
68 | + </button> | ||
69 | + <button mat-button | ||
70 | + fxHide.xs fxHide.sm | ||
71 | + (click)="fullscreen = !fullscreen" | ||
72 | + matTooltip="{{ 'widget.toggle-fullscreen' | translate }} (Shift + CTRL + F)" | ||
73 | + matTooltipPosition="below"> | ||
74 | + <mat-icon>{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
75 | + <span translate>widget.toggle-fullscreen</span> | ||
76 | + </button> | ||
77 | + <button mat-button mat-icon-button fxHide.gt-sm | ||
78 | + [matMenuTriggerFor]="widgetEditMenu"> | ||
79 | + <mat-icon>more_vert</mat-icon> | ||
80 | + </button> | ||
81 | + <mat-menu #widgetEditMenu="matMenu" xPosition="before"> | ||
82 | + <button mat-menu-item | ||
83 | + [disabled]="!iframeWidgetEditModeInited" | ||
84 | + (click)="applyWidgetScript()"> | ||
85 | + <mat-icon>play_arrow</mat-icon> | ||
86 | + <span translate>action.run</span> | ||
87 | + </button> | ||
88 | + <button mat-menu-item | ||
89 | + [disabled]="(isLoading$ | async) || undoDisabled()" | ||
90 | + (click)="undoWidget()"> | ||
91 | + <mat-icon>undo</mat-icon> | ||
92 | + <span translate>action.undo</span> | ||
93 | + </button> | ||
94 | + <button *ngIf="!isReadOnly" mat-menu-item | ||
95 | + [disabled]="(isLoading$ | async) || saveDisabled()" | ||
96 | + (click)="saveWidget()"> | ||
97 | + <mat-icon>save</mat-icon> | ||
98 | + <span translate>action.save</span> | ||
99 | + </button> | ||
100 | + <button mat-menu-item | ||
101 | + [disabled]="(isLoading$ | async) || saveAsDisabled()" | ||
102 | + (click)="saveWidgetAs()"> | ||
103 | + <mat-icon>save</mat-icon> | ||
104 | + <span translate>action.saveAs</span> | ||
105 | + </button> | ||
106 | + </mat-menu> | ||
107 | + </mat-toolbar> | ||
108 | + <div fxFlex style="position: relative;"> | ||
109 | + <div class="tb-editor tb-absolute-fill"> | ||
110 | + <div #topPanel class="tb-split tb-split-vertical"> | ||
111 | + <div #topLeftPanel class="tb-split tb-content"> | ||
112 | + <mat-tab-group selectedIndex="1" dynamicHeight="true" style="width: 100%; height: 100%;"> | ||
113 | + <mat-tab label="{{ 'widget.resources' | translate }}" style="width: 100%; height: 100%;"> | ||
114 | + <div class="tb-resize-container" style="background-color: #fff;"> | ||
115 | + <div class="mat-padding"> | ||
116 | + <div fxFlex fxLayout="row" style="max-height: 40px;" | ||
117 | + fxLayoutAlign="start center" | ||
118 | + *ngFor="let resource of widget.resources; let i = index" > | ||
119 | + <mat-form-field fxFlex class="mat-block resource-field" floatLabel="never" | ||
120 | + style="margin: 10px 0px 0px 0px; max-height: 40px;"> | ||
121 | + <input required matInput [(ngModel)]="resource.url" | ||
122 | + (ngModelChange)="isDirty = true" | ||
123 | + placeholder="{{ 'widget.resource-url' | translate }}"/> | ||
124 | + </mat-form-field> | ||
125 | + <button mat-button mat-icon-button color="primary" | ||
126 | + [disabled]="isLoading$ | async" | ||
127 | + (click)="removeResource(i)" | ||
128 | + matTooltip="{{'widget.remove-resource' | translate}}" | ||
129 | + matTooltipPosition="above"> | ||
130 | + <mat-icon>close</mat-icon> | ||
131 | + </button> | ||
132 | + </div> | ||
133 | + <div style="margin-top: 6px;"> | ||
134 | + <button mat-button mat-raised-button color="primary" | ||
135 | + [disabled]="isLoading$ | async" | ||
136 | + (click)="addResource()" | ||
137 | + matTooltip="{{'widget.add-resource' | translate}}" | ||
138 | + matTooltipPosition="above"> | ||
139 | + <span translate>action.add</span> | ||
140 | + </button> | ||
141 | + </div> | ||
142 | + </div> | ||
143 | + </div> | ||
144 | + </mat-tab> | ||
145 | + <mat-tab label="{{ 'widget.html' | translate }}" style="width: 100%; height: 100%;"> | ||
146 | + <div class="tb-resize-container" tb-fullscreen [fullscreen]="htmlFullscreen"> | ||
147 | + <div class="tb-editor-area-title-panel"> | ||
148 | + <button mat-button (click)="beautifyHtml()"> | ||
149 | + {{ 'widget.tidy' | translate }} | ||
150 | + </button> | ||
151 | + <button mat-button mat-icon-button class="tb-mat-32" | ||
152 | + (click)="htmlFullscreen = !htmlFullscreen" | ||
153 | + matTooltip="{{(htmlFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
154 | + matTooltipPosition="above"> | ||
155 | + <mat-icon>{{ htmlFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
156 | + </button> | ||
157 | + </div> | ||
158 | + <div #htmlInput></div> | ||
159 | + </div> | ||
160 | + </mat-tab> | ||
161 | + <mat-tab label="{{ 'widget.css' | translate }}" style="width: 100%; height: 100%;"> | ||
162 | + <div class="tb-resize-container" tb-fullscreen [fullscreen]="cssFullscreen"> | ||
163 | + <div class="tb-editor-area-title-panel"> | ||
164 | + <button mat-button (click)="beautifyCss()"> | ||
165 | + {{ 'widget.tidy' | translate }} | ||
166 | + </button> | ||
167 | + <button mat-button mat-icon-button class="tb-mat-32" | ||
168 | + (click)="cssFullscreen = !cssFullscreen" | ||
169 | + matTooltip="{{(cssFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
170 | + matTooltipPosition="above"> | ||
171 | + <mat-icon>{{ cssFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
172 | + </button> | ||
173 | + </div> | ||
174 | + <div #cssInput></div> | ||
175 | + </div> | ||
176 | + </mat-tab> | ||
177 | + </mat-tab-group> | ||
178 | + </div> | ||
179 | + <div #topRightPanel class="tb-split tb-content"> | ||
180 | + <mat-tab-group dynamicHeight="true" style="width: 100%; height: 100%;"> | ||
181 | + <mat-tab label="{{ 'widget.settings-schema' | translate }}" style="width: 100%; height: 100%;"> | ||
182 | + <div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonSettingsFullscreen"> | ||
183 | + <div class="tb-editor-area-title-panel"> | ||
184 | + <button mat-button (click)="beautifyJson()"> | ||
185 | + {{ 'widget.tidy' | translate }} | ||
186 | + </button> | ||
187 | + <button mat-button mat-icon-button class="tb-mat-32" | ||
188 | + (click)="jsonSettingsFullscreen = !jsonSettingsFullscreen" | ||
189 | + matTooltip="{{(jsonSettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
190 | + matTooltipPosition="above"> | ||
191 | + <mat-icon>{{ jsonSettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
192 | + </button> | ||
193 | + </div> | ||
194 | + <div #settingsJsonInput></div> | ||
195 | + </div> | ||
196 | + </mat-tab> | ||
197 | + <mat-tab label="{{ 'widget.datakey-settings-schema' | translate }}" style="width: 100%; height: 100%;"> | ||
198 | + <div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonDataKeySettingsFullscreen"> | ||
199 | + <div class="tb-editor-area-title-panel"> | ||
200 | + <button mat-button (click)="beautifyDataKeyJson()"> | ||
201 | + {{ 'widget.tidy' | translate }} | ||
202 | + </button> | ||
203 | + <button mat-button mat-icon-button class="tb-mat-32" | ||
204 | + (click)="jsonDataKeySettingsFullscreen = !jsonDataKeySettingsFullscreen" | ||
205 | + matTooltip="{{(jsonDataKeySettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
206 | + matTooltipPosition="above"> | ||
207 | + <mat-icon>{{ jsonDataKeySettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
208 | + </button> | ||
209 | + </div> | ||
210 | + <div #dataKeySettingsJsonInput></div> | ||
211 | + </div> | ||
212 | + </mat-tab> | ||
213 | + </mat-tab-group> | ||
214 | + </div> | ||
215 | + </div> | ||
216 | + <div #bottomPanel class="tb-split tb-split-vertical"> | ||
217 | + <div #javascriptPanel class="tb-split tb-content" tb-toast toastTarget="javascriptPanel"> | ||
218 | + <div class="tb-resize-container" tb-fullscreen [fullscreen]="javascriptFullscreen"> | ||
219 | + <div class="tb-editor-area-title-panel"> | ||
220 | + <label translate>widget.javascript</label> | ||
221 | + <button mat-button (click)="beautifyJs()"> | ||
222 | + {{ 'widget.tidy' | translate }} | ||
223 | + </button> | ||
224 | + <button mat-button mat-icon-button class="tb-mat-32" | ||
225 | + (click)="javascriptFullscreen = !javascriptFullscreen" | ||
226 | + matTooltip="{{(javascriptFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
227 | + matTooltipPosition="above"> | ||
228 | + <mat-icon>{{ javascriptFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
229 | + </button> | ||
230 | + </div> | ||
231 | + <div #javascriptInput></div> | ||
232 | + </div> | ||
233 | + </div> | ||
234 | + <div #framePanel class="tb-split tb-content" style="overflow-y: hidden; position: relative;"> | ||
235 | + <div class="mat-content tb-progress-cover" fxFlex fxLayout="column" fxLayoutAlign="center center" | ||
236 | + *ngIf="!iframeWidgetEditModeInited"> | ||
237 | + <mat-spinner diameter="100" mode="indeterminate" color="warn"></mat-spinner> | ||
238 | + </div> | ||
239 | + <div tb-fullscreen [fullscreen]="iFrameFullscreen" style="width: 100%; height: 100%;"> | ||
240 | + <iframe #widgetIFrame frameborder="0" height="100%" width="100%"></iframe> | ||
241 | + <button mat-button mat-icon-button | ||
242 | + class="tb-fullscreen-button-style" | ||
243 | + style="position: absolute; top: 10px; left: 10px; bottom: initial;" | ||
244 | + (click)="iFrameFullscreen = !iFrameFullscreen" | ||
245 | + matTooltip="{{(iFrameFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | ||
246 | + matTooltipPosition="above"> | ||
247 | + <mat-icon>{{ iFrameFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | ||
248 | + </button> | ||
249 | + </div> | ||
250 | + </div> | ||
251 | + </div> | ||
252 | + </div> | ||
253 | + </div> | ||
254 | + </div> | ||
255 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +$edit-toolbar-height: 40px !default; | ||
18 | + | ||
19 | +tb-widget-editor { | ||
20 | + flex: 1; | ||
21 | + display: flex; | ||
22 | + flex-direction: column; | ||
23 | +} | ||
24 | + | ||
25 | + | ||
26 | +.tb-editor { | ||
27 | + .tb-split { | ||
28 | + box-sizing: border-box; | ||
29 | + overflow-x: hidden; | ||
30 | + overflow-y: auto; | ||
31 | + } | ||
32 | + | ||
33 | + mat-form-field.resource-field { | ||
34 | + max-height: 40px; | ||
35 | + margin: 10px 0px 0px 0px; | ||
36 | + .mat-form-field-wrapper { | ||
37 | + padding-bottom: 0; | ||
38 | + .mat-form-field-flex { | ||
39 | + max-height: 40px; | ||
40 | + .mat-form-field-infix { | ||
41 | + border: 0; | ||
42 | + } | ||
43 | + } | ||
44 | + .mat-form-field-underline { | ||
45 | + bottom: 0; | ||
46 | + } | ||
47 | + } | ||
48 | + } | ||
49 | + | ||
50 | + .ace_editor { | ||
51 | + font-size: 14px !important; | ||
52 | + } | ||
53 | + | ||
54 | + .tb-content { | ||
55 | + border: 1px solid #c0c0c0; | ||
56 | + } | ||
57 | + | ||
58 | + .gutter { | ||
59 | + background-color: transparent; | ||
60 | + | ||
61 | + background-repeat: no-repeat; | ||
62 | + background-position: 50%; | ||
63 | + | ||
64 | + &.gutter-horizontal { | ||
65 | + cursor: col-resize; | ||
66 | + background-image: url("../../../../../assets/split.js/grips/vertical.png"); | ||
67 | + } | ||
68 | + | ||
69 | + &.gutter-vertical { | ||
70 | + cursor: row-resize; | ||
71 | + background-image: url("../../../../../assets/split.js/grips/horizontal.png"); | ||
72 | + } | ||
73 | + } | ||
74 | + | ||
75 | + .tb-split.tb-split-horizontal, | ||
76 | + .gutter.gutter-horizontal { | ||
77 | + float: left; | ||
78 | + height: 100%; | ||
79 | + } | ||
80 | + | ||
81 | + .tb-split.tb-split-vertical { | ||
82 | + display: flex; | ||
83 | + | ||
84 | + .tb-split.tb-content { | ||
85 | + height: 100%; | ||
86 | + } | ||
87 | + } | ||
88 | +} | ||
89 | + | ||
90 | +.tb-split-vertical { | ||
91 | + mat-tab-group { | ||
92 | + .mat-tab-body-wrapper { | ||
93 | + height: calc(100% - 49px); | ||
94 | + | ||
95 | + mat-tab-body { | ||
96 | + height: 100%; | ||
97 | + | ||
98 | + & > div { | ||
99 | + height: 100%; | ||
100 | + } | ||
101 | + } | ||
102 | + } | ||
103 | + } | ||
104 | +} | ||
105 | + | ||
106 | +div.tb-editor-area-title-panel { | ||
107 | + position: absolute; | ||
108 | + top: 5px; | ||
109 | + right: 20px; | ||
110 | + z-index: 5; | ||
111 | + font-size: .8rem; | ||
112 | + font-weight: 500; | ||
113 | + | ||
114 | + label { | ||
115 | + padding: 4px; | ||
116 | + color: #00acc1; | ||
117 | + text-transform: uppercase; | ||
118 | + background: rgba(220, 220, 220, .35); | ||
119 | + border-radius: 5px; | ||
120 | + &:not(:last-child) { | ||
121 | + margin-right: 4px; | ||
122 | + } | ||
123 | + } | ||
124 | + | ||
125 | + button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 { | ||
126 | + align-items: center; | ||
127 | + vertical-align: middle; | ||
128 | + min-width: 32px; | ||
129 | + min-height: 15px; | ||
130 | + padding: 4px; | ||
131 | + margin: 0; | ||
132 | + font-size: .8rem; | ||
133 | + line-height: 15px; | ||
134 | + color: #7b7b7b; | ||
135 | + background: rgba(220, 220, 220, .35); | ||
136 | + &:not(:last-child) { | ||
137 | + margin-right: 4px; | ||
138 | + } | ||
139 | + } | ||
140 | +} | ||
141 | + | ||
142 | +.tb-resize-container { | ||
143 | + position: relative; | ||
144 | + width: 100%; | ||
145 | + height: 100%; | ||
146 | + overflow-y: auto; | ||
147 | + | ||
148 | + .ace_editor { | ||
149 | + height: 100%; | ||
150 | + } | ||
151 | +} | ||
152 | + | ||
153 | +mat-toolbar.tb-edit-toolbar { | ||
154 | + | ||
155 | + min-height: $edit-toolbar-height !important; | ||
156 | + max-height: $edit-toolbar-height !important; | ||
157 | + | ||
158 | + button.mat-button { | ||
159 | + min-width: 65px; | ||
160 | + min-height: 30px; | ||
161 | + font-size: 12px; | ||
162 | + line-height: 30px; | ||
163 | + | ||
164 | + mat-icon { | ||
165 | + font-size: 20px; | ||
166 | + } | ||
167 | + | ||
168 | + span { | ||
169 | + padding-right: 6px; | ||
170 | + } | ||
171 | + } | ||
172 | + | ||
173 | + mat-form-field { | ||
174 | + input { | ||
175 | + font-size: 1.2rem; | ||
176 | + font-weight: 400; | ||
177 | + letter-spacing: .005em; | ||
178 | + } | ||
179 | + div.mat-form-field-infix { | ||
180 | + padding-bottom: 5px; | ||
181 | + } | ||
182 | + &.tb-widget-title { | ||
183 | + min-width: 250px; | ||
184 | + } | ||
185 | + } | ||
186 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { PageComponent } from '@shared/components/page.component'; | ||
18 | +import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; | ||
19 | +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | ||
20 | +import { Store } from '@ngrx/store'; | ||
21 | +import { AppState } from '@core/core.state'; | ||
22 | +import { WidgetService } from '@core/http/widget.service'; | ||
23 | +import { WidgetInfo } from '@home/models/widget-component.models'; | ||
24 | +import { WidgetConfig, widgetType, WidgetType, widgetTypesData, Widget } from '@shared/models/widget.models'; | ||
25 | +import { ActivatedRoute } from '@angular/router'; | ||
26 | +import { deepClone } from '@core/utils'; | ||
27 | +import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; | ||
28 | +import { AuthUser } from '@shared/models/user.model'; | ||
29 | +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; | ||
30 | +import { Authority } from '@shared/models/authority.enum'; | ||
31 | +import { NULL_UUID } from '@shared/models/id/has-uuid'; | ||
32 | +import { Hotkey, HotkeysService } from 'angular2-hotkeys'; | ||
33 | +import { TranslateService } from '@ngx-translate/core'; | ||
34 | +import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors'; | ||
35 | +import * as ace from 'ace-builds'; | ||
36 | +import { css_beautify, html_beautify } from 'js-beautify'; | ||
37 | +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; | ||
38 | +import { WINDOW } from '@core/services/window.service'; | ||
39 | +import { WindowMessage } from '@shared/models/window-message.model'; | ||
40 | +import { ExceptionData } from '@shared/models/error.models'; | ||
41 | +import Timeout = NodeJS.Timeout; | ||
42 | +import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; | ||
43 | + | ||
44 | +@Component({ | ||
45 | + selector: 'tb-widget-editor', | ||
46 | + templateUrl: './widget-editor.component.html', | ||
47 | + styleUrls: ['./widget-editor.component.scss'], | ||
48 | + encapsulation: ViewEncapsulation.None | ||
49 | +}) | ||
50 | +export class WidgetEditorComponent extends PageComponent implements OnInit, OnDestroy, HasDirtyFlag { | ||
51 | + | ||
52 | + @ViewChild('topPanel', {static: true}) | ||
53 | + topPanelElmRef: ElementRef; | ||
54 | + | ||
55 | + @ViewChild('topLeftPanel', {static: true}) | ||
56 | + topLeftPanelElmRef: ElementRef; | ||
57 | + | ||
58 | + @ViewChild('topRightPanel', {static: true}) | ||
59 | + topRightPanelElmRef: ElementRef; | ||
60 | + | ||
61 | + @ViewChild('bottomPanel', {static: true}) | ||
62 | + bottomPanelElmRef: ElementRef; | ||
63 | + | ||
64 | + @ViewChild('javascriptPanel', {static: true}) | ||
65 | + javascriptPanelElmRef: ElementRef; | ||
66 | + | ||
67 | + @ViewChild('framePanel', {static: true}) | ||
68 | + framePanelElmRef: ElementRef; | ||
69 | + | ||
70 | + @ViewChild('htmlInput', {static: true}) | ||
71 | + htmlInputElmRef: ElementRef; | ||
72 | + | ||
73 | + @ViewChild('cssInput', {static: true}) | ||
74 | + cssInputElmRef: ElementRef; | ||
75 | + | ||
76 | + @ViewChild('settingsJsonInput', {static: true}) | ||
77 | + settingsJsonInputElmRef: ElementRef; | ||
78 | + | ||
79 | + @ViewChild('dataKeySettingsJsonInput', {static: true}) | ||
80 | + dataKeySettingsJsonInputElmRef: ElementRef; | ||
81 | + | ||
82 | + @ViewChild('javascriptInput', {static: true}) | ||
83 | + javascriptInputElmRef: ElementRef; | ||
84 | + | ||
85 | + @ViewChild('widgetIFrame', {static: true}) | ||
86 | + widgetIFrameElmRef: ElementRef<HTMLIFrameElement>; | ||
87 | + | ||
88 | + iframe: JQuery<HTMLIFrameElement>; | ||
89 | + | ||
90 | + widgetTypes = widgetType; | ||
91 | + allWidgetTypes = Object.keys(widgetType); | ||
92 | + widgetTypesDataMap = widgetTypesData; | ||
93 | + | ||
94 | + authUser: AuthUser; | ||
95 | + | ||
96 | + isReadOnly: boolean; | ||
97 | + | ||
98 | + widgetsBundle: WidgetsBundle; | ||
99 | + widgetType: WidgetType; | ||
100 | + widget: WidgetInfo; | ||
101 | + origWidget: WidgetInfo; | ||
102 | + | ||
103 | + isDirty = false; | ||
104 | + | ||
105 | + fullscreen = false; | ||
106 | + htmlFullscreen = false; | ||
107 | + cssFullscreen = false; | ||
108 | + jsonSettingsFullscreen = false; | ||
109 | + jsonDataKeySettingsFullscreen = false; | ||
110 | + javascriptFullscreen = false; | ||
111 | + iFrameFullscreen = false; | ||
112 | + | ||
113 | + aceEditors: ace.Ace.Editor[] = []; | ||
114 | + editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {}; | ||
115 | + htmlEditor: ace.Ace.Editor; | ||
116 | + cssEditor: ace.Ace.Editor; | ||
117 | + jsonSettingsEditor: ace.Ace.Editor; | ||
118 | + dataKeyJsonSettingsEditor: ace.Ace.Editor; | ||
119 | + jsEditor: ace.Ace.Editor; | ||
120 | + aceResizeListeners: { element: any, resizeListener: any }[] = []; | ||
121 | + | ||
122 | + onWindowMessageListener = this.onWindowMessage.bind(this); | ||
123 | + | ||
124 | + iframeWidgetEditModeInited = false; | ||
125 | + saveWidgetPending = false; | ||
126 | + saveWidgetAsPending = false; | ||
127 | + | ||
128 | + gotError = false; | ||
129 | + errorMarkers: number[] = []; | ||
130 | + errorAnnotationId = -1; | ||
131 | + | ||
132 | + saveWidgetTimeout: Timeout; | ||
133 | + | ||
134 | + constructor(protected store: Store<AppState>, | ||
135 | + @Inject(WINDOW) private window: Window, | ||
136 | + private route: ActivatedRoute, | ||
137 | + private widgetService: WidgetService, | ||
138 | + private hotkeysService: HotkeysService, | ||
139 | + private translate: TranslateService, | ||
140 | + private raf: RafService) { | ||
141 | + super(store); | ||
142 | + | ||
143 | + this.authUser = getCurrentAuthUser(store); | ||
144 | + | ||
145 | + this.widgetsBundle = this.route.snapshot.data.widgetsBundle; | ||
146 | + if (this.authUser.authority === Authority.TENANT_ADMIN) { | ||
147 | + this.isReadOnly = !this.widgetsBundle || this.widgetsBundle.tenantId.id === NULL_UUID; | ||
148 | + } else { | ||
149 | + this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN; | ||
150 | + } | ||
151 | + this.widgetType = this.route.snapshot.data.widgetEditorData.widgetType; | ||
152 | + this.widget = this.route.snapshot.data.widgetEditorData.widget; | ||
153 | + if (this.widgetType) { | ||
154 | + const config = JSON.parse(this.widget.defaultConfig); | ||
155 | + this.widget.defaultConfig = JSON.stringify(config); | ||
156 | + } | ||
157 | + this.origWidget = deepClone(this.widget); | ||
158 | + if (!this.widgetType) { | ||
159 | + this.isDirty = true; | ||
160 | + } | ||
161 | + } | ||
162 | + | ||
163 | + ngOnInit(): void { | ||
164 | + this.initHotKeys(); | ||
165 | + this.initSplitLayout(); | ||
166 | + this.initAceEditors(); | ||
167 | + this.iframe = $(this.widgetIFrameElmRef.nativeElement); | ||
168 | + this.window.addEventListener('message', this.onWindowMessageListener); | ||
169 | + this.iframe.attr('data-widget', JSON.stringify(this.widget)); | ||
170 | + this.iframe.attr('src', '/widget-editor'); | ||
171 | + } | ||
172 | + | ||
173 | + ngOnDestroy(): void { | ||
174 | + this.window.removeEventListener('message', this.onWindowMessageListener); | ||
175 | + this.aceResizeListeners.forEach((resizeListener) => { | ||
176 | + // @ts-ignore | ||
177 | + removeResizeListener(resizeListener.element, resizeListener.resizeListener); | ||
178 | + }); | ||
179 | + } | ||
180 | + | ||
181 | + private initHotKeys(): void { | ||
182 | + this.hotkeysService.add( | ||
183 | + new Hotkey('ctrl+q', (event: KeyboardEvent) => { | ||
184 | + if (!getCurrentIsLoading(this.store) && !this.undoDisabled()) { | ||
185 | + event.preventDefault(); | ||
186 | + this.undoWidget(); | ||
187 | + } | ||
188 | + return false; | ||
189 | + }, ['INPUT', 'SELECT', 'TEXTAREA'], | ||
190 | + this.translate.instant('widget.undo')) | ||
191 | + ); | ||
192 | + this.hotkeysService.add( | ||
193 | + new Hotkey('ctrl+s', (event: KeyboardEvent) => { | ||
194 | + if (!getCurrentIsLoading(this.store) && !this.saveDisabled()) { | ||
195 | + event.preventDefault(); | ||
196 | + this.saveWidget(); | ||
197 | + } | ||
198 | + return false; | ||
199 | + }, ['INPUT', 'SELECT', 'TEXTAREA'], | ||
200 | + this.translate.instant('widget.save')) | ||
201 | + ); | ||
202 | + this.hotkeysService.add( | ||
203 | + new Hotkey('shift+ctrl+s', (event: KeyboardEvent) => { | ||
204 | + if (!getCurrentIsLoading(this.store) && !this.saveAsDisabled()) { | ||
205 | + event.preventDefault(); | ||
206 | + this.saveWidgetAs(); | ||
207 | + } | ||
208 | + return false; | ||
209 | + }, ['INPUT', 'SELECT', 'TEXTAREA'], | ||
210 | + this.translate.instant('widget.saveAs')) | ||
211 | + ); | ||
212 | + this.hotkeysService.add( | ||
213 | + new Hotkey('shift+ctrl+f', (event: KeyboardEvent) => { | ||
214 | + event.preventDefault(); | ||
215 | + this.fullscreen = !this.fullscreen; | ||
216 | + return false; | ||
217 | + }, ['INPUT', 'SELECT', 'TEXTAREA'], | ||
218 | + this.translate.instant('widget.toggle-fullscreen')) | ||
219 | + ); | ||
220 | + this.hotkeysService.add( | ||
221 | + new Hotkey('ctrl+enter', (event: KeyboardEvent) => { | ||
222 | + event.preventDefault(); | ||
223 | + this.applyWidgetScript(); | ||
224 | + return false; | ||
225 | + }, ['INPUT', 'SELECT', 'TEXTAREA'], | ||
226 | + this.translate.instant('widget.run')) | ||
227 | + ); | ||
228 | + } | ||
229 | + | ||
230 | + private initSplitLayout() { | ||
231 | + Split([this.topPanelElmRef.nativeElement, this.bottomPanelElmRef.nativeElement], { | ||
232 | + sizes: [35, 65], | ||
233 | + gutterSize: 8, | ||
234 | + cursor: 'row-resize', | ||
235 | + direction: 'vertical' | ||
236 | + }); | ||
237 | + Split([this.topLeftPanelElmRef.nativeElement, this.topRightPanelElmRef.nativeElement], { | ||
238 | + sizes: [50, 50], | ||
239 | + gutterSize: 8, | ||
240 | + cursor: 'col-resize' | ||
241 | + }); | ||
242 | + Split([this.javascriptPanelElmRef.nativeElement, this.framePanelElmRef.nativeElement], { | ||
243 | + sizes: [50, 50], | ||
244 | + gutterSize: 8, | ||
245 | + cursor: 'col-resize' | ||
246 | + }); | ||
247 | + } | ||
248 | + | ||
249 | + private initAceEditors() { | ||
250 | + this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html'); | ||
251 | + this.htmlEditor.on('input', () => { | ||
252 | + const editorValue = this.htmlEditor.getValue(); | ||
253 | + if (this.widget.templateHtml !== editorValue) { | ||
254 | + this.widget.templateHtml = editorValue; | ||
255 | + this.isDirty = true; | ||
256 | + } | ||
257 | + }); | ||
258 | + this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css'); | ||
259 | + this.cssEditor.on('input', () => { | ||
260 | + const editorValue = this.cssEditor.getValue(); | ||
261 | + if (this.widget.templateCss !== editorValue) { | ||
262 | + this.widget.templateCss = editorValue; | ||
263 | + this.isDirty = true; | ||
264 | + } | ||
265 | + }); | ||
266 | + this.jsonSettingsEditor = this.createAceEditor(this.settingsJsonInputElmRef, 'json'); | ||
267 | + this.jsonSettingsEditor.on('input', () => { | ||
268 | + const editorValue = this.jsonSettingsEditor.getValue(); | ||
269 | + if (this.widget.settingsSchema !== editorValue) { | ||
270 | + this.widget.settingsSchema = editorValue; | ||
271 | + this.isDirty = true; | ||
272 | + } | ||
273 | + }); | ||
274 | + this.dataKeyJsonSettingsEditor = this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json'); | ||
275 | + this.dataKeyJsonSettingsEditor.on('input', () => { | ||
276 | + const editorValue = this.dataKeyJsonSettingsEditor.getValue(); | ||
277 | + if (this.widget.dataKeySettingsSchema !== editorValue) { | ||
278 | + this.widget.dataKeySettingsSchema = editorValue; | ||
279 | + this.isDirty = true; | ||
280 | + } | ||
281 | + }); | ||
282 | + this.jsEditor = this.createAceEditor(this.javascriptInputElmRef, 'javascript'); | ||
283 | + this.jsEditor.on('input', () => { | ||
284 | + const editorValue = this.jsEditor.getValue(); | ||
285 | + if (this.widget.controllerScript !== editorValue) { | ||
286 | + this.widget.controllerScript = editorValue; | ||
287 | + this.isDirty = true; | ||
288 | + } | ||
289 | + }); | ||
290 | + this.jsEditor.on('change', () => { | ||
291 | + this.cleanupJsErrors(); | ||
292 | + }); | ||
293 | + this.setAceEditorValues(); | ||
294 | + } | ||
295 | + | ||
296 | + private setAceEditorValues() { | ||
297 | + this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); | ||
298 | + this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); | ||
299 | + this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); | ||
300 | + this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); | ||
301 | + this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); | ||
302 | + } | ||
303 | + | ||
304 | + private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor { | ||
305 | + const editorElement = editorElementRef.nativeElement; | ||
306 | + let editorOptions: Partial<ace.Ace.EditorOptions> = { | ||
307 | + mode: `ace/mode/${mode}`, | ||
308 | + showGutter: true, | ||
309 | + showPrintMargin: true | ||
310 | + }; | ||
311 | + const advancedOptions = { | ||
312 | + enableSnippets: true, | ||
313 | + enableBasicAutocompletion: true, | ||
314 | + enableLiveAutocompletion: true | ||
315 | + }; | ||
316 | + editorOptions = {...editorOptions, ...advancedOptions}; | ||
317 | + const aceEditor = ace.edit(editorElement, editorOptions); | ||
318 | + aceEditor.session.setUseWrapMode(true); | ||
319 | + this.aceEditors.push(aceEditor); | ||
320 | + | ||
321 | + const resizeListener = this.onAceEditorResize.bind(this, aceEditor); | ||
322 | + | ||
323 | + // @ts-ignore | ||
324 | + addResizeListener(editorElement, resizeListener); | ||
325 | + this.aceResizeListeners.push({element: editorElement, resizeListener}); | ||
326 | + return aceEditor; | ||
327 | + } | ||
328 | + | ||
329 | + private onAceEditorResize(aceEditor: ace.Ace.Editor) { | ||
330 | + if (this.editorsResizeCafs[aceEditor.id]) { | ||
331 | + this.editorsResizeCafs[aceEditor.id](); | ||
332 | + delete this.editorsResizeCafs[aceEditor.id]; | ||
333 | + } | ||
334 | + this.editorsResizeCafs[aceEditor.id] = this.raf.raf(() => { | ||
335 | + aceEditor.resize(); | ||
336 | + aceEditor.renderer.updateFull(); | ||
337 | + }); | ||
338 | + } | ||
339 | + | ||
340 | + private onWindowMessage(event: MessageEvent) { | ||
341 | + let message: WindowMessage; | ||
342 | + if (event.data) { | ||
343 | + try { | ||
344 | + message = JSON.parse(event.data); | ||
345 | + } catch (e) {} | ||
346 | + } | ||
347 | + if (message) { | ||
348 | + switch (message.type) { | ||
349 | + case 'widgetException': | ||
350 | + this.onWidgetException(message.data); | ||
351 | + break; | ||
352 | + case 'widgetEditModeInited': | ||
353 | + this.onWidgetEditModeInited(); | ||
354 | + break; | ||
355 | + case 'widgetEditUpdated': | ||
356 | + this.onWidgetEditUpdated(message.data); | ||
357 | + break; | ||
358 | + } | ||
359 | + } | ||
360 | + } | ||
361 | + | ||
362 | + private onWidgetEditModeInited() { | ||
363 | + this.iframeWidgetEditModeInited = true; | ||
364 | + if (this.saveWidgetPending || this.saveWidgetAsPending) { | ||
365 | + if (!this.saveWidgetTimeout) { | ||
366 | + this.saveWidgetTimeout = setTimeout(() => { | ||
367 | + if (!this.gotError) { | ||
368 | + if (this.saveWidgetPending) { | ||
369 | + this.commitSaveWidget(); | ||
370 | + } else if (this.saveWidgetAsPending) { | ||
371 | + this.commitSaveWidgetAs(); | ||
372 | + } | ||
373 | + } else { | ||
374 | + this.store.dispatch(new ActionNotificationShow( | ||
375 | + {message: this.translate.instant('widget.unable-to-save-widget-error'), type: 'error'})); | ||
376 | + this.saveWidgetPending = false; | ||
377 | + this.saveWidgetAsPending = false; | ||
378 | + } | ||
379 | + this.saveWidgetTimeout = undefined; | ||
380 | + }, 1500); | ||
381 | + } | ||
382 | + } | ||
383 | + } | ||
384 | + | ||
385 | + private onWidgetEditUpdated(widget: Widget) { | ||
386 | + this.widget.sizeX = widget.sizeX / 2; | ||
387 | + this.widget.sizeY = widget.sizeY / 2; | ||
388 | + this.widget.defaultConfig = JSON.stringify(widget.config); | ||
389 | + this.iframe.attr('data-widget', JSON.stringify(this.widget)); | ||
390 | + this.isDirty = true; | ||
391 | + } | ||
392 | + | ||
393 | + private onWidgetException(details: ExceptionData) { | ||
394 | + if (!this.gotError) { | ||
395 | + this.gotError = true; | ||
396 | + let errorInfo = 'Error:'; | ||
397 | + if (details.name) { | ||
398 | + errorInfo += ' ' + details.name + ':'; | ||
399 | + } | ||
400 | + if (details.message) { | ||
401 | + errorInfo += ' ' + details.message; | ||
402 | + } | ||
403 | + if (details.lineNumber) { | ||
404 | + errorInfo += '<br>Line ' + details.lineNumber; | ||
405 | + if (details.columnNumber) { | ||
406 | + errorInfo += ' column ' + details.columnNumber; | ||
407 | + } | ||
408 | + errorInfo += ' of script.'; | ||
409 | + } | ||
410 | + if (!this.saveWidgetPending && !this.saveWidgetAsPending) { | ||
411 | + this.store.dispatch(new ActionNotificationShow( | ||
412 | + {message: errorInfo, type: 'error', target: 'javascriptPanel'})); | ||
413 | + } | ||
414 | + if (details.lineNumber) { | ||
415 | + const line = details.lineNumber - 1; | ||
416 | + let column = 0; | ||
417 | + if (details.columnNumber) { | ||
418 | + column = details.columnNumber; | ||
419 | + } | ||
420 | + const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity), | ||
421 | + 'ace_active-line', 'screenLine'); | ||
422 | + this.errorMarkers.push(errorMarkerId); | ||
423 | + const annotations = this.jsEditor.session.getAnnotations(); | ||
424 | + const errorAnnotation: ace.Ace.Annotation = { | ||
425 | + row: line, | ||
426 | + column, | ||
427 | + text: details.message, | ||
428 | + type: 'error' | ||
429 | + }; | ||
430 | + this.errorAnnotationId = annotations.push(errorAnnotation) - 1; | ||
431 | + this.jsEditor.session.setAnnotations(annotations); | ||
432 | + } | ||
433 | + } | ||
434 | + } | ||
435 | + | ||
436 | + private cleanupJsErrors() { | ||
437 | + this.store.dispatch(new ActionNotificationHide({})); | ||
438 | + this.errorMarkers.forEach((errorMarker) => { | ||
439 | + this.jsEditor.session.removeMarker(errorMarker); | ||
440 | + }); | ||
441 | + this.errorMarkers.length = 0; | ||
442 | + if (this.errorAnnotationId > -1) { | ||
443 | + const annotations = this.jsEditor.session.getAnnotations(); | ||
444 | + annotations.splice(this.errorAnnotationId, 1); | ||
445 | + this.jsEditor.session.setAnnotations(annotations); | ||
446 | + this.errorAnnotationId = -1; | ||
447 | + } | ||
448 | + } | ||
449 | + | ||
450 | + private commitSaveWidget() { | ||
451 | + // TODO: | ||
452 | + this.saveWidgetPending = false; | ||
453 | + } | ||
454 | + | ||
455 | + private commitSaveWidgetAs() { | ||
456 | + // TODO: | ||
457 | + this.saveWidgetAsPending = false; | ||
458 | + } | ||
459 | + | ||
460 | + applyWidgetScript(): void { | ||
461 | + this.cleanupJsErrors(); | ||
462 | + this.gotError = false; | ||
463 | + this.iframeWidgetEditModeInited = false; | ||
464 | + const config: WidgetConfig = JSON.parse(this.widget.defaultConfig); | ||
465 | + config.title = this.widget.widgetName; | ||
466 | + this.widget.defaultConfig = JSON.stringify(config); | ||
467 | + this.iframe.attr('data-widget', JSON.stringify(this.widget)); | ||
468 | + this.iframe[0].contentWindow.location.reload(true); | ||
469 | + } | ||
470 | + | ||
471 | + undoWidget(): void { | ||
472 | + this.widget = deepClone(this.origWidget); | ||
473 | + this.setAceEditorValues(); | ||
474 | + this.isDirty = false; | ||
475 | + this.applyWidgetScript(); | ||
476 | + } | ||
477 | + | ||
478 | + saveWidget(): void { | ||
479 | + if (!this.widget.widgetName) { | ||
480 | + this.store.dispatch(new ActionNotificationShow( | ||
481 | + {message: this.translate.instant('widget.missing-widget-title-error'), type: 'error'})); | ||
482 | + } else { | ||
483 | + this.saveWidgetPending = true; | ||
484 | + this.applyWidgetScript(); | ||
485 | + } | ||
486 | + } | ||
487 | + | ||
488 | + saveWidgetAs(): void { | ||
489 | + this.saveWidgetAsPending = true; | ||
490 | + this.applyWidgetScript(); | ||
491 | + } | ||
492 | + | ||
493 | + undoDisabled(): boolean { | ||
494 | + return !this.isDirty | ||
495 | + || !this.iframeWidgetEditModeInited | ||
496 | + || this.saveWidgetPending | ||
497 | + || this.saveWidgetAsPending; | ||
498 | + } | ||
499 | + | ||
500 | + saveDisabled(): boolean { | ||
501 | + return this.isReadOnly | ||
502 | + || !this.isDirty | ||
503 | + || !this.iframeWidgetEditModeInited | ||
504 | + || this.saveWidgetPending | ||
505 | + || this.saveWidgetAsPending; | ||
506 | + } | ||
507 | + | ||
508 | + saveAsDisabled(): boolean { | ||
509 | + return !this.iframeWidgetEditModeInited | ||
510 | + || this.saveWidgetPending | ||
511 | + || this.saveWidgetAsPending; | ||
512 | + } | ||
513 | + | ||
514 | + beautifyCss(): void { | ||
515 | + const res = css_beautify(this.widget.templateCss, {indent_size: 4}); | ||
516 | + if (this.widget.templateCss !== res) { | ||
517 | + this.isDirty = true; | ||
518 | + this.widget.templateCss = res; | ||
519 | + this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); | ||
520 | + } | ||
521 | + } | ||
522 | + | ||
523 | + beautifyHtml(): void { | ||
524 | + const res = html_beautify(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}); | ||
525 | + if (this.widget.templateHtml !== res) { | ||
526 | + this.isDirty = true; | ||
527 | + this.widget.templateHtml = res; | ||
528 | + this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); | ||
529 | + } | ||
530 | + } | ||
531 | + | ||
532 | + beautifyJson(): void { | ||
533 | + const res = js_beautify(this.widget.settingsSchema, {indent_size: 4}); | ||
534 | + if (this.widget.settingsSchema !== res) { | ||
535 | + this.isDirty = true; | ||
536 | + this.widget.settingsSchema = res; | ||
537 | + this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); | ||
538 | + } | ||
539 | + } | ||
540 | + | ||
541 | + beautifyDataKeyJson(): void { | ||
542 | + const res = js_beautify(this.widget.dataKeySettingsSchema, {indent_size: 4}); | ||
543 | + if (this.widget.dataKeySettingsSchema !== res) { | ||
544 | + this.isDirty = true; | ||
545 | + this.widget.dataKeySettingsSchema = res; | ||
546 | + this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); | ||
547 | + } | ||
548 | + } | ||
549 | + | ||
550 | + beautifyJs(): void { | ||
551 | + const res = js_beautify(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}); | ||
552 | + if (this.widget.controllerScript !== res) { | ||
553 | + this.isDirty = true; | ||
554 | + this.widget.controllerScript = res; | ||
555 | + this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); | ||
556 | + } | ||
557 | + } | ||
558 | + | ||
559 | + removeResource(index: number) { | ||
560 | + if (index > -1) { | ||
561 | + if (this.widget.resources.splice(index, 1).length > 0) { | ||
562 | + this.isDirty = true; | ||
563 | + } | ||
564 | + } | ||
565 | + } | ||
566 | + | ||
567 | + addResource() { | ||
568 | + this.widget.resources.push({url: ''}); | ||
569 | + this.isDirty = true; | ||
570 | + } | ||
571 | + | ||
572 | + widetTypeChanged() { | ||
573 | + const config: WidgetConfig = JSON.parse(this.widget.defaultConfig); | ||
574 | + if (this.widget.type !== widgetType.rpc && | ||
575 | + this.widget.type !== widgetType.alarm) { | ||
576 | + if (config.targetDeviceAliases) { | ||
577 | + delete config.targetDeviceAliases; | ||
578 | + } | ||
579 | + if (config.alarmSource) { | ||
580 | + delete config.alarmSource; | ||
581 | + } | ||
582 | + if (!config.datasources) { | ||
583 | + config.datasources = []; | ||
584 | + } | ||
585 | + if (!config.timewindow) { | ||
586 | + config.timewindow = { | ||
587 | + realtime: { | ||
588 | + timewindowMs: 60000 | ||
589 | + } | ||
590 | + }; | ||
591 | + } | ||
592 | + } else if (this.widget.type === widgetType.rpc) { | ||
593 | + if (config.datasources) { | ||
594 | + delete config.datasources; | ||
595 | + } | ||
596 | + if (config.alarmSource) { | ||
597 | + delete config.alarmSource; | ||
598 | + } | ||
599 | + if (config.timewindow) { | ||
600 | + delete config.timewindow; | ||
601 | + } | ||
602 | + if (!config.targetDeviceAliases) { | ||
603 | + config.targetDeviceAliases = []; | ||
604 | + } | ||
605 | + } else { // alarm | ||
606 | + if (config.datasources) { | ||
607 | + delete config.datasources; | ||
608 | + } | ||
609 | + if (config.targetDeviceAliases) { | ||
610 | + delete config.targetDeviceAliases; | ||
611 | + } | ||
612 | + if (!config.alarmSource) { | ||
613 | + config.alarmSource = {}; | ||
614 | + } | ||
615 | + if (!config.timewindow) { | ||
616 | + config.timewindow = { | ||
617 | + realtime: { | ||
618 | + timewindowMs: 24 * 60 * 60 * 1000 | ||
619 | + } | ||
620 | + }; | ||
621 | + } | ||
622 | + } | ||
623 | + this.widget.defaultConfig = JSON.stringify(config); | ||
624 | + this.isDirty = true; | ||
625 | + } | ||
626 | +} |
@@ -17,20 +17,24 @@ | @@ -17,20 +17,24 @@ | ||
17 | import { Injectable, NgModule } from '@angular/core'; | 17 | import { Injectable, NgModule } from '@angular/core'; |
18 | import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; | 18 | import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; |
19 | 19 | ||
20 | -import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; | ||
21 | -import {Authority} from '@shared/models/authority.enum'; | ||
22 | -import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; | ||
23 | -import {WidgetsBundlesTableConfigResolver} from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; | 20 | +import { EntitiesTableComponent } from '../../components/entity/entities-table.component'; |
21 | +import { Authority } from '@shared/models/authority.enum'; | ||
22 | +import { WidgetsBundlesTableConfigResolver } from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; | ||
24 | import { WidgetLibraryComponent } from '@home/pages/widget/widget-library.component'; | 23 | import { WidgetLibraryComponent } from '@home/pages/widget/widget-library.component'; |
25 | import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; | 24 | import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; |
26 | -import { User } from '@shared/models/user.model'; | ||
27 | -import { Store } from '@ngrx/store'; | ||
28 | -import { AppState } from '@core/core.state'; | ||
29 | -import { UserService } from '@core/http/user.service'; | ||
30 | import { Observable } from 'rxjs'; | 25 | import { Observable } from 'rxjs'; |
31 | -import { getCurrentAuthUser } from '@core/auth/auth.selectors'; | ||
32 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | 26 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
33 | import { WidgetService } from '@core/http/widget.service'; | 27 | import { WidgetService } from '@core/http/widget.service'; |
28 | +import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component'; | ||
29 | +import { map } from 'rxjs/operators'; | ||
30 | +import { toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; | ||
31 | +import { widgetType, WidgetType } from '@app/shared/models/widget.models'; | ||
32 | +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; | ||
33 | + | ||
34 | +export interface WidgetEditorData { | ||
35 | + widgetType: WidgetType; | ||
36 | + widget: WidgetInfo; | ||
37 | +} | ||
34 | 38 | ||
35 | @Injectable() | 39 | @Injectable() |
36 | export class WidgetsBundleResolver implements Resolve<WidgetsBundle> { | 40 | export class WidgetsBundleResolver implements Resolve<WidgetsBundle> { |
@@ -39,13 +43,61 @@ export class WidgetsBundleResolver implements Resolve<WidgetsBundle> { | @@ -39,13 +43,61 @@ export class WidgetsBundleResolver implements Resolve<WidgetsBundle> { | ||
39 | } | 43 | } |
40 | 44 | ||
41 | resolve(route: ActivatedRouteSnapshot): Observable<WidgetsBundle> { | 45 | resolve(route: ActivatedRouteSnapshot): Observable<WidgetsBundle> { |
42 | - const widgetsBundleId = route.params.widgetsBundleId; | 46 | + let widgetsBundleId = route.params.widgetsBundleId; |
47 | + if (!widgetsBundleId) { | ||
48 | + widgetsBundleId = route.parent.params.widgetsBundleId; | ||
49 | + } | ||
43 | return this.widgetsService.getWidgetsBundle(widgetsBundleId); | 50 | return this.widgetsService.getWidgetsBundle(widgetsBundleId); |
44 | } | 51 | } |
45 | } | 52 | } |
46 | 53 | ||
54 | +@Injectable() | ||
55 | +export class WidgetEditorDataResolver implements Resolve<WidgetEditorData> { | ||
56 | + | ||
57 | + constructor(private widgetsService: WidgetService) { | ||
58 | + } | ||
59 | + | ||
60 | + resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> { | ||
61 | + const widgetTypeId = route.params.widgetTypeId; | ||
62 | + return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe( | ||
63 | + map((result) => { | ||
64 | + return { | ||
65 | + widgetType: result, | ||
66 | + widget: toWidgetInfo(result) | ||
67 | + }; | ||
68 | + }) | ||
69 | + ); | ||
70 | + } | ||
71 | +} | ||
72 | + | ||
73 | +@Injectable() | ||
74 | +export class WidgetEditorAddDataResolver implements Resolve<WidgetEditorData> { | ||
75 | + | ||
76 | + constructor(private widgetsService: WidgetService) { | ||
77 | + } | ||
78 | + | ||
79 | + resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> { | ||
80 | + let widgetTypeParam = route.params.widgetType as widgetType; | ||
81 | + if (!widgetTypeParam) { | ||
82 | + widgetTypeParam = widgetType.timeseries; | ||
83 | + } | ||
84 | + return this.widgetsService.getWidgetTemplate(widgetTypeParam).pipe( | ||
85 | + map((widget) => { | ||
86 | + widget.widgetName = null; | ||
87 | + return { | ||
88 | + widgetType: null, | ||
89 | + widget | ||
90 | + }; | ||
91 | + }) | ||
92 | + ); | ||
93 | + } | ||
94 | +} | ||
95 | + | ||
47 | export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate) => route.data.widgetsBundle.title); | 96 | export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate) => route.data.widgetsBundle.title); |
48 | 97 | ||
98 | +export const widgetEditorBreadcumbLabelFunction: BreadCrumbLabelFunction = | ||
99 | + ((route, translate, component) => component ? (component as WidgetEditorComponent).widget.widgetName : ''); | ||
100 | + | ||
49 | export const routes: Routes = [ | 101 | export const routes: Routes = [ |
50 | { | 102 | { |
51 | path: 'widgets-bundles', | 103 | path: 'widgets-bundles', |
@@ -69,10 +121,7 @@ export const routes: Routes = [ | @@ -69,10 +121,7 @@ export const routes: Routes = [ | ||
69 | }, | 121 | }, |
70 | { | 122 | { |
71 | path: ':widgetsBundleId/widgetTypes', | 123 | path: ':widgetsBundleId/widgetTypes', |
72 | - component: WidgetLibraryComponent, | ||
73 | data: { | 124 | data: { |
74 | - auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | ||
75 | - title: 'widget.widget-library', | ||
76 | breadcrumb: { | 125 | breadcrumb: { |
77 | labelFunction: widgetTypesBreadcumbLabelFunction, | 126 | labelFunction: widgetTypesBreadcumbLabelFunction, |
78 | icon: 'now_widgets' | 127 | icon: 'now_widgets' |
@@ -80,7 +129,49 @@ export const routes: Routes = [ | @@ -80,7 +129,49 @@ export const routes: Routes = [ | ||
80 | }, | 129 | }, |
81 | resolve: { | 130 | resolve: { |
82 | widgetsBundle: WidgetsBundleResolver | 131 | widgetsBundle: WidgetsBundleResolver |
83 | - } | 132 | + }, |
133 | + children: [ | ||
134 | + { | ||
135 | + path: '', | ||
136 | + component: WidgetLibraryComponent, | ||
137 | + data: { | ||
138 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | ||
139 | + title: 'widget.widget-library' | ||
140 | + } | ||
141 | + }, | ||
142 | + { | ||
143 | + path: ':widgetTypeId', | ||
144 | + component: WidgetEditorComponent, | ||
145 | + canDeactivate: [ConfirmOnExitGuard], | ||
146 | + data: { | ||
147 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | ||
148 | + title: 'widget.editor', | ||
149 | + breadcrumb: { | ||
150 | + labelFunction: widgetEditorBreadcumbLabelFunction, | ||
151 | + icon: 'insert_chart' | ||
152 | + } as BreadCrumbConfig | ||
153 | + }, | ||
154 | + resolve: { | ||
155 | + widgetEditorData: WidgetEditorDataResolver | ||
156 | + } | ||
157 | + }, | ||
158 | + { | ||
159 | + path: 'add/:widgetType', | ||
160 | + component: WidgetEditorComponent, | ||
161 | + canDeactivate: [ConfirmOnExitGuard], | ||
162 | + data: { | ||
163 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | ||
164 | + title: 'widget.editor', | ||
165 | + breadcrumb: { | ||
166 | + labelFunction: widgetEditorBreadcumbLabelFunction, | ||
167 | + icon: 'insert_chart' | ||
168 | + } as BreadCrumbConfig | ||
169 | + }, | ||
170 | + resolve: { | ||
171 | + widgetEditorData: WidgetEditorAddDataResolver | ||
172 | + } | ||
173 | + } | ||
174 | + ] | ||
84 | } | 175 | } |
85 | ] | 176 | ] |
86 | } | 177 | } |
@@ -91,7 +182,9 @@ export const routes: Routes = [ | @@ -91,7 +182,9 @@ export const routes: Routes = [ | ||
91 | exports: [RouterModule], | 182 | exports: [RouterModule], |
92 | providers: [ | 183 | providers: [ |
93 | WidgetsBundlesTableConfigResolver, | 184 | WidgetsBundlesTableConfigResolver, |
94 | - WidgetsBundleResolver | 185 | + WidgetsBundleResolver, |
186 | + WidgetEditorDataResolver, | ||
187 | + WidgetEditorAddDataResolver | ||
95 | ] | 188 | ] |
96 | }) | 189 | }) |
97 | export class WidgetLibraryRoutingModule { } | 190 | export class WidgetLibraryRoutingModule { } |
@@ -21,7 +21,7 @@ import { PageComponent } from '@shared/components/page.component'; | @@ -21,7 +21,7 @@ import { PageComponent } from '@shared/components/page.component'; | ||
21 | import { AuthUser } from '@shared/models/user.model'; | 21 | import { AuthUser } from '@shared/models/user.model'; |
22 | import { getCurrentAuthUser } from '@core/auth/auth.selectors'; | 22 | import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
23 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | 23 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
24 | -import { ActivatedRoute } from '@angular/router'; | 24 | +import { ActivatedRoute, Router } from '@angular/router'; |
25 | import { Authority } from '@shared/models/authority.enum'; | 25 | import { Authority } from '@shared/models/authority.enum'; |
26 | import { NULL_UUID } from '@shared/models/id/has-uuid'; | 26 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
27 | import { Observable } from 'rxjs'; | 27 | import { Observable } from 'rxjs'; |
@@ -34,6 +34,13 @@ import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-componen | @@ -34,6 +34,13 @@ import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-componen | ||
34 | import { IAliasController } from '@app/core/api/widget-api.models'; | 34 | import { IAliasController } from '@app/core/api/widget-api.models'; |
35 | import { toWidgetInfo } from '@home/models/widget-component.models'; | 35 | import { toWidgetInfo } from '@home/models/widget-component.models'; |
36 | import { DummyAliasController } from '@core/api/alias-controller'; | 36 | import { DummyAliasController } from '@core/api/alias-controller'; |
37 | +import { | ||
38 | + DeviceCredentialsDialogComponent, | ||
39 | + DeviceCredentialsDialogData | ||
40 | +} from '@home/pages/device/device-credentials-dialog.component'; | ||
41 | +import { DeviceCredentials } from '@shared/models/device.models'; | ||
42 | +import { MatDialog } from '@angular/material/dialog'; | ||
43 | +import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; | ||
37 | 44 | ||
38 | @Component({ | 45 | @Component({ |
39 | selector: 'tb-widget-library', | 46 | selector: 'tb-widget-library', |
@@ -83,8 +90,10 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | @@ -83,8 +90,10 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | ||
83 | 90 | ||
84 | constructor(protected store: Store<AppState>, | 91 | constructor(protected store: Store<AppState>, |
85 | private route: ActivatedRoute, | 92 | private route: ActivatedRoute, |
93 | + private router: Router, | ||
86 | private widgetService: WidgetService, | 94 | private widgetService: WidgetService, |
87 | - private dialogService: DialogService) { | 95 | + private dialogService: DialogService, |
96 | + private dialog: MatDialog) { | ||
88 | super(store); | 97 | super(store); |
89 | 98 | ||
90 | this.authUser = getCurrentAuthUser(store); | 99 | this.authUser = getCurrentAuthUser(store); |
@@ -163,33 +172,43 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | @@ -163,33 +172,43 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { | ||
163 | } | 172 | } |
164 | 173 | ||
165 | importWidgetType($event: Event): void { | 174 | importWidgetType($event: Event): void { |
166 | - if (event) { | ||
167 | - event.stopPropagation(); | 175 | + if ($event) { |
176 | + $event.stopPropagation(); | ||
168 | } | 177 | } |
169 | this.dialogService.todo(); | 178 | this.dialogService.todo(); |
170 | } | 179 | } |
171 | 180 | ||
172 | openWidgetType($event: Event, widget?: Widget): void { | 181 | openWidgetType($event: Event, widget?: Widget): void { |
173 | - if (event) { | ||
174 | - event.stopPropagation(); | 182 | + if ($event) { |
183 | + $event.stopPropagation(); | ||
175 | } | 184 | } |
176 | if (widget) { | 185 | if (widget) { |
177 | - this.dialogService.todo(); | 186 | + this.router.navigate([widget.typeId.id], {relativeTo: this.route}); |
178 | } else { | 187 | } else { |
179 | - this.dialogService.todo(); | 188 | + this.dialog.open<SelectWidgetTypeDialogComponent, any, |
189 | + widgetType>(SelectWidgetTypeDialogComponent, { | ||
190 | + disableClose: true, | ||
191 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] | ||
192 | + }).afterClosed().subscribe( | ||
193 | + (type) => { | ||
194 | + if (type) { | ||
195 | + this.router.navigate(['add', type], {relativeTo: this.route}); | ||
196 | + } | ||
197 | + } | ||
198 | + ); | ||
180 | } | 199 | } |
181 | } | 200 | } |
182 | 201 | ||
183 | exportWidgetType($event: Event, widget: Widget): void { | 202 | exportWidgetType($event: Event, widget: Widget): void { |
184 | - if (event) { | ||
185 | - event.stopPropagation(); | 203 | + if ($event) { |
204 | + $event.stopPropagation(); | ||
186 | } | 205 | } |
187 | this.dialogService.todo(); | 206 | this.dialogService.todo(); |
188 | } | 207 | } |
189 | 208 | ||
190 | removeWidgetType($event: Event, widget: Widget): void { | 209 | removeWidgetType($event: Event, widget: Widget): void { |
191 | - if (event) { | ||
192 | - event.stopPropagation(); | 210 | + if ($event) { |
211 | + $event.stopPropagation(); | ||
193 | } | 212 | } |
194 | this.dialogService.todo(); | 213 | this.dialogService.todo(); |
195 | } | 214 | } |
@@ -21,14 +21,19 @@ import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle. | @@ -21,14 +21,19 @@ import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle. | ||
21 | import {WidgetLibraryRoutingModule} from '@modules/home/pages/widget/widget-library-routing.module'; | 21 | import {WidgetLibraryRoutingModule} from '@modules/home/pages/widget/widget-library-routing.module'; |
22 | import {HomeComponentsModule} from '@modules/home/components/home-components.module'; | 22 | import {HomeComponentsModule} from '@modules/home/components/home-components.module'; |
23 | import { WidgetLibraryComponent } from './widget-library.component'; | 23 | import { WidgetLibraryComponent } from './widget-library.component'; |
24 | +import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component'; | ||
25 | +import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; | ||
24 | 26 | ||
25 | @NgModule({ | 27 | @NgModule({ |
26 | entryComponents: [ | 28 | entryComponents: [ |
27 | - WidgetsBundleComponent | 29 | + WidgetsBundleComponent, |
30 | + SelectWidgetTypeDialogComponent | ||
28 | ], | 31 | ], |
29 | declarations: [ | 32 | declarations: [ |
30 | WidgetsBundleComponent, | 33 | WidgetsBundleComponent, |
31 | - WidgetLibraryComponent | 34 | + WidgetLibraryComponent, |
35 | + WidgetEditorComponent, | ||
36 | + SelectWidgetTypeDialogComponent | ||
32 | ], | 37 | ], |
33 | imports: [ | 38 | imports: [ |
34 | CommonModule, | 39 | CommonModule, |
@@ -17,7 +17,7 @@ | @@ -17,7 +17,7 @@ | ||
17 | --> | 17 | --> |
18 | <div fxFlex class="tb-breadcrumb" fxLayout="row"> | 18 | <div fxFlex class="tb-breadcrumb" fxLayout="row"> |
19 | <h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb"> | 19 | <h1 fxFlex fxHide.gt-sm *ngIf="lastBreadcrumb$ | async; let breadcrumb"> |
20 | - {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} | 20 | + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }} |
21 | </h1> | 21 | </h1> |
22 | <span fxHide.xs fxHide.sm *ngFor="let breadcrumb of breadcrumbs$ | async; last as isLast;" [ngSwitch]="isLast"> | 22 | <span fxHide.xs fxHide.sm *ngFor="let breadcrumb of breadcrumbs$ | async; last as isLast;" [ngSwitch]="isLast"> |
23 | <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> | 23 | <a *ngSwitchCase="false" [routerLink]="breadcrumb.link" [queryParams]="breadcrumb.queryParams"> |
@@ -26,7 +26,7 @@ | @@ -26,7 +26,7 @@ | ||
26 | <mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> | 26 | <mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> |
27 | {{ breadcrumb.icon }} | 27 | {{ breadcrumb.icon }} |
28 | </mat-icon> | 28 | </mat-icon> |
29 | - {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} | 29 | + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }} |
30 | </a> | 30 | </a> |
31 | <span *ngSwitchCase="true"> | 31 | <span *ngSwitchCase="true"> |
32 | <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> | 32 | <mat-icon *ngIf="breadcrumb.isMdiIcon" [svgIcon]="breadcrumb.icon"> |
@@ -34,7 +34,7 @@ | @@ -34,7 +34,7 @@ | ||
34 | <mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> | 34 | <mat-icon *ngIf="!breadcrumb.isMdiIcon" class="material-icons"> |
35 | {{ breadcrumb.icon }} | 35 | {{ breadcrumb.icon }} |
36 | </mat-icon> | 36 | </mat-icon> |
37 | - {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} | 37 | + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }} |
38 | </span> | 38 | </span> |
39 | <span class="divider" [fxHide]="isLast"> > </span> | 39 | <span class="divider" [fxHide]="isLast"> > </span> |
40 | </span> | 40 | </span> |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { Component, OnDestroy, OnInit } from '@angular/core'; | 17 | +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; |
18 | import { BehaviorSubject, Subject } from 'rxjs'; | 18 | import { BehaviorSubject, Subject } from 'rxjs'; |
19 | import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; | 19 | import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; |
20 | import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; | 20 | import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; |
@@ -28,6 +28,13 @@ import { TranslateService } from '@ngx-translate/core'; | @@ -28,6 +28,13 @@ import { TranslateService } from '@ngx-translate/core'; | ||
28 | }) | 28 | }) |
29 | export class BreadcrumbComponent implements OnInit, OnDestroy { | 29 | export class BreadcrumbComponent implements OnInit, OnDestroy { |
30 | 30 | ||
31 | + activeComponentValue: any; | ||
32 | + | ||
33 | + @Input() | ||
34 | + set activeComponent(activeComponent: any) { | ||
35 | + this.activeComponentValue = activeComponent; | ||
36 | + } | ||
37 | + | ||
31 | breadcrumbs$: Subject<Array<BreadCrumb>> = new BehaviorSubject<Array<BreadCrumb>>(this.buildBreadCrumbs(this.activatedRoute.snapshot)); | 38 | breadcrumbs$: Subject<Array<BreadCrumb>> = new BehaviorSubject<Array<BreadCrumb>>(this.buildBreadCrumbs(this.activatedRoute.snapshot)); |
32 | 39 | ||
33 | routerEventsSubscription = this.router.events.pipe( | 40 | routerEventsSubscription = this.router.events.pipe( |
@@ -61,9 +68,12 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { | @@ -61,9 +68,12 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { | ||
61 | const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig; | 68 | const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig; |
62 | if (breadcrumbConfig && !breadcrumbConfig.skip) { | 69 | if (breadcrumbConfig && !breadcrumbConfig.skip) { |
63 | let label; | 70 | let label; |
71 | + let labelFunction; | ||
64 | let ignoreTranslate; | 72 | let ignoreTranslate; |
65 | if (breadcrumbConfig.labelFunction) { | 73 | if (breadcrumbConfig.labelFunction) { |
66 | - label = breadcrumbConfig.labelFunction(route, this.translate); | 74 | + labelFunction = () => { |
75 | + return breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue); | ||
76 | + }; | ||
67 | ignoreTranslate = true; | 77 | ignoreTranslate = true; |
68 | } else { | 78 | } else { |
69 | label = breadcrumbConfig.label || 'home.home'; | 79 | label = breadcrumbConfig.label || 'home.home'; |
@@ -71,10 +81,11 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { | @@ -71,10 +81,11 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { | ||
71 | } | 81 | } |
72 | const icon = breadcrumbConfig.icon || 'home'; | 82 | const icon = breadcrumbConfig.icon || 'home'; |
73 | const isMdiIcon = icon.startsWith('mdi:'); | 83 | const isMdiIcon = icon.startsWith('mdi:'); |
74 | - const link = [ '/' + route.url.join('') ]; | 84 | + const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ]; |
75 | const queryParams = route.queryParams; | 85 | const queryParams = route.queryParams; |
76 | const breadcrumb = { | 86 | const breadcrumb = { |
77 | label, | 87 | label, |
88 | + labelFunction, | ||
78 | ignoreTranslate, | 89 | ignoreTranslate, |
79 | icon, | 90 | icon, |
80 | isMdiIcon, | 91 | isMdiIcon, |
@@ -89,5 +100,4 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { | @@ -89,5 +100,4 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { | ||
89 | } | 100 | } |
90 | return newBreadcrumbs; | 101 | return newBreadcrumbs; |
91 | } | 102 | } |
92 | - | ||
93 | } | 103 | } |
@@ -19,6 +19,7 @@ import { TranslateService } from '@ngx-translate/core'; | @@ -19,6 +19,7 @@ import { TranslateService } from '@ngx-translate/core'; | ||
19 | 19 | ||
20 | export interface BreadCrumb { | 20 | export interface BreadCrumb { |
21 | label: string; | 21 | label: string; |
22 | + labelFunction?: () => string; | ||
22 | ignoreTranslate: boolean; | 23 | ignoreTranslate: boolean; |
23 | icon: string; | 24 | icon: string; |
24 | isMdiIcon: boolean; | 25 | isMdiIcon: boolean; |
@@ -26,7 +27,7 @@ export interface BreadCrumb { | @@ -26,7 +27,7 @@ export interface BreadCrumb { | ||
26 | queryParams: Params; | 27 | queryParams: Params; |
27 | } | 28 | } |
28 | 29 | ||
29 | -export type BreadCrumbLabelFunction = (route: ActivatedRouteSnapshot, translate: TranslateService) => string; | 30 | +export type BreadCrumbLabelFunction = (route: ActivatedRouteSnapshot, translate: TranslateService, component: any) => string; |
30 | 31 | ||
31 | export interface BreadCrumbConfig { | 32 | export interface BreadCrumbConfig { |
32 | labelFunction: BreadCrumbLabelFunction; | 33 | labelFunction: BreadCrumbLabelFunction; |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Directive, ElementRef, ViewContainerRef, ComponentFactoryResolver, ComponentRef, Input } from '@angular/core'; | ||
18 | +import { Overlay } from '@angular/cdk/overlay'; | ||
19 | +import { MatProgressBar, MatSpinner } from '@angular/material'; | ||
20 | + | ||
21 | +@Directive({ | ||
22 | + selector: '[tb-circular-progress]' | ||
23 | +}) | ||
24 | +export class CircularProgressDirective { | ||
25 | + | ||
26 | + showProgressValue = false; | ||
27 | + | ||
28 | + children: JQuery<any>; | ||
29 | + | ||
30 | + cssWidth: any; | ||
31 | + | ||
32 | + @Input('tb-circular-progress') | ||
33 | + set showProgress(showProgress: boolean) { | ||
34 | + if (this.showProgressValue !== showProgress) { | ||
35 | + const element = this.elementRef.nativeElement; | ||
36 | + this.showProgressValue = showProgress; | ||
37 | + this.spinnerRef.instance._elementRef.nativeElement.style.display = showProgress ? 'block' : 'none'; | ||
38 | + if (showProgress) { | ||
39 | + this.cssWidth = $(element).prop('style').width; | ||
40 | + if (!this.cssWidth) { | ||
41 | + $(element).css('width', ''); | ||
42 | + const width = $(element).prop('offsetWidth'); | ||
43 | + $(element).css('width', width + 'px'); | ||
44 | + } | ||
45 | + this.children = $(element).children(); | ||
46 | + $(element).empty(); | ||
47 | + $(element).append($(this.spinnerRef.instance._elementRef.nativeElement)); | ||
48 | + } else { | ||
49 | + $(element).empty(); | ||
50 | + $(element).append(this.children); | ||
51 | + if (this.cssWidth) { | ||
52 | + $(element).css('width', this.cssWidth); | ||
53 | + } else { | ||
54 | + $(element).css('width', ''); | ||
55 | + } | ||
56 | + } | ||
57 | + } | ||
58 | + } | ||
59 | + | ||
60 | + spinnerRef: ComponentRef<MatSpinner>; | ||
61 | + | ||
62 | + constructor(private elementRef: ElementRef, | ||
63 | + private componentFactoryResolver: ComponentFactoryResolver, | ||
64 | + private viewContainerRef: ViewContainerRef) { | ||
65 | + this.createCircularProgress(); | ||
66 | + } | ||
67 | + | ||
68 | + createCircularProgress() { | ||
69 | + this.elementRef.nativeElement.style.position = 'relative'; | ||
70 | + const factory = this.componentFactoryResolver.resolveComponentFactory(MatSpinner); | ||
71 | + this.spinnerRef = this.viewContainerRef.createComponent(factory, 0); | ||
72 | + this.spinnerRef.instance.mode = 'indeterminate'; | ||
73 | + this.spinnerRef.instance.diameter = 20; | ||
74 | + const el = this.spinnerRef.instance._elementRef.nativeElement; | ||
75 | + el.style.margin = 'auto'; | ||
76 | + el.style.position = 'absolute'; | ||
77 | + el.style.left = '0'; | ||
78 | + el.style.right = '0'; | ||
79 | + el.style.top = '0'; | ||
80 | + el.style.bottom = '0'; | ||
81 | + el.style.display = 'none'; | ||
82 | + } | ||
83 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div fxLayout="column" class="mat-content mat-padding"> | ||
19 | + <mat-form-field> | ||
20 | + <mat-label>{{'dashboard.select-dashboard' | translate}}</mat-label> | ||
21 | + <mat-select matInput [(ngModel)]="dashboardId" | ||
22 | + (ngModelChange)="dashboardSelected(dashboardId)"> | ||
23 | + <mat-option *ngFor="let dashboard of dashboards$ | async" [value]="dashboard.id.id"> | ||
24 | + {{dashboard.title}} | ||
25 | + </mat-option> | ||
26 | + </mat-select> | ||
27 | + </mat-form-field> | ||
28 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +@import "../../../scss/constants"; | ||
17 | + | ||
18 | +:host { | ||
19 | + min-width: 300px; | ||
20 | + max-width: 320px; | ||
21 | + max-height: 150px; | ||
22 | + overflow-x: hidden; | ||
23 | + overflow-y: auto; | ||
24 | + background: #fff; | ||
25 | + border-radius: 4px; | ||
26 | + box-shadow: | ||
27 | + 0 7px 8px -4px rgba(0, 0, 0, .2), | ||
28 | + 0 13px 19px 2px rgba(0, 0, 0, .14), | ||
29 | + 0 5px 24px 4px rgba(0, 0, 0, .12); | ||
30 | + | ||
31 | + @media (min-height: 350px) { | ||
32 | + max-height: 250px; | ||
33 | + } | ||
34 | + | ||
35 | + @media #{$mat-gt-xs} { | ||
36 | + max-width: 100%; | ||
37 | + } | ||
38 | + | ||
39 | + .mat-content { | ||
40 | + background-color: #fff; | ||
41 | + } | ||
42 | +} | ||
43 | + | ||
44 | +:host ::ng-deep { | ||
45 | + mat-form-field { | ||
46 | + .mat-form-field-infix { | ||
47 | + width: 100%; | ||
48 | + mat-select { | ||
49 | + .mat-select-value { | ||
50 | + max-width: 100%; | ||
51 | + } | ||
52 | + } | ||
53 | + } | ||
54 | + } | ||
55 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, InjectionToken } from '@angular/core'; | ||
18 | +import { Observable } from 'rxjs'; | ||
19 | +import { DashboardInfo } from '../models/dashboard.models'; | ||
20 | + | ||
21 | +export const DASHBOARD_SELECT_PANEL_DATA = new InjectionToken<any>('DashboardSelectPanelData'); | ||
22 | + | ||
23 | +export interface DashboardSelectPanelData { | ||
24 | + dashboards$: Observable<Array<DashboardInfo>>; | ||
25 | + dashboardId: string; | ||
26 | + onDashboardSelected: (dashboardId: string) => void; | ||
27 | +} | ||
28 | + | ||
29 | +@Component({ | ||
30 | + selector: 'tb-dashboard-select-panel', | ||
31 | + templateUrl: './dashboard-select-panel.component.html', | ||
32 | + styleUrls: ['./dashboard-select-panel.component.scss'] | ||
33 | +}) | ||
34 | +export class DashboardSelectPanelComponent { | ||
35 | + | ||
36 | + dashboards$: Observable<Array<DashboardInfo>>; | ||
37 | + dashboardId: string; | ||
38 | + | ||
39 | + constructor(@Inject(DASHBOARD_SELECT_PANEL_DATA) | ||
40 | + private data: DashboardSelectPanelData) { | ||
41 | + this.dashboards$ = this.data.dashboards$; | ||
42 | + this.dashboardId = this.data.dashboardId; | ||
43 | + } | ||
44 | + | ||
45 | + public dashboardSelected(dashboardId: string) { | ||
46 | + this.data.onDashboardSelected(dashboardId); | ||
47 | + } | ||
48 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<mat-select fxHide.xs fxHide.sm fxHide.md | ||
19 | + [required]="required" | ||
20 | + [disabled]="disabled" | ||
21 | + [(ngModel)]="dashboardId" | ||
22 | + (ngModelChange)="dashboardIdChanged()"> | ||
23 | + <mat-option *ngFor="let dashboard of dashboards$ | async" [value]="dashboard.id.id"> | ||
24 | + {{dashboard.title}} | ||
25 | + </mat-option> | ||
26 | +</mat-select> | ||
27 | +<section fxHide.gt-md class="tb-dashboard-select"> | ||
28 | + <button mat-button mat-icon-button | ||
29 | + cdkOverlayOrigin #dashboardSelectPanelOrigin="cdkOverlayOrigin" | ||
30 | + (click)="openDashboardSelectPanel()" | ||
31 | + matTooltip="{{ 'dashboard.select-dashboard' | translate }}" | ||
32 | + [matTooltipPosition]="tooltipPosition"> | ||
33 | + <mat-icon>dashboards</mat-icon> | ||
34 | + </button> | ||
35 | +</section> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host { | ||
17 | + min-width: 52px; | ||
18 | + | ||
19 | + mat-select { | ||
20 | + max-width: 300px; | ||
21 | + pointer-events: all; | ||
22 | + } | ||
23 | + | ||
24 | + .tb-dashboard-select { | ||
25 | + min-height: 32px; | ||
26 | + | ||
27 | + span { | ||
28 | + pointer-events: all; | ||
29 | + cursor: pointer; | ||
30 | + } | ||
31 | + } | ||
32 | +} | ||
33 | + | ||
34 | +:host ::ng-deep { | ||
35 | + mat-select { | ||
36 | + .mat-select-value { | ||
37 | + max-width: 282px; | ||
38 | + } | ||
39 | + } | ||
40 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, forwardRef, Inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | ||
18 | +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | ||
19 | +import { Observable, of } from 'rxjs'; | ||
20 | +import { PageLink } from '@shared/models/page/page-link'; | ||
21 | +import { map, share } from 'rxjs/operators'; | ||
22 | +import { emptyPageData, PageData } from '@shared/models/page/page-data'; | ||
23 | +import { DashboardInfo } from '@app/shared/models/dashboard.models'; | ||
24 | +import { DashboardService } from '@core/http/dashboard.service'; | ||
25 | +import { Store } from '@ngrx/store'; | ||
26 | +import { AppState } from '@app/core/core.state'; | ||
27 | +import { getCurrentAuthUser } from '@app/core/auth/auth.selectors'; | ||
28 | +import { Authority } from '@shared/models/authority.enum'; | ||
29 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | ||
30 | +import { TooltipPosition } from '@angular/material/tooltip'; | ||
31 | +import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | ||
32 | +import { BreakpointObserver } from '@angular/cdk/layout'; | ||
33 | +import { DOCUMENT } from '@angular/common'; | ||
34 | +import { WINDOW } from '@core/services/window.service'; | ||
35 | +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | ||
36 | +import { | ||
37 | + DASHBOARD_SELECT_PANEL_DATA, | ||
38 | + DashboardSelectPanelComponent, | ||
39 | + DashboardSelectPanelData | ||
40 | +} from './dashboard-select-panel.component'; | ||
41 | + | ||
42 | +@Component({ | ||
43 | + selector: 'tb-dashboard-select', | ||
44 | + templateUrl: './dashboard-select.component.html', | ||
45 | + styleUrls: ['./dashboard-select.component.scss'], | ||
46 | + providers: [{ | ||
47 | + provide: NG_VALUE_ACCESSOR, | ||
48 | + useExisting: forwardRef(() => DashboardSelectComponent), | ||
49 | + multi: true | ||
50 | + }] | ||
51 | +}) | ||
52 | +export class DashboardSelectComponent implements ControlValueAccessor, OnInit { | ||
53 | + | ||
54 | + @Input() | ||
55 | + dashboardsScope: 'customer' | 'tenant'; | ||
56 | + | ||
57 | + @Input() | ||
58 | + customerId: string; | ||
59 | + | ||
60 | + @Input() | ||
61 | + tooltipPosition: TooltipPosition = 'above'; | ||
62 | + | ||
63 | + private requiredValue: boolean; | ||
64 | + get required(): boolean { | ||
65 | + return this.requiredValue; | ||
66 | + } | ||
67 | + @Input() | ||
68 | + set required(value: boolean) { | ||
69 | + this.requiredValue = coerceBooleanProperty(value); | ||
70 | + } | ||
71 | + | ||
72 | + @Input() | ||
73 | + disabled: boolean; | ||
74 | + | ||
75 | + dashboards$: Observable<Array<DashboardInfo>>; | ||
76 | + | ||
77 | + dashboardId: string | null; | ||
78 | + | ||
79 | + @ViewChild('dashboardSelectPanelOrigin', {static: false}) dashboardSelectPanelOrigin: CdkOverlayOrigin; | ||
80 | + | ||
81 | + private propagateChange = (v: any) => { }; | ||
82 | + | ||
83 | + constructor(private store: Store<AppState>, | ||
84 | + private dashboardService: DashboardService, | ||
85 | + private overlay: Overlay, | ||
86 | + private breakpointObserver: BreakpointObserver, | ||
87 | + private viewContainerRef: ViewContainerRef, | ||
88 | + @Inject(DOCUMENT) private document: Document, | ||
89 | + @Inject(WINDOW) private window: Window) { | ||
90 | + } | ||
91 | + | ||
92 | + registerOnChange(fn: any): void { | ||
93 | + this.propagateChange = fn; | ||
94 | + } | ||
95 | + | ||
96 | + registerOnTouched(fn: any): void { | ||
97 | + } | ||
98 | + | ||
99 | + ngOnInit() { | ||
100 | + | ||
101 | + const pageLink = new PageLink(100); | ||
102 | + | ||
103 | + this.dashboards$ = this.getDashboards(pageLink).pipe( | ||
104 | + map((pageData) => pageData.data), | ||
105 | + share() | ||
106 | + ); | ||
107 | + } | ||
108 | + | ||
109 | + setDisabledState(isDisabled: boolean): void { | ||
110 | + this.disabled = isDisabled; | ||
111 | + } | ||
112 | + | ||
113 | + writeValue(value: string | null): void { | ||
114 | + this.dashboardId = value; | ||
115 | + } | ||
116 | + | ||
117 | + dashboardIdChanged() { | ||
118 | + this.updateView(); | ||
119 | + } | ||
120 | + | ||
121 | + openDashboardSelectPanel() { | ||
122 | + if (this.disabled) { | ||
123 | + return; | ||
124 | + } | ||
125 | + const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150; | ||
126 | + const panelWidth = 300; | ||
127 | + const position = this.overlay.position(); | ||
128 | + const config = new OverlayConfig({ | ||
129 | + panelClass: 'tb-dashboard-select-panel', | ||
130 | + backdropClass: 'cdk-overlay-transparent-backdrop', | ||
131 | + hasBackdrop: true, | ||
132 | + }); | ||
133 | + const el = this.dashboardSelectPanelOrigin.elementRef.nativeElement; | ||
134 | + const offset = el.getBoundingClientRect(); | ||
135 | + const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; | ||
136 | + const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; | ||
137 | + const bottomY = offset.bottom - scrollTop; | ||
138 | + const leftX = offset.left - scrollLeft; | ||
139 | + let originX; | ||
140 | + let originY; | ||
141 | + let overlayX; | ||
142 | + let overlayY; | ||
143 | + const wHeight = this.document.documentElement.clientHeight; | ||
144 | + const wWidth = this.document.documentElement.clientWidth; | ||
145 | + if (bottomY + panelHeight > wHeight) { | ||
146 | + originY = 'top'; | ||
147 | + overlayY = 'bottom'; | ||
148 | + } else { | ||
149 | + originY = 'bottom'; | ||
150 | + overlayY = 'top'; | ||
151 | + } | ||
152 | + if (leftX + panelWidth > wWidth) { | ||
153 | + originX = 'end'; | ||
154 | + overlayX = 'end'; | ||
155 | + } else { | ||
156 | + originX = 'start'; | ||
157 | + overlayX = 'start'; | ||
158 | + } | ||
159 | + const connectedPosition: ConnectedPosition = { | ||
160 | + originX, | ||
161 | + originY, | ||
162 | + overlayX, | ||
163 | + overlayY | ||
164 | + }; | ||
165 | + config.positionStrategy = position.flexibleConnectedTo(this.dashboardSelectPanelOrigin.elementRef) | ||
166 | + .withPositions([connectedPosition]); | ||
167 | + const overlayRef = this.overlay.create(config); | ||
168 | + overlayRef.backdropClick().subscribe(() => { | ||
169 | + overlayRef.dispose(); | ||
170 | + }); | ||
171 | + | ||
172 | + const injector = this._createDashboardSelectPanelInjector( | ||
173 | + overlayRef, | ||
174 | + { | ||
175 | + dashboards$: this.dashboards$, | ||
176 | + dashboardId: this.dashboardId, | ||
177 | + onDashboardSelected: (dashboardId) => { | ||
178 | + overlayRef.dispose(); | ||
179 | + this.dashboardId = dashboardId; | ||
180 | + this.updateView(); | ||
181 | + } | ||
182 | + } | ||
183 | + ); | ||
184 | + overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector)); | ||
185 | + } | ||
186 | + | ||
187 | + private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): PortalInjector { | ||
188 | + const injectionTokens = new WeakMap<any, any>([ | ||
189 | + [DASHBOARD_SELECT_PANEL_DATA, data], | ||
190 | + [OverlayRef, overlayRef] | ||
191 | + ]); | ||
192 | + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | ||
193 | + } | ||
194 | + | ||
195 | + private updateView() { | ||
196 | + this.propagateChange(this.dashboardId); | ||
197 | + } | ||
198 | + | ||
199 | + private getDashboards(pageLink: PageLink): Observable<PageData<DashboardInfo>> { | ||
200 | + let dashboardsObservable: Observable<PageData<DashboardInfo>>; | ||
201 | + const authUser = getCurrentAuthUser(this.store); | ||
202 | + if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { | ||
203 | + if (this.customerId) { | ||
204 | + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); | ||
205 | + } else { | ||
206 | + dashboardsObservable = of(emptyPageData()); | ||
207 | + } | ||
208 | + } else { | ||
209 | + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); | ||
210 | + } | ||
211 | + return dashboardsObservable; | ||
212 | + } | ||
213 | + | ||
214 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2019 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div class="mat-fab-toolbar-wrapper"> | ||
19 | + <div class="mat-fab-toolbar-content"> | ||
20 | + <ng-content></ng-content> | ||
21 | + </div> | ||
22 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +$font-size: 10px !default; | ||
17 | +@function rem($multiplier) { | ||
18 | + @return $multiplier * $font-size; | ||
19 | +} | ||
20 | + | ||
21 | +$button-fab-width: rem(5.600) !default; | ||
22 | +$button-fab-height: rem(5.600) !default; | ||
23 | +$button-fab-padding: rem(1.60) !default; | ||
24 | +$icon-button-margin: rem(0.600) !default; | ||
25 | +$z-index-fab: 20 !default; | ||
26 | + | ||
27 | +$swift-ease-in-duration: 0.3s !default; | ||
28 | +$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; | ||
29 | +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; | ||
30 | + | ||
31 | +@mixin rtl-prop($ltr-prop, $rtl-prop, $value, $reset-value) { | ||
32 | + #{$ltr-prop}: $value; | ||
33 | + [dir=rtl] & { | ||
34 | + #{$ltr-prop}: $reset-value; | ||
35 | + #{$rtl-prop}: $value; | ||
36 | + } | ||
37 | +} | ||
38 | + | ||
39 | +@mixin fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) { | ||
40 | + &.mat-fab-#{$spot} { | ||
41 | + top: $top; | ||
42 | + right: $right; | ||
43 | + bottom: $bottom; | ||
44 | + left: $left; | ||
45 | + position: absolute; | ||
46 | + } | ||
47 | +} | ||
48 | + | ||
49 | +@mixin fab-all-positions() { | ||
50 | + @include fab-position(bottom-right, auto, ($button-fab-width - $button-fab-padding)/2, ($button-fab-height - $button-fab-padding)/2, auto); | ||
51 | + @include fab-position(bottom-left, auto, auto, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2); | ||
52 | + @include fab-position(top-right, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2, auto, auto); | ||
53 | + @include fab-position(top-left, ($button-fab-height - $button-fab-padding)/2, auto, auto, ($button-fab-width - $button-fab-padding)/2); | ||
54 | +} | ||
55 | + | ||
56 | +mat-fab-toolbar { | ||
57 | + $icon-delay: 200ms; | ||
58 | + @include fab-all-positions(); | ||
59 | + display: block; | ||
60 | + | ||
61 | + .mat-fab-toolbar-wrapper { | ||
62 | + display: block; | ||
63 | + position: relative; | ||
64 | + overflow: hidden; | ||
65 | + | ||
66 | + height: $button-fab-width + ($icon-button-margin * 2); | ||
67 | + } | ||
68 | + | ||
69 | + mat-fab-trigger { | ||
70 | + position: absolute; | ||
71 | + z-index: $z-index-fab; | ||
72 | + | ||
73 | + button { | ||
74 | + overflow: visible !important; | ||
75 | + opacity: .5; | ||
76 | + } | ||
77 | + | ||
78 | + .mat-fab-toolbar-background { | ||
79 | + display: block; | ||
80 | + position: absolute; | ||
81 | + z-index: $z-index-fab + 1; | ||
82 | + opacity: 1; | ||
83 | + } | ||
84 | + | ||
85 | + mat-icon { | ||
86 | + position: relative; | ||
87 | + z-index: $z-index-fab + 2; | ||
88 | + | ||
89 | + opacity: 1; | ||
90 | + | ||
91 | + } | ||
92 | + | ||
93 | + } | ||
94 | + | ||
95 | + &.mat-left { | ||
96 | + mat-fab-trigger { | ||
97 | + @include rtl-prop(right, left, 0, auto); | ||
98 | + } | ||
99 | + | ||
100 | + .mat-toolbar-tools { | ||
101 | + flex-direction: row-reverse; | ||
102 | + | ||
103 | + > .mat-button:first-child { | ||
104 | + @include rtl-prop(margin-right, margin-left, 0.6rem, auto) | ||
105 | + } | ||
106 | + | ||
107 | + > .mat-button:first-child { | ||
108 | + @include rtl-prop(margin-left, margin-right, -0.8rem, auto); | ||
109 | + } | ||
110 | + | ||
111 | + | ||
112 | + > .mat-button:last-child { | ||
113 | + @include rtl-prop(margin-right, margin-left, 8px, auto); | ||
114 | + } | ||
115 | + | ||
116 | + } | ||
117 | + } | ||
118 | + | ||
119 | + &.mat-right { | ||
120 | + mat-fab-trigger { | ||
121 | + @include rtl-prop(left, right, 0, auto); | ||
122 | + } | ||
123 | + | ||
124 | + .mat-toolbar-tools { | ||
125 | + flex-direction: row; | ||
126 | + } | ||
127 | + } | ||
128 | + | ||
129 | + mat-toolbar { | ||
130 | + padding: 0 !important; | ||
131 | + background-color: transparent !important; | ||
132 | + pointer-events: none; | ||
133 | + position: relative; | ||
134 | + z-index: $z-index-fab + 3; | ||
135 | + | ||
136 | + .mat-toolbar-tools { | ||
137 | + padding: 0 20px !important; | ||
138 | + margin-top: 3px; | ||
139 | + } | ||
140 | + | ||
141 | + .mat-fab-action-item { | ||
142 | + opacity: 0; | ||
143 | + transform: scale(0); | ||
144 | + } | ||
145 | + } | ||
146 | + | ||
147 | + &.mat-is-open { | ||
148 | + mat-fab-trigger > button { | ||
149 | + box-shadow: none; | ||
150 | + opacity: 1; | ||
151 | + | ||
152 | + mat-icon { | ||
153 | + opacity: 0; | ||
154 | + } | ||
155 | + } | ||
156 | + | ||
157 | + .mat-fab-action-item { | ||
158 | + opacity: 1; | ||
159 | + transform: scale(1); | ||
160 | + } | ||
161 | + } | ||
162 | + | ||
163 | + &.mat-animation { | ||
164 | + mat-fab-trigger { | ||
165 | + button { | ||
166 | + transition: opacity .3s cubic-bezier(.55, 0, .55, .2) .2s; | ||
167 | + } | ||
168 | + .mat-fab-toolbar-background { | ||
169 | + transition: $swift-ease-in; | ||
170 | + } | ||
171 | + mat-icon { | ||
172 | + transition: all $icon-delay ease-in; | ||
173 | + } | ||
174 | + } | ||
175 | + mat-toolbar { | ||
176 | + .mat-fab-action-item { | ||
177 | + transition: $swift-ease-in; | ||
178 | + transition-duration: $swift-ease-in-duration / 2; | ||
179 | + } | ||
180 | + } | ||
181 | + &.mat-is-open { | ||
182 | + mat-fab-trigger > button { | ||
183 | + transition: opacity .3s cubic-bezier(.55, 0, .55, .2); | ||
184 | + } | ||
185 | + } | ||
186 | + } | ||
187 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { | ||
18 | + Component, | ||
19 | + ElementRef, | ||
20 | + Input, | ||
21 | + OnChanges, | ||
22 | + OnInit, | ||
23 | + Renderer2, | ||
24 | + ViewEncapsulation, | ||
25 | + SimpleChanges, | ||
26 | + Inject, AfterViewInit, AfterViewChecked, Directive, OnDestroy | ||
27 | +} from '@angular/core'; | ||
28 | +import { PageComponent } from '@shared/components/page.component'; | ||
29 | +import { WINDOW } from '@core/services/window.service'; | ||
30 | +import { mixinColor, CanColorCtor } from '@angular/material'; | ||
31 | + | ||
32 | +export declare type FabToolbarDirection = 'left' | 'right'; | ||
33 | + | ||
34 | +class MatFabToolbarBase { | ||
35 | + // tslint:disable-next-line:variable-name | ||
36 | + constructor(public _elementRef: ElementRef) {} | ||
37 | +} | ||
38 | +const MatFabToolbarMixinBase: CanColorCtor & typeof MatFabToolbarBase = mixinColor(MatFabToolbarBase); | ||
39 | + | ||
40 | +@Directive({ | ||
41 | + selector: 'mat-fab-trigger' | ||
42 | +}) | ||
43 | +export class FabTriggerDirective { | ||
44 | + | ||
45 | + constructor(private el: ElementRef<HTMLElement>) { | ||
46 | + } | ||
47 | + | ||
48 | +} | ||
49 | + | ||
50 | +@Directive({ | ||
51 | + selector: 'mat-fab-actions' | ||
52 | +}) | ||
53 | +export class FabActionsDirective implements OnInit { | ||
54 | + | ||
55 | + constructor(private el: ElementRef<HTMLElement>) { | ||
56 | + } | ||
57 | + | ||
58 | + ngOnInit(): void { | ||
59 | + const element = $(this.el.nativeElement); | ||
60 | + const children = element.children(); | ||
61 | + children.wrap('<div class="mat-fab-action-item">'); | ||
62 | + } | ||
63 | + | ||
64 | +} | ||
65 | + | ||
66 | +@Component({ | ||
67 | + selector: 'mat-fab-toolbar', | ||
68 | + templateUrl: './fab-toolbar.component.html', | ||
69 | + styleUrls: ['./fab-toolbar.component.scss'], | ||
70 | + inputs: ['color'], | ||
71 | + encapsulation: ViewEncapsulation.None | ||
72 | +}) | ||
73 | +export class FabToolbarComponent extends MatFabToolbarMixinBase implements OnInit, OnDestroy, AfterViewInit, OnChanges { | ||
74 | + | ||
75 | + @Input() | ||
76 | + isOpen: boolean; | ||
77 | + | ||
78 | + @Input() | ||
79 | + direction: FabToolbarDirection; | ||
80 | + | ||
81 | + fabToolbarResizeListener = this.onFabToolbarResize.bind(this); | ||
82 | + | ||
83 | + constructor(private el: ElementRef<HTMLElement>, | ||
84 | + @Inject(WINDOW) private window: Window) { | ||
85 | + super(el); | ||
86 | + } | ||
87 | + | ||
88 | + ngOnInit(): void { | ||
89 | + const element = $(this.el.nativeElement); | ||
90 | + element.addClass('mat-fab-toolbar'); | ||
91 | + element.find('mat-fab-trigger').find('button') | ||
92 | + .prepend('<div class="mat-fab-toolbar-background"></div>'); | ||
93 | + element.addClass(`mat-${this.direction}`); | ||
94 | + // @ts-ignore | ||
95 | + addResizeListener(this.el.nativeElement, this.fabToolbarResizeListener); | ||
96 | + } | ||
97 | + | ||
98 | + ngOnDestroy(): void { | ||
99 | + // @ts-ignore | ||
100 | + removeResizeListener(this.el.nativeElement, this.fabToolbarResizeListener); | ||
101 | + } | ||
102 | + | ||
103 | + ngAfterViewInit(): void { | ||
104 | + this.triggerOpenClose(true); | ||
105 | + } | ||
106 | + | ||
107 | + ngOnChanges(changes: SimpleChanges): void { | ||
108 | + for (const propName of Object.keys(changes)) { | ||
109 | + const change = changes[propName]; | ||
110 | + if (!change.firstChange && change.currentValue !== change.previousValue) { | ||
111 | + if (propName === 'isOpen') { | ||
112 | + this.triggerOpenClose(); | ||
113 | + } | ||
114 | + } | ||
115 | + } | ||
116 | + } | ||
117 | + | ||
118 | + private onFabToolbarResize() { | ||
119 | + if (this.isOpen) { | ||
120 | + this.triggerOpenClose(true); | ||
121 | + } | ||
122 | + } | ||
123 | + | ||
124 | + private triggerOpenClose(disableAnimation?: boolean): void { | ||
125 | + const el = this.el.nativeElement; | ||
126 | + const element = $(this.el.nativeElement); | ||
127 | + if (disableAnimation) { | ||
128 | + element.removeClass('mat-animation'); | ||
129 | + } else { | ||
130 | + element.addClass('mat-animation'); | ||
131 | + } | ||
132 | + const backgroundElement: HTMLElement = el.querySelector('.mat-fab-toolbar-background'); | ||
133 | + const triggerElement: HTMLElement = el.querySelector('mat-fab-trigger button'); | ||
134 | + const toolbarElement: HTMLElement = el.querySelector('mat-toolbar'); | ||
135 | + const iconElement: HTMLElement = el.querySelector('mat-fab-trigger button mat-icon'); | ||
136 | + const actions = element.find('mat-fab-actions').children(); | ||
137 | + if (triggerElement && backgroundElement) { | ||
138 | + const width = el.offsetWidth; | ||
139 | + const height = el.offsetHeight; | ||
140 | + const scale = 2 * (width / triggerElement.offsetWidth); | ||
141 | + | ||
142 | + backgroundElement.style.borderRadius = width + 'px'; | ||
143 | + | ||
144 | + if (this.isOpen) { | ||
145 | + element.addClass('mat-is-open'); | ||
146 | + toolbarElement.style.pointerEvents = 'inherit'; | ||
147 | + | ||
148 | + backgroundElement.style.width = triggerElement.offsetWidth + 'px'; | ||
149 | + backgroundElement.style.height = triggerElement.offsetHeight + 'px'; | ||
150 | + backgroundElement.style.transform = 'scale(' + scale + ')'; | ||
151 | + | ||
152 | + backgroundElement.style.transitionDelay = '0ms'; | ||
153 | + if (iconElement) { | ||
154 | + iconElement.style.transitionDelay = disableAnimation ? '0ms' : '.3s'; | ||
155 | + } | ||
156 | + | ||
157 | + actions.each((index, action) => { | ||
158 | + action.style.transitionDelay = disableAnimation ? '0ms' : ((actions.length - index) * 25 + 'ms'); | ||
159 | + }); | ||
160 | + | ||
161 | + } else { | ||
162 | + element.removeClass('mat-is-open'); | ||
163 | + toolbarElement.style.pointerEvents = 'none'; | ||
164 | + | ||
165 | + backgroundElement.style.transform = 'scale(1)'; | ||
166 | + | ||
167 | + backgroundElement.style.top = '0'; | ||
168 | + | ||
169 | + if (element.hasClass('mat-right')) { | ||
170 | + backgroundElement.style.left = '0'; | ||
171 | + backgroundElement.style.right = null; | ||
172 | + } | ||
173 | + | ||
174 | + if (element.hasClass('mat-left')) { | ||
175 | + backgroundElement.style.right = '0'; | ||
176 | + backgroundElement.style.left = null; | ||
177 | + } | ||
178 | + | ||
179 | + backgroundElement.style.transitionDelay = disableAnimation ? '0ms' : '200ms'; | ||
180 | + | ||
181 | + actions.each((index, action) => { | ||
182 | + action.style.transitionDelay = (disableAnimation ? 0 : 200) + (index * 25) + 'ms'; | ||
183 | + }); | ||
184 | + } | ||
185 | + } | ||
186 | + } | ||
187 | + | ||
188 | +} |
ui-ngx/src/app/shared/models/alias.models.ts
0 → 100644
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
18 | + | ||
19 | +export enum AliasFilterType { | ||
20 | + singleEntity = 'singleEntity', | ||
21 | + entityList = 'entityList', | ||
22 | + entityName = 'entityName', | ||
23 | + stateEntity = 'stateEntity', | ||
24 | + assetType = 'assetType', | ||
25 | + deviceType = 'deviceType', | ||
26 | + entityViewType = 'entityViewType', | ||
27 | + relationsQuery = 'relationsQuery', | ||
28 | + assetSearchQuery = 'assetSearchQuery', | ||
29 | + deviceSearchQuery = 'deviceSearchQuery', | ||
30 | + entityViewSearchQuery = 'entityViewSearchQuery' | ||
31 | +} | ||
32 | + | ||
33 | +export const aliasFilterTypeTranslationMap = new Map<AliasFilterType, string>( | ||
34 | + [ | ||
35 | + [ AliasFilterType.singleEntity, 'alias.filter-type-single-entity' ], | ||
36 | + [ AliasFilterType.entityList, 'alias.filter-type-entity-list' ], | ||
37 | + [ AliasFilterType.entityName, 'alias.filter-type-entity-name' ], | ||
38 | + [ AliasFilterType.stateEntity, 'alias.filter-type-state-entity' ], | ||
39 | + [ AliasFilterType.assetType, 'alias.filter-type-asset-type' ], | ||
40 | + [ AliasFilterType.deviceType, 'alias.filter-type-device-type' ], | ||
41 | + [ AliasFilterType.entityViewType, 'alias.filter-type-entity-view-type' ], | ||
42 | + [ AliasFilterType.relationsQuery, 'alias.filter-type-relations-query' ], | ||
43 | + [ AliasFilterType.assetSearchQuery, 'alias.filter-type-asset-search-query' ], | ||
44 | + [ AliasFilterType.deviceSearchQuery, 'alias.filter-type-device-search-query' ], | ||
45 | + [ AliasFilterType.entityViewSearchQuery, 'alias.filter-type-entity-view-search-query' ] | ||
46 | + ] | ||
47 | +); | ||
48 | + | ||
49 | +export interface EntityAliasFilter { | ||
50 | + type: AliasFilterType; | ||
51 | + entityType: EntityType; | ||
52 | + resolveMultiple: boolean; | ||
53 | + entityList?: string[]; | ||
54 | + entityNameFilter?: string; | ||
55 | + [key: string]: any; | ||
56 | + // TODO: | ||
57 | + | ||
58 | +} | ||
59 | + | ||
60 | +export interface EntityAlias { | ||
61 | + id: string; | ||
62 | + alias: string; | ||
63 | + filter: EntityAliasFilter; | ||
64 | + [key: string]: any; | ||
65 | + // TODO: | ||
66 | +} | ||
67 | + | ||
68 | +export interface EntityAliases { | ||
69 | + [id: string]: EntityAlias; | ||
70 | +} |
@@ -14,22 +14,26 @@ | @@ -14,22 +14,26 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import {BaseData} from '@shared/models/base-data'; | ||
18 | -import {DashboardId} from '@shared/models/id/dashboard-id'; | ||
19 | -import {TenantId} from '@shared/models/id/tenant-id'; | ||
20 | -import {ShortCustomerInfo} from '@shared/models/customer.model'; | 17 | +import { BaseData } from '@shared/models/base-data'; |
18 | +import { DashboardId } from '@shared/models/id/dashboard-id'; | ||
19 | +import { TenantId } from '@shared/models/id/tenant-id'; | ||
20 | +import { ShortCustomerInfo } from '@shared/models/customer.model'; | ||
21 | +import { Widget } from './widget.models'; | ||
22 | +import { Timewindow } from '@shared/models/time/time.models'; | ||
23 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
24 | +import { EntityAlias, EntityAliases } from './alias.models'; | ||
21 | 25 | ||
22 | export interface DashboardInfo extends BaseData<DashboardId> { | 26 | export interface DashboardInfo extends BaseData<DashboardId> { |
23 | - tenantId: TenantId; | ||
24 | - title: string; | ||
25 | - assignedCustomers: Array<ShortCustomerInfo>; | 27 | + tenantId?: TenantId; |
28 | + title?: string; | ||
29 | + assignedCustomers?: Array<ShortCustomerInfo>; | ||
26 | } | 30 | } |
27 | 31 | ||
28 | export interface WidgetLayout { | 32 | export interface WidgetLayout { |
29 | sizeX: number; | 33 | sizeX: number; |
30 | sizeY: number; | 34 | sizeY: number; |
31 | - mobileHeight: number; | ||
32 | - mobileOrder: number; | 35 | + mobileHeight?: number; |
36 | + mobileOrder?: number; | ||
33 | col: number; | 37 | col: number; |
34 | row: number; | 38 | row: number; |
35 | } | 39 | } |
@@ -38,13 +42,59 @@ export interface WidgetLayouts { | @@ -38,13 +42,59 @@ export interface WidgetLayouts { | ||
38 | [id: string]: WidgetLayout; | 42 | [id: string]: WidgetLayout; |
39 | } | 43 | } |
40 | 44 | ||
45 | +export interface GridSettings { | ||
46 | + backgroundColor?: string; | ||
47 | + color?: string; | ||
48 | + columns?: number; | ||
49 | + margins?: [number, number]; | ||
50 | + backgroundSizeMode?: string; | ||
51 | + [key: string]: any; | ||
52 | + // TODO: | ||
53 | +} | ||
54 | + | ||
55 | +export interface DashboardLayout { | ||
56 | + widgets: WidgetLayouts; | ||
57 | + gridSettings: GridSettings; | ||
58 | +} | ||
59 | + | ||
60 | +export declare type DashboardLayoutId = 'main' | 'right'; | ||
61 | + | ||
62 | +export interface DashboardStateLayouts { | ||
63 | + main?: DashboardLayout; | ||
64 | + right?: DashboardLayout; | ||
65 | +} | ||
66 | + | ||
67 | +export interface DashboardState { | ||
68 | + name: string; | ||
69 | + root: boolean; | ||
70 | + layouts: DashboardStateLayouts; | ||
71 | +} | ||
72 | + | ||
73 | +export declare type StateControllerId = 'entity' | 'default' | string; | ||
74 | + | ||
75 | +export interface DashboardSettings { | ||
76 | + stateControllerId?: StateControllerId; | ||
77 | + showTitle?: boolean; | ||
78 | + showDashboardsSelect?: boolean; | ||
79 | + showEntitiesSelect?: boolean; | ||
80 | + showDashboardTimewindow?: boolean; | ||
81 | + showDashboardExport?: boolean; | ||
82 | + toolbarAlwaysOpen?: boolean; | ||
83 | + titleColor?: string; | ||
84 | +} | ||
85 | + | ||
41 | export interface DashboardConfiguration { | 86 | export interface DashboardConfiguration { |
87 | + timewindow?: Timewindow; | ||
88 | + settings?: DashboardSettings; | ||
89 | + widgets?: {[id: string]: Widget } | Widget[]; | ||
90 | + states?: {[id: string]: DashboardState }; | ||
91 | + entityAliases?: EntityAliases; | ||
42 | [key: string]: any; | 92 | [key: string]: any; |
43 | // TODO: | 93 | // TODO: |
44 | } | 94 | } |
45 | 95 | ||
46 | export interface Dashboard extends DashboardInfo { | 96 | export interface Dashboard extends DashboardInfo { |
47 | - configuration: DashboardConfiguration; | 97 | + configuration?: DashboardConfiguration; |
48 | } | 98 | } |
49 | 99 | ||
50 | export function isPublicDashboard(dashboard: DashboardInfo): boolean { | 100 | export function isPublicDashboard(dashboard: DashboardInfo): boolean { |
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { BaseData } from '@shared/models/base-data'; | ||
18 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
19 | +import { EntityId } from '@shared/models/id/entity-id'; | ||
20 | + | ||
21 | +export interface EntityInfo { | ||
22 | + origEntity?: BaseData<EntityId>; | ||
23 | + name?: string; | ||
24 | + label?: string; | ||
25 | + entityType?: EntityType; | ||
26 | + id?: string; | ||
27 | + entityDescription?: string; | ||
28 | +} |
@@ -38,6 +38,8 @@ export interface WidgetTypeTemplate { | @@ -38,6 +38,8 @@ export interface WidgetTypeTemplate { | ||
38 | 38 | ||
39 | export interface WidgetTypeData { | 39 | export interface WidgetTypeData { |
40 | name: string; | 40 | name: string; |
41 | + icon: string; | ||
42 | + isMdiIcon?: boolean; | ||
41 | template: WidgetTypeTemplate; | 43 | template: WidgetTypeTemplate; |
42 | } | 44 | } |
43 | 45 | ||
@@ -47,6 +49,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | @@ -47,6 +49,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | ||
47 | widgetType.timeseries, | 49 | widgetType.timeseries, |
48 | { | 50 | { |
49 | name: 'widget.timeseries', | 51 | name: 'widget.timeseries', |
52 | + icon: 'timeline', | ||
50 | template: { | 53 | template: { |
51 | bundleAlias: 'charts', | 54 | bundleAlias: 'charts', |
52 | alias: 'basic_timeseries' | 55 | alias: 'basic_timeseries' |
@@ -57,6 +60,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | @@ -57,6 +60,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | ||
57 | widgetType.latest, | 60 | widgetType.latest, |
58 | { | 61 | { |
59 | name: 'widget.latest-values', | 62 | name: 'widget.latest-values', |
63 | + icon: 'track_changes', | ||
60 | template: { | 64 | template: { |
61 | bundleAlias: 'cards', | 65 | bundleAlias: 'cards', |
62 | alias: 'attributes_card' | 66 | alias: 'attributes_card' |
@@ -67,6 +71,8 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | @@ -67,6 +71,8 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | ||
67 | widgetType.rpc, | 71 | widgetType.rpc, |
68 | { | 72 | { |
69 | name: 'widget.rpc', | 73 | name: 'widget.rpc', |
74 | + icon: 'mdi:developer-board', | ||
75 | + isMdiIcon: true, | ||
70 | template: { | 76 | template: { |
71 | bundleAlias: 'gpio_widgets', | 77 | bundleAlias: 'gpio_widgets', |
72 | alias: 'basic_gpio_control' | 78 | alias: 'basic_gpio_control' |
@@ -77,6 +83,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | @@ -77,6 +83,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | ||
77 | widgetType.alarm, | 83 | widgetType.alarm, |
78 | { | 84 | { |
79 | name: 'widget.alarm', | 85 | name: 'widget.alarm', |
86 | + icon: 'error', | ||
80 | template: { | 87 | template: { |
81 | bundleAlias: 'alarm_widgets', | 88 | bundleAlias: 'alarm_widgets', |
82 | alias: 'alarms_table' | 89 | alias: 'alarms_table' |
@@ -87,6 +94,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | @@ -87,6 +94,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>( | ||
87 | widgetType.static, | 94 | widgetType.static, |
88 | { | 95 | { |
89 | name: 'widget.static', | 96 | name: 'widget.static', |
97 | + icon: 'font_download', | ||
90 | template: { | 98 | template: { |
91 | bundleAlias: 'cards', | 99 | bundleAlias: 'cards', |
92 | alias: 'html_card' | 100 | alias: 'html_card' |
@@ -214,7 +222,7 @@ export enum DatasourceType { | @@ -214,7 +222,7 @@ export enum DatasourceType { | ||
214 | } | 222 | } |
215 | 223 | ||
216 | export interface Datasource { | 224 | export interface Datasource { |
217 | - type: DatasourceType; | 225 | + type?: DatasourceType | any; |
218 | name?: string; | 226 | name?: string; |
219 | dataKeys?: Array<DataKey>; | 227 | dataKeys?: Array<DataKey>; |
220 | entityType?: EntityType; | 228 | entityType?: EntityType; |
@@ -333,7 +341,7 @@ export interface WidgetConfig { | @@ -333,7 +341,7 @@ export interface WidgetConfig { | ||
333 | 341 | ||
334 | export interface Widget { | 342 | export interface Widget { |
335 | id?: string; | 343 | id?: string; |
336 | - typeId: WidgetTypeId; | 344 | + typeId?: WidgetTypeId; |
337 | isSystemType: boolean; | 345 | isSystemType: boolean; |
338 | bundleAlias: string; | 346 | bundleAlias: string; |
339 | typeAlias: string; | 347 | typeAlias: string; |
@@ -14,9 +14,9 @@ | @@ -14,9 +14,9 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -export type WindowMessageType = 'widgetException'; | 17 | +export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated'; |
18 | 18 | ||
19 | export interface WindowMessage { | 19 | export interface WindowMessage { |
20 | type: WindowMessageType; | 20 | type: WindowMessageType; |
21 | - data: any; | 21 | + data?: any; |
22 | } | 22 | } |
@@ -14,12 +14,12 @@ | @@ -14,12 +14,12 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import {NgModule} from '@angular/core'; | ||
18 | -import {CommonModule, DatePipe} from '@angular/common'; | ||
19 | -import {FooterComponent} from './components/footer.component'; | ||
20 | -import {LogoComponent} from './components/logo.component'; | ||
21 | -import {TbSnackBarComponent, ToastDirective} from './components/toast.directive'; | ||
22 | -import {BreadcrumbComponent} from '@app/shared/components/breadcrumb.component'; | 17 | +import { NgModule } from '@angular/core'; |
18 | +import { CommonModule, DatePipe } from '@angular/common'; | ||
19 | +import { FooterComponent } from './components/footer.component'; | ||
20 | +import { LogoComponent } from './components/logo.component'; | ||
21 | +import { TbSnackBarComponent, ToastDirective } from './components/toast.directive'; | ||
22 | +import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component'; | ||
23 | 23 | ||
24 | import { | 24 | import { |
25 | MatAutocompleteModule, | 25 | MatAutocompleteModule, |
@@ -51,43 +51,49 @@ import { | @@ -51,43 +51,49 @@ import { | ||
51 | MatToolbarModule, | 51 | MatToolbarModule, |
52 | MatTooltipModule | 52 | MatTooltipModule |
53 | } from '@angular/material'; | 53 | } from '@angular/material'; |
54 | -import {MatDatetimepickerModule, MatNativeDatetimeModule} from '@mat-datetimepicker/core'; | ||
55 | -import {GridsterModule} from 'angular-gridster2'; | ||
56 | -import {FlexLayoutModule} from '@angular/flex-layout'; | ||
57 | -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; | ||
58 | -import {RouterModule} from '@angular/router'; | ||
59 | -import {ShareModule as ShareButtonsModule} from '@ngx-share/core'; | ||
60 | -import {UserMenuComponent} from '@shared/components/user-menu.component'; | ||
61 | -import {NospacePipe} from './pipe/nospace.pipe'; | ||
62 | -import {TranslateModule} from '@ngx-translate/core'; | ||
63 | -import {TbCheckboxComponent} from '@shared/components/tb-checkbox.component'; | ||
64 | -import {HelpComponent} from '@shared/components/help.component'; | ||
65 | -import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; | ||
66 | -import {MillisecondsToTimeStringPipe} from '@shared/pipe/milliseconds-to-time-string.pipe'; | ||
67 | -import {TimewindowComponent} from '@shared/components/time/timewindow.component'; | ||
68 | -import {OverlayModule} from '@angular/cdk/overlay'; | ||
69 | -import {TimewindowPanelComponent} from '@shared/components/time/timewindow-panel.component'; | ||
70 | -import {TimeintervalComponent} from '@shared/components/time/timeinterval.component'; | ||
71 | -import {DatetimePeriodComponent} from '@shared/components/time/datetime-period.component'; | ||
72 | -import {EnumToArrayPipe} from '@shared/pipe/enum-to-array.pipe'; | ||
73 | -import {ClipboardModule} from 'ngx-clipboard'; | 54 | +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; |
55 | +import { GridsterModule } from 'angular-gridster2'; | ||
56 | +import { FlexLayoutModule } from '@angular/flex-layout'; | ||
57 | +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
58 | +import { RouterModule } from '@angular/router'; | ||
59 | +import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; | ||
60 | +import { HotkeyModule } from 'angular2-hotkeys'; | ||
61 | +import { UserMenuComponent } from '@shared/components/user-menu.component'; | ||
62 | +import { NospacePipe } from './pipe/nospace.pipe'; | ||
63 | +import { TranslateModule } from '@ngx-translate/core'; | ||
64 | +import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; | ||
65 | +import { HelpComponent } from '@shared/components/help.component'; | ||
66 | +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | ||
67 | +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; | ||
68 | +import { TimewindowComponent } from '@shared/components/time/timewindow.component'; | ||
69 | +import { OverlayModule } from '@angular/cdk/overlay'; | ||
70 | +import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; | ||
71 | +import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; | ||
72 | +import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; | ||
73 | +import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; | ||
74 | +import { ClipboardModule } from 'ngx-clipboard'; | ||
74 | import { ValueInputComponent } from '@shared/components/value-input.component'; | 75 | import { ValueInputComponent } from '@shared/components/value-input.component'; |
75 | -import {FullscreenDirective} from '@shared/components/fullscreen.directive'; | ||
76 | -import {HighlightPipe} from '@shared/pipe/highlight.pipe'; | ||
77 | -import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; | ||
78 | -import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; | ||
79 | -import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; | ||
80 | -import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; | ||
81 | -import {EntityListComponent} from '@shared/components/entity/entity-list.component'; | ||
82 | -import {EntityTypeSelectComponent} from './components/entity/entity-type-select.component'; | ||
83 | -import {EntitySelectComponent} from './components/entity/entity-select.component'; | ||
84 | -import {DatetimeComponent} from '@shared/components/time/datetime.component'; | ||
85 | -import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; | ||
86 | -import {SocialSharePanelComponent} from './components/socialshare-panel.component'; | 76 | +import { FullscreenDirective } from '@shared/components/fullscreen.directive'; |
77 | +import { HighlightPipe } from '@shared/pipe/highlight.pipe'; | ||
78 | +import { DashboardAutocompleteComponent } from '@shared/components/dashboard-autocomplete.component'; | ||
79 | +import { EntitySubTypeAutocompleteComponent } from '@shared/components/entity/entity-subtype-autocomplete.component'; | ||
80 | +import { EntitySubTypeSelectComponent } from './components/entity/entity-subtype-select.component'; | ||
81 | +import { EntityAutocompleteComponent } from './components/entity/entity-autocomplete.component'; | ||
82 | +import { EntityListComponent } from '@shared/components/entity/entity-list.component'; | ||
83 | +import { EntityTypeSelectComponent } from './components/entity/entity-type-select.component'; | ||
84 | +import { EntitySelectComponent } from './components/entity/entity-select.component'; | ||
85 | +import { DatetimeComponent } from '@shared/components/time/datetime.component'; | ||
86 | +import { EntityKeysListComponent } from './components/entity/entity-keys-list.component'; | ||
87 | +import { SocialSharePanelComponent } from './components/socialshare-panel.component'; | ||
87 | import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; | 88 | import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; |
88 | import { EntityListSelectComponent } from './components/entity/entity-list-select.component'; | 89 | import { EntityListSelectComponent } from './components/entity/entity-list-select.component'; |
89 | import { JsonObjectEditComponent } from './components/json-object-edit.component'; | 90 | import { JsonObjectEditComponent } from './components/json-object-edit.component'; |
90 | import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons.component'; | 91 | import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons.component'; |
92 | +import { CircularProgressDirective } from './components/circular-progress.directive'; | ||
93 | +import { MatSpinner } from '@angular/material/progress-spinner'; | ||
94 | +import { FabToolbarComponent, FabActionsDirective, FabTriggerDirective } from './components/fab-toolbar.component'; | ||
95 | +import { DashboardSelectPanelComponent } from '@shared/components/dashboard-select-panel.component'; | ||
96 | +import { DashboardSelectComponent } from '@shared/components/dashboard-select.component'; | ||
91 | 97 | ||
92 | @NgModule({ | 98 | @NgModule({ |
93 | providers: [ | 99 | providers: [ |
@@ -100,6 +106,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -100,6 +106,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
100 | TbSnackBarComponent, | 106 | TbSnackBarComponent, |
101 | TbAnchorComponent, | 107 | TbAnchorComponent, |
102 | TimewindowPanelComponent, | 108 | TimewindowPanelComponent, |
109 | + DashboardSelectPanelComponent, | ||
110 | + MatSpinner | ||
103 | ], | 111 | ], |
104 | declarations: [ | 112 | declarations: [ |
105 | FooterComponent, | 113 | FooterComponent, |
@@ -107,6 +115,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -107,6 +115,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
107 | FooterFabButtonsComponent, | 115 | FooterFabButtonsComponent, |
108 | ToastDirective, | 116 | ToastDirective, |
109 | FullscreenDirective, | 117 | FullscreenDirective, |
118 | + CircularProgressDirective, | ||
110 | TbAnchorComponent, | 119 | TbAnchorComponent, |
111 | HelpComponent, | 120 | HelpComponent, |
112 | TbCheckboxComponent, | 121 | TbCheckboxComponent, |
@@ -116,6 +125,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -116,6 +125,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
116 | TimewindowComponent, | 125 | TimewindowComponent, |
117 | TimewindowPanelComponent, | 126 | TimewindowPanelComponent, |
118 | TimeintervalComponent, | 127 | TimeintervalComponent, |
128 | + DashboardSelectComponent, | ||
129 | + DashboardSelectPanelComponent, | ||
119 | DatetimePeriodComponent, | 130 | DatetimePeriodComponent, |
120 | DatetimeComponent, | 131 | DatetimeComponent, |
121 | ValueInputComponent, | 132 | ValueInputComponent, |
@@ -131,6 +142,9 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -131,6 +142,9 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
131 | RelationTypeAutocompleteComponent, | 142 | RelationTypeAutocompleteComponent, |
132 | SocialSharePanelComponent, | 143 | SocialSharePanelComponent, |
133 | JsonObjectEditComponent, | 144 | JsonObjectEditComponent, |
145 | + FabTriggerDirective, | ||
146 | + FabActionsDirective, | ||
147 | + FabToolbarComponent, | ||
134 | NospacePipe, | 148 | NospacePipe, |
135 | MillisecondsToTimeStringPipe, | 149 | MillisecondsToTimeStringPipe, |
136 | EnumToArrayPipe, | 150 | EnumToArrayPipe, |
@@ -176,7 +190,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -176,7 +190,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
176 | FormsModule, | 190 | FormsModule, |
177 | ReactiveFormsModule, | 191 | ReactiveFormsModule, |
178 | OverlayModule, | 192 | OverlayModule, |
179 | - ShareButtonsModule | 193 | + ShareButtonsModule, |
194 | + HotkeyModule | ||
180 | ], | 195 | ], |
181 | exports: [ | 196 | exports: [ |
182 | FooterComponent, | 197 | FooterComponent, |
@@ -184,6 +199,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -184,6 +199,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
184 | FooterFabButtonsComponent, | 199 | FooterFabButtonsComponent, |
185 | ToastDirective, | 200 | ToastDirective, |
186 | FullscreenDirective, | 201 | FullscreenDirective, |
202 | + CircularProgressDirective, | ||
187 | TbAnchorComponent, | 203 | TbAnchorComponent, |
188 | HelpComponent, | 204 | HelpComponent, |
189 | TbCheckboxComponent, | 205 | TbCheckboxComponent, |
@@ -192,6 +208,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -192,6 +208,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
192 | TimewindowComponent, | 208 | TimewindowComponent, |
193 | TimewindowPanelComponent, | 209 | TimewindowPanelComponent, |
194 | TimeintervalComponent, | 210 | TimeintervalComponent, |
211 | + DashboardSelectComponent, | ||
195 | DatetimePeriodComponent, | 212 | DatetimePeriodComponent, |
196 | DatetimeComponent, | 213 | DatetimeComponent, |
197 | DashboardAutocompleteComponent, | 214 | DashboardAutocompleteComponent, |
@@ -206,6 +223,9 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -206,6 +223,9 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
206 | RelationTypeAutocompleteComponent, | 223 | RelationTypeAutocompleteComponent, |
207 | SocialSharePanelComponent, | 224 | SocialSharePanelComponent, |
208 | JsonObjectEditComponent, | 225 | JsonObjectEditComponent, |
226 | + FabTriggerDirective, | ||
227 | + FabActionsDirective, | ||
228 | + FabToolbarComponent, | ||
209 | ValueInputComponent, | 229 | ValueInputComponent, |
210 | MatButtonModule, | 230 | MatButtonModule, |
211 | MatCheckboxModule, | 231 | MatCheckboxModule, |
@@ -244,6 +264,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | @@ -244,6 +264,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons | ||
244 | ReactiveFormsModule, | 264 | ReactiveFormsModule, |
245 | OverlayModule, | 265 | OverlayModule, |
246 | ShareButtonsModule, | 266 | ShareButtonsModule, |
267 | + HotkeyModule, | ||
247 | NospacePipe, | 268 | NospacePipe, |
248 | MillisecondsToTimeStringPipe, | 269 | MillisecondsToTimeStringPipe, |
249 | EnumToArrayPipe, | 270 | EnumToArrayPipe, |
104 Bytes
91 Bytes
@@ -169,6 +169,25 @@ section.tb-header-buttons { | @@ -169,6 +169,25 @@ section.tb-header-buttons { | ||
169 | } | 169 | } |
170 | } | 170 | } |
171 | 171 | ||
172 | +section.tb-footer-buttons { | ||
173 | + position: fixed; | ||
174 | + right: 20px; | ||
175 | + bottom: 20px; | ||
176 | + z-index: 30; | ||
177 | + pointer-events: none; | ||
178 | + | ||
179 | + .tb-btn-footer { | ||
180 | + margin: 6px 8px; | ||
181 | + position: relative !important; | ||
182 | + display: inline-block !important; | ||
183 | + animation: tbMoveFromBottomFade .3s ease both; | ||
184 | + &.tb-hide { | ||
185 | + animation: tbMoveToBottomFade .3s ease both; | ||
186 | + } | ||
187 | + } | ||
188 | +} | ||
189 | + | ||
190 | + | ||
172 | .tb-details-buttons { | 191 | .tb-details-buttons { |
173 | button { | 192 | button { |
174 | margin: 6px 8px; | 193 | margin: 6px 8px; |
@@ -293,7 +312,7 @@ pre.tb-highlight { | @@ -293,7 +312,7 @@ pre.tb-highlight { | ||
293 | } | 312 | } |
294 | 313 | ||
295 | .tb-fullscreen-parent { | 314 | .tb-fullscreen-parent { |
296 | - background: #fff; | 315 | + background: #eee; |
297 | } | 316 | } |
298 | 317 | ||
299 | mat-label { | 318 | mat-label { |
@@ -166,9 +166,52 @@ $tb-dark-theme: get-tb-dark-theme( | @@ -166,9 +166,52 @@ $tb-dark-theme: get-tb-dark-theme( | ||
166 | $tb-accent | 166 | $tb-accent |
167 | ); | 167 | ); |
168 | 168 | ||
169 | +@mixin mat-fab-toolbar-theme($theme) { | ||
170 | + $primary: map-get($theme, primary); | ||
171 | + $accent: map-get($theme, accent); | ||
172 | + $warn: map-get($theme, warn); | ||
173 | + $background: map-get($theme, background); | ||
174 | + $foreground: map-get($theme, foreground); | ||
175 | + | ||
176 | + mat-fab-toolbar { | ||
177 | + .mat-fab-toolbar-background { | ||
178 | + background: mat-color($background, app-bar); | ||
179 | + color: mat-color($foreground, text); | ||
180 | + } | ||
181 | + &.mat-primary { | ||
182 | + .mat-fab-toolbar-background { | ||
183 | + @include _mat-toolbar-color($primary); | ||
184 | + } | ||
185 | + } | ||
186 | + &.mat-accent { | ||
187 | + .mat-fab-toolbar-background { | ||
188 | + @include _mat-toolbar-color($accent); | ||
189 | + } | ||
190 | + } | ||
191 | + &.mat-warn { | ||
192 | + .mat-fab-toolbar-background { | ||
193 | + @include _mat-toolbar-color($warn); | ||
194 | + } | ||
195 | + } | ||
196 | + } | ||
197 | +} | ||
198 | + | ||
199 | +@mixin tb-components-theme($theme) { | ||
200 | + $primary: map-get($theme, primary); | ||
201 | + | ||
202 | + mat-toolbar{ | ||
203 | + &.mat-hue-3 { | ||
204 | + background-color: mat-color($primary, 'A100'); | ||
205 | + } | ||
206 | + } | ||
207 | + | ||
208 | + @include mat-fab-toolbar-theme($tb-theme); | ||
209 | +} | ||
210 | + | ||
169 | .tb-default { | 211 | .tb-default { |
170 | @include angular-material-theme($tb-theme); | 212 | @include angular-material-theme($tb-theme); |
171 | @include mat-datetimepicker-theme($tb-theme); | 213 | @include mat-datetimepicker-theme($tb-theme); |
214 | + @include tb-components-theme($tb-theme); | ||
172 | } | 215 | } |
173 | 216 | ||
174 | .tb-dark { | 217 | .tb-dark { |
@@ -388,11 +431,13 @@ $tb-dark-theme: get-tb-dark-theme( | @@ -388,11 +431,13 @@ $tb-dark-theme: get-tb-dark-theme( | ||
388 | width: 32px; | 431 | width: 32px; |
389 | height: 32px; | 432 | height: 32px; |
390 | line-height: 32px; | 433 | line-height: 32px; |
434 | + padding: 0 !important; | ||
391 | } | 435 | } |
392 | &.tb-mat-96 { | 436 | &.tb-mat-96 { |
393 | width: 96px; | 437 | width: 96px; |
394 | height: 96px; | 438 | height: 96px; |
395 | line-height: 96px; | 439 | line-height: 96px; |
440 | + padding: 0 !important; | ||
396 | } | 441 | } |
397 | } | 442 | } |
398 | 443 | ||
@@ -549,6 +594,27 @@ $tb-dark-theme: get-tb-dark-theme( | @@ -549,6 +594,27 @@ $tb-dark-theme: get-tb-dark-theme( | ||
549 | right: 0; | 594 | right: 0; |
550 | } | 595 | } |
551 | 596 | ||
597 | + .tb-progress-cover { | ||
598 | + position: absolute; | ||
599 | + top: 0; | ||
600 | + right: 0; | ||
601 | + bottom: 0; | ||
602 | + left: 0; | ||
603 | + z-index: 6; | ||
604 | + background-color: #eee; | ||
605 | + opacity: 1; | ||
606 | + } | ||
607 | + | ||
608 | + .mat-button.tb-fullscreen-button-style, | ||
609 | + .tb-fullscreen-button-style { | ||
610 | + background: #ccc; | ||
611 | + opacity: .85; | ||
612 | + | ||
613 | + mat-icon { | ||
614 | + color: #666; | ||
615 | + } | ||
616 | + } | ||
617 | + | ||
552 | span.no-data-found { | 618 | span.no-data-found { |
553 | position: relative; | 619 | position: relative; |
554 | display: flex; | 620 | display: flex; |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | "extends": "../tsconfig.json", | 2 | "extends": "../tsconfig.json", |
3 | "compilerOptions": { | 3 | "compilerOptions": { |
4 | "outDir": "../out-tsc/app", | 4 | "outDir": "../out-tsc/app", |
5 | - "types": ["node", "jquery", "flot", "tinycolor2"] | 5 | + "types": ["node", "jquery", "flot", "tinycolor2", "js-beautify"] |
6 | }, | 6 | }, |
7 | "exclude": [ | 7 | "exclude": [ |
8 | "test.ts", | 8 | "test.ts", |
ui-ngx/src/typings/split.js.typings.d.ts
0 → 100644
1 | +/// | ||
2 | +/// Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +interface SplitOptions { | ||
18 | + sizes?: number[]; | ||
19 | + minSize?: number[] | number; | ||
20 | + gutterSize?: number; | ||
21 | + snapOffset?: number; | ||
22 | + direction?: 'horizontal' | 'vertical'; | ||
23 | + cursor?: 'col-resize' | 'row-resize'; | ||
24 | + gutter?: (index: number, direction: string) => HTMLElement; | ||
25 | + elementStyle?: (dimension: string, elementSize: number, gutterSize: number) => any; | ||
26 | + gutterStyle?: (dimension: string, gutterSize: number) => any; | ||
27 | + onDrag?: () => void; | ||
28 | + onDragStart?: () => void; | ||
29 | + onDragEnd?: () => void; | ||
30 | +} | ||
31 | + | ||
32 | +interface SplitObject { | ||
33 | + setSizes: (sizes: number[]) => void; | ||
34 | + getSizes: () => number[]; | ||
35 | + collapse: (index: number) => void; | ||
36 | + destroy: () => void; | ||
37 | +} | ||
38 | + | ||
39 | +declare function Split(elements: HTMLElement | string[], options?: SplitOptions): SplitObject; |
@@ -14,7 +14,8 @@ | @@ -14,7 +14,8 @@ | ||
14 | "typeRoots": [ | 14 | "typeRoots": [ |
15 | "node_modules/@types", | 15 | "node_modules/@types", |
16 | "src/typings/jquery.typings.d.ts", | 16 | "src/typings/jquery.typings.d.ts", |
17 | - "src/typings/jquery.flot.typings.d.ts" | 17 | + "src/typings/jquery.flot.typings.d.ts", |
18 | + "src/typings/split.js.typings.d.ts" | ||
18 | ], | 19 | ], |
19 | "paths": { | 20 | "paths": { |
20 | "@app/*": ["src/app/*"], | 21 | "@app/*": ["src/app/*"], |