mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 02:29:24 +00:00
Compare commits
1023 Commits
open-v0.7.
...
prototype-
Author | SHA1 | Date | |
---|---|---|---|
5013646e89 | |||
9925b91405 | |||
fdfb524eef | |||
ef250f58de | |||
15ed91f651 | |||
074254a513 | |||
4c84789d5d | |||
92573b817f | |||
5382cca435 | |||
15c1bf20ab | |||
685dd2114d | |||
6e30a25a6f | |||
42fa5bfd7e | |||
fb0ce1eca8 | |||
d7bd793bf3 | |||
9cc0c0b06f | |||
c703714cb3 | |||
b48a07cd3e | |||
8c29c8ed0e | |||
5763511ec8 | |||
2accf21518 | |||
28c42fcd4c | |||
1efa97e6f5 | |||
756f728865 | |||
d8276c532b | |||
fc0bfa77db | |||
12c6e53939 | |||
4f716ad5c5 | |||
8093fcbda1 | |||
0ea5721f76 | |||
d902943552 | |||
5f7c8ccadb | |||
520d17f9db | |||
7532db5f49 | |||
ebd8fdeee3 | |||
f738f84075 | |||
cc19a0acba | |||
134b749bbf | |||
36d06e8b54 | |||
5520d90984 | |||
77b0086d18 | |||
146e948097 | |||
669b434c36 | |||
431c74ca49 | |||
cd0c0f77cc | |||
8cba321886 | |||
dd83816035 | |||
1ca2b769d9 | |||
a1d1261179 | |||
99048a4ee3 | |||
553b17fafe | |||
c4aff95341 | |||
13095b4135 | |||
fd927d4c03 | |||
445f22ccb0 | |||
1ad0bf337c | |||
73dc16d398 | |||
bebe53820f | |||
dfe909d6b5 | |||
3cf62ded08 | |||
6cbd3e5fae | |||
3050b265fb | |||
5104a7990a | |||
571beb8df2 | |||
bea5002752 | |||
d04c5e6858 | |||
0d1f3bf87a | |||
b632926d8e | |||
78f3f8367e | |||
85ac4a9a32 | |||
ef527df381 | |||
c2868a4573 | |||
77c66053f3 | |||
0891e15936 | |||
2979ee90a3 | |||
77c399f2a2 | |||
fe8543158e | |||
a4f3e0d776 | |||
866c8882ca | |||
ad7d3d642e | |||
333f7cb848 | |||
f198c281bc | |||
d6fe543c16 | |||
581dbacc9f | |||
23de3917bb | |||
badaca53d3 | |||
00ac249ee2 | |||
00aa6821d1 | |||
e33485ec59 | |||
afb1202865 | |||
5b02bc8104 | |||
f5a4a370f9 | |||
2848a8458b | |||
4f0e8ada2f | |||
cbaf45afe9 | |||
7a677062e4 | |||
a7153f320f | |||
b3da6edd0c | |||
ff1fd26efc | |||
4ced6c44a6 | |||
67f627b51f | |||
1d83516982 | |||
9e64dfe3b9 | |||
09f5fa42ab | |||
54a077a4e2 | |||
13525a67c2 | |||
cc6b6538d5 | |||
0c7de98195 | |||
404d1e7801 | |||
1214a32c26 | |||
6bd8e7a47c | |||
825d93cee3 | |||
9f7dc1da9b | |||
3d8aec2d01 | |||
928e31b548 | |||
f182d1f2c4 | |||
d238b669a5 | |||
5d5a7c26c5 | |||
0b0cee3afb | |||
0260e6fff4 | |||
9811443c71 | |||
7dc13dab66 | |||
e67a2e63cf | |||
6ce4ce6ada | |||
70bc17c79f | |||
f3aceb1644 | |||
056b0c8c5b | |||
fa60d62039 | |||
082122ddec | |||
6dbccd5000 | |||
fb0861f256 | |||
dbe1875e70 | |||
97892869ae | |||
8db334e45b | |||
4743833f7c | |||
6996883b85 | |||
119403e71c | |||
ff24f06475 | |||
597b18af1c | |||
aba0969312 | |||
b4a2bfd727 | |||
2040304980 | |||
55362f6b26 | |||
8611926533 | |||
c17269ba8b | |||
56ccf29cfe | |||
8b7a94dcf8 | |||
3e5d294a28 | |||
480da3476e | |||
54334a8906 | |||
c9b2f5804d | |||
0c1f77cfab | |||
ae4313253c | |||
1e6c4732f1 | |||
884d36ad6d | |||
24586ae378 | |||
cf97b30084 | |||
e86e61c2ac | |||
b163b45a5e | |||
d236fd10c3 | |||
befa66a9e2 | |||
5a1c83fca8 | |||
88bb213162 | |||
ad552511c1 | |||
65368ed0c3 | |||
bf05e55d02 | |||
c057ba38a5 | |||
70bcb723ef | |||
4c5808c8c9 | |||
fa186a1556 | |||
0b4f44f00d | |||
dce7253b27 | |||
158fbf5365 | |||
1d1589da68 | |||
6aba34dd5a | |||
733a282f3f | |||
19500c2704 | |||
2090b1a958 | |||
411a4ebfbf | |||
cc3b0ecfca | |||
ac48039b90 | |||
411f0d904d | |||
d92ae4d508 | |||
bc4c7feb6c | |||
c4aed57165 | |||
88b8528aaf | |||
d1c0d81120 | |||
a233856bac | |||
9a7f435b61 | |||
f4e53a946d | |||
9fdb9f0170 | |||
f28d2f19d0 | |||
de71bde62f | |||
8f24e014e0 | |||
190f5fd0ea | |||
ad29fb0f92 | |||
acdd35cf65 | |||
fcd073c010 | |||
071368c3b9 | |||
4f5a1115d2 | |||
7a97588aa5 | |||
f776561303 | |||
e34fe1a289 | |||
70d9587c9b | |||
9a78b63065 | |||
6c497f3c36 | |||
d951b794e3 | |||
797046aca4 | |||
cf76583ed7 | |||
6f28ab0145 | |||
d981e7bb1d | |||
a3a6706869 | |||
72c122e0ee | |||
9ebf157ec0 | |||
493c63be44 | |||
f29951140f | |||
d0b5bb2d21 | |||
cd98886a43 | |||
4549828cae | |||
d0478c3433 | |||
53369ec0dc | |||
de99969f0a | |||
24449d2dcc | |||
f42c5ca1e5 | |||
8c1b70f085 | |||
c010011798 | |||
890aafc203 | |||
ecf7f4b235 | |||
3e5d46a62b | |||
01045e6ee1 | |||
febb0edede | |||
048eb20450 | |||
36e38b9454 | |||
215b4a154b | |||
47bfe40294 | |||
3a3829682c | |||
861ea8bab7 | |||
f17410a85b | |||
2a14cf2dfc | |||
62962e119e | |||
a840b59044 | |||
d14c4e08b8 | |||
de6dfe1fdc | |||
2229e868ce | |||
3b2454c6a9 | |||
8d209f4d19 | |||
86bb89a162 | |||
2758250833 | |||
7d20351a6a | |||
78fae345da | |||
4c79c9a1b1 | |||
2ec9956d44 | |||
426ab44d93 | |||
3792dab0f9 | |||
c6df7cebe5 | |||
52df7fe1e2 | |||
e3b191b5dc | |||
32b895ba62 | |||
a4dda695dd | |||
4db48afab0 | |||
0d710209b1 | |||
52c471bd3a | |||
560c8612bd | |||
6a917f5393 | |||
fdbc91131b | |||
d2dfec3ce7 | |||
351181d38e | |||
760f4b818f | |||
c026bfa17d | |||
47b97a504e | |||
86deec6364 | |||
ac41ed7d64 | |||
dafcda281a | |||
29c460556a | |||
4d276888e1 | |||
142af3db77 | |||
b66759e519 | |||
c58ffb4a52 | |||
600ff1a3ee | |||
77d11e1bcf | |||
d158aa6028 | |||
c2985d61b7 | |||
3ce40ab870 | |||
bfb19dea74 | |||
01a6d2e6a7 | |||
cc5908d26f | |||
af462ff3ee | |||
5c1d209eff | |||
8a76c3a425 | |||
9ccd0b9188 | |||
f83588d980 | |||
a481b377cb | |||
35ff4efbca | |||
436e010738 | |||
37c34c668c | |||
bf4765fcb6 | |||
dbfb8b9861 | |||
681cd0bb9c | |||
b668fb58fb | |||
f74da6b935 | |||
e4dec21ceb | |||
fc2860810b | |||
62898c921f | |||
4997fc1417 | |||
bb1a02c8bd | |||
9d6b70f433 | |||
57a947eaef | |||
a18cc50a43 | |||
91fe3d798f | |||
e873389655 | |||
678d11832c | |||
3310016264 | |||
17e9e87a2b | |||
b2649de649 | |||
b1238b0c96 | |||
ba6e542d08 | |||
8759fdbd95 | |||
d3d94d67ea | |||
b9d8b124ff | |||
8e995eba8f | |||
194bc41322 | |||
2f57f47234 | |||
59e181c0e5 | |||
8a5c61e2e6 | |||
199be71f8f | |||
ac2144d5c5 | |||
01f2fab70f | |||
7562c82369 | |||
c3efb29dfc | |||
bb738f9a93 | |||
97071fc72c | |||
b39753d0a6 | |||
e3eda5112c | |||
2cbe686732 | |||
021d105fd0 | |||
7ffbc9ad2f | |||
90f62b92a5 | |||
5a991076ea | |||
eb4ea8f2d4 | |||
9692596859 | |||
ce152881cc | |||
f40e7d7105 | |||
a8fc5ee8ed | |||
5400f51204 | |||
29c9b2a08f | |||
e4d136d345 | |||
620c0415cf | |||
54bf39344a | |||
683b3f7511 | |||
510e7d1261 | |||
63b41d796a | |||
80a91970f6 | |||
f0c64b6b92 | |||
48136c2265 | |||
1961adf8d6 | |||
5230bdfc6b | |||
97acf2012a | |||
687d86790e | |||
3ec4cc099b | |||
dadbf3f6dc | |||
c4c4c42415 | |||
8c4c65241a | |||
67cb5f8077 | |||
bb2f086623 | |||
a0169ad158 | |||
6e172359b4 | |||
d33678276c | |||
1cbca41c53 | |||
a27083c28c | |||
def5e17bb8 | |||
2cd1907b71 | |||
b8b9721ddc | |||
f80266b23f | |||
9a3009f327 | |||
2542255342 | |||
e7563ff4e9 | |||
89bfa54563 | |||
a507557cec | |||
793c99ac2f | |||
39bd292886 | |||
e8bc9ecc1a | |||
0fa330adff | |||
509badb225 | |||
9cd57614e4 | |||
d80c359a86 | |||
07d0706cd1 | |||
fd5a911d50 | |||
c71f1fe447 | |||
cd46dab5c1 | |||
e1858bf0ae | |||
7542c6d49a | |||
6571da922d | |||
13b66cd215 | |||
aca06c6007 | |||
5184e08a5b | |||
69c6d4bd26 | |||
6a2bdd103b | |||
76c4b96683 | |||
c81e2dbb4a | |||
44ed4e0e0d | |||
b860db302c | |||
e96d3e3738 | |||
f1ca9ff15e | |||
9eb23a62bc | |||
9b6a839c06 | |||
6aaa887e65 | |||
1e332da11b | |||
3a050cc5b8 | |||
039d692e4c | |||
ee608cc4a4 | |||
d0183d44c9 | |||
a7cc06a28b | |||
da8eb334e3 | |||
67592def90 | |||
3484b315cf | |||
b1e1e85bbd | |||
1e79c507c8 | |||
249a9b2eb2 | |||
02abbdf4fe | |||
08d68f99a5 | |||
f27e4d6e06 | |||
9c578c53db | |||
a4a9acd2ea | |||
c118234bbf | |||
a94763041e | |||
26892e7104 | |||
5c74365f4b | |||
94854e5965 | |||
9cf30f4213 | |||
eb776e69c0 | |||
c4dd4f5c45 | |||
33a63947e5 | |||
432ec519b6 | |||
7f65ba236c | |||
3a86f5e746 | |||
4e3e3653a3 | |||
d3ea67fbd9 | |||
9ebc04ef14 | |||
e426e0e5ec | |||
3d1e1659c2 | |||
17f2bb966b | |||
158f549df2 | |||
a98b65286f | |||
17aa666519 | |||
f6cf4c3215 | |||
86f0a9100f | |||
f4b87cf70d | |||
841273edf6 | |||
366ec3d516 | |||
a4e2aba6dc | |||
f4ae86eb53 | |||
3a932f5443 | |||
ce58aff18a | |||
24ee8ec063 | |||
eee8790682 | |||
083932e902 | |||
9ab06cfdd2 | |||
e159b7a15d | |||
4481c44c4b | |||
6d660d48ca | |||
bf417a14e0 | |||
4495c5b64a | |||
0cee5ad380 | |||
f22c0a7ee4 | |||
83fce6ad77 | |||
7b4934ec55 | |||
2ef0c2a8cb | |||
80c99d2eb6 | |||
2ccca016a5 | |||
cb0f72052f | |||
1030eff943 | |||
5e3caf6252 | |||
aa67489404 | |||
1ea6f7620e | |||
365134b085 | |||
07a2065c11 | |||
f8cb3f464c | |||
31337eaf4f | |||
3e8ea972c2 | |||
0f6287e715 | |||
0cfd881510 | |||
edca2a9f03 | |||
cc05c50769 | |||
adbb3486c5 | |||
ced1609e51 | |||
7fe866060b | |||
7bc9b83aac | |||
d6d16f8f1c | |||
bcea3832ed | |||
94662cb904 | |||
26ab9af911 | |||
f38b89a582 | |||
c17ec19f97 | |||
2ca7a175bd | |||
371b690072 | |||
c62e73f863 | |||
5726dd0e0c | |||
1c187c3914 | |||
74cf4d73d5 | |||
c8694f182a | |||
2e767c94c4 | |||
6302eee17e | |||
820c15d74c | |||
b26aa3cab7 | |||
c51856522c | |||
aefad6fdd3 | |||
8371b1b25b | |||
a9c85d5241 | |||
edf52f32ad | |||
18bc7d3637 | |||
175490e1f7 | |||
3492cd3942 | |||
ed53808556 | |||
41ddb76385 | |||
94531a39d0 | |||
55dd8cb1a8 | |||
eefc746567 | |||
0218bad9e8 | |||
dd993c81a7 | |||
3db62edf59 | |||
914c823675 | |||
a76e54483a | |||
515470fa6a | |||
cab675c8ca | |||
deb9e3e2ee | |||
3555f41f3e | |||
b6b716b767 | |||
7aea1647a2 | |||
b74cbf1969 | |||
eb10cbf87c | |||
7918d7b287 | |||
fde0dc2a35 | |||
85f9d5a2da | |||
ef7c1bd025 | |||
9dd520f17b | |||
a9e2d48036 | |||
0520f1c70c | |||
3b62f1a979 | |||
d701567b70 | |||
7911909c5f | |||
b93d752c88 | |||
5e4dcc1e35 | |||
9939c809be | |||
89cb6867bd | |||
98801cb5d6 | |||
48693df51f | |||
23bc246a48 | |||
11a45e4db0 | |||
c450c22ddd | |||
c19b3384ca | |||
b7765ff388 | |||
f377c7cb71 | |||
aa08db1050 | |||
a48a0820ad | |||
109d933945 | |||
7b471e5379 | |||
de291ad3b1 | |||
140d767026 | |||
be5cad212a | |||
efc42aa8f2 | |||
f8a0ddb484 | |||
3a0ba4f5a6 | |||
a77920bd18 | |||
78146d97f8 | |||
2d5ec97dc3 | |||
87aa0cfce2 | |||
afddca510c | |||
0b9b936368 | |||
23f18c799d | |||
a7a1ac70e5 | |||
31eb366e7f | |||
eaaa1a19ca | |||
c08a460d30 | |||
14f97eae9c | |||
2f79323264 | |||
1558c9d1bd | |||
03aba8bbf7 | |||
7141c2818a | |||
058a0d393d | |||
b0a23590d4 | |||
48a9eef924 | |||
62f335573a | |||
c80e9bfa26 | |||
ec62c27f36 | |||
19b80ded16 | |||
f72f88adfa | |||
f083d019a3 | |||
10ec30ba76 | |||
3fb4296d23 | |||
197ec0eb2c | |||
1d162888dd | |||
5ab26df4c9 | |||
3783ed69d7 | |||
1d2cd4745c | |||
ee2d7efae2 | |||
4a755e259f | |||
4c56e4ffdc | |||
7cad3ba0bc | |||
449db4f3a9 | |||
168a805e8e | |||
7ded288154 | |||
63a99f26f6 | |||
7ca15a9de2 | |||
7ac1d2458a | |||
0bc4f2dc6e | |||
6802484ad8 | |||
dcd7d61c9a | |||
3e94eba0d1 | |||
ddae7823f9 | |||
633c00f161 | |||
be5253b9d3 | |||
320be34798 | |||
3dace28eb3 | |||
19db1c9c2b | |||
935360e2eb | |||
e0a0d293fa | |||
1a4d7618c1 | |||
a2eabc1b08 | |||
f76f6548a5 | |||
44dce05ec5 | |||
e729a966c7 | |||
836c508698 | |||
1fe492cc3d | |||
8923f23f70 | |||
4c8d07bba8 | |||
37cd73c6e8 | |||
096dce1fe0 | |||
7c17581659 | |||
0a39984c4f | |||
ae763d000c | |||
a146185bd2 | |||
0b635afcf7 | |||
6407a66d30 | |||
4533262b4a | |||
6a0bd10d5b | |||
3f2a23c93b | |||
96f738b6b6 | |||
f46a0853b9 | |||
3c325b8870 | |||
7f486f739d | |||
66c81ce3d6 | |||
5fa6db72d2 | |||
6b65ae77e7 | |||
e43e14ec00 | |||
c9acfc9f89 | |||
0bf510ca08 | |||
fc0c902886 | |||
f8dd69bbfa | |||
926ee14546 | |||
66408eeec8 | |||
aea286779e | |||
55d52d060a | |||
d23f4de36a | |||
503bae661c | |||
d82be0deef | |||
eab140df48 | |||
15e39e00c2 | |||
077a0ce3e3 | |||
5711b2b241 | |||
d9a1b9d530 | |||
b6c1eadb91 | |||
56265c2277 | |||
9ea1d24121 | |||
40e85b718d | |||
bbce5c3154 | |||
c869aaf91a | |||
5b0bf0a0ba | |||
ec87ac7652 | |||
8f65c73c8a | |||
35316dc31c | |||
b6bb689ccc | |||
3d524d7572 | |||
45bedb20c1 | |||
2ca3c6ea93 | |||
9b922913a0 | |||
4803eef907 | |||
4240e8843b | |||
4439e1680d | |||
5569ef8b2d | |||
e05f16d780 | |||
ae7fc27c9e | |||
ff5e7486bc | |||
867b5f726a | |||
a4c704a5a2 | |||
e928e02b1c | |||
1cd6685b36 | |||
695582b30f | |||
d0bad46627 | |||
eb0bdba666 | |||
19b9668190 | |||
59d4c362f6 | |||
9be646f1e7 | |||
164d5485e9 | |||
7678289ead | |||
2a62494296 | |||
ae94ba73d7 | |||
560a2e035e | |||
5b3f78287e | |||
4bb000dd93 | |||
06d9b68406 | |||
eca52a8ca6 | |||
64e2de7aa6 | |||
3a3e98cc48 | |||
ee64ef5fc1 | |||
6e11941ae9 | |||
ad482600de | |||
7c89e4aa4a | |||
f8471bc944 | |||
15b7e3ac9b | |||
2983ebb2c6 | |||
25e8bb44d2 | |||
51bc7c6a7f | |||
60dad014cc | |||
1a8eece90f | |||
85658d3d1f | |||
a7cd6d8807 | |||
ddce0f371d | |||
495cd06ed5 | |||
1624866656 | |||
bce56c53ed | |||
0b3170d2ef | |||
2d1aa65d63 | |||
7e7754f9aa | |||
3a96a246a4 | |||
24682c5f7a | |||
28365602f0 | |||
2c5da44e5e | |||
a03046400c | |||
d5c835e777 | |||
af630d1b3e | |||
c333a2e70a | |||
6b613f234b | |||
1619f236cf | |||
906354764b | |||
12ec293f3d | |||
bdf8b4d3f1 | |||
5ee8dd239b | |||
6e60088b11 | |||
97bf530b1d | |||
18348476c6 | |||
7e35e55f0b | |||
f2cd55d6cd | |||
28a2a5b92a | |||
639cf1cf59 | |||
d262520594 | |||
7cc14a195b | |||
84b9e4d781 | |||
ce433a01b2 | |||
495d8c9830 | |||
52e0476d24 | |||
86340623f7 | |||
356fa73c91 | |||
a073ef69ac | |||
3c6c420023 | |||
2ffe1cdff9 | |||
32b58715c6 | |||
1a9dd2f144 | |||
21964fe68e | |||
62714bb97c | |||
2a4943f584 | |||
e32403a75f | |||
d6a7dc9820 | |||
dc53cf60d1 | |||
e0e5ad1347 | |||
a6b11354cd | |||
f7c0cb6214 | |||
561d671181 | |||
d569b13101 | |||
b0c42c12b7 | |||
621ccc25ec | |||
617df739ee | |||
6e43a92191 | |||
84486f2086 | |||
dc79f460e5 | |||
7cac858697 | |||
a3977a18f0 | |||
5e858f73d5 | |||
a9418fd2c7 | |||
1d7a0fa48d | |||
30c530178a | |||
3d0795cde3 | |||
181fb32a2a | |||
232a648fbd | |||
d35a149108 | |||
c85a3787c0 | |||
066258ab83 | |||
ddc2295ec3 | |||
011e6fc512 | |||
dca0014862 | |||
6b02295bf4 | |||
05c43db6fe | |||
9143f5febd | |||
91bd58215a | |||
b37ee19fbc | |||
2355d354b3 | |||
300280e03e | |||
200c6e49fc | |||
a89f9eed42 | |||
341218e8f6 | |||
33e8084852 | |||
7d495f6389 | |||
f4d34a2a9c | |||
0feab1965c | |||
7993e4c03f | |||
b592b89dc8 | |||
d115166dde | |||
d176f9f811 | |||
aa17d3034e | |||
52a9fcf5d6 | |||
143e3eeb6c | |||
7b271dcb6b | |||
2680e466fa | |||
29d5535e7c | |||
ebb5474b34 | |||
4759162bb7 | |||
deba184103 | |||
c65a278fcf | |||
50ce800c2a | |||
1a1eecb4c3 | |||
b3bc8b6876 | |||
8c08f7b93e | |||
501e426868 | |||
8f1f4eb177 | |||
b0e7dca985 | |||
b16af5fe3e | |||
1f7ba70ad7 | |||
1dbe039be8 | |||
d82538a799 | |||
57d45b24be | |||
dcd4006017 | |||
7aba3b6672 | |||
3ae681c79c | |||
ab3d3ec45b | |||
ff3770c9ee | |||
bcc1e2e26f | |||
926b3d075c | |||
b5f8e6d90c | |||
827cb27f28 | |||
739fa423c5 | |||
5f30249065 | |||
4191bc1ac3 | |||
92df11acc1 | |||
416bd82589 | |||
7934e8d425 | |||
0842f464db | |||
5a93e5a2bc | |||
56e51ea32a | |||
41c38f202d | |||
f0f396f656 | |||
a5febf8f0f | |||
4f4af87285 | |||
1b5fbccc06 | |||
fc123cd367 | |||
a0f4b98eed | |||
7adcfc221a | |||
c1dcd8ea5b | |||
74961e1106 | |||
c79d1f2648 | |||
f4bd7d7a44 | |||
1891b24bde | |||
5520037908 | |||
c0c0371451 | |||
ed956d351d | |||
e9e9ca146b | |||
dcdafbaebf | |||
f98915cddd | |||
4e6c307684 | |||
ce6d74390e | |||
6e406fd060 | |||
2614427e0e | |||
e8b662571b | |||
2a784115d5 | |||
9f5f14826b | |||
c75d94289b | |||
272c6bca97 | |||
0d7387080d | |||
492dbcbc51 | |||
1e2e20b145 | |||
c82163fa7b | |||
cf3169dd68 | |||
c1ebd50e4c | |||
488829a20c | |||
85c7a36e25 | |||
442a1979e7 | |||
d99b4d75d8 | |||
922a724e36 | |||
15a88967d0 | |||
15a9d2380c | |||
80e0bd875a | |||
6e6fbe0d65 | |||
180e5f14ae | |||
e9314898d2 | |||
bc2072b8c6 | |||
25208a9074 | |||
ce75d19480 | |||
cd3c2312a5 | |||
6f2ad0dadc | |||
1e05ebbfd7 | |||
c5c3546b92 | |||
a9dd1f9828 | |||
4b5540830b | |||
672fa74321 | |||
3fcf061679 | |||
ee35976c92 | |||
b94ef695e2 | |||
54b6cd1100 | |||
759eaa3b35 | |||
eee9309650 | |||
e634d77a8e | |||
06b26b799e | |||
d6720884f1 | |||
eb5946c1c1 | |||
227cb42ba7 | |||
3420eb69d3 | |||
6b3088f241 | |||
fdcc6432f1 | |||
f8e028a5d1 | |||
5e6f1214fb | |||
0371e2e314 | |||
0f89e98a71 | |||
6f07a21bb8 | |||
dcb5af87a7 | |||
f3678d9d52 | |||
35d038e826 | |||
27c7410318 | |||
7c627fae8f | |||
bf0e6692f5 | |||
1d0d2302d8 | |||
1a8fd38430 | |||
503811f69c | |||
5892594668 | |||
b9f8f6e33d | |||
457193657f | |||
c92d15ba42 | |||
d7dd53c2da | |||
b35fddefc0 | |||
07e08c283a | |||
89d0ea6506 | |||
75ce5cd0e9 | |||
9a7bbd92bd | |||
b994b2a862 | |||
3cb0b41b22 | |||
edcafc5835 | |||
7d09df9a85 | |||
b00eee00fc | |||
c0d83f9395 | |||
be757066f5 | |||
5980575918 | |||
409d12aa75 | |||
885433390e | |||
687f810475 | |||
137a60f510 | |||
2432e9c2aa | |||
46d5a1431f | |||
404d02ec23 | |||
03c0678cbe | |||
04257aedae | |||
33b30f7583 | |||
8eda495aa7 | |||
a337e04fae | |||
2e7f23a766 | |||
6d1cb85a07 | |||
b5756d2b99 | |||
eec955317a | |||
efba0f0236 | |||
67890a7298 | |||
dd457f26c6 | |||
85c6bda5c9 | |||
b7b5f87002 | |||
b0c5d807e7 | |||
4d4776e0ef | |||
61e1aeb1d8 | |||
9caa603a65 | |||
5c99e469d5 | |||
4d9dc3624b | |||
d3ae4b729f | |||
4e39c4f900 | |||
30bed434fe | |||
3e4ae4d38d | |||
7414275a85 | |||
fcb0033864 | |||
f079471a18 | |||
1fb1174c0a | |||
635e1eda69 | |||
40d53f941f | |||
e339ee1dd1 | |||
684a0e88a2 | |||
30a4f15330 | |||
dfd08000f1 | |||
94b306e129 | |||
82c8d26264 | |||
bd236e1cb1 | |||
e47a36e799 | |||
30efec0090 | |||
b9371ea03d | |||
7974f33781 | |||
32eaf3893a | |||
cdcaedc8dd | |||
845b9a2faa | |||
5870248617 | |||
07ef4dfe8a | |||
342be0822f | |||
5c56484889 | |||
2b4162c0be | |||
3704d64560 | |||
24fae72492 | |||
79fb6eabd9 | |||
1ddb00c8d6 | |||
8a0b77ec5c | |||
2fc447e16f | |||
d828bf59f9 | |||
d8806f14aa | |||
0c6a9ca857 | |||
07c907b315 | |||
d343d38037 | |||
7e89c13839 | |||
2a032bf66d | |||
e0727e8485 | |||
d5d7ac90ac | |||
3e5fa30d9d | |||
73241efdc8 | |||
cc470f671a | |||
933927cda1 |
12
.gitignore
vendored
12
.gitignore
vendored
@ -3,8 +3,13 @@
|
||||
*.gzip
|
||||
*.tgz
|
||||
*.DS_Store
|
||||
*.idea
|
||||
|
||||
*.sass-cache
|
||||
*COMPILE.css
|
||||
|
||||
# Intellij project configuration files
|
||||
*.idea
|
||||
*.iml
|
||||
|
||||
# External dependencies
|
||||
|
||||
@ -20,5 +25,6 @@ closed-lib
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Build documentation
|
||||
docs
|
||||
# Protractor logs
|
||||
protractor/logs
|
||||
|
||||
|
301
CONTRIBUTING.md
Normal file
301
CONTRIBUTING.md
Normal file
@ -0,0 +1,301 @@
|
||||
# Contributing to Open MCT Web
|
||||
|
||||
This document describes the process of contributing to Open MCT Web as well
|
||||
as the standards that will be applied when evaluating contributions.
|
||||
|
||||
Please be aware that additional agreements will be necessary before we can
|
||||
accept changes from external contributors.
|
||||
|
||||
## Summary
|
||||
|
||||
The short version:
|
||||
|
||||
1. Write your contribution.
|
||||
2. Make sure your contribution meets code, test, and commit message
|
||||
standards as described below.
|
||||
3. Submit a pull request from a topic branch back to `master`. Include a check
|
||||
list, as described below. (Optionally, assign this to a specific member
|
||||
for review.)
|
||||
4. Respond to any discussion. When the reviewer decides it's ready, they
|
||||
will merge back `master` and fill out their own check list.
|
||||
|
||||
## Contribution Process
|
||||
|
||||
Open MCT Web uses git for software version control, and for branching and
|
||||
merging. The central repository is at
|
||||
https://github.com/nasa/openmctweb.git.
|
||||
|
||||
### Roles
|
||||
|
||||
References to roles are made throughout this document. These are not intended
|
||||
to reflect titles or long-term job assignments; rather, these are used as
|
||||
descriptors to refer to members of the development team performing tasks in
|
||||
the check-in process. These roles are:
|
||||
|
||||
* _Author_: The individual who has made changes to files in the software
|
||||
repository, and wishes to check these in.
|
||||
* _Reviewer_: The individual who reviews changes to files before they are
|
||||
checked in.
|
||||
* _Integrator_: The individual who performs the task of merging these files.
|
||||
Usually the reviewer.
|
||||
|
||||
### Branching
|
||||
|
||||
Three basic types of branches may be included in the above repository:
|
||||
|
||||
1. Master branch.
|
||||
2. Topic branches.
|
||||
3. Developer branches.
|
||||
|
||||
Branches which do not fit into the above categories may be created and used
|
||||
during the course of development for various reasons, such as large-scale
|
||||
refactoring of code or implementation of complex features which may cause
|
||||
instability. In these exceptional cases it is the responsibility of the
|
||||
developer who initiates the task which motivated this branching to
|
||||
communicate to the team the role of these branches and any associated
|
||||
procedures for the duration of their use.
|
||||
|
||||
#### Master Branch
|
||||
|
||||
The role of the `master` branches is to represent the latest
|
||||
"ready for test" version of the software. Source code on the master
|
||||
branch has undergone peer review, and will undergo regular automated
|
||||
testing with notification on failure. Master branches may be unstable
|
||||
(particularly for recent features), but the intent is for the stability of
|
||||
any features on master branches to be non-decreasing. It is the shared
|
||||
responsibility of authors, reviewers, and integrators to ensure this.
|
||||
|
||||
#### Topic Branches
|
||||
|
||||
Topic branches are used by developers to perform and record work on issues.
|
||||
|
||||
Topic branches need not necessarily be stable, even when pushed to the
|
||||
central repository; in fact, the practice of making incremental commits
|
||||
while working on an issue and pushing these to the central repository is
|
||||
encouraged, to avoid lost work and to share work-in-progress. (Small commits
|
||||
also help isolate changes, which can help in identifying which change
|
||||
introduced a defect, particularly when that defect went unnoticed for some
|
||||
time, e.g. using `git bisect`.)
|
||||
|
||||
Topic branches should be named according to their corresponding issue
|
||||
identifiers, all lower case, without hyphens. (e.g. branch mct9 would refer
|
||||
to issue #9.)
|
||||
|
||||
In some cases, work on an issue may warrant the use of multiple divergent
|
||||
branches; for instance, when a developer wants to try more than one solution
|
||||
and compare them, or when a "dead end" is reached and an initial approach to
|
||||
resolving an issue needs to be abandoned. In these cases, a short suffix
|
||||
should be added to the additional branches; this may be simply a single
|
||||
character (e.g. wtd481b) or, where useful, a descriptive term for what
|
||||
distinguishes the branches (e.g. wtd481verbose). It is the responsibility of
|
||||
the author to communicate which branch is intended to be merged to both the
|
||||
reviewer and the integrator.
|
||||
|
||||
#### Developer Branches
|
||||
|
||||
Developer branches are any branches used for purposes outside of the scope
|
||||
of the above; e.g. to try things out, or maintain a "my latest stuff"
|
||||
branch that is not delayed by the review and integration process. These
|
||||
may be pushed to the central repository, and may follow any naming convention
|
||||
desired so long as the owner of the branch is identifiable, and so long as
|
||||
the name chosen could not be mistaken for a topic or master branch.
|
||||
|
||||
### Merging
|
||||
|
||||
When development is complete on an issue, the first step toward merging it
|
||||
back into the master branch is to file a Pull Request. The contributions
|
||||
should meet code, test, and commit message standards as described below,
|
||||
and the pull request should include a completed author checklist, also
|
||||
as described below. Pull requests may be assigned to specific team
|
||||
members when appropriate (e.g. to draw to a specific person's attention.)
|
||||
|
||||
Code review should take place using discussion features within the pull
|
||||
request. When the reviewer is satisfied, they should add a comment to
|
||||
the pull request containing the reviewer checklist (from below) and complete
|
||||
the merge back to the master branch.
|
||||
|
||||
## Standards
|
||||
|
||||
Contributions to Open MCT Web are expected to meet the following standards.
|
||||
In addition, reviewers should use general discretion before accepting
|
||||
changes.
|
||||
|
||||
### Code Standards
|
||||
|
||||
JavaScript sources in Open MCT Web must satisfy JSLint under its default
|
||||
settings. This is verified by the command line build.
|
||||
|
||||
#### Code Guidelines
|
||||
|
||||
JavaScript sources in Open MCT Web should:
|
||||
|
||||
* Use four spaces for indentation. Tabs should not be used.
|
||||
* Include JSDoc for any exposed API (e.g. public methods, constructors.)
|
||||
* Include non-JSDoc comments as-needed for explaining private variables,
|
||||
methods, or algorithms when they are non-obvious.
|
||||
* Define one public class per script, expressed as a constructor function
|
||||
returned from an AMD-style module.
|
||||
* Follow “Java-like” naming conventions. These includes:
|
||||
* Classes should use camel case, first letter capitalized
|
||||
(e.g. SomeClassName.)
|
||||
* Methods, variables, fields, and function names should use camel case,
|
||||
first letter lower-case (e.g. someVariableName.) Constants
|
||||
(variables or fields which are meant to be declared and initialized
|
||||
statically, and never changed) should use only capital letters, with
|
||||
underscores between words (e.g. SOME_CONSTANT.)
|
||||
* File name should be the name of the exported class, plus a .js extension
|
||||
(e.g. SomeClassName.js)
|
||||
* Avoid anonymous functions, except when functions are short (a few lines)
|
||||
and/or their inclusion makes sense within the flow of the code
|
||||
(e.g. as arguments to a forEach call.)
|
||||
* Avoid deep nesting (especially of functions), except where necessary
|
||||
(e.g. due to closure scope.)
|
||||
* End with a single new-line character.
|
||||
* Expose public methods by declaring them on the class's prototype.
|
||||
* Within a given function's scope, do not mix declarations and imperative
|
||||
code, and present these in the following order:
|
||||
* First, variable declarations and initialization.
|
||||
* Second, function declarations.
|
||||
* Third, imperative statements.
|
||||
* Finally, the returned value.
|
||||
|
||||
Deviations from Open MCT Web code style guidelines require two-party agreement,
|
||||
typically from the author of the change and its reviewer.
|
||||
|
||||
#### Code Example
|
||||
|
||||
```js
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Bundles should declare themselves as namespaces in whichever source
|
||||
* file is most like the "main point of entry" to the bundle.
|
||||
* @namespace some/bundle
|
||||
*/
|
||||
define(
|
||||
['./OtherClass'],
|
||||
function (OtherClass) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A summary of how to use this class goes here.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof some/bundle
|
||||
*/
|
||||
function ExampleClass() {
|
||||
}
|
||||
|
||||
// Methods which are not intended for external use should
|
||||
// not have JSDoc (or should be marked @private)
|
||||
ExampleClass.prototype.privateMethod = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* A summary of this method goes here.
|
||||
* @param {number} n a parameter
|
||||
* @returns {number} a return value
|
||||
*/
|
||||
ExampleClass.prototype.publicMethod = function (n) {
|
||||
return n * 2;
|
||||
}
|
||||
|
||||
return ExampleClass;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Test Standards
|
||||
|
||||
Automated testing shall occur whenever changes are merged into the main
|
||||
development branch and must be confirmed alongside any pull request.
|
||||
|
||||
Automated tests are typically unit tests which exercise individual software
|
||||
components. Tests are subject to code review along with the actual
|
||||
implementation, to ensure that tests are applicable and useful.
|
||||
|
||||
Examples of useful tests:
|
||||
* Tests which replicate bugs (or their root causes) to verify their
|
||||
resolution.
|
||||
* Tests which reflect details from software specifications.
|
||||
* Tests which exercise edge or corner cases among inputs.
|
||||
* Tests which verify expected interactions with other components in the
|
||||
system.
|
||||
|
||||
During automated testing, code coverage metrics will be reported. Line
|
||||
coverage must remain at or above 80%.
|
||||
|
||||
### Commit Message Standards
|
||||
|
||||
Commit messages should:
|
||||
|
||||
* Contain a one-line subject, followed by one line of white space,
|
||||
followed by one or more descriptive paragraphs, each separated by one
|
||||
 line of white space.
|
||||
* Contain a short (usually one word) reference to the feature or subsystem
|
||||
the commit effects, in square brackets, at the start of the subject line
|
||||
(e.g. `[Documentation] Draft of check-in process`)
|
||||
* Contain a reference to a relevant issue number in the body of the commit.
|
||||
* This is important for traceability; while branch names also provide this,
|
||||
you cannot tell from looking at a commit what branch it was authored on.
|
||||
* This may be omitted if the relevant issue is otherwise obvious from the
|
||||
commit history (that is, if using `git log` from the relevant commit
|
||||
directly leads to a similar issue reference) to minimize clutter.
|
||||
* Describe the change that was made, and any useful rationale therefore.
|
||||
* Comments in code should explain what things do, commit messages describe
|
||||
how they came to be done that way.
|
||||
* Provide sufficient information for a reviewer to understand the changes
|
||||
made and their relationship to previous code.
|
||||
|
||||
Commit messages should not:
|
||||
|
||||
* Exceed 54 characters in length on the subject line.
|
||||
* Exceed 72 characters in length in the body of the commit.
|
||||
* Except where necessary to maintain the structure of machine-readable or
|
||||
machine-generated text (e.g. error messages)
|
||||
|
||||
See [Contributing to a Project](http://git-scm.com/book/ch5-2.html) from
|
||||
Pro Git by Shawn Chacon and Ben Straub for a bit of the rationale behind
|
||||
these standards.
|
||||
|
||||
## Issue Reporting
|
||||
|
||||
Issues are tracked at https://github.com/nasa/openmctweb/issues
|
||||
|
||||
Issues should include:
|
||||
|
||||
* A short description of the issue encountered.
|
||||
* A longer-form description of the issue encountered. When possible, steps to
|
||||
reproduce the issue.
|
||||
* When possible, a description of the impact of the issue. What use case does
|
||||
it impede?
|
||||
* An assessment of the severity of the issue.
|
||||
|
||||
Issue severity is categorized as follows (in ascending order):
|
||||
|
||||
* _Trivial_: Minimal impact on the usefulness and functionality of the
|
||||
software; a "nice-to-have."
|
||||
* _(Unspecified)_: Major loss of functionality or impairment of use.
|
||||
* _Critical_: Large-scale loss of functionality or impairment of use,
|
||||
such that remaining utility becomes marginal.
|
||||
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
|
||||
|
||||
## Check Lists
|
||||
|
||||
The following check lists should be completed and attached to pull requests
|
||||
when they are filed (author checklist) and when they are merged (reviewer
|
||||
checklist.)
|
||||
|
||||
### Author Checklist
|
||||
|
||||
1. Changes address original issue?
|
||||
2. Unit tests included and/or updated with changes?
|
||||
3. Command line build passes?
|
||||
4. Expect to pass code review?
|
||||
|
||||
### Reviewer Checklist
|
||||
|
||||
1. Changes appear to address issue?
|
||||
2. Appropriate unit tests included?
|
||||
3. Code style and in-line documentation are appropriate?
|
||||
4. Commit messages meet standards?
|
28
README.md
28
README.md
@ -67,6 +67,19 @@ as described above.
|
||||
An example of this is expressed in `platform/framework`, which follows
|
||||
bundle conventions.
|
||||
|
||||
### Functional Testing
|
||||
|
||||
The tests described above are all at the unit-level; an additional
|
||||
test suite using [Protractor](https://angular.github.io/protractor/)
|
||||
us under development, in the `protractor` folder.
|
||||
|
||||
To run:
|
||||
|
||||
* Install protractor following the instructions above.
|
||||
* `cd protractor`
|
||||
* `npm install`
|
||||
* `npm run all`
|
||||
|
||||
## Build
|
||||
|
||||
Open MCT Web includes a Maven command line build. Although Open MCT Web
|
||||
@ -90,6 +103,21 @@ This build will:
|
||||
|
||||
Run as `mvn clean install`.
|
||||
|
||||
### Building Documentation
|
||||
|
||||
Open MCT Web's documentation is generated by an
|
||||
[npm](https://www.npmjs.com/)-based build:
|
||||
|
||||
* `npm install` _(only needs to run once)_
|
||||
* `npm run docs`
|
||||
|
||||
Documentation will be generated in `target/docs`. Note that diagram
|
||||
generation is dependent on having [Cairo](http://cairographics.org/download/)
|
||||
installed; see
|
||||
[node-canvas](https://github.com/Automattic/node-canvas#installation)'s
|
||||
documentation for help with installation.
|
||||
|
||||
|
||||
# Glossary
|
||||
|
||||
Certain terms are used throughout Open MCT Web with consistent meanings
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
# Script to build and deploy docs to github pages.
|
||||
|
||||
OUTPUT_DIRECTORY="docs"
|
||||
OUTPUT_DIRECTORY="target/docs"
|
||||
REPOSITORY_URL="git@github.com:nasa/openmctweb.git"
|
||||
|
||||
BUILD_SHA=`git rev-parse head`
|
||||
@ -39,7 +39,7 @@ if [ -d $OUTPUT_DIRECTORY ]; then
|
||||
rm -rf $OUTPUT_DIRECTORY || exit 1
|
||||
fi
|
||||
|
||||
npm run-script jsdoc
|
||||
npm run docs
|
||||
cd $OUTPUT_DIRECTORY || exit 1
|
||||
|
||||
echo "git init"
|
||||
|
@ -8,6 +8,8 @@
|
||||
"platform/commonUI/dialog",
|
||||
"platform/commonUI/general",
|
||||
"platform/commonUI/inspect",
|
||||
"platform/commonUI/mobile",
|
||||
"platform/commonUI/themes/espresso",
|
||||
"platform/containment",
|
||||
"platform/execution",
|
||||
"platform/telemetry",
|
||||
@ -18,12 +20,14 @@
|
||||
"platform/features/scrolling",
|
||||
"platform/features/events",
|
||||
"platform/forms",
|
||||
"platform/identity",
|
||||
"platform/persistence/local",
|
||||
"platform/persistence/queue",
|
||||
"platform/policy",
|
||||
"platform/entanglement",
|
||||
"platform/search",
|
||||
|
||||
"example/imagery",
|
||||
"example/persistence",
|
||||
"example/eventGenerator",
|
||||
"example/generator"
|
||||
]
|
||||
|
@ -4,3 +4,11 @@ deployment:
|
||||
commands:
|
||||
- ./build-docs.sh
|
||||
- git push git@heroku.com:openmctweb-demo.git $CIRCLE_SHA1:refs/heads/master
|
||||
openmctweb-staging-un:
|
||||
branch: search
|
||||
heroku:
|
||||
appname: openmctweb-staging-un
|
||||
openmctweb-staging-deux:
|
||||
branch: mobile
|
||||
heroku:
|
||||
appname: openmctweb-staging-deux
|
||||
|
193
docs/gendocs.js
Normal file
193
docs/gendocs.js
Normal file
@ -0,0 +1,193 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*global require,process,GLOBAL*/
|
||||
/*jslint nomen: false */
|
||||
|
||||
|
||||
// Usage:
|
||||
// node gendocs.js --in <source directory> --out <dest directory>
|
||||
|
||||
var CONSTANTS = {
|
||||
DIAGRAM_WIDTH: 800,
|
||||
DIAGRAM_HEIGHT: 500
|
||||
};
|
||||
|
||||
GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be defined
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs"),
|
||||
mkdirp = require("mkdirp"),
|
||||
path = require("path"),
|
||||
glob = require("glob"),
|
||||
marked = require("marked"),
|
||||
split = require("split"),
|
||||
stream = require("stream"),
|
||||
nomnoml = require('nomnoml'),
|
||||
Canvas = require('canvas'),
|
||||
options = require("minimist")(process.argv.slice(2));
|
||||
|
||||
// Convert from nomnoml source to a target PNG file.
|
||||
function renderNomnoml(source, target) {
|
||||
var canvas =
|
||||
new Canvas(CONSTANTS.DIAGRAM_WIDTH, CONSTANTS.DIAGRAM_HEIGHT);
|
||||
nomnoml.draw(canvas, source, 1.0);
|
||||
canvas.pngStream().pipe(fs.createWriteStream(target));
|
||||
}
|
||||
|
||||
// Stream transform.
|
||||
// Pulls out nomnoml diagrams from fenced code blocks and renders them
|
||||
// as PNG files in the output directory, prefixed with a provided name.
|
||||
// The fenced code blocks will be replaced with Markdown in the
|
||||
// output of this stream.
|
||||
function nomnomlifier(outputDirectory, prefix) {
|
||||
var transform = new stream.Transform({ objectMode: true }),
|
||||
isBuilding = false,
|
||||
counter = 1,
|
||||
outputPath,
|
||||
source = "";
|
||||
|
||||
transform._transform = function (chunk, encoding, done) {
|
||||
if (!isBuilding) {
|
||||
if (chunk.trim().indexOf("```nomnoml") === 0) {
|
||||
var outputFilename = prefix + '-' + counter + '.png';
|
||||
outputPath = path.join(outputDirectory, outputFilename);
|
||||
this.push([
|
||||
"\n\n\n"
|
||||
].join(""));
|
||||
isBuilding = true;
|
||||
source = "";
|
||||
counter += 1;
|
||||
} else {
|
||||
// Otherwise, pass through
|
||||
this.push(chunk + '\n');
|
||||
}
|
||||
} else {
|
||||
if (chunk.trim() === "```") {
|
||||
// End nomnoml
|
||||
renderNomnoml(source, outputPath);
|
||||
isBuilding = false;
|
||||
} else {
|
||||
source += chunk + '\n';
|
||||
}
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
// Convert from Github-flavored Markdown to HTML
|
||||
function gfmifier() {
|
||||
var transform = new stream.Transform({ objectMode: true }),
|
||||
markdown = "";
|
||||
transform._transform = function (chunk, encoding, done) {
|
||||
markdown += chunk;
|
||||
done();
|
||||
};
|
||||
transform._flush = function (done) {
|
||||
this.push("<html><body>\n");
|
||||
this.push(marked(markdown));
|
||||
this.push("\n</body></html>\n");
|
||||
done();
|
||||
};
|
||||
return transform;
|
||||
}
|
||||
|
||||
// Custom renderer for marked; converts relative links from md to html,
|
||||
// and makes headings linkable.
|
||||
function CustomRenderer() {
|
||||
var renderer = new marked.Renderer(),
|
||||
customRenderer = Object.create(renderer);
|
||||
customRenderer.heading = function (text, level) {
|
||||
var escapedText = (text || "").trim().toLowerCase().replace(/\W/g, "-"),
|
||||
aOpen = "<a name=\"" + escapedText + "\" href=\"#" + escapedText + "\">",
|
||||
aClose = "</a>";
|
||||
return aOpen + renderer.heading.apply(renderer, arguments) + aClose;
|
||||
};
|
||||
// Change links to .md files to .html
|
||||
customRenderer.link = function (href, title, text) {
|
||||
// ...but only if they look like relative paths
|
||||
return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
|
||||
renderer.link(href.replace(/\.md/, ".html"), title, text) :
|
||||
renderer.link.apply(renderer, arguments);
|
||||
};
|
||||
return customRenderer;
|
||||
}
|
||||
|
||||
options['in'] = options['in'] || options.i;
|
||||
options.out = options.out || options.o;
|
||||
|
||||
marked.setOptions({
|
||||
renderer: new CustomRenderer(),
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
pedantic: false,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: false
|
||||
});
|
||||
|
||||
// Convert all markdown files.
|
||||
// First, pull out nomnoml diagrams.
|
||||
// Then, convert remaining Markdown to HTML.
|
||||
glob(options['in'] + "/**/*.md", {}, function (err, files) {
|
||||
files.forEach(function (file) {
|
||||
var destination = file.replace(options['in'], options.out)
|
||||
.replace(/md$/, "html"),
|
||||
destPath = path.dirname(destination),
|
||||
prefix = path.basename(destination).replace(/\.html$/, "");
|
||||
|
||||
mkdirp(destPath, function (err) {
|
||||
fs.createReadStream(file, { encoding: 'utf8' })
|
||||
.pipe(split())
|
||||
.pipe(nomnomlifier(destPath, prefix))
|
||||
.pipe(gfmifier())
|
||||
.pipe(fs.createWriteStream(destination, {
|
||||
encoding: 'utf8'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Also copy over all HTML, CSS, or PNG files
|
||||
glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) {
|
||||
files.forEach(function (file) {
|
||||
var destination = file.replace(options['in'], options.out),
|
||||
destPath = path.dirname(destination);
|
||||
|
||||
mkdirp(destPath, function (err) {
|
||||
fs.createReadStream(file, { encoding: 'utf8' })
|
||||
.pipe(fs.createWriteStream(destination, {
|
||||
encoding: 'utf8'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
232
docs/src/architecture/Framework.md
Normal file
232
docs/src/architecture/Framework.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Overview
|
||||
|
||||
The framework layer's most basic responsibility is allowing individual
|
||||
software components to communicate. The software components it recognizes
|
||||
are:
|
||||
|
||||
* _Extensions_: Individual units of functionality that can be added to
|
||||
or removed from Open MCT Web. _Extension categories_ distinguish what
|
||||
type of functionality is being added/removed.
|
||||
* _Bundles_: A grouping of related extensions
|
||||
(named after an analogous concept from [OSGi](http://www.osgi.org/))
|
||||
that may be added or removed as a group.
|
||||
|
||||
The framework layer operates by taking a set of active bundles, and
|
||||
exposing extensions to one another as-needed, using
|
||||
[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection).
|
||||
Extensions are responsible for declaring their dependencies in a
|
||||
manner which the framework layer can understand.
|
||||
|
||||
```nomnoml
|
||||
#direction: down
|
||||
[Open MCT Web|
|
||||
[Dependency injection framework]-->[Platform bundle #1]
|
||||
[Dependency injection framework]-->[Platform bundle #2]
|
||||
[Dependency injection framework]-->[Plugin bundle #1]
|
||||
[Dependency injection framework]-->[Plugin bundle #2]
|
||||
[Platform bundle #1|[Extensions]]
|
||||
[Platform bundle #2|[Extensions]]
|
||||
[Plugin bundle #1|[Extensions]]
|
||||
[Plugin bundle #2|[Extensions]]
|
||||
[Platform bundle #1]<->[Platform bundle #2]
|
||||
[Plugin bundle #1]<->[Platform bundle #2]
|
||||
[Plugin bundle #1]<->[Plugin bundle #2]
|
||||
]
|
||||
```
|
||||
|
||||
The "dependency injection framework" in this case is
|
||||
[AngularJS](https://angularjs.org/). Open MCT Web's framework layer
|
||||
is really just a thin wrapper over Angular that recognizes the
|
||||
concepts of bundles and extensions (as declared in JSON files) and
|
||||
registering extensions with Angular. It additionally acts as a
|
||||
mediator between Angular and [RequireJS](http://requirejs.org/),
|
||||
which is used to load JavaScript sources which implement
|
||||
extensions.
|
||||
|
||||
```nomnoml
|
||||
[Framework layer|
|
||||
[AngularJS]<-[Framework Component]
|
||||
[RequireJS]<-[Framework Component]
|
||||
[Framework Component]1o-*[Bundles]
|
||||
]
|
||||
```
|
||||
|
||||
It is worth noting that _no other components_ are "aware" of the
|
||||
framework component directly; Angular and Require are _used by_ the
|
||||
framework components, and extensions in various bundles will have
|
||||
their dependencies satisfied by Angular as a consequence of registration
|
||||
activities which were performed by the framework component.
|
||||
|
||||
|
||||
## Application Initialization
|
||||
|
||||
The framework component initializes an Open MCT Web application following
|
||||
a simple sequence of steps.
|
||||
|
||||
```nomnoml
|
||||
[<start> Start]->[<state> Load bundles.json]
|
||||
[Load bundles.json]->[<state> Load bundle.json files]
|
||||
[Load bundle.json files]->[<state> Resolve implementations]
|
||||
[Resolve implementations]->[<state> Register with Angular]
|
||||
[Register with Angular]->[<state> Bootstrap application]
|
||||
[Bootstrap application]->[<end> End]
|
||||
```
|
||||
|
||||
1. __Loading bundles.json.__ A file named `bundles.json` is loaded to determine
|
||||
which bundles to load. Bundles are given in this file as relative paths
|
||||
which point to bundle directories.
|
||||
2. __Load bundle.json files.__ Individual bundle definitions are loaded; a
|
||||
`bundle.json` file is expected in each bundle directory.
|
||||
2. __Resolving implementations.__ Any scripts which provide implementations for
|
||||
extensions exposed by bundles are loaded, using RequireJS.
|
||||
3. __Register with Angular.__ Resolved extensions are registered with Angular,
|
||||
such that they can be used by the application at run-time. This stage
|
||||
includes both registration of Angular built-ins (directives, controllers,
|
||||
routes, constants, and services) as well as registration of non-Angular
|
||||
extensions.
|
||||
4. __Bootstrap application.__ Once all extensions have been registered,
|
||||
the Angular application
|
||||
[is bootstrapped](https://docs.angularjs.org/guide/bootstrap).
|
||||
|
||||
## Architectural Paradigm
|
||||
|
||||
```nomnoml
|
||||
[Extension]
|
||||
[Extension]o->[Dependency #1]
|
||||
[Extension]o->[Dependency #2]
|
||||
[Extension]o->[Dependency #3]
|
||||
```
|
||||
|
||||
Open MCT Web's architecture relies on a simple premise: Individual units
|
||||
(extensions) only have access to the dependencies they declare that they
|
||||
need, and they acquire references to these dependencies via dependency
|
||||
injection. This has several desirable traits:
|
||||
|
||||
* Programming to an interface is enforced. Any given dependency can be
|
||||
swapped out for something which exposes an equivalent interface. This
|
||||
improves flexibility against refactoring, simplifies testing, and
|
||||
provides a common mechanism for extension and reconfiguration.
|
||||
* The dependencies of a unit must be explicitly defined. This means that
|
||||
it can be easily determined what a given unit's role is within the
|
||||
larger system, in terms of what other components it will interact with.
|
||||
It also helps to enforce good separation of concerns: When a set of
|
||||
declared dependencies becomes long it is obvious, and this is usually
|
||||
a sign that a given unit is involved in too many concerns and should
|
||||
be refactored into smaller pieces.
|
||||
* Individual units do not need to be aware of the framework; they need
|
||||
only be aware of the interfaces to the components they specifically
|
||||
use. This avoids introducing a ubiquitous dependency upon the framework
|
||||
layer itself; it is plausible to modify or replace the framework
|
||||
without making changes to individual software components which run upon
|
||||
the framework.
|
||||
|
||||
A drawback to this approach is that it makes it difficult to define
|
||||
"the architecture" of Open MCT Web, in terms of describing the specific
|
||||
units that interact at run-time. The run-time architecture is determined
|
||||
by the framework as the consequence of wiring together dependencies.
|
||||
As such, the specific architecture of any given application built on
|
||||
Open MCT Web can look very different.
|
||||
|
||||
Keeping that in mind, there are a few useful patterns supported by the
|
||||
framework that are useful to keep in mind.
|
||||
|
||||
The specific service infrastructure provided by the platform is described
|
||||
in the [Platform Architecture](Platform.md).
|
||||
|
||||
## Extension Categories
|
||||
|
||||
One of the capabilities that the framework component layers on top of
|
||||
AngularJS is support for many-to-one dependencies. That is, a specific
|
||||
extension may declare a dependency to _all extensions of a specific
|
||||
category_, instead of being limited to declaring specific dependencies.
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[Specific Extension] 1 o-> * [Extension of Some Category]
|
||||
```
|
||||
|
||||
This is useful for introducing specific extension points to an application.
|
||||
Some unit of software will depend upon all extensions of a given category
|
||||
and integrate their behavior into the system in some fashion; plugin authors
|
||||
can then add new extensions of that category to augment existing behaviors.
|
||||
|
||||
Some developers may be familiar with the use of registries to achieve
|
||||
similar characteristics. This approach is similar, except that the registry
|
||||
is effectively implicit whenever a new extension category is used or
|
||||
depended-upon. This has some advantages over a more straightforward
|
||||
registry-based approach:
|
||||
|
||||
* These many-to-one relationships are expressed as dependencies; an
|
||||
extension category is registered as having dependencies on all individual
|
||||
extensions of this category. This avoids ordering issues that may occur
|
||||
with more conventional registries, which may be observed before all
|
||||
dependencies are resolved.
|
||||
* The need for service registries of specific types is removed, reducing
|
||||
the number of interfaces to manage within the system. Groups of
|
||||
extensions are provided as arrays.
|
||||
|
||||
## Composite Services
|
||||
|
||||
Composite services (registered via extension category `components`) are
|
||||
a pattern supported by the framework. These allow service instances to
|
||||
be built from multiple components at run-time; support for this pattern
|
||||
allows additional bundles to introduce or modify behavior associated
|
||||
with these services without modifying or replacing original service
|
||||
instances.
|
||||
|
||||
```nomnoml
|
||||
#direction: down
|
||||
[<abstract> FooService]
|
||||
[FooDecorator #1]--:>[FooService]
|
||||
[FooDecorator #n]--:>[FooService]
|
||||
[FooAggregator]--:>[FooService]
|
||||
[FooProvider #1]--:>[FooService]
|
||||
[FooProvider #n]--:>[FooService]
|
||||
|
||||
[FooDecorator #1]o->[<state> ...decorators...]
|
||||
[...decorators...]o->[FooDecorator #n]
|
||||
[FooDecorator #n]o->[FooAggregator]
|
||||
[FooAggregator]o->[FooProvider #1]
|
||||
[FooAggregator]o->[<state> ...providers...]
|
||||
[FooAggregator]o->[FooProvider #n]
|
||||
|
||||
[FooDecorator #1]--[<note> Exposed as fooService]
|
||||
```
|
||||
|
||||
In this pattern, components all implement an interface which is
|
||||
standardized for that service. Components additionally declare
|
||||
that they belong to one of three types:
|
||||
|
||||
* __Providers.__ A provider actually implements the behavior
|
||||
(satisfies the contract) for that kind of service. For instance,
|
||||
if a service is responsible for looking up documents by an identifier,
|
||||
one provider may do so by querying a database, while another may
|
||||
do so by reading a static JSON document. From the outside, either
|
||||
provider would look the same (they expose the same interface) and
|
||||
they could be swapped out easily.
|
||||
* __Aggregator.__ An aggregator takes many providers and makes them
|
||||
behave as one. Again, this implements the same interface as an
|
||||
individual provider, so users of the service do not need to be
|
||||
concerned about the difference between consulting many providers
|
||||
and consulting one. Continuing with the example of a service that
|
||||
looks up documents by identifiers, an aggregator here might consult
|
||||
all providers, and return any document is found (perhaps picking one
|
||||
over the other or merging documents if there are multiple matches.)
|
||||
* __Decorators.__ A decorator exposes the same interface as other
|
||||
components, but instead of fully implementing the behavior associated
|
||||
with that kind of service, it only acts as an intermediary, delegating
|
||||
the actual behavior to a different component. Decorators may transform
|
||||
inputs or outputs, or initiate some side effects associated with a
|
||||
service. This is useful if certain common behavior associated with a
|
||||
service (caching, for instance) may be useful across many different
|
||||
implementations of that same service.
|
||||
|
||||
The framework will register extensions in this category such that an
|
||||
aggregator will depend on all of its providers, and decorators will
|
||||
depend upon on one another in a chain. The result of this compositing step
|
||||
(the last decorator, if any; otherwise the aggregator, if any;
|
||||
otherwise a single provider) will be exposed as a single service that
|
||||
other extensions can acquire through dependency injection. Because all
|
||||
components of the same type of service expose the same interface, users
|
||||
of that service do not need to be aware that they are talking to an
|
||||
aggregator or a provider, for instance.
|
714
docs/src/architecture/Platform.md
Normal file
714
docs/src/architecture/Platform.md
Normal file
@ -0,0 +1,714 @@
|
||||
# Overview
|
||||
|
||||
The Open MCT Web platform utilizes the [framework layer](Framework.md)
|
||||
to provide an extensible baseline for applications which includes:
|
||||
|
||||
* A common user interface (and user interface paradigm) for dealing with
|
||||
domain objects of various sorts.
|
||||
* A variety of extension points for introducing new functionality
|
||||
of various kinds within the context of the common user interface.
|
||||
* A service infrastructure to support building additional components.
|
||||
|
||||
## Platform Architecture
|
||||
|
||||
While the framework provides a more general architectural paradigm for
|
||||
building application, the platform adds more specificity by defining
|
||||
additional extension types and allowing for integration with back end
|
||||
components.
|
||||
|
||||
The run-time architecture of an Open MCT Web application can be categorized
|
||||
into certain high-level tiers:
|
||||
|
||||
```nomnoml
|
||||
[DOM]->[<state> AngularJS]
|
||||
[AngularJS]->[Presentation Layer]
|
||||
[Presentation Layer]->[Information Model]
|
||||
[Presentation Layer]->[Service Infrastructure]
|
||||
[Information Model]->[Service Infrastructure]
|
||||
[Service Infrastructure]->[<state> Browser APIs]
|
||||
[Browser APIs]->[Back-end]
|
||||
```
|
||||
|
||||
Applications built using Open MCT Web may add or configure functionality
|
||||
in __any of these tiers__.
|
||||
|
||||
* _DOM_: The rendered HTML document, composed from HTML templates which
|
||||
have been processed by AngularJS and will be updated by AngularJS
|
||||
to reflect changes from the presentation layer. User interactions
|
||||
are initiated from here and invoke behavior in the presentation layer.
|
||||
* [_Presentation layer_](#presentation-layer): The presentation layer
|
||||
is responsible for updating (and providing information to update)
|
||||
the displayed state of the application. The presentation layer consists
|
||||
primarily of _controllers_ and _directives_. The presentation layer is
|
||||
concerned with inspecting the information model and preparing it for
|
||||
display.
|
||||
* [_Information model_](#information-model): The information model
|
||||
describes the state and behavior of the objects with which the user
|
||||
interacts.
|
||||
* [_Service infrastructure_](#service-infrastructure): The service
|
||||
infrastructure is responsible for providing the underlying general
|
||||
functionality needed to support the information model. This includes
|
||||
exposing underlying sets of extensions and mediating with the
|
||||
back-end.
|
||||
* _Back-end_: The back-end is out of the scope of Open MCT Web, except
|
||||
for the interfaces which are utilized by adapters participating in the
|
||||
service infrastructure.
|
||||
|
||||
## Application Start-up
|
||||
|
||||
Once the
|
||||
[application has been initialized](Framework.md#application-initialization)
|
||||
Open MCT Web primarily operates in an event-driven paradigm; various
|
||||
events (mouse clicks, timers firing, receiving responses to XHRs) trigger
|
||||
the invocation of functions, typically in the presentation layer for
|
||||
user actions or in the service infrastructure for server responses.
|
||||
|
||||
The "main point of entry" into an initialized Open MCT Web application
|
||||
is effectively the
|
||||
[route](https://docs.angularjs.org/api/ngRoute/service/$route#example)
|
||||
which is associated with the URL used to access Open MCT Web (or a
|
||||
default route.) This route will be associated with a template which
|
||||
will be displayed; this template will include references to directives
|
||||
and controllers which will be interpreted by Angular and used to
|
||||
initialize the state of the display in a manner which is backed by
|
||||
both the information model and the service infrastructure.
|
||||
|
||||
```nomnoml
|
||||
[<start> Start]->[<state> page load]
|
||||
[page load]->[<state> route selection]
|
||||
[route selection]->[<state> compile, display template]
|
||||
[compile, display template]->[Template]
|
||||
[Template]->[<state> use Controllers]
|
||||
[Template]->[<state> use Directives]
|
||||
[use Controllers]->[Controllers]
|
||||
[use Directives]->[Directives]
|
||||
[Controllers]->[<state> consult information model]
|
||||
[consult information model]->[<state> expose data]
|
||||
[expose data]->[Angular]
|
||||
[Angular]->[<state> update display]
|
||||
[Directives]->[<state> add event listeners]
|
||||
[Directives]->[<state> update display]
|
||||
[add event listeners]->[<end> End]
|
||||
[update display]->[<end> End]
|
||||
```
|
||||
|
||||
|
||||
# Presentation Layer
|
||||
|
||||
The presentation layer of Open MCT Web is responsible for providing
|
||||
information to display within templates, and for handling interactions
|
||||
which are initiated from templated DOM elements. AngularJS acts as
|
||||
an intermediary between the web page as the user sees it, and the
|
||||
presentation layer implemented as Open MCT Web extensions.
|
||||
|
||||
```nomnoml
|
||||
[Presentation Layer|
|
||||
[Angular built-ins|
|
||||
[routes]
|
||||
[controllers]
|
||||
[directives]
|
||||
[templates]
|
||||
]
|
||||
[Domain object representation|
|
||||
[views]
|
||||
[representations]
|
||||
[representers]
|
||||
[gestures]
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
## Angular built-ins
|
||||
|
||||
Several extension categories in the presentation layer map directly
|
||||
to primitives from AngularJS:
|
||||
|
||||
* [_Controllers_](https://docs.angularjs.org/guide/controller) provide
|
||||
data to templates, and expose functionality that can be called from
|
||||
templates.
|
||||
* [_Directives_](https://docs.angularjs.org/guide/directive) effectively
|
||||
extend HTML to provide custom behavior associated with specific
|
||||
attributes and tags.
|
||||
* [_Routes_](https://docs.angularjs.org/api/ngRoute/service/$route#example)
|
||||
are used to associate specific URLs (including the fragment identifier)
|
||||
with specific application states. (In Open MCT Web, these are used to
|
||||
describe the mode of usage - e.g. browse or edit - as well as to
|
||||
identify the object being used.)
|
||||
* [_Templates_](https://docs.angularjs.org/guide/templates) are partial
|
||||
HTML documents that will be rendered and kept up-to-date by AngularJS.
|
||||
Open MCT Web introduces a custom `mct-include` directive which acts
|
||||
as a wrapper around `ng-include` to allow templates to be referred
|
||||
to by symbolic names.
|
||||
|
||||
## Domain object representation
|
||||
|
||||
The remaining extension categories in the presentation layer are specific
|
||||
to displaying domain objects.
|
||||
|
||||
* _Representations_ are templates that will be used to display
|
||||
domain objects in specific ways (e.g. "as a tree node.")
|
||||
* _Views_ are representations which are exposed to the user as options
|
||||
for displaying domain objects.
|
||||
* _Representers_ are extensions which modify or augment the process
|
||||
of representing domain objects generally (e.g. by attaching
|
||||
gestures to them.)
|
||||
* _Gestures_ provide associations between specific user actions
|
||||
(expressed as DOM events) and resulting behavior upon domain objects
|
||||
(typically expressed as members of the `actions` extension category)
|
||||
that can be reused across domain objects. For instance, `drag` and
|
||||
`drop` are both gestures associated with using drag-and-drop to
|
||||
modify the composition of domain objects by interacting with their
|
||||
representations.
|
||||
|
||||
# Information Model
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[Information Model|
|
||||
[DomainObject|
|
||||
getId() : string
|
||||
getModel() : object
|
||||
getCapability(key : string) : Capability
|
||||
hasCapability(key : string) : boolean
|
||||
useCapability(key : string, args...) : *
|
||||
]
|
||||
[DomainObject] 1 +- 1 [Model]
|
||||
[DomainObject] 1 o- * [Capability]
|
||||
]
|
||||
```
|
||||
|
||||
Domain objects are the most fundamental component of Open MCT Web's
|
||||
information model. A domain object is some distinct thing relevant to a
|
||||
user's work flow, such as a telemetry channel, display, or similar.
|
||||
Open MCT Web is a tool for viewing, browsing, manipulating, and otherwise
|
||||
interacting with a graph of domain objects.
|
||||
|
||||
A domain object should be conceived of as the union of the following:
|
||||
|
||||
* _Identifier_: A machine-readable string that uniquely identifies the
|
||||
domain object within this application instance.
|
||||
* _Model_: The persistent state of the domain object. A domain object's
|
||||
model is a JavaScript object that can be losslessly converted to JSON.
|
||||
* _Capabilities_: Dynamic behavior associated with the domain object.
|
||||
Capabilities are JavaScript objects which provide additional methods
|
||||
for interacting with the domain objects which expose those capabilities.
|
||||
Not all domain objects expose all capabilities. The interface exposed
|
||||
by any given capability will depend on its type (as identified
|
||||
by the `key` argument.) For instance, a `persistence` capability
|
||||
has a different interface from a `telemetry` capability. Using
|
||||
capabilities requires some prior knowledge of their interface.
|
||||
|
||||
## Capabilities and Services
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[DomainObject]o-[FooCapability]
|
||||
[FooCapability]o-[FooService]
|
||||
[FooService]o-[foos]
|
||||
```
|
||||
|
||||
At run-time, the user is primarily concerned with interacting with
|
||||
domain objects. These interactions are ultimately supported via back-end
|
||||
services, but to allow customization per-object, these are often mediated
|
||||
by capabilities.
|
||||
|
||||
A common pattern that emerges in the Open MCT Platform is as follows:
|
||||
|
||||
* A `DomainObject` has some particular behavior that will be supported
|
||||
by a service.
|
||||
* A `Capability` of that domain object will define that behavior,
|
||||
_for that domain object_, supported by a service.
|
||||
* A `Service` utilized by that capability will perform the actual behavior.
|
||||
* An extension category will be utilized by that capability to determine
|
||||
the set of possible behaviors.
|
||||
|
||||
Concrete examples of capabilities which follow this pattern
|
||||
(or a subset of this pattern) include:
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[DomainObject]1 o- *[Capability]
|
||||
[Capability]<:--[TypeCapability]
|
||||
[Capability]<:--[ActionCapability]
|
||||
[Capability]<:--[PersistenceCapability]
|
||||
[Capability]<:--[TelemetryCapability]
|
||||
[TypeCapability]o-[TypeService]
|
||||
[TypeService]o-[types]
|
||||
[ActionCapability]o-[ActionService]
|
||||
[ActionService]o-[actions]
|
||||
[PersistenceCapability]o-[PersistenceService]
|
||||
[TelemetryCapability]o-[TelemetryService]
|
||||
```
|
||||
|
||||
# Service Infrastructure
|
||||
|
||||
Most services exposed by the Open MCT Web platform follow the
|
||||
[composite services](Framework.md#composite-services) to permit
|
||||
a higher degree of flexibility in how a service can be modified
|
||||
or customized for specific applications.
|
||||
|
||||
To simplify usage for plugin developers, the platform also usually
|
||||
includes a provider implementation for these service type that consumes
|
||||
some extension category. For instance, an `ActionService` provider is
|
||||
included which depends upon extension category `actions`, and exposes
|
||||
all actions declared as such to the system. As such, plugin developers
|
||||
can simply implement the new actions they wish to be made available without
|
||||
worrying about the details of composite services or implementing a new
|
||||
`ActionService` provider; however, the ability to implement a new provider
|
||||
remains useful when the expressive power of individual extensions is
|
||||
insufficient.
|
||||
|
||||
```nomnoml
|
||||
[ Service Infrastructure |
|
||||
[ObjectService]->[ModelService]
|
||||
[ModelService]->[PersistenceService]
|
||||
[ObjectService]->[CapabilityService]
|
||||
[CapabilityService]->[capabilities]
|
||||
[capabilities]->[TelemetryService]
|
||||
[capabilities]->[PersistenceService]
|
||||
[capabilities]->[TypeService]
|
||||
[capabilities]->[ActionService]
|
||||
[capabilities]->[ViewService]
|
||||
[PersistenceService]->[<database> Document store]
|
||||
[TelemetryService]->[<database> Telemetry source]
|
||||
[ActionService]->[actions]
|
||||
[ActionService]->[PolicyService]
|
||||
[ViewService]->[PolicyService]
|
||||
[ViewService]->[views]
|
||||
[PolicyService]->[policies]
|
||||
[TypeService]->[types]
|
||||
]
|
||||
```
|
||||
|
||||
A short summary of the roles of these services:
|
||||
|
||||
* _[ObjectService](#object-service)_: Allows retrieval of domain objects by
|
||||
their identifiers; in practice, often the main point of entry into the
|
||||
[information model](#information-model).
|
||||
* _[ModelService](#model-service)_: Provides domain object models, retrieved
|
||||
by their identifier.
|
||||
* _[CapabilityService](#capability-service)_: Provides capabilities, as they
|
||||
apply to specific domain objects (as judged from their model.)
|
||||
* _[TelemetryService](#telemetry-service)_: Provides access to historical
|
||||
and real-time telemetry data.
|
||||
* _[PersistenceService](#persistence-service)_: Provides the ability to
|
||||
store and retrieve documents (such as domain object models.)
|
||||
* _[ActionService](#action-service)_: Provides distinct user actions that
|
||||
can take place within the system (typically, upon or using domain objects.)
|
||||
* _[ViewService](#view-service)_: Provides views for domain objects. A view
|
||||
is a user-selectable representation of a domain object (in practice, an
|
||||
HTML template.)
|
||||
* _[PolicyService](#policy-service)_: Handles decisions about which
|
||||
behavior are allowed within certain specific contexts.
|
||||
* _[TypeService](#type-service)_: Provides information to distinguish
|
||||
different types of domain objects from one another within the system.
|
||||
|
||||
## Object Service
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[<abstract> ObjectService|
|
||||
getObjects(ids : Array.<string>) : Promise.<object.<string, DomainObject>>
|
||||
]
|
||||
[DomainObjectProvider]--:>[ObjectService]
|
||||
[DomainObjectProvider]o-[ModelService]
|
||||
[DomainObjectProvider]o-[CapabilityService]
|
||||
```
|
||||
|
||||
As domain objects are central to Open MCT Web's information model,
|
||||
acquiring domain objects is equally important.
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[<start> Start]->[<state> Look up models]
|
||||
[<state> Look up models]->[<state> Look up capabilities]
|
||||
[<state> Look up capabilities]->[<state> Instantiate DomainObject]
|
||||
[<state> Instantiate DomainObject]->[<end> End]
|
||||
```
|
||||
|
||||
Open MCT Web includes an implementation of an `ObjectService` which
|
||||
satisfies this capability by:
|
||||
|
||||
* Consulting the [Model Service](#model-service) to acquire domain object
|
||||
models by identifier.
|
||||
* Passing these models to a [Capability Service](#capability-service) to
|
||||
determine which capabilities are applicable.
|
||||
* Combining these results together as [DomainObject](#information-model)
|
||||
instances.
|
||||
|
||||
## Model Service
|
||||
|
||||
```nomnoml
|
||||
#direction: down
|
||||
[<abstract> ModelService|
|
||||
getModels(ids : Array.<string>) : Promise.<object.<string, object>>
|
||||
]
|
||||
[StaticModelProvider]--:>[ModelService]
|
||||
[RootModelProvider]--:>[ModelService]
|
||||
[PersistedModelProvider]--:>[ModelService]
|
||||
[ModelAggregator]--:>[ModelService]
|
||||
[CachingModelDecorator]--:>[ModelService]
|
||||
[MissingModelDecorator]--:>[ModelService]
|
||||
|
||||
[MissingModelDecorator]o-[CachingModelDecorator]
|
||||
[CachingModelDecorator]o-[ModelAggregator]
|
||||
[ModelAggregator]o-[StaticModelProvider]
|
||||
[ModelAggregator]o-[RootModelProvider]
|
||||
[ModelAggregator]o-[PersistedModelProvider]
|
||||
|
||||
[PersistedModelProvider]o-[PersistenceService]
|
||||
[RootModelProvider]o-[roots]
|
||||
[StaticModelProvider]o-[models]
|
||||
```
|
||||
|
||||
The platform's model service is responsible for providing domain object
|
||||
models (effectively, JSON documents describing the persistent state
|
||||
associated with domain objects.) These are retrieved by identifier.
|
||||
|
||||
The platform includes multiple components of this variety:
|
||||
|
||||
* `PersistedModelProvider` looks up domain object models from
|
||||
a persistence store (the [`PersistenceService`](#persistence-service));
|
||||
this is how user-created and user-modified
|
||||
domain object models are retrieved.
|
||||
* `RootModelProvider` provides domain object models that have been
|
||||
declared via the `roots` extension category. These will appear at the
|
||||
top level of the tree hierarchy in the user interface.
|
||||
* `StaticModelProvider` provides domain object models that have been
|
||||
declared via the `models` extension category. This is useful for
|
||||
allowing plugins to expose new domain objects declaratively.
|
||||
* `ModelAggregator` merges together the results from multiple providers.
|
||||
If multiple providers return models for the same domain object,
|
||||
the most recently modified version (as determined by the `modified`
|
||||
property of the model) is chosen.
|
||||
* `CachingModelDecorator` caches model instances in memory. This
|
||||
ensures that only a single instance of a domain object model is
|
||||
present at any given time within the application, and prevent
|
||||
redundant retrievals.
|
||||
* `MissingModelDecorator` adds in placeholders when no providers
|
||||
have returned domain object models for a specific identifier. This
|
||||
allows the user to easily see that something was expected to be
|
||||
present, but wasn't.
|
||||
|
||||
## Capability Service
|
||||
|
||||
```nomnoml
|
||||
#direction: down
|
||||
[<abstract> CapabilityService|
|
||||
getCapabilities(model : object) : object.<string, Function>
|
||||
]
|
||||
[CoreCapabilityProvider]--:>[CapabilityService]
|
||||
[QueuingPersistenceCapabilityDecorator]--:>[CapabilityService]
|
||||
|
||||
[CoreCapabilityProvider]o-[capabilities]
|
||||
[QueuingPersistenceCapabilityDecorator]o-[CoreCapabilityProvider]
|
||||
```
|
||||
|
||||
The capability service is responsible for determining which capabilities
|
||||
are applicable for a given domain object, based on its model. Primarily,
|
||||
this is handled by the `CoreCapabilityProvider`, which examines
|
||||
capabilities exposed via the `capabilities` extension category.
|
||||
|
||||
Additionally, `platform/persistence/queue` decorates the persistence
|
||||
capability specifically to batch persistence attempts among multiple
|
||||
objects (this allows failures to be recognized and handled in groups.)
|
||||
|
||||
## Telemetry Service
|
||||
|
||||
```nomnoml
|
||||
[<abstract> TelemetryService|
|
||||
requestData(requests : Array.<TelemetryRequest>) : Promise.<object>
|
||||
subscribe(requests : Array.<TelemetryRequest>) : Function
|
||||
]<--:[TelemetryAggregator]
|
||||
```
|
||||
|
||||
The telemetry service is responsible for acquiring telemetry data.
|
||||
|
||||
Notably, the platform does not include any providers for
|
||||
`TelemetryService`; applications built on Open MCT Web will need to
|
||||
implement a provider for this service if they wish to expose telemetry
|
||||
data. This is usually the most important step for integrating Open MCT Web
|
||||
into an existing telemetry system.
|
||||
|
||||
Requests for telemetry data are usually initiated in the
|
||||
[presentation layer](#presentation-layer) by some `Controller` referenced
|
||||
from a view. The `telemetryHandler` service is most commonly used (although
|
||||
one could also use an object's `telemetry` capability directly) as this
|
||||
handles capability delegation, by which a domain object such as a Telemetry
|
||||
Panel can declare that its `telemetry` capability should be handled by the
|
||||
objects it contains. Ultimately, the request for historical data and the
|
||||
new subscriptions will reach the `TelemetryService`, and, by way of the
|
||||
provider(s) which are present for that `TelemetryService`, will pass the
|
||||
same requests to the back-end.
|
||||
|
||||
```nomnoml
|
||||
[<start> Start]->[Controller]
|
||||
[Controller]->[<state> declares object of interest]
|
||||
[declares object of interest]->[TelemetryHandler]
|
||||
[TelemetryHandler]->[<state> requests telemetry from capabilities]
|
||||
[TelemetryHandler]->[<state> subscribes to telemetry using capabilities]
|
||||
[requests telemetry from capabilities]->[TelemetryCapability]
|
||||
[subscribes to telemetry using capabilities]->[TelemetryCapability]
|
||||
[TelemetryCapability]->[<state> requests telemetry]
|
||||
[TelemetryCapability]->[<state> subscribes to telemetry]
|
||||
[requests telemetry]->[TelemetryService]
|
||||
[subscribes to telemetry]->[TelemetryService]
|
||||
[TelemetryService]->[<state> issues request]
|
||||
[TelemetryService]->[<state> updates subscriptions]
|
||||
[TelemetryService]->[<state> listens for real-time data]
|
||||
[issues request]->[<database> Telemetry Back-end]
|
||||
[updates subscriptions]->[Telemetry Back-end]
|
||||
[listens for real-time data]->[Telemetry Back-end]
|
||||
[Telemetry Back-end]->[<end> End]
|
||||
```
|
||||
|
||||
The back-end, in turn, is expected to provide whatever historical
|
||||
telemetry is available to satisfy the request that has been issue.
|
||||
|
||||
```nomnoml
|
||||
[<start> Start]->[<database> Telemetry Back-end]
|
||||
[Telemetry Back-end]->[<state> transmits historical telemetry]
|
||||
[transmits historical telemetry]->[TelemetryService]
|
||||
[TelemetryService]->[<state> packages telemetry, fulfills requests]
|
||||
[packages telemetry, fulfills requests]->[TelemetryCapability]
|
||||
[TelemetryCapability]->[<state> unpacks telemetry per-object, fulfills request]
|
||||
[unpacks telemetry per-object, fulfills request]->[TelemetryHandler]
|
||||
[TelemetryHandler]->[<state> exposes data]
|
||||
[TelemetryHandler]->[<state> notifies controller]
|
||||
[exposes data]->[Controller]
|
||||
[notifies controller]->[Controller]
|
||||
[Controller]->[<state> prepares data for template]
|
||||
[prepares data for template]->[Template]
|
||||
[Template]->[<state> displays data]
|
||||
[displays data]->[<end> End]
|
||||
```
|
||||
|
||||
One peculiarity of this approach is that we package many responses
|
||||
together at once in the `TelemetryService`, then unpack these in the
|
||||
`TelemetryCapability`, then repackage these in the `TelemetryHandler`.
|
||||
The rationale for this is as follows:
|
||||
|
||||
* In the `TelemetryService`, we want to have the ability to combine
|
||||
multiple requests into one call to the back-end, as many back-ends
|
||||
will support this. It follows that we give the response as a single
|
||||
object, packages in a manner that allows responses to individual
|
||||
requests to be easily identified.
|
||||
* In the `TelemetryCapability`, we want to provide telemetry for a
|
||||
_single object_, so the telemetry data gets unpacked. This allows
|
||||
for the unpacking of data to be handled in a single place, and
|
||||
also permits a flexible substitution method; domain objects may have
|
||||
implementations of the `telemetry` capability that do not use the
|
||||
`TelemetryService` at all, while still maintaining compatibility
|
||||
with any presentation layer code written to utilize this capability.
|
||||
(This is true of capabilities generally.)
|
||||
* In the `TelemetryHandler`, we want to group multiple responses back
|
||||
together again to make it easy for the presentation layer to consume.
|
||||
In this case, the grouping is different from what may have occurred
|
||||
in the `TelemetryService`; this grouping is based on what is expected
|
||||
to be useful _in a specific view_. The `TelemetryService`
|
||||
may be receiving requests from multiple views.
|
||||
|
||||
```nomnoml
|
||||
[<start> Start]->[<database> Telemetry Back-end]
|
||||
[Telemetry Back-end]->[<state> notifies client of new data]
|
||||
[notifies client of new data]->[TelemetryService]
|
||||
[TelemetryService]->[<choice> relevant subscribers?]
|
||||
[relevant subscribers?] yes ->[<state> notify subscribers]
|
||||
[relevant subscribers?] no ->[<state> ignore]
|
||||
[ignore]->[<end> Ignored]
|
||||
[notify subscribers]->[TelemetryCapability]
|
||||
[TelemetryCapability]->[<state> notify listener]
|
||||
[notify listener]->[TelemetryHandler]
|
||||
[TelemetryHandler]->[<state> exposes data]
|
||||
[TelemetryHandler]->[<state> notifies controller]
|
||||
[exposes data]->[Controller]
|
||||
[notifies controller]->[Controller]
|
||||
[Controller]->[<state> prepares data for template]
|
||||
[prepares data for template]->[Template]
|
||||
[Template]->[<state> displays data]
|
||||
[displays data]->[<end> End]
|
||||
```
|
||||
|
||||
The flow of real-time data is similar, and is handled by a sequence
|
||||
of callbacks between the presentation layer component which is
|
||||
interested in data and the telemetry service. Providers in the
|
||||
telemetry service listen to the back-end for new data (via whatever
|
||||
mechanism their specific back-end supports), package this data in
|
||||
the same manner as historical data, and pass that to the callbacks
|
||||
which are associated with relevant requests.
|
||||
|
||||
## Persistence Service
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[<abstract> PersistenceService|
|
||||
listSpaces() : Promise.<Array.<string>>
|
||||
listObjects() : Promise.<Array.<string>>
|
||||
createObject(space : string, key : string, document : object) : Promise.<boolean>
|
||||
readObject(space : string, key : string, document : object) : Promise.<object>
|
||||
updateObject(space : string, key : string, document : object) : Promise.<boolean>
|
||||
deleteObject(space : string, key : string, document : object) : Promise.<boolean>
|
||||
]
|
||||
|
||||
[ElasticPersistenceProvider]--:>[PersistenceService]
|
||||
[ElasticPersistenceProvider]->[<database> ElasticSearch]
|
||||
|
||||
[CouchPersistenceProvider]--:>[PersistenceService]
|
||||
[CouchPersistenceProvider]->[<database> CouchDB]
|
||||
```
|
||||
|
||||
Closely related to the notion of domain objects models is their
|
||||
persistence. The `PersistenceService` allows these to be saved
|
||||
and loaded. (Currently, this capability is only used for domain
|
||||
object models, but the interface has been designed without this idea
|
||||
in mind; other kinds of documents could be saved and loaded in the
|
||||
same manner.)
|
||||
|
||||
There is no single definitive implementation of a `PersistenceService` in
|
||||
the platform. Optional adapters are provided to store and load documents
|
||||
from CouchDB and ElasticSearch, respectively; plugin authors may also
|
||||
write additional adapters to utilize different back end technologies.
|
||||
|
||||
## Action Service
|
||||
|
||||
```nomnoml
|
||||
[ActionService|
|
||||
getActions(context : ActionContext) : Array.<Action>
|
||||
]
|
||||
[ActionProvider]--:>[ActionService]
|
||||
[CreateActionProvider]--:>[ActionService]
|
||||
[ActionAggregator]--:>[ActionService]
|
||||
[LoggingActionDecorator]--:>[ActionService]
|
||||
[PolicyActionDecorator]--:>[ActionService]
|
||||
|
||||
[LoggingActionDecorator]o-[PolicyActionDecorator]
|
||||
[PolicyActionDecorator]o-[ActionAggregator]
|
||||
[ActionAggregator]o-[ActionProvider]
|
||||
[ActionAggregator]o-[CreateActionProvider]
|
||||
|
||||
[ActionProvider]o-[actions]
|
||||
[CreateActionProvider]o-[TypeService]
|
||||
[PolicyActionDecorator]o-[PolicyService]
|
||||
```
|
||||
|
||||
Actions are discrete tasks or behaviors that can be initiated by a user
|
||||
upon or using a domain object. Actions may appear as menu items or
|
||||
buttons in the user interface, or may be triggered by certain gestures.
|
||||
|
||||
Responsibilities of platform components of the action service are as
|
||||
follows:
|
||||
|
||||
* `ActionProvider` exposes actions registered via extension category
|
||||
`actions`, supporting simple addition of new actions. Actions are
|
||||
filtered down to match action contexts based on criteria defined as
|
||||
part of an action's extension definition.
|
||||
* `CreateActionProvider` provides the various Create actions which
|
||||
populate the Create menu. These are driven by the available types,
|
||||
so do not map easily ot extension category `actions`; instead, these
|
||||
are generated after looking up which actions are available from the
|
||||
[`TypeService`](#type-service).
|
||||
* `ActionAggregator` merges together actions from multiple providers.
|
||||
* `PolicyActionDecorator` enforces the `action` policy category by
|
||||
filtering out actions which violate this policy, as determined by
|
||||
consulting the [`PolicyService`](#policy-service).
|
||||
* `LoggingActionDecorator` wraps exposed actions and writes to the
|
||||
console when they are performed.
|
||||
|
||||
## View Service
|
||||
|
||||
```nomnoml
|
||||
[ViewService|
|
||||
getViews(domainObject : DomainObject) : Array.<View>
|
||||
]
|
||||
[ViewProvider]--:>[ViewService]
|
||||
[PolicyViewDecorator]--:>[ViewService]
|
||||
|
||||
[ViewProvider]o-[views]
|
||||
[PolicyViewDecorator]o-[ViewProvider]
|
||||
```
|
||||
|
||||
The view service provides views that are relevant to a specified domain
|
||||
object. A "view" is a user-selectable visualization of a domain object.
|
||||
|
||||
The responsibilities of components of the view service are as follows:
|
||||
|
||||
* `ViewProvider` exposes views registered via extension category
|
||||
`views`, supporting simple addition of new views. Views are
|
||||
filtered down to match domain objects based on criteria defined as
|
||||
part of a view's extension definition.
|
||||
* `PolicyViewDecorator` enforces the `view` policy category by
|
||||
filtering out views which violate this policy, as determined by
|
||||
consulting the [`PolicyService`](#policy-service).
|
||||
|
||||
## Policy Service
|
||||
|
||||
```nomnoml
|
||||
[PolicyService|
|
||||
allow(category : string, candidate : object, context : object, callback? : Function) : boolean
|
||||
]
|
||||
[PolicyProvider]--:>[PolicyService]
|
||||
[PolicyProvider]o-[policies]
|
||||
```
|
||||
|
||||
The policy service provides a general-purpose extensible decision-making
|
||||
mechanism; plugins can add new extensions of category `policies` to
|
||||
modify decisions of a known category.
|
||||
|
||||
Often, the policy service is referenced from a decorator for another
|
||||
service, to filter down the results of using that service based on some
|
||||
appropriate policy category.
|
||||
|
||||
The policy provider works by looking up all registered policy extensions
|
||||
which are relevant to a particular _category_, then consulting each in
|
||||
order to see if they allow a particular _candidate_ in a particular
|
||||
_context_; the types for the `candidate` and `context` arguments will
|
||||
vary depending on the `category`. Any one policy may disallow the
|
||||
decision as a whole.
|
||||
|
||||
|
||||
```nomnoml
|
||||
[<start> Start]->[<state> is something allowed?]
|
||||
[is something allowed?]->[PolicyService]
|
||||
[PolicyService]->[<state> look up relevant policies by category]
|
||||
[look up relevant policies by category]->[<state> consult policy #1]
|
||||
[consult policy #1]->[Policy #1]
|
||||
[Policy #1]->[<choice> policy #1 allows?]
|
||||
[policy #1 allows?] no ->[<state> decision disallowed]
|
||||
[policy #1 allows?] yes ->[<state> consult policy #2]
|
||||
[consult policy #2]->[Policy #2]
|
||||
[Policy #2]->[<choice> policy #2 allows?]
|
||||
[policy #2 allows?] no ->[<state> decision disallowed]
|
||||
[policy #2 allows?] yes ->[<state> consult policy #3]
|
||||
[consult policy #3]->[<state> ...]
|
||||
[...]->[<state> consult policy #n]
|
||||
[consult policy #n]->[Policy #n]
|
||||
[Policy #n]->[<choice> policy #n allows?]
|
||||
[policy #n allows?] no ->[<state> decision disallowed]
|
||||
[policy #n allows?] yes ->[<state> decision allowed]
|
||||
[decision disallowed]->[<end> Disallowed]
|
||||
[decision allowed]->[<end> Allowed]
|
||||
```
|
||||
|
||||
The policy decision is effectively an "and" operation over the individual
|
||||
policy decisions: That is, all policies must agree to allow a particular
|
||||
policy decision, and the first policy to disallow a decision will cause
|
||||
the entire decision to be disallowed. As a consequence of this, policies
|
||||
should generally be written with a default behavior of "allow", and
|
||||
should only disallow the specific circumstances they are intended to
|
||||
disallow.
|
||||
|
||||
## Type Service
|
||||
|
||||
```nomnoml
|
||||
[TypeService|
|
||||
listTypes() : Array.<Type>
|
||||
getType(key : string) : Type
|
||||
]
|
||||
[TypeProvider]--:>[TypeService]
|
||||
[TypeProvider]o-[types]
|
||||
```
|
||||
|
||||
The type service provides metadata about the different types of domain
|
||||
objects that exist within an Open MCT Web application. The platform
|
||||
implementation reads these types in from extension category `types`
|
||||
and wraps them in a JavaScript interface.
|
78
docs/src/architecture/index.md
Normal file
78
docs/src/architecture/index.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Introduction
|
||||
|
||||
The purpose of this document is to familiarize developers with the
|
||||
overall architecture of Open MCT Web.
|
||||
|
||||
The target audience includes:
|
||||
|
||||
* _Platform maintainers_: Individuals involved in developing,
|
||||
extending, and maintaing capabilities of the platform.
|
||||
* _Integration developers_: Individuals tasked with integrated
|
||||
Open MCT Web into a larger system, who need to understand
|
||||
its inner workings sufficiently to complete this integration.
|
||||
|
||||
As the focus of this document is on architecture, whenever possible
|
||||
implementation details (such as relevant API or JSON syntax) have been
|
||||
omitted. These details may be found in the developer guide.
|
||||
|
||||
# Overview
|
||||
|
||||
Open MCT Web is client software: It runs in a web browser and
|
||||
provides a user interface, while communicating with various
|
||||
server-side resources through browser APIs.
|
||||
|
||||
```nomnoml
|
||||
#direction: right
|
||||
[Client|[Browser|[Open MCT Web]->[Browser APIs]]]
|
||||
[Server|[Web services]]
|
||||
[Client]<->[Server]
|
||||
```
|
||||
|
||||
While Open MCT Web can be configured to run as a standalone client,
|
||||
this is rarely very useful. Instead, it is intended to be used as a
|
||||
display and interaction layer for information obtained from a
|
||||
variety of back-end services. Doing so requires authoring or utilizing
|
||||
adapter plugins which allow Open MCT Web to interact with these services.
|
||||
|
||||
Typically, the pattern here is to provide a known interface that
|
||||
Open MCT Web can utilize, and implement it such that it interacts with
|
||||
whatever back-end provides the relevant information.
|
||||
Examples of back-ends that can be utilized in this fashion include
|
||||
databases for the persistence of user-created objects, or sources of
|
||||
telemetry data.
|
||||
|
||||
## Software Architecture
|
||||
|
||||
The simplest overview of Open MCT Web is to look at it as a "layered"
|
||||
architecture, where each layer more clearly specifies the behavior
|
||||
of the software.
|
||||
|
||||
```nomnoml
|
||||
#direction: down
|
||||
[Open MCT Web|
|
||||
[Platform]<->[Application]
|
||||
[Framework]->[Application]
|
||||
[Framework]->[Platform]
|
||||
]
|
||||
```
|
||||
|
||||
These layers are:
|
||||
|
||||
* [_Framework_](Framework.md): The framework layer is responsible for
|
||||
managing the interactions between application components. It has no
|
||||
application-specific knowledge; at this layer, we have only
|
||||
established an abstraction by which different software components
|
||||
may communicate and/or interact.
|
||||
* [_Platform_](Platform.md): The platform layer defines the general look, feel, and
|
||||
behavior of Open MCT Web. This includes user-facing components like
|
||||
Browse mode and Edit mode, as well as underlying elements of the
|
||||
information model and the general service infrastructure.
|
||||
* _Application_: The application layer defines specific features of
|
||||
an application built on Open MCT Web. This includes adapters to
|
||||
specific back-ends, new types of things for users to create, and
|
||||
new ways of visualizing objects within the system. This layer
|
||||
typically consists of a mix of custom plug-ins to Open MCT Web,
|
||||
as well as optional features (such as Plot view) included alongside
|
||||
the platform.
|
||||
|
||||
|
3
docs/src/guide/index.md
Normal file
3
docs/src/guide/index.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Developer Guide
|
||||
|
||||
This is a placeholder for the developer guide.
|
36
docs/src/index.html
Normal file
36
docs/src/index.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT Web includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>Open MCT Web Documentation</title>
|
||||
</head>
|
||||
<body class="user-environ" ng-view>
|
||||
Sections:
|
||||
<ul>
|
||||
<li><a href="api/">API</a></li>
|
||||
<li><a href="guide/">Developer Guide</a></li>
|
||||
<li><a href="architecture/">Architecture Overview</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
@ -34,6 +34,10 @@
|
||||
{
|
||||
"key": "time",
|
||||
"name": "Time"
|
||||
},
|
||||
{
|
||||
"key": "yesterday",
|
||||
"name": "Yesterday"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
@ -61,4 +65,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,8 @@
|
||||
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
|
||||
*/
|
||||
define(
|
||||
["./SinewaveTelemetry"],
|
||||
function (SinewaveTelemetry) {
|
||||
["./SinewaveTelemetrySeries"],
|
||||
function (SinewaveTelemetrySeries) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -45,7 +45,7 @@ define(
|
||||
function generateData(request) {
|
||||
return {
|
||||
key: request.key,
|
||||
telemetry: new SinewaveTelemetry(request)
|
||||
telemetry: new SinewaveTelemetrySeries(request)
|
||||
};
|
||||
}
|
||||
|
||||
@ -112,4 +112,4 @@ define(
|
||||
|
||||
return SinewaveTelemetryProvider;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -29,35 +29,47 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var firstObservedTime = Date.now();
|
||||
var ONE_DAY = 60 * 60 * 24,
|
||||
firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SinewaveTelemetry(request) {
|
||||
var latestObservedTime = Date.now(),
|
||||
count = Math.floor((latestObservedTime - firstObservedTime) / 1000),
|
||||
period = request.period || 30,
|
||||
generatorData = {};
|
||||
function SinewaveTelemetrySeries(request) {
|
||||
var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0,
|
||||
latestTime = Math.floor(Date.now() / 1000) - timeOffset,
|
||||
firstTime = firstObservedTime - timeOffset,
|
||||
endTime = (request.end !== undefined) ?
|
||||
Math.floor(request.end / 1000) : latestTime,
|
||||
count = Math.min(endTime, latestTime) - firstTime,
|
||||
period = +request.period || 30,
|
||||
generatorData = {},
|
||||
requestStart = (request.start === undefined) ? firstTime :
|
||||
Math.max(Math.floor(request.start / 1000), firstTime),
|
||||
offset = requestStart - firstTime;
|
||||
|
||||
if (request.size !== undefined) {
|
||||
offset = Math.max(offset, count - request.size);
|
||||
}
|
||||
|
||||
generatorData.getPointCount = function () {
|
||||
return count;
|
||||
return count - offset;
|
||||
};
|
||||
|
||||
generatorData.getDomainValue = function (i, domain) {
|
||||
return i * 1000 +
|
||||
(domain !== 'delta' ? firstObservedTime : 0);
|
||||
return (i + offset) * 1000 + firstTime * 1000 -
|
||||
(domain === 'yesterday' ? ONE_DAY : 0);
|
||||
};
|
||||
|
||||
generatorData.getRangeValue = function (i, range) {
|
||||
range = range || "sin";
|
||||
return Math[range](i * Math.PI * 2 / period);
|
||||
return Math[range]((i + offset) * Math.PI * 2 / period);
|
||||
};
|
||||
|
||||
return generatorData;
|
||||
}
|
||||
|
||||
return SinewaveTelemetry;
|
||||
return SinewaveTelemetrySeries;
|
||||
}
|
||||
);
|
||||
);
|
12
example/identity/bundle.json
Normal file
12
example/identity/bundle.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extensions": {
|
||||
"components": [
|
||||
{
|
||||
"implementation": "ExampleIdentityService.js",
|
||||
"provides": "identityService",
|
||||
"type": "provider",
|
||||
"depends": [ "dialogService" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
75
example/identity/src/ExampleIdentityService.js
Normal file
75
example/identity/src/ExampleIdentityService.js
Normal file
@ -0,0 +1,75 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var DEFAULT_IDENTITY = { key: "user", name: "Example User" },
|
||||
DIALOG_STRUCTURE = {
|
||||
name: "Identify Yourself",
|
||||
sections: [{ rows: [
|
||||
{
|
||||
name: "User ID",
|
||||
control: "textfield",
|
||||
key: "key",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "Human name",
|
||||
control: "textfield",
|
||||
key: "name",
|
||||
required: true
|
||||
}
|
||||
]}]
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Example implementation of an identity service. This prompts the
|
||||
* user to enter a name and user ID; in a more realistic
|
||||
* implementation, this would be read from a server, possibly
|
||||
* prompting for a user name and password (or similar) as
|
||||
* appropriate.
|
||||
*
|
||||
* @implements {IdentityService}
|
||||
* @memberof platform/identity
|
||||
*/
|
||||
function ExampleIdentityProvider(dialogService) {
|
||||
// Handle rejected dialog messages by treating the
|
||||
// current user as undefined.
|
||||
function echo(v) { return v; }
|
||||
function giveUndefined() { return undefined; }
|
||||
|
||||
this.userPromise =
|
||||
dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY)
|
||||
.then(echo, giveUndefined);
|
||||
}
|
||||
|
||||
ExampleIdentityProvider.prototype.getUser = function () {
|
||||
return this.userPromise;
|
||||
};
|
||||
|
||||
return ExampleIdentityProvider;
|
||||
}
|
||||
);
|
@ -1,86 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*global define,localStorage*/
|
||||
/**
|
||||
* Stubbed implementation of a persistence provider,
|
||||
* to permit objects to be created, saved, etc.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
function BrowserPersistenceProvider($q, SPACE) {
|
||||
var spaces = SPACE ? [SPACE] : [],
|
||||
promises = {
|
||||
as: function (value) {
|
||||
return $q.when(value);
|
||||
}
|
||||
},
|
||||
provider;
|
||||
|
||||
function setValue(key, value) {
|
||||
localStorage[key] = JSON.stringify(value);
|
||||
}
|
||||
|
||||
function getValue(key) {
|
||||
if (localStorage[key]) {
|
||||
return JSON.parse(localStorage[key]);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
provider = {
|
||||
listSpaces: function () {
|
||||
return promises.as(spaces);
|
||||
},
|
||||
listObjects: function (space) {
|
||||
var space_obj = getValue(space);
|
||||
return promises.as(Object.keys(space_obj));
|
||||
},
|
||||
createObject: function (space, key, value) {
|
||||
var space_obj = getValue(space);
|
||||
space_obj[key] = value;
|
||||
setValue(space, space_obj);
|
||||
return promises.as(true);
|
||||
},
|
||||
readObject: function (space, key) {
|
||||
var space_obj = getValue(space);
|
||||
return promises.as(space_obj[key]);
|
||||
},
|
||||
deleteObject: function (space, key, value) {
|
||||
var space_obj = getValue(space);
|
||||
delete space_obj[key];
|
||||
return promises.as(true);
|
||||
}
|
||||
};
|
||||
|
||||
provider.updateObject = provider.createObject;
|
||||
|
||||
return provider;
|
||||
|
||||
}
|
||||
|
||||
return BrowserPersistenceProvider;
|
||||
}
|
||||
);
|
@ -4,7 +4,11 @@
|
||||
{
|
||||
"implementation": "WatchIndicator.js",
|
||||
"depends": ["$interval", "$rootScope"]
|
||||
},
|
||||
{
|
||||
"implementation": "DigestIndicator.js",
|
||||
"depends": ["$interval", "$rootScope"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
77
example/profiling/src/DigestIndicator.js
Normal file
77
example/profiling/src/DigestIndicator.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Displays the number of digests that have occurred since the
|
||||
* indicator was first instantiated.
|
||||
* @constructor
|
||||
* @param $interval Angular's $interval
|
||||
* @implements {Indicator}
|
||||
*/
|
||||
function DigestIndicator($interval, $rootScope) {
|
||||
var digests = 0,
|
||||
displayed = 0,
|
||||
start = Date.now();
|
||||
|
||||
function update() {
|
||||
var secs = (Date.now() - start) / 1000;
|
||||
displayed = Math.round(digests / secs);
|
||||
}
|
||||
|
||||
function increment() {
|
||||
digests += 1;
|
||||
}
|
||||
|
||||
$rootScope.$watch(increment);
|
||||
|
||||
// Update state every second
|
||||
$interval(update, 1000);
|
||||
|
||||
// Provide initial state, too
|
||||
update();
|
||||
|
||||
return {
|
||||
getGlyph: function () {
|
||||
return ".";
|
||||
},
|
||||
getGlyphClass: function () {
|
||||
return undefined;
|
||||
},
|
||||
getText: function () {
|
||||
return displayed + " digests/sec";
|
||||
},
|
||||
getDescription: function () {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return DigestIndicator;
|
||||
|
||||
}
|
||||
);
|
@ -23,6 +23,7 @@
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title></title>
|
||||
<script type="text/javascript"
|
||||
src="platform/framework/lib/require.js"
|
||||
|
10
jsdoc.json
10
jsdoc.json
@ -1,10 +1,12 @@
|
||||
{
|
||||
"source": {
|
||||
"include": [
|
||||
"example/",
|
||||
"platform/"
|
||||
],
|
||||
"includePattern": "(example|platform)/.+\\.js$",
|
||||
"includePattern": "platform/.+\\.js$",
|
||||
"excludePattern": ".+\\Spec\\.js$|lib/.+"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown"
|
||||
]
|
||||
}
|
||||
|
13
package.json
13
package.json
@ -16,13 +16,22 @@
|
||||
"karma-jasmine": "^0.1.5",
|
||||
"karma-phantomjs-launcher": "^0.1.4",
|
||||
"karma-requirejs": "^0.2.2",
|
||||
"requirejs": "^2.1.17"
|
||||
"requirejs": "^2.1.17",
|
||||
"marked": "^0.3.5",
|
||||
"glob": ">= 3.0.0",
|
||||
"split": "^1.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nomnoml": "^0.0.3",
|
||||
"canvas": "^1.2.7"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "karma start --single-run",
|
||||
"jshint": "jshint platform example || exit 0",
|
||||
"jsdoc": "jsdoc -c jsdoc.json -r -d docs"
|
||||
"watch": "karma start",
|
||||
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
|
||||
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs",
|
||||
"docs": "npm run jsdoc ; npm run otherdoc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -21,6 +21,11 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
|
||||
/**
|
||||
* Implements Open MCT Web's About dialog.
|
||||
* @namespace platform/commonUI/about
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
@ -29,35 +34,36 @@ define(
|
||||
/**
|
||||
* The AboutController provides information to populate the
|
||||
* About dialog.
|
||||
* @memberof platform/commonUI/about
|
||||
* @constructor
|
||||
* @param {object[]} versions an array of version extensions;
|
||||
* injected from `versions[]`
|
||||
* @param $window Angular-injected window object
|
||||
*/
|
||||
function AboutController(versions, $window) {
|
||||
return {
|
||||
/**
|
||||
* Get version info. This is given as an array of
|
||||
* objects, where each object is intended to appear
|
||||
* as a line-item in the version information listing.
|
||||
* @memberof AboutController#
|
||||
* @returns {object[]} version information
|
||||
*/
|
||||
versions: function () {
|
||||
return versions;
|
||||
},
|
||||
/**
|
||||
* Open a new window (or tab, depending on browser
|
||||
* configuration) containing open source licenses.
|
||||
* @memberof AboutController#
|
||||
*/
|
||||
openLicenses: function () {
|
||||
// Open a new browser window at the licenses route
|
||||
$window.open("#/licenses");
|
||||
}
|
||||
};
|
||||
this.versionDefinitions = versions;
|
||||
this.$window = $window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get version info. This is given as an array of
|
||||
* objects, where each object is intended to appear
|
||||
* as a line-item in the version information listing.
|
||||
* @returns {object[]} version information
|
||||
*/
|
||||
AboutController.prototype.versions = function () {
|
||||
return this.versionDefinitions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a new window (or tab, depending on browser
|
||||
* configuration) containing open source licenses.
|
||||
*/
|
||||
AboutController.prototype.openLicenses = function () {
|
||||
// Open a new browser window at the licenses route
|
||||
this.$window.open("#/licenses");
|
||||
};
|
||||
|
||||
return AboutController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -29,20 +29,22 @@ define(
|
||||
/**
|
||||
* Provides extension-introduced licenses information to the
|
||||
* licenses route.
|
||||
* @memberof platform/commonUI/about
|
||||
* @constructor
|
||||
*/
|
||||
function LicenseController(licenses) {
|
||||
return {
|
||||
/**
|
||||
* Get license information.
|
||||
* @returns {Array} license extensions
|
||||
*/
|
||||
licenses: function () {
|
||||
return licenses;
|
||||
}
|
||||
};
|
||||
this.licenseDefinitions = licenses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get license information.
|
||||
* @returns {Array} license extensions
|
||||
* @memberof platform/commonUI/about.LicenseController#
|
||||
*/
|
||||
LicenseController.prototype.licenses = function () {
|
||||
return this.licenseDefinitions;
|
||||
};
|
||||
|
||||
return LicenseController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -29,21 +29,23 @@ define(
|
||||
/**
|
||||
* The LogoController provides functionality to the application
|
||||
* logo in the bottom-right of the user interface.
|
||||
* @memberof platform/commonUI/about
|
||||
* @constructor
|
||||
* @param {OverlayService} overlayService the overlay service
|
||||
*/
|
||||
function LogoController(overlayService) {
|
||||
return {
|
||||
/**
|
||||
* Display the About dialog.
|
||||
* @memberof LogoController#
|
||||
*/
|
||||
showAboutDialog: function () {
|
||||
overlayService.createOverlay("overlay-about");
|
||||
}
|
||||
};
|
||||
this.overlayService = overlayService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the About dialog.
|
||||
* @memberof LogoController#
|
||||
* @memberof platform/commonUI/about.LogoController#
|
||||
*/
|
||||
LogoController.prototype.showAboutDialog = function () {
|
||||
this.overlayService.createOverlay("overlay-about");
|
||||
};
|
||||
|
||||
return LogoController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -16,26 +16,41 @@
|
||||
{
|
||||
"key": "BrowseController",
|
||||
"implementation": "BrowseController.js",
|
||||
"depends": [ "$scope", "$route", "$location", "objectService", "navigationService", "urlService" ]
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$route",
|
||||
"$location",
|
||||
"$q",
|
||||
"objectService",
|
||||
"navigationService",
|
||||
"urlService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "BrowseTreeController",
|
||||
"implementation": "BrowseTreeController.js",
|
||||
"priority": "preferred",
|
||||
"depends": [ "$scope", "agentService" ]
|
||||
},
|
||||
{
|
||||
"key": "BrowseObjectController",
|
||||
"implementation": "BrowseObjectController.js",
|
||||
"depends": [ "$scope", "$location", "$route" ]
|
||||
"depends": [ "$scope", "$location", "$route", "$q",
|
||||
"navigationService" ]
|
||||
},
|
||||
{
|
||||
"key": "CreateMenuController",
|
||||
"implementation": "creation/CreateMenuController",
|
||||
"implementation": "creation/CreateMenuController.js",
|
||||
"depends": [ "$scope" ]
|
||||
},
|
||||
{
|
||||
"key": "LocatorController",
|
||||
"implementation": "creation/LocatorController",
|
||||
"depends": [ "$scope" ]
|
||||
"implementation": "creation/LocatorController.js",
|
||||
"depends": [ "$scope", "$timeout" ]
|
||||
},
|
||||
{
|
||||
"key": "MenuArrowController",
|
||||
"implementation": "MenuArrowController",
|
||||
"implementation": "MenuArrowController.js",
|
||||
"depends": [ "$scope" ]
|
||||
}
|
||||
],
|
||||
@ -45,16 +60,11 @@
|
||||
"templateUrl": "templates/create/locator.html"
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"key": "topbar-browse",
|
||||
"templateUrl": "templates/topbar-browse.html"
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "browse-object",
|
||||
"templateUrl": "templates/browse-object.html",
|
||||
"gestures": ["drop"],
|
||||
"uses": [ "view" ]
|
||||
},
|
||||
{
|
||||
@ -69,8 +79,8 @@
|
||||
{
|
||||
"key": "grid-item",
|
||||
"templateUrl": "templates/items/grid-item.html",
|
||||
"uses": [ "type", "action" ],
|
||||
"gestures": [ "info","menu" ]
|
||||
"uses": [ "type", "action", "location" ],
|
||||
"gestures": [ "info", "menu" ]
|
||||
},
|
||||
{
|
||||
"key": "object-header",
|
||||
@ -82,18 +92,18 @@
|
||||
"templateUrl": "templates/menu-arrow.html",
|
||||
"uses": [ "action" ],
|
||||
"gestures": [ "menu" ]
|
||||
},
|
||||
{
|
||||
"key": "back-arrow",
|
||||
"uses": [ "context" ],
|
||||
"templateUrl": "templates/back-arrow.html"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "navigationService",
|
||||
"implementation": "navigation/NavigationService.js"
|
||||
},
|
||||
{
|
||||
"key": "creationService",
|
||||
"implementation": "creation/CreationService.js",
|
||||
"depends": [ "persistenceService", "$q", "$log" ]
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
@ -110,7 +120,7 @@
|
||||
"depends": [ "urlService", "$window" ],
|
||||
"group": "windowing",
|
||||
"glyph": "y",
|
||||
"priority": "preferred"
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"key": "fullscreen",
|
||||
@ -118,7 +128,7 @@
|
||||
"category": "view-control",
|
||||
"group": "windowing",
|
||||
"glyph": "z",
|
||||
"priority": "default"
|
||||
"priority": "default"
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
@ -141,6 +151,13 @@
|
||||
"type": "provider",
|
||||
"implementation": "creation/CreateActionProvider.js",
|
||||
"depends": [ "typeService", "dialogService", "creationService", "policyService" ]
|
||||
},
|
||||
{
|
||||
"key": "CreationService",
|
||||
"provides": "creationService",
|
||||
"type": "provider",
|
||||
"implementation": "creation/CreationService.js",
|
||||
"depends": [ "persistenceService", "now", "$q", "$log" ]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
|
30
platform/commonUI/browse/res/templates/back-arrow.html
Normal file
30
platform/commonUI/browse/res/templates/back-arrow.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT Web includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
|
||||
<!-- Back Arrow Icon used on mobile-->
|
||||
<a
|
||||
class='type-icon icon ui-symbol s-back'
|
||||
ng-show="context.getPath().length > 2"
|
||||
ng-click="context.getParent().getCapability('action').perform('navigate')">
|
||||
{
|
||||
</a>
|
||||
|
@ -19,29 +19,100 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="BrowseObjectController">
|
||||
<div class="object-browse-bar bar abs">
|
||||
<div class="items-select left abs">
|
||||
<div ng-init="
|
||||
editBtns = [{ cssclass: 'save',title: 'Save' },{ cssclass: 'cancel',title: 'Discard Changes', action:'cancelEditing' }];
|
||||
"></div>
|
||||
<span ng-controller="BrowseObjectController"
|
||||
mct-before-unload="getUnloadWarning()">
|
||||
<a class="s-btn"
|
||||
style="opacity: 0.9; position:absolute; right: 250px; z-index: 100"
|
||||
ng-class="{ major:!editMode }"
|
||||
ng-click="editMode = !editMode">Set EditMode to {{!editMode}}</a>
|
||||
<div class="object-browse-bar bar l-flex">
|
||||
<div class="items-select left">
|
||||
<mct-representation key="'back-arrow'"
|
||||
mct-object="domainObject"
|
||||
class="l-back"></mct-representation>
|
||||
<mct-representation key="'object-header'" mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
|
||||
<div class="btn-bar right abs">
|
||||
<mct-representation key="'action-group'"
|
||||
mct-object="domainObject"
|
||||
parameters="{ category: 'view-control' }">
|
||||
</mct-representation>
|
||||
|
||||
<div class="btn-bar right">
|
||||
<mct-representation key="'switcher'"
|
||||
mct-object="domainObject"
|
||||
ng-model="representation">
|
||||
</mct-representation>
|
||||
<!-- Temporarily, on mobile, the action buttons are hidden-->
|
||||
<mct-representation key="'action-group'"
|
||||
mct-object="domainObject"
|
||||
parameters="{ category: 'view-control' }"
|
||||
class="mobile-hide">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='object-holder abs vscroll'>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject">
|
||||
</mct-representation>
|
||||
<div class="l-object-wrapper"
|
||||
ng-class="{ active:editMode, 'edit-main':editMode}">
|
||||
<div class="l-object-wrapper-inner l-flex flex-col">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex flex-row flex-align-end"
|
||||
ng-class="{ active:editMode }">
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <mct-toolbar name="mctToolbar"
|
||||
structure="toolbar.structure"
|
||||
ng-model="toolbar.state"
|
||||
class="flex-elem grow">
|
||||
</mct-toolbar>-->
|
||||
<!-- from toolbar.html -->
|
||||
<mct-toolbar-x class="flex-elem grow ellipsis">
|
||||
<form novalidate>
|
||||
<div class="tool-bar btn-bar">
|
||||
<span class="l-control-group">
|
||||
<span class="s-menu-btn">Foo</span>
|
||||
<span class="s-menu-btn">Bar</span>
|
||||
</span>
|
||||
<span class="l-control-group">
|
||||
<span class="s-menu-btn">Foo</span>
|
||||
<span class="s-menu-btn">Bar</span>
|
||||
<span class="s-menu-btn">Foo</span>
|
||||
<span class="s-menu-btn">Lorem</span>
|
||||
</span>
|
||||
<span class="l-control-group">
|
||||
<span class="s-menu-btn">Foo</span>
|
||||
<span class="s-menu-btn">Bar</span>
|
||||
<span class="s-menu-btn">Lorem</span>
|
||||
</span>
|
||||
<span class="s-menu-btn">Lorem</span>
|
||||
</div>
|
||||
</form>
|
||||
</mct-toolbar-x>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <mct-representation key="'edit-action-buttons'"
|
||||
mct-object="domainObject"
|
||||
class='flex-elem conclude-editing'>
|
||||
</mct-representation>-->
|
||||
<!-- from edit-action-buttons.html -->
|
||||
<span class='flex-elem conclude-editing'>
|
||||
<span ng-repeat="btn in editBtns">
|
||||
<a class='s-btn t-{{btn.cssclass}}'
|
||||
title='{{btn.title}}'
|
||||
ng-click="doAction(btn.action)"
|
||||
ng-class="{ major: $index === 0 }">
|
||||
<span class="title-label">{{btn.title}}</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="flex-elem grow object-holder">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -19,30 +19,51 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div content="jquery-wrapper" class="abs holder-all browse-mode">
|
||||
|
||||
<div class="abs holder-all browse-mode" ng-controller="BrowseController">
|
||||
<mct-include key="'topbar-browse'"></mct-include>
|
||||
<div class="holder browse-area s-browse-area abs" ng-controller="BrowseController">
|
||||
<div class="holder browse-area s-browse-area abs browse-wrapper"
|
||||
ng-controller="BrowseTreeController as tree"
|
||||
ng-class="tree.visible() ? 'browse-showtree' : 'browse-hidetree'">
|
||||
<mct-split-pane class='contents abs' anchor='left'>
|
||||
<div
|
||||
class='split-pane-component treeview pane left'
|
||||
>
|
||||
<mct-representation key="'create-button'" mct-object="navigatedObject">
|
||||
</mct-representation>
|
||||
<div class='holder tree-holder abs'>
|
||||
<mct-representation key="'tree'"
|
||||
mct-object="domainObject"
|
||||
ng-model="treeModel">
|
||||
<div class='split-pane-component treeview pane left'>
|
||||
<div class="holder abs l-mobile">
|
||||
<mct-representation key="'create-button'"
|
||||
mct-object="navigatedObject"
|
||||
mct-device="desktop">
|
||||
</mct-representation>
|
||||
<div class='holder search-holder abs'
|
||||
ng-class="{active: treeModel.search}">
|
||||
<mct-representation key="'search'"
|
||||
mct-object="domainObject"
|
||||
ng-model="treeModel">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div class='tree-holder abs mobile-tree-holder'
|
||||
ng-hide="treeModel.search">
|
||||
<mct-representation key="'tree'"
|
||||
mct-object="domainObject"
|
||||
parameters="tree"
|
||||
ng-model="treeModel">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mct-splitter></mct-splitter>
|
||||
<div class='split-pane-component items pane'>
|
||||
<div class='holder abs' id='content-area'>
|
||||
<mct-representation mct-object="navigatedObject" key="'browse-object'">
|
||||
|
||||
<mct-splitter class="mobile-hide"></mct-splitter>
|
||||
|
||||
<div class='split-pane-component items pane right-repr'>
|
||||
<div class='holder abs l-mobile' id='content-area'>
|
||||
<mct-representation mct-object="navigatedObject"
|
||||
key="'browse-object'">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<div class="key-properties ui-symbol icon mobile-menu-icon desktop-hide"
|
||||
mct-device="mobile"
|
||||
ng-click="tree.toggle()">m</div>
|
||||
</div>
|
||||
</mct-split-pane>
|
||||
</div>
|
||||
<mct-include key="'bottombar'"></mct-include>
|
||||
</div>
|
||||
|
||||
|
@ -19,11 +19,11 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class='object-header'>
|
||||
<span class="label s-label">
|
||||
<span class='type-icon icon ui-symbol'>{{type.getGlyph()}}</span>
|
||||
<div class='object-header object-header-mobile'>
|
||||
<span class='type-icon ui-symbol'>{{type.getGlyph()}}</span>
|
||||
<!--span class='type-name mobile-important-hide'>{{type.getName()}}</span-->
|
||||
<span class="l-elem-wrapper l-flex">
|
||||
<span ng-if="parameters.mode" class='action'>{{parameters.mode}}</span>
|
||||
<span class='type-name'>{{type.getName()}}</span>
|
||||
<span class='title-label'>{{model.name}}</span>
|
||||
<mct-representation key="'menu-arrow'" mct-object='domainObject'></mct-representation>
|
||||
</span>
|
||||
|
@ -20,10 +20,11 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="menu-element wrapper" ng-controller="ClickAwayController as createController">
|
||||
<div class="btn btn-menu create-btn major" ng-click="createController.toggle()">
|
||||
Create
|
||||
<div class="s-menu-btn major create-btn" ng-click="createController.toggle()">
|
||||
<span class="ui-symbol icon type-icon">+</span>
|
||||
<span class="title-label">Create</span>
|
||||
</div>
|
||||
<div class="menu dropdown super-menu" ng-show="createController.isActive()">
|
||||
<div class="menu super-menu" ng-show="createController.isActive()">
|
||||
<mct-representation mct-object="domainObject" key="'create-menu'">
|
||||
</mct-representation>
|
||||
</div>
|
||||
|
@ -23,9 +23,8 @@
|
||||
<div class="pane left menu-items">
|
||||
<ul>
|
||||
|
||||
<li ng-repeat="createAction in createActions">
|
||||
<li ng-repeat="createAction in createActions" ng-click="createAction.perform()">
|
||||
<a
|
||||
ng-click="createAction.perform()"
|
||||
ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
|
||||
ng-mouseleave="representation.activeMetadata = undefined">
|
||||
<span class="ui-symbol icon type-icon">
|
||||
@ -48,4 +47,4 @@
|
||||
{{representation.activeMetadata.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-controller="LocatorController" class="selector-list">
|
||||
<div>
|
||||
<div class="wrapper">
|
||||
<mct-representation key="'tree'"
|
||||
mct-object="rootObject"
|
||||
ng-model="treeModel">
|
||||
|
@ -21,27 +21,29 @@
|
||||
-->
|
||||
<!-- For selected, add class 'selected' to outer div -->
|
||||
<div class='item grid-item' ng-click='action.perform("navigate")'>
|
||||
<div class="contents abs">
|
||||
<div class='contents abs'>
|
||||
<div class='top-bar bar abs'>
|
||||
<div class='left abs'>
|
||||
<mct-include key="_checkbox"></mct-include>
|
||||
</div>
|
||||
<div class='right abs'>
|
||||
<div class='ui-symbol icon alert hidden' onclick="alert('Not yet functional. When this is visible, it means that this object needs to be updated. Clicking will allow that action via a dialog.');">!</div>
|
||||
<div class='ui-symbol icon profile' title="Shared">P</div>
|
||||
</div>
|
||||
<div class='ui-symbol profile' title='Shared'>O</div>
|
||||
<mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation>
|
||||
</div>
|
||||
<div class='item-main abs'>
|
||||
<div class='ui-symbol icon lg abs item-type'>{{type.getGlyph()}}</div>
|
||||
<div class='ui-symbol icon abs item-open'>}</div>
|
||||
<div class='ui-symbol icon lg item-type'>
|
||||
{{type.getGlyph()}}
|
||||
<span
|
||||
class="ui-symbol l-icon-link" title="This object is a link"
|
||||
ng-show="location.isLink()"
|
||||
></span>
|
||||
</div>
|
||||
<div class='ui-symbol abs item-open'>}</div>
|
||||
</div>
|
||||
<div class='bottom-bar bar abs'>
|
||||
<div class='title'>{{model.name}}</div>
|
||||
<div class='details'>
|
||||
<span>{{type.getName()}}</span>
|
||||
<span ng-show="model.composition !== undefined">
|
||||
{{model.composition.length}} Items
|
||||
- {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,36 +0,0 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT Web includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class='top-bar browse abs'>
|
||||
<!-- TO-DO: replace JS placeholders for click actions -->
|
||||
<div class='browse-main bar abs left'>
|
||||
<a class="menu-element btn btn-menu browse-btn" onclick="alert('Not yet functional. This will allow filtering of browsed objects and search context.');">
|
||||
<span class='ui-symbol badge major' href=''>*</span>Browse All<span class='ui-symbol invoke-menu'>v</span>
|
||||
</a>
|
||||
<input type='text' class='control filter' name='filter-available'/>
|
||||
<a class='icon icon-filter ui-symbol' onclick="alert('Not yet functional. This will initiate a search.');">M</a>
|
||||
</div>
|
||||
<div class='icon-buttons-main bar abs right'>
|
||||
<a class='ui-symbol icon major alert' onclick="alert('Not yet functional. This will allow updating of domain objects that need to be refreshed.');">!<span id='alert-actions-menu' class='ui-symbol invoke-menu'>v</span></a>
|
||||
<!--a class='ui-symbol icon major profile' href=''>P<span id='profile-actions-menu' class='ui-symbol invoke-menu'>v</span></a-->
|
||||
<a class='ui-symbol icon major settings' onclick="alert('Not yet functional. This will allow access to application configuration settings.');">G<span id='settings-actions-menu' class='ui-symbol invoke-menu'>v</span></a>
|
||||
</div>
|
||||
</div>
|
@ -22,11 +22,15 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining BrowseController. Created by vwoeltje on 11/7/14.
|
||||
* This bundle implements Browse mode.
|
||||
* @namespace platform/commonUI/browse
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
[
|
||||
'../../../representation/src/gestures/GestureConstants',
|
||||
'../../edit/src/objects/EditableDomainObject'
|
||||
],
|
||||
function (GestureConstants, EditableDomainObject) {
|
||||
"use strict";
|
||||
|
||||
var ROOT_ID = "ROOT",
|
||||
@ -39,9 +43,10 @@ define(
|
||||
* which Angular templates first have access to the domain object
|
||||
* hierarchy.
|
||||
*
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function BrowseController($scope, $route, $location, objectService, navigationService, urlService) {
|
||||
function BrowseController($scope, $route, $location, $q, objectService, navigationService, urlService) {
|
||||
var path = [ROOT_ID].concat(
|
||||
($route.current.params.ids || DEFAULT_PATH).split("/")
|
||||
);
|
||||
@ -53,7 +58,7 @@ define(
|
||||
|
||||
unlisten = $scope.$on('$locationChangeSuccess', function () {
|
||||
// Checks path to make sure /browse/ is at front
|
||||
// if so, change $route.current
|
||||
// if so, change $route.current
|
||||
if ($location.path().indexOf("/browse/") === 0) {
|
||||
$route.current = priorRoute;
|
||||
}
|
||||
@ -63,19 +68,23 @@ define(
|
||||
// path to new, addressed, path based on
|
||||
// domainObject
|
||||
$location.path(urlService.urlForLocation("browse", domainObject));
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Callback for updating the in-scope reference to the object
|
||||
// that is currently navigated-to.
|
||||
function setNavigation(domainObject) {
|
||||
$scope.navigatedObject = domainObject;
|
||||
|
||||
var wrappedObject = domainObject;
|
||||
|
||||
$scope.navigatedObject = wrappedObject;
|
||||
$scope.treeModel.selectedObject = domainObject;
|
||||
navigationService.setNavigation(domainObject);
|
||||
updateRoute(domainObject);
|
||||
}
|
||||
|
||||
function navigateTo(domainObject) {
|
||||
|
||||
// Check if an object has been navigated-to already...
|
||||
// If not, or if an ID path has been explicitly set in the URL,
|
||||
// navigate to the URL-specified object.
|
||||
@ -146,14 +155,15 @@ define(
|
||||
|
||||
// Also listen for changes which come from the tree
|
||||
$scope.$watch("treeModel.selectedObject", setNavigation);
|
||||
|
||||
|
||||
// Clean up when the scope is destroyed
|
||||
$scope.$on("$destroy", function () {
|
||||
navigationService.removeListener(setNavigation);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return BrowseController;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -22,17 +22,21 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
['../../../representation/src/gestures/GestureConstants',
|
||||
'../../edit/src/objects/EditableDomainObject'],
|
||||
function (GestureConstants, EditableDomainObject) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Controller for the `browse-object` representation of a domain
|
||||
* object (the right-hand side of Browse mode.)
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function BrowseObjectController($scope, $location, $route) {
|
||||
function BrowseObjectController($scope, $location, $route, $q, navigationService) {
|
||||
var navigatedObject;
|
||||
function setViewForDomainObject(domainObject) {
|
||||
|
||||
var locationViewKey = $location.search().view;
|
||||
|
||||
function selectViewIfMatching(view) {
|
||||
@ -46,6 +50,8 @@ define(
|
||||
((domainObject && domainObject.useCapability('view')) || [])
|
||||
.forEach(selectViewIfMatching);
|
||||
}
|
||||
$scope.editMode = domainObject.getDomainObject ? true : false;
|
||||
navigatedObject = domainObject;
|
||||
}
|
||||
|
||||
function updateQueryParam(viewKey) {
|
||||
@ -66,8 +72,18 @@ define(
|
||||
|
||||
$scope.$watch('domainObject', setViewForDomainObject);
|
||||
$scope.$watch('representation.selected.key', updateQueryParam);
|
||||
|
||||
$scope.cancelEditing = function() {
|
||||
navigationService.setNavigation($scope.domainObject.getDomainObject());
|
||||
}
|
||||
|
||||
$scope.doAction = function (action){
|
||||
$scope[action] && $scope[action]();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return BrowseObjectController;
|
||||
}
|
||||
);
|
||||
|
||||
|
78
platform/commonUI/browse/src/BrowseTreeController.js
Normal file
78
platform/commonUI/browse/src/BrowseTreeController.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise*/
|
||||
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Controller to provide the ability to show/hide the tree in
|
||||
* Browse mode.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/browse
|
||||
*/
|
||||
function BrowseTreeController($scope, agentService) {
|
||||
var self = this;
|
||||
this.agentService = agentService;
|
||||
this.state = true;
|
||||
|
||||
/**
|
||||
* Callback to invoke when any selection occurs in the tree.
|
||||
* This controller can be passed in as the `parameters` object
|
||||
* to the tree representation.
|
||||
*
|
||||
* @property {Function} callback
|
||||
* @memberof platform/commonUI/browse.BrowseTreeController#
|
||||
*/
|
||||
this.callback = function () {
|
||||
// Note that, since this is a callback to pass, this is not
|
||||
// declared as a method but as a property which happens to
|
||||
// be a function.
|
||||
if (agentService.isPhone() && agentService.isPortrait()) {
|
||||
// On phones, trees should collapse in portrait mode
|
||||
// when something is navigated-to.
|
||||
self.state = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the tree.
|
||||
*/
|
||||
BrowseTreeController.prototype.toggle = function () {
|
||||
this.state = !this.state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the desired visibility state of the tree.
|
||||
* @returns {boolean} true when visible
|
||||
*/
|
||||
BrowseTreeController.prototype.visible = function () {
|
||||
return this.state;
|
||||
};
|
||||
|
||||
return BrowseTreeController;
|
||||
}
|
||||
);
|
@ -33,19 +33,29 @@ define(
|
||||
* A left-click on the menu arrow should display a
|
||||
* context menu. This controller launches the context
|
||||
* menu.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function MenuArrowController($scope) {
|
||||
function showMenu(event) {
|
||||
var actionContext = {key: 'menu', domainObject: $scope.domainObject, event: event};
|
||||
$scope.domainObject.getCapability('action').perform(actionContext);
|
||||
}
|
||||
|
||||
return {
|
||||
showMenu: showMenu
|
||||
};
|
||||
this.$scope = $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a context menu for the domain object in this scope.
|
||||
*
|
||||
* @param event the browser event which caused this (used to
|
||||
* position the menu)
|
||||
*/
|
||||
MenuArrowController.prototype.showMenu = function (event) {
|
||||
var actionContext = {
|
||||
key: 'menu',
|
||||
domainObject: this.$scope.domainObject,
|
||||
event: event
|
||||
};
|
||||
|
||||
this.$scope.domainObject.getCapability('action').perform(actionContext);
|
||||
};
|
||||
|
||||
return MenuArrowController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -34,7 +34,10 @@ define(
|
||||
* domain objects of a specific type. This is the action that
|
||||
* is performed when a user uses the Create menu.
|
||||
*
|
||||
* @memberof platform/commonUI/browse
|
||||
* @implements {Action}
|
||||
* @constructor
|
||||
*
|
||||
* @param {Type} type the type of domain object to create
|
||||
* @param {DomainObject} parent the domain object that should
|
||||
* act as a container for the newly-created object
|
||||
@ -49,78 +52,84 @@ define(
|
||||
* of the newly-created domain object
|
||||
*/
|
||||
function CreateAction(type, parent, context, dialogService, creationService, policyService) {
|
||||
this.metadata = {
|
||||
key: 'create',
|
||||
glyph: type.getGlyph(),
|
||||
name: type.getName(),
|
||||
type: type.getKey(),
|
||||
description: type.getDescription(),
|
||||
context: context
|
||||
};
|
||||
|
||||
this.type = type;
|
||||
this.parent = parent;
|
||||
this.policyService = policyService;
|
||||
this.dialogService = dialogService;
|
||||
this.creationService = creationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new object of the given type.
|
||||
* This will prompt for user input first.
|
||||
*/
|
||||
CreateAction.prototype.perform = function () {
|
||||
/*
|
||||
Overview of steps in object creation:
|
||||
|
||||
1. Show dialog
|
||||
a. Prepare dialog contents
|
||||
b. Invoke dialogService
|
||||
a. Prepare dialog contents
|
||||
b. Invoke dialogService
|
||||
2. Create new object in persistence service
|
||||
a. Generate UUID
|
||||
b. Store model
|
||||
a. Generate UUID
|
||||
b. Store model
|
||||
3. Mutate destination container
|
||||
a. Get mutation capability
|
||||
b. Add new id to composition
|
||||
a. Get mutation capability
|
||||
b. Add new id to composition
|
||||
4. Persist destination container
|
||||
a. ...use persistence capability.
|
||||
a. ...use persistence capability.
|
||||
*/
|
||||
|
||||
function perform() {
|
||||
// The wizard will handle creating the form model based
|
||||
// on the type...
|
||||
var wizard = new CreateWizard(type, parent, policyService);
|
||||
// The wizard will handle creating the form model based
|
||||
// on the type...
|
||||
var wizard =
|
||||
new CreateWizard(this.type, this.parent, this.policyService),
|
||||
self = this;
|
||||
|
||||
// Create and persist the new object, based on user
|
||||
// input.
|
||||
function persistResult(formValue) {
|
||||
var parent = wizard.getLocation(formValue),
|
||||
newModel = wizard.createModel(formValue);
|
||||
return creationService.createObject(newModel, parent);
|
||||
}
|
||||
|
||||
function doNothing() {
|
||||
// Create cancelled, do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
return dialogService.getUserInput(
|
||||
wizard.getFormStructure(),
|
||||
wizard.getInitialFormValue()
|
||||
).then(persistResult, doNothing);
|
||||
// Create and persist the new object, based on user
|
||||
// input.
|
||||
function persistResult(formValue) {
|
||||
var parent = wizard.getLocation(formValue),
|
||||
newModel = wizard.createModel(formValue);
|
||||
return self.creationService.createObject(newModel, parent);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Create a new object of the given type.
|
||||
* This will prompt for user input first.
|
||||
* @method
|
||||
* @memberof CreateAction
|
||||
*/
|
||||
perform: perform,
|
||||
function doNothing() {
|
||||
// Create cancelled, do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata about this action. This includes fields:
|
||||
* * `name`: Human-readable name
|
||||
* * `key`: Machine-readable identifier ("create")
|
||||
* * `glyph`: Glyph to use as an icon for this action
|
||||
* * `description`: Human-readable description
|
||||
* * `context`: The context in which this action will be performed.
|
||||
*
|
||||
* @return {object} metadata about the create action
|
||||
*/
|
||||
getMetadata: function () {
|
||||
return {
|
||||
key: 'create',
|
||||
glyph: type.getGlyph(),
|
||||
name: type.getName(),
|
||||
type: type.getKey(),
|
||||
description: type.getDescription(),
|
||||
context: context
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
return this.dialogService.getUserInput(
|
||||
wizard.getFormStructure(),
|
||||
wizard.getInitialFormValue()
|
||||
).then(persistResult, doNothing);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Metadata associated with a Create action.
|
||||
* @typedef {ActionMetadata} CreateActionMetadata
|
||||
* @property {string} type the key for the type of domain object
|
||||
* to be created
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get metadata about this action.
|
||||
* @returns {CreateActionMetadata} metadata about this action
|
||||
*/
|
||||
CreateAction.prototype.getMetadata = function () {
|
||||
return this.metadata;
|
||||
};
|
||||
|
||||
return CreateAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -33,7 +33,10 @@ define(
|
||||
* The CreateActionProvider is an ActionProvider which introduces
|
||||
* a Create action for each creatable domain object type.
|
||||
*
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
* @implements {ActionService}
|
||||
*
|
||||
* @param {TypeService} typeService the type service, used to discover
|
||||
* available types
|
||||
* @param {DialogService} dialogService the dialog service, used by
|
||||
@ -44,44 +47,41 @@ define(
|
||||
* object creation.
|
||||
*/
|
||||
function CreateActionProvider(typeService, dialogService, creationService, policyService) {
|
||||
return {
|
||||
/**
|
||||
* Get all Create actions which are applicable in the provided
|
||||
* context.
|
||||
* @memberof CreateActionProvider
|
||||
* @method
|
||||
* @returns {CreateAction[]}
|
||||
*/
|
||||
getActions: function (actionContext) {
|
||||
var context = actionContext || {},
|
||||
key = context.key,
|
||||
destination = context.domainObject;
|
||||
|
||||
// We only provide Create actions, and we need a
|
||||
// domain object to serve as the container for the
|
||||
// newly-created object (although the user may later
|
||||
// make a different selection)
|
||||
if (key !== 'create' || !destination) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Introduce one create action per type
|
||||
return typeService.listTypes().filter(function (type) {
|
||||
return type.hasFeature("creation");
|
||||
}).map(function (type) {
|
||||
return new CreateAction(
|
||||
type,
|
||||
destination,
|
||||
context,
|
||||
dialogService,
|
||||
creationService,
|
||||
policyService
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
this.typeService = typeService;
|
||||
this.dialogService = dialogService;
|
||||
this.creationService = creationService;
|
||||
this.policyService = policyService;
|
||||
}
|
||||
|
||||
CreateActionProvider.prototype.getActions = function (actionContext) {
|
||||
var context = actionContext || {},
|
||||
key = context.key,
|
||||
destination = context.domainObject,
|
||||
self = this;
|
||||
|
||||
// We only provide Create actions, and we need a
|
||||
// domain object to serve as the container for the
|
||||
// newly-created object (although the user may later
|
||||
// make a different selection)
|
||||
if (key !== 'create' || !destination) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Introduce one create action per type
|
||||
return this.typeService.listTypes().filter(function (type) {
|
||||
return type.hasFeature("creation");
|
||||
}).map(function (type) {
|
||||
return new CreateAction(
|
||||
type,
|
||||
destination,
|
||||
context,
|
||||
self.dialogService,
|
||||
self.creationService,
|
||||
self.policyService
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return CreateActionProvider;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -34,6 +34,7 @@ define(
|
||||
* set of Create actions based on the currently-selected
|
||||
* domain object.
|
||||
*
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function CreateMenuController($scope) {
|
||||
@ -55,4 +56,4 @@ define(
|
||||
|
||||
return CreateMenuController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -21,12 +21,6 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Defines the CreateWizard, used by the CreateAction to
|
||||
* populate the form shown in dialog based on the created type.
|
||||
*
|
||||
* @module core/action/create-wizard
|
||||
*/
|
||||
define(
|
||||
function () {
|
||||
'use strict';
|
||||
@ -37,16 +31,33 @@ define(
|
||||
* @param {TypeImpl} type the type of domain object to be created
|
||||
* @param {DomainObject} parent the domain object to serve as
|
||||
* the initial parent for the created object, in the dialog
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
* @memberof module:core/action/create-wizard
|
||||
*/
|
||||
function CreateWizard(type, parent, policyService) {
|
||||
var model = type.getInitialModel(),
|
||||
properties = type.getProperties();
|
||||
this.type = type;
|
||||
this.model = type.getInitialModel();
|
||||
this.properties = type.getProperties();
|
||||
this.parent = parent;
|
||||
this.policyService = policyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form model for this wizard; this is a description
|
||||
* that will be rendered to an HTML form. See the
|
||||
* platform/forms bundle
|
||||
*
|
||||
* @return {FormModel} formModel the form model to
|
||||
* show in the create dialog
|
||||
*/
|
||||
CreateWizard.prototype.getFormStructure = function () {
|
||||
var sections = [],
|
||||
type = this.type,
|
||||
policyService = this.policyService;
|
||||
|
||||
function validateLocation(locatingObject) {
|
||||
var locatingType = locatingObject &&
|
||||
locatingObject.getCapability('type');
|
||||
locatingObject.getCapability('type');
|
||||
return locatingType && policyService.allow(
|
||||
"composition",
|
||||
locatingType,
|
||||
@ -54,96 +65,87 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
sections.push({
|
||||
name: "Properties",
|
||||
rows: this.properties.map(function (property, index) {
|
||||
// Property definition is same as form row definition
|
||||
var row = Object.create(property.getDefinition());
|
||||
|
||||
// Use index as the key into the formValue;
|
||||
// this correlates to the indexing provided by
|
||||
// getInitialFormValue
|
||||
row.key = index;
|
||||
|
||||
return row;
|
||||
}).filter(function (row) {
|
||||
// Only show rows which have defined controls
|
||||
return row && row.control;
|
||||
})
|
||||
});
|
||||
|
||||
// Ensure there is always a "save in" section
|
||||
sections.push({ name: 'Location', rows: [{
|
||||
name: "Save In",
|
||||
control: "locator",
|
||||
validate: validateLocation,
|
||||
key: "createParent"
|
||||
}]});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the form model for this wizard; this is a description
|
||||
* that will be rendered to an HTML form. See the
|
||||
* platform/forms bundle
|
||||
*
|
||||
* @return {FormModel} formModel the form model to
|
||||
* show in the create dialog
|
||||
*/
|
||||
getFormStructure: function () {
|
||||
var sections = [];
|
||||
|
||||
sections.push({
|
||||
name: "Properties",
|
||||
rows: properties.map(function (property, index) {
|
||||
// Property definition is same as form row definition
|
||||
var row = Object.create(property.getDefinition());
|
||||
|
||||
// Use index as the key into the formValue;
|
||||
// this correlates to the indexing provided by
|
||||
// getInitialFormValue
|
||||
row.key = index;
|
||||
|
||||
return row;
|
||||
})
|
||||
});
|
||||
|
||||
// Ensure there is always a "save in" section
|
||||
sections.push({ name: 'Location', rows: [{
|
||||
name: "Save In",
|
||||
control: "locator",
|
||||
validate: validateLocation,
|
||||
key: "createParent"
|
||||
}]});
|
||||
|
||||
return {
|
||||
sections: sections,
|
||||
name: "Create a New " + type.getName()
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Get the initial value for the form being described.
|
||||
* This will include the values for all properties described
|
||||
* in the structure.
|
||||
*
|
||||
* @returns {object} the initial value of the form
|
||||
*/
|
||||
getInitialFormValue: function () {
|
||||
// Start with initial values for properties
|
||||
var formValue = properties.map(function (property) {
|
||||
return property.getValue(model);
|
||||
});
|
||||
|
||||
// Include the createParent
|
||||
formValue.createParent = parent;
|
||||
|
||||
return formValue;
|
||||
},
|
||||
/**
|
||||
* Based on a populated form, get the domain object which
|
||||
* should be used as a parent for the newly-created object.
|
||||
* @return {DomainObject}
|
||||
*/
|
||||
getLocation: function (formValue) {
|
||||
return formValue.createParent || parent;
|
||||
},
|
||||
/**
|
||||
* Create the domain object model for a newly-created object,
|
||||
* based on user input read from a formModel.
|
||||
* @return {object} the domain object' model
|
||||
*/
|
||||
createModel: function (formValue) {
|
||||
// Clone
|
||||
var newModel = JSON.parse(JSON.stringify(model));
|
||||
|
||||
// Always use the type from the type definition
|
||||
newModel.type = type.getKey();
|
||||
|
||||
// Update all properties
|
||||
properties.forEach(function (property, index) {
|
||||
property.setValue(newModel, formValue[index]);
|
||||
});
|
||||
|
||||
return newModel;
|
||||
}
|
||||
sections: sections,
|
||||
name: "Create a New " + this.type.getName()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial value for the form being described.
|
||||
* This will include the values for all properties described
|
||||
* in the structure.
|
||||
*
|
||||
* @returns {object} the initial value of the form
|
||||
*/
|
||||
CreateWizard.prototype.getInitialFormValue = function () {
|
||||
// Start with initial values for properties
|
||||
var model = this.model,
|
||||
formValue = this.properties.map(function (property) {
|
||||
return property.getValue(model);
|
||||
});
|
||||
|
||||
}
|
||||
// Include the createParent
|
||||
formValue.createParent = this.parent;
|
||||
|
||||
return formValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Based on a populated form, get the domain object which
|
||||
* should be used as a parent for the newly-created object.
|
||||
* @return {DomainObject}
|
||||
*/
|
||||
CreateWizard.prototype.getLocation = function (formValue) {
|
||||
return formValue.createParent || this.parent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the domain object model for a newly-created object,
|
||||
* based on user input read from a formModel.
|
||||
* @return {object} the domain object model
|
||||
*/
|
||||
CreateWizard.prototype.createModel = function (formValue) {
|
||||
// Clone
|
||||
var newModel = JSON.parse(JSON.stringify(this.model));
|
||||
|
||||
// Always use the type from the type definition
|
||||
newModel.type = this.type.getKey();
|
||||
|
||||
// Update all properties
|
||||
this.properties.forEach(function (property, index) {
|
||||
property.setValue(newModel, formValue[index]);
|
||||
});
|
||||
|
||||
return newModel;
|
||||
};
|
||||
|
||||
return CreateWizard;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -39,15 +39,44 @@ define(
|
||||
* persisting new domain objects. Handles all actual object
|
||||
* mutation and persistence associated with domain object
|
||||
* creation.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function CreationService(persistenceService, $q, $log) {
|
||||
function CreationService(persistenceService, now, $q, $log) {
|
||||
this.persistenceService = persistenceService;
|
||||
this.$q = $q;
|
||||
this.$log = $log;
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new domain object with the provided model, as
|
||||
* a member of the provided parent domain object's composition.
|
||||
* This parent will additionally determine which persistence
|
||||
* space an object is created within (as it is possible to
|
||||
* have multiple persistence spaces attached.)
|
||||
*
|
||||
* Note that the model passed in for object creation may be
|
||||
* modified to attach additional initial properties associated
|
||||
* with domain object creation.
|
||||
*
|
||||
* @param {object} model the model for the newly-created
|
||||
* domain object
|
||||
* @param {DomainObject} parent the domain object which
|
||||
* should contain the newly-created domain object
|
||||
* in its composition
|
||||
* @return {Promise} a promise that will resolve when the domain
|
||||
* object has been created
|
||||
*/
|
||||
CreationService.prototype.createObject = function (model, parent) {
|
||||
var persistence = parent.getCapability("persistence"),
|
||||
self = this;
|
||||
|
||||
// Persist the new domain object's model; it will be fully
|
||||
// constituted as a domain object when loaded back, as all
|
||||
// domain object models are.
|
||||
function doPersist(space, id, model) {
|
||||
return persistenceService.createObject(
|
||||
return self.persistenceService.createObject(
|
||||
space,
|
||||
id,
|
||||
model
|
||||
@ -58,84 +87,44 @@ define(
|
||||
// composition, so that it will subsequently appear
|
||||
// as a child contained by that parent.
|
||||
function addToComposition(id, parent, parentPersistence) {
|
||||
var mutatationResult = parent.useCapability("mutation", function (model) {
|
||||
if (Array.isArray(model.composition)) {
|
||||
// Don't add if the id is already there
|
||||
if (model.composition.indexOf(id) === -1) {
|
||||
model.composition.push(id);
|
||||
}
|
||||
} else {
|
||||
// This is abnormal; composition should be an array
|
||||
$log.warn(NO_COMPOSITION_WARNING + parent.getId());
|
||||
return false; // Cancel mutation
|
||||
}
|
||||
});
|
||||
var compositionCapability = parent.getCapability('composition'),
|
||||
addResult = compositionCapability &&
|
||||
compositionCapability.add(id);
|
||||
|
||||
return $q.when(mutatationResult).then(function (result) {
|
||||
return self.$q.when(addResult).then(function (result) {
|
||||
if (!result) {
|
||||
$log.error("Could not mutate " + parent.getId());
|
||||
self.$log.error("Could not modify " + parent.getId());
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parentPersistence.persist().then(function () {
|
||||
// Locate and return new Object in context of parent.
|
||||
return parent
|
||||
.useCapability('composition')
|
||||
.then(function (children) {
|
||||
var i;
|
||||
for (i = 0; i < children.length; i += 1) {
|
||||
if (children[i].getId() === id) {
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Create a new domain object with the provided model as a
|
||||
// member of the specified parent's composition
|
||||
function createObject(model, parent) {
|
||||
var persistence = parent.getCapability("persistence");
|
||||
|
||||
// We need the parent's persistence capability to determine
|
||||
// what space to create the new object's model in.
|
||||
if (!persistence) {
|
||||
$log.warn(NON_PERSISTENT_WARNING);
|
||||
return $q.reject(new Error(NON_PERSISTENT_WARNING));
|
||||
}
|
||||
|
||||
// We create a new domain object in three sequential steps:
|
||||
// 1. Get a new UUID for the object
|
||||
// 2. Create a model with that ID in the persistence space
|
||||
// 3. Add that ID to
|
||||
return $q.when(
|
||||
uuid()
|
||||
).then(function (id) {
|
||||
return doPersist(persistence.getSpace(), id, model);
|
||||
}).then(function (id) {
|
||||
return addToComposition(id, parent, persistence);
|
||||
});
|
||||
// We need the parent's persistence capability to determine
|
||||
// what space to create the new object's model in.
|
||||
if (!persistence) {
|
||||
self.$log.warn(NON_PERSISTENT_WARNING);
|
||||
return self.$q.reject(new Error(NON_PERSISTENT_WARNING));
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Create a new domain object with the provided model, as
|
||||
* a member of the provided parent domain object's composition.
|
||||
* This parent will additionally determine which persistence
|
||||
* space an object is created within (as it is possible to
|
||||
* have multiple persistence spaces attached.)
|
||||
*
|
||||
* @param {object} model the model for the newly-created
|
||||
* domain object
|
||||
* @param {DomainObject} parent the domain object which
|
||||
* should contain the newly-created domain object
|
||||
* in its composition
|
||||
*/
|
||||
createObject: createObject
|
||||
};
|
||||
}
|
||||
// We create a new domain object in three sequential steps:
|
||||
// 1. Get a new UUID for the object
|
||||
// 2. Create a model with that ID in the persistence space
|
||||
// 3. Add that ID to
|
||||
return self.$q.when(uuid()).then(function (id) {
|
||||
model.persisted = self.now();
|
||||
return doPersist(persistence.getSpace(), id, model);
|
||||
}).then(function (id) {
|
||||
return addToComposition(id, parent, persistence);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return CreationService;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -30,9 +30,10 @@ define(
|
||||
* Controller for the "locator" control, which provides the
|
||||
* user with the ability to select a domain object as the
|
||||
* destination for a newly-created object in the Create menu.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function LocatorController($scope) {
|
||||
function LocatorController($scope, $timeout) {
|
||||
// Populate values needed by the locator control. These are:
|
||||
// * rootObject: The top-level object, since we want to show
|
||||
// the full tree
|
||||
@ -40,9 +41,19 @@ define(
|
||||
// used for bi-directional object selection.
|
||||
function setLocatingObject(domainObject, priorObject) {
|
||||
var context = domainObject &&
|
||||
domainObject.getCapability("context");
|
||||
domainObject.getCapability("context"),
|
||||
contextRoot = context && context.getRoot();
|
||||
|
||||
if (contextRoot && contextRoot !== $scope.rootObject) {
|
||||
$scope.rootObject = undefined;
|
||||
// Update the displayed tree on a timeout to avoid
|
||||
// an infinite digest exception.
|
||||
$timeout(function () {
|
||||
$scope.rootObject =
|
||||
(context && context.getRoot()) || $scope.rootObject;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
$scope.rootObject = (context && context.getRoot()) || $scope.rootObject;
|
||||
$scope.treeModel.selectedObject = domainObject;
|
||||
$scope.ngModel[$scope.field] = domainObject;
|
||||
|
||||
@ -51,10 +62,7 @@ define(
|
||||
$scope.structure &&
|
||||
$scope.structure.validate) {
|
||||
if (!$scope.structure.validate(domainObject)) {
|
||||
setLocatingObject(
|
||||
$scope.structure.validate(priorObject) ?
|
||||
priorObject : undefined
|
||||
);
|
||||
setLocatingObject(priorObject, undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -79,3 +87,4 @@ define(
|
||||
return LocatorController;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -31,32 +31,34 @@ define(
|
||||
|
||||
/**
|
||||
* The navigate action navigates to a specific domain object.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function NavigateAction(navigationService, $q, context) {
|
||||
var domainObject = context.domainObject;
|
||||
|
||||
function perform() {
|
||||
// Set navigation, and wrap like a promise
|
||||
return $q.when(navigationService.setNavigation(domainObject));
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Navigate to the object described in the context.
|
||||
* @returns {Promise} a promise that is resolved once the
|
||||
* navigation has been updated
|
||||
*/
|
||||
perform: perform
|
||||
};
|
||||
this.domainObject = context.domainObject;
|
||||
this.$q = $q;
|
||||
this.navigationService = navigationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the object described in the context.
|
||||
* @returns {Promise} a promise that is resolved once the
|
||||
* navigation has been updated
|
||||
*/
|
||||
NavigateAction.prototype.perform = function () {
|
||||
// Set navigation, and wrap like a promise
|
||||
return this.$q.when(
|
||||
this.navigationService.setNavigation(this.domainObject)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate as an action is only applicable when a domain object
|
||||
* is described in the action context.
|
||||
* @param {ActionContext} context the context in which the action
|
||||
* will be performed
|
||||
* @returns true if applicable
|
||||
* @returns {boolean} true if applicable
|
||||
*/
|
||||
NavigateAction.appliesTo = function (context) {
|
||||
return context.domainObject !== undefined;
|
||||
@ -64,4 +66,4 @@ define(
|
||||
|
||||
return NavigateAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -32,68 +32,58 @@ define(
|
||||
/**
|
||||
* The navigation service maintains the application's current
|
||||
* navigation state, and allows listening for changes thereto.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function NavigationService() {
|
||||
var navigated,
|
||||
callbacks = [];
|
||||
this.navigated = undefined;
|
||||
this.callbacks = [];
|
||||
}
|
||||
|
||||
// Getter for current navigation
|
||||
function getNavigation() {
|
||||
return navigated;
|
||||
}
|
||||
/**
|
||||
* Get the current navigation state.
|
||||
* @returns {DomainObject} the object that is navigated-to
|
||||
*/
|
||||
NavigationService.prototype.getNavigation = function () {
|
||||
return this.navigated;
|
||||
};
|
||||
|
||||
// Setter for navigation; invokes callbacks
|
||||
function setNavigation(value) {
|
||||
if (navigated !== value) {
|
||||
navigated = value;
|
||||
callbacks.forEach(function (callback) {
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a callback
|
||||
function addListener(callback) {
|
||||
callbacks.push(callback);
|
||||
}
|
||||
|
||||
// Filters out a callback
|
||||
function removeListener(callback) {
|
||||
callbacks = callbacks.filter(function (cb) {
|
||||
return cb !== callback;
|
||||
/**
|
||||
* Set the current navigation state. This will invoke listeners.
|
||||
* @param {DomainObject} domainObject the domain object to navigate to
|
||||
*/
|
||||
NavigationService.prototype.setNavigation = function (value) {
|
||||
if (this.navigated !== value) {
|
||||
this.navigated = value;
|
||||
this.callbacks.forEach(function (callback) {
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the current navigation state.
|
||||
*/
|
||||
getNavigation: getNavigation,
|
||||
/**
|
||||
* Set the current navigation state. Thiswill invoke listeners.
|
||||
* @param {DomainObject} value the domain object to navigate
|
||||
* to
|
||||
*/
|
||||
setNavigation: setNavigation,
|
||||
/**
|
||||
* Listen for changes in navigation. The passed callback will
|
||||
* be invoked with the new domain object of navigation when
|
||||
* this changes.
|
||||
* @param {function} callback the callback to invoke when
|
||||
* navigation state changes
|
||||
*/
|
||||
addListener: addListener,
|
||||
/**
|
||||
* Stop listening for changes in navigation state.
|
||||
* @param {function} callback the callback which should
|
||||
* no longer be invoked when navigation state
|
||||
* changes
|
||||
*/
|
||||
removeListener: removeListener
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Listen for changes in navigation. The passed callback will
|
||||
* be invoked with the new domain object of navigation when
|
||||
* this changes.
|
||||
* @param {function} callback the callback to invoke when
|
||||
* navigation state changes
|
||||
*/
|
||||
NavigationService.prototype.addListener = function (callback) {
|
||||
this.callbacks.push(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop listening for changes in navigation state.
|
||||
* @param {function} callback the callback which should
|
||||
* no longer be invoked when navigation state
|
||||
* changes
|
||||
*/
|
||||
NavigationService.prototype.removeListener = function (callback) {
|
||||
this.callbacks = this.callbacks.filter(function (cb) {
|
||||
return cb !== callback;
|
||||
});
|
||||
};
|
||||
|
||||
return NavigationService;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -35,36 +35,32 @@ define(
|
||||
/**
|
||||
* The fullscreen action toggles between fullscreen display
|
||||
* and regular in-window display.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function FullscreenAction(context) {
|
||||
return {
|
||||
/**
|
||||
* Toggle full screen state
|
||||
*/
|
||||
perform: function () {
|
||||
screenfull.toggle();
|
||||
},
|
||||
/**
|
||||
* Get metadata about this action, including the
|
||||
* applicable glyph to display.
|
||||
*/
|
||||
getMetadata: function () {
|
||||
// We override getMetadata, because the glyph and
|
||||
// description need to be determined at run-time
|
||||
// based on whether or not we are currently
|
||||
// full screen.
|
||||
var metadata = Object.create(FullscreenAction);
|
||||
metadata.glyph = screenfull.isFullscreen ? "_" : "z";
|
||||
metadata.description = screenfull.isFullscreen ?
|
||||
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
|
||||
metadata.group = "windowing";
|
||||
metadata.context = context;
|
||||
return metadata;
|
||||
}
|
||||
};
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
FullscreenAction.prototype.perform = function () {
|
||||
screenfull.toggle();
|
||||
};
|
||||
|
||||
FullscreenAction.prototype.getMetadata = function () {
|
||||
// We override getMetadata, because the glyph and
|
||||
// description need to be determined at run-time
|
||||
// based on whether or not we are currently
|
||||
// full screen.
|
||||
var metadata = Object.create(FullscreenAction);
|
||||
metadata.glyph = screenfull.isFullscreen ? "_" : "z";
|
||||
metadata.description = screenfull.isFullscreen ?
|
||||
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
|
||||
metadata.group = "windowing";
|
||||
metadata.context = this.context;
|
||||
return metadata;
|
||||
};
|
||||
|
||||
return FullscreenAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -33,35 +33,29 @@ define(
|
||||
/**
|
||||
* The new tab action allows a domain object to be opened
|
||||
* into a new browser tab.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function NewTabAction(urlService, $window, context) {
|
||||
// Returns the selected domain object
|
||||
// when using the context menu or the top right button
|
||||
// based on the context and the existance of the object
|
||||
// It is set to object an returned
|
||||
function getSelectedObject() {
|
||||
var object;
|
||||
if (context.selectedObject) {
|
||||
object = context.selectedObject;
|
||||
} else {
|
||||
object = context.domainObject;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
return {
|
||||
// Performs the open in new tab function
|
||||
// By calling the url service, the mode needed
|
||||
// (browse) and the domainObject is passed in and
|
||||
// the path is returned and opened in a new tab
|
||||
perform: function () {
|
||||
$window.open(urlService.urlForNewTab("browse", getSelectedObject()),
|
||||
"_blank");
|
||||
}
|
||||
context = context || {};
|
||||
|
||||
this.urlService = urlService;
|
||||
this.open = function () {
|
||||
$window.open.apply($window, arguments);
|
||||
};
|
||||
|
||||
// Choose the object to be opened into a new tab
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
}
|
||||
|
||||
NewTabAction.prototype.perform = function () {
|
||||
this.open(
|
||||
this.urlService.urlForNewTab("browse", this.domainObject),
|
||||
"_blank"
|
||||
);
|
||||
};
|
||||
|
||||
return NewTabAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -29,6 +29,7 @@ define(
|
||||
/**
|
||||
* Updates the title of the current window to reflect the name
|
||||
* of the currently navigated-to domain object.
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function WindowTitler(navigationService, $rootScope, $document) {
|
||||
@ -49,4 +50,4 @@ define(
|
||||
|
||||
return WindowTitler;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -146,12 +146,14 @@ define(
|
||||
expect(mockScope.navigatedObject).toEqual(mockDomainObject);
|
||||
});
|
||||
|
||||
|
||||
it("releases its navigation listener when its scope is destroyed", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
"$destroy",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
|
||||
// Should remove the listener it added earlier
|
||||
expect(mockNavigationService.removeListener).toHaveBeenCalledWith(
|
||||
mockNavigationService.addListener.mostRecentCall.args[0]
|
||||
@ -222,16 +224,16 @@ define(
|
||||
mockNavigationService.addListener.mostRecentCall.args[0](
|
||||
mockNextObject
|
||||
);
|
||||
|
||||
|
||||
// Allows the path index to be checked
|
||||
// prior to setting $route.current
|
||||
// prior to setting $route.current
|
||||
mockLocation.path.andReturn("/browse/");
|
||||
|
||||
|
||||
// Exercise the Angular workaround
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
expect(mockUnlisten).toHaveBeenCalled();
|
||||
|
||||
// location.path to be called with the urlService's
|
||||
|
||||
// location.path to be called with the urlService's
|
||||
// urlFor function with the next domainObject and mode
|
||||
expect(mockLocation.path).toHaveBeenCalledWith(
|
||||
mockUrlService.urlForLocation(mockMode, mockNextObject)
|
||||
|
90
platform/commonUI/browse/test/BrowseTreeControllerSpec.js
Normal file
90
platform/commonUI/browse/test/BrowseTreeControllerSpec.js
Normal file
@ -0,0 +1,90 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../src/BrowseTreeController"],
|
||||
function (BrowseTreeController) {
|
||||
'use strict';
|
||||
|
||||
describe("The BrowseTreeController", function () {
|
||||
var mockScope,
|
||||
mockAgentService,
|
||||
mockDomainObjects,
|
||||
controller;
|
||||
|
||||
// We want to reinstantiate for each test case
|
||||
// because device state can influence constructor-time behavior
|
||||
function instantiateController() {
|
||||
return new BrowseTreeController(
|
||||
mockScope,
|
||||
mockAgentService
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", [ "$on" ]);
|
||||
mockDomainObjects = ['a', 'b'].map(function (id) {
|
||||
var mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject-' + id,
|
||||
[ 'getId', 'getModel', 'getCapability' ]
|
||||
);
|
||||
|
||||
mockDomainObject.getId.andReturn(id);
|
||||
mockDomainObject.getModel.andReturn({});
|
||||
|
||||
return mockDomainObject;
|
||||
});
|
||||
mockAgentService = jasmine.createSpyObj(
|
||||
"agentService",
|
||||
[ "isMobile", "isPhone", "isTablet", "isPortrait", "isLandscape" ]
|
||||
);
|
||||
});
|
||||
|
||||
it("is initially visible", function () {
|
||||
expect(instantiateController().visible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("allows visibility to be toggled", function () {
|
||||
controller = instantiateController();
|
||||
controller.toggle();
|
||||
expect(controller.visible()).toBeFalsy();
|
||||
controller.toggle();
|
||||
expect(controller.visible()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("collapses on navigation changes on portrait-oriented phones", function () {
|
||||
mockAgentService.isMobile.andReturn(true);
|
||||
mockAgentService.isPhone.andReturn(true);
|
||||
mockAgentService.isPortrait.andReturn(true);
|
||||
controller = instantiateController();
|
||||
expect(controller.visible()).toBeTruthy();
|
||||
|
||||
// Simulate a change from the tree by invoking controller's
|
||||
controller.callback();
|
||||
|
||||
// Tree should have collapsed
|
||||
expect(controller.visible()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -42,7 +42,9 @@ define(
|
||||
"property" + name,
|
||||
[ "getDefinition", "getValue", "setValue" ]
|
||||
);
|
||||
mockProperty.getDefinition.andReturn({});
|
||||
mockProperty.getDefinition.andReturn({
|
||||
control: "textfield"
|
||||
});
|
||||
mockProperty.getValue.andReturn(name);
|
||||
return mockProperty;
|
||||
}
|
||||
@ -157,4 +159,4 @@ define(
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -31,6 +31,7 @@ define(
|
||||
|
||||
describe("The creation service", function () {
|
||||
var mockPersistenceService,
|
||||
mockNow,
|
||||
mockQ,
|
||||
mockLog,
|
||||
mockParentObject,
|
||||
@ -38,6 +39,7 @@ define(
|
||||
mockMutationCapability,
|
||||
mockPersistenceCapability,
|
||||
mockCompositionCapability,
|
||||
mockContextCapability,
|
||||
mockCapabilities,
|
||||
creationService;
|
||||
|
||||
@ -62,6 +64,7 @@ define(
|
||||
"persistenceService",
|
||||
[ "createObject" ]
|
||||
);
|
||||
mockNow = jasmine.createSpy('now');
|
||||
mockQ = { when: mockPromise, reject: mockReject };
|
||||
mockLog = jasmine.createSpyObj(
|
||||
"$log",
|
||||
@ -85,24 +88,32 @@ define(
|
||||
);
|
||||
mockCompositionCapability = jasmine.createSpyObj(
|
||||
"composition",
|
||||
["invoke"]
|
||||
["invoke", "add"]
|
||||
);
|
||||
mockContextCapability = jasmine.createSpyObj(
|
||||
"context",
|
||||
["getPath"]
|
||||
);
|
||||
mockCapabilities = {
|
||||
mutation: mockMutationCapability,
|
||||
persistence: mockPersistenceCapability,
|
||||
composition: mockCompositionCapability
|
||||
composition: mockCompositionCapability,
|
||||
context: mockContextCapability
|
||||
};
|
||||
|
||||
mockPersistenceService.createObject.andReturn(
|
||||
mockPromise(true)
|
||||
);
|
||||
|
||||
mockNow.andReturn(12321);
|
||||
|
||||
mockParentObject.getCapability.andCallFake(function (key) {
|
||||
return mockCapabilities[key];
|
||||
});
|
||||
mockParentObject.useCapability.andCallFake(function (key, value) {
|
||||
return mockCapabilities[key].invoke(value);
|
||||
});
|
||||
mockParentObject.getId.andReturn('parentId');
|
||||
|
||||
mockPersistenceCapability.persist.andReturn(
|
||||
mockPromise(true)
|
||||
@ -113,9 +124,11 @@ define(
|
||||
mockCompositionCapability.invoke.andReturn(
|
||||
mockPromise([mockNewObject])
|
||||
);
|
||||
mockCompositionCapability.add.andReturn(mockPromise(true));
|
||||
|
||||
creationService = new CreationService(
|
||||
mockPersistenceService,
|
||||
mockNow,
|
||||
mockQ,
|
||||
mockLog
|
||||
);
|
||||
@ -136,33 +149,34 @@ define(
|
||||
parentModel = { composition: ["notAnyUUID"] };
|
||||
creationService.createObject(model, mockParentObject);
|
||||
|
||||
// Invoke the mutation callback
|
||||
expect(mockMutationCapability.invoke).toHaveBeenCalled();
|
||||
mockMutationCapability.invoke.mostRecentCall.args[0](parentModel);
|
||||
|
||||
// Should have a longer composition now, with the new UUID
|
||||
expect(parentModel.composition.length).toEqual(2);
|
||||
// Verify that a new ID was added
|
||||
expect(mockCompositionCapability.add)
|
||||
.toHaveBeenCalledWith(jasmine.any(String));
|
||||
});
|
||||
|
||||
it("warns if parent has no composition", function () {
|
||||
var model = { someKey: "some value" },
|
||||
parentModel = { };
|
||||
creationService.createObject(model, mockParentObject);
|
||||
it("provides the newly-created object", function () {
|
||||
var mockDomainObject = jasmine.createSpyObj(
|
||||
'newDomainObject',
|
||||
['getId', 'getModel', 'getCapability']
|
||||
),
|
||||
mockCallback = jasmine.createSpy('callback');
|
||||
|
||||
// Verify precondition; no prior warnings
|
||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||
// Act as if the object had been created
|
||||
mockCompositionCapability.add.andCallFake(function (id) {
|
||||
mockDomainObject.getId.andReturn(id);
|
||||
mockCompositionCapability.invoke
|
||||
.andReturn(mockPromise([mockDomainObject]));
|
||||
return mockPromise(mockDomainObject);
|
||||
});
|
||||
|
||||
// Invoke the mutation callback
|
||||
expect(mockMutationCapability.invoke).toHaveBeenCalled();
|
||||
mockMutationCapability.invoke.mostRecentCall.args[0](parentModel);
|
||||
// Should find it in the composition
|
||||
creationService.createObject({}, mockParentObject)
|
||||
.then(mockCallback);
|
||||
|
||||
expect(mockCallback).toHaveBeenCalledWith(mockDomainObject);
|
||||
|
||||
// Should have a longer composition now, with the new UUID
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
// Composition should still be undefined
|
||||
expect(parentModel.composition).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it("warns if parent has no persistence capability", function () {
|
||||
// Callbacks
|
||||
var success = jasmine.createSpy("success"),
|
||||
@ -178,7 +192,6 @@ define(
|
||||
expect(mockLog.warn).toHaveBeenCalled();
|
||||
expect(success).not.toHaveBeenCalled();
|
||||
expect(failure).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it("logs an error when mutaton fails", function () {
|
||||
@ -187,14 +200,19 @@ define(
|
||||
var model = { someKey: "some value" },
|
||||
parentModel = { composition: ["notAnyUUID"] };
|
||||
|
||||
mockMutationCapability.invoke.andReturn(mockPromise(false));
|
||||
mockCompositionCapability.add.andReturn(mockPromise(false));
|
||||
|
||||
creationService.createObject(model, mockParentObject);
|
||||
|
||||
expect(mockLog.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("attaches a 'persisted' timestamp", function () {
|
||||
var model = { someKey: "some value" };
|
||||
creationService.createObject(model, mockParentObject);
|
||||
expect(model.persisted).toEqual(mockNow());
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -31,6 +31,7 @@ define(
|
||||
|
||||
describe("The locator controller", function () {
|
||||
var mockScope,
|
||||
mockTimeout,
|
||||
mockDomainObject,
|
||||
mockRootObject,
|
||||
mockContext,
|
||||
@ -41,6 +42,7 @@ define(
|
||||
"$scope",
|
||||
[ "$watch" ]
|
||||
);
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getCapability" ]
|
||||
@ -60,7 +62,7 @@ define(
|
||||
mockScope.ngModel = {};
|
||||
mockScope.field = "someField";
|
||||
|
||||
controller = new LocatorController(mockScope);
|
||||
controller = new LocatorController(mockScope, mockTimeout);
|
||||
});
|
||||
|
||||
it("adds a treeModel to scope", function () {
|
||||
@ -80,6 +82,7 @@ define(
|
||||
// Need to pass on selection changes as updates to
|
||||
// the control's value
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
expect(mockScope.ngModel.someField).toEqual(mockDomainObject);
|
||||
expect(mockScope.rootObject).toEqual(mockRootObject);
|
||||
|
||||
@ -95,6 +98,7 @@ define(
|
||||
|
||||
// Pass selection change
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
|
||||
expect(mockScope.structure.validate).toHaveBeenCalled();
|
||||
// Change should have been rejected
|
||||
@ -108,14 +112,16 @@ define(
|
||||
);
|
||||
|
||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
expect(mockScope.ngModelController.$setValidity)
|
||||
.toHaveBeenCalledWith(jasmine.any(String), true);
|
||||
|
||||
mockScope.$watch.mostRecentCall.args[1](undefined);
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
expect(mockScope.ngModelController.$setValidity)
|
||||
.toHaveBeenCalledWith(jasmine.any(String), false);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
[
|
||||
"BrowseController",
|
||||
"BrowseObjectController",
|
||||
"BrowseTreeController",
|
||||
"MenuArrowController",
|
||||
"creation/CreateAction",
|
||||
"creation/CreateActionProvider",
|
||||
|
@ -25,7 +25,7 @@
|
||||
All fields marked <span class="ui-symbol req">*</span> are required.
|
||||
</div>
|
||||
</div>
|
||||
<div class="abs form outline editor">
|
||||
<div class="abs form editor">
|
||||
<div class='abs contents l-dialog'>
|
||||
<mct-form ng-model="ngModel.value"
|
||||
structure="ngModel.structure"
|
||||
@ -34,13 +34,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="abs bottom-bar">
|
||||
<a class='btn lg major'
|
||||
href=''
|
||||
<a class='s-btn major'
|
||||
ng-class="{ disabled: !createForm.$valid }"
|
||||
ng-click="ngModel.confirm()">
|
||||
OK
|
||||
</a>
|
||||
<a class='btn lg subtle' href='' ng-click="ngModel.cancel()">
|
||||
<a class='s-btn'
|
||||
ng-click="ngModel.cancel()">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
@ -35,7 +35,7 @@
|
||||
<div class="abs bottom-bar">
|
||||
<a ng-repeat="option in ngModel.dialog.options"
|
||||
href=''
|
||||
class="btn lg"
|
||||
class="s-btn lg"
|
||||
title="{{option.description}}"
|
||||
ng-click="ngModel.confirm(option.key)"
|
||||
ng-class="{ major: $first, subtle: !$first }">
|
||||
|
@ -25,7 +25,7 @@
|
||||
<a href=""
|
||||
ng-click="ngModel.cancel()"
|
||||
ng-if="ngModel.cancel"
|
||||
class="btn normal ui-symbol close">
|
||||
class="clk-icon icon ui-symbol close">
|
||||
x
|
||||
</a>
|
||||
<div class="abs contents" ng-transclude>
|
||||
|
@ -22,7 +22,9 @@
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Module defining DialogService. Created by vwoeltje on 11/10/14.
|
||||
* This bundle implements the dialog service, which can be used to
|
||||
* launch dialogs for user input & notifications.
|
||||
* @namespace platform/commonUI/dialog
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
@ -32,128 +34,130 @@ define(
|
||||
* The dialog service is responsible for handling window-modal
|
||||
* communication with the user, such as displaying forms for user
|
||||
* input.
|
||||
* @memberof platform/commonUI/dialog
|
||||
* @constructor
|
||||
*/
|
||||
function DialogService(overlayService, $q, $log) {
|
||||
var overlay,
|
||||
dialogVisible = false;
|
||||
|
||||
// Stop showing whatever overlay is currently active
|
||||
// (e.g. because the user hit cancel)
|
||||
function dismiss() {
|
||||
if (overlay) {
|
||||
overlay.dismiss();
|
||||
}
|
||||
dialogVisible = false;
|
||||
}
|
||||
|
||||
function getDialogResponse(key, model, resultGetter) {
|
||||
// We will return this result as a promise, because user
|
||||
// input is asynchronous.
|
||||
var deferred = $q.defer(),
|
||||
overlayModel;
|
||||
|
||||
// Confirm function; this will be passed in to the
|
||||
// overlay-dialog template and associated with a
|
||||
// OK button click
|
||||
function confirm(value) {
|
||||
// Pass along the result
|
||||
deferred.resolve(resultGetter ? resultGetter() : value);
|
||||
|
||||
// Stop showing the dialog
|
||||
dismiss();
|
||||
}
|
||||
|
||||
// Cancel function; this will be passed in to the
|
||||
// overlay-dialog template and associated with a
|
||||
// Cancel or X button click
|
||||
function cancel() {
|
||||
deferred.reject();
|
||||
dismiss();
|
||||
}
|
||||
|
||||
// Add confirm/cancel callbacks
|
||||
model.confirm = confirm;
|
||||
model.cancel = cancel;
|
||||
|
||||
if (dialogVisible) {
|
||||
// Only one dialog should be shown at a time.
|
||||
// The application design should be such that
|
||||
// we never even try to do this.
|
||||
$log.warn([
|
||||
"Dialog already showing; ",
|
||||
"unable to show ",
|
||||
model.name
|
||||
].join(""));
|
||||
deferred.reject();
|
||||
} else {
|
||||
// Add the overlay using the OverlayService, which
|
||||
// will handle actual insertion into the DOM
|
||||
overlay = overlayService.createOverlay(
|
||||
key,
|
||||
model
|
||||
);
|
||||
|
||||
// Track that a dialog is already visible, to
|
||||
// avoid spawning multiple dialogs at once.
|
||||
dialogVisible = true;
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getUserInput(formModel, value) {
|
||||
var overlayModel = {
|
||||
title: formModel.name,
|
||||
message: formModel.message,
|
||||
structure: formModel,
|
||||
value: value
|
||||
};
|
||||
|
||||
// Provide result from the model
|
||||
function resultGetter() {
|
||||
return overlayModel.value;
|
||||
}
|
||||
|
||||
// Show the overlay-dialog
|
||||
return getDialogResponse(
|
||||
"overlay-dialog",
|
||||
overlayModel,
|
||||
resultGetter
|
||||
);
|
||||
}
|
||||
|
||||
function getUserChoice(dialogModel) {
|
||||
// Show the overlay-options dialog
|
||||
return getDialogResponse(
|
||||
"overlay-options",
|
||||
{ dialog: dialogModel }
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Request user input via a window-modal dialog.
|
||||
*
|
||||
* @param {FormModel} formModel a description of the form
|
||||
* to be shown (see platform/forms)
|
||||
* @param {object} value the initial state of the form
|
||||
* @returns {Promise} a promsie for the form value that the
|
||||
* user has supplied; this may be rejected if
|
||||
* user input cannot be obtained (for instance,
|
||||
* because the user cancelled the dialog)
|
||||
*/
|
||||
getUserInput: getUserInput,
|
||||
/**
|
||||
* Request that the user chooses from a set of options,
|
||||
* which will be shown as buttons.
|
||||
*
|
||||
* @param dialogModel a description of the dialog to show
|
||||
*/
|
||||
getUserChoice: getUserChoice
|
||||
};
|
||||
this.overlayService = overlayService;
|
||||
this.$q = $q;
|
||||
this.$log = $log;
|
||||
this.overlay = undefined;
|
||||
this.dialogVisible = false;
|
||||
}
|
||||
|
||||
// Stop showing whatever overlay is currently active
|
||||
// (e.g. because the user hit cancel)
|
||||
DialogService.prototype.dismiss = function () {
|
||||
var overlay = this.overlay;
|
||||
if (overlay) {
|
||||
overlay.dismiss();
|
||||
}
|
||||
this.dialogVisible = false;
|
||||
};
|
||||
|
||||
DialogService.prototype.getDialogResponse = function (key, model, resultGetter) {
|
||||
// We will return this result as a promise, because user
|
||||
// input is asynchronous.
|
||||
var deferred = this.$q.defer(),
|
||||
self = this;
|
||||
|
||||
// Confirm function; this will be passed in to the
|
||||
// overlay-dialog template and associated with a
|
||||
// OK button click
|
||||
function confirm(value) {
|
||||
// Pass along the result
|
||||
deferred.resolve(resultGetter ? resultGetter() : value);
|
||||
|
||||
// Stop showing the dialog
|
||||
self.dismiss();
|
||||
}
|
||||
|
||||
// Cancel function; this will be passed in to the
|
||||
// overlay-dialog template and associated with a
|
||||
// Cancel or X button click
|
||||
function cancel() {
|
||||
deferred.reject();
|
||||
self.dismiss();
|
||||
}
|
||||
|
||||
// Add confirm/cancel callbacks
|
||||
model.confirm = confirm;
|
||||
model.cancel = cancel;
|
||||
|
||||
if (this.dialogVisible) {
|
||||
// Only one dialog should be shown at a time.
|
||||
// The application design should be such that
|
||||
// we never even try to do this.
|
||||
this.$log.warn([
|
||||
"Dialog already showing; ",
|
||||
"unable to show ",
|
||||
model.name
|
||||
].join(""));
|
||||
deferred.reject();
|
||||
} else {
|
||||
// Add the overlay using the OverlayService, which
|
||||
// will handle actual insertion into the DOM
|
||||
this.overlay = this.overlayService.createOverlay(
|
||||
key,
|
||||
model
|
||||
);
|
||||
|
||||
// Track that a dialog is already visible, to
|
||||
// avoid spawning multiple dialogs at once.
|
||||
this.dialogVisible = true;
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request user input via a window-modal dialog.
|
||||
*
|
||||
* @param {FormModel} formModel a description of the form
|
||||
* to be shown (see platform/forms)
|
||||
* @param {object} value the initial state of the form
|
||||
* @returns {Promise} a promise for the form value that the
|
||||
* user has supplied; this may be rejected if
|
||||
* user input cannot be obtained (for instance,
|
||||
* because the user cancelled the dialog)
|
||||
*/
|
||||
DialogService.prototype.getUserInput = function (formModel, value) {
|
||||
var overlayModel = {
|
||||
title: formModel.name,
|
||||
message: formModel.message,
|
||||
structure: formModel,
|
||||
value: value
|
||||
};
|
||||
|
||||
// Provide result from the model
|
||||
function resultGetter() {
|
||||
return overlayModel.value;
|
||||
}
|
||||
|
||||
// Show the overlay-dialog
|
||||
return this.getDialogResponse(
|
||||
"overlay-dialog",
|
||||
overlayModel,
|
||||
resultGetter
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Request that the user chooses from a set of options,
|
||||
* which will be shown as buttons.
|
||||
*
|
||||
* @param dialogModel a description of the dialog to show
|
||||
* @return {Promise} a promise for the user's choice
|
||||
*/
|
||||
DialogService.prototype.getUserChoice = function (dialogModel) {
|
||||
// Show the overlay-options dialog
|
||||
return this.getDialogResponse(
|
||||
"overlay-options",
|
||||
{ dialog: dialogModel }
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return DialogService;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -43,57 +43,63 @@ define(
|
||||
* particularly where a multiple-overlay effect is not specifically
|
||||
* desired).
|
||||
*
|
||||
* @memberof platform/commonUI/dialog
|
||||
* @constructor
|
||||
*/
|
||||
function OverlayService($document, $compile, $rootScope) {
|
||||
function createOverlay(key, overlayModel) {
|
||||
// Create a new scope for this overlay
|
||||
var scope = $rootScope.$new(),
|
||||
element;
|
||||
this.$compile = $compile;
|
||||
|
||||
// Stop showing the overlay; additionally, release the scope
|
||||
// that it uses.
|
||||
function dismiss() {
|
||||
scope.$destroy();
|
||||
element.remove();
|
||||
}
|
||||
|
||||
// If no model is supplied, just fill in a default "cancel"
|
||||
overlayModel = overlayModel || { cancel: dismiss };
|
||||
|
||||
// Populate the scope; will be passed directly to the template
|
||||
scope.overlay = overlayModel;
|
||||
scope.key = key;
|
||||
|
||||
// Create the overlay element and add it to the document's body
|
||||
element = $compile(TEMPLATE)(scope);
|
||||
$document.find('body').prepend(element);
|
||||
|
||||
|
||||
|
||||
return {
|
||||
dismiss: dismiss
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Add a new overlay to the document. This will be
|
||||
* prepended to the document body; the overlay's
|
||||
* template (as pointed to by the `key` argument) is
|
||||
* responsible for having a useful z-order, and for
|
||||
* blocking user interactions if appropriate.
|
||||
*
|
||||
* @param {string} key the symbolic key which identifies
|
||||
* the template of the overlay to be shown
|
||||
* @param {object} overlayModel the model to pass to the
|
||||
* included overlay template (this will be passed
|
||||
* in via ng-model)
|
||||
*/
|
||||
createOverlay: createOverlay
|
||||
// Don't include $document and $rootScope directly;
|
||||
// avoids https://docs.angularjs.org/error/ng/cpws
|
||||
this.findBody = function () {
|
||||
return $document.find('body');
|
||||
};
|
||||
this.newScope = function () {
|
||||
return $rootScope.$new();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new overlay to the document. This will be
|
||||
* prepended to the document body; the overlay's
|
||||
* template (as pointed to by the `key` argument) is
|
||||
* responsible for having a useful z-order, and for
|
||||
* blocking user interactions if appropriate.
|
||||
*
|
||||
* @param {string} key the symbolic key which identifies
|
||||
* the template of the overlay to be shown
|
||||
* @param {object} overlayModel the model to pass to the
|
||||
* included overlay template (this will be passed
|
||||
* in via ng-model)
|
||||
*/
|
||||
OverlayService.prototype.createOverlay = function (key, overlayModel) {
|
||||
// Create a new scope for this overlay
|
||||
var scope = this.newScope(),
|
||||
element;
|
||||
|
||||
// Stop showing the overlay; additionally, release the scope
|
||||
// that it uses.
|
||||
function dismiss() {
|
||||
scope.$destroy();
|
||||
element.remove();
|
||||
}
|
||||
|
||||
// If no model is supplied, just fill in a default "cancel"
|
||||
overlayModel = overlayModel || { cancel: dismiss };
|
||||
|
||||
// Populate the scope; will be passed directly to the template
|
||||
scope.overlay = overlayModel;
|
||||
scope.key = key;
|
||||
|
||||
// Create the overlay element and add it to the document's body
|
||||
element = this.$compile(TEMPLATE)(scope);
|
||||
this.findBody().prepend(element);
|
||||
|
||||
return {
|
||||
dismiss: dismiss
|
||||
};
|
||||
};
|
||||
|
||||
return OverlayService;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -59,7 +59,7 @@
|
||||
"glyph": "Z",
|
||||
"name": "Remove",
|
||||
"description": "Remove this object from its containing object.",
|
||||
"depends": [ "$q" ]
|
||||
"depends": [ "$q", "navigationService" ]
|
||||
},
|
||||
{
|
||||
"key": "save",
|
||||
|
@ -21,10 +21,11 @@
|
||||
-->
|
||||
<span ng-controller="EditActionController">
|
||||
<span ng-repeat="currentAction in editActions">
|
||||
<a class='btn'
|
||||
<a class='s-btn t-{{currentAction.getMetadata().key}}'
|
||||
title='{{currentAction.getMetadata().name}}'
|
||||
ng-click="currentAction.perform()"
|
||||
ng-class="{ major: $index === 0, subtle: $index !== 0 }">
|
||||
{{currentAction.getMetadata().name}}
|
||||
ng-class="{ major: $index === 0 }">
|
||||
<span class="title-label">{{currentAction.getMetadata().name}}</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
@ -30,12 +30,11 @@
|
||||
structure="toolbar.structure"
|
||||
ng-model="toolbar.state">
|
||||
</mct-toolbar>
|
||||
<div class='holder abs object-holder work-area'>
|
||||
<mct-representation key="representation.selected.key"
|
||||
toolbar="toolbar"
|
||||
mct-object="representation.selected.key && domainObject">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
toolbar="toolbar"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="holder abs object-holder work-area">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<mct-splitter></mct-splitter>
|
||||
<div
|
||||
|
@ -29,9 +29,26 @@ define(
|
||||
* The "Cancel" action; the action triggered by clicking Cancel from
|
||||
* Edit Mode. Exits the editing user interface and invokes object
|
||||
* capabilities to persist the changes that have been made.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {Action}
|
||||
*/
|
||||
function CancelAction($location, urlService, context) {
|
||||
var domainObject = context.domainObject;
|
||||
this.domainObject = context.domainObject;
|
||||
this.$location = $location;
|
||||
this.urlService = urlService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel editing.
|
||||
*
|
||||
* @returns {Promise} a promise that will be fulfilled when
|
||||
* cancellation has completed
|
||||
*/
|
||||
CancelAction.prototype.perform = function () {
|
||||
var domainObject = this.domainObject,
|
||||
$location = this.$location,
|
||||
urlService = this.urlService;
|
||||
|
||||
// Look up the object's "editor.completion" capability;
|
||||
// this is introduced by EditableDomainObject which is
|
||||
@ -56,25 +73,15 @@ define(
|
||||
)));
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Cancel editing.
|
||||
*
|
||||
* @returns {Promise} a promise that will be fulfilled when
|
||||
* cancellation has completed
|
||||
*/
|
||||
perform: function () {
|
||||
return doCancel(getEditorCapability())
|
||||
.then(returnToBrowse);
|
||||
}
|
||||
};
|
||||
}
|
||||
return doCancel(getEditorCapability())
|
||||
.then(returnToBrowse);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this action is applicable in a given context.
|
||||
* This will ensure that a domain object is present in the context,
|
||||
* and that this domain object is in Edit mode.
|
||||
* @returns true if applicable
|
||||
* @returns {boolean} true if applicable
|
||||
*/
|
||||
CancelAction.appliesTo = function (context) {
|
||||
var domainObject = (context || {}).domainObject;
|
||||
@ -84,4 +91,4 @@ define(
|
||||
|
||||
return CancelAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -25,8 +25,8 @@
|
||||
* Module defining EditAction. Created by vwoeltje on 11/14/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
['../objects/EditableDomainObject'],
|
||||
function (EditableDomainObject) {
|
||||
"use strict";
|
||||
|
||||
// A no-op action to return in the event that the action cannot
|
||||
@ -42,7 +42,9 @@ define(
|
||||
* mode (typically triggered by the Edit button.) This will
|
||||
* show the user interface for editing (by way of a change in
|
||||
* route)
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
*/
|
||||
function EditAction($location, navigationService, $log, context) {
|
||||
var domainObject = (context || {}).domainObject;
|
||||
@ -60,17 +62,21 @@ define(
|
||||
return NULL_ACTION;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Enter edit mode.
|
||||
*/
|
||||
perform: function () {
|
||||
navigationService.setNavigation(domainObject);
|
||||
$location.path("/edit");
|
||||
}
|
||||
};
|
||||
this.domainObject = domainObject;
|
||||
this.$location = $location;
|
||||
this.navigationService = navigationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter edit mode.
|
||||
*/
|
||||
EditAction.prototype.perform = function () {
|
||||
if (!this.domainObject.getDomainObject) {
|
||||
this.navigationService.setNavigation(new EditableDomainObject(this.domainObject));
|
||||
}
|
||||
//this.$location.path("/edit");
|
||||
};
|
||||
|
||||
/**
|
||||
* Check for applicability; verify that a domain object is present
|
||||
* for this action to be performed upon.
|
||||
@ -79,12 +85,13 @@ define(
|
||||
*/
|
||||
EditAction.appliesTo = function (context) {
|
||||
var domainObject = (context || {}).domainObject,
|
||||
type = domainObject && domainObject.getCapability('type');
|
||||
type = domainObject && domainObject.getCapability('type'),
|
||||
isEditMode = domainObject && domainObject.getDomainObject ? true : false;
|
||||
|
||||
// Only allow creatable types to be edited
|
||||
return type && type.hasFeature('creation');
|
||||
return type && type.hasFeature('creation') && !isEditMode;
|
||||
};
|
||||
|
||||
return EditAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -29,42 +29,36 @@ define(
|
||||
|
||||
/**
|
||||
* Add one domain object to another's composition.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {Action}
|
||||
*/
|
||||
function LinkAction(context) {
|
||||
var domainObject = (context || {}).domainObject,
|
||||
selectedObject = (context || {}).selectedObject,
|
||||
selectedId = selectedObject && selectedObject.getId();
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.selectedObject = (context || {}).selectedObject;
|
||||
}
|
||||
|
||||
// Add this domain object's identifier
|
||||
function addId(model) {
|
||||
if (Array.isArray(model.composition) &&
|
||||
model.composition.indexOf(selectedId) < 0) {
|
||||
model.composition.push(selectedId);
|
||||
}
|
||||
}
|
||||
LinkAction.prototype.perform = function () {
|
||||
var self = this;
|
||||
|
||||
// Persist changes to the domain object
|
||||
function doPersist() {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
var persistence =
|
||||
self.domainObject.getCapability('persistence');
|
||||
return persistence.persist();
|
||||
}
|
||||
|
||||
// Link these objects
|
||||
function doLink() {
|
||||
return domainObject.useCapability("mutation", addId)
|
||||
.then(doPersist);
|
||||
var composition = self.domainObject &&
|
||||
self.domainObject.getCapability('composition');
|
||||
return composition && composition.add(self.selectedObject)
|
||||
.then(doPersist);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Perform this action.
|
||||
*/
|
||||
perform: function () {
|
||||
return selectedId && doLink();
|
||||
}
|
||||
};
|
||||
}
|
||||
return this.selectedObject && doLink();
|
||||
};
|
||||
|
||||
return LinkAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -32,58 +32,58 @@ define(
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Construct an action which will allow an object's metadata to be
|
||||
* edited.
|
||||
* Implements the "Edit Properties" action, which prompts the user
|
||||
* to modify a domain object's properties.
|
||||
*
|
||||
* @param {DialogService} dialogService a service which will show the dialog
|
||||
* @param {DomainObject} object the object to be edited
|
||||
* @param {ActionContext} context the context in which this action is performed
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {Action}
|
||||
* @constructor
|
||||
*/
|
||||
function PropertiesAction(dialogService, context) {
|
||||
var object = context.domainObject;
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.dialogService = dialogService;
|
||||
}
|
||||
|
||||
PropertiesAction.prototype.perform = function () {
|
||||
var type = this.domainObject.getCapability('type'),
|
||||
domainObject = this.domainObject,
|
||||
dialogService = this.dialogService;
|
||||
|
||||
// Persist modifications to this domain object
|
||||
function doPersist() {
|
||||
var persistence = object.getCapability('persistence');
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
return persistence && persistence.persist();
|
||||
}
|
||||
|
||||
// Update the domain object model based on user input
|
||||
function updateModel(userInput, dialog) {
|
||||
return object.useCapability('mutation', function (model) {
|
||||
return domainObject.useCapability('mutation', function (model) {
|
||||
dialog.updateModel(model, userInput);
|
||||
});
|
||||
}
|
||||
|
||||
function showDialog(type) {
|
||||
// Create a dialog object to generate the form structure, etc.
|
||||
var dialog = new PropertiesDialog(type, object.getModel());
|
||||
var dialog =
|
||||
new PropertiesDialog(type, domainObject.getModel());
|
||||
|
||||
// Show the dialog
|
||||
return dialogService.getUserInput(
|
||||
dialog.getFormStructure(),
|
||||
dialog.getInitialFormValue()
|
||||
).then(function (userInput) {
|
||||
// Update the model, if user input was provided
|
||||
return userInput && updateModel(userInput, dialog);
|
||||
}).then(function (result) {
|
||||
return result && doPersist();
|
||||
});
|
||||
// Update the model, if user input was provided
|
||||
return userInput && updateModel(userInput, dialog);
|
||||
}).then(function (result) {
|
||||
return result && doPersist();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Perform this action.
|
||||
* @return {Promise} a promise which will be
|
||||
* fulfilled when the action has completed.
|
||||
*/
|
||||
perform: function () {
|
||||
var type = object.getCapability('type');
|
||||
return type && showDialog(type);
|
||||
}
|
||||
};
|
||||
}
|
||||
return type && showDialog(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter this action for applicability against a given context.
|
||||
@ -106,3 +106,4 @@ define(
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
@ -21,12 +21,6 @@
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Defines the PropertiesDialog, used by the PropertiesAction to
|
||||
* populate the form shown in dialog based on the created type.
|
||||
*
|
||||
* @module common/actions/properties-dialog
|
||||
*/
|
||||
define(
|
||||
function () {
|
||||
'use strict';
|
||||
@ -37,58 +31,63 @@ define(
|
||||
* @param {TypeImpl} type the type of domain object for which properties
|
||||
* will be specified
|
||||
* @param {DomainObject} the object for which properties will be set
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @memberof module:common/actions/properties-dialog
|
||||
*/
|
||||
function PropertiesDialog(type, model) {
|
||||
var properties = type.getProperties();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get sections provided by this dialog.
|
||||
* @return {FormStructure} the structure of this form
|
||||
*/
|
||||
getFormStructure: function () {
|
||||
return {
|
||||
name: "Edit " + model.name,
|
||||
sections: [{
|
||||
name: "Properties",
|
||||
rows: properties.map(function (property, index) {
|
||||
// Property definition is same as form row definition
|
||||
var row = Object.create(property.getDefinition());
|
||||
row.key = index;
|
||||
return row;
|
||||
})
|
||||
}]
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Get the initial state of the form shown by this dialog
|
||||
* (based on the object model)
|
||||
* @returns {object} initial state of the form
|
||||
*/
|
||||
getInitialFormValue: function () {
|
||||
// Start with initial values for properties
|
||||
// Note that index needs to correlate to row.key
|
||||
// from getFormStructure
|
||||
return properties.map(function (property) {
|
||||
return property.getValue(model);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Update a domain object model based on the value of a form.
|
||||
*/
|
||||
updateModel: function (model, formValue) {
|
||||
// Update all properties
|
||||
properties.forEach(function (property, index) {
|
||||
property.setValue(model, formValue[index]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.type = type;
|
||||
this.model = model;
|
||||
this.properties = type.getProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sections provided by this dialog.
|
||||
* @return {FormStructure} the structure of this form
|
||||
*/
|
||||
PropertiesDialog.prototype.getFormStructure = function () {
|
||||
return {
|
||||
name: "Edit " + this.model.name,
|
||||
sections: [{
|
||||
name: "Properties",
|
||||
rows: this.properties.map(function (property, index) {
|
||||
// Property definition is same as form row definition
|
||||
var row = Object.create(property.getDefinition());
|
||||
row.key = index;
|
||||
return row;
|
||||
}).filter(function (row) {
|
||||
// Only show properties which are editable
|
||||
return row.control;
|
||||
})
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial state of the form shown by this dialog
|
||||
* (based on the object model)
|
||||
* @returns {object} initial state of the form
|
||||
*/
|
||||
PropertiesDialog.prototype.getInitialFormValue = function () {
|
||||
var model = this.model;
|
||||
|
||||
// Start with initial values for properties
|
||||
// Note that index needs to correlate to row.key
|
||||
// from getFormStructure
|
||||
return this.properties.map(function (property) {
|
||||
return property.getValue(model);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a domain object model based on the value of a form.
|
||||
*/
|
||||
PropertiesDialog.prototype.updateModel = function (model, formValue) {
|
||||
// Update all properties
|
||||
this.properties.forEach(function (property, index) {
|
||||
property.setValue(model, formValue[index]);
|
||||
});
|
||||
};
|
||||
|
||||
return PropertiesDialog;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -37,22 +37,35 @@ define(
|
||||
*
|
||||
* @param {DomainObject} object the object to be removed
|
||||
* @param {ActionContext} context the context in which this action is performed
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @memberof module:editor/actions/remove-action
|
||||
* @implements {Action}
|
||||
*/
|
||||
function RemoveAction($q, context) {
|
||||
var object = (context || {}).domainObject;
|
||||
function RemoveAction($q, navigationService, context) {
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.$q = $q;
|
||||
this.navigationService = navigationService;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Perform this action.
|
||||
* @return {Promise} a promise which will be
|
||||
* fulfilled when the action has completed.
|
||||
*/
|
||||
RemoveAction.prototype.perform = function () {
|
||||
var $q = this.$q,
|
||||
navigationService = this.navigationService,
|
||||
domainObject = this.domainObject;
|
||||
/*
|
||||
* Check whether an object ID matches the ID of the object being
|
||||
* removed (used to filter a parent's composition to handle the
|
||||
* removal.)
|
||||
*/
|
||||
function isNotObject(otherObjectId) {
|
||||
return otherObjectId !== object.getId();
|
||||
return otherObjectId !== domainObject.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Mutate a parent object such that it no longer contains the object
|
||||
* which is being removed.
|
||||
*/
|
||||
@ -60,7 +73,7 @@ define(
|
||||
model.composition = model.composition.filter(isNotObject);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Invoke persistence on a domain object. This will be called upon
|
||||
* the removed object's parent (as its composition will have changed.)
|
||||
*/
|
||||
@ -69,33 +82,55 @@ define(
|
||||
return persistence && persistence.persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the object from its parent, as identified by its context
|
||||
* capability.
|
||||
* @param {ContextCapability} contextCapability the "context" capability
|
||||
* of the domain object being removed.
|
||||
/*
|
||||
* Checks current object and ascendants of current
|
||||
* object with object being removed, if the current
|
||||
* object or any in the current object's path is being removed,
|
||||
* navigate back to parent of removed object.
|
||||
*/
|
||||
function removeFromContext(contextCapability) {
|
||||
var parent = contextCapability.getParent();
|
||||
$q.when(
|
||||
function checkObjectNavigation(object, parentObject) {
|
||||
// Traverse object starts at current location
|
||||
var traverseObject = (navigationService).getNavigation(),
|
||||
context;
|
||||
|
||||
// Stop when object is not defined (above ROOT)
|
||||
while (traverseObject) {
|
||||
// If object currently traversed to is object being removed
|
||||
// navigate to parent of current object and then exit loop
|
||||
if (traverseObject.getId() === object.getId()) {
|
||||
navigationService.setNavigation(parentObject);
|
||||
return;
|
||||
}
|
||||
// Traverses to parent of current object, moving
|
||||
// up the ascendant path
|
||||
context = traverseObject.getCapability('context');
|
||||
traverseObject = context && context.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the object from its parent, as identified by its context
|
||||
* capability. Based on object's location and selected object's location
|
||||
* user may be navigated to existing parent object
|
||||
*/
|
||||
function removeFromContext(object) {
|
||||
var contextCapability = object.getCapability('context'),
|
||||
parent = contextCapability.getParent();
|
||||
|
||||
// If currently within path of removed object(s),
|
||||
// navigates to existing object up tree
|
||||
checkObjectNavigation(object, parent);
|
||||
|
||||
return $q.when(
|
||||
parent.useCapability('mutation', doMutate)
|
||||
).then(function () {
|
||||
return doPersist(parent);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Perform this action.
|
||||
* @return {module:core/promises.Promise} a promise which will be
|
||||
* fulfilled when the action has completed.
|
||||
*/
|
||||
perform: function () {
|
||||
return $q.when(object.getCapability('context'))
|
||||
.then(removeFromContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
return $q.when(domainObject)
|
||||
.then(removeFromContext);
|
||||
};
|
||||
|
||||
// Object needs to have a parent for Remove to be applicable
|
||||
RemoveAction.appliesTo = function (context) {
|
||||
@ -113,4 +148,4 @@ define(
|
||||
|
||||
return RemoveAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -30,9 +30,27 @@ define(
|
||||
* The "Save" action; the action triggered by clicking Save from
|
||||
* Edit Mode. Exits the editing user interface and invokes object
|
||||
* capabilities to persist the changes that have been made.
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
* @memberof platform/commonUI/edit
|
||||
*/
|
||||
function SaveAction($location, urlService, context) {
|
||||
var domainObject = context.domainObject;
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.$location = $location;
|
||||
this.urlService = urlService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes and conclude editing.
|
||||
*
|
||||
* @returns {Promise} a promise that will be fulfilled when
|
||||
* cancellation has completed
|
||||
* @memberof platform/commonUI/edit.SaveAction#
|
||||
*/
|
||||
SaveAction.prototype.perform = function () {
|
||||
var domainObject = this.domainObject,
|
||||
$location = this.$location,
|
||||
urlService = this.urlService;
|
||||
|
||||
// Invoke any save behavior introduced by the editor capability;
|
||||
// this is introduced by EditableDomainObject which is
|
||||
@ -51,18 +69,8 @@ define(
|
||||
));
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Save changes and conclude editing.
|
||||
*
|
||||
* @returns {Promise} a promise that will be fulfilled when
|
||||
* cancellation has completed
|
||||
*/
|
||||
perform: function () {
|
||||
return doSave().then(returnToBrowse);
|
||||
}
|
||||
};
|
||||
}
|
||||
return doSave().then(returnToBrowse);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this action is applicable in a given context.
|
||||
@ -78,4 +86,4 @@ define(
|
||||
|
||||
return SaveAction;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -35,6 +35,9 @@ define(
|
||||
* Meant specifically for use by EditableDomainObject and the
|
||||
* associated cache; the constructor signature is particular
|
||||
* to a pattern used there and may contain unused arguments.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {CompositionCapability}
|
||||
*/
|
||||
return function EditableCompositionCapability(
|
||||
contextCapability,
|
||||
@ -54,4 +57,4 @@ define(
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -35,6 +35,9 @@ define(
|
||||
* Meant specifically for use by EditableDomainObject and the
|
||||
* associated cache; the constructor signature is particular
|
||||
* to a pattern used there and may contain unused arguments.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {ContextCapability}
|
||||
*/
|
||||
return function EditableContextCapability(
|
||||
contextCapability,
|
||||
@ -72,4 +75,4 @@ define(
|
||||
return capability;
|
||||
};
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -35,6 +35,8 @@ define(
|
||||
* Meant specifically for use by EditableDomainObject and the
|
||||
* associated cache; the constructor signature is particular
|
||||
* to a pattern used there and may contain unused arguments.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
*/
|
||||
return function EditableLookupCapability(
|
||||
contextCapability,
|
||||
@ -76,7 +78,7 @@ define(
|
||||
// Wrap a returned value (see above); if it's a promise, wrap
|
||||
// the resolved value.
|
||||
function wrapResult(result) {
|
||||
return result.then ? // promise-like
|
||||
return (result && result.then) ? // promise-like
|
||||
result.then(makeEditable) :
|
||||
makeEditable(result);
|
||||
}
|
||||
@ -105,8 +107,10 @@ define(
|
||||
|
||||
// Wrap a method of this capability
|
||||
function wrapMethod(fn) {
|
||||
capability[fn] =
|
||||
(idempotent ? oneTimeFunction : wrapFunction)(fn);
|
||||
if (typeof capability[fn] === 'function') {
|
||||
capability[fn] =
|
||||
(idempotent ? oneTimeFunction : wrapFunction)(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap all methods; return only editable domain objects.
|
||||
@ -115,4 +119,4 @@ define(
|
||||
return capability;
|
||||
};
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -35,6 +35,9 @@ define(
|
||||
* Meant specifically for use by EditableDomainObject and the
|
||||
* associated cache; the constructor signature is particular
|
||||
* to a pattern used there and may contain unused arguments.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {PersistenceCapability}
|
||||
*/
|
||||
function EditablePersistenceCapability(
|
||||
persistenceCapability,
|
||||
@ -62,4 +65,4 @@ define(
|
||||
|
||||
return EditablePersistenceCapability;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -35,6 +35,9 @@ define(
|
||||
* Meant specifically for use by EditableDomainObject and the
|
||||
* associated cache; the constructor signature is particular
|
||||
* to a pattern used there and may contain unused arguments.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {RelationshipCapability}
|
||||
*/
|
||||
return function EditableRelationshipCapability(
|
||||
relationshipCapability,
|
||||
@ -54,4 +57,4 @@ define(
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -39,27 +39,48 @@ define(
|
||||
* Meant specifically for use by EditableDomainObject and the
|
||||
* associated cache; the constructor signature is particular
|
||||
* to a pattern used there and may contain unused arguments.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
*/
|
||||
return function EditorCapability(
|
||||
function EditorCapability(
|
||||
persistenceCapability,
|
||||
editableObject,
|
||||
domainObject,
|
||||
cache
|
||||
) {
|
||||
this.editableObject = editableObject;
|
||||
this.domainObject = domainObject;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
// Simulate Promise.resolve (or $q.when); the former
|
||||
// causes a delayed reaction from Angular (since it
|
||||
// does not trigger a digest) and the latter is not
|
||||
// readily accessible, since we're a few classes
|
||||
// removed from the layer which gets dependency
|
||||
// injection.
|
||||
function resolvePromise(value) {
|
||||
return (value && value.then) ? value : {
|
||||
then: function (callback) {
|
||||
return resolvePromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
// Simulate Promise.resolve (or $q.when); the former
|
||||
// causes a delayed reaction from Angular (since it
|
||||
// does not trigger a digest) and the latter is not
|
||||
// readily accessible, since we're a few classes
|
||||
// removed from the layer which gets dependency
|
||||
// injection.
|
||||
function resolvePromise(value) {
|
||||
return (value && value.then) ? value : {
|
||||
then: function (callback) {
|
||||
return resolvePromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any changes that have been made to this domain object
|
||||
* (as well as to others that might have been retrieved and
|
||||
* modified during the editing session)
|
||||
* @param {boolean} nonrecursive if true, save only this
|
||||
* object (and not other objects with associated changes)
|
||||
* @returns {Promise} a promise that will be fulfilled after
|
||||
* persistence has completed.
|
||||
* @memberof platform/commonUI/edit.EditorCapability#
|
||||
*/
|
||||
EditorCapability.prototype.save = function (nonrecursive) {
|
||||
var domainObject = this.domainObject,
|
||||
editableObject = this.editableObject,
|
||||
cache = this.cache;
|
||||
|
||||
// Update the underlying, "real" domain object's model
|
||||
// with changes made to the copy used for editing.
|
||||
@ -74,39 +95,32 @@ define(
|
||||
return domainObject.getCapability('persistence').persist();
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Save any changes that have been made to this domain object
|
||||
* (as well as to others that might have been retrieved and
|
||||
* modified during the editing session)
|
||||
* @param {boolean} nonrecursive if true, save only this
|
||||
* object (and not other objects with associated changes)
|
||||
* @returns {Promise} a promise that will be fulfilled after
|
||||
* persistence has completed.
|
||||
*/
|
||||
save: function (nonrecursive) {
|
||||
return nonrecursive ?
|
||||
resolvePromise(doMutate()).then(doPersist) :
|
||||
resolvePromise(cache.saveAll());
|
||||
},
|
||||
/**
|
||||
* Cancel editing; Discard any changes that have been made to
|
||||
* this domain object (as well as to others that might have
|
||||
* been retrieved and modified during the editing session)
|
||||
* @returns {Promise} a promise that will be fulfilled after
|
||||
* cancellation has completed.
|
||||
*/
|
||||
cancel: function () {
|
||||
return resolvePromise(undefined);
|
||||
},
|
||||
/**
|
||||
* Check if there are any unsaved changes.
|
||||
* @returns {boolean} true if there are unsaved changes
|
||||
*/
|
||||
dirty: function () {
|
||||
return cache.dirty();
|
||||
}
|
||||
};
|
||||
return nonrecursive ?
|
||||
resolvePromise(doMutate()).then(doPersist) :
|
||||
resolvePromise(cache.saveAll());
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel editing; Discard any changes that have been made to
|
||||
* this domain object (as well as to others that might have
|
||||
* been retrieved and modified during the editing session)
|
||||
* @returns {Promise} a promise that will be fulfilled after
|
||||
* cancellation has completed.
|
||||
* @memberof platform/commonUI/edit.EditorCapability#
|
||||
*/
|
||||
EditorCapability.prototype.cancel = function () {
|
||||
return resolvePromise(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if there are any unsaved changes.
|
||||
* @returns {boolean} true if there are unsaved changes
|
||||
* @memberof platform/commonUI/edit.EditorCapability#
|
||||
*/
|
||||
EditorCapability.prototype.dirty = function () {
|
||||
return this.cache.dirty();
|
||||
};
|
||||
|
||||
return EditorCapability;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -33,6 +33,7 @@ define(
|
||||
|
||||
/**
|
||||
* Controller which supplies action instances for Save/Cancel.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditActionController($scope) {
|
||||
@ -51,4 +52,4 @@ define(
|
||||
|
||||
return EditActionController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -22,7 +22,8 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining EditController. Created by vwoeltje on 11/14/14.
|
||||
* This bundle implements Edit mode.
|
||||
* @namespace platform/commonUI/edit
|
||||
*/
|
||||
define(
|
||||
["../objects/EditableDomainObject"],
|
||||
@ -33,15 +34,16 @@ define(
|
||||
* Controller which is responsible for populating the scope for
|
||||
* Edit mode; introduces an editable version of the currently
|
||||
* navigated domain object into the scope.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditController($scope, $q, navigationService) {
|
||||
var navigatedObject;
|
||||
var self = this;
|
||||
|
||||
function setNavigation(domainObject) {
|
||||
// Wrap the domain object such that all mutation is
|
||||
// confined to edit mode (until Save)
|
||||
navigatedObject =
|
||||
self.navigatedDomainObject =
|
||||
domainObject && new EditableDomainObject(domainObject, $q);
|
||||
}
|
||||
|
||||
@ -50,33 +52,33 @@ define(
|
||||
$scope.$on("$destroy", function () {
|
||||
navigationService.removeListener(setNavigation);
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the domain object which is navigated-to.
|
||||
* @returns {DomainObject} the domain object that is navigated-to
|
||||
*/
|
||||
navigatedObject: function () {
|
||||
return navigatedObject;
|
||||
},
|
||||
/**
|
||||
* Get the warning to show if the user attempts to navigate
|
||||
* away from Edit mode while unsaved changes are present.
|
||||
* @returns {string} the warning to show, or undefined if
|
||||
* there are no unsaved changes
|
||||
*/
|
||||
getUnloadWarning: function () {
|
||||
var editorCapability = navigatedObject &&
|
||||
navigatedObject.getCapability("editor"),
|
||||
hasChanges = editorCapability && editorCapability.dirty();
|
||||
|
||||
return hasChanges ?
|
||||
"Unsaved changes will be lost if you leave this page." :
|
||||
undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain object which is navigated-to.
|
||||
* @returns {DomainObject} the domain object that is navigated-to
|
||||
*/
|
||||
EditController.prototype.navigatedObject = function () {
|
||||
return this.navigatedDomainObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the warning to show if the user attempts to navigate
|
||||
* away from Edit mode while unsaved changes are present.
|
||||
* @returns {string} the warning to show, or undefined if
|
||||
* there are no unsaved changes
|
||||
*/
|
||||
EditController.prototype.getUnloadWarning = function () {
|
||||
var navigatedObject = this.navigatedDomainObject,
|
||||
editorCapability = navigatedObject &&
|
||||
navigatedObject.getCapability("editor"),
|
||||
hasChanges = editorCapability && editorCapability.dirty();
|
||||
|
||||
return hasChanges ?
|
||||
"Unsaved changes will be lost if you leave this page." :
|
||||
undefined;
|
||||
};
|
||||
|
||||
return EditController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -28,15 +28,17 @@ define(
|
||||
|
||||
/**
|
||||
* Supports the Library and Elements panes in Edit mode.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditPanesController($scope) {
|
||||
var root;
|
||||
var self = this;
|
||||
|
||||
// Update root object based on represented object
|
||||
function updateRoot(domainObject) {
|
||||
var context = domainObject &&
|
||||
domainObject.getCapability('context'),
|
||||
var root = self.rootDomainObject,
|
||||
context = domainObject &&
|
||||
domainObject.getCapability('context'),
|
||||
newRoot = context && context.getTrueRoot(),
|
||||
oldId = root && root.getId(),
|
||||
newId = newRoot && newRoot.getId();
|
||||
@ -44,25 +46,22 @@ define(
|
||||
// Only update if this has actually changed,
|
||||
// to avoid excessive refreshing.
|
||||
if (oldId !== newId) {
|
||||
root = newRoot;
|
||||
self.rootDomainObject = newRoot;
|
||||
}
|
||||
}
|
||||
|
||||
// Update root when represented object changes
|
||||
$scope.$watch('domainObject', updateRoot);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the root-level domain object, as reported by the
|
||||
* represented domain object.
|
||||
* @returns {DomainObject} the root object
|
||||
*/
|
||||
getRoot: function () {
|
||||
return root;
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the root-level domain object, as reported by the
|
||||
* represented domain object.
|
||||
* @returns {DomainObject} the root object
|
||||
*/
|
||||
EditPanesController.prototype.getRoot = function () {
|
||||
return this.rootDomainObject;
|
||||
};
|
||||
|
||||
return EditPanesController;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -31,6 +31,7 @@ define(
|
||||
* to this attribute will be evaluated during page navigation events
|
||||
* and, if it returns a truthy value, will be used to populate a
|
||||
* prompt to the user to confirm this navigation.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @param $window the window
|
||||
*/
|
||||
@ -102,4 +103,4 @@ define(
|
||||
return MCTBeforeUnload;
|
||||
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -68,6 +68,9 @@ define(
|
||||
* which need to behave differently in edit mode,
|
||||
* and provides a "working copy" of the object's
|
||||
* model to allow changes to be easily cancelled.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {DomainObject}
|
||||
*/
|
||||
function EditableDomainObject(domainObject, $q) {
|
||||
// The cache will hold all domain objects reached from
|
||||
@ -92,12 +95,16 @@ define(
|
||||
this,
|
||||
delegateArguments
|
||||
),
|
||||
factory = capabilityFactories[name];
|
||||
Factory = capabilityFactories[name];
|
||||
|
||||
return (factory && capability) ?
|
||||
factory(capability, editableObject, domainObject, cache) :
|
||||
return (Factory && capability) ?
|
||||
new Factory(capability, editableObject, domainObject, cache) :
|
||||
capability;
|
||||
};
|
||||
|
||||
editableObject.getDomainObject = function() {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
return editableObject;
|
||||
}
|
||||
@ -109,4 +116,4 @@ define(
|
||||
|
||||
return EditableDomainObject;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -22,7 +22,7 @@
|
||||
/*global define*/
|
||||
|
||||
|
||||
/**
|
||||
/*
|
||||
* An editable domain object cache stores domain objects that have been
|
||||
* made editable, in a group that can be saved all-at-once. This supports
|
||||
* Edit mode, which is launched for a specific object but may contain
|
||||
@ -32,8 +32,6 @@
|
||||
* to ensure that changes made while in edit mode do not propagate up
|
||||
* to the objects used in browse mode (or to persistence) until the user
|
||||
* initiates a Save.
|
||||
*
|
||||
* @module editor/object/editable-domain-object-cache
|
||||
*/
|
||||
define(
|
||||
["./EditableModelCache"],
|
||||
@ -46,107 +44,118 @@ define(
|
||||
* of objects retrieved via composition or context capabilities as
|
||||
* editable domain objects.
|
||||
*
|
||||
* @param {Constructor<EditableDomainObject>} EditableDomainObject a
|
||||
* @param {Constructor<DomainObject>} EditableDomainObject a
|
||||
* constructor function which takes a regular domain object as
|
||||
* an argument, and returns an editable domain object as its
|
||||
* result.
|
||||
* @param $q Angular's $q, for promise handling
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @memberof module:editor/object/editable-domain-object-cache
|
||||
*/
|
||||
function EditableDomainObjectCache(EditableDomainObject, $q) {
|
||||
var cache = new EditableModelCache(),
|
||||
dirty = {},
|
||||
root;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Wrap this domain object in an editable form, or pull such
|
||||
* an object from the cache if one already exists.
|
||||
*
|
||||
* @param {DomainObject} domainObject the regular domain object
|
||||
* @returns {DomainObject} the domain object in an editable form
|
||||
*/
|
||||
getEditableObject: function (domainObject) {
|
||||
var type = domainObject.getCapability('type');
|
||||
|
||||
// Track the top-level domain object; this will have
|
||||
// some special behavior for its context capability.
|
||||
root = root || domainObject;
|
||||
|
||||
// Avoid double-wrapping (WTD-1017)
|
||||
if (domainObject.hasCapability('editor')) {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
// Don't bother wrapping non-editable objects
|
||||
if (!type || !type.hasFeature('creation')) {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
// Provide an editable form of the object
|
||||
return new EditableDomainObject(
|
||||
domainObject,
|
||||
cache.getCachedModel(domainObject)
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Check if a domain object is (effectively) the top-level
|
||||
* object in this editable subgraph.
|
||||
* @returns {boolean} true if it is the root
|
||||
*/
|
||||
isRoot: function (domainObject) {
|
||||
return domainObject === root;
|
||||
},
|
||||
/**
|
||||
* Mark an editable domain object (presumably already cached)
|
||||
* as having received modifications during editing; it should be
|
||||
* included in the bulk save invoked when editing completes.
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
*/
|
||||
markDirty: function (domainObject) {
|
||||
dirty[domainObject.getId()] = domainObject;
|
||||
},
|
||||
/**
|
||||
* Mark an object (presumably already cached) as having had its
|
||||
* changes saved (and thus no longer needing to be subject to a
|
||||
* save operation.)
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
*/
|
||||
markClean: function (domainObject) {
|
||||
delete dirty[domainObject.getId()];
|
||||
},
|
||||
/**
|
||||
* Initiate a save on all objects that have been cached.
|
||||
*/
|
||||
saveAll: function () {
|
||||
// Get a list of all dirty objects
|
||||
var objects = Object.keys(dirty).map(function (k) {
|
||||
return dirty[k];
|
||||
});
|
||||
|
||||
// Clear dirty set, since we're about to save.
|
||||
dirty = {};
|
||||
|
||||
// Most save logic is handled by the "editor.completion"
|
||||
// capability, so that is delegated here.
|
||||
return $q.all(objects.map(function (object) {
|
||||
// Save; pass a nonrecursive flag to avoid looping
|
||||
return object.getCapability('editor').save(true);
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Check if any objects have been marked dirty in this cache.
|
||||
* @returns {boolean} true if objects are dirty
|
||||
*/
|
||||
dirty: function () {
|
||||
return Object.keys(dirty).length > 0;
|
||||
}
|
||||
};
|
||||
this.cache = new EditableModelCache();
|
||||
this.dirtyObjects = {};
|
||||
this.root = undefined;
|
||||
this.$q = $q;
|
||||
this.EditableDomainObject = EditableDomainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap this domain object in an editable form, or pull such
|
||||
* an object from the cache if one already exists.
|
||||
*
|
||||
* @param {DomainObject} domainObject the regular domain object
|
||||
* @returns {DomainObject} the domain object in an editable form
|
||||
*/
|
||||
EditableDomainObjectCache.prototype.getEditableObject = function (domainObject) {
|
||||
var type = domainObject.getCapability('type'),
|
||||
EditableDomainObject = this.EditableDomainObject;
|
||||
|
||||
// Track the top-level domain object; this will have
|
||||
// some special behavior for its context capability.
|
||||
this.root = this.root || domainObject;
|
||||
|
||||
// Avoid double-wrapping (WTD-1017)
|
||||
if (domainObject.hasCapability('editor')) {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
// Don't bother wrapping non-editable objects
|
||||
if (!type || !type.hasFeature('creation')) {
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
// Provide an editable form of the object
|
||||
return new EditableDomainObject(
|
||||
domainObject,
|
||||
this.cache.getCachedModel(domainObject)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a domain object is (effectively) the top-level
|
||||
* object in this editable subgraph.
|
||||
* @returns {boolean} true if it is the root
|
||||
*/
|
||||
EditableDomainObjectCache.prototype.isRoot = function (domainObject) {
|
||||
return domainObject === this.root;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark an editable domain object (presumably already cached)
|
||||
* as having received modifications during editing; it should be
|
||||
* included in the bulk save invoked when editing completes.
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
* @memberof platform/commonUI/edit.EditableDomainObjectCache#
|
||||
*/
|
||||
EditableDomainObjectCache.prototype.markDirty = function (domainObject) {
|
||||
this.dirtyObjects[domainObject.getId()] = domainObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark an object (presumably already cached) as having had its
|
||||
* changes saved (and thus no longer needing to be subject to a
|
||||
* save operation.)
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
*/
|
||||
EditableDomainObjectCache.prototype.markClean = function (domainObject) {
|
||||
delete this.dirtyObjects[domainObject.getId()];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiate a save on all objects that have been cached.
|
||||
* @return {Promise} A promise which will resolve when all objects are
|
||||
* persisted.
|
||||
*/
|
||||
EditableDomainObjectCache.prototype.saveAll = function () {
|
||||
// Get a list of all dirty objects
|
||||
var dirty = this.dirtyObjects,
|
||||
objects = Object.keys(dirty).map(function (k) {
|
||||
return dirty[k];
|
||||
});
|
||||
|
||||
// Clear dirty set, since we're about to save.
|
||||
this.dirtyObjects = {};
|
||||
|
||||
// Most save logic is handled by the "editor.completion"
|
||||
// capability, so that is delegated here.
|
||||
return this.$q.all(objects.map(function (object) {
|
||||
// Save; pass a nonrecursive flag to avoid looping
|
||||
return object.getCapability('editor').save(true);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if any objects have been marked dirty in this cache.
|
||||
* @returns {boolean} true if objects are dirty
|
||||
*/
|
||||
EditableDomainObjectCache.prototype.dirty = function () {
|
||||
return Object.keys(this.dirtyObjects).length > 0;
|
||||
};
|
||||
|
||||
return EditableDomainObjectCache;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -31,33 +31,32 @@ define(
|
||||
* made editable, to support a group that can be saved all-at-once.
|
||||
* This is useful in Edit mode, which is launched for a specific
|
||||
* object but may contain changes across many objects.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditableModelCache() {
|
||||
var cache = {};
|
||||
|
||||
// Deep-copy a model. Models are JSONifiable, so this can be
|
||||
// done by stringification then destringification
|
||||
function clone(model) {
|
||||
return JSON.parse(JSON.stringify(model));
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get this domain object's model from the cache (or
|
||||
* place it in the cache if it isn't in the cache yet)
|
||||
* @returns a clone of the domain object's model
|
||||
*/
|
||||
getCachedModel: function (domainObject) {
|
||||
var id = domainObject.getId();
|
||||
|
||||
return (cache[id] =
|
||||
cache[id] || clone(domainObject.getModel()));
|
||||
}
|
||||
};
|
||||
|
||||
this.cache = {};
|
||||
}
|
||||
|
||||
// Deep-copy a model. Models are JSONifiable, so this can be
|
||||
// done by stringification then destringification
|
||||
function clone(model) {
|
||||
return JSON.parse(JSON.stringify(model));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this domain object's model from the cache (or
|
||||
* place it in the cache if it isn't in the cache yet)
|
||||
* @returns a clone of the domain object's model
|
||||
*/
|
||||
EditableModelCache.prototype.getCachedModel = function (domainObject) {
|
||||
var id = domainObject.getId(),
|
||||
cache = this.cache;
|
||||
|
||||
return (cache[id] =
|
||||
cache[id] || clone(domainObject.getModel()));
|
||||
};
|
||||
|
||||
return EditableModelCache;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -30,53 +30,47 @@ define(
|
||||
* Policy controlling when the `edit` and/or `properties` actions
|
||||
* can appear as applicable actions of the `view-control` category
|
||||
* (shown as buttons in the top-right of browse mode.)
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<Action, ActionContext>}
|
||||
*/
|
||||
function EditActionPolicy() {
|
||||
// Get a count of views which are not flagged as non-editable.
|
||||
function countEditableViews(context) {
|
||||
var domainObject = (context || {}).domainObject,
|
||||
views = domainObject && domainObject.useCapability('view'),
|
||||
count = 0;
|
||||
}
|
||||
|
||||
// A view is editable unless explicitly flagged as not
|
||||
(views || []).forEach(function (view) {
|
||||
count += (view.editable !== false) ? 1 : 0;
|
||||
});
|
||||
// Get a count of views which are not flagged as non-editable.
|
||||
function countEditableViews(context) {
|
||||
var domainObject = (context || {}).domainObject,
|
||||
views = domainObject && domainObject.useCapability('view'),
|
||||
count = 0;
|
||||
|
||||
return count;
|
||||
// A view is editable unless explicitly flagged as not
|
||||
(views || []).forEach(function (view) {
|
||||
count += (view.editable !== false) ? 1 : 0;
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
EditActionPolicy.prototype.allow = function (action, context) {
|
||||
var key = action.getMetadata().key,
|
||||
category = (context || {}).category;
|
||||
|
||||
// Only worry about actions in the view-control category
|
||||
if (category === 'view-control') {
|
||||
// Restrict 'edit' to cases where there are editable
|
||||
// views (similarly, restrict 'properties' to when
|
||||
// the converse is true)
|
||||
if (key === 'edit') {
|
||||
return countEditableViews(context) > 0;
|
||||
} else if (key === 'properties') {
|
||||
return countEditableViews(context) < 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Check whether or not a given action is allowed by this
|
||||
* policy.
|
||||
* @param {Action} action the action
|
||||
* @param context the context
|
||||
* @returns {boolean} true if not disallowed
|
||||
*/
|
||||
allow: function (action, context) {
|
||||
var key = action.getMetadata().key,
|
||||
category = (context || {}).category;
|
||||
|
||||
// Only worry about actions in the view-control category
|
||||
if (category === 'view-control') {
|
||||
// Restrict 'edit' to cases where there are editable
|
||||
// views (similarly, restrict 'properties' to when
|
||||
// the converse is true)
|
||||
if (key === 'edit') {
|
||||
return countEditableViews(context) > 0;
|
||||
} else if (key === 'properties') {
|
||||
return countEditableViews(context) < 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditActionPolicy;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -28,30 +28,24 @@ define(
|
||||
|
||||
/**
|
||||
* Policy controlling which views should be visible in Edit mode.
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Policy.<View, DomainObject>}
|
||||
*/
|
||||
function EditableViewPolicy() {
|
||||
return {
|
||||
/**
|
||||
* Check whether or not a given action is allowed by this
|
||||
* policy.
|
||||
* @param {Action} action the action
|
||||
* @param domainObject the domain object which will be viewed
|
||||
* @returns {boolean} true if not disallowed
|
||||
*/
|
||||
allow: function (view, domainObject) {
|
||||
// If a view is flagged as non-editable, only allow it
|
||||
// while we're not in Edit mode.
|
||||
if ((view || {}).editable === false) {
|
||||
return !domainObject.hasCapability('editor');
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
EditableViewPolicy.prototype.allow = function (view, domainObject) {
|
||||
// If a view is flagged as non-editable, only allow it
|
||||
// while we're not in Edit mode.
|
||||
if ((view || {}).editable === false) {
|
||||
return !domainObject.hasCapability('editor');
|
||||
}
|
||||
|
||||
// Like all policies, allow by default.
|
||||
return true;
|
||||
};
|
||||
|
||||
return EditableViewPolicy;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -41,14 +41,17 @@ define(
|
||||
* and may be reused for different domain objects and/or
|
||||
* representations resulting from changes there.
|
||||
*
|
||||
* @memberof platform/commonUI/edit
|
||||
* @implements {Representer}
|
||||
* @constructor
|
||||
*/
|
||||
function EditRepresenter($q, $log, scope) {
|
||||
var domainObject,
|
||||
key;
|
||||
var self = this;
|
||||
|
||||
// Mutate and persist a new version of a domain object's model.
|
||||
function doPersist(model) {
|
||||
var domainObject = self.domainObject;
|
||||
|
||||
// First, mutate; then, persist.
|
||||
return $q.when(domainObject.useCapability("mutation", function () {
|
||||
return model;
|
||||
@ -64,7 +67,8 @@ define(
|
||||
// Look up from scope; these will have been populated by
|
||||
// mct-representation.
|
||||
var model = scope.model,
|
||||
configuration = scope.configuration;
|
||||
configuration = scope.configuration,
|
||||
domainObject = self.domainObject;
|
||||
|
||||
// Log the commit message
|
||||
$log.debug([
|
||||
@ -78,50 +82,33 @@ define(
|
||||
if (domainObject && domainObject.hasCapability("persistence")) {
|
||||
// Configurations for specific views are stored by
|
||||
// key in the "configuration" field of the model.
|
||||
if (key && configuration) {
|
||||
if (self.key && configuration) {
|
||||
model.configuration = model.configuration || {};
|
||||
model.configuration[key] = configuration;
|
||||
model.configuration[self.key] = configuration;
|
||||
}
|
||||
doPersist(model);
|
||||
}
|
||||
}
|
||||
|
||||
// Respond to the destruction of the current representation.
|
||||
function destroy() {
|
||||
// Nothing to clean up
|
||||
}
|
||||
|
||||
// Handle a specific representation of a specific domain object
|
||||
function represent(representation, representedObject) {
|
||||
// Track the key, to know which view configuration to save to.
|
||||
key = (representation || {}).key;
|
||||
// Track the represented object
|
||||
domainObject = representedObject;
|
||||
// Ensure existing watches are released
|
||||
destroy();
|
||||
}
|
||||
|
||||
// Place the "commit" method in the scope
|
||||
scope.commit = commit;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Set the current representation in use, and the domain
|
||||
* object being represented.
|
||||
*
|
||||
* @param {RepresentationDefinition} representation the
|
||||
* definition of the representation in use
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
* being represented
|
||||
*/
|
||||
represent: represent,
|
||||
/**
|
||||
* Release any resources associated with this representer.
|
||||
*/
|
||||
destroy: destroy
|
||||
};
|
||||
}
|
||||
|
||||
// Handle a specific representation of a specific domain object
|
||||
EditRepresenter.prototype.represent = function represent(representation, representedObject) {
|
||||
// Track the key, to know which view configuration to save to.
|
||||
this.key = (representation || {}).key;
|
||||
// Track the represented object
|
||||
this.domainObject = representedObject;
|
||||
// Ensure existing watches are released
|
||||
this.destroy();
|
||||
};
|
||||
|
||||
// Respond to the destruction of the current representation.
|
||||
EditRepresenter.prototype.destroy = function destroy() {
|
||||
// Nothing to clean up
|
||||
};
|
||||
|
||||
return EditRepresenter;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -38,125 +38,23 @@ define(
|
||||
*
|
||||
* @param structure toolbar structure, as provided by view definition
|
||||
* @param {Function} commit callback to invoke after changes
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbar(structure, commit) {
|
||||
var toolbarStructure = Object.create(structure || {}),
|
||||
toolbarState,
|
||||
selection,
|
||||
properties = [];
|
||||
var self = this;
|
||||
|
||||
// Generate a new key for an item's property
|
||||
function addKey(property) {
|
||||
properties.push(property);
|
||||
return properties.length - 1; // Return index of property
|
||||
}
|
||||
|
||||
// Update value for this property in all elements of the
|
||||
// selection which have this property.
|
||||
function updateProperties(property, value) {
|
||||
var changed = false;
|
||||
|
||||
// Update property in a selected element
|
||||
function updateProperty(selected) {
|
||||
// Ignore selected elements which don't have this property
|
||||
if (selected[property] !== undefined) {
|
||||
// Check if this is a setter, or just assignable
|
||||
if (typeof selected[property] === 'function') {
|
||||
changed =
|
||||
changed || (selected[property]() !== value);
|
||||
selected[property](value);
|
||||
} else {
|
||||
changed =
|
||||
changed || (selected[property] !== value);
|
||||
selected[property] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update property in all selected elements
|
||||
selection.forEach(updateProperty);
|
||||
|
||||
// Return whether or not anything changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Look up the current value associated with a property
|
||||
// in selection i
|
||||
function lookupState(property, selected) {
|
||||
var value = selected[property];
|
||||
return (typeof value === 'function') ? value() : value;
|
||||
}
|
||||
|
||||
// Get initial value for a given property
|
||||
function initializeState(property) {
|
||||
var result;
|
||||
// Look through all selections for this property;
|
||||
// values should all match by the time we perform
|
||||
// this lookup anyway.
|
||||
selection.forEach(function (selected) {
|
||||
result = (selected[property] !== undefined) ?
|
||||
lookupState(property, selected) :
|
||||
result;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if all elements of the selection which have this
|
||||
// property have the same value for this property.
|
||||
function isConsistent(property) {
|
||||
var consistent = true,
|
||||
observed = false,
|
||||
state;
|
||||
|
||||
// Check if a given element of the selection is consistent
|
||||
// with previously-observed elements for this property.
|
||||
function checkConsistency(selected) {
|
||||
var next;
|
||||
// Ignore selections which don't have this property
|
||||
if (selected[property] !== undefined) {
|
||||
// Look up state of this element in the selection
|
||||
next = lookupState(property, selected);
|
||||
// Detect inconsistency
|
||||
if (observed) {
|
||||
consistent = consistent && (next === state);
|
||||
}
|
||||
// Track state for next iteration
|
||||
state = next;
|
||||
observed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through selections
|
||||
selection.forEach(checkConsistency);
|
||||
|
||||
return consistent;
|
||||
}
|
||||
|
||||
// Used to filter out items which are applicable (or not)
|
||||
// to the current selection.
|
||||
function isApplicable(item) {
|
||||
var property = (item || {}).property,
|
||||
method = (item || {}).method,
|
||||
exclusive = !!(item || {}).exclusive;
|
||||
|
||||
// Check if a selected item defines this property
|
||||
function hasProperty(selected) {
|
||||
return (property && (selected[property] !== undefined)) ||
|
||||
(method && (typeof selected[method] === 'function'));
|
||||
}
|
||||
|
||||
return selection.map(hasProperty).reduce(
|
||||
exclusive ? and : or,
|
||||
exclusive
|
||||
) && isConsistent(property);
|
||||
self.properties.push(property);
|
||||
return self.properties.length - 1; // Return index of property
|
||||
}
|
||||
|
||||
// Invoke all functions in selections with the given name
|
||||
function invoke(method, value) {
|
||||
if (method) {
|
||||
// Make the change in the selection
|
||||
selection.forEach(function (selected) {
|
||||
self.selection.forEach(function (selected) {
|
||||
if (typeof selected[method] === 'function') {
|
||||
selected[method](value);
|
||||
}
|
||||
@ -189,73 +87,172 @@ define(
|
||||
return converted;
|
||||
}
|
||||
|
||||
this.toolbarState = [];
|
||||
this.selection = undefined;
|
||||
this.properties = [];
|
||||
this.toolbarStructure = Object.create(structure || {});
|
||||
this.toolbarStructure.sections =
|
||||
((structure || {}).sections || []).map(convertSection);
|
||||
}
|
||||
|
||||
// Check if all elements of the selection which have this
|
||||
// property have the same value for this property.
|
||||
EditToolbar.prototype.isConsistent = function (property) {
|
||||
var self = this,
|
||||
consistent = true,
|
||||
observed = false,
|
||||
state;
|
||||
|
||||
// Check if a given element of the selection is consistent
|
||||
// with previously-observed elements for this property.
|
||||
function checkConsistency(selected) {
|
||||
var next;
|
||||
// Ignore selections which don't have this property
|
||||
if (selected[property] !== undefined) {
|
||||
// Look up state of this element in the selection
|
||||
next = self.lookupState(property, selected);
|
||||
// Detect inconsistency
|
||||
if (observed) {
|
||||
consistent = consistent && (next === state);
|
||||
}
|
||||
// Track state for next iteration
|
||||
state = next;
|
||||
observed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through selections
|
||||
self.selection.forEach(checkConsistency);
|
||||
|
||||
return consistent;
|
||||
};
|
||||
|
||||
// Used to filter out items which are applicable (or not)
|
||||
// to the current selection.
|
||||
EditToolbar.prototype.isApplicable = function (item) {
|
||||
var property = (item || {}).property,
|
||||
method = (item || {}).method,
|
||||
exclusive = !!(item || {}).exclusive;
|
||||
|
||||
// Check if a selected item defines this property
|
||||
function hasProperty(selected) {
|
||||
return (property && (selected[property] !== undefined)) ||
|
||||
(method && (typeof selected[method] === 'function'));
|
||||
}
|
||||
|
||||
return this.selection.map(hasProperty).reduce(
|
||||
exclusive ? and : or,
|
||||
exclusive
|
||||
) && this.isConsistent(property);
|
||||
};
|
||||
|
||||
|
||||
// Look up the current value associated with a property
|
||||
EditToolbar.prototype.lookupState = function (property, selected) {
|
||||
var value = selected[property];
|
||||
return (typeof value === 'function') ? value() : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current selection. Visibility of sections
|
||||
* and items in the toolbar will be updated to match this.
|
||||
* @param {Array} s the new selection
|
||||
*/
|
||||
EditToolbar.prototype.setSelection = function (s) {
|
||||
var self = this;
|
||||
|
||||
// Show/hide controls in this section per applicability
|
||||
function refreshSectionApplicability(section) {
|
||||
var count = 0;
|
||||
// Show/hide each item
|
||||
(section.items || []).forEach(function (item) {
|
||||
item.hidden = !isApplicable(item);
|
||||
item.hidden = !self.isApplicable(item);
|
||||
count += item.hidden ? 0 : 1;
|
||||
});
|
||||
// Hide this section if there are no applicable items
|
||||
section.hidden = !count;
|
||||
}
|
||||
|
||||
// Show/hide controls if they are applicable
|
||||
function refreshApplicability() {
|
||||
toolbarStructure.sections.forEach(refreshSectionApplicability);
|
||||
// Get initial value for a given property
|
||||
function initializeState(property) {
|
||||
var result;
|
||||
// Look through all selections for this property;
|
||||
// values should all match by the time we perform
|
||||
// this lookup anyway.
|
||||
self.selection.forEach(function (selected) {
|
||||
result = (selected[property] !== undefined) ?
|
||||
self.lookupState(property, selected) :
|
||||
result;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Refresh toolbar state to match selection
|
||||
function refreshState() {
|
||||
toolbarState = properties.map(initializeState);
|
||||
}
|
||||
this.selection = s;
|
||||
this.toolbarStructure.sections.forEach(refreshSectionApplicability);
|
||||
this.toolbarState = this.properties.map(initializeState);
|
||||
};
|
||||
|
||||
toolbarStructure.sections =
|
||||
((structure || {}).sections || []).map(convertSection);
|
||||
/**
|
||||
* Get the structure of the toolbar, as appropriate to
|
||||
* pass to `mct-toolbar`.
|
||||
* @returns the toolbar structure
|
||||
*/
|
||||
EditToolbar.prototype.getStructure = function () {
|
||||
return this.toolbarStructure;
|
||||
};
|
||||
|
||||
toolbarState = [];
|
||||
/**
|
||||
* Get the current state of the toolbar, as appropriate
|
||||
* to two-way bind to the state handled by `mct-toolbar`.
|
||||
* @returns {Array} state of the toolbar
|
||||
*/
|
||||
EditToolbar.prototype.getState = function () {
|
||||
return this.toolbarState;
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Set the current selection. Visisbility of sections
|
||||
* and items in the toolbar will be updated to match this.
|
||||
* @param {Array} s the new selection
|
||||
*/
|
||||
setSelection: function (s) {
|
||||
selection = s;
|
||||
refreshApplicability();
|
||||
refreshState();
|
||||
},
|
||||
/**
|
||||
* Get the structure of the toolbar, as appropriate to
|
||||
* pass to `mct-toolbar`.
|
||||
* @returns the toolbar structure
|
||||
*/
|
||||
getStructure: function () {
|
||||
return toolbarStructure;
|
||||
},
|
||||
/**
|
||||
* Get the current state of the toolbar, as appropriate
|
||||
* to two-way bind to the state handled by `mct-toolbar`.
|
||||
* @returns {Array} state of the toolbar
|
||||
*/
|
||||
getState: function () {
|
||||
return toolbarState;
|
||||
},
|
||||
/**
|
||||
* Update state within the current selection.
|
||||
* @param {number} index the index of the corresponding
|
||||
* element in the state array
|
||||
* @param value the new value to convey to the selection
|
||||
*/
|
||||
updateState: function (index, value) {
|
||||
return updateProperties(properties[index], value);
|
||||
/**
|
||||
* Update state within the current selection.
|
||||
* @param {number} index the index of the corresponding
|
||||
* element in the state array
|
||||
* @param value the new value to convey to the selection
|
||||
*/
|
||||
EditToolbar.prototype.updateState = function (index, value) {
|
||||
var self = this;
|
||||
|
||||
// Update value for this property in all elements of the
|
||||
// selection which have this property.
|
||||
function updateProperties(property, value) {
|
||||
var changed = false;
|
||||
|
||||
// Update property in a selected element
|
||||
function updateProperty(selected) {
|
||||
// Ignore selected elements which don't have this property
|
||||
if (selected[property] !== undefined) {
|
||||
// Check if this is a setter, or just assignable
|
||||
if (typeof selected[property] === 'function') {
|
||||
changed =
|
||||
changed || (selected[property]() !== value);
|
||||
selected[property](value);
|
||||
} else {
|
||||
changed =
|
||||
changed || (selected[property] !== value);
|
||||
selected[property] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update property in all selected elements
|
||||
self.selection.forEach(updateProperty);
|
||||
|
||||
// Return whether or not anything changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
return updateProperties(this.properties[index], value);
|
||||
};
|
||||
|
||||
return EditToolbar;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
@ -27,17 +27,21 @@ define(
|
||||
"use strict";
|
||||
|
||||
// No operation
|
||||
function noop() {}
|
||||
var NOOP_REPRESENTER = {
|
||||
represent: function () {},
|
||||
destroy: function () {}
|
||||
};
|
||||
|
||||
/**
|
||||
* The EditToolbarRepresenter populates the toolbar in Edit mode
|
||||
* based on a view's definition.
|
||||
* @param {Scope} scope the Angular scope of the representation
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
* @implements {Representer}
|
||||
*/
|
||||
function EditToolbarRepresenter(scope, element, attrs) {
|
||||
var toolbar,
|
||||
toolbarObject = {};
|
||||
var self = this;
|
||||
|
||||
// Mark changes as ready to persist
|
||||
function commit(message) {
|
||||
@ -49,31 +53,33 @@ define(
|
||||
// Handle changes to the current selection
|
||||
function updateSelection(selection) {
|
||||
// Only update if there is a toolbar to update
|
||||
if (toolbar) {
|
||||
if (self.toolbar) {
|
||||
// Make sure selection is array-like
|
||||
selection = Array.isArray(selection) ?
|
||||
selection :
|
||||
(selection ? [selection] : []);
|
||||
|
||||
// Update the toolbar's selection
|
||||
toolbar.setSelection(selection);
|
||||
self.toolbar.setSelection(selection);
|
||||
|
||||
// ...and expose its structure/state
|
||||
toolbarObject.structure = toolbar.getStructure();
|
||||
toolbarObject.state = toolbar.getState();
|
||||
self.toolbarObject.structure =
|
||||
self.toolbar.getStructure();
|
||||
self.toolbarObject.state =
|
||||
self.toolbar.getState();
|
||||
}
|
||||
}
|
||||
|
||||
// Get state (to watch it)
|
||||
function getState() {
|
||||
return toolbarObject.state;
|
||||
return self.toolbarObject.state;
|
||||
}
|
||||
|
||||
// Update selection models to match changed toolbar state
|
||||
function updateState(state) {
|
||||
// Update underlying state based on toolbar changes
|
||||
var changed = (state || []).map(function (value, index) {
|
||||
return toolbar.updateState(index, value);
|
||||
return self.toolbar.updateState(index, value);
|
||||
}).reduce(function (a, b) {
|
||||
return a || b;
|
||||
}, false);
|
||||
@ -85,66 +91,73 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize toolbar (expose object to parent scope)
|
||||
function initialize(definition) {
|
||||
// If we have been asked to expose toolbar state...
|
||||
if (attrs.toolbar) {
|
||||
// Initialize toolbar object
|
||||
toolbar = new EditToolbar(definition, commit);
|
||||
// Ensure toolbar state is exposed
|
||||
scope.$parent[attrs.toolbar] = toolbarObject;
|
||||
}
|
||||
}
|
||||
|
||||
// Represent a domain object using this definition
|
||||
function represent(representation) {
|
||||
// Get the newest toolbar definition from the view
|
||||
var definition = (representation || {}).toolbar || {};
|
||||
// Expose the toolbar object to the parent scope
|
||||
initialize(definition);
|
||||
// Create a selection scope
|
||||
scope.selection = new EditToolbarSelection();
|
||||
// Initialize toolbar to an empty selection
|
||||
updateSelection([]);
|
||||
}
|
||||
|
||||
// Destroy; remove toolbar object from parent scope
|
||||
function destroy() {
|
||||
// Avoid attaching scope to this;
|
||||
// http://errors.angularjs.org/1.2.26/ng/cpws
|
||||
this.setSelection = function (s) {
|
||||
scope.selection = s;
|
||||
};
|
||||
this.clearExposedToolbar = function () {
|
||||
// Clear exposed toolbar state (if any)
|
||||
if (attrs.toolbar) {
|
||||
delete scope.$parent[attrs.toolbar];
|
||||
}
|
||||
}
|
||||
};
|
||||
this.exposeToolbar = function () {
|
||||
scope.$parent[self.attrs.toolbar] = self.toolbarObject;
|
||||
};
|
||||
|
||||
this.commit = commit;
|
||||
this.attrs = attrs;
|
||||
this.updateSelection = updateSelection;
|
||||
this.toolbar = undefined;
|
||||
this.toolbarObject = {};
|
||||
|
||||
// If this representation exposes a toolbar, set up watches
|
||||
// to synchronize with it.
|
||||
if (attrs.toolbar) {
|
||||
if (attrs && attrs.toolbar) {
|
||||
// Detect and handle changes to state from the toolbar
|
||||
scope.$watchCollection(getState, updateState);
|
||||
// Watch for changes in the current selection state
|
||||
scope.$watchCollection("selection.all()", updateSelection);
|
||||
// Expose toolbar state under that name
|
||||
scope.$parent[attrs.toolbar] = toolbarObject;
|
||||
scope.$parent[attrs.toolbar] = this.toolbarObject;
|
||||
} else {
|
||||
// No toolbar declared, so do nothing.
|
||||
return NOOP_REPRESENTER;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Set the current representation in use, and the domain
|
||||
* object being represented.
|
||||
*
|
||||
* @param {RepresentationDefinition} representation the
|
||||
* definition of the representation in use
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
* being represented
|
||||
*/
|
||||
represent: (attrs || {}).toolbar ? represent : noop,
|
||||
/**
|
||||
* Release any resources associated with this representer.
|
||||
*/
|
||||
destroy: (attrs || {}).toolbar ? destroy : noop
|
||||
};
|
||||
}
|
||||
|
||||
// Represent a domain object using this definition
|
||||
EditToolbarRepresenter.prototype.represent = function (representation) {
|
||||
// Get the newest toolbar definition from the view
|
||||
var definition = (representation || {}).toolbar || {},
|
||||
self = this;
|
||||
|
||||
// Initialize toolbar (expose object to parent scope)
|
||||
function initialize(definition) {
|
||||
// If we have been asked to expose toolbar state...
|
||||
if (self.attrs.toolbar) {
|
||||
// Initialize toolbar object
|
||||
self.toolbar = new EditToolbar(definition, self.commit);
|
||||
// Ensure toolbar state is exposed
|
||||
self.exposeToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
// Expose the toolbar object to the parent scope
|
||||
initialize(definition);
|
||||
// Create a selection scope
|
||||
this.setSelection(new EditToolbarSelection());
|
||||
// Initialize toolbar to an empty selection
|
||||
this.updateSelection([]);
|
||||
};
|
||||
|
||||
// Destroy; remove toolbar object from parent scope
|
||||
EditToolbarRepresenter.prototype.destroy = function () {
|
||||
this.clearExposedToolbar();
|
||||
};
|
||||
|
||||
return EditToolbarRepresenter;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -37,110 +37,96 @@ define(
|
||||
* * The selection, for single selected elements within the
|
||||
* view.
|
||||
*
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbarSelection() {
|
||||
var selection = [ {} ],
|
||||
selecting = false,
|
||||
selected;
|
||||
this.selection = [{}];
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
}
|
||||
|
||||
// Remove the currently-selected object
|
||||
function deselect() {
|
||||
// Nothing to do if we don't have a selected object
|
||||
if (selecting) {
|
||||
// Clear state tracking
|
||||
selecting = false;
|
||||
selected = undefined;
|
||||
/**
|
||||
* Check if an object is currently selected.
|
||||
* @param {*} obj the object to check for selection
|
||||
* @returns {boolean} true if selected, otherwise false
|
||||
*/
|
||||
EditToolbarSelection.prototype.selected = function (obj) {
|
||||
return (obj === this.selectedObj) || (obj === this.selection[0]);
|
||||
};
|
||||
|
||||
// Remove the selection
|
||||
selection.pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Select an object.
|
||||
* @param obj the object to select
|
||||
* @returns {boolean} true if selection changed
|
||||
*/
|
||||
EditToolbarSelection.prototype.select = function (obj) {
|
||||
// Proxy is always selected
|
||||
if (obj === this.selection[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select an object
|
||||
function select(obj) {
|
||||
// Proxy is always selected
|
||||
if (obj === selection[0]) {
|
||||
return false;
|
||||
}
|
||||
// Clear any existing selection
|
||||
this.deselect();
|
||||
|
||||
// Clear any existing selection
|
||||
deselect();
|
||||
// Note the current selection state
|
||||
this.selectedObj = obj;
|
||||
this.selecting = true;
|
||||
|
||||
// Note the current selection state
|
||||
selected = obj;
|
||||
selecting = true;
|
||||
// Add the selection
|
||||
this.selection.push(obj);
|
||||
};
|
||||
|
||||
// Add the selection
|
||||
selection.push(obj);
|
||||
/**
|
||||
* Clear the current selection.
|
||||
* @returns {boolean} true if selection changed
|
||||
*/
|
||||
EditToolbarSelection.prototype.deselect = function () {
|
||||
// Nothing to do if we don't have a selected object
|
||||
if (this.selecting) {
|
||||
// Clear state tracking
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
|
||||
// Remove the selection
|
||||
this.selection.pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the currently-selected object.
|
||||
* @returns the currently selected object
|
||||
*/
|
||||
EditToolbarSelection.prototype.get = function () {
|
||||
return this.selectedObj;
|
||||
};
|
||||
|
||||
// Check if an object is selected
|
||||
function isSelected(obj) {
|
||||
return (obj === selected) || (obj === selection[0]);
|
||||
/**
|
||||
* Get/set the view proxy (for toolbar actions taken upon
|
||||
* the view itself.)
|
||||
* @param [proxy] the view proxy (if setting)
|
||||
* @returns the current view proxy
|
||||
*/
|
||||
EditToolbarSelection.prototype.proxy = function (p) {
|
||||
if (arguments.length > 0) {
|
||||
this.selection[0] = p;
|
||||
}
|
||||
return this.selection[0];
|
||||
};
|
||||
|
||||
// Getter for current selection
|
||||
function get() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
// Getter/setter for view proxy
|
||||
function proxy(p) {
|
||||
if (arguments.length > 0) {
|
||||
selection[0] = p;
|
||||
}
|
||||
return selection[0];
|
||||
}
|
||||
|
||||
// Getter for the full array of selected objects (incl. view proxy)
|
||||
function all() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Check if an object is currently selected.
|
||||
* @returns true if selected, otherwise false
|
||||
*/
|
||||
selected: isSelected,
|
||||
/**
|
||||
* Select an object.
|
||||
* @param obj the object to select
|
||||
* @returns {boolean} true if selection changed
|
||||
*/
|
||||
select: select,
|
||||
/**
|
||||
* Clear the current selection.
|
||||
* @returns {boolean} true if selection changed
|
||||
*/
|
||||
deselect: deselect,
|
||||
/**
|
||||
* Get the currently-selected object.
|
||||
* @returns the currently selected object
|
||||
*/
|
||||
get: get,
|
||||
/**
|
||||
* Get/set the view proxy (for toolbar actions taken upon
|
||||
* the view itself.)
|
||||
* @param [proxy] the view proxy (if setting)
|
||||
* @returns the current view proxy
|
||||
*/
|
||||
proxy: proxy,
|
||||
/**
|
||||
* Get an array containing all selections, including the
|
||||
* selection proxy. It is generally not advisable to
|
||||
* mutate this array directly.
|
||||
* @returns {Array} all selections
|
||||
*/
|
||||
all: all
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get an array containing all selections, including the
|
||||
* selection proxy. It is generally not advisable to
|
||||
* mutate this array directly.
|
||||
* @returns {Array} all selections
|
||||
*/
|
||||
EditToolbarSelection.prototype.all = function () {
|
||||
return this.selection;
|
||||
};
|
||||
|
||||
return EditToolbarSelection;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ define(
|
||||
mockDomainObject,
|
||||
mockParent,
|
||||
mockContext,
|
||||
mockMutation,
|
||||
mockComposition,
|
||||
mockPersistence,
|
||||
mockType,
|
||||
actionContext,
|
||||
@ -67,7 +67,7 @@ define(
|
||||
}
|
||||
};
|
||||
mockContext = jasmine.createSpyObj("context", [ "getParent" ]);
|
||||
mockMutation = jasmine.createSpyObj("mutation", [ "invoke" ]);
|
||||
mockComposition = jasmine.createSpyObj("composition", [ "invoke", "add" ]);
|
||||
mockPersistence = jasmine.createSpyObj("persistence", [ "persist" ]);
|
||||
mockType = jasmine.createSpyObj("type", [ "hasFeature" ]);
|
||||
|
||||
@ -75,11 +75,11 @@ define(
|
||||
mockDomainObject.getCapability.andReturn(mockContext);
|
||||
mockContext.getParent.andReturn(mockParent);
|
||||
mockType.hasFeature.andReturn(true);
|
||||
mockMutation.invoke.andReturn(mockPromise(true));
|
||||
|
||||
mockComposition.invoke.andReturn(mockPromise(true));
|
||||
mockComposition.add.andReturn(mockPromise(true));
|
||||
|
||||
capabilities = {
|
||||
mutation: mockMutation,
|
||||
composition: mockComposition,
|
||||
persistence: mockPersistence,
|
||||
type: mockType
|
||||
};
|
||||
@ -96,33 +96,17 @@ define(
|
||||
});
|
||||
|
||||
|
||||
it("mutates the parent when performed", function () {
|
||||
it("adds to the parent's composition when performed", function () {
|
||||
action.perform();
|
||||
expect(mockMutation.invoke)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
expect(mockComposition.add)
|
||||
.toHaveBeenCalledWith(mockDomainObject);
|
||||
});
|
||||
|
||||
it("changes composition from its mutation function", function () {
|
||||
var mutator, result;
|
||||
it("persists changes afterward", function () {
|
||||
action.perform();
|
||||
mutator = mockMutation.invoke.mostRecentCall.args[0];
|
||||
result = mutator(model);
|
||||
|
||||
// Should not have cancelled the mutation
|
||||
expect(result).not.toBe(false);
|
||||
|
||||
// Simulate mutate's behavior (remove can either return a
|
||||
// new model or modify this one in-place)
|
||||
result = result || model;
|
||||
|
||||
// Should have removed "test" - that was our
|
||||
// mock domain object's id.
|
||||
expect(result.composition).toEqual(["a", "b", "c", "test"]);
|
||||
|
||||
// Finally, should have persisted
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ define(
|
||||
return {
|
||||
getValue: function (model) { return model[k]; },
|
||||
setValue: function (model, v) { model[k] = v; },
|
||||
getDefinition: function () { return {}; }
|
||||
getDefinition: function () { return { control: 'textfield '}; }
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -28,9 +28,16 @@ define(
|
||||
|
||||
describe("The Remove action", function () {
|
||||
var mockQ,
|
||||
mockNavigationService,
|
||||
mockDomainObject,
|
||||
mockParent,
|
||||
mockChildObject,
|
||||
mockGrandchildObject,
|
||||
mockRootObject,
|
||||
mockContext,
|
||||
mockChildContext,
|
||||
mockGrandchildContext,
|
||||
mockRootContext,
|
||||
mockMutation,
|
||||
mockPersistence,
|
||||
mockType,
|
||||
@ -54,6 +61,18 @@ define(
|
||||
"domainObject",
|
||||
[ "getId", "getCapability" ]
|
||||
);
|
||||
mockChildObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getId", "getCapability" ]
|
||||
);
|
||||
mockGrandchildObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getId", "getCapability" ]
|
||||
);
|
||||
mockRootObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getId", "getCapability" ]
|
||||
);
|
||||
mockQ = { when: mockPromise };
|
||||
mockParent = {
|
||||
getModel: function () {
|
||||
@ -67,28 +86,41 @@ define(
|
||||
}
|
||||
};
|
||||
mockContext = jasmine.createSpyObj("context", [ "getParent" ]);
|
||||
mockChildContext = jasmine.createSpyObj("context", [ "getParent" ]);
|
||||
mockGrandchildContext = jasmine.createSpyObj("context", [ "getParent" ]);
|
||||
mockRootContext = jasmine.createSpyObj("context", [ "getParent" ]);
|
||||
mockMutation = jasmine.createSpyObj("mutation", [ "invoke" ]);
|
||||
mockPersistence = jasmine.createSpyObj("persistence", [ "persist" ]);
|
||||
mockType = jasmine.createSpyObj("type", [ "hasFeature" ]);
|
||||
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
"navigationService",
|
||||
[
|
||||
"getNavigation",
|
||||
"setNavigation",
|
||||
"addListener",
|
||||
"removeListener"
|
||||
]
|
||||
);
|
||||
mockNavigationService.getNavigation.andReturn(mockDomainObject);
|
||||
|
||||
|
||||
mockDomainObject.getId.andReturn("test");
|
||||
mockDomainObject.getCapability.andReturn(mockContext);
|
||||
mockContext.getParent.andReturn(mockParent);
|
||||
mockType.hasFeature.andReturn(true);
|
||||
|
||||
|
||||
capabilities = {
|
||||
mutation: mockMutation,
|
||||
persistence: mockPersistence,
|
||||
type: mockType
|
||||
};
|
||||
model = {
|
||||
composition: [ "a", "test", "b", "c" ]
|
||||
composition: [ "a", "test", "b" ]
|
||||
};
|
||||
|
||||
actionContext = { domainObject: mockDomainObject };
|
||||
|
||||
action = new RemoveAction(mockQ, actionContext);
|
||||
action = new RemoveAction(mockQ, mockNavigationService, actionContext);
|
||||
});
|
||||
|
||||
it("only applies to objects with parents", function () {
|
||||
@ -123,11 +155,64 @@ define(
|
||||
|
||||
// Should have removed "test" - that was our
|
||||
// mock domain object's id.
|
||||
expect(result.composition).toEqual(["a", "b", "c"]);
|
||||
expect(result.composition).toEqual(["a", "b"]);
|
||||
|
||||
// Finally, should have persisted
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes parent of object currently navigated to", function () {
|
||||
// Navigates to child object
|
||||
mockNavigationService.getNavigation.andReturn(mockChildObject);
|
||||
|
||||
// Test is id of object being removed
|
||||
// Child object has different id
|
||||
mockDomainObject.getId.andReturn("test");
|
||||
mockChildObject.getId.andReturn("not test");
|
||||
|
||||
// Sets context for the child and domainObject
|
||||
mockDomainObject.getCapability.andReturn(mockContext);
|
||||
mockChildObject.getCapability.andReturn(mockChildContext);
|
||||
|
||||
// Parents of child and domainObject are set
|
||||
mockContext.getParent.andReturn(mockParent);
|
||||
mockChildContext.getParent.andReturn(mockDomainObject);
|
||||
|
||||
mockType.hasFeature.andReturn(true);
|
||||
|
||||
action.perform();
|
||||
|
||||
// Expects navigation to parent of domainObject (removed object)
|
||||
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
|
||||
});
|
||||
|
||||
it("checks if removing object not in ascendent path (reaches ROOT)", function () {
|
||||
// Navigates to grandchild of ROOT
|
||||
mockNavigationService.getNavigation.andReturn(mockGrandchildObject);
|
||||
|
||||
// domainObject (grandparent) is set as ROOT, child and grandchild
|
||||
// are set objects not being removed
|
||||
mockDomainObject.getId.andReturn("test 1");
|
||||
mockRootObject.getId.andReturn("ROOT");
|
||||
mockChildObject.getId.andReturn("not test 2");
|
||||
mockGrandchildObject.getId.andReturn("not test 3");
|
||||
|
||||
// Sets context for the grandchild, child, and domainObject
|
||||
mockRootObject.getCapability.andReturn(mockRootContext);
|
||||
mockChildObject.getCapability.andReturn(mockChildContext);
|
||||
mockGrandchildObject.getCapability.andReturn(mockGrandchildContext);
|
||||
|
||||
// Parents of grandchild and child are set
|
||||
mockChildContext.getParent.andReturn(mockRootObject);
|
||||
mockGrandchildContext.getParent.andReturn(mockChildObject);
|
||||
|
||||
mockType.hasFeature.andReturn(true);
|
||||
|
||||
action.perform();
|
||||
|
||||
// Expects no navigation to occur
|
||||
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -112,7 +112,9 @@ define(
|
||||
});
|
||||
|
||||
it("saves objects that have been marked dirty", function () {
|
||||
var objects = ['a', 'b', 'c'].map(TestObject).map(cache.getEditableObject);
|
||||
var objects = ['a', 'b', 'c'].map(TestObject).map(function (domainObject) {
|
||||
return cache.getEditableObject(domainObject);
|
||||
});
|
||||
|
||||
cache.markDirty(objects[0]);
|
||||
cache.markDirty(objects[2]);
|
||||
@ -123,7 +125,9 @@ define(
|
||||
});
|
||||
|
||||
it("does not save objects that have been marked clean", function () {
|
||||
var objects = ['a', 'b', 'c'].map(TestObject).map(cache.getEditableObject);
|
||||
var objects = ['a', 'b', 'c'].map(TestObject).map(function (domainObject) {
|
||||
return cache.getEditableObject(domainObject);
|
||||
});
|
||||
|
||||
cache.markDirty(objects[0]);
|
||||
cache.markDirty(objects[2]);
|
||||
|
@ -6,8 +6,13 @@
|
||||
"services": [
|
||||
{
|
||||
"key": "urlService",
|
||||
"implementation": "/services/UrlService.js",
|
||||
"implementation": "services/UrlService.js",
|
||||
"depends": [ "$location" ]
|
||||
},
|
||||
{
|
||||
"key": "popupService",
|
||||
"implementation": "services/PopupService.js",
|
||||
"depends": [ "$document", "$window" ]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
@ -20,18 +25,6 @@
|
||||
{
|
||||
"stylesheetUrl": "css/normalize.min.css",
|
||||
"priority": "mandatory"
|
||||
},
|
||||
{
|
||||
"stylesheetUrl": "css/theme-espresso.css",
|
||||
"priority": 1000
|
||||
},
|
||||
{
|
||||
"stylesheetUrl": "css/items.css",
|
||||
"priority": 901
|
||||
},
|
||||
{
|
||||
"stylesheetUrl": "css/tree.css",
|
||||
"priority": 900
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
@ -57,6 +50,16 @@
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TimeRangeController",
|
||||
"implementation": "controllers/TimeRangeController.js",
|
||||
"depends": [ "$scope", "now" ]
|
||||
},
|
||||
{
|
||||
"key": "DateTimePickerController",
|
||||
"implementation": "controllers/DateTimePickerController.js",
|
||||
"depends": [ "$scope", "now" ]
|
||||
},
|
||||
{
|
||||
"key": "TreeNodeController",
|
||||
"implementation": "controllers/TreeNodeController.js",
|
||||
@ -117,11 +120,21 @@
|
||||
"implementation": "directives/MCTDrag.js",
|
||||
"depends": [ "$document" ]
|
||||
},
|
||||
{
|
||||
"key": "mctClickElsewhere",
|
||||
"implementation": "directives/MCTClickElsewhere.js",
|
||||
"depends": [ "$document" ]
|
||||
},
|
||||
{
|
||||
"key": "mctResize",
|
||||
"implementation": "directives/MCTResize.js",
|
||||
"depends": [ "$timeout" ]
|
||||
},
|
||||
{
|
||||
"key": "mctPopup",
|
||||
"implementation": "directives/MCTPopup.js",
|
||||
"depends": [ "$compile", "popupService" ]
|
||||
},
|
||||
{
|
||||
"key": "mctScrollX",
|
||||
"implementation": "directives/MCTScroll.js",
|
||||
@ -135,7 +148,7 @@
|
||||
{
|
||||
"key": "mctSplitPane",
|
||||
"implementation": "directives/MCTSplitPane.js",
|
||||
"depends": [ "$parse", "$log" ]
|
||||
"depends": [ "$parse", "$log", "$interval" ]
|
||||
},
|
||||
{
|
||||
"key": "mctSplitter",
|
||||
@ -196,7 +209,7 @@
|
||||
{
|
||||
"key": "label",
|
||||
"templateUrl": "templates/label.html",
|
||||
"uses": [ "type" ],
|
||||
"uses": [ "type", "location" ],
|
||||
"gestures": [ "drag", "menu", "info" ]
|
||||
},
|
||||
{
|
||||
@ -225,6 +238,10 @@
|
||||
{
|
||||
"key": "selector",
|
||||
"templateUrl": "templates/controls/selector.html"
|
||||
},
|
||||
{
|
||||
"key": "datetime-picker",
|
||||
"templateUrl": "templates/controls/datetime-picker.html"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
|
@ -13,7 +13,7 @@ javascripts_dir = "js"
|
||||
output_style = :nested
|
||||
|
||||
# To enable relative paths to assets via compass helper functions. Uncomment:
|
||||
# relative_assets = true
|
||||
relative_assets = true
|
||||
|
||||
# To disable debugging comments that display the original location of your selectors. Uncomment:
|
||||
# line_comments = false
|
||||
|
@ -1,541 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/************************** FEATURES */
|
||||
/************************** VERY INFLUENTIAL GLOBAL DIMENSIONS */
|
||||
/************************** COLORS AND SHADING */
|
||||
/************************** RATIOS */
|
||||
/************************** LAYOUT */
|
||||
/************************** CONTROLS */
|
||||
/************************** PATHS */
|
||||
/************************** TIMINGS */
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*
|
||||
@mixin invokeMenu($baseColor: $colorBodyFg) {
|
||||
$c: $baseColor;
|
||||
color: $c;
|
||||
&:hover {
|
||||
color: lighten($c, $ltGamma);
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 22, ../sass/forms/_elems.scss */
|
||||
.section-header {
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
padding: 5px; }
|
||||
/* line 28, ../sass/forms/_elems.scss */
|
||||
.section-header:first-child {
|
||||
margin-top: 0; }
|
||||
|
||||
/* line 35, ../sass/forms/_elems.scss */
|
||||
.form .form-section {
|
||||
position: relative; }
|
||||
/* line 39, ../sass/forms/_elems.scss */
|
||||
.form .form-row {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
border-top: 1px solid #4d4d4d;
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
position: relative; }
|
||||
/* line 46, ../sass/forms/_elems.scss */
|
||||
.form .form-row.first {
|
||||
border-top: none; }
|
||||
/* line 50, ../sass/forms/_elems.scss */
|
||||
.form .form-row .label,
|
||||
.form .form-row .controls {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.75rem;
|
||||
line-height: 22px;
|
||||
min-height: 22px; }
|
||||
/* line 61, ../sass/forms/_elems.scss */
|
||||
.form .form-row > .label {
|
||||
float: left;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
width: 20%; }
|
||||
/* line 69, ../sass/forms/_elems.scss */
|
||||
.form .form-row .value {
|
||||
color: #cccccc; }
|
||||
/* line 73, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls {
|
||||
float: left;
|
||||
position: relative;
|
||||
width: 79.9%; }
|
||||
/* line 80, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls .l-composite-control.l-checkbox {
|
||||
display: inline-block;
|
||||
line-height: 14px;
|
||||
margin-right: 5px; }
|
||||
/* line 89, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls input[type="text"] {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
margin-top: -4px;
|
||||
vertical-align: baseline; }
|
||||
/* line 96, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls .l-med input[type="text"] {
|
||||
width: 200px; }
|
||||
/* line 100, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls .l-small input[type="text"] {
|
||||
width: 50px; }
|
||||
/* line 104, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls .l-numeric input[type="text"] {
|
||||
text-align: right; }
|
||||
/* line 108, ../sass/forms/_elems.scss */
|
||||
.form .form-row .controls .select {
|
||||
margin-right: 5px; }
|
||||
/* line 113, ../sass/forms/_elems.scss */
|
||||
.form .form-row .field-hints {
|
||||
color: #666666; }
|
||||
/* line 117, ../sass/forms/_elems.scss */
|
||||
.form .form-row .selector-list {
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
height: 150px;
|
||||
overflow: auto; }
|
||||
/* line 128, ../sass/forms/_elems.scss */
|
||||
.form .form-row .selector-list .wrapper {
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
left: 5px; }
|
||||
|
||||
/* line 142, ../sass/forms/_elems.scss */
|
||||
label.form-control.checkbox input {
|
||||
margin-right: 5px;
|
||||
vertical-align: top; }
|
||||
|
||||
/* line 148, ../sass/forms/_elems.scss */
|
||||
.hint,
|
||||
.s-hint {
|
||||
font-size: 0.9em; }
|
||||
|
||||
/* line 153, ../sass/forms/_elems.scss */
|
||||
.l-result {
|
||||
display: inline-block;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
position: relative;
|
||||
vertical-align: top; }
|
||||
/* line 160, ../sass/forms/_elems.scss */
|
||||
.l-result div.s-hint {
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
background: rgba(255, 153, 0, 0.8);
|
||||
display: block;
|
||||
color: #ffd699;
|
||||
padding: 5px; }
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 22, ../sass/forms/_textarea.scss */
|
||||
.edit-main textarea {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
-webkit-box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #cccccc;
|
||||
outline: none;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%; }
|
||||
/* line 33, ../sass/forms/_mixins.scss */
|
||||
.edit-main textarea.error {
|
||||
background: rgba(255, 0, 0, 0.5); }
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 22, ../sass/forms/_text-input.scss */
|
||||
input[type="text"] {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
-webkit-box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #cccccc;
|
||||
outline: none;
|
||||
padding: 0 3px; }
|
||||
/* line 33, ../sass/forms/_mixins.scss */
|
||||
input[type="text"].error {
|
||||
background: rgba(255, 0, 0, 0.5); }
|
||||
/* line 29, ../sass/forms/_text-input.scss */
|
||||
input[type="text"].numeric {
|
||||
text-align: right; }
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 22, ../sass/forms/_selects.scss */
|
||||
.form-control.select {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzUyNTI1MiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzQ1NDU0NSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
|
||||
background-size: 100%;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #525252), color-stop(100%, #454545));
|
||||
background-image: -moz-linear-gradient(#525252, #454545);
|
||||
background-image: -webkit-linear-gradient(#525252, #454545);
|
||||
background-image: linear-gradient(#525252, #454545);
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
border: none;
|
||||
border-top: 1px solid #575757;
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 0 2px 2px;
|
||||
overflow: hidden;
|
||||
position: relative; }
|
||||
/* line 162, ../sass/_mixins.scss */
|
||||
.form-control.select:not(.disabled):hover {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzYzNjM2MyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzU3NTc1NyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
|
||||
background-size: 100%;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #636363), color-stop(100%, #575757));
|
||||
background-image: -moz-linear-gradient(#636363, #575757);
|
||||
background-image: -webkit-linear-gradient(#636363, #575757);
|
||||
background-image: linear-gradient(#636363, #575757);
|
||||
color: #bdbdbd; }
|
||||
/* line 165, ../sass/_mixins.scss */
|
||||
.form-control.select:not(.disabled):hover.btn-menu .invoke-menu {
|
||||
color: #878787; }
|
||||
/* line 170, ../sass/_mixins.scss */
|
||||
.form-control.select.btn-menu .invoke-menu {
|
||||
color: #757575; }
|
||||
/* line 29, ../sass/forms/_selects.scss */
|
||||
.form-control.select select {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: none;
|
||||
color: #999;
|
||||
border: none !important;
|
||||
cursor: pointer;
|
||||
padding: 4px 25px 2px 5px;
|
||||
width: 120%; }
|
||||
/* line 38, ../sass/forms/_selects.scss */
|
||||
.form-control.select select option {
|
||||
margin: 5px 0; }
|
||||
/* line 42, ../sass/forms/_selects.scss */
|
||||
.form-control.select:after {
|
||||
color: #0099cc;
|
||||
content: "v";
|
||||
display: block;
|
||||
font-family: 'symbolsfont';
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 0; }
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 23, ../sass/forms/_channel-selector.scss */
|
||||
.channel-selector .line {
|
||||
margin-bottom: 5px;
|
||||
min-height: 22px; }
|
||||
/* line 27, ../sass/forms/_channel-selector.scss */
|
||||
.channel-selector .treeview {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
-webkit-box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
box-shadow: inset rgba(0, 0, 0, 0.5) 0 1px 5px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #cccccc;
|
||||
outline: none;
|
||||
padding: 0 3px;
|
||||
background: #3b3b3b;
|
||||
border-bottom: 1px solid #4d4d4d;
|
||||
min-height: 300px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
padding: 5px; }
|
||||
/* line 33, ../sass/forms/_mixins.scss */
|
||||
.channel-selector .treeview.error {
|
||||
background: rgba(255, 0, 0, 0.5); }
|
||||
/* line 36, ../sass/forms/_channel-selector.scss */
|
||||
.channel-selector .btns-add-remove {
|
||||
margin-top: 150px; }
|
||||
/* line 39, ../sass/forms/_channel-selector.scss */
|
||||
.channel-selector .btns-add-remove .btn {
|
||||
display: block;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 5px;
|
||||
padding: 10px;
|
||||
text-align: center; }
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 23, ../sass/forms/_datetime.scss */
|
||||
.complex.datetime span {
|
||||
display: inline-block;
|
||||
margin-right: 5px; }
|
||||
/* line 36, ../sass/forms/_datetime.scss */
|
||||
.complex.datetime .fields {
|
||||
margin-top: 3px 0;
|
||||
padding: 3px 0; }
|
||||
/* line 41, ../sass/forms/_datetime.scss */
|
||||
.complex.datetime .date {
|
||||
width: 85px; }
|
||||
/* line 44, ../sass/forms/_datetime.scss */
|
||||
.complex.datetime .date input {
|
||||
width: 80px; }
|
||||
/* line 50, ../sass/forms/_datetime.scss */
|
||||
.complex.datetime .time.sm {
|
||||
width: 45px; }
|
||||
/* line 53, ../sass/forms/_datetime.scss */
|
||||
.complex.datetime .time.sm input {
|
||||
width: 40px; }
|
@ -1,298 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/************************** FEATURES */
|
||||
/************************** VERY INFLUENTIAL GLOBAL DIMENSIONS */
|
||||
/************************** COLORS AND SHADING */
|
||||
/************************** RATIOS */
|
||||
/************************** LAYOUT */
|
||||
/************************** CONTROLS */
|
||||
/************************** PATHS */
|
||||
/************************** TIMINGS */
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*
|
||||
@mixin invokeMenu($baseColor: $colorBodyFg) {
|
||||
$c: $baseColor;
|
||||
color: $c;
|
||||
&:hover {
|
||||
color: lighten($c, $ltGamma);
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* line 22, ../sass/items/_item.scss */
|
||||
.items-holder {
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
overflow-y: auto; }
|
||||
/* line 25, ../sass/items/_item.scss */
|
||||
.items-holder .contents {
|
||||
top: 0; }
|
||||
/* line 29, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzVlNWU1ZSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzUyNTI1MiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
|
||||
background-size: 100%;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5e5e5e), color-stop(100%, #525252));
|
||||
background-image: -moz-linear-gradient(#5e5e5e, #525252);
|
||||
background-image: -webkit-linear-gradient(#5e5e5e, #525252);
|
||||
background-image: linear-gradient(#5e5e5e, #525252);
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
border: none;
|
||||
border-top: 1px solid #636363;
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
margin-bottom: 3px;
|
||||
margin-right: 3px;
|
||||
position: relative; }
|
||||
/* line 162, ../sass/_mixins.scss */
|
||||
.items-holder .item.grid-item:not(.disabled):hover {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzcwNzA3MCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzYzNjM2MyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
|
||||
background-size: 100%;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #707070), color-stop(100%, #636363));
|
||||
background-image: -moz-linear-gradient(#707070, #636363);
|
||||
background-image: -webkit-linear-gradient(#707070, #636363);
|
||||
background-image: linear-gradient(#707070, #636363);
|
||||
color: #bdbdbd; }
|
||||
/* line 165, ../sass/_mixins.scss */
|
||||
.items-holder .item.grid-item:not(.disabled):hover.btn-menu .invoke-menu {
|
||||
color: #949494; }
|
||||
/* line 170, ../sass/_mixins.scss */
|
||||
.items-holder .item.grid-item.btn-menu .invoke-menu {
|
||||
color: #828282; }
|
||||
/* line 46, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item:hover .item-main .item-type {
|
||||
color: #0099cc !important; }
|
||||
/* line 48, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item:hover .item-main .item-type .l-icon-link {
|
||||
color: #49dedb; }
|
||||
/* line 52, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item:hover .item-main .item-open {
|
||||
opacity: 1; }
|
||||
/* line 57, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .contents {
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
left: 5px; }
|
||||
/* line 61, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .bar.top-bar.abs {
|
||||
bottom: auto;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
z-index: 5; }
|
||||
/* line 66, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .bar.top-bar.abs .left, .items-holder .item.grid-item .bar.top-bar.abs .right {
|
||||
width: auto; }
|
||||
/* line 68, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .bar.top-bar.abs .left .icon, .items-holder .item.grid-item .bar.top-bar.abs .right .icon {
|
||||
margin-left: 3px; }
|
||||
/* line 70, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .bar.top-bar.abs .left .icon.l-icon-link, .items-holder .item.grid-item .bar.top-bar.abs .right .icon.l-icon-link {
|
||||
color: #49dedb; }
|
||||
/* line 76, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .bar.bottom-bar.abs {
|
||||
top: auto;
|
||||
height: 30px;
|
||||
padding: 5px; }
|
||||
/* line 82, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .item-main {
|
||||
line-height: 160px;
|
||||
z-index: 1; }
|
||||
/* line 88, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .item-main .item-type {
|
||||
overflow: false;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 40px;
|
||||
bottom: 40px;
|
||||
left: 40px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
color: #737373;
|
||||
text-align: center;
|
||||
font-size: 96.9px;
|
||||
line-height: 102px;
|
||||
bottom: auto;
|
||||
height: 102px;
|
||||
top: 30px; }
|
||||
/* line 100, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .item-main .item-type .l-icon-link {
|
||||
color: #1a8e8b;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
position: absolute;
|
||||
font-size: 32px;
|
||||
left: 0px;
|
||||
bottom: 10px;
|
||||
z-index: 2; }
|
||||
/* line 112, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .item-main .item-open {
|
||||
-moz-transition-property: "opacity";
|
||||
-o-transition-property: "opacity";
|
||||
-webkit-transition-property: "opacity";
|
||||
transition-property: "opacity";
|
||||
-moz-transition-duration: 200ms;
|
||||
-o-transition-duration: 200ms;
|
||||
-webkit-transition-duration: 200ms;
|
||||
transition-duration: 200ms;
|
||||
-moz-transition-timing-function: ease-in-out;
|
||||
-o-transition-timing-function: ease-in-out;
|
||||
-webkit-transition-timing-function: ease-in-out;
|
||||
transition-timing-function: ease-in-out;
|
||||
opacity: 0;
|
||||
font-size: 3em;
|
||||
left: auto;
|
||||
width: 50px;
|
||||
pointer-events: none;
|
||||
text-align: right; }
|
||||
/* line 124, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .title {
|
||||
text-shadow: rgba(0, 0, 0, 0.1) 0 1px 2px;
|
||||
color: #cccccc;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis; }
|
||||
/* line 132, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item .details {
|
||||
font-size: 0.8em; }
|
||||
/* line 135, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item.selected {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzBhYzJmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzAwYjRmMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
|
||||
background-size: 100%;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0ac2ff), color-stop(100%, #00b4f0));
|
||||
background-image: -moz-linear-gradient(#0ac2ff, #00b4f0);
|
||||
background-image: -webkit-linear-gradient(#0ac2ff, #00b4f0);
|
||||
background-image: linear-gradient(#0ac2ff, #00b4f0);
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px;
|
||||
border: none;
|
||||
border-top: 1px solid #14c4ff;
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
color: #80dfff; }
|
||||
/* line 162, ../sass/_mixins.scss */
|
||||
.items-holder .item.grid-item.selected:not(.disabled):hover {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzJlY2JmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzE0YzRmZiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
|
||||
background-size: 100%;
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2ecbff), color-stop(100%, #14c4ff));
|
||||
background-image: -moz-linear-gradient(#2ecbff, #14c4ff);
|
||||
background-image: -webkit-linear-gradient(#2ecbff, #14c4ff);
|
||||
background-image: linear-gradient(#2ecbff, #14c4ff);
|
||||
color: #bdbdbd; }
|
||||
/* line 165, ../sass/_mixins.scss */
|
||||
.items-holder .item.grid-item.selected:not(.disabled):hover.btn-menu .invoke-menu {
|
||||
color: #75ddff; }
|
||||
/* line 170, ../sass/_mixins.scss */
|
||||
.items-holder .item.grid-item.selected.btn-menu .invoke-menu {
|
||||
color: #52d4ff; }
|
||||
/* line 140, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item.selected .item-type, .items-holder .item.grid-item.selected .top-bar .icon:not(.alert) {
|
||||
color: #80dfff; }
|
||||
/* line 141, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item.selected .item-main .item-open {
|
||||
color: #80dfff; }
|
||||
/* line 142, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item.selected .title {
|
||||
color: white; }
|
||||
/* line 144, ../sass/items/_item.scss */
|
||||
.items-holder .item.grid-item.selected:hover .item-main .item-type {
|
||||
color: white !important; }
|
@ -1,120 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*
|
||||
Syntax error: Undefined variable: "$colorBodyBg".
|
||||
on line 66 of /Users/iMac/dev/nasa/wtd-dev/platform/sass/_effects.scss, in `boxOutline'
|
||||
from line 7 of /Users/iMac/dev/nasa/wtd-dev/platform/sass/_effects.scss
|
||||
from line 10 of /Users/iMac/dev/nasa/wtd-dev/platform/sass/main.scss
|
||||
|
||||
Backtrace:
|
||||
/Users/iMac/dev/nasa/wtd-dev/platform/sass/_effects.scss:66:in `boxOutline'
|
||||
/Users/iMac/dev/nasa/wtd-dev/platform/sass/_effects.scss:7
|
||||
/Users/iMac/dev/nasa/wtd-dev/platform/sass/main.scss:10
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/script/variable.rb:49:in `_perform'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/script/node.rb:40:in `perform'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/script/funcall.rb:93:in `_perform'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/script/funcall.rb:93:in `map'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/script/funcall.rb:93:in `_perform'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/script/node.rb:40:in `perform'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:56:in `perform_arguments'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:50:in `zip'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:50:in `perform_arguments'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:261:in `visit_mixin'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `send'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:98:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:53:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:53:in `map'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:53:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:107:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:119:in `with_environment'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:106:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:318:in `visit_rule'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `send'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:98:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:225:in `visit_import'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:225:in `map'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:225:in `visit_import'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `send'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:98:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:53:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:53:in `map'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:53:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:107:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:119:in `with_environment'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:106:in `visit_children'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:126:in `visit_root'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `send'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/base.rb:37:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:98:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:7:in `send'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/visitors/perform.rb:7:in `visit'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/tree/root_node.rb:20:in `render'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/engine.rb:315:in `_render'
|
||||
/Library/Ruby/Gems/1.8/gems/sass-3.2.5/lib/sass/../sass/engine.rb:262:in `render'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:140:in `compile'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:126:in `timed'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:139:in `compile'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/logger.rb:45:in `red'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:138:in `compile'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:118:in `compile_if_required'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:103:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:101:in `each'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:101:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:126:in `timed'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/compiler.rb:100:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/commands/watch_project.rb:147:in `recompile'
|
||||
/Library/Ruby/Site/1.8/rubygems/custom_require.rb:36:in `to_proc'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/path.rb:73:in `call'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/path.rb:73:in `run_callback'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/path.rb:55:in `callback_action'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/path.rb:35:in `update'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/state/directory.rb:39:in `modified'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/state/directory.rb:37:in `each'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/state/directory.rb:37:in `modified'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/state/directory.rb:18:in `refresh'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/backends/polling.rb:17:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/backends/polling.rb:17:in `each'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/backends/polling.rb:17:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/backends/polling.rb:15:in `loop'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/backends/polling.rb:15:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm/monitor.rb:26:in `run'
|
||||
/Library/Ruby/Gems/1.8/gems/fssm-0.2.9/lib/fssm.rb:70:in `monitor'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/commands/watch_project.rb:87:in `perform'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/commands/base.rb:18:in `execute'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/commands/project_base.rb:19:in `execute'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/exec/sub_command_ui.rb:43:in `perform!'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/lib/compass/exec/sub_command_ui.rb:15:in `run!'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/bin/compass:30
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/bin/compass:44:in `call'
|
||||
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/bin/compass:44
|
||||
/usr/bin/compass:23:in `load'
|
||||
/usr/bin/compass:23
|
||||
*/
|
||||
body:before {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
content: "Syntax error: Undefined variable: \"$colorBodyBg\".\A on line 66 of /Users/iMac/dev/nasa/wtd-dev/platform/sass/_effects.scss, in `boxOutline'\A from line 7 of /Users/iMac/dev/nasa/wtd-dev/platform/sass/_effects.scss\A from line 10 of /Users/iMac/dev/nasa/wtd-dev/platform/sass/main.scss"; }
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user