mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
906 Commits
feature/as
...
v0.12.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c82f87cd76 | ||
|
|
51d8a6d9bf | ||
|
|
d334aa2088 | ||
|
|
710e003bdc | ||
|
|
b2f5b4f861 | ||
|
|
0ac87930c8 | ||
|
|
241a482236 | ||
|
|
2abaffafcf | ||
|
|
4545fedf31 | ||
|
|
a47a690a68 | ||
|
|
f89c44e899 | ||
|
|
59b0df5c82 | ||
|
|
5ec6ffb30a | ||
|
|
5956d2009c | ||
|
|
d9c7f21c02 | ||
|
|
926e508b8d | ||
|
|
ac83772945 | ||
|
|
cddf5cf70f | ||
|
|
d53acdb46a | ||
|
|
cfae8f4fc6 | ||
|
|
74cd4cecbf | ||
|
|
3e9e6a1389 | ||
|
|
9788450c08 | ||
|
|
10b27aed34 | ||
|
|
64f95be828 | ||
|
|
a54634023b | ||
|
|
9d942b78ef | ||
|
|
4cd5357241 | ||
|
|
f985a96988 | ||
|
|
0e3938da79 | ||
|
|
ec9bfc4731 | ||
|
|
9b91ebb8d2 | ||
|
|
da3f2367d7 | ||
|
|
17cdeec34b | ||
|
|
3446afd087 | ||
|
|
b12fef652c | ||
|
|
21c7193281 | ||
|
|
a5e64274a2 | ||
|
|
3817202875 | ||
|
|
874fcb12a1 | ||
|
|
e0c5783703 | ||
|
|
a57e037b05 | ||
|
|
8546918cbb | ||
|
|
82284029f2 | ||
|
|
7c20e865a5 | ||
|
|
79267d4e12 | ||
|
|
50aeb70597 | ||
|
|
1d22a79074 | ||
|
|
7f442f4206 | ||
|
|
985326989c | ||
|
|
be8f2afa37 | ||
|
|
98882984b4 | ||
|
|
a6cd0fdb85 | ||
|
|
7bc5ba7e9a | ||
|
|
37e552cd36 | ||
|
|
51e2a4de7d | ||
|
|
91ce2fcb06 | ||
|
|
925a379702 | ||
|
|
3153cfd0ff | ||
|
|
ac8831b4c7 | ||
|
|
acc535e1a4 | ||
|
|
fdacb4fe7d | ||
|
|
fc7208469d | ||
|
|
5c38cb733a | ||
|
|
515a67a320 | ||
|
|
941348f1db | ||
|
|
8d7752b0bc | ||
|
|
15af660424 | ||
|
|
790555ae89 | ||
|
|
3cc4df4e29 | ||
|
|
395d1cee70 | ||
|
|
89bc7efbca | ||
|
|
8f893a9752 | ||
|
|
54e02e412c | ||
|
|
808d7aab3f | ||
|
|
24a8f8f38b | ||
|
|
90c00bed2f | ||
|
|
054c911a1f | ||
|
|
c2d5432a5d | ||
|
|
dd64b70f5b | ||
|
|
a69dbeb10f | ||
|
|
976a768446 | ||
|
|
5612cec91f | ||
|
|
46996eb81c | ||
|
|
12990f9fb2 | ||
|
|
77ff988232 | ||
|
|
8df98c29b8 | ||
|
|
7554f8395b | ||
|
|
6fc0bac106 | ||
|
|
d252feddc9 | ||
|
|
8c2645498d | ||
|
|
528acc4aa4 | ||
|
|
2ce45eab06 | ||
|
|
074c24aa10 | ||
|
|
79e4007732 | ||
|
|
742ce6673c | ||
|
|
4a74f588b9 | ||
|
|
f876dc066c | ||
|
|
65aaf386c2 | ||
|
|
4eae6bd362 | ||
|
|
4f7cfd6bd4 | ||
|
|
1136ad09ee | ||
|
|
4a67bd945c | ||
|
|
a2841f7cf2 | ||
|
|
4fa6e9dafe | ||
|
|
7ebd7959f9 | ||
|
|
4386015cff | ||
|
|
5cd014f7e6 | ||
|
|
30d0bfbdf0 | ||
|
|
48cfeca220 | ||
|
|
5fefc48a0b | ||
|
|
42f2ae16ec | ||
|
|
3d47b0201f | ||
|
|
7607b1215f | ||
|
|
eae2b40898 | ||
|
|
8e253046d8 | ||
|
|
b0e17f02b5 | ||
|
|
3f3c131737 | ||
|
|
2b227fcca5 | ||
|
|
639e55b537 | ||
|
|
8386404b25 | ||
|
|
a093afb630 | ||
|
|
7208688128 | ||
|
|
b94d406bd9 | ||
|
|
79188b7d62 | ||
|
|
85b2fc503d | ||
|
|
57f33109b2 | ||
|
|
d039547886 | ||
|
|
55621b7826 | ||
|
|
854730f258 | ||
|
|
9c4f73f314 | ||
|
|
e1fa491af7 | ||
|
|
b909a3e05c | ||
|
|
4159c539e7 | ||
|
|
d1bd232ec7 | ||
|
|
a47898a2c4 | ||
|
|
06b69e516a | ||
|
|
312c89aaee | ||
|
|
a5b1f020ae | ||
|
|
e9cd1bef43 | ||
|
|
7b17ce5de1 | ||
|
|
ffb1b06bf4 | ||
|
|
21e9ffec97 | ||
|
|
7cfaa2adfc | ||
|
|
4e80e35976 | ||
|
|
e9f78f6ace | ||
|
|
4d074c6fa9 | ||
|
|
0b3c2e198e | ||
|
|
48cd7200cc | ||
|
|
d925702b98 | ||
|
|
55cbd72a47 | ||
|
|
fbf1ba172f | ||
|
|
abb722f405 | ||
|
|
2c121dc2c3 | ||
|
|
dbcdbda2ef | ||
|
|
03784ba82e | ||
|
|
04768ad8fa | ||
|
|
29943408e5 | ||
|
|
dbadc05bef | ||
|
|
2e6347b380 | ||
|
|
6c65b1ffde | ||
|
|
358416687f | ||
|
|
1dc061e1c6 | ||
|
|
e7f2572f42 | ||
|
|
463ee7e027 | ||
|
|
667bc95d02 | ||
|
|
3f8cc71814 | ||
|
|
a9a6e96a6a | ||
|
|
02caf05aaf | ||
|
|
da88678a43 | ||
|
|
1e8a9de60a | ||
|
|
1a6181cb15 | ||
|
|
ece69342b8 | ||
|
|
fe601d631b | ||
|
|
42d2e5aaef | ||
|
|
bac7ab7f01 | ||
|
|
c6ac1474d4 | ||
|
|
5a9c4ad8f3 | ||
|
|
babc59f85c | ||
|
|
982a4361cd | ||
|
|
15e53ca55e | ||
|
|
ceb428b8bd | ||
|
|
b7fe3ed745 | ||
|
|
817b614e61 | ||
|
|
3321e95b54 | ||
|
|
fd5f536d14 | ||
|
|
fdbcb32be7 | ||
|
|
a774cee1d9 | ||
|
|
1c46f227d2 | ||
|
|
6191c96347 | ||
|
|
12918fdfee | ||
|
|
8cea246fc8 | ||
|
|
41455a008b | ||
|
|
be22d5c95d | ||
|
|
b298a62e46 | ||
|
|
839045ed66 | ||
|
|
a4a5c61fef | ||
|
|
dd6aa9e4cf | ||
|
|
e299b51779 | ||
|
|
d3f0129c3f | ||
|
|
ae938c66cc | ||
|
|
8d662f4f89 | ||
|
|
a1caa41fe9 | ||
|
|
6028263681 | ||
|
|
30f9291496 | ||
|
|
2400a44ded | ||
|
|
952556a9d1 | ||
|
|
2e18b5c64b | ||
|
|
cb499c6105 | ||
|
|
7f87bcddc1 | ||
|
|
6798917bc9 | ||
|
|
cf3f32ac37 | ||
|
|
a41e1dafc2 | ||
|
|
3ac5124e5c | ||
|
|
83a472794b | ||
|
|
af78d62b76 | ||
|
|
27a0708909 | ||
|
|
5ab58f86ba | ||
|
|
2c0bfdccf9 | ||
|
|
581618370b | ||
|
|
bdf217c45a | ||
|
|
42d6594159 | ||
|
|
fac1750b63 | ||
|
|
6907d192f5 | ||
|
|
0380f36489 | ||
|
|
1524dc2680 | ||
|
|
c95cc0a52b | ||
|
|
a876c5a888 | ||
|
|
bdd925886a | ||
|
|
4d3d292add | ||
|
|
48f03e79e2 | ||
|
|
e3af622a36 | ||
|
|
dcb300ab50 | ||
|
|
95ffd46a63 | ||
|
|
6f5d4a8620 | ||
|
|
62855cc969 | ||
|
|
bdfe9dfab2 | ||
|
|
465ddf1a01 | ||
|
|
18a4ac1653 | ||
|
|
76d55144c5 | ||
|
|
665590a2f9 | ||
|
|
81aa9a31c6 | ||
|
|
082490708f | ||
|
|
20ca4f8260 | ||
|
|
eda057eb07 | ||
|
|
aed31c0eba | ||
|
|
ea585458c7 | ||
|
|
e226e1c045 | ||
|
|
ef330dd613 | ||
|
|
1225560ccd | ||
|
|
eef184e6ef | ||
|
|
50337eb731 | ||
|
|
77f5e7d581 | ||
|
|
02118bac76 | ||
|
|
8b41be238a | ||
|
|
8791e70c67 | ||
|
|
34a4b8f2d2 | ||
|
|
54c936b010 | ||
|
|
ed8d95e055 | ||
|
|
25e8d080af | ||
|
|
4599a7ab4e | ||
|
|
5a106e0e6d | ||
|
|
e4a211ba02 | ||
|
|
7ab373c942 | ||
|
|
18f1ebf715 | ||
|
|
2c02f44b26 | ||
|
|
8ae8168252 | ||
|
|
c504f0b166 | ||
|
|
23da559d26 | ||
|
|
8b3ca12658 | ||
|
|
0f70b5662c | ||
|
|
475541cac0 | ||
|
|
007d6ad9c3 | ||
|
|
79db8d91ab | ||
|
|
a0c6feb386 | ||
|
|
1c72601123 | ||
|
|
c6bcdd1ae1 | ||
|
|
e0acdba626 | ||
|
|
a73e72f267 | ||
|
|
ee6682e7c8 | ||
|
|
6c36ea5753 | ||
|
|
3f798f1843 | ||
|
|
4a806db8ae | ||
|
|
9c12125512 | ||
|
|
51130793fe | ||
|
|
d39c93ee7e | ||
|
|
e1cfb29fa0 | ||
|
|
4dde16d836 | ||
|
|
1c57ef5931 | ||
|
|
0f642fe3ad | ||
|
|
46844b516a | ||
|
|
b9cef228d9 | ||
|
|
fd71d04000 | ||
|
|
bedb811621 | ||
|
|
173736fd69 | ||
|
|
21eea27c4a | ||
|
|
b39f04fbc3 | ||
|
|
04f898be21 | ||
|
|
defedda891 | ||
|
|
bb2c8e5fd2 | ||
|
|
7410bb9e9a | ||
|
|
fd3907596b | ||
|
|
221a233ea4 | ||
|
|
389adda8ec | ||
|
|
835b65740b | ||
|
|
b24abffd0d | ||
|
|
2ddf578f07 | ||
|
|
61b83c3e34 | ||
|
|
3454ddbb2a | ||
|
|
194e4ca70d | ||
|
|
074981a709 | ||
|
|
f157200a9f | ||
|
|
f70fcddaad | ||
|
|
aa2129b60e | ||
|
|
3794541828 | ||
|
|
b1a6dc65ba | ||
|
|
040ae293fb | ||
|
|
b74e93f3a4 | ||
|
|
dd3cc29af1 | ||
|
|
72b9a93088 | ||
|
|
4446f0a1f3 | ||
|
|
35802c12f6 | ||
|
|
f5fd30883a | ||
|
|
f8e720d6dc | ||
|
|
9f8f04caec | ||
|
|
69aec85882 | ||
|
|
e84012ec38 | ||
|
|
128df6a2bf | ||
|
|
7b76951461 | ||
|
|
38d87450e6 | ||
|
|
b9c6b84ff5 | ||
|
|
5ba1d57b1f | ||
|
|
a4d567e44d | ||
|
|
19a62ba7e9 | ||
|
|
65d6c7b558 | ||
|
|
8c828e7881 | ||
|
|
b711780688 | ||
|
|
855cdc8eda | ||
|
|
c930cce914 | ||
|
|
424696cf96 | ||
|
|
ea4ef45289 | ||
|
|
d2e13e6ac6 | ||
|
|
70ff6658eb | ||
|
|
3e1732b9c0 | ||
|
|
4eb18dc797 | ||
|
|
6e7adfadb0 | ||
|
|
a866224f7b | ||
|
|
77c50d6880 | ||
|
|
4290e2e7b0 | ||
|
|
4e8632de73 | ||
|
|
2792fe82a3 | ||
|
|
ab6df9f8a9 | ||
|
|
425489c74b | ||
|
|
9f04517801 | ||
|
|
9954639096 | ||
|
|
cd78364c17 | ||
|
|
563b986591 | ||
|
|
fe5552773d | ||
|
|
b7acaf9519 | ||
|
|
deffc90531 | ||
|
|
f54a6e8513 | ||
|
|
ab4dc641af | ||
|
|
dfe725dd78 | ||
|
|
dbe1e24561 | ||
|
|
075557929a | ||
|
|
d994db9c74 | ||
|
|
c462c61ac9 | ||
|
|
1ef9719059 | ||
|
|
3df0e7899c | ||
|
|
927972d889 | ||
|
|
00f409d21d | ||
|
|
6aea95b486 | ||
|
|
e4dd78b9a4 | ||
|
|
d940d5a597 | ||
|
|
5f1ea21be1 | ||
|
|
278851224d | ||
|
|
0161bc5a4e | ||
|
|
9cf5168d3f | ||
|
|
f066a4bdf4 | ||
|
|
0009756ed9 | ||
|
|
481e5437f1 | ||
|
|
238f54e011 | ||
|
|
180525f538 | ||
|
|
9a1958d19a | ||
|
|
e1469a1e44 | ||
|
|
85bde2b00d | ||
|
|
b20a15b4ed | ||
|
|
a67161f521 | ||
|
|
8d621e87a0 | ||
|
|
a560034614 | ||
|
|
b329c1f94c | ||
|
|
06459c0f52 | ||
|
|
2655c2511d | ||
|
|
fa9fdb01b0 | ||
|
|
4896948d38 | ||
|
|
133a37817e | ||
|
|
0cdee5ed7a | ||
|
|
2c21989b47 | ||
|
|
17f796847d | ||
|
|
02578dbf3a | ||
|
|
9f86e84830 | ||
|
|
1ae65bb999 | ||
|
|
ca72f5fa99 | ||
|
|
bc9b537d84 | ||
|
|
d9336d0ba2 | ||
|
|
eb537eaa24 | ||
|
|
f1d814ba04 | ||
|
|
760d360e96 | ||
|
|
5431fa1405 | ||
|
|
eb809e388b | ||
|
|
0205ad7a4f | ||
|
|
9599b14aa8 | ||
|
|
d72585e3d1 | ||
|
|
735a15d7c6 | ||
|
|
0984e6ed90 | ||
|
|
6004a4250a | ||
|
|
b39fcc75d0 | ||
|
|
a401553c13 | ||
|
|
a312abff31 | ||
|
|
92c1722469 | ||
|
|
e48136d31e | ||
|
|
b9d6f6fd9c | ||
|
|
66c8dfe44d | ||
|
|
5910c936b9 | ||
|
|
a2645061fe | ||
|
|
7b887d2fd5 | ||
|
|
8d2367ed82 | ||
|
|
075583f4be | ||
|
|
ce86e0a54a | ||
|
|
9308f8ea69 | ||
|
|
cc8b425adc | ||
|
|
25cf7955c9 | ||
|
|
d393865efc | ||
|
|
9d877320d8 | ||
|
|
1f72bef393 | ||
|
|
99c1069229 | ||
|
|
28718429e3 | ||
|
|
e145021760 | ||
|
|
b7dd51c594 | ||
|
|
9ebc3e5eec | ||
|
|
4db60d7b27 | ||
|
|
5d85199ae3 | ||
|
|
088c9e5450 | ||
|
|
bd51d1b2a2 | ||
|
|
0de7b6f63a | ||
|
|
85706eac84 | ||
|
|
96b646b94a | ||
|
|
753f6fc671 | ||
|
|
3f186747ed | ||
|
|
e6748c0793 | ||
|
|
2383796c49 | ||
|
|
fb38ab4ef2 | ||
|
|
1242a62bca | ||
|
|
55ceb12277 | ||
|
|
6dd4e7676d | ||
|
|
4abb58b239 | ||
|
|
385dab6036 | ||
|
|
2e9291decf | ||
|
|
623036f03b | ||
|
|
98075e9d9d | ||
|
|
b80ce0e222 | ||
|
|
b0162efb1f | ||
|
|
7b3e9ab031 | ||
|
|
b35e866d95 | ||
|
|
2d273d1550 | ||
|
|
6aef3eb879 | ||
|
|
61b0393657 | ||
|
|
30bf6c0714 | ||
|
|
fc64b115d3 | ||
|
|
bdc3f24ca6 | ||
|
|
b28300b184 | ||
|
|
5c1f9d03e4 | ||
|
|
ed368a225d | ||
|
|
2f17f392d5 | ||
|
|
b00a6e526e | ||
|
|
75031df45b | ||
|
|
39148a99d0 | ||
|
|
d72f5ee2ad | ||
|
|
c6ea459a68 | ||
|
|
e8525e5513 | ||
|
|
9046b85ca4 | ||
|
|
19257b76d2 | ||
|
|
fd8990b294 | ||
|
|
eb34f5e058 | ||
|
|
f8ced7e065 | ||
|
|
bf0a2d3c6c | ||
|
|
f5878326f7 | ||
|
|
1f0b439168 | ||
|
|
3464e11152 | ||
|
|
b5f99be635 | ||
|
|
3153b35c2a | ||
|
|
c450f9abc2 | ||
|
|
912434b259 | ||
|
|
5f61fc658a | ||
|
|
a268b74636 | ||
|
|
82a1bf7afd | ||
|
|
4392676667 | ||
|
|
c47ec2c3c0 | ||
|
|
e756fc8daa | ||
|
|
4629262a1a | ||
|
|
741aec198b | ||
|
|
c3e5b4fa36 | ||
|
|
d022907bcd | ||
|
|
8a7603a45c | ||
|
|
2ade82d883 | ||
|
|
3d8c38b558 | ||
|
|
0f2d0a33b4 | ||
|
|
d4ce1499cc | ||
|
|
9ab7895772 | ||
|
|
ee9f79e287 | ||
|
|
ad61dafb9a | ||
|
|
b81361ff6d | ||
|
|
52417c0a60 | ||
|
|
000beeb737 | ||
|
|
7cce94000b | ||
|
|
7a4bdb4191 | ||
|
|
279a8ff989 | ||
|
|
ed06a05ff0 | ||
|
|
a56ac24cc2 | ||
|
|
c8efd31d08 | ||
|
|
d8875151e8 | ||
|
|
6082431161 | ||
|
|
846c23a5a2 | ||
|
|
9a5faa92c4 | ||
|
|
f149be26ce | ||
|
|
c6d6ca5a5c | ||
|
|
98dc8069e3 | ||
|
|
32f5d68e51 | ||
|
|
855347a5d3 | ||
|
|
78c22cb74a | ||
|
|
8626a3efea | ||
|
|
d7e0d64ac2 | ||
|
|
e26b1e54c8 | ||
|
|
594024472d | ||
|
|
5f60d09d0b | ||
|
|
a05abda25c | ||
|
|
daa8e44180 | ||
|
|
837a26daee | ||
|
|
1223d3a2b3 | ||
|
|
a4d7eaf709 | ||
|
|
4b12d5527b | ||
|
|
e7553d6f15 | ||
|
|
b795d7de4e | ||
|
|
e732afd64c | ||
|
|
40b25e3826 | ||
|
|
b4d80b467a | ||
|
|
62ef377dd1 | ||
|
|
b85b1e95b4 | ||
|
|
f2ab7001d2 | ||
|
|
7fa6a2e9ef | ||
|
|
c499bbaa9f | ||
|
|
f2b16c414f | ||
|
|
2a73e28b19 | ||
|
|
9252d1290c | ||
|
|
049bd85b29 | ||
|
|
f95a0fa1d3 | ||
|
|
416466a0e1 | ||
|
|
bfd97c5484 | ||
|
|
9c1badd6aa | ||
|
|
929cb29c5b | ||
|
|
1e788962ef | ||
|
|
26f8c11526 | ||
|
|
a9b3922061 | ||
|
|
00ebe4fab2 | ||
|
|
1bee6aece7 | ||
|
|
be4eff413d | ||
|
|
e56571840b | ||
|
|
f931222256 | ||
|
|
25ff7d430d | ||
|
|
d9cf3ceb9b | ||
|
|
c37a84ab4d | ||
|
|
cf97fc9b95 | ||
|
|
03a8651cfd | ||
|
|
b206525f8e | ||
|
|
443850b778 | ||
|
|
6962fbf6f7 | ||
|
|
a2a7173e30 | ||
|
|
698ad2b890 | ||
|
|
dd43a82042 | ||
|
|
a25515dc2b | ||
|
|
e1a8e119bf | ||
|
|
4712b72019 | ||
|
|
2d162b0d4e | ||
|
|
1ea4b4d0b5 | ||
|
|
c75c7019a8 | ||
|
|
67ab5fbc8a | ||
|
|
31a5bee228 | ||
|
|
bcd7195bf5 | ||
|
|
322a9d397a | ||
|
|
71e6646ea0 | ||
|
|
b505507c35 | ||
|
|
76535c1da5 | ||
|
|
9edd94e1c0 | ||
|
|
32449b3c55 | ||
|
|
76c3d38e11 | ||
|
|
0e8ed7d770 | ||
|
|
5af4743f2f | ||
|
|
ab86232294 | ||
|
|
0202daf551 | ||
|
|
3b062de156 | ||
|
|
65786c4d41 | ||
|
|
d3bb742308 | ||
|
|
f98aa3f12b | ||
|
|
70881bb367 | ||
|
|
50adb5fdf5 | ||
|
|
c05701fc19 | ||
|
|
5e68b5f3e3 | ||
|
|
5f46d97409 | ||
|
|
7f07e47488 | ||
|
|
3999f4d3a5 | ||
|
|
b3d6220b01 | ||
|
|
7d88775b5a | ||
|
|
2ca5d3e0df | ||
|
|
3f34030a12 | ||
|
|
11dfa3d9aa | ||
|
|
6923a11038 | ||
|
|
33d78fcf29 | ||
|
|
2b3cfdf18b | ||
|
|
aa7a3f7013 | ||
|
|
1e1b3c8f5f | ||
|
|
a7cac36974 | ||
|
|
3af800e522 | ||
|
|
cf135dd658 | ||
|
|
4e9d113896 | ||
|
|
af8e81d208 | ||
|
|
83fa1ea4a6 | ||
|
|
a72f8b7b65 | ||
|
|
1b940a3117 | ||
|
|
5012831e7b | ||
|
|
85fb08e9a5 | ||
|
|
7ac84b6a91 | ||
|
|
3a72526016 | ||
|
|
c3e78f41b0 | ||
|
|
fdea9dddee | ||
|
|
2d3dd7d91d | ||
|
|
2487245e94 | ||
|
|
a4f12691ce | ||
|
|
e7e83eb8bb | ||
|
|
e8ffb68c08 | ||
|
|
283f69cb65 | ||
|
|
0fb4ab2dcf | ||
|
|
063ac989be | ||
|
|
82fb11a7b5 | ||
|
|
d238d61906 | ||
|
|
22fdc90159 | ||
|
|
b6cf4c4375 | ||
|
|
8aa32fff34 | ||
|
|
76d6ffea4a | ||
|
|
ea8ca8ea1e | ||
|
|
ed9ca04c58 | ||
|
|
093fbaa178 | ||
|
|
5f4c8cf176 | ||
|
|
c7ee37804c | ||
|
|
e0c31aa5af | ||
|
|
ab1494777d | ||
|
|
c01fafb605 | ||
|
|
5be2ffc0b2 | ||
|
|
3ede9396da | ||
|
|
e400a7a15e | ||
|
|
2939006a9a | ||
|
|
df975a0b6b | ||
|
|
bda86de632 | ||
|
|
5738a4ba48 | ||
|
|
38cfa46131 | ||
|
|
9525df1381 | ||
|
|
d393577ba8 | ||
|
|
f7c2a07b70 | ||
|
|
adf69b4890 | ||
|
|
593d22b640 | ||
|
|
7706eebafb | ||
|
|
91bd5127fb | ||
|
|
b87a0d7f25 | ||
|
|
0f431ed384 | ||
|
|
d520921b13 | ||
|
|
8d4b9076f7 | ||
|
|
93ebbcaf04 | ||
|
|
ecde1580fd | ||
|
|
8f73bb222c | ||
|
|
1108e04eff | ||
|
|
0f1a8f3358 | ||
|
|
77de4df0ff | ||
|
|
0564de37ee | ||
|
|
8c584ae0e0 | ||
|
|
30c80279c8 | ||
|
|
3f0ab9a88a | ||
|
|
6da3fcf446 | ||
|
|
26746ca303 | ||
|
|
64158eac43 | ||
|
|
9e3a7f5eda | ||
|
|
0a4f3db8ae | ||
|
|
c095085d84 | ||
|
|
41cc4e68e3 | ||
|
|
21fb7959a5 | ||
|
|
3aa22127e0 | ||
|
|
a679a37ffa | ||
|
|
649857ec28 | ||
|
|
4a6136b918 | ||
|
|
6e1c468a80 | ||
|
|
cadcd0c5e8 | ||
|
|
a531c306f1 | ||
|
|
b0a9449335 | ||
|
|
b6deb87cae | ||
|
|
7319c88674 | ||
|
|
d36317c563 | ||
|
|
1917202bd0 | ||
|
|
a8338912e0 | ||
|
|
bfea3201e8 | ||
|
|
a9bb440e6c | ||
|
|
f0942d58e8 | ||
|
|
aa093659a0 | ||
|
|
cccdbc05a5 | ||
|
|
dfa32b7be4 | ||
|
|
c7e0bd037a | ||
|
|
6c711b76b0 | ||
|
|
9c914f10c4 | ||
|
|
eda56d118a | ||
|
|
02c7351c6d | ||
|
|
ab618235f1 | ||
|
|
e4239c924b | ||
|
|
ffead9ed70 | ||
|
|
36aefadced | ||
|
|
75ccfe38ce | ||
|
|
e3cb3fe2e4 | ||
|
|
983e7e9b75 | ||
|
|
3db47c076c | ||
|
|
eeff285b33 | ||
|
|
029595f8ea | ||
|
|
ea2ec27724 | ||
|
|
f6bf4a416f | ||
|
|
af978a68e3 | ||
|
|
89dc1323e1 | ||
|
|
a4b5f63deb | ||
|
|
feaa6ccff4 | ||
|
|
7159293337 | ||
|
|
4a5b31e3a7 | ||
|
|
6f1dc89fb3 | ||
|
|
29dd405fe5 | ||
|
|
0f0b0cd3d8 | ||
|
|
262528e36a | ||
|
|
597e86dd57 | ||
|
|
b604dba948 | ||
|
|
1837a64bd2 | ||
|
|
9b413de4d8 | ||
|
|
3d77cbd677 | ||
|
|
62176c3218 | ||
|
|
d7a01c32cc | ||
|
|
cc493fd545 | ||
|
|
6b8679454d | ||
|
|
1b68d61e54 | ||
|
|
418de862e6 | ||
|
|
413653858e | ||
|
|
f0886a7556 | ||
|
|
0e2666948f | ||
|
|
d2fc851816 | ||
|
|
e1fb29c8c5 | ||
|
|
fe3158fdd6 | ||
|
|
721b26f80b | ||
|
|
d3ecfb22ee | ||
|
|
27a98020c9 | ||
|
|
ab56b72f39 | ||
|
|
8063f66958 | ||
|
|
bf270b9adb | ||
|
|
972db08740 | ||
|
|
6326c7cbaa | ||
|
|
4152ace514 | ||
|
|
038221408c | ||
|
|
9f76def7ce | ||
|
|
1b83770c5c | ||
|
|
3458d924ca | ||
|
|
0749b9500c | ||
|
|
b1dfc18a8c | ||
|
|
7b25c282c0 | ||
|
|
a128ceaf2d | ||
|
|
f266cab580 | ||
|
|
23bf9aaf17 | ||
|
|
1983f60ec6 | ||
|
|
27f8909406 | ||
|
|
9988206911 | ||
|
|
31fe1fdfa6 | ||
|
|
77b125ce2d | ||
|
|
6e68e07aa2 | ||
|
|
86bb010a93 | ||
|
|
4a623b596b | ||
|
|
bcf098ea7d | ||
|
|
4bfb226fb5 | ||
|
|
691615108b | ||
|
|
858ab00e36 | ||
|
|
7023f5b145 | ||
|
|
eb4fabeac6 | ||
|
|
a5e09f9ce4 | ||
|
|
c2fe4e8b6d | ||
|
|
5d22648d34 | ||
|
|
066fd15184 | ||
|
|
fe90c230d5 | ||
|
|
0b5ae92136 | ||
|
|
1c5565aaee | ||
|
|
69c177a3ec | ||
|
|
0645b3f65b | ||
|
|
9e7471fcc0 | ||
|
|
c520f53799 | ||
|
|
0bf1386802 | ||
|
|
b2ab3797aa | ||
|
|
ede0ca8bd1 | ||
|
|
81e35f0cc3 | ||
|
|
237522a1f7 | ||
|
|
2f94e1d2c9 | ||
|
|
2689dd32bb | ||
|
|
ad5e703b8f | ||
|
|
d3bc2e9279 | ||
|
|
0cd1644cf1 | ||
|
|
f02b405c12 | ||
|
|
baa7036799 | ||
|
|
431aecaf00 | ||
|
|
f31bb56ea6 | ||
|
|
cf3b805c46 | ||
|
|
517283ca58 | ||
|
|
f416b7ba47 | ||
|
|
973190b7a1 | ||
|
|
f536a9d3d3 | ||
|
|
1348ec3bcf | ||
|
|
9a250a4861 | ||
|
|
6450927192 | ||
|
|
7c9dbdc802 | ||
|
|
8d460afe2d | ||
|
|
6c44c2cf24 | ||
|
|
cea550ebba | ||
|
|
911a352ee6 | ||
|
|
3fadfbe06e | ||
|
|
5bf362927f | ||
|
|
4da5ca5ba9 | ||
|
|
d747005b30 | ||
|
|
03a395107d | ||
|
|
58ef4ccabf | ||
|
|
71ed082bb5 | ||
|
|
0819ac8124 | ||
|
|
0cdd223172 | ||
|
|
571393f146 | ||
|
|
c85868c652 | ||
|
|
a7a6f3b020 | ||
|
|
3a0a11d55a | ||
|
|
7eb8ddf372 | ||
|
|
87af63644a | ||
|
|
0a9dd18070 | ||
|
|
f82b061ba7 | ||
|
|
c17509e2a0 | ||
|
|
cb383d4f62 | ||
|
|
451f950d0d | ||
|
|
bd0eae0961 | ||
|
|
53a401f847 | ||
|
|
b288f5ca19 | ||
|
|
7a2fc19c4f | ||
|
|
046d712d6a | ||
|
|
e829aaecf1 | ||
|
|
9ab2f5338e | ||
|
|
d7bda924be | ||
|
|
07eb2e51b7 | ||
|
|
dfafa7ae40 | ||
|
|
dde266768c | ||
|
|
01c81675f7 | ||
|
|
71972eb362 | ||
|
|
eb9f5f9025 | ||
|
|
eb4d4d7437 | ||
|
|
1cb5e09109 | ||
|
|
cc82fff5d3 | ||
|
|
3212e59dcc | ||
|
|
44a795bf18 | ||
|
|
376e6f35a2 | ||
|
|
3b324e9532 | ||
|
|
a0df8f3490 | ||
|
|
6c14789362 | ||
|
|
880a12b914 | ||
|
|
93d69400e6 | ||
|
|
d4829e49ea | ||
|
|
1c56be3a6b | ||
|
|
07a0dfddc7 | ||
|
|
b86f9086ef | ||
|
|
343ca12c6f | ||
|
|
af3c4f84b6 | ||
|
|
3679e8795f | ||
|
|
39b4805a76 | ||
|
|
3bdcdf96d4 | ||
|
|
b54e5d33bc | ||
|
|
85e020a513 | ||
|
|
5b6268f5bc | ||
|
|
063b58eebb | ||
|
|
f8b38e4683 | ||
|
|
18e85c32b4 | ||
|
|
831fba9a53 | ||
|
|
b1f233cd8c | ||
|
|
3d0caba695 | ||
|
|
79c92f1f8e | ||
|
|
87f26e47bb | ||
|
|
9d8d04ae28 | ||
|
|
a42f046ff8 | ||
|
|
01c24a578b | ||
|
|
6b82354129 | ||
|
|
b00fbda1ae | ||
|
|
bab200ff03 | ||
|
|
357e81aeca | ||
|
|
3189c748b5 | ||
|
|
2700643cbf | ||
|
|
b0f95cd9e0 | ||
|
|
f5fa7d6d4b | ||
|
|
77bff6e6c2 | ||
|
|
49a08d14c3 | ||
|
|
25990f59d8 |
@@ -10,3 +10,7 @@
|
||||
^man-roxygen$
|
||||
^\.travis\.yml$
|
||||
^staticdocs$
|
||||
^tools$
|
||||
^srcjs$
|
||||
^CONTRIBUTING.md$
|
||||
^cran-comments.md$
|
||||
|
||||
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/NEWS merge=union
|
||||
/inst/www/shared/shiny.js -merge -diff
|
||||
*.min.js -merge -diff
|
||||
*.js.map -merge -diff
|
||||
35
.travis.yml
35
.travis.yml
@@ -1,27 +1,12 @@
|
||||
# it is not really python, but there is no R support on Travis CI yet
|
||||
language: python
|
||||
language: r
|
||||
warnings_are_errors: true
|
||||
|
||||
# environment variables
|
||||
env:
|
||||
- R_LIBS_USER=~/R R_MY_PKG="$(basename $TRAVIS_REPO_SLUG)"
|
||||
r_binary_packages:
|
||||
- Rcpp
|
||||
- cairo
|
||||
- knitr
|
||||
|
||||
# install dependencies
|
||||
install:
|
||||
- sudo apt-add-repository -y "deb http://cran.rstudio.com/bin/linux/ubuntu `lsb_release -cs`/"
|
||||
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
|
||||
- sudo apt-add-repository -y ppa:marutter/c2d4u
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install r-base-dev r-cran-shiny r-cran-cairo r-cran-markdown
|
||||
- "[ ! -d ~/R ] && mkdir ~/R"
|
||||
- Rscript -e "install.packages(c('xtable'), repos = 'http://cran.rstudio.org')"
|
||||
- Rscript -e "install.packages('knitr', repos = c('http://rforge.net', 'http://cran.rstudio.org'))"
|
||||
- Rscript -e "install.packages('$R_MY_PKG', dep = TRUE, repos = 'http://cran.rstudio.org')"
|
||||
|
||||
# run tests
|
||||
script:
|
||||
- cd ..; rm -f *.tar.gz; R CMD build $R_MY_PKG
|
||||
- R CMD check $R_MY_PKG*.tar.gz --no-manual
|
||||
|
||||
after_failure:
|
||||
- cat $R_MY_PKG.Rcheck/00install.out || true
|
||||
- cat $R_MY_PKG.Rcheck/00check.log || true
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: change
|
||||
|
||||
10
CONTRIBUTING.md
Normal file
10
CONTRIBUTING.md
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
We welcome contributions to the **shiny** package. To submit a contribution:
|
||||
|
||||
1. [Fork](https://github.com/rstudio/shiny/fork) the repository and make your changes.
|
||||
|
||||
2. Ensure that you have signed the [individual](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioIndividualContributorAgreement.pdf) or [corporate](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioCorporateContributorAgreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com.
|
||||
|
||||
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
We'll try to be as responsive as possible in reviewing and accepting pull requests. We appreciate your contributions!
|
||||
107
DESCRIPTION
107
DESCRIPTION
@@ -1,64 +1,133 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.9.1.9007
|
||||
Date: 2014-03-19
|
||||
Author: RStudio, Inc.
|
||||
Maintainer: Winston Chang <winston@rstudio.com>
|
||||
Description: Shiny makes it incredibly easy to build interactive web
|
||||
Version: 0.12.2
|
||||
Date: 2015-08-04
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
person("JJ", "Allaire", role = "aut", email = "jj@rstudio.com"),
|
||||
person("Yihui", "Xie", role = "aut", email = "yihui@rstudio.com"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@rstudio.com"),
|
||||
person(family = "RStudio", role = "cph"),
|
||||
person(family = "jQuery Foundation", role = "cph",
|
||||
comment = "jQuery library and jQuery UI library"),
|
||||
person(family = "jQuery contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
|
||||
person(family = "jQuery UI contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/1.10.4/AUTHORS.txt"),
|
||||
person("Mark", "Otto", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person("Jacob", "Thornton", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Bootstrap contributors", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Twitter, Inc", role = "cph",
|
||||
comment = "Bootstrap library"),
|
||||
person("Alexander", "Farkas", role = c("ctb", "cph"),
|
||||
comment = "html5shiv library"),
|
||||
person("Scott", "Jehl", role = c("ctb", "cph"),
|
||||
comment = "Respond.js library"),
|
||||
person("Stefan", "Petre", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Andrew", "Rowls", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Dave", "Gandy", role = c("ctb", "cph"),
|
||||
comment = "Font-Awesome font"),
|
||||
person("Brian", "Reavis", role = c("ctb", "cph"),
|
||||
comment = "selectize.js library"),
|
||||
person("Kristopher Michael", "Kowal", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person(family = "es5-shim contributors", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person("Denis", "Ineshin", role = c("ctb", "cph"),
|
||||
comment = "ion.rangeSlider library"),
|
||||
person("Sami", "Samhuri", role = c("ctb", "cph"),
|
||||
comment = "Javascript strftime library"),
|
||||
person(family = "SpryMedia Limited", role = c("ctb", "cph"),
|
||||
comment = "DataTables library"),
|
||||
person("John", "Fraser", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("John", "Gruber", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
|
||||
comment = "highlight.js library"),
|
||||
person(family = "R Core Team", role = c("ctb", "cph"),
|
||||
comment = "tar implementation from R")
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3
|
||||
License: GPL-3 | file LICENSE
|
||||
Depends:
|
||||
R (>= 2.14.1),
|
||||
R (>= 3.0.0),
|
||||
methods
|
||||
Imports:
|
||||
tools,
|
||||
utils,
|
||||
httpuv (>= 1.2.0),
|
||||
caTools,
|
||||
RJSONIO,
|
||||
httpuv (>= 1.3.3),
|
||||
mime (>= 0.3),
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
digest
|
||||
digest,
|
||||
htmltools (>= 0.2.6),
|
||||
R6 (>= 2.0)
|
||||
Suggests:
|
||||
datasets,
|
||||
markdown,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat,
|
||||
knitr
|
||||
URL: http://www.rstudio.com/shiny/
|
||||
knitr (>= 1.6),
|
||||
markdown,
|
||||
ggplot2
|
||||
URL: http://shiny.rstudio.com
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Roxygen: list(wrap = FALSE)
|
||||
Collate:
|
||||
'app.R'
|
||||
'bootstrap-layout.R'
|
||||
'map.R'
|
||||
'globals.R'
|
||||
'utils.R'
|
||||
'htmltools.R'
|
||||
'bootstrap.R'
|
||||
'cache.R'
|
||||
'fileupload.R'
|
||||
'stack.R'
|
||||
'graph.R'
|
||||
'hooks.R'
|
||||
'html-deps.R'
|
||||
'htmltools.R'
|
||||
'image-interact-opts.R'
|
||||
'image-interact.R'
|
||||
'imageutils.R'
|
||||
'input-action.R'
|
||||
'input-checkbox.R'
|
||||
'input-checkboxgroup.R'
|
||||
'input-date.R'
|
||||
'input-daterange.R'
|
||||
'input-file.R'
|
||||
'input-numeric.R'
|
||||
'input-password.R'
|
||||
'input-radiobuttons.R'
|
||||
'input-select.R'
|
||||
'input-slider.R'
|
||||
'input-submit.R'
|
||||
'input-text.R'
|
||||
'input-utils.R'
|
||||
'jqueryui.R'
|
||||
'middleware-shiny.R'
|
||||
'middleware.R'
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
'react.R'
|
||||
'reactive-domains.R'
|
||||
'reactives.R'
|
||||
'render-plot.R'
|
||||
'run-url.R'
|
||||
'server-input-handlers.R'
|
||||
'server.R'
|
||||
'shiny.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'slider.R'
|
||||
'tags.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
|
||||
38
NAMESPACE
38
NAMESPACE
@@ -1,4 +1,4 @@
|
||||
# Generated by roxygen2 (4.0.0): do not edit by hand
|
||||
# Generated by roxygen2 (4.1.1): do not edit by hand
|
||||
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",shinyoutput)
|
||||
@@ -13,23 +13,18 @@ S3method("[[",shinyoutput)
|
||||
S3method("[[<-",reactivevalues)
|
||||
S3method("[[<-",shinyoutput)
|
||||
S3method("names<-",reactivevalues)
|
||||
S3method(as.character,shiny.tag)
|
||||
S3method(as.character,shiny.tag.list)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(as.shiny.appobj,character)
|
||||
S3method(as.shiny.appobj,list)
|
||||
S3method(as.shiny.appobj,shiny.appobj)
|
||||
S3method(format,html)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(format,shiny.tag.list)
|
||||
S3method(as.tags,shiny.appobj)
|
||||
S3method(as.tags,shiny.render.function)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,html)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.appobj)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(print,shiny.tag.list)
|
||||
S3method(str,reactivevalues)
|
||||
export(HTML)
|
||||
export(Progress)
|
||||
export(a)
|
||||
export(absolutePanel)
|
||||
export(actionButton)
|
||||
@@ -40,19 +35,25 @@ export(as.shiny.appobj)
|
||||
export(basicPage)
|
||||
export(bootstrapPage)
|
||||
export(br)
|
||||
export(brushOpts)
|
||||
export(brushedPoints)
|
||||
export(checkboxGroupInput)
|
||||
export(checkboxInput)
|
||||
export(clickOpts)
|
||||
export(code)
|
||||
export(column)
|
||||
export(conditionalPanel)
|
||||
export(createWebDependency)
|
||||
export(dataTableOutput)
|
||||
export(dateInput)
|
||||
export(dateRangeInput)
|
||||
export(dblclickOpts)
|
||||
export(div)
|
||||
export(downloadButton)
|
||||
export(downloadHandler)
|
||||
export(downloadLink)
|
||||
export(em)
|
||||
export(eventReactive)
|
||||
export(exprToFunction)
|
||||
export(fileInput)
|
||||
export(fixedPage)
|
||||
@@ -62,7 +63,6 @@ export(flowLayout)
|
||||
export(fluidPage)
|
||||
export(fluidRow)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(getProvidedHtmlDependencies)
|
||||
export(h1)
|
||||
export(h2)
|
||||
export(h3)
|
||||
@@ -71,11 +71,13 @@ export(h5)
|
||||
export(h6)
|
||||
export(headerPanel)
|
||||
export(helpText)
|
||||
export(hoverOpts)
|
||||
export(hr)
|
||||
export(htmlOutput)
|
||||
export(icon)
|
||||
export(imageOutput)
|
||||
export(img)
|
||||
export(incProgress)
|
||||
export(includeCSS)
|
||||
export(includeHTML)
|
||||
export(includeMarkdown)
|
||||
@@ -86,7 +88,10 @@ export(installExprFunction)
|
||||
export(invalidateLater)
|
||||
export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(is.shiny.appobj)
|
||||
export(is.singleton)
|
||||
export(isolate)
|
||||
export(knit_print.html)
|
||||
export(knit_print.shiny.appobj)
|
||||
export(knit_print.shiny.render.function)
|
||||
export(knit_print.shiny.tag)
|
||||
@@ -98,14 +103,17 @@ export(maskReactiveContext)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(nearPoints)
|
||||
export(need)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
export(observeEvent)
|
||||
export(onReactiveDomainEnded)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
export(parseQueryString)
|
||||
export(passwordInput)
|
||||
export(plotOutput)
|
||||
export(plotPNG)
|
||||
export(pre)
|
||||
@@ -138,6 +146,8 @@ export(runGitHub)
|
||||
export(runUrl)
|
||||
export(selectInput)
|
||||
export(selectizeInput)
|
||||
export(serverInfo)
|
||||
export(setProgress)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyServer)
|
||||
@@ -170,6 +180,8 @@ export(updateCheckboxGroupInput)
|
||||
export(updateCheckboxInput)
|
||||
export(updateDateInput)
|
||||
export(updateDateRangeInput)
|
||||
export(updateNavbarPage)
|
||||
export(updateNavlistPanel)
|
||||
export(updateNumericInput)
|
||||
export(updateRadioButtons)
|
||||
export(updateSelectInput)
|
||||
@@ -183,11 +195,13 @@ export(verbatimTextOutput)
|
||||
export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withMathJax)
|
||||
export(withProgress)
|
||||
export(withReactiveDomain)
|
||||
export(withTags)
|
||||
import(RJSONIO)
|
||||
import(caTools)
|
||||
import(R6)
|
||||
import(digest)
|
||||
import(htmltools)
|
||||
import(httpuv)
|
||||
import(methods)
|
||||
import(mime)
|
||||
import(xtable)
|
||||
|
||||
312
NEWS
312
NEWS
@@ -1,9 +1,283 @@
|
||||
shiny 0.9.1.9XXX
|
||||
shiny 0.12.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* GitHub changed URLs for gists from .tar.gz to .zip, so `runGist` was updated
|
||||
to work with the new URLs.
|
||||
|
||||
* Callbacks from the session object are now guaranteed to execute in the order
|
||||
in which registration occurred.
|
||||
|
||||
* Minor bugs in sliderInput's animation behavior have been fixed. (#852)
|
||||
|
||||
* Updated to ion.rangeSlider to 2.0.12.
|
||||
|
||||
* Added `shiny.minified` option, which controls whether the minified version
|
||||
of shiny.js is used. Setting it to FALSe can be useful for debugging. (#826,
|
||||
#850)
|
||||
|
||||
* Fixed an issue for outputting plots from ggplot objects which also have an
|
||||
additional class whose print method takes precedence over `print.ggplot`.
|
||||
(#840, 841)
|
||||
|
||||
* Added `width` option to Shiny's input functions. (#589, #834)
|
||||
|
||||
* Added two alias functions of `updateTabsetPanel()` to update the selected tab:
|
||||
`updateNavbarPage()` and `updateNavlistPanel()`. (#881)
|
||||
|
||||
* All non-base functions are now explicitly namespaced, to pass checks in
|
||||
R-devel.
|
||||
|
||||
* Shiny now correctly handles HTTP HEAD requests. (#876)
|
||||
|
||||
shiny 0.12.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed an issue where unbindAll() causes subsequent bindAll() to be ignored for
|
||||
previously bound outputs. (#856)
|
||||
|
||||
* Undeprecate `dataTableOutput` and `renderDataTable`, which had been deprecated
|
||||
in favor of the new DT package. The DT package is a bit too new and has a
|
||||
slightly different API, we were too hasty in deprecating the existing Shiny
|
||||
functions.
|
||||
|
||||
shiny 0.12.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Switched from RJSONIO to jsonlite. This improves consistency and speed when
|
||||
converting between R data structures and JSON. One notable change is that
|
||||
POSIXt objects are now serialized to JSON in UTC8601 format (like
|
||||
"2015-03-20T20:00:00Z"), instead of as seconds from the epoch).
|
||||
|
||||
* In addition to the existing support for clicking and hovering on plots
|
||||
created by base graphics, added support for double-clicking and brushing.
|
||||
(#769)
|
||||
|
||||
* Added support for clicking, hovering, double-clicking, and brushing for
|
||||
plots created by ggplot2, including support for facets. (#802)
|
||||
|
||||
* Added `nearPoints` and `brushedPoints` functions for easily selecting rows of
|
||||
data that are clicked/hovered, or brushed. (#802)
|
||||
|
||||
* Added `shiny.port` option. If this is option is set, `runApp()` will listen on
|
||||
this port by default. (#756)
|
||||
|
||||
* `runUrl`, `runGist`, and `runGitHub` now can save downloaded applications,
|
||||
with the `destdir` argument. (#688)
|
||||
|
||||
* Restored ability to set labels for `selectInput`. (#741)
|
||||
|
||||
* Travis continuous integration now uses Travis's native R support.
|
||||
|
||||
* Fixed encoding issue when the server receives data from the client browser.
|
||||
(#742)
|
||||
|
||||
* The `session` object now has class `ShinySession`, making it easier to test
|
||||
whether an object is indeed a session object. (#720, #746)
|
||||
|
||||
* Fix JavaScript error when an output appears in nested uiOutputs. (Thanks,
|
||||
Gregory Zhang. #749)
|
||||
|
||||
* Eliminate delay on receiving new value when `updateSliderInput(value=...)` is
|
||||
called.
|
||||
|
||||
* Updated to DataTables (Javascript library) 1.10.5.
|
||||
|
||||
* Fixed downloading of files that have no filename extension. (#575, #753)
|
||||
|
||||
* Fixed bug where nested UI outputs broke outputs. (#749, #750)
|
||||
|
||||
* Removed unneeded HTML ID attributes for `checkboxGroupInputs` and
|
||||
`radioButtons`. (#684)
|
||||
|
||||
* Fixed bug where checkboxes were still active even after `Shiny.unbindAll()`
|
||||
was called. (#206)
|
||||
|
||||
* The server side selectize input will load the first 1000 options by default
|
||||
before users start to type and search in the box. (#823)
|
||||
|
||||
* renderDataTable() and dataTableOutput() have been deprecated in shiny and will
|
||||
be removed in future versions of shiny. Please use the DT package instead:
|
||||
http://rstudio.github.io/DT/ (#807)
|
||||
|
||||
shiny 0.11.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Major client-side performance improvements for pages that have many
|
||||
conditionalPanels, tabPanels, and plotOutputs. (#693, #717, #723)
|
||||
|
||||
* `tabPanel`s now use the `title` for `value` by default. This fixes a bug
|
||||
in which an icon in the title caused problems with a conditionalPanel's test
|
||||
condition. (#725, #728)
|
||||
|
||||
* `selectInput` now has a `size` argument to control the height of the input
|
||||
box. (#729)
|
||||
|
||||
* `navbarPage` no longer includes a first row of extra whitespace when
|
||||
`header=NULL`. (#722)
|
||||
|
||||
* `selectInput`s now use Bootstrap styling when `selectize=FALSE`. (#724)
|
||||
|
||||
* Better vertical spacing of label for checkbox groups and radio buttons.
|
||||
|
||||
* `selectInput` correctly uses width for both selectize and non-selectize
|
||||
inputs. (#702)
|
||||
|
||||
* The wrapper tag generated by `htmlOutput` and `uiOutput` can now be any type
|
||||
of HTML tag, instead of just span and div. Also, custom classes are now
|
||||
allowed on the tag. (#704)
|
||||
|
||||
* Slider problems in IE 11 and Chrome on touchscreen-equipped Windows computers
|
||||
have been fixed. (#700)
|
||||
|
||||
* Sliders now work correctly with draggable panels. (#711)
|
||||
|
||||
* Fixed arguments in `fixedPanel`. (#709)
|
||||
|
||||
* downloadHandler content callback functions are now invoked with a temp file
|
||||
name that has the same extension as the final filename that will be used by
|
||||
the download. This is to deal with the fact that some file writing functions
|
||||
in R will auto-append the extension for their file type (pdf, zip).
|
||||
|
||||
shiny 0.11
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changed sliders from jquery-slider to ion.rangeSlider. These sliders have
|
||||
an improved appearance, support updating more properties from the server,
|
||||
and can be controlled with keyboard input.
|
||||
|
||||
* Switched from Bootstrap 2 to Bootstrap 3. For most users, this will work
|
||||
seamlessly, but some users may need to use the shinybootstrap2 package for
|
||||
backward compatibility.
|
||||
|
||||
* The UI of a Shiny app can now have a body tag. This is useful for CSS
|
||||
templates that use classes on the body tag.
|
||||
|
||||
* `actionButton` and `actionLink` now pass their `...` arguments to the
|
||||
underlying tag function. (#607)
|
||||
|
||||
* Added `observeEvent` and `eventReactive` functions for clearer, more concise
|
||||
handling of `actionButton`, plot clicks, and other naturally-imperative
|
||||
inputs.
|
||||
|
||||
* Errors that happen in reactives no longer prevent any remaining pending
|
||||
observers from executing. It is also now possible for users to control how
|
||||
errors are handled, with the 'shiny.observer.error' global option. (#603,
|
||||
#604)
|
||||
|
||||
* Added an `escape` argument to `renderDataTable()` to escape the HTML entities
|
||||
in the data table for security reasons. This might break tables from previous
|
||||
versions of shiny that use raw HTML in the table content, and the old behavior
|
||||
can be brought back by `escape = FALSE` if you are aware of the security
|
||||
implications. (#627)
|
||||
|
||||
* Changed the URI encoding/decoding functions internally to use `encodeURI()`,
|
||||
`encodeURIComponent()`, and `decodeURIComponent()` from the httpuv package
|
||||
instead of `utils::URLencode()` and `utils::URLdecode()`. (#630)
|
||||
|
||||
* Shiny's web assets are now minified.
|
||||
|
||||
* The default reactive domain is now available in event handler functions. (#669)
|
||||
|
||||
* Password input fields can now be used, with `passwordInput()`. (#672)
|
||||
|
||||
shiny 0.10.2.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Remove use of `rstudio::viewer` in a code example, for R CMD check.
|
||||
|
||||
shiny 0.10.2.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changed some examples to use \donttest instead of \dontrun.
|
||||
|
||||
shiny 0.10.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* The minimal version of R required for the shiny package is 3.0.0 now.
|
||||
|
||||
* Shiny apps can now consist of a single file, app.R, instead of ui.R and
|
||||
server.R.
|
||||
|
||||
* Upgraded DataTables from 1.9.4 to 1.10.2. This might be a breaking change if
|
||||
you have customized the DataTables options in your apps. (More info:
|
||||
https://github.com/rstudio/shiny/pull/558)
|
||||
|
||||
* File uploading via `fileInput()` works for Internet Explorer 8 and 9 now. Note
|
||||
IE8/9 do not support multiple files from a single file input. If you need to
|
||||
upload multiple files, you have to use one file input for each file.
|
||||
|
||||
* Switched away from reference classes to R6.
|
||||
|
||||
* Reactive log performance has been greatly improved.
|
||||
|
||||
* Added `Progress` and `withProgress`, to display the progress of computation
|
||||
on the client browser.
|
||||
|
||||
* Fixed #557: updateSelectizeInput(choices, server = TRUE) did not work when
|
||||
`choices` is a character vector.
|
||||
|
||||
* Searching in DataTables is case-insensitive and the search strings are not
|
||||
treated as regular expressions by default now. If you want case-sensitive
|
||||
searching or regular expressions, you can use the configuration options
|
||||
`search$caseInsensitive` and `search$regex`, e.g. `renderDataTable(...,
|
||||
options = list(search = list(caseInsensitve = FALSE, regex = TRUE)))`.
|
||||
|
||||
* Added support for `htmltools::htmlDependency`'s new `attachment` parameter to
|
||||
`renderUI`/`uiOutput`.
|
||||
|
||||
* Exported `createWebDependency`. It takes an `htmltools::htmlDependency` object
|
||||
and makes it available over Shiny's built-in web server.
|
||||
|
||||
* Custom output bindings can now render `htmltools::htmlDependency` objects at
|
||||
runtime using `Shiny.renderDependencies()`.
|
||||
|
||||
* Fixes to rounding behavior of sliderInput. (#301, #502)
|
||||
|
||||
* Updated selectize.js to version 0.11.2. (#596)
|
||||
* Added `position` parameter to `navbarPage`.
|
||||
|
||||
shiny 0.10.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Added Unicode support for Windows. Shiny apps running on Windows must use the
|
||||
UTF-8 encoding for ui.R and server.R (also the optional global.R) if they
|
||||
contain non-ASCII characters. See this article for details and examples:
|
||||
http://shiny.rstudio.com/gallery/unicode-characters.html (#516)
|
||||
|
||||
* `runGitHub()` also allows the 'username/repo' syntax now, which is equivalent
|
||||
to `runGitHub('repo', 'username')`. (#427)
|
||||
|
||||
* `navbarPage()` now accepts a `windowTitle` parameter to set the web browser
|
||||
page title to something other than the title displayed in the navbar.
|
||||
|
||||
* Added an `inline` argument to `textOutput()`, `imageOutput()`, `plotOutput()`,
|
||||
and `htmlOutput()`. When `inline = TRUE`, these outputs will be put in
|
||||
`span()` instead of the default `div()`. This occurs automatically when these
|
||||
outputs are created via the inline expressions (e.g. `r renderText(expr)`) in
|
||||
R Markdown documents. See an R Markdown example at
|
||||
http://shiny.rstudio.com/gallery/inline-output.html (#512)
|
||||
|
||||
* Added support for option groups in the select/selectize inputs. When the
|
||||
`choices` argument for `selectInput()`/`selectizeInput()` is a list of
|
||||
sub-lists and any sub-list is of length greater than 1, the HTML tag
|
||||
`<optgroup>` will be used. See an example at
|
||||
http://shiny.rstudio.com/gallery/option-groups-for-selectize-input.html (#542)
|
||||
|
||||
shiny 0.10.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: By default, observers now terminate themselves if they were
|
||||
created during a session and that session ends. See ?domains for more details.
|
||||
|
||||
* Shiny can now be used in R Markdown v2 documents, to create "Shiny Docs":
|
||||
reports and presentations that combine narrative, statically computed output,
|
||||
and fully dynamic inputs and outputs. For more info, including examples, see
|
||||
http://rmarkdown.rstudio.com/authoring_shiny.html.
|
||||
|
||||
* The `session` object that can be passed into a server function (e.g.
|
||||
shinyServer(function(input, output, session) {...})) is now documented: see
|
||||
`?session`.
|
||||
|
||||
* Most inputs can now accept `NULL` label values to omit the label altogether.
|
||||
|
||||
* New `actionLink` input control; like `actionButton`, but with the appearance
|
||||
@@ -12,20 +286,52 @@ shiny 0.9.1.9XXX
|
||||
* `renderPlot` now calls `print` on its result if it's visible (i.e. no more
|
||||
explicit `print()` required for ggplot2).
|
||||
|
||||
* Introduced Shiny app objects (see `?shinyApp`). These essentially replace the
|
||||
little-advertised ability for `runApp` to take a `list(ui=..., server=...)`
|
||||
as the first argument instead of a directory (though that ability remains for
|
||||
backward compatibility). Unlike those lists, Shiny app objects are tagged with
|
||||
class `shiny.appobj` so they can be run simply by printing them.
|
||||
|
||||
* Added `maskReactiveContext` function. It blocks the current reactive context,
|
||||
to evaluate expressions that shouldn't use reactive sources directly. (This
|
||||
should not be commonly needed.)
|
||||
|
||||
* Added `flowLayout`, `splitLayout`, and `inputPanel` functions for putting UI
|
||||
elements side by side. `flowPanel` lays out its children in a left-to-right,
|
||||
elements side by side. `flowLayout` lays out its children in a left-to-right,
|
||||
top-to-bottom arrangement. `splitLayout` evenly divides its horizontal space
|
||||
among its children (or unevenly divides if `cellWidths` argument is provided).
|
||||
`inputPanel` is like `flowPanel`, but with a light grey background, and is
|
||||
intended to be used to encapsulate small input controls wherever vertical
|
||||
space is at a premium.
|
||||
|
||||
* Added `serverInfo` to obtain info about the Shiny Server if the app is served
|
||||
through it.
|
||||
|
||||
* Added an `inline` argument (TRUE/FALSE) in `checkboxGroupInput()` and
|
||||
`radioButtons()` to allow the horizontal layout (inline = TRUE) of checkboxes
|
||||
or radio buttons. (Thanks, @saurfang, #481)
|
||||
|
||||
* `sliderInput` and `selectizeInput`/`selectInput` now use a standard horizontal
|
||||
size instead of filling up all available horizontal space.
|
||||
size instead of filling up all available horizontal space. Pass `width="100%"`
|
||||
explicitly for the old behavior.
|
||||
|
||||
* Added the `updateSelectizeInput()` function to make it possible to process
|
||||
searching on the server side (i.e. using R), which can be much faster than the
|
||||
client side processing (i.e. using HTML and JavaScript). See the article at
|
||||
http://shiny.rstudio.com/articles/selectize.html for a detailed introduction.
|
||||
|
||||
* Fixed a bug of renderDataTable() when the data object only has 1 row and 1
|
||||
column. (Thanks, ZJ Dai, #429)
|
||||
|
||||
* `renderPrint` gained a new argument 'width' to control the width of the text
|
||||
output, e.g. renderPrint({mtcars}, width = 40).
|
||||
|
||||
* Fixed #220: the zip file for a directory created by some programs may not have
|
||||
the directory name as its first entry, in which case runUrl() can fail. (#220)
|
||||
|
||||
* `runGitHub()` can also take a value of the form "username/repo" in its first
|
||||
argument, e.g. both runGitHub("shiny_example", "rstudio") and
|
||||
runGitHub("rstudio/shiny_example") are valid ways to run the GitHub repo.
|
||||
|
||||
shiny 0.9.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
300
R/app.R
300
R/app.R
@@ -3,11 +3,16 @@
|
||||
#' Create a Shiny app object
|
||||
#'
|
||||
#' These functions create Shiny app objects from either an explicit UI/server
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that
|
||||
#' contains a Shiny app (\code{shinyAppDir}). You generally shouldn't need to
|
||||
#' use these functions to create/run applications; they are intended for
|
||||
#' interoperability purposes, such as embedding Shiny apps inside a \pkg{knitr}
|
||||
#' document.
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that contains a
|
||||
#' Shiny app (\code{shinyAppDir}). You generally shouldn't need to use these
|
||||
#' functions to create/run applications; they are intended for interoperability
|
||||
#' purposes, such as embedding Shiny apps inside a \pkg{knitr} document.
|
||||
#'
|
||||
#' Normally when this function is used at the R console, the Shiny app object is
|
||||
#' automatically passed to the \code{print()} function, which runs the app. If
|
||||
#' this is called in the middle of a function, the value will not be passed to
|
||||
#' \code{print()} and the app will not be run. To make the app run, pass the app
|
||||
#' object to \code{print()} or \code{\link{runApp}()}.
|
||||
#'
|
||||
#' @param ui The UI definition of the app (for example, a call to
|
||||
#' \code{fluidPage()} with nested controls)
|
||||
@@ -22,55 +27,50 @@
|
||||
#' request to determine whether the \code{ui} should be used to handle the
|
||||
#' request. Note that the entire request path must match the regular
|
||||
#' expression in order for the match to be considered successful.
|
||||
#' @return An object that represents the app. Printing the object will run the
|
||||
#' app.
|
||||
#' @return An object that represents the app. Printing the object or passing it
|
||||
#' to \code{\link{runApp}} will run the app.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' },
|
||||
#' options=list(launch.browser = rstudio::viewer)
|
||||
#' )
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#'
|
||||
#'
|
||||
#' # The object can be passed to runApp()
|
||||
#' app <- shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyApp <- function(ui, server, onStart=NULL, options=list(), uiPattern="/") {
|
||||
shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
|
||||
uiPattern="/") {
|
||||
if (is.null(server)) {
|
||||
stop("`server` missing from shinyApp")
|
||||
}
|
||||
|
||||
# Ensure that the entire path is a match
|
||||
uiPattern <- sprintf("^%s$", uiPattern)
|
||||
|
||||
httpHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=html))
|
||||
}
|
||||
httpHandler <- uiHttpHandler(ui, uiPattern)
|
||||
|
||||
serverFuncSource <- function() {
|
||||
server
|
||||
@@ -91,10 +91,7 @@ shinyApp <- function(ui, server, onStart=NULL, options=list(), uiPattern="/") {
|
||||
#' file and either ui.R or www/index.html)
|
||||
#' @export
|
||||
shinyAppDir <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
if (!file.exists(appDir)) {
|
||||
if (!utils::file_test('-d', appDir)) {
|
||||
stop("No Shiny application exists at the path \"", appDir, "\"")
|
||||
}
|
||||
|
||||
@@ -102,18 +99,35 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
# affected by future changes to the path)
|
||||
appDir <- normalizePath(appDir, mustWork = TRUE)
|
||||
|
||||
if (file.exists.ci(appDir, "server.R")) {
|
||||
shinyAppDir_serverR(appDir, options = options)
|
||||
} else if (file.exists.ci(appDir, "app.R")) {
|
||||
shinyAppDir_appR(appDir, options = options)
|
||||
} else {
|
||||
stop("App dir must contain either app.R or server.R.")
|
||||
}
|
||||
}
|
||||
|
||||
# This reads in an app dir in the case that there's a server.R (and ui.R/www)
|
||||
# present, and returns a shiny.appobj.
|
||||
shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
# uiHandlerSource is a function that returns an HTTP handler for serving up
|
||||
# ui.R as a webpage. The "cachedFuncWithFile" call makes sure that the closure
|
||||
# we're creating here only gets executed when ui.R's contents change.
|
||||
uiHandlerSource <- cachedFuncWithFile(appDir, "ui.R", case.sensitive = FALSE,
|
||||
function() {
|
||||
# Have to use file.path.ci every time in case the case of ui.R has
|
||||
# changed. (Hmmm, overengineering a bit?)
|
||||
uiR <- file.path.ci(appDir, "ui.R")
|
||||
function(uiR) {
|
||||
if (file.exists(uiR)) {
|
||||
ui <- source(uiR,
|
||||
local = new.env(parent = globalenv()),
|
||||
keep.source = TRUE)$value
|
||||
# If ui.R contains a call to shinyUI (which sets .globals$ui), use that.
|
||||
# If not, then take the last expression that's returned from ui.R.
|
||||
.globals$ui <- NULL
|
||||
on.exit(.globals$ui <- NULL, add = FALSE)
|
||||
ui <- sourceUTF8(uiR, local = new.env(parent = globalenv()))$value
|
||||
if (!is.null(.globals$ui)) {
|
||||
ui <- .globals$ui[[1]]
|
||||
}
|
||||
return(uiHttpHandler(ui))
|
||||
} else {
|
||||
return(function(req) NULL)
|
||||
@@ -126,14 +140,25 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
serverSource <- cachedSource(appDir, "server.R", case.sensitive = FALSE)
|
||||
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
|
||||
function(serverR) {
|
||||
# If server.R contains a call to shinyServer (which sets .globals$server),
|
||||
# use that. If not, then take the last expression that's returned from
|
||||
# server.R.
|
||||
.globals$server <- NULL
|
||||
on.exit(.globals$server <- NULL, add = TRUE)
|
||||
result <- sourceUTF8(serverR, local = new.env(parent = globalenv()))$value
|
||||
if (!is.null(.globals$server)) {
|
||||
result <- .globals$server[[1]]
|
||||
}
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# This function stands in for the server function, and reloads the
|
||||
# real server function as necessary whenever server.R changes
|
||||
serverFuncSource <- function() {
|
||||
serverFunction <- serverSource(
|
||||
local = new.env(parent = globalenv()),
|
||||
keep.source = TRUE)$value
|
||||
serverFunction <- serverSource()
|
||||
if (is.null(serverFunction)) {
|
||||
return(function(input, output) NULL)
|
||||
} else if (is.function(serverFunction)) {
|
||||
@@ -150,7 +175,7 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
source(file.path.ci(appDir, "global.R"), keep.source = TRUE)
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
@@ -167,6 +192,60 @@ shinyAppDir <- function(appDir, options=list()) {
|
||||
)
|
||||
}
|
||||
|
||||
# This reads in an app dir in the case that there's a app.R present, and returns
|
||||
# a shiny.appobj.
|
||||
shinyAppDir_appR <- function(appDir, options=list()) {
|
||||
fullpath <- file.path.ci(appDir, "app.R")
|
||||
|
||||
# This sources app.R and caches the content. When appObj() is called but
|
||||
# app.R hasn't changed, it won't re-source the file. But if called and
|
||||
# app.R has changed, it'll re-source the file and return the result.
|
||||
appObj <- cachedFuncWithFile(appDir, "app.R", case.sensitive = FALSE,
|
||||
function(appR) {
|
||||
result <- sourceUTF8(fullpath, local = new.env(parent = globalenv()))$value
|
||||
|
||||
if (!is.shiny.appobj(result))
|
||||
stop("app.R did not return a shiny.appobj object.")
|
||||
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# A function that invokes the http handler from the appObj in app.R, but
|
||||
# since this uses appObj(), it only re-sources the file when it changes.
|
||||
dynHttpHandler <- function(...) {
|
||||
appObj()$httpHandler(...)
|
||||
}
|
||||
|
||||
dynServerFuncSource <- function(...) {
|
||||
appObj()$serverFuncSource(...)
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
|
||||
oldwd <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = dynServerFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
options = options
|
||||
),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param x Object to convert to a Shiny app.
|
||||
#' @export
|
||||
@@ -192,6 +271,12 @@ as.shiny.appobj.character <- function(x) {
|
||||
shinyAppDir(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
is.shiny.appobj <- function(x) {
|
||||
inherits(x, "shiny.appobj")
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param ... Additional parameters to be passed to print.
|
||||
#' @export
|
||||
@@ -205,6 +290,21 @@ print.shiny.appobj <- function(x, ...) {
|
||||
do.call(runApp, args)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @method as.tags shiny.appobj
|
||||
#' @export
|
||||
as.tags.shiny.appobj <- function(x, ...) {
|
||||
# jcheng 06/06/2014: Unfortunate copy/paste between this function and
|
||||
# knit_print.shiny.appobj, but I am trying to make the most conservative
|
||||
# change possible due to upcoming release.
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
path <- addSubApp(x)
|
||||
tags$iframe(src=path, width=width, height=height, class="shiny-frame")
|
||||
}
|
||||
|
||||
#' Knitr S3 methods
|
||||
#'
|
||||
#' These S3 methods are necessary to help Shiny applications and UI chunks embed
|
||||
@@ -215,26 +315,33 @@ print.shiny.appobj <- function(x, ...) {
|
||||
#' @param ... Additional knit_print arguments
|
||||
NULL
|
||||
|
||||
# If there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# return a warning indicating the runtime is inappropriate for this object.
|
||||
# Returns NULL in all other cases.
|
||||
shiny_rmd_warning <- function() {
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny")
|
||||
# note that the RStudio IDE checks for this specific string to detect Shiny
|
||||
# applications in static document
|
||||
list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
else
|
||||
NULL
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
shiny_warning <- NULL
|
||||
# if there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# emit a warning indicating the runtime is inappropriate for this object
|
||||
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny") {
|
||||
# note that the RStudio IDE checks for this specific string to detect Shiny
|
||||
# applications in static document
|
||||
shiny_warning <- list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
|
||||
# create a box exactly the same dimensions as the Shiny app would have had
|
||||
# (so the document continues to flow as it would have with the app), and
|
||||
# display a diagnostic message
|
||||
# If not rendering to a Shiny document, create a box exactly the same
|
||||
# dimensions as the Shiny app would have had (so the document continues to
|
||||
# flow as it would have with the app), and display a diagnostic message
|
||||
width <- validateCssUnit(width)
|
||||
height <- validateCssUnit(height)
|
||||
output <- tags$div(
|
||||
@@ -254,51 +361,20 @@ knit_print.shiny.appobj <- function(x, ...) {
|
||||
# need to grab those and put them in meta, like in knit_print.shiny.tag. But
|
||||
# for now it's not an issue, so just return the HTML and warning.
|
||||
|
||||
knitr::asis_output(html_preserve(format(output, indent=FALSE)),
|
||||
meta = shiny_warning, cacheable = FALSE)
|
||||
knitr::asis_output(htmlPreserve(format(output, indent=FALSE)),
|
||||
meta = shiny_rmd_warning(), cacheable = FALSE)
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.tag <- function(x, ...) {
|
||||
output <- surroundSingletons(x)
|
||||
deps <- getNewestDeps(findDependencies(x))
|
||||
content <- takeHeads(output)
|
||||
head_content <- doRenderTags(tagList(content$head))
|
||||
|
||||
meta <- if (length(head_content) > 1 || head_content != "") {
|
||||
list(structure(head_content, class = "shiny_head"))
|
||||
}
|
||||
meta <- c(meta, deps)
|
||||
|
||||
knitr::asis_output(html_preserve(format(content$ui, indent=FALSE)), meta = meta)
|
||||
}
|
||||
|
||||
knit_print.html <- function(x, ...) {
|
||||
deps <- getNewestDeps(findDependencies(x))
|
||||
knitr::asis_output(html_preserve(as.character(x)),
|
||||
meta = if (length(deps)) list(deps))
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.tag.list <- knit_print.shiny.tag
|
||||
|
||||
|
||||
# Lets us use a nicer syntax in knitr chunks than literally
|
||||
# Let us use a nicer syntax in knitr chunks than literally
|
||||
# calling output$value <- renderFoo(...) and fooOutput().
|
||||
#' @rdname knitr_methods
|
||||
#' @param inline Whether the object is printed inline.
|
||||
#' @export
|
||||
knit_print.shiny.render.function <- function(x, ...) {
|
||||
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
x <- htmltools::as.tags(x, inline = inline)
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
|
||||
shiny_rmd_warning())
|
||||
output
|
||||
}
|
||||
|
||||
html_preserve <- function(x) {
|
||||
x <- paste(x, collapse = "\r\n")
|
||||
if (nzchar(x))
|
||||
sprintf("<!--html_preserve-->%s<!--/html_preserve-->", x)
|
||||
else
|
||||
x
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
#' @param ... Elements to include within the page
|
||||
#' @param title The browser window title (defaults to the host URL of the page).
|
||||
#' Can also be set as a side effect of the \code{\link{titlePanel}} function.
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' and resize page elements based on the size of the viewing device)
|
||||
#' @param responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
@@ -24,9 +24,8 @@
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' higher-level layout functions like \code{\link{sidebarLayout}}.
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
@@ -69,7 +68,7 @@
|
||||
#'
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
fluidPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
@@ -80,7 +79,7 @@ fluidPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidRow <- function(...) {
|
||||
div(class = "row-fluid", ...)
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
#' Create a page with a fixed layout
|
||||
@@ -95,8 +94,8 @@ fluidRow <- function(...) {
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @param responsive \code{TRUE} to use responsive layout (automatically adapt
|
||||
#' and resize page elements based on the size of the viewing device)
|
||||
#' @param responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
@@ -109,8 +108,7 @@ fluidRow <- function(...) {
|
||||
#' layout functions like \code{sidebarLayout}, rather, all layout must be done
|
||||
#' with \code{fixedRow} and \code{column}.
|
||||
#'
|
||||
#' @note See the
|
||||
#' \href{https://github.com/rstudio/shiny/wiki/Shiny-Application-Layout-Guide}{
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
@@ -131,7 +129,7 @@ fluidRow <- function(...) {
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, responsive = TRUE, theme = NULL) {
|
||||
fixedPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
@@ -186,9 +184,9 @@ column <- function(width, ..., offset = 0) {
|
||||
if (!is.numeric(width) || (width < 1) || (width > 12))
|
||||
stop("column width must be between 1 and 12")
|
||||
|
||||
colClass <- paste0("span", width)
|
||||
colClass <- paste0("col-sm-", width)
|
||||
if (offset > 0)
|
||||
colClass <- paste0(colClass, " offset", offset)
|
||||
colClass <- paste0(colClass, " col-sm-offset-", offset)
|
||||
div(class = colClass, ...)
|
||||
}
|
||||
|
||||
@@ -210,7 +208,7 @@ column <- function(width, ..., offset = 0) {
|
||||
titlePanel <- function(title, windowTitle=title) {
|
||||
tagList(
|
||||
tags$head(tags$title(windowTitle)),
|
||||
h2(style = "padding: 10px 0px;", title)
|
||||
h2(title)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -310,8 +308,8 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
#'
|
||||
#' Lays out elements in a left-to-right, top-to-bottom arrangement. The elements
|
||||
#' on a given row will be top-aligned with each other. This layout will not work
|
||||
#' well with elements that have a percentage-based width (e.g. `plotOutput` at
|
||||
#' its default setting of `width = "100%"`).
|
||||
#' well with elements that have a percentage-based width (e.g.
|
||||
#' \code{\link{plotOutput}} at its default setting of \code{width = "100\%"}).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
@@ -320,7 +318,7 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
#'
|
||||
#' @seealso \code{\link{verticalLayout}}
|
||||
#'
|
||||
#' #' @examples
|
||||
#' @examples
|
||||
#' flowLayout(
|
||||
#' numericInput("rows", "How many rows?", 5),
|
||||
#' selectInput("letter", "Which letter?", LETTERS),
|
||||
@@ -370,7 +368,7 @@ inputPanel <- function(...) {
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' #' @examples
|
||||
#' @examples
|
||||
#' # Equal sizing
|
||||
#' splitLayout(
|
||||
#' plotOutput("plot1"),
|
||||
|
||||
1561
R/bootstrap.R
1561
R/bootstrap.R
File diff suppressed because it is too large
Load Diff
21
R/cache.R
21
R/cache.R
@@ -2,18 +2,15 @@
|
||||
# files changes on disk. Each time the cache is dirtied, the set of files is
|
||||
# cleared. Therefore, the set of files needs to be re-built each time the cached
|
||||
# code executes. This approach allows for dynamic dependency graphs.
|
||||
CacheContext <- setRefClass(
|
||||
CacheContext <- R6Class(
|
||||
'CacheContext',
|
||||
fields = list(
|
||||
.dirty = 'logical',
|
||||
.tests = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.dirty <<- TRUE
|
||||
# List of functions that return TRUE if dirty
|
||||
.tests <<- list()
|
||||
},
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.dirty = TRUE,
|
||||
# List of functions that return TRUE if dirty
|
||||
.tests = list(),
|
||||
|
||||
addDependencyFile = function(file) {
|
||||
if (.dirty)
|
||||
return()
|
||||
@@ -53,7 +50,7 @@ CacheContext <- setRefClass(
|
||||
},
|
||||
with = function(func) {
|
||||
oldCC <- .currentCacheContext$cc
|
||||
.currentCacheContext$cc <- .self
|
||||
.currentCacheContext$cc <- self
|
||||
on.exit(.currentCacheContext$cc <- oldCC)
|
||||
|
||||
return(func())
|
||||
|
||||
@@ -20,18 +20,19 @@
|
||||
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
|
||||
# the websockets package's HTTP server at the moment.
|
||||
|
||||
FileUploadOperation <- setRefClass(
|
||||
FileUploadOperation <- R6Class(
|
||||
'FileUploadOperation',
|
||||
fields = list(
|
||||
.parent = 'ANY',
|
||||
.id = 'character',
|
||||
.files = 'data.frame',
|
||||
.dir = 'character',
|
||||
.currentFileInfo = 'list',
|
||||
.currentFileData = 'ANY',
|
||||
.pendingFileInfos = 'list'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.parent = NULL,
|
||||
.id = character(0),
|
||||
.files = data.frame(),
|
||||
.dir = character(0),
|
||||
.currentFileInfo = list(),
|
||||
.currentFileData = NULL,
|
||||
.pendingFileInfos = list(),
|
||||
|
||||
initialize = function(parent, id, dir, fileInfos) {
|
||||
.parent <<- parent
|
||||
.id <<- id
|
||||
@@ -78,33 +79,49 @@ FileUploadOperation <- setRefClass(
|
||||
)
|
||||
|
||||
#' @include map.R
|
||||
FileUploadContext <- setRefClass(
|
||||
FileUploadContext <- R6Class(
|
||||
'FileUploadContext',
|
||||
fields = list(
|
||||
.basedir = 'character',
|
||||
.operations = 'Map'
|
||||
class = FALSE,
|
||||
private = list(
|
||||
basedir = character(0),
|
||||
operations = 'Map',
|
||||
ids = character(0) # Keep track of all ids used for file uploads
|
||||
),
|
||||
methods = list(
|
||||
public = list(
|
||||
initialize = function(dir=tempdir()) {
|
||||
.basedir <<- dir
|
||||
private$basedir <- dir
|
||||
private$operations <- Map$new()
|
||||
},
|
||||
createUploadOperation = function(fileInfos) {
|
||||
while (TRUE) {
|
||||
id <- paste(as.raw(p_runif(12, min=0, max=0xFF)), collapse='')
|
||||
dir <- file.path(.basedir, id)
|
||||
private$ids <- c(private$ids, id)
|
||||
dir <- file.path(private$basedir, id)
|
||||
if (!dir.create(dir))
|
||||
next
|
||||
|
||||
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
|
||||
.operations$set(id, op)
|
||||
op <- FileUploadOperation$new(self, id, dir, fileInfos)
|
||||
private$operations$set(id, op)
|
||||
return(id)
|
||||
}
|
||||
},
|
||||
getUploadOperation = function(jobId) {
|
||||
.operations$get(jobId)
|
||||
private$operations$get(jobId)
|
||||
},
|
||||
onJobFinished = function(jobId) {
|
||||
.operations$remove(jobId)
|
||||
private$operations$remove(jobId)
|
||||
},
|
||||
# Remove the directories containing file uploads; this is to be called when
|
||||
# a session ends.
|
||||
rmUploadDirs = function() {
|
||||
# Make sure all_paths is underneath the tempdir()
|
||||
if (!grepl(normalizePath(tempdir()), normalizePath(private$basedir), fixed = TRUE)) {
|
||||
stop("Won't remove upload path ", private$basedir,
|
||||
"because it is not under tempdir(): ", tempdir())
|
||||
}
|
||||
|
||||
all_paths <- file.path(private$basedir, private$ids)
|
||||
unlink(all_paths, recursive = TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
16
R/globals.R
16
R/globals.R
@@ -5,5 +5,19 @@
|
||||
# R's lazy-loading package scheme causes the private seed to be cached in the
|
||||
# package itself, making our PRNG completely deterministic. This line resets
|
||||
# the private seed during load.
|
||||
withPrivateSeed(set.seed(NULL))
|
||||
withPrivateSeed(reinitializeSeed())
|
||||
}
|
||||
|
||||
.onAttach <- function(libname, pkgname) {
|
||||
# Check for htmlwidgets version, if installed. As of Shiny 0.12.0 and
|
||||
# htmlwidgets 0.4, both packages switched from RJSONIO to jsonlite. Because of
|
||||
# this change, Shiny 0.12.0 will work only with htmlwidgets >= 0.4, and vice
|
||||
# versa.
|
||||
if (system.file(package = "htmlwidgets") != "" &&
|
||||
utils::packageVersion("htmlwidgets") < "0.4") {
|
||||
packageStartupMessage(
|
||||
"This version of Shiny is designed to work with htmlwidgets >= 0.4. ",
|
||||
"Please upgrade your version of htmlwidgets."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
14
R/graph.R
14
R/graph.R
@@ -1,5 +1,5 @@
|
||||
writeReactLog <- function(file=stdout()) {
|
||||
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
|
||||
cat(toJSON(.graphStack$as_list(), pretty=TRUE), file=file)
|
||||
}
|
||||
|
||||
#' Reactive Log Visualizer
|
||||
@@ -37,7 +37,7 @@ writeReactLog <- function(file=stdout()) {
|
||||
#'
|
||||
#' @export
|
||||
showReactLog <- function() {
|
||||
browseURL(renderReactLog())
|
||||
utils::browseURL(renderReactLog())
|
||||
}
|
||||
|
||||
renderReactLog <- function() {
|
||||
@@ -55,8 +55,8 @@ renderReactLog <- function() {
|
||||
}
|
||||
|
||||
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
||||
if (isTRUE(getOption('shiny.reactlog', FALSE)))
|
||||
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
|
||||
if (isTRUE(getOption('shiny.reactlog')))
|
||||
.graphStack$push(logEntry)
|
||||
|
||||
if (!is.null(domain)) {
|
||||
domain$reactlog(logEntry)
|
||||
@@ -91,7 +91,7 @@ renderReactLog <- function() {
|
||||
.graphAppend(list(
|
||||
action = 'valueChange',
|
||||
id = label,
|
||||
value = paste(capture.output(str(value)), collapse='\n')
|
||||
value = paste(utils::capture.output(utils::str(value)), collapse='\n')
|
||||
))
|
||||
}
|
||||
|
||||
@@ -99,5 +99,5 @@ renderReactLog <- function() {
|
||||
.graphAppend(list(action='invalidate', id=id), domain)
|
||||
}
|
||||
|
||||
.graphEnv <- new.env()
|
||||
.graphEnv$log <- list()
|
||||
#' @include stack.R
|
||||
.graphStack <- Stack$new()
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
pathPattern <- "^(~|/|[a-zA-Z]:[/\\\\]|\\\\\\\\)"
|
||||
|
||||
#' Create a web dependency
|
||||
#'
|
||||
#' Ensure that a file-based HTML dependency (from the htmltools package) can be
|
||||
#' served over Shiny's HTTP server. This function works by using
|
||||
#' \code{\link{addResourcePath}} to map the HTML dependency's directory to a
|
||||
#' URL.
|
||||
#'
|
||||
#' @param dependency A single HTML dependency object, created using
|
||||
#' \code{\link{htmlDependency}}. If the \code{src} value is named, then
|
||||
#' \code{href} and/or \code{file} names must be present.
|
||||
#'
|
||||
#' @return A single HTML dependency object that has an \code{href}-named element
|
||||
#' in its \code{src}.
|
||||
#' @export
|
||||
createWebDependency <- function(dependency) {
|
||||
if (is.null(dependency))
|
||||
return(NULL)
|
||||
@@ -7,53 +19,11 @@ createWebDependency <- function(dependency) {
|
||||
if (!inherits(dependency, "html_dependency"))
|
||||
stop("Unexpected non-html_dependency type")
|
||||
|
||||
# Does it look like a path on disk? Register it as a resource and replace the
|
||||
# disk-based path with a relative URL
|
||||
if (grepl(pathPattern, dependency$path, perl = TRUE)) {
|
||||
if (is.null(dependency$src$href)) {
|
||||
prefix <- paste(dependency$name, "-", dependency$version, sep = "")
|
||||
addResourcePath(prefix, dependency$path)
|
||||
dependency$path <- prefix
|
||||
addResourcePath(prefix, dependency$src$file)
|
||||
dependency$src$href <- prefix
|
||||
}
|
||||
|
||||
return(dependency)
|
||||
}
|
||||
|
||||
# Given a list of dependencies, choose the latest versions and return them as a
|
||||
# named list in the correct order.
|
||||
getNewestDeps <- function(dependencies) {
|
||||
result <- list()
|
||||
for (dep in dependencies) {
|
||||
if (!is.null(dep)) {
|
||||
other <- result[[dep$name]]
|
||||
if (is.null(other) || compareVersion(dep$version, other$version) > 0) {
|
||||
# Note that if the dep was already in the result list, then this
|
||||
# assignment preserves its position in the list
|
||||
result[[dep$name]] <- dep
|
||||
}
|
||||
}
|
||||
}
|
||||
return(result)
|
||||
}
|
||||
|
||||
# Remove `remove` from `dependencies` if the name matches.
|
||||
# dependencies is a named list of dependencies.
|
||||
# remove is a named list of dependencies that take priority.
|
||||
# If warnOnConflict, then warn when a dependency is being removed because of an
|
||||
# older version already being loaded.
|
||||
removeDeps <- function(dependencies, remove, warnOnConflict = TRUE) {
|
||||
matches <- names(dependencies) %in% names(remove)
|
||||
if (warnOnConflict) {
|
||||
for (depname in names(dependencies)[matches]) {
|
||||
loser <- dependencies[[depname]]
|
||||
winner <- remove[[depname]]
|
||||
if (compareVersion(loser$version, winner$version) > 0) {
|
||||
warning(sprintf(paste("The dependency %s %s conflicts with",
|
||||
"version %s"), loser$name, loser$version, winner$version
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return only deps that weren't in remove
|
||||
return(dependencies[!matches])
|
||||
}
|
||||
|
||||
104
R/htmltools.R
104
R/htmltools.R
@@ -1,97 +1,7 @@
|
||||
|
||||
|
||||
# Define an HTML dependency
|
||||
#
|
||||
# Define an HTML dependency (e.g. CSS or Javascript and related library). HTML
|
||||
# dependency definitions are required for \code{\link{html_output}} that
|
||||
# require CSS or JavaScript within the document head to render correctly.
|
||||
#
|
||||
# @param name Library name
|
||||
# @param version Library version
|
||||
# @param path Full path to library
|
||||
# @param meta Named list of meta tags to insert into document head
|
||||
# @param script Script(s) to include within the document head (should be
|
||||
# specified relative to the \code{path} parameter).
|
||||
# @param stylesheet Stylesheet(s) to include within the document (should be
|
||||
# specified relative to the \code{path} parameter).
|
||||
# @param head Arbitrary lines of HTML to insert into the document head
|
||||
#
|
||||
# @return An object that can be included in the list of dependencies passed to
|
||||
# \code{\link{html_print}} or \code{\link{html_knit_print}}.
|
||||
#
|
||||
# @details See the documentation on
|
||||
# \href{http://rmarkdown.rstudio.com/developer_html_widgets.html}{R
|
||||
# Markdown HTML Widgets} for examples and additional details.
|
||||
#
|
||||
html_dependency <- function(name,
|
||||
version,
|
||||
path,
|
||||
meta = NULL,
|
||||
script = NULL,
|
||||
stylesheet = NULL,
|
||||
head = NULL) {
|
||||
structure(class = "html_dependency", list(
|
||||
name = name,
|
||||
version = version,
|
||||
path = path,
|
||||
meta = meta,
|
||||
script = script,
|
||||
stylesheet = stylesheet,
|
||||
head = head
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
# Given a list of HTML dependencies produce a character representation
|
||||
# suitable for inclusion within the head of an HTML document
|
||||
html_dependencies_as_character <- function(dependencies, lib_dir = NULL) {
|
||||
|
||||
html <- c()
|
||||
|
||||
for (dep in dependencies) {
|
||||
|
||||
# copy library files if necessary
|
||||
if (!is.null(lib_dir)) {
|
||||
|
||||
if (!file.exists(lib_dir))
|
||||
dir.create(lib_dir)
|
||||
|
||||
target_dir <- file.path(lib_dir, basename(dep$path))
|
||||
if (!file.exists(target_dir))
|
||||
file.copy(from = dep$path, to = lib_dir, recursive = TRUE)
|
||||
|
||||
dep$path <- file.path(basename(lib_dir), basename(target_dir))
|
||||
}
|
||||
|
||||
# add meta content
|
||||
for (name in names(dep$meta)) {
|
||||
html <- c(html, paste("<meta name=\"", name,
|
||||
"\" content=\"", dep$meta[[name]], "\" />",
|
||||
sep = ""))
|
||||
}
|
||||
|
||||
# add stylesheets
|
||||
for (stylesheet in dep$stylesheet) {
|
||||
stylesheet <- file.path(dep$path, stylesheet)
|
||||
html <- c(html, paste("<link href=\"", stylesheet, "\" ",
|
||||
"rel=\"stylesheet\" />",
|
||||
sep = ""))
|
||||
}
|
||||
|
||||
# add scripts
|
||||
for (script in dep$script) {
|
||||
script <- file.path(dep$path, script)
|
||||
html <- c(html,
|
||||
paste("<script src=\"", script, "\"></script>", sep = ""))
|
||||
}
|
||||
|
||||
# add raw head content
|
||||
html <- c(html, dep$head)
|
||||
}
|
||||
|
||||
html
|
||||
}
|
||||
|
||||
attach_dependency <- function(x, dependency) {
|
||||
structure(x, html_dependency = dependency)
|
||||
}
|
||||
#' @export a br code div em h1 h2 h3 h4 h5 h6 hr HTML img p pre span strong
|
||||
#' @export includeCSS includeHTML includeMarkdown includeScript includeText
|
||||
#' @export is.singleton singleton
|
||||
#' @export tag tagAppendAttributes tagAppendChild tagAppendChildren tagList tags tagSetChildren withTags
|
||||
#' @export validateCssUnit
|
||||
#' @export knit_print.html knit_print.shiny.tag knit_print.shiny.tag.list
|
||||
NULL
|
||||
|
||||
136
R/image-interact-opts.R
Normal file
136
R/image-interact-opts.R
Normal file
@@ -0,0 +1,136 @@
|
||||
#' Create an object representing click options
|
||||
#'
|
||||
#' This generates an object representing click options, to be passed as the
|
||||
#' \code{click} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_click"},
|
||||
#' then the click coordinates will be available as \code{input$plot_click}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive click events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @export
|
||||
clickOpts <- function(id = NULL, clip = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
clip = clip
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' Create an object representing double-click options
|
||||
#'
|
||||
#' This generates an object representing dobule-click options, to be passed as
|
||||
#' the \code{dblclick} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is
|
||||
#' \code{"plot_dblclick"}, then the click coordinates will be available as
|
||||
#' \code{input$plot_dblclick}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive double-click events even when the mouse is
|
||||
#' outside the plotting area, as long as it is still inside the image.
|
||||
#' @param delay Maximum delay (in ms) between a pair clicks for them to be
|
||||
#' counted as a double-click.
|
||||
#' @export
|
||||
dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
clip = clip,
|
||||
delay = delay
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing hover options
|
||||
#'
|
||||
#' This generates an object representing hovering options, to be passed as the
|
||||
#' \code{hover} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_hover"},
|
||||
#' then the hover coordinates will be available as \code{input$plot_hover}.
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the mouse location to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of hover
|
||||
#' events. Use \code{"throttle"} to limit the number of hover events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the hover area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive hover events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @param nullOutside If \code{TRUE} (the default), the value will be set to
|
||||
#' \code{NULL} when the mouse exits the plotting area. If \code{FALSE}, the
|
||||
#' value will stop changing when the cursor exits the plotting area.
|
||||
#' @export
|
||||
hoverOpts <- function(id = NULL, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
nullOutside = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
delay = delay,
|
||||
delayType = match.arg(delayType),
|
||||
clip = clip,
|
||||
nullOutside = nullOutside
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing brushing options
|
||||
#'
|
||||
#' This generates an object representing brushing options, to be passed as the
|
||||
#' \code{brush} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_brush"},
|
||||
#' then the coordinates will be available as \code{input$plot_brush}.
|
||||
#' @param fill Fill color of the brush.
|
||||
#' @param stroke Outline color of the brush.
|
||||
#' @param opacity Opacity of the brush
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the brush data to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of brush
|
||||
#' events. Use \code{"throttle"} to limit the number of brush events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the brush area be clipped to the plotting area? If FALSE,
|
||||
#' then the user will be able to brush outside the plotting area, as long as
|
||||
#' it is still inside the image.
|
||||
#' @param direction The direction for brushing. If \code{"xy"}, the brush can be
|
||||
#' drawn and moved in both x and y directions. If \code{"x"}, or \code{"y"},
|
||||
#' the brush wil work horizontally or vertically.
|
||||
#' @param resetOnNew When a new image is sent to the browser (via
|
||||
#' \code{\link{renderImage}}), should the brush be reset? The default,
|
||||
#' \code{FALSE}, is useful if you want to update the plot while keeping the
|
||||
#' brush. Using \code{TRUE} is useful if you want to clear the brush whenever
|
||||
#' the plot is updated.
|
||||
#' @export
|
||||
brushOpts <- function(id = NULL, fill = "#9cf", stroke = "#036",
|
||||
opacity = 0.25, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
direction = c("xy", "x", "y"),
|
||||
resetOnNew = FALSE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
fill = fill,
|
||||
stroke = stroke,
|
||||
opacity = opacity,
|
||||
delay = delay,
|
||||
delayType = match.arg(delayType),
|
||||
clip = clip,
|
||||
direction = match.arg(direction),
|
||||
resetOnNew = resetOnNew
|
||||
)
|
||||
}
|
||||
437
R/image-interact.R
Normal file
437
R/image-interact.R
Normal file
@@ -0,0 +1,437 @@
|
||||
#' Find rows of data that are selected by a brush
|
||||
#'
|
||||
#' This function returns rows from a data frame which are under a brush used
|
||||
#' with \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' It is also possible for this function to return all rows from the input data
|
||||
#' frame, but with an additional column \code{selected_}, which indicates which
|
||||
#' rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#' selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#' \code{allRows=TRUE} option.
|
||||
#'
|
||||
#' The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2}
|
||||
#' arguments specify which columns in the data correspond to the x variable, y
|
||||
#' variable, and panel variables of the plot. For example, if your plot is
|
||||
#' \code{plot(x=cars$speed, y=cars$dist)}, and your brush is named
|
||||
#' \code{"cars_brush"}, then you would use \code{brushedPoints(cars,
|
||||
#' input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#' For plots created with ggplot2, it should not be necessary to specify the
|
||||
#' column names; that information will already be contained in the brush,
|
||||
#' provided that variables are in the original data, and not computed. For
|
||||
#' example, with \code{ggplot(cars, aes(x=speed, y=dist)) + geom_point()}, you
|
||||
#' could use \code{brushedPoints(cars, input$cars_brush)}. If, however, you use
|
||||
#' a computed column, like \code{ggplot(cars, aes(x=speed/2, y=dist)) +
|
||||
#' geom_point()}, then it will not be able to automatically extract column names
|
||||
#' and filter on them. If you want to use this function to filter data, it is
|
||||
#' recommended that you not use computed columns; instead, modify the data
|
||||
#' first, and then make the plot with "raw" columns in the modified data.
|
||||
#'
|
||||
#' If a specified x or y column is a factor, then it will be coerced to an
|
||||
#' integer vector. If it is a character vector, then it will be coerced to a
|
||||
#' factor and then integer vector. This means that the brush will be considered
|
||||
#' to cover a given character/factor value when it covers the center value.
|
||||
#'
|
||||
#' If the brush is operating in just the x or y directions (e.g., with
|
||||
#' \code{brushOpts(direction = "x")}, then this function will filter out points
|
||||
#' using just the x or y variable, whichever is appropriate.
|
||||
#'
|
||||
#' @param brush The data from a brush, such as \code{input$plot_brush}.
|
||||
#' @param df A data frame from which to select rows.
|
||||
#' @param xvar,yvar A string with the name of the variable on the x or y axis.
|
||||
#' This must also be the name of a column in \code{df}. If absent, then this
|
||||
#' function will try to infer the variable from the brush (only works for
|
||||
#' ggplot2).
|
||||
#' @param panelvar1,panelvar2 Each of these is a string with the name of a panel
|
||||
#' variable. For example, if with ggplot2, you facet on a variable called
|
||||
#' \code{cyl}, then you can use \code{"cyl"} here. However, specifying the
|
||||
#' panel variable should not be necessary with ggplot2; Shiny should be able
|
||||
#' to auto-detect the panel variable.
|
||||
#' @param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' brush (\code{TRUE}) or outside the brush (\code{FALSE}).
|
||||
#'
|
||||
#' @seealso \code{\link{plotOutput}} for example usage.
|
||||
#' @export
|
||||
brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
allRows = FALSE) {
|
||||
if (is.null(brush)) {
|
||||
if (allRows)
|
||||
df$selected_ <- FALSE
|
||||
else
|
||||
df <- df[0, , drop = FALSE]
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
if (is.null(brush$xmin)) {
|
||||
stop("brushedPoints requires a brush object with xmin, xmax, ymin, and ymax.")
|
||||
}
|
||||
|
||||
# Which direction(s) the brush is selecting over. Direction can be 'x', 'y',
|
||||
# or 'xy'.
|
||||
use_x <- grepl("x", brush$direction)
|
||||
use_y <- grepl("y", brush$direction)
|
||||
|
||||
# Try to extract vars from brush object
|
||||
xvar <- xvar %OR% brush$mapping$x
|
||||
yvar <- yvar %OR% brush$mapping$y
|
||||
panelvar1 <- panelvar1 %OR% brush$mapping$panelvar1
|
||||
panelvar2 <- panelvar2 %OR% brush$mapping$panelvar2
|
||||
|
||||
# Filter out x and y values
|
||||
keep_rows <- rep(TRUE, nrow(df))
|
||||
if (use_x) {
|
||||
if (is.null(xvar))
|
||||
stop("brushedPoints: not able to automatically infer `xvar` from brush")
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
keep_rows <- keep_rows & (x >= brush$xmin & x <= brush$xmax)
|
||||
}
|
||||
if (use_y) {
|
||||
if (is.null(yvar))
|
||||
stop("brushedPoints: not able to automatically infer `yvar` from brush")
|
||||
y <- asNumber(df[[yvar]])
|
||||
keep_rows <- keep_rows & (y >= brush$ymin & y <= brush$ymax)
|
||||
}
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
if (!is.null(panelvar1))
|
||||
keep_rows <- keep_rows & panelMatch(brush$panelvar1, df[[panelvar1]])
|
||||
if (!is.null(panelvar2))
|
||||
keep_rows <- keep_rows & panelMatch(brush$panelvar2, df[[panelvar2]])
|
||||
|
||||
if (allRows) {
|
||||
df$selected_ <- keep_rows
|
||||
df
|
||||
} else {
|
||||
df[keep_rows, , drop = FALSE]
|
||||
}
|
||||
}
|
||||
|
||||
# The `brush` data structure will look something like the examples below.
|
||||
# For base graphics, `mapping` is empty, and there are no panelvars:
|
||||
# List of 8
|
||||
# $ xmin : num 3.73
|
||||
# $ xmax : num 4.22
|
||||
# $ ymin : num 13.9
|
||||
# $ ymax : num 19.8
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
# ..$ right : num 5.58
|
||||
# ..$ bottom: num 9.46
|
||||
# ..$ top : num 34.8
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 58
|
||||
# ..$ right : num 429
|
||||
# ..$ bottom: num 226
|
||||
# ..$ top : num 58
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ direction: chr "y"
|
||||
#
|
||||
# For ggplot2, the mapping vars usually will be included, and if faceting is
|
||||
# used, they will be listed as panelvars:
|
||||
# List of 10
|
||||
# $ xmin : num 3.18
|
||||
# $ xmax : num 3.78
|
||||
# $ ymin : num 17.1
|
||||
# $ ymax : num 20.4
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
# ..$ panelvar1: chr "cyl"
|
||||
# ..$ panelvar2: chr "am"
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.32
|
||||
# ..$ right : num 5.62
|
||||
# ..$ bottom: num 9.22
|
||||
# ..$ top : num 35.1
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 172
|
||||
# ..$ right : num 300
|
||||
# ..$ bottom: num 144
|
||||
# ..$ top : num 28.5
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ direction: chr "y"
|
||||
|
||||
|
||||
#'Find rows of data that are near a click/hover/double-click
|
||||
#'
|
||||
#'This function returns rows from a data frame which are near a click, hover, or
|
||||
#'double-click, when used with \code{\link{plotOutput}}. The rows will be sorted
|
||||
#'by their distance to the mouse event.
|
||||
#'
|
||||
#'It is also possible for this function to return all rows from the input data
|
||||
#'frame, but with an additional column \code{selected_}, which indicates which
|
||||
#'rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#'selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#'\code{allRows=TRUE} option. If this is used, the resulting data frame will not
|
||||
#'be sorted by distance to the mouse event.
|
||||
#'
|
||||
#'The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2} arguments
|
||||
#'specify which columns in the data correspond to the x variable, y variable,
|
||||
#'and panel variables of the plot. For example, if your plot is
|
||||
#'\code{plot(x=cars$speed, y=cars$dist)}, and your click variable is named
|
||||
#'\code{"cars_click"}, then you would use \code{nearPoints(cars,
|
||||
#'input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#'@inheritParams brushedPoints
|
||||
#'@param coordinfo The data from a mouse event, such as \code{input$plot_click}.
|
||||
#'@param threshold A maxmimum distance to the click point; rows in the data
|
||||
#' frame where the distance to the click is less than \code{threshold} will be
|
||||
#' returned.
|
||||
#'@param maxpoints Maximum number of rows to return. If NULL (the default),
|
||||
#' return all rows that are within the threshold distance.
|
||||
#'@param addDist If TRUE, add a column named \code{dist_} that contains the
|
||||
#' distance from the coordinate to the point, in pixels. When no mouse event
|
||||
#' has yet occured, the value of \code{dist_} will be \code{NA}.
|
||||
#'@param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' selected by the mouse event (\code{TRUE}) or not (\code{FALSE}).
|
||||
#'
|
||||
#'@seealso \code{\link{plotOutput}} for more examples.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Note that in practice, these examples would need to go in reactives
|
||||
#' # or observers.
|
||||
#'
|
||||
#' # This would select all points within 5 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click)
|
||||
#'
|
||||
#' # Select just the nearest point within 10 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
|
||||
#'
|
||||
#' }
|
||||
#'@export
|
||||
nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
threshold = 5, maxpoints = NULL, addDist = FALSE,
|
||||
allRows = FALSE) {
|
||||
if (is.null(coordinfo)) {
|
||||
if (addDist)
|
||||
df$dist_ <- NA_real_
|
||||
|
||||
if (allRows)
|
||||
df$selected_ <- FALSE
|
||||
else
|
||||
df <- df[0, , drop = FALSE]
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
if (is.null(coordinfo$x)) {
|
||||
stop("nearPoints requires a click/hover/double-click object with x and y values.")
|
||||
}
|
||||
|
||||
# Try to extract vars from coordinfo object
|
||||
xvar <- xvar %OR% coordinfo$mapping$x
|
||||
yvar <- yvar %OR% coordinfo$mapping$y
|
||||
panelvar1 <- panelvar1 %OR% coordinfo$mapping$panelvar1
|
||||
panelvar2 <- panelvar2 %OR% coordinfo$mapping$panelvar2
|
||||
|
||||
if (is.null(xvar))
|
||||
stop("nearPoints: not able to automatically infer `xvar` from coordinfo")
|
||||
if (is.null(yvar))
|
||||
stop("nearPoints: not able to automatically infer `yvar` from coordinfo")
|
||||
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
y <- asNumber(df[[yvar]])
|
||||
|
||||
# Get the pixel coordinates of the point
|
||||
coordPx <- scaleCoords(coordinfo$x, coordinfo$y, coordinfo)
|
||||
|
||||
# Get pixel coordinates of data points
|
||||
dataPx <- scaleCoords(x, y, coordinfo)
|
||||
|
||||
# Distances of data points to coordPx
|
||||
dists <- sqrt((dataPx$x - coordPx$x) ^ 2 + (dataPx$y - coordPx$y) ^ 2)
|
||||
|
||||
if (addDist)
|
||||
df$dist_ <- dists
|
||||
|
||||
keep_rows <- (dists <= threshold)
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
if (!is.null(panelvar1))
|
||||
keep_rows <- keep_rows & panelMatch(coordinfo$panelvar1, df[[panelvar1]])
|
||||
if (!is.null(panelvar2))
|
||||
keep_rows <- keep_rows & panelMatch(coordinfo$panelvar2, df[[panelvar2]])
|
||||
|
||||
# Track the indices to keep
|
||||
keep_idx <- which(keep_rows)
|
||||
|
||||
# Order by distance
|
||||
dists <- dists[keep_idx]
|
||||
keep_idx <- keep_idx[order(dists)]
|
||||
|
||||
# Keep max number of rows
|
||||
if (!is.null(maxpoints) && length(keep_idx) > maxpoints) {
|
||||
keep_idx <- keep_idx[seq_len(maxpoints)]
|
||||
}
|
||||
|
||||
if (allRows) {
|
||||
# Add selected_ column if needed
|
||||
df$selected_ <- FALSE
|
||||
df$selected_[keep_idx] <- TRUE
|
||||
|
||||
} else {
|
||||
# If we don't keep all rows, return just the selected rows, sorted by
|
||||
# distance.
|
||||
df <- df[keep_idx, , drop = FALSE]
|
||||
}
|
||||
|
||||
df
|
||||
}
|
||||
|
||||
# The coordinfo data structure will look something like the examples below.
|
||||
# For base graphics, `mapping` is empty, and there are no panelvars:
|
||||
# List of 7
|
||||
# $ x : num 4.37
|
||||
# $ y : num 12
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
# ..$ right : num 5.58
|
||||
# ..$ bottom: num 9.46
|
||||
# ..$ top : num 34.8
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 58
|
||||
# ..$ right : num 429
|
||||
# ..$ bottom: num 226
|
||||
# ..$ top : num 58
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ .nonce : num 0.343
|
||||
#
|
||||
# For ggplot2, the mapping vars usually will be included, and if faceting is
|
||||
# used, they will be listed as panelvars:
|
||||
# List of 9
|
||||
# $ x : num 3.78
|
||||
# $ y : num 17.1
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
# ..$ panelvar1: chr "cyl"
|
||||
# ..$ panelvar2: chr "am"
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.32
|
||||
# ..$ right : num 5.62
|
||||
# ..$ bottom: num 9.22
|
||||
# ..$ top : num 35.1
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 172
|
||||
# ..$ right : num 300
|
||||
# ..$ bottom: num 144
|
||||
# ..$ top : num 28.5
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ .nonce : num 0.603
|
||||
|
||||
|
||||
|
||||
# Coerce various types of variables to numbers. This works for Date, POSIXt,
|
||||
# characters, and factors. Used because the mouse coords are numeric.
|
||||
asNumber <- function(x) {
|
||||
if (is.character(x)) x <- as.factor(x)
|
||||
if (is.factor(x)) x <- as.integer(x)
|
||||
as.numeric(x)
|
||||
}
|
||||
|
||||
# Given a panelvar value and a vector x, return logical vector indicating which
|
||||
# items match the panelvar value. Because the panelvar value is always a
|
||||
# string but the vector could be numeric, it might be necessary to coerce the
|
||||
# panelvar to a number before comparing to the vector.
|
||||
panelMatch <- function(search_value, x) {
|
||||
if (is.numeric(x)) search_value <- as.numeric(search_value)
|
||||
x == search_value
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Scaling functions
|
||||
# These functions have direct analogs in Javascript code, except these are
|
||||
# vectorized for x and y.
|
||||
|
||||
# Map a value x from a domain to a range. If clip is true, clip it to the
|
||||
# range.
|
||||
mapLinear <- function(x, domainMin, domainMax, rangeMin, rangeMax, clip = TRUE) {
|
||||
factor <- (rangeMax - rangeMin) / (domainMax - domainMin)
|
||||
val <- x - domainMin
|
||||
newval <- (val * factor) + rangeMin
|
||||
|
||||
if (clip) {
|
||||
maxval <- max(rangeMax, rangeMin)
|
||||
minval <- min(rangeMax, rangeMin)
|
||||
newval[newval > maxval] <- maxval
|
||||
newval[newval < minval] <- minval
|
||||
}
|
||||
newval
|
||||
}
|
||||
|
||||
# Scale val from domain to range. If logbase is present, use log scaling.
|
||||
scale1D <- function(val, domainMin, domainMax, rangeMin, rangeMax,
|
||||
logbase = NULL, clip = TRUE) {
|
||||
if (!is.null(logbase))
|
||||
val <- log(val, logbase)
|
||||
mapLinear(val, domainMin, domainMax, rangeMin, rangeMax, clip)
|
||||
}
|
||||
|
||||
# Inverse scale val, from range to domain. If logbase is present, use inverse
|
||||
# log (power) transformation.
|
||||
scaleInv1D <- function(val, domainMin, domainMax, rangeMin, rangeMax,
|
||||
logbase = NULL, clip = TRUE) {
|
||||
res <- mapLinear(val, rangeMin, rangeMax, domainMin, domainMax, clip)
|
||||
if (!is.null(logbase))
|
||||
res <- logbase ^ res
|
||||
res
|
||||
}
|
||||
|
||||
# Scale x and y coordinates from domain to range, using information in
|
||||
# scaleinfo. scaleinfo must contain items $domain, $range, and $log. The
|
||||
# scaleinfo object corresponds to one element from the coordmap object generated
|
||||
# by getPrevPlotCoordmap or getGgplotCoordmap; it is the scaling information for
|
||||
# one panel in a plot.
|
||||
scaleCoords <- function(x, y, scaleinfo) {
|
||||
if (is.null(scaleinfo))
|
||||
return(NULL)
|
||||
|
||||
domain <- scaleinfo$domain
|
||||
range <- scaleinfo$range
|
||||
log <- scaleinfo$log
|
||||
|
||||
list(
|
||||
x = scale1D(x, domain$left, domain$right, range$left, range$right, log$x),
|
||||
y = scale1D(y, domain$bottom, domain$top, range$bottom, range$top, log$y)
|
||||
)
|
||||
}
|
||||
|
||||
# Inverse scale x and y coordinates from range to domain, using information in
|
||||
# scaleinfo.
|
||||
scaleInvCoords <- function(x, y, scaleinfo) {
|
||||
if (is.null(scaleinfo))
|
||||
return(NULL)
|
||||
|
||||
domain <- scaleinfo$domain
|
||||
range <- scaleinfo$range
|
||||
log <- scaleinfo$log
|
||||
|
||||
list(
|
||||
x = scaleInv1D(x, domain$left, domain$right, range$left, range$right, log$x),
|
||||
y = scaleInv1D(y, domain$bottom, domain$top, range$bottom, range$top, log$y)
|
||||
)
|
||||
}
|
||||
@@ -33,17 +33,26 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
# Otherwise, if the Cairo package is installed, use CairoPNG().
|
||||
# Finally, if neither quartz nor Cairo, use png().
|
||||
if (capabilities("aqua")) {
|
||||
pngfun <- png
|
||||
} else if (getOption('shiny.usecairo', TRUE) &&
|
||||
pngfun <- grDevices::png
|
||||
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
|
||||
nchar(system.file(package = "Cairo"))) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
pngfun <- png
|
||||
pngfun <- grDevices::png
|
||||
}
|
||||
|
||||
pngfun(filename=filename, width=width, height=height, res=res, ...)
|
||||
dv <- dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
|
||||
# Call plot.new() so that even if no plotting operations are performed at
|
||||
# least we have a blank background. N.B. we need to set the margin to 0
|
||||
# temporarily before plot.new() because when the plot size is small (e.g.
|
||||
# 200x50), we will get an error "figure margin too large", which is triggered
|
||||
# by plot.new() with the default (large) margin. However, this does not
|
||||
# guarantee user's code in func() will not trigger the error -- they may have
|
||||
# to set par(mar = smaller_value) before they draw base graphics.
|
||||
op <- graphics::par(mar = rep(0, 4))
|
||||
tryCatch(graphics::plot.new(), finally = graphics::par(op))
|
||||
dv <- grDevices::dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = grDevices::dev.off(dv))
|
||||
|
||||
filename
|
||||
}
|
||||
|
||||
51
R/input-action.R
Normal file
51
R/input-action.R
Normal file
@@ -0,0 +1,51 @@
|
||||
#' Action button/link
|
||||
#'
|
||||
#' Creates an action button or link whose value is initially zero, and increments by one
|
||||
#' each time it is pressed.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param label The contents of the button or link--usually a text label, but
|
||||
#' you could also use any other HTML, like an image.
|
||||
#' @param icon An optional \code{\link{icon}} to appear on the button.
|
||||
#' @param ... Named attributes to be applied to the button or link.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # In server.R
|
||||
#' output$distPlot <- renderPlot({
|
||||
#' # Take a dependency on input$goButton
|
||||
#' input$goButton
|
||||
#'
|
||||
#' # Use isolate() to avoid dependency on input$obs
|
||||
#' dist <- isolate(rnorm(input$obs))
|
||||
#' hist(dist)
|
||||
#' })
|
||||
#'
|
||||
#' # In ui.R
|
||||
#' actionButton("goButton", "Go!")
|
||||
#' }
|
||||
#'
|
||||
#' @seealso \code{\link{observeEvent}} and \code{\link{eventReactive}}
|
||||
#'
|
||||
#' @export
|
||||
actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
|
||||
tags$button(id=inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
type="button",
|
||||
class="btn btn-default action-button",
|
||||
list(icon, label),
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname actionButton
|
||||
#' @export
|
||||
actionLink <- function(inputId, label, icon = NULL, ...) {
|
||||
tags$a(id=inputId,
|
||||
href="#",
|
||||
class="action-button",
|
||||
list(icon, label),
|
||||
...
|
||||
)
|
||||
}
|
||||
26
R/input-checkbox.R
Normal file
26
R/input-checkbox.R
Normal file
@@ -0,0 +1,26 @@
|
||||
#' Checkbox Input Control
|
||||
#'
|
||||
#' Create a checkbox that can be used to specify logical values.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param value Initial value (\code{TRUE} or \code{FALSE}).
|
||||
#' @return A checkbox control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' checkboxInput("outliers", "Show outliers", FALSE)
|
||||
#' @export
|
||||
checkboxInput <- function(inputId, label, value = FALSE, width = NULL) {
|
||||
inputTag <- tags$input(id = inputId, type="checkbox")
|
||||
if (!is.null(value) && value)
|
||||
inputTag$attribs$checked <- "checked"
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
div(class = "checkbox",
|
||||
tags$label(inputTag, tags$span(label))
|
||||
)
|
||||
)
|
||||
}
|
||||
45
R/input-checkboxgroup.R
Normal file
45
R/input-checkboxgroup.R
Normal file
@@ -0,0 +1,45 @@
|
||||
#' Checkbox Group Input Control
|
||||
#'
|
||||
#' Create a group of checkboxes that can be used to toggle multiple choices
|
||||
#' independently. The server will receive the input as a character vector of the
|
||||
#' selected values.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to show checkboxes for. If elements of the list
|
||||
#' are named then that name rather than the value is displayed to the user.
|
||||
#' @param selected The values that should be initially selected, if any.
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @return A list of HTML elements that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' checkboxGroupInput("variable", "Variable:",
|
||||
#' c("Cylinders" = "cyl",
|
||||
#' "Transmission" = "am",
|
||||
#' "Gears" = "gear"))
|
||||
#'
|
||||
#' @export
|
||||
checkboxGroupInput <- function(inputId, label, choices, selected = NULL,
|
||||
inline = FALSE, width = NULL) {
|
||||
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- generateOptions(inputId, choices, selected, inline)
|
||||
|
||||
divClass <- "form-group shiny-input-checkboxgroup shiny-input-container"
|
||||
if (inline)
|
||||
divClass <- paste(divClass, "shiny-input-container-inline")
|
||||
|
||||
# return label and select tag
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
class = divClass,
|
||||
controlLabel(inputId, label),
|
||||
options
|
||||
)
|
||||
}
|
||||
102
R/input-date.R
Normal file
102
R/input-date.R
Normal file
@@ -0,0 +1,102 @@
|
||||
#' Create date input
|
||||
#'
|
||||
#' Creates a text input which, when clicked on, brings up a calendar that
|
||||
#' the user can click on to select dates.
|
||||
#'
|
||||
#' The date \code{format} string specifies how the date will be displayed in
|
||||
#' the browser. It allows the following values:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item \code{yy} Year without century (12)
|
||||
#' \item \code{yyyy} Year with century (2012)
|
||||
#' \item \code{mm} Month number, with leading zero (01-12)
|
||||
#' \item \code{m} Month number, without leading zero (01-12)
|
||||
#' \item \code{M} Abbreviated month name
|
||||
#' \item \code{MM} Full month name
|
||||
#' \item \code{dd} Day of month with leading zero
|
||||
#' \item \code{d} Day of month without leading zero
|
||||
#' \item \code{D} Abbreviated weekday name
|
||||
#' \item \code{DD} Full weekday name
|
||||
#' }
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param value The starting date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param min The minimum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param max The maximum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param format The format of the date to display in the browser. Defaults to
|
||||
#' \code{"yyyy-mm-dd"}.
|
||||
#' @param startview The date range shown when the input object is first
|
||||
#' clicked. Can be "month" (the default), "year", or "decade".
|
||||
#' @param weekstart Which day is the start of the week. Should be an integer
|
||||
#' from 0 (Sunday) to 6 (Saturday).
|
||||
#' @param language The language used for month and day names. Default is "en".
|
||||
#' Other valid values include "bg", "ca", "cs", "da", "de", "el", "es", "fi",
|
||||
#' "fr", "he", "hr", "hu", "id", "is", "it", "ja", "kr", "lt", "lv", "ms",
|
||||
#' "nb", "nl", "pl", "pt", "pt-BR", "ro", "rs", "rs-latin", "ru", "sk", "sl",
|
||||
#' "sv", "sw", "th", "tr", "uk", "zh-CN", and "zh-TW".
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' dateInput("date", "Date:", value = "2012-02-29")
|
||||
#'
|
||||
#' # Default value is the date in client's time zone
|
||||
#' dateInput("date", "Date:")
|
||||
#'
|
||||
#' # value is always yyyy-mm-dd, even if the display format is different
|
||||
#' dateInput("date", "Date:", value = "2012-02-29", format = "mm/dd/yy")
|
||||
#'
|
||||
#' # Pass in a Date object
|
||||
#' dateInput("date", "Date:", value = Sys.Date()-10)
|
||||
#'
|
||||
#' # Use different language and different first day of week
|
||||
#' dateInput("date", "Date:",
|
||||
#' language = "de",
|
||||
#' weekstart = 1)
|
||||
#'
|
||||
#' # Start with decade view instead of default month view
|
||||
#' dateInput("date", "Date:",
|
||||
#' startview = "decade")
|
||||
#'
|
||||
#' @export
|
||||
dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
|
||||
format = "yyyy-mm-dd", startview = "month", weekstart = 0, language = "en",
|
||||
width = NULL) {
|
||||
|
||||
# If value is a date object, convert it to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
if (inherits(value, "Date")) value <- format(value, "%Y-%m-%d")
|
||||
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
|
||||
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
|
||||
|
||||
attachDependencies(
|
||||
tags$div(id = inputId,
|
||||
class = "shiny-date-input form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
controlLabel(inputId, label),
|
||||
tags$input(type = "text",
|
||||
# datepicker class necessary for dropdown to display correctly
|
||||
class = "form-control datepicker",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = value
|
||||
)
|
||||
),
|
||||
datePickerDependency
|
||||
)
|
||||
}
|
||||
|
||||
datePickerDependency <- htmlDependency(
|
||||
"bootstrap-datepicker", "1.0.2", c(href = "shared/datepicker"),
|
||||
script = "js/bootstrap-datepicker.min.js",
|
||||
stylesheet = "css/datepicker.css")
|
||||
113
R/input-daterange.R
Normal file
113
R/input-daterange.R
Normal file
@@ -0,0 +1,113 @@
|
||||
#' Create date range input
|
||||
#'
|
||||
#' Creates a pair of text inputs which, when clicked on, bring up calendars that
|
||||
#' the user can click on to select dates.
|
||||
#'
|
||||
#' The date \code{format} string specifies how the date will be displayed in
|
||||
#' the browser. It allows the following values:
|
||||
#'
|
||||
#' \itemize{
|
||||
#' \item \code{yy} Year without century (12)
|
||||
#' \item \code{yyyy} Year with century (2012)
|
||||
#' \item \code{mm} Month number, with leading zero (01-12)
|
||||
#' \item \code{m} Month number, without leading zero (01-12)
|
||||
#' \item \code{M} Abbreviated month name
|
||||
#' \item \code{MM} Full month name
|
||||
#' \item \code{dd} Day of month with leading zero
|
||||
#' \item \code{d} Day of month without leading zero
|
||||
#' \item \code{D} Abbreviated weekday name
|
||||
#' \item \code{DD} Full weekday name
|
||||
#' }
|
||||
#'
|
||||
#' @inheritParams dateInput
|
||||
#' @param start The initial start date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param end The initial end date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format. If NULL (the default), will use the current
|
||||
#' date in the client's time zone.
|
||||
#' @param separator String to display between the start and end input boxes.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' dateRangeInput("daterange", "Date range:",
|
||||
#' start = "2001-01-01",
|
||||
#' end = "2010-12-31")
|
||||
#'
|
||||
#' # Default start and end is the current date in the client's time zone
|
||||
#' dateRangeInput("daterange", "Date range:")
|
||||
#'
|
||||
#' # start and end are always specified in yyyy-mm-dd, even if the display
|
||||
#' # format is different
|
||||
#' dateRangeInput("daterange", "Date range:",
|
||||
#' start = "2001-01-01",
|
||||
#' end = "2010-12-31",
|
||||
#' min = "2001-01-01",
|
||||
#' max = "2012-12-21",
|
||||
#' format = "mm/dd/yy",
|
||||
#' separator = " - ")
|
||||
#'
|
||||
#' # Pass in Date objects
|
||||
#' dateRangeInput("daterange", "Date range:",
|
||||
#' start = Sys.Date()-10,
|
||||
#' end = Sys.Date()+10)
|
||||
#'
|
||||
#' # Use different language and different first day of week
|
||||
#' dateRangeInput("daterange", "Date range:",
|
||||
#' language = "de",
|
||||
#' weekstart = 1)
|
||||
#'
|
||||
#' # Start with decade view instead of default month view
|
||||
#' dateRangeInput("daterange", "Date range:",
|
||||
#' startview = "decade")
|
||||
#'
|
||||
#' @export
|
||||
dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
min = NULL, max = NULL, format = "yyyy-mm-dd", startview = "month",
|
||||
weekstart = 0, language = "en", separator = " to ", width = NULL) {
|
||||
|
||||
# If start and end are date objects, convert to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
if (inherits(start, "Date")) start <- format(start, "%Y-%m-%d")
|
||||
if (inherits(end, "Date")) end <- format(end, "%Y-%m-%d")
|
||||
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
|
||||
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
|
||||
|
||||
attachDependencies(
|
||||
div(id = inputId,
|
||||
class = "shiny-date-range-input form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
controlLabel(inputId, label),
|
||||
# input-daterange class is needed for dropdown behavior
|
||||
div(class = "input-daterange input-group",
|
||||
tags$input(
|
||||
class = "input-sm form-control",
|
||||
type = "text",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = start
|
||||
),
|
||||
span(class = "input-group-addon", separator),
|
||||
tags$input(
|
||||
class = "input-sm form-control",
|
||||
type = "text",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
`data-max-date` = max,
|
||||
`data-initial-date` = end
|
||||
)
|
||||
)
|
||||
),
|
||||
datePickerDependency
|
||||
)
|
||||
}
|
||||
51
R/input-file.R
Normal file
51
R/input-file.R
Normal file
@@ -0,0 +1,51 @@
|
||||
#' File Upload Control
|
||||
#'
|
||||
#' Create a file upload control that can be used to upload one or more files.
|
||||
#'
|
||||
#' Whenever a file upload completes, the corresponding input variable is set
|
||||
#' to a dataframe. This dataframe contains one row for each selected file, and
|
||||
#' the following columns:
|
||||
#' \describe{
|
||||
#' \item{\code{name}}{The filename provided by the web browser. This is
|
||||
#' \strong{not} the path to read to get at the actual data that was uploaded
|
||||
#' (see
|
||||
#' \code{datapath} column).}
|
||||
#' \item{\code{size}}{The size of the uploaded data, in
|
||||
#' bytes.}
|
||||
#' \item{\code{type}}{The MIME type reported by the browser (for example,
|
||||
#' \code{text/plain}), or empty string if the browser didn't know.}
|
||||
#' \item{\code{datapath}}{The path to a temp file that contains the data that was
|
||||
#' uploaded. This file may be deleted if the user performs another upload
|
||||
#' operation.}
|
||||
#' }
|
||||
#'
|
||||
#' @family input elements
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param multiple Whether the user should be allowed to select and upload
|
||||
#' multiple files at once. \bold{Does not work on older browsers, including
|
||||
#' Internet Explorer 9 and earlier.}
|
||||
#' @param accept A character vector of MIME types; gives the browser a hint of
|
||||
#' what kind of files the server is expecting.
|
||||
#'
|
||||
#' @export
|
||||
fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
width = NULL) {
|
||||
|
||||
inputTag <- tags$input(id = inputId, name = inputId, type = "file")
|
||||
if (multiple)
|
||||
inputTag$attribs$multiple <- "multiple"
|
||||
if (length(accept) > 0)
|
||||
inputTag$attribs$accept <- paste(accept, collapse=',')
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label),
|
||||
inputTag,
|
||||
tags$div(
|
||||
id=paste(inputId, "_progress", sep=""),
|
||||
class="progress progress-striped active shiny-file-input-progress",
|
||||
tags$div(class="progress-bar")
|
||||
)
|
||||
)
|
||||
}
|
||||
37
R/input-numeric.R
Normal file
37
R/input-numeric.R
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
#' Create a numeric input control
|
||||
#'
|
||||
#' Create an input control for entry of numeric values
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param min Minimum allowed value
|
||||
#' @param max Maximum allowed value
|
||||
#' @param step Interval to use when stepping between min and max
|
||||
#' @return A numeric input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateNumericInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' numericInput("obs", "Observations:", 10,
|
||||
#' min = 1, max = 100)
|
||||
#' @export
|
||||
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
|
||||
width = NULL) {
|
||||
|
||||
# build input tag
|
||||
inputTag <- tags$input(id = inputId, type = "number", class="form-control",
|
||||
value = formatNoSci(value))
|
||||
if (!is.na(min))
|
||||
inputTag$attribs$min = min
|
||||
if (!is.na(max))
|
||||
inputTag$attribs$max = max
|
||||
if (!is.na(step))
|
||||
inputTag$attribs$step = step
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
inputTag
|
||||
)
|
||||
}
|
||||
20
R/input-password.R
Normal file
20
R/input-password.R
Normal file
@@ -0,0 +1,20 @@
|
||||
#' Create a password input control
|
||||
#'
|
||||
#' Create an password control for entry of passwords.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @return A text input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateTextInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' passwordInput("password", "Password:")
|
||||
#' @export
|
||||
passwordInput <- function(inputId, label, value = "", width = NULL) {
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
tags$input(id = inputId, type="password", class="form-control", value=value)
|
||||
)
|
||||
}
|
||||
54
R/input-radiobuttons.R
Normal file
54
R/input-radiobuttons.R
Normal file
@@ -0,0 +1,54 @@
|
||||
#' Create radio buttons
|
||||
#'
|
||||
#' Create a set of radio buttons used to select an item from a list.
|
||||
#'
|
||||
#' If you need to represent a "None selected" state, it's possible to default
|
||||
#' the radio buttons to have no options selected by using
|
||||
#' \code{selected = character(0)}. However, this is not recommended, as it gives
|
||||
#' the user no way to return to that state once they've made a selection.
|
||||
#' Instead, consider having the first of your choices be \code{c("None selected"
|
||||
#' = "")}.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from (if elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user)
|
||||
#' @param selected The initially selected value (if not specified then
|
||||
#' defaults to the first value)
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @return A set of radio buttons that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateRadioButtons}}
|
||||
#'
|
||||
#' @examples
|
||||
#' radioButtons("dist", "Distribution type:",
|
||||
#' c("Normal" = "norm",
|
||||
#' "Uniform" = "unif",
|
||||
#' "Log-normal" = "lnorm",
|
||||
#' "Exponential" = "exp"))
|
||||
#' @export
|
||||
radioButtons <- function(inputId, label, choices, selected = NULL,
|
||||
inline = FALSE, width = NULL) {
|
||||
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
|
||||
# default value if it's not specified
|
||||
selected <- if (is.null(selected)) choices[[1]] else {
|
||||
validateSelected(selected, choices, inputId)
|
||||
}
|
||||
if (length(selected) > 1) stop("The 'selected' argument must be of length 1")
|
||||
|
||||
options <- generateOptions(inputId, choices, selected, inline, type = 'radio')
|
||||
|
||||
divClass <- "form-group shiny-input-radiogroup shiny-input-container"
|
||||
if (inline)
|
||||
divClass <- paste(divClass, "shiny-input-container-inline")
|
||||
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
class = divClass,
|
||||
controlLabel(inputId, label),
|
||||
options
|
||||
)
|
||||
}
|
||||
167
R/input-select.R
Normal file
167
R/input-select.R
Normal file
@@ -0,0 +1,167 @@
|
||||
#' Create a select list input control
|
||||
#'
|
||||
#' Create a select list that can be used to choose a single or multiple items
|
||||
#' from a list of values.
|
||||
#'
|
||||
#' By default, \code{selectInput()} and \code{selectizeInput()} use the
|
||||
#' JavaScript library \pkg{selectize.js}
|
||||
#' (\url{https://github.com/brianreavis/selectize.js}) to instead of the basic
|
||||
#' select input element. To use the standard HTML select input element, use
|
||||
#' \code{selectInput()} with \code{selectize=FALSE}.
|
||||
#'
|
||||
#' In selectize mode, if the first element in \code{choices} has a value of
|
||||
#' \code{""}, its name will be treated as a placeholder prompt. For example:
|
||||
#' \code{selectInput("letter", "Letter", c("Choose one" = "", LETTERS))}
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from. If elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user.
|
||||
#' @param selected The initially selected value (or multiple values if
|
||||
#' \code{multiple = TRUE}). If not specified then defaults to the first value
|
||||
#' for single-select lists and no values for multiple select lists.
|
||||
#' @param multiple Is selection of multiple items allowed?
|
||||
#' @param selectize Whether to use \pkg{selectize.js} or not.
|
||||
#' @param size Number of items to show in the selection box; a larger number
|
||||
#' will result in a taller box. Not compatible with \code{selectize=TRUE}.
|
||||
#' Normally, when \code{multiple=FALSE}, a select input will be a drop-down
|
||||
#' list, but when \code{size} is set, it will be a box instead.
|
||||
#' @return A select list control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSelectInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' selectInput("variable", "Variable:",
|
||||
#' c("Cylinders" = "cyl",
|
||||
#' "Transmission" = "am",
|
||||
#' "Gears" = "gear"))
|
||||
#' @export
|
||||
selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
multiple = FALSE, selectize = TRUE, width = NULL,
|
||||
size = NULL) {
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
|
||||
# default value if it's not specified
|
||||
if (is.null(selected)) {
|
||||
if (!multiple) selected <- firstChoice(choices)
|
||||
} else selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
if (!is.null(size) && selectize) {
|
||||
stop("'size' argument is incompatible with 'selectize=TRUE'.")
|
||||
}
|
||||
|
||||
# create select tag and add options
|
||||
selectTag <- tags$select(
|
||||
id = inputId,
|
||||
class = if (!selectize) "form-control",
|
||||
size = size,
|
||||
selectOptions(choices, selected)
|
||||
)
|
||||
if (multiple)
|
||||
selectTag$attribs$multiple <- "multiple"
|
||||
|
||||
# return label and select tag
|
||||
res <- div(
|
||||
class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
controlLabel(inputId, label),
|
||||
div(selectTag)
|
||||
)
|
||||
|
||||
if (!selectize) return(res)
|
||||
|
||||
selectizeIt(inputId, res, NULL, nonempty = !multiple && !("" %in% choices))
|
||||
}
|
||||
|
||||
firstChoice <- function(choices) {
|
||||
if (length(choices) == 0L) return()
|
||||
choice <- choices[[1]]
|
||||
if (is.list(choice)) firstChoice(choice) else choice
|
||||
}
|
||||
|
||||
# Create tags for each of the options; use <optgroup> if necessary.
|
||||
# This returns a HTML string instead of tags, because of the 'selected'
|
||||
# attribute.
|
||||
selectOptions <- function(choices, selected = NULL) {
|
||||
html <- mapply(choices, names(choices), FUN = function(choice, label) {
|
||||
if (is.list(choice)) {
|
||||
# If sub-list, create an optgroup and recurse into the sublist
|
||||
sprintf(
|
||||
'<optgroup label="%s">\n%s\n</optgroup>',
|
||||
htmlEscape(label),
|
||||
selectOptions(choice, selected)
|
||||
)
|
||||
|
||||
} else {
|
||||
# If single item, just return option string
|
||||
sprintf(
|
||||
'<option value="%s"%s>%s</option>',
|
||||
htmlEscape(choice),
|
||||
if (choice %in% selected) ' selected' else '',
|
||||
htmlEscape(label)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
HTML(paste(html, collapse = '\n'))
|
||||
}
|
||||
|
||||
# need <optgroup> when choices contains sub-lists
|
||||
needOptgroup <- function(choices) {
|
||||
any(vapply(choices, is.list, logical(1)))
|
||||
}
|
||||
|
||||
#' @rdname selectInput
|
||||
#' @param ... Arguments passed to \code{selectInput()}.
|
||||
#' @param options A list of options. See the documentation of \pkg{selectize.js}
|
||||
#' for possible options (character option values inside \code{\link{I}()} will
|
||||
#' be treated as literal JavaScript code; see \code{\link{renderDataTable}()}
|
||||
#' for details).
|
||||
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @note The selectize input created from \code{selectizeInput()} allows
|
||||
#' deletion of the selected option even in a single select input, which will
|
||||
#' return an empty string as its value. This is the default behavior of
|
||||
#' \pkg{selectize.js}. However, the selectize input created from
|
||||
#' \code{selectInput(..., selectize = TRUE)} will ignore the empty string
|
||||
#' value when it is a single choice input and the empty string is not in the
|
||||
#' \code{choices} argument. This is to keep compatibility with
|
||||
#' \code{selectInput(..., selectize = FALSE)}.
|
||||
#' @export
|
||||
selectizeInput <- function(inputId, ..., options = NULL, width = NULL) {
|
||||
selectizeIt(
|
||||
inputId,
|
||||
selectInput(inputId, ..., selectize = FALSE, width = width),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
# given a select input and its id, selectize it
|
||||
selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
|
||||
res <- checkAsIs(options)
|
||||
|
||||
selectizeDep <- htmlDependency(
|
||||
"selectize", "0.11.2", c(href = "shared/selectize"),
|
||||
stylesheet = "css/selectize.bootstrap3.css",
|
||||
head = format(tagList(
|
||||
HTML('<!--[if lt IE 9]>'),
|
||||
tags$script(src = 'shared/selectize/js/es5-shim.min.js'),
|
||||
HTML('<![endif]-->'),
|
||||
tags$script(src = 'shared/selectize/js/selectize.min.js')
|
||||
))
|
||||
)
|
||||
|
||||
# Insert script on same level as <select> tag
|
||||
select$children[[2]] <- tagAppendChild(
|
||||
select$children[[2]],
|
||||
tags$script(
|
||||
type = 'application/json',
|
||||
`data-for` = inputId, `data-nonempty` = if (nonempty) '',
|
||||
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
|
||||
if (length(res$options)) HTML(toJSON(res$options)) else '{}'
|
||||
)
|
||||
)
|
||||
|
||||
attachDependencies(select, selectizeDep)
|
||||
}
|
||||
234
R/input-slider.R
Normal file
234
R/input-slider.R
Normal file
@@ -0,0 +1,234 @@
|
||||
#' Slider Input Widget
|
||||
#'
|
||||
#' Constructs a slider widget to select a numeric value from a range.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param min The minimum value (inclusive) that can be selected.
|
||||
#' @param max The maximum value (inclusive) that can be selected.
|
||||
#' @param value The initial value of the slider. A numeric vector of length one
|
||||
#' will create a regular slider; a numeric vector of length two will create a
|
||||
#' double-ended range slider. A warning will be issued if the value doesn't
|
||||
#' fit between \code{min} and \code{max}.
|
||||
#' @param step Specifies the interval between each selectable value on the
|
||||
#' slider (if \code{NULL}, a heuristic is used to determine the step size). If
|
||||
#' the values are dates, \code{step} is in days; if the values are times
|
||||
#' (POSIXt), \code{step} is in seconds.
|
||||
#' @param round \code{TRUE} to round all values to the nearest integer;
|
||||
#' \code{FALSE} if no rounding is desired; or an integer to round to that
|
||||
#' number of digits (for example, 1 will round to the nearest 10, and -2 will
|
||||
#' round to the nearest .01). Any rounding will be applied after snapping to
|
||||
#' the nearest step.
|
||||
#' @param format Deprecated.
|
||||
#' @param locale Deprecated.
|
||||
#' @param ticks \code{FALSE} to hide tick marks, \code{TRUE} to show them
|
||||
#' according to some simple heuristics.
|
||||
#' @param animate \code{TRUE} to show simple animation controls with default
|
||||
#' settings; \code{FALSE} not to; or a custom settings list, such as those
|
||||
#' created using \code{\link{animationOptions}}.
|
||||
#' @param sep Separator between thousands places in numbers.
|
||||
#' @param pre A prefix string to put in front of the value.
|
||||
#' @param post A suffix string to put after the value.
|
||||
#' @param dragRange This option is used only if it is a range slider (with two
|
||||
#' values). If \code{TRUE} (the default), the range can be dragged. In other
|
||||
#' words, the min and max can be dragged together. If \code{FALSE}, the range
|
||||
#' cannot be dragged.
|
||||
#' @param timeFormat Only used if the values are Date or POSIXt objects. A time
|
||||
#' format string, to be passed to the Javascript strftime library. See
|
||||
#' \url{https://github.com/samsonjs/strftime} for more details. The allowed
|
||||
#' format specifications are very similar, but not identical, to those for R's
|
||||
#' \code{\link{strftime}} function. For Dates, the default is \code{"\%F"}
|
||||
#' (like \code{"2015-07-01"}), and for POSIXt, the default is \code{"\%F \%T"}
|
||||
#' (like \code{"2015-07-01 15:32:10"}).
|
||||
#' @param timezone Only used if the values are POSIXt objects. A string
|
||||
#' specifying the time zone offset for the displayed times, in the format
|
||||
#' \code{"+HHMM"} or \code{"-HHMM"}. If \code{NULL} (the default), times will
|
||||
#' be displayed in the browser's time zone. The value \code{"+0000"} will
|
||||
#' result in UTC time.
|
||||
#' @inheritParams selectizeInput
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateSliderInput}}
|
||||
#'
|
||||
#' @export
|
||||
sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
round = FALSE, format = NULL, locale = NULL,
|
||||
ticks = TRUE, animate = FALSE, width = NULL, sep = ",",
|
||||
pre = NULL, post = NULL, timeFormat = NULL,
|
||||
timezone = NULL, dragRange = TRUE)
|
||||
{
|
||||
if (!missing(format)) {
|
||||
shinyDeprecated(msg = "The `format` argument to sliderInput is deprecated. Use `sep`, `pre`, and `post` instead.",
|
||||
version = "0.10.2.2")
|
||||
}
|
||||
if (!missing(locale)) {
|
||||
shinyDeprecated(msg = "The `locale` argument to sliderInput is deprecated. Use `sep`, `pre`, and `post` instead.",
|
||||
version = "0.10.2.2")
|
||||
}
|
||||
|
||||
# If step is NULL, use heuristic to set the step size.
|
||||
findStepSize <- function(min, max, step) {
|
||||
if (!is.null(step)) return(step)
|
||||
|
||||
range <- max - min
|
||||
# If short range or decimals, use continuous decimal with ~100 points
|
||||
if (range < 2 || hasDecimals(min) || hasDecimals(max)) {
|
||||
step <- pretty(c(min, max), n = 100)
|
||||
step[2] - step[1]
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
if (inherits(min, "Date")) {
|
||||
if (!inherits(max, "Date") || !inherits(value, "Date"))
|
||||
stop("`min`, `max`, and `value must all be Date or non-Date objects")
|
||||
dataType <- "date"
|
||||
|
||||
if (is.null(timeFormat))
|
||||
timeFormat <- "%F"
|
||||
|
||||
} else if (inherits(min, "POSIXt")) {
|
||||
if (!inherits(max, "POSIXt") || !inherits(value, "POSIXt"))
|
||||
stop("`min`, `max`, and `value must all be POSIXt or non-POSIXt objects")
|
||||
dataType <- "datetime"
|
||||
|
||||
if (is.null(timeFormat))
|
||||
timeFormat <- "%F %T"
|
||||
|
||||
} else {
|
||||
dataType <- "number"
|
||||
}
|
||||
|
||||
step <- findStepSize(min, max, step)
|
||||
|
||||
if (dataType %in% c("date", "datetime")) {
|
||||
# For Dates, this conversion uses midnight on that date in UTC
|
||||
to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
|
||||
|
||||
# Convert values to milliseconds since epoch (this is the value JS uses)
|
||||
# Find step size in ms
|
||||
step <- to_ms(max) - to_ms(max - step)
|
||||
min <- to_ms(min)
|
||||
max <- to_ms(max)
|
||||
value <- to_ms(value)
|
||||
}
|
||||
|
||||
range <- max - min
|
||||
|
||||
# Try to get a sane number of tick marks
|
||||
if (ticks) {
|
||||
n_steps <- range / step
|
||||
|
||||
# Make sure there are <= 10 steps.
|
||||
# n_ticks can be a noninteger, which is good when the range is not an
|
||||
# integer multiple of the step size, e.g., min=1, max=10, step=4
|
||||
scale_factor <- ceiling(n_steps / 10)
|
||||
n_ticks <- n_steps / scale_factor
|
||||
|
||||
} else {
|
||||
n_ticks <- NULL
|
||||
}
|
||||
|
||||
sliderProps <- dropNulls(list(
|
||||
class = "js-range-slider",
|
||||
id = inputId,
|
||||
`data-type` = if (length(value) > 1) "double",
|
||||
`data-min` = formatNoSci(min),
|
||||
`data-max` = formatNoSci(max),
|
||||
`data-from` = formatNoSci(value[1]),
|
||||
`data-to` = if (length(value) > 1) formatNoSci(value[2]),
|
||||
`data-step` = formatNoSci(step),
|
||||
`data-grid` = ticks,
|
||||
`data-grid-num` = n_ticks,
|
||||
`data-grid-snap` = FALSE,
|
||||
`data-prettify-separator` = sep,
|
||||
`data-prefix` = pre,
|
||||
`data-postfix` = post,
|
||||
`data-keyboard` = TRUE,
|
||||
`data-keyboard-step` = step / (max - min) * 100,
|
||||
`data-drag-interval` = dragRange,
|
||||
# The following are ignored by the ion.rangeSlider, but are used by Shiny.
|
||||
`data-data-type` = dataType,
|
||||
`data-time-format` = timeFormat,
|
||||
`data-timezone` = timezone
|
||||
))
|
||||
|
||||
# Replace any TRUE and FALSE with "true" and "false"
|
||||
sliderProps <- lapply(sliderProps, function(x) {
|
||||
if (identical(x, TRUE)) "true"
|
||||
else if (identical(x, FALSE)) "false"
|
||||
else x
|
||||
})
|
||||
|
||||
sliderTag <- div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
if (!is.null(label)) controlLabel(inputId, label),
|
||||
do.call(tags$input, sliderProps)
|
||||
)
|
||||
|
||||
# Add animation buttons
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
if (!is.null(animate) && !identical(animate, FALSE)) {
|
||||
if (is.null(animate$playButton))
|
||||
animate$playButton <- icon('play', lib = 'glyphicon')
|
||||
if (is.null(animate$pauseButton))
|
||||
animate$pauseButton <- icon('pause', lib = 'glyphicon')
|
||||
|
||||
sliderTag <- tagAppendChild(
|
||||
sliderTag,
|
||||
tags$div(class='slider-animate-container',
|
||||
tags$a(href='#',
|
||||
class='slider-animate-button',
|
||||
'data-target-id'=inputId,
|
||||
'data-interval'=animate$interval,
|
||||
'data-loop'=animate$loop,
|
||||
span(class = 'play', animate$playButton),
|
||||
span(class = 'pause', animate$pauseButton)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
dep <- list(
|
||||
htmlDependency("ionrangeslider", "2.0.12", c(href="shared/ionrangeslider"),
|
||||
script = "js/ion.rangeSlider.min.js",
|
||||
# ion.rangeSlider also needs normalize.css, which is already included in
|
||||
# Bootstrap.
|
||||
stylesheet = c("css/ion.rangeSlider.css",
|
||||
"css/ion.rangeSlider.skinShiny.css")
|
||||
),
|
||||
htmlDependency("strftime", "0.9.2", c(href="shared/strftime"),
|
||||
script = "strftime-min.js"
|
||||
)
|
||||
)
|
||||
|
||||
attachDependencies(sliderTag, dep)
|
||||
}
|
||||
|
||||
hasDecimals <- function(value) {
|
||||
truncatedValue <- round(value)
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
#' @param interval The interval, in milliseconds, between each animation step.
|
||||
#' @param loop \code{TRUE} to automatically restart the animation when it
|
||||
#' reaches the end.
|
||||
#' @param playButton Specifies the appearance of the play button. Valid values
|
||||
#' are a one-element character vector (for a simple text label), an HTML tag
|
||||
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
|
||||
#' \code{\link{HTML}}).
|
||||
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
|
||||
#'
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
playButton=NULL,
|
||||
pauseButton=NULL) {
|
||||
list(interval=interval,
|
||||
loop=loop,
|
||||
playButton=playButton,
|
||||
pauseButton=pauseButton)
|
||||
}
|
||||
28
R/input-submit.R
Normal file
28
R/input-submit.R
Normal file
@@ -0,0 +1,28 @@
|
||||
#' Create a submit button
|
||||
#'
|
||||
#' Create a submit button for an input form. Forms that include a submit
|
||||
#' button do not automatically update their outputs when inputs change,
|
||||
#' rather they wait until the user explicitly clicks the submit button.
|
||||
#'
|
||||
#' @param text Button caption
|
||||
#' @param icon Optional \code{\link{icon}} to appear on the button
|
||||
#' @param width The width of the button, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @return A submit button that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#'
|
||||
#' @examples
|
||||
#' submitButton("Update View")
|
||||
#' submitButton("Update View", icon("refresh"))
|
||||
#' @export
|
||||
submitButton <- function(text = "Apply Changes", icon = NULL, width = NULL) {
|
||||
div(
|
||||
tags$button(
|
||||
type="submit",
|
||||
class="btn btn-primary",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
list(icon, text)
|
||||
)
|
||||
)
|
||||
}
|
||||
24
R/input-text.R
Normal file
24
R/input-text.R
Normal file
@@ -0,0 +1,24 @@
|
||||
#' Create a text input control
|
||||
#'
|
||||
#' Create an input control for entry of unstructured text values
|
||||
#'
|
||||
#' @param inputId The \code{input} slot that will be used to access the value.
|
||||
#' @param label Display label for the control, or \code{NULL} for no label.
|
||||
#' @param value Initial value.
|
||||
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
#' see \code{\link{validateCssUnit}}.
|
||||
#' @return A text input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateTextInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' textInput("caption", "Caption:", "Data Summary")
|
||||
#' @export
|
||||
textInput <- function(inputId, label, value = "", width = NULL) {
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
label %AND% tags$label(label, `for` = inputId),
|
||||
tags$input(id = inputId, type="text", class="form-control", value=value)
|
||||
)
|
||||
}
|
||||
105
R/input-utils.R
Normal file
105
R/input-utils.R
Normal file
@@ -0,0 +1,105 @@
|
||||
controlLabel <- function(controlName, label) {
|
||||
label %AND% tags$label(class = "control-label", `for` = controlName, label)
|
||||
}
|
||||
|
||||
|
||||
# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it
|
||||
# refers to values. Below is a function for backward compatibility.
|
||||
validateSelected <- function(selected, choices, inputId) {
|
||||
# drop names, otherwise toJSON() keeps them too
|
||||
selected <- unname(selected)
|
||||
# if you are using optgroups, you're using shiny > 0.10.0, and you should
|
||||
# already know that `selected` must be a value instead of a label
|
||||
if (needOptgroup(choices)) return(selected)
|
||||
|
||||
if (is.list(choices)) choices <- unlist(choices)
|
||||
|
||||
nms <- names(choices)
|
||||
# labels and values are identical, no need to validate
|
||||
if (identical(nms, unname(choices))) return(selected)
|
||||
# when selected labels instead of values
|
||||
i <- (selected %in% nms) & !(selected %in% choices)
|
||||
if (any(i)) {
|
||||
warnFun <- if (all(i)) {
|
||||
# replace names with values
|
||||
selected <- unname(choices[selected])
|
||||
warning
|
||||
} else stop # stop when it is ambiguous (some labels == values)
|
||||
warnFun("'selected' must be the values instead of names of 'choices' ",
|
||||
"for the input '", inputId, "'")
|
||||
}
|
||||
selected
|
||||
}
|
||||
|
||||
|
||||
# generate options for radio buttons and checkbox groups (type = 'checkbox' or
|
||||
# 'radio')
|
||||
generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox') {
|
||||
# generate a list of <input type=? [checked] />
|
||||
options <- mapply(
|
||||
choices, names(choices),
|
||||
FUN = function(value, name) {
|
||||
inputTag <- tags$input(
|
||||
type = type, name = inputId, value = value
|
||||
)
|
||||
if (value %in% selected)
|
||||
inputTag$attribs$checked <- "checked"
|
||||
|
||||
# If inline, there's no wrapper div, and the label needs a class like
|
||||
# checkbox-inline.
|
||||
if (inline) {
|
||||
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
|
||||
} else {
|
||||
tags$div(class = type,
|
||||
tags$label(inputTag, tags$span(name))
|
||||
)
|
||||
}
|
||||
},
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE
|
||||
)
|
||||
|
||||
div(class = "shiny-options-group", options)
|
||||
}
|
||||
|
||||
|
||||
# Takes a vector or list, and adds names (same as the value) to any entries
|
||||
# without names.
|
||||
choicesWithNames <- function(choices) {
|
||||
# Take a vector or list, and convert to list. Also, if any children are
|
||||
# vectors with length > 1, convert those to list. If the list is unnamed,
|
||||
# convert it to a named list with blank names.
|
||||
listify <- function(obj) {
|
||||
# If a list/vector is unnamed, give it blank names
|
||||
makeNamed <- function(x) {
|
||||
if (is.null(names(x))) names(x) <- character(length(x))
|
||||
x
|
||||
}
|
||||
|
||||
res <- lapply(obj, function(val) {
|
||||
if (is.list(val))
|
||||
listify(val)
|
||||
else if (length(val) == 1 && is.null(names(val)))
|
||||
val
|
||||
else
|
||||
makeNamed(as.list(val))
|
||||
})
|
||||
|
||||
makeNamed(res)
|
||||
}
|
||||
|
||||
choices <- listify(choices)
|
||||
if (length(choices) == 0) return(choices)
|
||||
|
||||
# Recurse into any subgroups
|
||||
choices <- mapply(choices, names(choices), FUN = function(choice, name) {
|
||||
if (!is.list(choice)) return(choice)
|
||||
if (name == "") stop('All sub-lists in "choices" must be named.')
|
||||
choicesWithNames(choice)
|
||||
}, SIMPLIFY = FALSE)
|
||||
|
||||
# default missing names to choice values
|
||||
missing <- names(choices) == ""
|
||||
names(choices)[missing] <- as.character(choices)[missing]
|
||||
|
||||
choices
|
||||
}
|
||||
@@ -97,8 +97,8 @@ fixedPanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE,
|
||||
cursor = c('move', 'default', 'inherit')) {
|
||||
cursor = c('auto', 'move', 'default', 'inherit')) {
|
||||
absolutePanel(..., top=top, left=left, right=right, bottom=bottom,
|
||||
width=width, height=height, draggable=draggable, cursor=cursor,
|
||||
width=width, height=height, draggable=draggable, cursor=match.arg(cursor),
|
||||
fixed=TRUE)
|
||||
}
|
||||
|
||||
58
R/map.R
58
R/map.R
@@ -9,58 +9,68 @@
|
||||
# Remove of unknown key does nothing
|
||||
# Setting a key twice always results in last-one-wins
|
||||
# /TESTS
|
||||
Map <- setRefClass(
|
||||
Map <- R6Class(
|
||||
'Map',
|
||||
fields = list(
|
||||
.env = 'environment'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
initialize = function() {
|
||||
.env <<- new.env(parent=emptyenv())
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
},
|
||||
get = function(key) {
|
||||
if (.self$containsKey(key))
|
||||
base::get(key, pos=.env, inherits=FALSE)
|
||||
env[[key]]
|
||||
},
|
||||
set = function(key, value) {
|
||||
assign(key, value, pos=.env, inherits=FALSE)
|
||||
env[[key]] <- value
|
||||
value
|
||||
},
|
||||
mget = function(keys) {
|
||||
base::mget(keys, env)
|
||||
},
|
||||
mset = function(...) {
|
||||
args <- list(...)
|
||||
for (key in names(args))
|
||||
set(key, args[[key]])
|
||||
if (length(args) == 0)
|
||||
return()
|
||||
|
||||
arg_names <- names(args)
|
||||
if (is.null(arg_names) || any(!nzchar(arg_names)))
|
||||
stop("All elements must be named")
|
||||
|
||||
list2env(args, envir = env)
|
||||
},
|
||||
remove = function(key) {
|
||||
if (.self$containsKey(key)) {
|
||||
result <- .self$get(key)
|
||||
rm(list = key, pos=.env, inherits=FALSE)
|
||||
result
|
||||
}
|
||||
if (!self$containsKey(key))
|
||||
return(NULL)
|
||||
|
||||
result <- env[[key]]
|
||||
rm(list=key, envir=env, inherits=FALSE)
|
||||
result
|
||||
},
|
||||
containsKey = function(key) {
|
||||
exists(key, where=.env, inherits=FALSE)
|
||||
exists(key, envir=env, inherits=FALSE)
|
||||
},
|
||||
keys = function() {
|
||||
ls(envir=.env, all.names=TRUE)
|
||||
# Sadly, this is much faster than ls(), because it doesn't sort the keys.
|
||||
names(as.list(env, all.names=TRUE))
|
||||
},
|
||||
values = function() {
|
||||
mget(.self$keys(), envir=.env, inherits=FALSE)
|
||||
as.list(env, all.names=TRUE)
|
||||
},
|
||||
clear = function() {
|
||||
.env <<- new.env(parent=emptyenv())
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
invisible(NULL)
|
||||
},
|
||||
size = function() {
|
||||
length(.env)
|
||||
length(env)
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
env = 'environment'
|
||||
)
|
||||
)
|
||||
|
||||
as.list.Map <- function(map) {
|
||||
sapply(map$keys(),
|
||||
map$get,
|
||||
simplify=FALSE)
|
||||
map$values()
|
||||
}
|
||||
length.Map <- function(map) {
|
||||
map$size()
|
||||
|
||||
@@ -5,7 +5,7 @@ reactLogHandler <- function(req) {
|
||||
if (!identical(req$PATH_INFO, '/reactlog'))
|
||||
return(NULL)
|
||||
|
||||
if (!getOption('shiny.reactlog', FALSE)) {
|
||||
if (!isTRUE(getOption('shiny.reactlog'))) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ sessionHandler <- function(req) {
|
||||
subreq$PATH_INFO <- subpath
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, matches[[1]][2], sep='')
|
||||
|
||||
return(shinysession$handleRequest(subreq))
|
||||
withReactiveDomain(shinysession, {
|
||||
shinysession$handleRequest(subreq)
|
||||
})
|
||||
}
|
||||
|
||||
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
|
||||
@@ -21,7 +21,7 @@ httpResponse <- function(status = 200,
|
||||
# Make sure it's a list, not a vector
|
||||
headers <- as.list(headers)
|
||||
if (is.null(headers$`X-UA-Compatible`))
|
||||
headers$`X-UA-Compatible` <- "chrome=1"
|
||||
headers$`X-UA-Compatible` <- "IE=edge,chrome=1"
|
||||
resp <- list(status = status, content_type = content_type, content = content,
|
||||
headers = headers)
|
||||
class(resp) <- 'httpResponse'
|
||||
@@ -203,8 +203,7 @@ staticHandler <- function(root) {
|
||||
if (is.null(abs.path))
|
||||
return(NULL)
|
||||
|
||||
ext <- tools::file_ext(abs.path)
|
||||
content.type <- getContentType(ext)
|
||||
content.type <- getContentType(abs.path)
|
||||
response.content <- readBin(abs.path, 'raw', n=file.info(abs.path)$size)
|
||||
return(httpResponse(200, content.type, response.content))
|
||||
})
|
||||
@@ -222,11 +221,12 @@ staticHandler <- function(root) {
|
||||
# return value of `createHttpuvApp` to httpuv's `startServer` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
HandlerList <- setRefClass("HandlerList",
|
||||
fields = list(
|
||||
handlers = "list"
|
||||
),
|
||||
methods = list(
|
||||
HandlerList <- R6Class("HandlerList",
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
handlers = list(),
|
||||
|
||||
add = function(handler, key, tail = FALSE) {
|
||||
if (!is.null(handlers[[key]]))
|
||||
stop("Key ", key, " already in use")
|
||||
@@ -256,12 +256,18 @@ HandlerList <- setRefClass("HandlerList",
|
||||
)
|
||||
)
|
||||
|
||||
HandlerManager <- setRefClass("HandlerManager",
|
||||
fields = list(
|
||||
HandlerManager <- R6Class("HandlerManager",
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
handlers = "HandlerList",
|
||||
wsHandlers = "HandlerList"
|
||||
),
|
||||
methods = list(
|
||||
wsHandlers = "HandlerList",
|
||||
|
||||
initialize = function() {
|
||||
handlers <<- HandlerList$new()
|
||||
wsHandlers <<- HandlerList$new()
|
||||
},
|
||||
|
||||
addHandler = function(handler, key, tail = FALSE) {
|
||||
handlers$add(handler, key, tail)
|
||||
},
|
||||
@@ -281,7 +287,7 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
createHttpuvApp = function() {
|
||||
list(
|
||||
onHeaders = function(req) {
|
||||
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
|
||||
maxSize <- getOption('shiny.maxRequestSize') %OR% (5 * 1024 * 1024)
|
||||
if (maxSize <= 0)
|
||||
return(NULL)
|
||||
|
||||
@@ -306,7 +312,7 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
function (req) {
|
||||
return(handlers$invoke(req))
|
||||
},
|
||||
getOption('shiny.sharedSecret', NULL)
|
||||
getOption('shiny.sharedSecret')
|
||||
),
|
||||
onWSOpen = function(ws) {
|
||||
return(wsHandlers$invoke(ws))
|
||||
@@ -314,7 +320,7 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
)
|
||||
},
|
||||
.httpServer = function(handler, sharedSecret) {
|
||||
filter <- getOption('shiny.http.response.filter', NULL)
|
||||
filter <- getOption('shiny.http.response.filter')
|
||||
if (is.null(filter))
|
||||
filter <- function(req, response) response
|
||||
|
||||
@@ -326,18 +332,39 @@ HandlerManager <- setRefClass("HandlerManager",
|
||||
headers=list('Content-Type' = 'text/html')))
|
||||
}
|
||||
|
||||
# Catch HEAD requests. For the purposes of handler functions, they
|
||||
# should be treated like GET. The difference is that they shouldn't
|
||||
# return a body in the http response.
|
||||
head_request <- FALSE
|
||||
if (identical(req$REQUEST_METHOD, "HEAD")) {
|
||||
head_request <- TRUE
|
||||
req$REQUEST_METHOD <- "GET"
|
||||
}
|
||||
|
||||
response <- handler(req)
|
||||
if (is.null(response))
|
||||
response <- httpResponse(404, content="<h1>Not Found</h1>")
|
||||
|
||||
|
||||
if (inherits(response, "httpResponse")) {
|
||||
headers <- as.list(response$headers)
|
||||
headers$'Content-Type' <- response$content_type
|
||||
|
||||
|
||||
response <- filter(req, response)
|
||||
return(list(status=response$status,
|
||||
body=response$content,
|
||||
headers=headers))
|
||||
if (head_request) {
|
||||
headers$`Content-Length` <- nchar(response$content, type = "bytes")
|
||||
return(list(
|
||||
status = response$status,
|
||||
body = "",
|
||||
headers = headers
|
||||
))
|
||||
} else {
|
||||
return(list(
|
||||
status = response$status,
|
||||
body = response$content,
|
||||
headers = headers
|
||||
))
|
||||
}
|
||||
|
||||
} else {
|
||||
# Assume it's a Rook-compatible response
|
||||
return(response)
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
# elements have the same priority, they are served according to their order in
|
||||
# the queue." (http://en.wikipedia.org/wiki/Priority_queue)
|
||||
|
||||
PriorityQueue <- setRefClass(
|
||||
PriorityQueue <- R6Class(
|
||||
'PriorityQueue',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
# Keys are priorities, values are subqueues (implemented as list)
|
||||
.itemsByPriority = 'Map',
|
||||
# Sorted vector (largest first)
|
||||
.priorities = 'numeric'
|
||||
),
|
||||
methods = list(
|
||||
.priorities = numeric(0),
|
||||
|
||||
initialize = function() {
|
||||
.itemsByPriority <<- Map$new()
|
||||
},
|
||||
# Enqueue an item, with the given priority level (must be integer). Higher
|
||||
# priority numbers are dequeued earlier than lower.
|
||||
enqueue = function(item, priority) {
|
||||
|
||||
283
R/progress.R
Normal file
283
R/progress.R
Normal file
@@ -0,0 +1,283 @@
|
||||
#' Reporting progress (object-oriented API)
|
||||
#'
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with
|
||||
#' progress. \code{\link{withProgress}} and \code{\link{setProgress}}
|
||||
#' together provide a simple function-based interface, while the
|
||||
#' \code{Progress} reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Instantiating a \code{Progress} object causes a progress panel to be
|
||||
#' created, and it will be displayed the first time the \code{set}
|
||||
#' method is called. Calling \code{close} will cause the progress panel
|
||||
#' to be removed.
|
||||
#'
|
||||
#' \strong{Methods}
|
||||
#' \describe{
|
||||
#' \item{\code{initialize(session, min = 0, max = 1)}}{
|
||||
#' Creates a new progress panel (but does not display it).
|
||||
#' }
|
||||
#' \item{\code{set(value = NULL, message = NULL, detail = NULL)}}{
|
||||
#' Updates the progress panel. When called the first time, the
|
||||
#' progress panel is displayed.
|
||||
#' }
|
||||
#' \item{\code{inc(amount = 0.1, message = NULL, detail = NULL)}}{
|
||||
#' Like \code{set}, this updates the progress panel. The difference is
|
||||
#' that \code{inc} increases the progress bar by \code{amount}, instead
|
||||
#' of setting it to a specific value.
|
||||
#' }
|
||||
#' \item{\code{close()}}{
|
||||
#' Removes the progress panel. Future calls to \code{set} and
|
||||
#' \code{close} will be ignored.
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by
|
||||
#' \code{shinyServer} to the server function.
|
||||
#' @param min The value that represents the starting point of the
|
||||
#' progress bar. Must be less tham \code{max}.
|
||||
#' @param max The value that represents the end of the progress bar.
|
||||
#' Must be greater than \code{min}.
|
||||
#' @param message A single-element character vector; the message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current message
|
||||
#' (if any).
|
||||
#' @param detail A single-element character vector; the detail message
|
||||
#' to be displayed to the user, or \code{NULL} to hide the current
|
||||
#' detail message (if any). The detail message will be shown with a
|
||||
#' de-emphasized appearance relative to \code{message}.
|
||||
#' @param value A numeric value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' \code{NULL} hides the progress bar, if it is currently visible.
|
||||
#' @param amount Single-element numeric vector; the value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' \code{NULL} hides the progress bar, if it is currently visible.
|
||||
#' @param amount For the \code{inc()} method, a numeric value to increment the
|
||||
#' progress bar.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # server.R
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' progress <- shiny::Progress$new(session, min=1, max=15)
|
||||
#' on.exit(progress$close())
|
||||
#'
|
||||
#' progress$set(message = 'Calculation in progress',
|
||||
#' detail = 'This may take a while...')
|
||||
#'
|
||||
#' for (i in 1:15) {
|
||||
#' progress$set(value = i)
|
||||
#' Sys.sleep(0.5)
|
||||
#' }
|
||||
#' plot(cars)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @seealso \code{\link{withProgress}}
|
||||
#' @format NULL
|
||||
#' @usage NULL
|
||||
#' @export
|
||||
Progress <- R6Class(
|
||||
'Progress',
|
||||
portable = TRUE,
|
||||
public = list(
|
||||
|
||||
initialize = function(session = getDefaultReactiveDomain(), min = 0, max = 1) {
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
private$session <- session
|
||||
private$id <- paste(as.character(as.raw(stats::runif(8, min=0, max=255))), collapse='')
|
||||
private$min <- min
|
||||
private$max <- max
|
||||
private$value <- NULL
|
||||
private$closed <- FALSE
|
||||
|
||||
session$sendProgress('open', list(id = private$id))
|
||||
},
|
||||
|
||||
set = function(value = NULL, message = NULL, detail = NULL) {
|
||||
if (private$closed) {
|
||||
warning("Attempting to set progress, but progress already closed.")
|
||||
return()
|
||||
}
|
||||
|
||||
if (is.null(value) || is.na(value)) {
|
||||
value <- NULL
|
||||
} else {
|
||||
# Normalize value to number between 0 and 1
|
||||
value <- min(1, max(0, (value - private$min) / (private$max - private$min)))
|
||||
}
|
||||
|
||||
private$value <- value
|
||||
|
||||
data <- dropNulls(list(
|
||||
id = private$id,
|
||||
message = message,
|
||||
detail = detail,
|
||||
value = value
|
||||
))
|
||||
|
||||
private$session$sendProgress('update', data)
|
||||
},
|
||||
|
||||
inc = function(amount = 0.1, message = NULL, detail = NULL) {
|
||||
value <- min(self$getValue() + amount, private$max)
|
||||
self$set(value, message, detail)
|
||||
},
|
||||
|
||||
getMin = function() private$min,
|
||||
|
||||
getMax = function() private$max,
|
||||
|
||||
# Return value (not the normalized 0-1 value, but in the original range)
|
||||
getValue = function() {
|
||||
private$value * (private$max - private$min) + private$min
|
||||
},
|
||||
|
||||
close = function() {
|
||||
if (private$closed) {
|
||||
warning("Attempting to close progress, but progress already closed.")
|
||||
return()
|
||||
}
|
||||
|
||||
private$session$sendProgress('close', list(id = private$id))
|
||||
private$closed <- TRUE
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
session = 'environment',
|
||||
id = character(0),
|
||||
min = numeric(0),
|
||||
max = numeric(0),
|
||||
value = NULL,
|
||||
closed = logical(0)
|
||||
)
|
||||
)
|
||||
|
||||
#' Reporting progress (functional API)
|
||||
#'
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with progress.
|
||||
#' Using \code{withProgress} with \code{incProgress} or \code{setProgress}
|
||||
#' provide a simple function-based interface, while the \code{\link{Progress}}
|
||||
#' reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Use \code{withProgress} to wrap the scope of your work; doing so will cause a
|
||||
#' new progress panel to be created, and it will be displayed the first time
|
||||
#' \code{incProgress} or \code{setProgress} are called. When \code{withProgress}
|
||||
#' exits, the corresponding progress panel will be removed.
|
||||
#'
|
||||
#' The \code{incProgress} function increments the status bar by a specified
|
||||
#' amount, whereas the \code{setProgress} function sets it to a specific value,
|
||||
#' and can also set the text displayed.
|
||||
#'
|
||||
#' Generally, \code{withProgress}/\code{incProgress}/\code{setProgress} should
|
||||
#' be sufficient; the exception is if the work to be done is asynchronous (this
|
||||
#' is not common) or otherwise cannot be encapsulated by a single scope. In that
|
||||
#' case, you can use the \code{Progress} reference class.
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by \code{shinyServer} to
|
||||
#' the server function. The default is to automatically find the session by
|
||||
#' using the current reactive domain.
|
||||
#' @param expr The work to be done. This expression should contain calls to
|
||||
#' \code{setProgress}.
|
||||
#' @param min The value that represents the starting point of the progress bar.
|
||||
#' Must be less tham \code{max}. Default is 0.
|
||||
#' @param max The value that represents the end of the progress bar. Must be
|
||||
#' greater than \code{min}. Default is 1.
|
||||
#' @param amount For \code{incProgress}, the amount to increment the status bar.
|
||||
#' Default is 0.1.
|
||||
#' @param env The environment in which \code{expr} should be evaluated.
|
||||
#' @param quoted Whether \code{expr} is a quoted expression (this is not
|
||||
#' common).
|
||||
#' @param message A single-element character vector; the message to be displayed
|
||||
#' to the user, or \code{NULL} to hide the current message (if any).
|
||||
#' @param detail A single-element character vector; the detail message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current detail message
|
||||
#' (if any). The detail message will be shown with a de-emphasized appearance
|
||||
#' relative to \code{message}.
|
||||
#' @param value Single-element numeric vector; the value at which to set the
|
||||
#' progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
|
||||
#' progress bar, if it is currently visible.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # server.R
|
||||
#' shinyServer(function(input, output) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' withProgress(message = 'Calculation in progress',
|
||||
#' detail = 'This may take a while...', value = 0, {
|
||||
#' for (i in 1:15) {
|
||||
#' incProgress(1/15)
|
||||
#' Sys.sleep(0.25)
|
||||
#' }
|
||||
#' })
|
||||
#' plot(cars)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @seealso \code{\link{Progress}}
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
withProgress <- function(expr, min = 0, max = 1,
|
||||
value = min + (max - min) * 0.1,
|
||||
message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain(),
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
|
||||
if (!quoted)
|
||||
expr <- substitute(expr)
|
||||
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
p <- Progress$new(session, min = min, max = max)
|
||||
|
||||
session$progressStack$push(p)
|
||||
on.exit({
|
||||
session$progressStack$pop()
|
||||
p$close()
|
||||
})
|
||||
|
||||
p$set(value, message, detail)
|
||||
|
||||
eval(expr, env)
|
||||
}
|
||||
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
setProgress <- function(value = NULL, message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
if (session$progressStack$size() == 0) {
|
||||
warning('setProgress was called outside of withProgress; ignoring')
|
||||
return()
|
||||
}
|
||||
|
||||
session$progressStack$peek()$set(value, message, detail)
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
incProgress <- function(amount = 0.1, message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
if (session$progressStack$size() == 0) {
|
||||
warning('incProgress was called outside of withProgress; ignoring')
|
||||
return()
|
||||
}
|
||||
|
||||
p <- session$progressStack$peek()
|
||||
p$inc(amount, message, detail)
|
||||
invisible()
|
||||
}
|
||||
58
R/react.R
58
R/react.R
@@ -1,19 +1,17 @@
|
||||
Context <- setRefClass(
|
||||
Context <- R6Class(
|
||||
'Context',
|
||||
fields = list(
|
||||
id = 'character',
|
||||
.label = 'character', # For debug purposes
|
||||
.invalidated = 'logical',
|
||||
.invalidateCallbacks = 'list',
|
||||
.flushCallbacks = 'list',
|
||||
.domain = 'ANY'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
id = character(0),
|
||||
.label = character(0), # For debug purposes
|
||||
.invalidated = FALSE,
|
||||
.invalidateCallbacks = list(),
|
||||
.flushCallbacks = list(),
|
||||
.domain = NULL,
|
||||
|
||||
initialize = function(domain, label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.invalidated <<- FALSE
|
||||
.invalidateCallbacks <<- list()
|
||||
.flushCallbacks <<- list()
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.graphCreateContext(id, label, type, prevId, domain)
|
||||
@@ -24,7 +22,7 @@ Context <- setRefClass(
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
tryCatch(
|
||||
env$runWith(.self, func),
|
||||
env$runWith(self, func),
|
||||
finally = .graphExitContext(id)
|
||||
)
|
||||
})
|
||||
@@ -56,7 +54,7 @@ Context <- setRefClass(
|
||||
addPendingFlush = function(priority) {
|
||||
"Tell the reactive environment that this context should be flushed the
|
||||
next time flushReact() called."
|
||||
.getReactiveEnvironment()$addPendingFlush(.self, priority)
|
||||
.getReactiveEnvironment()$addPendingFlush(self, priority)
|
||||
},
|
||||
onFlush = function(func) {
|
||||
"Register a function to be called when this context is flushed."
|
||||
@@ -65,32 +63,24 @@ Context <- setRefClass(
|
||||
executeFlushCallbacks = function() {
|
||||
"For internal use only."
|
||||
lapply(.flushCallbacks, function(func) {
|
||||
withCallingHandlers({
|
||||
func()
|
||||
}, warning = function(e) {
|
||||
# TODO: Callbacks in app
|
||||
}, error = function(e) {
|
||||
# TODO: Callbacks in app
|
||||
})
|
||||
func()
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
ReactiveEnvironment <- setRefClass(
|
||||
ReactiveEnvironment <- R6Class(
|
||||
'ReactiveEnvironment',
|
||||
fields = list(
|
||||
.currentContext = 'ANY',
|
||||
.nextId = 'integer',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.currentContext = NULL,
|
||||
.nextId = 0L,
|
||||
.pendingFlush = 'PriorityQueue',
|
||||
.inFlush = 'logical'
|
||||
),
|
||||
methods = list(
|
||||
.inFlush = FALSE,
|
||||
|
||||
initialize = function() {
|
||||
.currentContext <<- NULL
|
||||
.nextId <<- 0L
|
||||
.pendingFlush <<- PriorityQueue$new()
|
||||
.inFlush <<- FALSE
|
||||
},
|
||||
nextId = function() {
|
||||
.nextId <<- .nextId + 1L
|
||||
@@ -98,7 +88,7 @@ ReactiveEnvironment <- setRefClass(
|
||||
},
|
||||
currentContext = function() {
|
||||
if (is.null(.currentContext)) {
|
||||
if (isTRUE(getOption('shiny.suppressMissingContextError', FALSE))) {
|
||||
if (isTRUE(getOption('shiny.suppressMissingContextError'))) {
|
||||
return(getDummyContext())
|
||||
} else {
|
||||
stop('Operation not allowed without an active reactive context. ',
|
||||
@@ -138,7 +128,7 @@ ReactiveEnvironment <- setRefClass(
|
||||
reactiveEnvironment <<- ReactiveEnvironment$new()
|
||||
return(reactiveEnvironment)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
# Causes any pending invalidations to run.
|
||||
flushReact <- function() {
|
||||
|
||||
@@ -75,6 +75,7 @@ createMockDomain <- function() {
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @name domains
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
getDefaultReactiveDomain <- function() {
|
||||
|
||||
344
R/reactives.R
344
R/reactives.R
@@ -1,12 +1,16 @@
|
||||
#' @include utils.R
|
||||
NULL
|
||||
|
||||
Dependents <- setRefClass(
|
||||
Dependents <- R6Class(
|
||||
'Dependents',
|
||||
fields = list(
|
||||
.dependents = 'Map'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.dependents = 'Map',
|
||||
|
||||
initialize = function() {
|
||||
.dependents <<- Map$new()
|
||||
},
|
||||
register = function(depId=NULL, depLabel=NULL) {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
if (!.dependents$containsKey(ctx$id)) {
|
||||
@@ -36,11 +40,12 @@ Dependents <- setRefClass(
|
||||
|
||||
# ReactiveValues ------------------------------------------------------------
|
||||
|
||||
ReactiveValues <- setRefClass(
|
||||
ReactiveValues <- R6Class(
|
||||
'ReactiveValues',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
# For debug purposes
|
||||
.label = 'character',
|
||||
.label = character(0),
|
||||
.values = 'environment',
|
||||
.dependents = 'environment',
|
||||
# Dependents for the list of all names, including hidden
|
||||
@@ -48,15 +53,17 @@ ReactiveValues <- setRefClass(
|
||||
# Dependents for all values, including hidden
|
||||
.allValuesDeps = 'Dependents',
|
||||
# Dependents for all values
|
||||
.valuesDeps = 'Dependents'
|
||||
),
|
||||
methods = list(
|
||||
.valuesDeps = 'Dependents',
|
||||
|
||||
initialize = function() {
|
||||
.label <<- paste('reactiveValues',
|
||||
p_randomInt(1000, 10000),
|
||||
sep="")
|
||||
.values <<- new.env(parent=emptyenv())
|
||||
.dependents <<- new.env(parent=emptyenv())
|
||||
.namesDeps <<- Dependents$new()
|
||||
.allValuesDeps <<- Dependents$new()
|
||||
.valuesDeps <<- Dependents$new()
|
||||
},
|
||||
get = function(key) {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
@@ -114,7 +121,7 @@ ReactiveValues <- setRefClass(
|
||||
mset = function(lst) {
|
||||
lapply(base::names(lst),
|
||||
function(name) {
|
||||
.self$set(name, lst[[name]])
|
||||
self$set(name, lst[[name]])
|
||||
})
|
||||
},
|
||||
names = function() {
|
||||
@@ -145,11 +152,12 @@ ReactiveValues <- setRefClass(
|
||||
|
||||
#' Create an object for storing reactive values
|
||||
#'
|
||||
#' This function returns an object for storing reactive values. It is similar
|
||||
#' to a list, but with special capabilities for reactive programming. When you
|
||||
#' read a value from it, the calling reactive expression takes a reactive
|
||||
#' dependency on that value, and when you write to it, it notifies any reactive
|
||||
#' functions that depend on that value.
|
||||
#' This function returns an object for storing reactive values. It is similar to
|
||||
#' a list, but with special capabilities for reactive programming. When you read
|
||||
#' a value from it, the calling reactive expression takes a reactive dependency
|
||||
#' on that value, and when you write to it, it notifies any reactive functions
|
||||
#' that depend on that value. Note that values taken from the reactiveValues
|
||||
#' object are reactive, but the reactiveValues object itself is not.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Create the object with no values
|
||||
@@ -192,9 +200,6 @@ reactiveValues <- function(...) {
|
||||
values
|
||||
}
|
||||
|
||||
# Register the S3 class so that it can be used for a field in a Reference Class
|
||||
setOldClass("reactivevalues")
|
||||
|
||||
# Create a reactivevalues object
|
||||
#
|
||||
# @param values A ReactiveValues object
|
||||
@@ -302,29 +307,29 @@ reactiveValuesToList <- function(x, all.names=FALSE) {
|
||||
# x[['impl']].
|
||||
#' @export
|
||||
str.reactivevalues <- function(object, indent.str = " ", ...) {
|
||||
str(unclass(object), indent.str = indent.str, ...)
|
||||
utils::str(unclass(object), indent.str = indent.str, ...)
|
||||
# Need to manually print out the class field,
|
||||
cat(indent.str, '- attr(*, "class")=', sep = "")
|
||||
str(class(object))
|
||||
utils::str(class(object))
|
||||
}
|
||||
|
||||
# Observable ----------------------------------------------------------------
|
||||
|
||||
Observable <- setRefClass(
|
||||
Observable <- R6Class(
|
||||
'Observable',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
.func = 'function',
|
||||
.label = 'character',
|
||||
.domain = 'ANY',
|
||||
.label = character(0),
|
||||
.domain = NULL,
|
||||
.dependents = 'Dependents',
|
||||
.invalidated = 'logical',
|
||||
.running = 'logical',
|
||||
.value = 'ANY',
|
||||
.visible = 'logical',
|
||||
.execCount = 'integer',
|
||||
.mostRecentCtxId = 'character'
|
||||
),
|
||||
methods = list(
|
||||
.invalidated = logical(0),
|
||||
.running = logical(0),
|
||||
.value = NULL,
|
||||
.visible = logical(0),
|
||||
.execCount = integer(0),
|
||||
.mostRecentCtxId = character(0),
|
||||
|
||||
initialize = function(func, label = deparse(substitute(func)),
|
||||
domain = getDefaultReactiveDomain()) {
|
||||
if (length(formals(func)) > 0)
|
||||
@@ -332,10 +337,11 @@ Observable <- setRefClass(
|
||||
"or more parameters; only functions without parameters can be ",
|
||||
"reactive.")
|
||||
.func <<- func
|
||||
.invalidated <<- TRUE
|
||||
.running <<- FALSE
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.dependents <<- Dependents$new()
|
||||
.invalidated <<- TRUE
|
||||
.running <<- FALSE
|
||||
.execCount <<- 0L
|
||||
.mostRecentCtxId <<- ""
|
||||
},
|
||||
@@ -343,7 +349,7 @@ Observable <- setRefClass(
|
||||
.dependents$register()
|
||||
|
||||
if (.invalidated || .running) {
|
||||
.self$.updateValue()
|
||||
self$.updateValue()
|
||||
}
|
||||
|
||||
.graphDependsOnId(getCurrentContext()$id, .mostRecentCtxId)
|
||||
@@ -373,7 +379,7 @@ Observable <- setRefClass(
|
||||
on.exit(.running <<- wasRunning)
|
||||
|
||||
ctx$run(function() {
|
||||
result <- withVisible(try(shinyCallingHandlers(.func()), silent=FALSE))
|
||||
result <- withVisible(try(shinyCallingHandlers(.func()), silent=TRUE))
|
||||
.visible <<- result$visible
|
||||
.value <<- result$value
|
||||
})
|
||||
@@ -386,25 +392,25 @@ Observable <- setRefClass(
|
||||
#' Wraps a normal expression to create a reactive expression. Conceptually, a
|
||||
#' reactive expression is a expression whose result will change over time.
|
||||
#'
|
||||
#' Reactive expressions are expressions that can read reactive values and call other
|
||||
#' reactive expressions. Whenever a reactive value changes, any reactive expressions
|
||||
#' that depended on it are marked as "invalidated" and will automatically
|
||||
#' re-execute if necessary. If a reactive expression is marked as invalidated, any
|
||||
#' other reactive expressions that recently called it are also marked as
|
||||
#' invalidated. In this way, invalidations ripple through the expressions that
|
||||
#' depend on each other.
|
||||
#' Reactive expressions are expressions that can read reactive values and call
|
||||
#' other reactive expressions. Whenever a reactive value changes, any reactive
|
||||
#' expressions that depended on it are marked as "invalidated" and will
|
||||
#' automatically re-execute if necessary. If a reactive expression is marked as
|
||||
#' invalidated, any other reactive expressions that recently called it are also
|
||||
#' marked as invalidated. In this way, invalidations ripple through the
|
||||
#' expressions that depend on each other.
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
|
||||
#' more information about reactive expressions.
|
||||
#'
|
||||
#' @param x For \code{reactive}, an expression (quoted or unquoted). For
|
||||
#' \code{is.reactive}, an object to test.
|
||||
#' @param env The parent environment for the reactive expression. By default, this
|
||||
#' is the calling environment, the same as when defining an ordinary
|
||||
#' @param env The parent environment for the reactive expression. By default,
|
||||
#' this is the calling environment, the same as when defining an ordinary
|
||||
#' non-reactive expression.
|
||||
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
|
||||
#' This is useful when you want to use an expression that is stored in a
|
||||
#' variable; to do so, it must be quoted with `quote()`.
|
||||
#' variable; to do so, it must be quoted with \code{quote()}.
|
||||
#' @param label A label for the reactive expression, useful for debugging.
|
||||
#' @param domain See \link{domains}.
|
||||
#' @return a function, wrapped in a S3 class "reactive"
|
||||
@@ -440,7 +446,7 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
|
||||
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
|
||||
o <- Observable$new(fun, label, domain)
|
||||
registerDebugHook(".func", o, "Reactive")
|
||||
structure(o$getValue@.Data, observable = o, class = "reactive")
|
||||
structure(o$getValue, observable = o, class = "reactive")
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -455,8 +461,8 @@ is.reactive <- function(x) inherits(x, "reactive")
|
||||
|
||||
# Return the number of times that a reactive expression or observer has been run
|
||||
execCount <- function(x) {
|
||||
if (is.function(x))
|
||||
return(environment(x)$.execCount)
|
||||
if (is.reactive(x))
|
||||
return(attr(x, "observable")$.execCount)
|
||||
else if (inherits(x, 'Observer'))
|
||||
return(x$.execCount)
|
||||
else
|
||||
@@ -465,22 +471,22 @@ execCount <- function(x) {
|
||||
|
||||
# Observer ------------------------------------------------------------------
|
||||
|
||||
Observer <- setRefClass(
|
||||
Observer <- R6Class(
|
||||
'Observer',
|
||||
fields = list(
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
.func = 'function',
|
||||
.label = 'character',
|
||||
.label = character(0),
|
||||
.domain = 'ANY',
|
||||
.priority = 'numeric',
|
||||
.autoDestroy = 'logical',
|
||||
.invalidateCallbacks = 'list',
|
||||
.execCount = 'integer',
|
||||
.priority = numeric(0),
|
||||
.autoDestroy = logical(0),
|
||||
.invalidateCallbacks = list(),
|
||||
.execCount = integer(0),
|
||||
.onResume = 'function',
|
||||
.suspended = 'logical',
|
||||
.destroyed = 'logical',
|
||||
.prevId = 'character'
|
||||
),
|
||||
methods = list(
|
||||
.suspended = logical(0),
|
||||
.destroyed = logical(0),
|
||||
.prevId = character(0),
|
||||
|
||||
initialize = function(func, label, suspended = FALSE, priority = 0,
|
||||
domain = getDefaultReactiveDomain(),
|
||||
autoDestroy = TRUE) {
|
||||
@@ -488,7 +494,15 @@ Observer <- setRefClass(
|
||||
stop("Can't make an observer from a function that takes parameters; ",
|
||||
"only functions without parameters can be reactive.")
|
||||
|
||||
.func <<- func
|
||||
.func <<- function() {
|
||||
tryCatch(
|
||||
func(),
|
||||
validation = function(e) {
|
||||
# It's OK for a validation error to cause an observer to stop
|
||||
# running
|
||||
}
|
||||
)
|
||||
}
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.autoDestroy <<- autoDestroy
|
||||
@@ -499,7 +513,7 @@ Observer <- setRefClass(
|
||||
.destroyed <<- FALSE
|
||||
.prevId <<- ''
|
||||
|
||||
onReactiveDomainEnded(.domain, .self$.onDomainEnded)
|
||||
onReactiveDomainEnded(.domain, self$.onDomainEnded)
|
||||
|
||||
# Defer the first running of this until flushReact is called
|
||||
.createContext()$invalidate()
|
||||
@@ -525,8 +539,27 @@ Observer <- setRefClass(
|
||||
})
|
||||
|
||||
ctx$onFlush(function() {
|
||||
if (!.destroyed)
|
||||
run()
|
||||
tryCatch({
|
||||
if (!.destroyed)
|
||||
run()
|
||||
|
||||
}, error = function(e) {
|
||||
# A function to handle errors that occur during a flush
|
||||
flushErrorHandler <- getOption('shiny.observer.error')
|
||||
|
||||
# Default handler function, if not available from global option
|
||||
if (is.null(flushErrorHandler)) {
|
||||
flushErrorHandler <- function(e, label, domain) {
|
||||
warning("Unhandled error in observer: ",
|
||||
e$message, "\n", label, immediate. = TRUE, call. = FALSE)
|
||||
if (!is.null(domain)) {
|
||||
domain$unhandledError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flushErrorHandler(e, .label, .domain)
|
||||
})
|
||||
})
|
||||
|
||||
return(ctx)
|
||||
@@ -623,7 +656,7 @@ Observer <- setRefClass(
|
||||
#' non-reactive expression.
|
||||
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
|
||||
#' This is useful when you want to use an expression that is stored in a
|
||||
#' variable; to do so, it must be quoted with `quote()`.
|
||||
#' variable; to do so, it must be quoted with \code{quote()}.
|
||||
#' @param label A label for the observer, useful for debugging.
|
||||
#' @param suspended If \code{TRUE}, start the observer in a suspended state.
|
||||
#' If \code{FALSE} (the default), start in a non-suspended state.
|
||||
@@ -1184,3 +1217,178 @@ maskReactiveContext <- function(expr) {
|
||||
expr
|
||||
})
|
||||
}
|
||||
|
||||
#' Event handler
|
||||
#'
|
||||
#' Respond to "event-like" reactive inputs, values, and expressions.
|
||||
#'
|
||||
#' Shiny's reactive programming framework is primarily designed for calculated
|
||||
#' values (reactive expressions) and side-effect-causing actions (observers)
|
||||
#' that respond to \emph{any} of their inputs changing. That's often what is
|
||||
#' desired in Shiny apps, but not always: sometimes you want to wait for a
|
||||
#' specific action to be taken from the user, like clicking an
|
||||
#' \code{\link{actionButton}}, before calculating an expression or taking an
|
||||
#' action. A reactive value or expression that is used to trigger other
|
||||
#' calculations in this way is called an \emph{event}.
|
||||
#'
|
||||
#' These situations demand a more imperative, "event handling" style of
|
||||
#' programming that is possible--but not particularly intuitive--using the
|
||||
#' reactive programming primitives \code{\link{observe}} and
|
||||
#' \code{\link{isolate}}. \code{observeEvent} and \code{eventReactive} provide
|
||||
#' straightforward APIs for event handling that wrap \code{observe} and
|
||||
#' \code{isolate}.
|
||||
#'
|
||||
#' Use \code{observeEvent} whenever you want to \emph{perform an action} in
|
||||
#' response to an event. (Note that "recalculate a value" does not generally
|
||||
#' count as performing an action--see \code{eventReactive} for that.) The first
|
||||
#' argument is the event you want to respond to, and the second argument is a
|
||||
#' function that should be called whenever the event occurs.
|
||||
#'
|
||||
#' Use \code{eventReactive} to create a \emph{calculated value} that only
|
||||
#' updates in response to an event. This is just like a normal
|
||||
#' \link[=reactive]{reactive expression} except it ignores all the usual
|
||||
#' invalidations that come from its reactive dependencies; it only invalidates
|
||||
#' in response to the given event.
|
||||
#'
|
||||
#' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
|
||||
#' parameter that affects behavior when the \code{eventExpr} evaluates to
|
||||
#' \code{NULL} (or in the special case of an \code{\link{actionButton}},
|
||||
#' \code{0}). In these cases, if \code{ignoreNULL} is \code{TRUE}, then an
|
||||
#' \code{observeEvent} will not execute and an \code{eventReactive} will raise a
|
||||
#' silent \link[=validate]{validation} error. This is useful behavior if you
|
||||
#' don't want to do the action or calculation when your app first starts, but
|
||||
#' wait for the user to initiate the action first (like a "Submit" button);
|
||||
#' whereas \code{ignoreNULL=FALSE} is desirable if you want to initially perform
|
||||
#' the action/calculation and just let the user re-initiate it (like a
|
||||
#' "Recalculate" button).
|
||||
#'
|
||||
#' @param eventExpr A (quoted or unquoted) expression that represents the event;
|
||||
#' this can be a simple reactive value like \code{input$click}, a call to a
|
||||
#' reactive expression like \code{dataset()}, or even a complex expression
|
||||
#' inside curly braces
|
||||
#' @param handlerExpr The expression to call whenever \code{eventExpr} is
|
||||
#' invalidated. This should be a side-effect-producing action (the return
|
||||
#' value will be ignored). It will be executed within an \code{\link{isolate}}
|
||||
#' scope.
|
||||
#' @param valueExpr The expression that produces the return value of the
|
||||
#' \code{eventReactive}. It will be executed within an \code{\link{isolate}}
|
||||
#' scope.
|
||||
#' @param event.env The parent environment for \code{eventExpr}. By default,
|
||||
#' this is the calling environment.
|
||||
#' @param event.quoted Is the \code{eventExpr} expression quoted? By default,
|
||||
#' this is \code{FALSE}. This is useful when you want to use an expression
|
||||
#' that is stored in a variable; to do so, it must be quoted with
|
||||
#' \code{quote()}.
|
||||
#' @param handler.env The parent environment for \code{handlerExpr}. By default,
|
||||
#' this is the calling environment.
|
||||
#' @param handler.quoted Is the \code{handlerExpr} expression quoted? By
|
||||
#' default, this is \code{FALSE}. This is useful when you want to use an
|
||||
#' expression that is stored in a variable; to do so, it must be quoted with
|
||||
#' \code{quote()}.
|
||||
#' @param value.env The parent environment for \code{valueExpr}. By default,
|
||||
#' this is the calling environment.
|
||||
#' @param value.quoted Is the \code{valueExpr} expression quoted? By default,
|
||||
#' this is \code{FALSE}. This is useful when you want to use an expression
|
||||
#' that is stored in a variable; to do so, it must be quoted with \code{quote()}.
|
||||
#' @param label A label for the observer or reactive, useful for debugging.
|
||||
#' @param suspended If \code{TRUE}, start the observer in a suspended state. If
|
||||
#' \code{FALSE} (the default), start in a non-suspended state.
|
||||
#' @param priority An integer or numeric that controls the priority with which
|
||||
#' this observer should be executed. An observer with a given priority level
|
||||
#' will always execute sooner than all observers with a lower priority level.
|
||||
#' Positive, negative, and zero values are allowed.
|
||||
#' @param domain See \link{domains}.
|
||||
#' @param autoDestroy If \code{TRUE} (the default), the observer will be
|
||||
#' automatically destroyed when its domain (if any) ends.
|
||||
#' @param ignoreNULL Whether the action should be triggered (or value
|
||||
#' calculated, in the case of \code{eventReactive}) when the input is
|
||||
#' \code{NULL}. See Details.
|
||||
#' @return \code{observeEvent} returns an observer reference class object (see
|
||||
#' \code{\link{observe}}). \code{eventReactive} returns a reactive expression
|
||||
#' object (see \code{\link{reactive}}).
|
||||
#'
|
||||
#' @seealso \code{\link{actionButton}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' ui <- fluidPage(
|
||||
#' column(4,
|
||||
#' numericInput("x", "Value", 5),
|
||||
#' br(),
|
||||
#' actionButton("button", "Show")
|
||||
#' ),
|
||||
#' column(8, tableOutput("table"))
|
||||
#' )
|
||||
#' server <- function(input, output) {
|
||||
#' # Take an action every time button is pressed;
|
||||
#' # here, we just print a message to the console
|
||||
#' observeEvent(input$button, {
|
||||
#' cat("Showing", input$x, "rows\n")
|
||||
#' })
|
||||
#' # Take a reactive dependency on input$button, but
|
||||
#' # not on any of the stuff inside the function
|
||||
#' df <- eventReactive(input$button, {
|
||||
#' head(cars, input$x)
|
||||
#' })
|
||||
#' output$table <- renderTable({
|
||||
#' df()
|
||||
#' })
|
||||
#' }
|
||||
#' shinyApp(ui=ui, server=server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
observeEvent <- function(eventExpr, handlerExpr,
|
||||
event.env = parent.frame(), event.quoted = FALSE,
|
||||
handler.env = parent.frame(), handler.quoted = FALSE,
|
||||
label=NULL, suspended=FALSE, priority=0, domain=getDefaultReactiveDomain(),
|
||||
autoDestroy = TRUE, ignoreNULL = TRUE) {
|
||||
|
||||
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
|
||||
if (is.null(label))
|
||||
label <- sprintf('observeEvent(%s)', paste(deparse(body(eventFunc)), collapse='\n'))
|
||||
|
||||
handlerFunc <- exprToFunction(handlerExpr, handler.env, handler.quoted)
|
||||
|
||||
invisible(observe({
|
||||
e <- eventFunc()
|
||||
|
||||
if (ignoreNULL && isNullEvent(e)) {
|
||||
return()
|
||||
}
|
||||
|
||||
isolate(handlerFunc())
|
||||
}, label = label, suspended = suspended, priority = priority, domain = domain,
|
||||
autoDestroy = TRUE))
|
||||
}
|
||||
|
||||
#' @rdname observeEvent
|
||||
#' @export
|
||||
eventReactive <- function(eventExpr, valueExpr,
|
||||
event.env = parent.frame(), event.quoted = FALSE,
|
||||
value.env = parent.frame(), value.quoted = FALSE,
|
||||
label=NULL, domain=getDefaultReactiveDomain(),
|
||||
ignoreNULL = TRUE) {
|
||||
|
||||
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
|
||||
if (is.null(label))
|
||||
label <- sprintf('eventReactive(%s)', paste(deparse(body(eventFunc)), collapse='\n'))
|
||||
|
||||
handlerFunc <- exprToFunction(valueExpr, value.env, value.quoted)
|
||||
|
||||
invisible(reactive({
|
||||
e <- eventFunc()
|
||||
|
||||
validate(need(
|
||||
!ignoreNULL || !isNullEvent(e),
|
||||
message = FALSE
|
||||
))
|
||||
|
||||
isolate(handlerFunc())
|
||||
}, label = label, domain = domain))
|
||||
}
|
||||
|
||||
isNullEvent <- function(value) {
|
||||
is.null(value) || (inherits(value, 'shinyActionButtonValue') && value == 0)
|
||||
}
|
||||
|
||||
591
R/render-plot.R
Normal file
591
R/render-plot.R
Normal file
@@ -0,0 +1,591 @@
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' @seealso For the corresponding client-side output function, and example
|
||||
#' usage, see \code{\link{plotOutput}}. For more details on how the plots are
|
||||
#' generated, and how to control the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width,height The width/height of the rendered plot, in pixels; or
|
||||
#' \code{'auto'} to use the \code{offsetWidth}/\code{offsetHeight} of the HTML
|
||||
#' element that is bound to this plot. You can also pass in a function that
|
||||
#' returns the width/height in pixels or \code{'auto'}; in the body of the
|
||||
#' function you may reference reactive values and functions. When rendering an
|
||||
#' inline plot, you must provide numeric values (in pixels) to both
|
||||
#' \code{width} and \code{height}.
|
||||
#' @param res Resolution of resulting plot, in pixels per inch. This value is
|
||||
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
|
||||
#' rendering in R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that generates a plot (deprecated; use \code{expr}
|
||||
#' instead).
|
||||
#'
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- NULL
|
||||
|
||||
if (is.function(height))
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
# If renderPlot isn't going to adapt to the height of the div, then the
|
||||
# div needs to adapt to the height of renderPlot. By default, plotOutput
|
||||
# sets the height to 400px, so to make it adapt we need to override it
|
||||
# with NULL.
|
||||
outputFunc <- plotOutput
|
||||
if (!identical(height, 'auto')) formals(outputFunc)['height'] <- list(NULL)
|
||||
|
||||
return(markRenderFunction(outputFunc, function(shinysession, name, ...) {
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
prefix <- 'output_'
|
||||
if (width == 'auto')
|
||||
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
|
||||
if (height == 'auto')
|
||||
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
|
||||
|
||||
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
result <- withVisible(func())
|
||||
|
||||
coordmap <<- NULL
|
||||
|
||||
if (result$visible) {
|
||||
# Use capture.output to squelch printing to the actual console; we
|
||||
# are only interested in plot output
|
||||
|
||||
# Special case for ggplot objects - need to capture coordmap
|
||||
if (inherits(result$value, "ggplot")) {
|
||||
utils::capture.output(coordmap <<- getGgplotCoordmap(result$value, pixelratio))
|
||||
} else {
|
||||
utils::capture.output(print(result$value))
|
||||
}
|
||||
}
|
||||
|
||||
if (is.null(coordmap)) {
|
||||
coordmap <<- getPrevPlotCoordmap(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
|
||||
height=height*pixelratio, res=res*pixelratio, args))
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
# A list of attributes for the img
|
||||
res <- list(
|
||||
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
|
||||
width=width, height=height, coordmap=coordmap
|
||||
)
|
||||
|
||||
# Get error message if present (from attribute on the coordmap)
|
||||
error <- attr(coordmap, "error", exact = TRUE)
|
||||
if (!is.null(error)) {
|
||||
res$error <- error
|
||||
}
|
||||
|
||||
res
|
||||
}))
|
||||
}
|
||||
|
||||
# The coordmap extraction functions below return something like the examples
|
||||
# below. For base graphics:
|
||||
# plot(mtcars$wt, mtcars$mpg)
|
||||
# str(getPrevPlotCoordmap(400, 300))
|
||||
# List of 1
|
||||
# $ :List of 4
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.36
|
||||
# .. ..$ right : num 5.58
|
||||
# .. ..$ bottom: num 9.46
|
||||
# .. ..$ top : num 34.8
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 50.4
|
||||
# .. ..$ right : num 373
|
||||
# .. ..$ bottom: num 199
|
||||
# .. ..$ top : num 79.6
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ mapping: Named list()
|
||||
#
|
||||
# For ggplot2, it might be something like:
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# List of 1
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars: Named list()
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 2
|
||||
# .. ..$ x: chr "wt"
|
||||
# .. ..$ y: chr "mpg"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 40.8
|
||||
# .. ..$ right : num 446
|
||||
# .. ..$ bottom: num 263
|
||||
# .. ..$ top : num 14.4
|
||||
#
|
||||
# With a faceted ggplot2 plot, the outer list contains two objects, each of
|
||||
# which represents one panel. In this example, there is one panelvar, but there
|
||||
# can be up to two of them.
|
||||
# mtc <- mtcars
|
||||
# mtc$am <- factor(mtc$am)
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_wrap(~ am)
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# List of 2
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 45.6
|
||||
# .. ..$ right : num 317
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 2
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 2
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 322
|
||||
# .. ..$ right : num 594
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
|
||||
|
||||
# Get a coordmap for the previous plot made with base graphics.
|
||||
# Requires width and height of output image, in pixels.
|
||||
# Must be called before the graphics device is closed.
|
||||
getPrevPlotCoordmap <- function(width, height) {
|
||||
usrCoords <- graphics::par('usr')
|
||||
usrBounds <- usrCoords
|
||||
if (graphics::par('xlog')) {
|
||||
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
|
||||
}
|
||||
if (graphics::par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
# Wrapped in double list because other types of plots can have multiple panels.
|
||||
list(list(
|
||||
# Bounds of the plot area, in data space
|
||||
domain = list(
|
||||
left = usrCoords[1],
|
||||
right = usrCoords[2],
|
||||
bottom = usrCoords[3],
|
||||
top = usrCoords[4]
|
||||
),
|
||||
# The bounds of the plot area, in DOM pixels
|
||||
range = list(
|
||||
left = graphics::grconvertX(usrBounds[1], 'user', 'nfc') * width,
|
||||
right = graphics::grconvertX(usrBounds[2], 'user', 'nfc') * width,
|
||||
bottom = (1-graphics::grconvertY(usrBounds[3], 'user', 'nfc')) * height - 1,
|
||||
top = (1-graphics::grconvertY(usrBounds[4], 'user', 'nfc')) * height - 1
|
||||
),
|
||||
log = list(
|
||||
x = if (graphics::par('xlog')) 10 else NULL,
|
||||
y = if (graphics::par('ylog')) 10 else NULL
|
||||
),
|
||||
# We can't extract the original variable names from a base graphic.
|
||||
# `mapping` is an empty _named_ list, so that it is converted to an object
|
||||
# (not an array) in JSON.
|
||||
mapping = list(x = NULL)[0]
|
||||
))
|
||||
}
|
||||
|
||||
# Print a ggplot object and return a coordmap for it.
|
||||
getGgplotCoordmap <- function(p, pixelratio) {
|
||||
if (!inherits(p, "ggplot"))
|
||||
return(NULL)
|
||||
|
||||
# A modified version of print.ggplot which returns the built ggplot object as
|
||||
# well as the gtable grob. This overrides the ggplot::print.ggplot method, but
|
||||
# only within the context of getGgplotCoordmap. The reason this needs to be an
|
||||
# (pseudo) S3 method is so that, if an object has a class in addition to
|
||||
# ggplot, and there's a print method for that class, that we won't override
|
||||
# that method.
|
||||
# https://github.com/rstudio/shiny/issues/841
|
||||
print.ggplot <- function(x) {
|
||||
grid::grid.newpage()
|
||||
|
||||
build <- ggplot2::ggplot_build(x)
|
||||
|
||||
gtable <- ggplot2::ggplot_gtable(build)
|
||||
grid::grid.draw(gtable)
|
||||
|
||||
list(
|
||||
build = build,
|
||||
gtable = gtable
|
||||
)
|
||||
}
|
||||
|
||||
# Given the name of a generic function and an object, return the class name
|
||||
# for the method that would be used on the object.
|
||||
which_method <- function(generic, x) {
|
||||
classes <- class(x)
|
||||
method_names <- paste(generic, classes, sep = ".")
|
||||
idx <- which(method_names %in% utils::methods(generic))
|
||||
|
||||
if (length(idx) == 0)
|
||||
return(NULL)
|
||||
|
||||
# Return name of first class with matching method
|
||||
classes[idx[1]]
|
||||
}
|
||||
|
||||
|
||||
# Given a built ggplot object, return x and y domains (data space coords) for
|
||||
# each panel.
|
||||
find_panel_info <- function(b) {
|
||||
layout <- b$panel$layout
|
||||
# Convert factor to numbers
|
||||
layout$PANEL <- as.integer(as.character(layout$PANEL))
|
||||
|
||||
# Names of facets
|
||||
facet <- b$plot$facet
|
||||
facet_vars <- NULL
|
||||
if (inherits(facet, "grid")) {
|
||||
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "wrap")) {
|
||||
facet_vars <- vapply(facet$facets, as.character, character(1))
|
||||
}
|
||||
|
||||
# Iterate over each row in the layout data frame
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
# Slice out one row
|
||||
l <- layout[i, ]
|
||||
|
||||
scale_x <- l$SCALE_X
|
||||
scale_y <- l$SCALE_Y
|
||||
|
||||
mapping <- find_plot_mappings(b)
|
||||
|
||||
# For each of the faceting variables, get the value of that variable in
|
||||
# the current panel. Default to empty _named_ list so that it's sent as a
|
||||
# JSON object, not array.
|
||||
panel_vars <- list(a = NULL)[0]
|
||||
for (i in seq_along(facet_vars)) {
|
||||
var_name <- facet_vars[[i]]
|
||||
vname <- paste0("panelvar", i)
|
||||
|
||||
mapping[[vname]] <- var_name
|
||||
panel_vars[[vname]] <- l[[var_name]]
|
||||
}
|
||||
|
||||
list(
|
||||
panel = l$PANEL,
|
||||
row = l$ROW,
|
||||
col = l$COL,
|
||||
panel_vars = panel_vars,
|
||||
scale_x = scale_x,
|
||||
scale_y = scale_x,
|
||||
log = check_log_scales(b, scale_x, scale_y),
|
||||
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
|
||||
mapping = mapping
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# Given a single range object (representing the data domain) from a built
|
||||
# ggplot object, return the domain.
|
||||
find_panel_domain <- function(b, panel_num, scalex_num = 1, scaley_num = 1) {
|
||||
range <- b$panel$ranges[[panel_num]]
|
||||
res <- list(
|
||||
left = range$x.range[1],
|
||||
right = range$x.range[2],
|
||||
bottom = range$y.range[1],
|
||||
top = range$y.range[2]
|
||||
)
|
||||
|
||||
# Check for reversed scales
|
||||
xscale <- b$panel$x_scales[[scalex_num]]
|
||||
yscale <- b$panel$y_scales[[scaley_num]]
|
||||
|
||||
if (!is.null(xscale$trans) && xscale$trans$name == "reverse") {
|
||||
res$left <- -res$left
|
||||
res$right <- -res$right
|
||||
}
|
||||
if (!is.null(yscale$trans) && yscale$trans$name == "reverse") {
|
||||
res$top <- -res$top
|
||||
res$bottom <- -res$bottom
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
# Given built ggplot object, return object with the log base for x and y if
|
||||
# there are log scales or coord transforms.
|
||||
check_log_scales <- function(b, scalex_num = 1, scaley_num = 1) {
|
||||
|
||||
# Given a vector of transformation names like c("log-10", "identity"),
|
||||
# return the first log base, like 10. If none are present, return NULL.
|
||||
extract_log_base <- function(names) {
|
||||
names <- names[grepl("^log-", names)]
|
||||
|
||||
if (length(names) == 0)
|
||||
return(NULL)
|
||||
|
||||
names <- names[1]
|
||||
|
||||
as.numeric(sub("^log-", "", names))
|
||||
}
|
||||
|
||||
# Look for log scales and log coord transforms. People shouldn't use both.
|
||||
x_names <- character(0)
|
||||
y_names <- character(0)
|
||||
|
||||
# Continuous scales have a trans; discrete ones don't
|
||||
if (!is.null(b$panel$x_scales[[scalex_num]]$trans))
|
||||
x_names <- b$panel$x_scales[[scalex_num]]$trans$name
|
||||
if (!is.null(b$panel$y_scales[[scaley_num]]$trans))
|
||||
y_names <- b$panel$y_scales[[scaley_num]]$trans$name
|
||||
|
||||
coords <- b$plot$coordinates
|
||||
if (!is.null(coords$trans)) {
|
||||
if (!is.null(coords$trans$x))
|
||||
x_names <- c(x_names, coords$trans$x$name)
|
||||
if (!is.null(coords$trans$y))
|
||||
y_names <- c(y_names, coords$trans$y$name)
|
||||
}
|
||||
|
||||
# Keep only scale/trans names that start with "log-"
|
||||
x_names <- x_names[grepl("^log-", x_names)]
|
||||
y_names <- y_names[grepl("^log-", y_names)]
|
||||
|
||||
# Extract the log base from the trans name -- a string like "log-10".
|
||||
list(
|
||||
x = extract_log_base(x_names),
|
||||
y = extract_log_base(y_names)
|
||||
)
|
||||
}
|
||||
|
||||
# Given a built ggplot object, return a named list of variables mapped to x
|
||||
# and y. This function will be called for each panel, but in practice the
|
||||
# result is always the same across panels, so we'll cache the result.
|
||||
mappings_cache <- NULL
|
||||
find_plot_mappings <- function(b) {
|
||||
if (!is.null(mappings_cache))
|
||||
return(mappings_cache)
|
||||
|
||||
# lapply'ing as.character results in unexpected behavior for expressions
|
||||
# like `wt/2`. This works better.
|
||||
mappings <- as.list(as.character(b$plot$mapping))
|
||||
|
||||
# If x or y mapping is missing, look in each layer for mappings and return
|
||||
# the first one.
|
||||
missing_mappings <- setdiff(c("x", "y"), names(mappings))
|
||||
if (length(missing_mappings) != 0) {
|
||||
# Grab mappings for each layer
|
||||
layer_mappings <- lapply(b$plot$layers, function(layer) {
|
||||
lapply(layer$mapping, as.character)
|
||||
})
|
||||
|
||||
# Get just the first x or y value in the combined list of plot and layer
|
||||
# mappings.
|
||||
mappings <- c(list(mappings), layer_mappings)
|
||||
mappings <- Reduce(x = mappings, init = list(x = NULL, y = NULL),
|
||||
function(init, m) {
|
||||
if (is.null(init$x) && !is.null(m$x)) init$x <- m$x
|
||||
if (is.null(init$y) && !is.null(m$y)) init$y <- m$y
|
||||
init
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
mappings_cache <<- mappings
|
||||
mappings
|
||||
}
|
||||
|
||||
# Given a gtable object, return the x and y ranges (in pixel dimensions)
|
||||
find_panel_ranges <- function(g, pixelratio) {
|
||||
# Given a vector of unit objects, return logical vector indicating which ones
|
||||
# are "null" units. These units use the remaining available width/height --
|
||||
# that is, the space not occupied by elements that have an absolute size.
|
||||
is_null_unit <- function(x) {
|
||||
vapply(x, FUN.VALUE = logical(1), function(u) {
|
||||
isTRUE(attr(u, "unit", exact = TRUE) == "null")
|
||||
})
|
||||
}
|
||||
|
||||
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
|
||||
h_px <- function(x) as.numeric(grid::convertHeight(x, "native"))
|
||||
w_px <- function(x) as.numeric(grid::convertWidth(x, "native"))
|
||||
|
||||
# Given a vector of relative sizes (in grid units), and a function for
|
||||
# converting grid units to numeric pixels, return a numeric vector of
|
||||
# pixel sizes.
|
||||
find_px_sizes <- function(rel_sizes, unit_to_px) {
|
||||
# Total pixels (in height or width)
|
||||
total_px <- unit_to_px(grid::unit(1, "npc"))
|
||||
# Calculate size of all panel(s) together. Panels (and only panels) have
|
||||
# null size.
|
||||
null_idx <- is_null_unit(rel_sizes)
|
||||
# All the absolute heights. At this point, null heights are 0. We need to
|
||||
# calculate them separately and add them in later.
|
||||
px_sizes <- unit_to_px(rel_sizes)
|
||||
# Total size for panels is image size minus absolute (non-panel) elements
|
||||
panel_px_total <- total_px - sum(px_sizes)
|
||||
# Divide up the total panel size up into the panels (scaled by size)
|
||||
panel_sizes_rel <- as.numeric(rel_sizes[null_idx])
|
||||
panel_sizes_rel <- panel_sizes_rel / sum(panel_sizes_rel)
|
||||
px_sizes[null_idx] <- panel_px_total * panel_sizes_rel
|
||||
abs(px_sizes)
|
||||
}
|
||||
|
||||
px_heights <- find_px_sizes(g$heights, h_px)
|
||||
px_widths <- find_px_sizes(g$widths, w_px)
|
||||
|
||||
# Convert to absolute pixel positions
|
||||
x_pos <- cumsum(px_widths)
|
||||
y_pos <- cumsum(px_heights)
|
||||
|
||||
# Match up the pixel dimensions to panels
|
||||
layout <- g$layout
|
||||
# For panels:
|
||||
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
|
||||
# * For no facet or facet_grid, they'll just be named "panel". For
|
||||
# facet_grid, we need to re-order the layout table. Assume that panel
|
||||
# numbers go from left to right, then next row.
|
||||
# Assign a number to each panel, corresponding to PANEl in the built ggplot
|
||||
# object.
|
||||
layout <- layout[grepl("^panel", layout$name), ]
|
||||
layout <- layout[order(layout$t, layout$l), ]
|
||||
layout$panel <- seq_len(nrow(layout))
|
||||
|
||||
# When using a HiDPI client on a Linux server, the pixel
|
||||
# dimensions are doubled, so we have to divide the dimensions by
|
||||
# `pixelratio`. When a HiDPI client is used on a Mac server (with
|
||||
# the quartz device), the pixel dimensions _aren't_ doubled, even though
|
||||
# the image has double size. In the latter case we don't have to scale the
|
||||
# numbers down.
|
||||
pix_ratio <- 1
|
||||
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
|
||||
pix_ratio <- pixelratio
|
||||
}
|
||||
|
||||
# Return list of lists, where each inner list has left, right, top, bottom
|
||||
# values for a panel
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
p <- layout[i, , drop = FALSE]
|
||||
list(
|
||||
left = x_pos[p$l - 1] / pix_ratio,
|
||||
right = x_pos[p$r] / pix_ratio,
|
||||
bottom = y_pos[p$b] / pix_ratio,
|
||||
top = y_pos[p$t - 1] / pix_ratio
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# If print(p) gets dispatched to print.ggplot(p), attempt to extract coordmap.
|
||||
# If dispatched to another method, just print the object and don't attempt to
|
||||
# extract the coordmap. This can happen if there's another print method that
|
||||
# takes precedence.
|
||||
if (identical(which_method("print", p), "ggplot")) {
|
||||
res <- print(p)
|
||||
|
||||
tryCatch({
|
||||
# Get info from built ggplot object
|
||||
info <- find_panel_info(res$build)
|
||||
|
||||
# Get ranges from gtable - it's possible for this to return more elements than
|
||||
# info, because it calculates positions even for panels that aren't present.
|
||||
# This can happen with facet_wrap.
|
||||
ranges <- find_panel_ranges(res$gtable, pixelratio)
|
||||
|
||||
for (i in seq_along(info)) {
|
||||
info[[i]]$range <- ranges[[i]]
|
||||
}
|
||||
|
||||
return(info)
|
||||
|
||||
}, error = function(e) {
|
||||
# If there was an error extracting info from the ggplot object, just return
|
||||
# a list with the error message.
|
||||
return(structure(list(), error = e$message))
|
||||
})
|
||||
|
||||
} else {
|
||||
print(p)
|
||||
return(list())
|
||||
}
|
||||
}
|
||||
227
R/run-url.R
227
R/run-url.R
@@ -1,124 +1,35 @@
|
||||
#' Run a Shiny application from https://gist.github.com
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
|
||||
#'
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'}
|
||||
#' are all valid values.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGist <- function(gist,
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive())) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype=".tar.gz", subdir=NULL, port=port,
|
||||
launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a GitHub repository
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted in a GitHub repository.
|
||||
#'
|
||||
#' @param repo Name of the repository
|
||||
#' @param username GitHub username
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch
|
||||
#' name. Defaults to \code{"master"}.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, port = NULL,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
|
||||
if (is.null(ref)) {
|
||||
stop("Must specify either a ref. ")
|
||||
}
|
||||
|
||||
message("Downloading github repo(s) ",
|
||||
paste(repo, ref, sep = "/", collapse = ", "),
|
||||
" from ",
|
||||
paste(username, collapse = ", "))
|
||||
name <- paste(username, "-", repo, sep = "")
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir=subdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a URL
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted at a downloadable
|
||||
#' URL. The Shiny application must be saved in a .zip, .tar, or .tar.gz file.
|
||||
#' The Shiny application files must be contained in a subdirectory in the
|
||||
#' archive. For example, the files might be \code{myapp/server.r} and
|
||||
#' \code{myapp/ui.r}.
|
||||
#'
|
||||
#' \code{runUrl()} downloads and launches a Shiny application that is hosted at
|
||||
#' a downloadable URL. The Shiny application must be saved in a .zip, .tar, or
|
||||
#' .tar.gz file. The Shiny application files must be contained in the root
|
||||
#' directory or a subdirectory in the archive. For example, the files might be
|
||||
#' \code{myapp/server.r} and \code{myapp/ui.r}. The functions \code{runGitHub()}
|
||||
#' and \code{runGist()} are based on \code{runUrl()}, using URL's from GitHub
|
||||
#' (\url{https://github.com}) and GitHub gists (\url{https://gist.github.com}),
|
||||
#' respectively.
|
||||
#' @param url URL of the application.
|
||||
#' @param filetype The file type (\code{".zip"}, \code{".tar"}, or
|
||||
#' \code{".tar.gz"}. Defaults to the file extension taken from the url.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @param destdir Directory to store the downloaded application files. If \code{NULL}
|
||||
#' (the default), the application files will be stored in a temporary directory
|
||||
#' and removed when the app exits
|
||||
#' @param ... Other arguments to be passed to \code{\link{runApp}()}, such as
|
||||
#' \code{port} and \code{launch.browser}.
|
||||
#' @export
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
|
||||
stop("'..' not allowed in subdir")
|
||||
@@ -136,7 +47,15 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
stop("Unknown file extension.")
|
||||
|
||||
message("Downloading ", url)
|
||||
filePath <- tempfile('shinyapp', fileext=fileext)
|
||||
if (is.null(destdir)) {
|
||||
filePath <- tempfile('shinyapp', fileext = fileext)
|
||||
fileDir <- tempfile('shinyapp')
|
||||
} else {
|
||||
fileDir <- destdir
|
||||
filePath <- paste(destdir, fileext)
|
||||
}
|
||||
|
||||
dir.create(fileDir, showWarnings = FALSE)
|
||||
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", url)
|
||||
on.exit(unlink(filePath))
|
||||
@@ -148,17 +67,83 @@ runUrl <- function(url, filetype = NULL, subdir = NULL, port = NULL,
|
||||
# 2) If the internal untar implementation is used, it chokes on the 'g'
|
||||
# type flag that github uses (to stash their commit hash info).
|
||||
# By using our own forked/modified untar2 we sidestep both issues.
|
||||
dirname <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = dirname(filePath))
|
||||
first <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = fileDir)
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
dirname <- as.character(unzip(filePath, list=TRUE)$Name[1])
|
||||
unzip(filePath, exdir = dirname(filePath))
|
||||
first <- as.character(utils::unzip(filePath, list=TRUE)$Name)[1]
|
||||
utils::unzip(filePath, exdir = fileDir)
|
||||
}
|
||||
|
||||
appdir <- file.path(dirname(filePath), dirname)
|
||||
on.exit(unlink(appdir, recursive = TRUE), add = TRUE)
|
||||
if(is.null(destdir)){
|
||||
on.exit(unlink(fileDir, recursive = TRUE), add = TRUE)
|
||||
}
|
||||
|
||||
appsubdir <- ifelse(is.null(subdir), appdir, file.path(appdir, subdir))
|
||||
runApp(appsubdir, port=port, launch.browser=launch.browser)
|
||||
appdir <- file.path(fileDir, first)
|
||||
if (!utils::file_test('-d', appdir)) appdir <- dirname(appdir)
|
||||
|
||||
if (!is.null(subdir)) appdir <- file.path(appdir, subdir)
|
||||
runApp(appdir, ...)
|
||||
}
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'} are
|
||||
#' all valid values.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
runGist <- function(gist, destdir = NULL, ...) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype = ".zip", destdir = destdir, ...)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param repo Name of the repository.
|
||||
#' @param username GitHub username. If \code{repo} is of the form
|
||||
#' \code{"username/repo"}, \code{username} will be taken from \code{repo}.
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch name.
|
||||
#' Defaults to \code{"master"}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#' # or runGitHub("rstudio/shiny_example")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
if (grepl('/', repo)) {
|
||||
res <- strsplit(repo, '/')[[1]]
|
||||
if (length(res) != 2) stop("'repo' must be of the form 'username/repo'")
|
||||
username <- res[1]
|
||||
repo <- res[2]
|
||||
}
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir = subdir, destdir = destdir, ...)
|
||||
}
|
||||
|
||||
111
R/server-input-handlers.R
Normal file
111
R/server-input-handlers.R
Normal file
@@ -0,0 +1,111 @@
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by jsonlite) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using jsonlite.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default jsonlite serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", function(data, ...) {
|
||||
if (length(data) == 0)
|
||||
return(matrix(nrow=0, ncol=0))
|
||||
|
||||
m <- matrix(unlist(lapply(data, function(x) {
|
||||
sapply(x, function(y) {
|
||||
ifelse(is.null(y), NA, y)
|
||||
})
|
||||
})), nrow = length(data[[1]]), ncol = length(data))
|
||||
return(m)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.datetime", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to POSIXct vector
|
||||
times <- lapply(val, function(x) {
|
||||
if (is.null(x)) NA
|
||||
else x
|
||||
})
|
||||
as.POSIXct(unlist(times), origin = "1970-01-01", tz = "UTC")
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
245
R/server.R
245
R/server.R
@@ -1,110 +1,7 @@
|
||||
#' @include globals.R
|
||||
#' @include server-input-handlers.R
|
||||
|
||||
appsByToken <- Map$new()
|
||||
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by RJSONIO) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using RJSONIO.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default RJSONIO serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", function(data, ...) {
|
||||
if (length(data) == 0)
|
||||
return(matrix(nrow=0, ncol=0))
|
||||
|
||||
m <- matrix(unlist(lapply(data, function(x) {
|
||||
sapply(x, function(y) {
|
||||
ifelse(is.null(y), NA, y)
|
||||
})
|
||||
})), nrow = length(data[[1]]), ncol = length(data))
|
||||
return(m)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
|
||||
# Provide a character representation of the WS that can be used
|
||||
# as a key in a Map.
|
||||
wsToKey <- function(WS) {
|
||||
@@ -169,13 +66,6 @@ addResourcePath <- function(prefix, directoryPath) {
|
||||
|
||||
existing <- .globals$resources[[prefix]]
|
||||
|
||||
if (!is.null(existing)) {
|
||||
if (!identical(existing$directoryPath, directoryPath)) {
|
||||
warning("Overriding existing prefix ", prefix, " => ",
|
||||
existing$directoryPath)
|
||||
}
|
||||
}
|
||||
|
||||
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
|
||||
func=staticHandler(directoryPath))
|
||||
}
|
||||
@@ -209,14 +99,15 @@ resourcePathHandler <- function(req) {
|
||||
#'
|
||||
#' Defines the server-side logic of the Shiny application. This generally
|
||||
#' involves creating functions that map user inputs to various kinds of output.
|
||||
#' In older versions of Shiny, it was necessary to call \code{shinyServer()} in
|
||||
#' the \code{server.R} file, but this is no longer required as of Shiny 0.10.
|
||||
#' Now the \code{server.R} file may simply return the appropriate server
|
||||
#' function (as the last expression in the code), without calling
|
||||
#' \code{shinyServer()}.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @details
|
||||
#' Call \code{shinyServer} from your application's \code{server.R} file, passing
|
||||
#' in a "server function" that provides the server-side logic of your
|
||||
#' application.
|
||||
#' Call \code{shinyServer} from your application's \code{server.R}
|
||||
#' file, passing in a "server function" that provides the server-side logic of
|
||||
#' your application.
|
||||
#'
|
||||
#' The server function will be called when each client (web browser) first loads
|
||||
#' the Shiny application's page. It must take an \code{input} and an
|
||||
@@ -227,6 +118,9 @@ resourcePathHandler <- function(req) {
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
|
||||
#' on how to write a server function.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # A very simple Shiny app that takes a message from the user
|
||||
@@ -236,10 +130,21 @@ resourcePathHandler <- function(req) {
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' })
|
||||
#'
|
||||
#'
|
||||
#' # It is also possible for a server.R file to simply return the function,
|
||||
#' # without calling shinyServer().
|
||||
#' # For example, the server.R file could contain just the following:
|
||||
#' function(input, output, session) {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyServer <- function(func) {
|
||||
.globals$server <- list(func)
|
||||
invisible(func)
|
||||
}
|
||||
|
||||
@@ -248,8 +153,12 @@ decodeMessage <- function(data) {
|
||||
packBits(rawToBits(data[pos:(pos+3)]), type='integer')
|
||||
}
|
||||
|
||||
if (readInt(1) != 0x01020202L)
|
||||
return(fromJSON(rawToChar(data), asText=TRUE, simplify=FALSE))
|
||||
if (readInt(1) != 0x01020202L) {
|
||||
# Treat message as UTF-8
|
||||
charData <- rawToChar(data)
|
||||
Encoding(charData) <- 'UTF-8'
|
||||
return(jsonlite::fromJSON(charData, simplifyVector=FALSE))
|
||||
}
|
||||
|
||||
i <- 5
|
||||
parts <- list()
|
||||
@@ -277,7 +186,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
# This value, if non-NULL, must be present on all HTTP and WebSocket
|
||||
# requests as the Shiny-Shared-Secret header or else access will be
|
||||
# denied (403 response for HTTP, and instant close for websocket).
|
||||
sharedSecret <- getOption('shiny.sharedSecret', NULL)
|
||||
sharedSecret <- getOption('shiny.sharedSecret')
|
||||
|
||||
appHandlers <- list(
|
||||
http = joinHandlers(c(
|
||||
@@ -302,7 +211,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
if (is.character(msg))
|
||||
msg <- charToRaw(msg)
|
||||
|
||||
if (getOption('shiny.trace', FALSE)) {
|
||||
if (isTRUE(getOption('shiny.trace'))) {
|
||||
if (binary)
|
||||
message("RECV ", '$$binary data$$')
|
||||
else
|
||||
@@ -394,9 +303,9 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
args$clientData <- shinysession$clientData
|
||||
|
||||
if ('session' %in% names(formals(serverFunc)))
|
||||
args$session <- shinysession$session
|
||||
args$session <- shinysession
|
||||
|
||||
withReactiveDomain(shinysession$session, {
|
||||
withReactiveDomain(shinysession, {
|
||||
do.call(appvars$server, args)
|
||||
})
|
||||
})
|
||||
@@ -436,7 +345,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
})
|
||||
|
||||
ws$onClose(function() {
|
||||
shinysession$close()
|
||||
shinysession$wsClosed()
|
||||
appsByToken$remove(shinysession$token)
|
||||
})
|
||||
|
||||
@@ -462,7 +371,7 @@ identicalFunctionBodies <- function(a, b) {
|
||||
handlerManager <- HandlerManager$new()
|
||||
|
||||
addSubApp <- function(appObj, autoRemove = TRUE) {
|
||||
path <- sprintf("/%s", createUniqueId(16))
|
||||
path <- createUniqueId(16, "/app")
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
|
||||
# remove the leading / from the path so a relative path is returned
|
||||
@@ -538,8 +447,8 @@ serviceApp <- function() {
|
||||
|
||||
#' Run Shiny Application
|
||||
#'
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt
|
||||
#' R to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt R
|
||||
#' to stop the application (usually by pressing Ctrl+C or Esc).
|
||||
#'
|
||||
#' The host parameter was introduced in Shiny 0.9.0. Its default value of
|
||||
#' \code{"127.0.0.1"} means that, contrary to previous versions of Shiny, only
|
||||
@@ -549,9 +458,15 @@ serviceApp <- function() {
|
||||
#'
|
||||
#' @param appDir The directory of the application. Should contain
|
||||
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
|
||||
#' contains the file \code{index.html}. Defaults to the working directory.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' choosing a random port.
|
||||
#' contains the file \code{index.html}. Alternately, instead of
|
||||
#' \code{server.R} and \code{ui.R}, the directory may contain just
|
||||
#' \code{app.R}. Defaults to the working directory. Instead of a directory,
|
||||
#' this could be a list with \code{ui} and \code{server} components, or a
|
||||
#' Shiny app object created by \code{\link{shinyApp}}.
|
||||
#' @param port The TCP port that the application should listen on. If the
|
||||
#' \code{port} is not specified, and the \code{shiny.port} option is set (with
|
||||
#' \code{options(shiny.port = XX)}), then that port will be used. Otherwise,
|
||||
#' use a random port.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only. This value of this parameter can also be a
|
||||
@@ -566,8 +481,8 @@ serviceApp <- function() {
|
||||
#' the value \code{"showcase"}, shows application code and metadata from a
|
||||
#' \code{DESCRIPTION} file in the application directory alongside the
|
||||
#' application. If set to \code{"normal"}, displays the application normally.
|
||||
#' Defaults to \code{"auto"}, which displays the application in the mode
|
||||
#' given in its \code{DESCRIPTION} file, if any.
|
||||
#' Defaults to \code{"auto"}, which displays the application in the mode given
|
||||
#' in its \code{DESCRIPTION} file, if any.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
@@ -576,22 +491,37 @@ serviceApp <- function() {
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#' }
|
||||
#'
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Apps can be run without a server.r and ui.r file
|
||||
#' runApp(list(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' # Apps can be run without a server.r and ui.r file
|
||||
#' runApp(list(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#' # Running a Shiny app object
|
||||
#' app <- shinyApp(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' )
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=NULL,
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
@@ -617,7 +547,7 @@ runApp <- function(appDir=getwd(),
|
||||
# SHINY_SERVER_VERSION, those will return "" which is considered less than
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
if (utils::compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
}
|
||||
@@ -633,7 +563,9 @@ runApp <- function(appDir=getwd(),
|
||||
if (is.character(appDir)) {
|
||||
desc <- file.path.ci(appDir, "DESCRIPTION")
|
||||
if (file.exists(desc)) {
|
||||
settings <- read.dcf(desc)
|
||||
con <- file(desc, encoding = checkEncoding(desc))
|
||||
on.exit(close(con), add = TRUE)
|
||||
settings <- read.dcf(con)
|
||||
if ("DisplayMode" %in% colnames(settings)) {
|
||||
mode <- settings[1,"DisplayMode"]
|
||||
if (mode == "Showcase") {
|
||||
@@ -684,10 +616,12 @@ runApp <- function(appDir=getwd(),
|
||||
}
|
||||
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
# Set up the onEnd before we call onStart, so that it gets called even if an
|
||||
# error happens in onStart.
|
||||
if (!is.null(appParts$onEnd))
|
||||
on.exit(appParts$onEnd(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
@@ -759,15 +693,16 @@ stopApp <- function(returnValue = NULL) {
|
||||
#' code or commentary.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#'
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#'
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' }
|
||||
#' @export
|
||||
runExample <- function(example=NA,
|
||||
|
||||
566
R/shiny.R
566
R/shiny.R
@@ -1,4 +1,4 @@
|
||||
#' @include utils.R
|
||||
#' @include utils.R stack.R
|
||||
NULL
|
||||
|
||||
#' Web Application Framework for R
|
||||
@@ -8,22 +8,102 @@ NULL
|
||||
#' pre-built widgets make it possible to build beautiful, responsive, and
|
||||
#' powerful applications with minimal effort.
|
||||
#'
|
||||
#' The Shiny tutorial at \url{http://rstudio.github.com/shiny/tutorial} explains
|
||||
#' The Shiny tutorial at \url{http://shiny.rstudio.com/tutorial/} explains
|
||||
#' the framework in depth, walks you through building a simple application, and
|
||||
#' includes extensive annotated examples.
|
||||
#'
|
||||
#' @seealso \link{shiny-options} for documentation about global options.
|
||||
#'
|
||||
#' @name shiny-package
|
||||
#' @aliases shiny
|
||||
#' @docType package
|
||||
#' @import httpuv caTools RJSONIO xtable digest methods
|
||||
#' @import htmltools httpuv xtable digest R6 mime
|
||||
NULL
|
||||
|
||||
createUniqueId <- function(bytes) {
|
||||
# It's necessary to Depend on methods so Rscript doesn't fail. It's necessary
|
||||
# to import(methods) in NAMESPACE so R CMD check doesn't complain. This
|
||||
# approach isn't foolproof because Rscript -e pkgname::func() doesn't actually
|
||||
# cause methods to be attached, but it's not a problem for shiny::runApp()
|
||||
# since we call require(shiny) as part of loading the app.
|
||||
#' @import methods
|
||||
NULL
|
||||
|
||||
|
||||
#' Global options for Shiny
|
||||
#'
|
||||
#' There are a number of global options that affect Shiny's behavior. These can
|
||||
#' be set with (for example) \code{options(shiny.trace=TRUE)}.
|
||||
#'
|
||||
#' \describe{
|
||||
#' \item{shiny.launch.browser}{A boolean which controls the default behavior
|
||||
#' when an app is run. See \code{\link{runApp}} for more information.}
|
||||
#' \item{shiny.port}{A port number that Shiny will listen on. See
|
||||
#' \code{\link{runApp}} for more information.}
|
||||
#' \item{shiny.trace}{If \code{TRUE}, all of the messages sent between the R
|
||||
#' server and the web browser client will be printed on the console. This
|
||||
#' is useful for debugging.}
|
||||
#' \item{shiny.reactlog}{If \code{TRUE}, enable logging of reactive events,
|
||||
#' which can be viewed later with the \code{\link{showReactLog}} function.
|
||||
#' This incurs a substantial performance penalty and should not be used in
|
||||
#' production.}
|
||||
#' \item{shiny.usecairo}{This is used to disable graphical rendering by the
|
||||
#' Cairo package, if it is installed. See \code{\link{plotPNG}} for more
|
||||
#' information.}
|
||||
#' \item{shiny.maxRequestSize}{This is a number which specifies the maximum
|
||||
#' web request size, which serves as a size limit for file uploads. If
|
||||
#' unset, the maximum request size defaults to 5MB.}
|
||||
#' \item{shiny.suppressMissingContextError}{Normally, invoking a reactive
|
||||
#' outside of a reactive context (or \code{\link{isolate}()}) results in
|
||||
#' an error. If this is \code{TRUE}, don't error in these cases. This
|
||||
#' should only be used for debugging or demonstrations of reactivity at the
|
||||
#' console.}
|
||||
#' \item{shiny.host}{The IP address that Shiny should listen on. See
|
||||
#' \code{\link{runApp}} for more information.}
|
||||
#' \item{shiny.json.digits}{The number of digits to use when converting
|
||||
#' numbers to JSON format to send to the client web browser.}
|
||||
#' \item{shiny.minified}{If this is \code{TRUE} or unset (the default), then
|
||||
#' Shiny will use minified JavaScript (\code{shiny.min.js}). If
|
||||
#' \code{FALSE}, then Shiny will use the un-minified JavaScript
|
||||
#' (\code{shiny.js}); this can be useful during development.}
|
||||
#' \item{shiny.error}{This can be a function which is called when an error
|
||||
#' occurs. For example, \code{options(shiny.error=recover)} will result a
|
||||
#' the debugger prompt when an error occurs.}
|
||||
#' \item{shiny.observer.error}{This can be a function that is called by an
|
||||
#' observer when an unhandled error occurs in it or an upstream reactive.
|
||||
#' By default, these errors will result in a warning at the console, and
|
||||
#' the websocket connection will close.}
|
||||
#' \item{shiny.table.class}{CSS class names to use for tables.}
|
||||
#' \item{shiny.deprecation.messages}{This controls whether messages for
|
||||
#' deprecated functions in Shiny will be printed. See
|
||||
#' \code{\link{shinyDeprecated}} for more information.}
|
||||
#' }
|
||||
#' @name shiny-options
|
||||
NULL
|
||||
createUniqueId <- function(bytes, prefix = "", suffix = "") {
|
||||
withPrivateSeed({
|
||||
paste(as.hexmode(sample(256, bytes, replace = TRUE)-1), collapse = "")
|
||||
paste(
|
||||
prefix,
|
||||
paste(
|
||||
format(as.hexmode(sample(256, bytes, replace = TRUE)-1), width=2),
|
||||
collapse = ""),
|
||||
suffix,
|
||||
sep = ""
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
toJSON <- function(x, ..., dataframe = "columns", null = "null", na = "null",
|
||||
auto_unbox = TRUE, digits = getOption("shiny.json.digits", 16),
|
||||
use_signif = TRUE, force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
|
||||
rownames = FALSE, keep_vec_names = TRUE) {
|
||||
|
||||
# I(x) is so that length-1 atomic vectors get put in [].
|
||||
jsonlite::toJSON(I(x), dataframe = dataframe, null = null, na = na,
|
||||
auto_unbox = auto_unbox, digits = digits, use_signif = use_signif,
|
||||
force = force, POSIXt = POSIXt, UTC = UTC, rownames = rownames,
|
||||
keep_vec_names = keep_vec_names, json_verbatim = TRUE, ...)
|
||||
}
|
||||
|
||||
# Call the workerId func with no args to get the worker id, and with an arg to
|
||||
# set it.
|
||||
#
|
||||
@@ -80,10 +160,10 @@ workerId <- local({
|
||||
#' }
|
||||
#' }
|
||||
#' \code{clientData} also contains information about each output.
|
||||
#' \code{output_\emph{outputId}_width} and \code{output_\emph{outputId}_height}
|
||||
#' \code{output_\var{outputId}_width} and \code{output_\var{outputId}_height}
|
||||
#' give the dimensions (using \code{offsetWidth} and \code{offsetHeight}) of
|
||||
#' the DOM element that is bound to \code{\emph{outputId}}, and
|
||||
#' \code{output_\emph{outputId}_hidden} is a logical that indicates whether
|
||||
#' the DOM element that is bound to \code{\var{outputId}}, and
|
||||
#' \code{output_\var{outputId}_hidden} is a logical that indicates whether
|
||||
#' the element is hidden. These values may be \code{NULL} if the output is
|
||||
#' not bound.
|
||||
#' }
|
||||
@@ -140,7 +220,7 @@ workerId <- local({
|
||||
#' \item{sendCustomMessage(type, message)}{
|
||||
#' Sends a custom message to the web page. \code{type} must be a
|
||||
#' single-element character vector giving the type of message, while
|
||||
#' \code{message} can be any RJSONIO-encodable value. Custom messages
|
||||
#' \code{message} can be any jsonlite-encodable value. Custom messages
|
||||
#' have no meaning to Shiny itself; they are used soley to convey information
|
||||
#' to custom JavaScript logic in the browser. You can do this by adding
|
||||
#' JavaScript code to the browser that calls
|
||||
@@ -163,84 +243,147 @@ NULL
|
||||
|
||||
|
||||
#' @include utils.R
|
||||
ShinySession <- setRefClass(
|
||||
ShinySession <- R6Class(
|
||||
'ShinySession',
|
||||
fields = list(
|
||||
.websocket = 'ANY',
|
||||
.invalidatedOutputValues = 'Map',
|
||||
.invalidatedOutputErrors = 'Map',
|
||||
.inputMessageQueue = 'list', # A list of inputMessages to send when flushed
|
||||
.outputs = 'list', # Keeps track of all the output observer objects
|
||||
.outputOptions = 'list', # Options for each of the output observer objects
|
||||
.progressKeys = 'character',
|
||||
.showcase = 'ANY',
|
||||
.fileUploadContext = 'FileUploadContext',
|
||||
private = list(
|
||||
# There are some private items with a leading "."; except for the dot, these
|
||||
# items share a name with a public item.
|
||||
websocket = 'ANY',
|
||||
invalidatedOutputValues = 'Map',
|
||||
invalidatedOutputErrors = 'Map',
|
||||
inputMessageQueue = list(), # A list of inputMessages to send when flushed
|
||||
.outputs = list(), # Keeps track of all the output observer objects
|
||||
.outputOptions = list(), # Options for each of the output observer objects
|
||||
progressKeys = 'character',
|
||||
showcase = 'ANY',
|
||||
fileUploadContext = 'FileUploadContext',
|
||||
.input = 'ANY', # Internal ReactiveValues object for normal input sent from client
|
||||
.clientData = 'ANY', # Internal ReactiveValues object for other data sent from the client
|
||||
.closedCallbacks = 'Callbacks',
|
||||
.flushCallbacks = 'Callbacks',
|
||||
.flushedCallbacks = 'Callbacks',
|
||||
closedCallbacks = 'Callbacks',
|
||||
flushCallbacks = 'Callbacks',
|
||||
flushedCallbacks = 'Callbacks',
|
||||
inputReceivedCallbacks = 'Callbacks',
|
||||
sendResponse = function(requestMsg, value) {
|
||||
if (is.null(requestMsg$tag)) {
|
||||
warning("Tried to send response for untagged message; method: ",
|
||||
requestMsg$method)
|
||||
return()
|
||||
}
|
||||
private$write(toJSON(list(response=list(tag=requestMsg$tag, value=value))))
|
||||
},
|
||||
sendErrorResponse = function(requestMsg, error) {
|
||||
if (is.null(requestMsg$tag))
|
||||
return()
|
||||
private$write(toJSON(list(response=list(tag=requestMsg$tag, error=error))))
|
||||
},
|
||||
write = function(json) {
|
||||
if (self$closed){
|
||||
return()
|
||||
}
|
||||
if (isTRUE(getOption('shiny.trace')))
|
||||
message('SEND ',
|
||||
gsub('(?m)base64,[a-zA-Z0-9+/=]+','[base64 data]',json,perl=TRUE))
|
||||
# first convert to native encoding, then to UTF8, otherwise we may get the
|
||||
# error in Chrome "WebSocket connection failed: Could not decode a text
|
||||
# frame as UTF-8"
|
||||
json <- enc2utf8(enc2native(json))
|
||||
private$websocket$send(json)
|
||||
},
|
||||
getOutputOption = function(outputName, propertyName, defaultValue) {
|
||||
opts <- private$.outputOptions[[outputName]]
|
||||
if (is.null(opts))
|
||||
return(defaultValue)
|
||||
result <- opts[[propertyName]]
|
||||
if (is.null(result))
|
||||
return(defaultValue)
|
||||
return(result)
|
||||
},
|
||||
shouldSuspend = function(name) {
|
||||
# Find corresponding hidden state clientData variable, with the format
|
||||
# "output_foo_hidden". (It comes from .clientdata_output_foo_hidden
|
||||
# on the JS side)
|
||||
# Some tricky stuff: instead of accessing names using input$names(),
|
||||
# get the names directly via input$.values, to avoid triggering reactivity.
|
||||
# Need to handle cases where the output object isn't actually used
|
||||
# in the web page; in these cases, there's no output_foo_hidden flag,
|
||||
# and hidden should be TRUE. In other words, NULL and TRUE should map to
|
||||
# TRUE, FALSE should map to FALSE.
|
||||
hidden <- private$.clientData$.values[[paste("output_", name, "_hidden",
|
||||
sep="")]]
|
||||
if (is.null(hidden)) hidden <- TRUE
|
||||
|
||||
return(hidden && private$getOutputOption(name, 'suspendWhenHidden', TRUE))
|
||||
},
|
||||
|
||||
registerSessionEndCallbacks = function() {
|
||||
# This is to be called from the initialization. It registers functions
|
||||
# that are called when a session ends.
|
||||
|
||||
# Clear file upload directories, if present
|
||||
self$onSessionEnded(private$fileUploadContext$rmUploadDirs)
|
||||
}
|
||||
),
|
||||
public = list(
|
||||
progressStack = 'Stack', # Stack of progress objects
|
||||
input = 'reactivevalues', # Externally-usable S3 wrapper object for .input
|
||||
output = 'ANY', # Externally-usable S3 wrapper object for .outputs
|
||||
clientData = 'reactivevalues', # Externally-usable S3 wrapper object for .clientData
|
||||
token = 'character', # Used to identify this instance in URLs
|
||||
files = 'Map', # For keeping track of files sent to client
|
||||
downloads = 'Map',
|
||||
closed = 'logical',
|
||||
session = 'environment', # Object for the server app to access session stuff
|
||||
singletons = 'character' # Tracks singleton HTML fragments sent to the page
|
||||
),
|
||||
methods = list(
|
||||
closed = logical(0),
|
||||
request = 'ANY', # Websocket request object
|
||||
singletons = character(0), # Tracks singleton HTML fragments sent to the page
|
||||
user = NULL,
|
||||
groups = NULL,
|
||||
|
||||
initialize = function(websocket) {
|
||||
.websocket <<- websocket
|
||||
closed <<- FALSE
|
||||
private$websocket <- websocket
|
||||
self$closed <- FALSE
|
||||
# TODO: Put file upload context in user/app-specific dir if possible
|
||||
|
||||
.input <<- ReactiveValues$new()
|
||||
.clientData <<- ReactiveValues$new()
|
||||
private$invalidatedOutputValues <- Map$new()
|
||||
private$invalidatedOutputErrors <- Map$new()
|
||||
private$fileUploadContext <- FileUploadContext$new()
|
||||
private$closedCallbacks <- Callbacks$new()
|
||||
private$flushCallbacks <- Callbacks$new()
|
||||
private$flushedCallbacks <- Callbacks$new()
|
||||
private$inputReceivedCallbacks <- Callbacks$new()
|
||||
private$.input <- ReactiveValues$new()
|
||||
private$.clientData <- ReactiveValues$new()
|
||||
self$progressStack <- Stack$new()
|
||||
self$files <- Map$new()
|
||||
self$downloads <- Map$new()
|
||||
|
||||
input <<- .createReactiveValues(.input, readonly=TRUE)
|
||||
.setLabel(input, 'input')
|
||||
clientData <<- .createReactiveValues(.clientData, readonly=TRUE)
|
||||
.setLabel(clientData, 'clientData')
|
||||
self$input <- .createReactiveValues(private$.input, readonly=TRUE)
|
||||
.setLabel(self$input, 'input')
|
||||
self$clientData <- .createReactiveValues(private$.clientData, readonly=TRUE)
|
||||
.setLabel(self$clientData, 'clientData')
|
||||
|
||||
output <<- .createOutputWriter(.self)
|
||||
self$output <- .createOutputWriter(self)
|
||||
|
||||
token <<- createUniqueId(16)
|
||||
.outputs <<- list()
|
||||
.outputOptions <<- list()
|
||||
self$token <- createUniqueId(16)
|
||||
private$.outputs <- list()
|
||||
private$.outputOptions <- list()
|
||||
|
||||
session <<- new.env(parent=emptyenv())
|
||||
session$clientData <<- clientData
|
||||
session$sendCustomMessage <<- .self$.sendCustomMessage
|
||||
session$sendInputMessage <<- .self$.sendInputMessage
|
||||
session$onSessionEnded <<- .self$onSessionEnded
|
||||
session$onEnded <<- .self$onEnded
|
||||
session$onFlush <<- .self$onFlush
|
||||
session$onFlushed <<- .self$onFlushed
|
||||
session$isClosed <<- .self$isClosed
|
||||
session$input <<- .self$input
|
||||
session$output <<- .self$output
|
||||
session$reactlog <<- .self$reactlog
|
||||
session$registerDataObj <<- .self$registerDataObj
|
||||
session$.impl <<- .self
|
||||
private$registerSessionEndCallbacks()
|
||||
|
||||
if (!is.null(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)) {
|
||||
try({
|
||||
creds <- fromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
|
||||
session$user <<- creds$user
|
||||
session$groups <<- creds$groups
|
||||
creds <- jsonlite::fromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
|
||||
self$user <- creds$user
|
||||
self$groups <- creds$groups
|
||||
}, silent=FALSE)
|
||||
}
|
||||
|
||||
# session$request should throw an error if httpuv doesn't have
|
||||
# websocket$request, but don't throw it until a caller actually
|
||||
# tries to access session$request
|
||||
delayedAssign('request', websocket$request, assign.env = session)
|
||||
delayedAssign('request', websocket$request, assign.env = self)
|
||||
|
||||
.write(toJSON(list(config = list(
|
||||
private$write(toJSON(list(config = list(
|
||||
workerId = workerId(),
|
||||
sessionId = token
|
||||
sessionId = self$token
|
||||
))))
|
||||
},
|
||||
onSessionEnded = function(callback) {
|
||||
@@ -248,18 +391,31 @@ ShinySession <- setRefClass(
|
||||
(i.e. the connection to the client has been severed). The return value
|
||||
is a function which unregisters the callback. If multiple callbacks are
|
||||
registered, the order in which they are invoked is not guaranteed."
|
||||
return(.closedCallbacks$register(callback))
|
||||
return(private$closedCallbacks$register(callback))
|
||||
},
|
||||
onEnded = function(callback) {
|
||||
"Synonym for onSessionEnded"
|
||||
return(onSessionEnded(callback))
|
||||
return(self$onSessionEnded(callback))
|
||||
},
|
||||
onInputReceived = function(callback) {
|
||||
"Registers the given callback to be invoked when the session receives
|
||||
new data from the client."
|
||||
return(private$inputReceivedCallbacks$register(callback))
|
||||
},
|
||||
unhandledError = function(e) {
|
||||
self$close()
|
||||
},
|
||||
close = function() {
|
||||
closed <<- TRUE
|
||||
for (output in .outputs) {
|
||||
if (!self$closed) {
|
||||
private$websocket$close()
|
||||
}
|
||||
},
|
||||
wsClosed = function() {
|
||||
self$closed <- TRUE
|
||||
for (output in private$.outputs) {
|
||||
output$suspend()
|
||||
}
|
||||
.closedCallbacks$invoke(onError=function(e) {
|
||||
private$closedCallbacks$invoke(onError=function(e) {
|
||||
warning(simpleWarning(
|
||||
paste("An error occurred in an onSessionEnded handler:",
|
||||
e$message),
|
||||
@@ -273,13 +429,13 @@ ShinySession <- setRefClass(
|
||||
})
|
||||
},
|
||||
isClosed = function() {
|
||||
return(closed)
|
||||
return(self$closed)
|
||||
},
|
||||
isEnded = function() {
|
||||
return(isClosed())
|
||||
return(self$isClosed())
|
||||
},
|
||||
setShowcase = function(value) {
|
||||
.showcase <<- !is.null(value) && as.logical(value)
|
||||
private$showcase <- !is.null(value) && as.logical(value)
|
||||
},
|
||||
defineOutput = function(name, func, label) {
|
||||
"Binds an output generating function to this name. The function can either
|
||||
@@ -292,15 +448,15 @@ ShinySession <- setRefClass(
|
||||
force(name)
|
||||
|
||||
# If overwriting an output object, suspend the previous copy of it
|
||||
if (!is.null(.outputs[[name]])) {
|
||||
.outputs[[name]]$suspend()
|
||||
if (!is.null(private$.outputs[[name]])) {
|
||||
private$.outputs[[name]]$suspend()
|
||||
}
|
||||
|
||||
if (is.function(func)) {
|
||||
if (length(formals(func)) != 0) {
|
||||
orig <- func
|
||||
func <- function() {
|
||||
orig(name=name, shinysession=.self)
|
||||
orig(name=name, shinysession=self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,31 +470,49 @@ ShinySession <- setRefClass(
|
||||
|
||||
obs <- observe({
|
||||
|
||||
value <- try(shinyCallingHandlers(func()), silent=FALSE)
|
||||
value <- try(
|
||||
{
|
||||
tryCatch(
|
||||
shinyCallingHandlers(func()),
|
||||
shiny.silent.error = function(cond) {
|
||||
# Don't let shiny.silent.error go through the normal stop
|
||||
# path of try, because we don't want it to print. But we
|
||||
# do want to try to return the same looking result so that
|
||||
# the code below can send the error to the browser.
|
||||
structure(
|
||||
NULL,
|
||||
class = "try-error",
|
||||
condition = cond
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
silent=FALSE
|
||||
)
|
||||
|
||||
.invalidatedOutputErrors$remove(name)
|
||||
.invalidatedOutputValues$remove(name)
|
||||
private$invalidatedOutputErrors$remove(name)
|
||||
private$invalidatedOutputValues$remove(name)
|
||||
|
||||
if (inherits(value, 'try-error')) {
|
||||
cond <- attr(value, 'condition')
|
||||
type <- setdiff(class(cond), c('simpleError', 'error', 'condition'))
|
||||
.invalidatedOutputErrors$set(
|
||||
private$invalidatedOutputErrors$set(
|
||||
name,
|
||||
list(message = cond$message,
|
||||
call = capture.output(print(cond$call)),
|
||||
call = utils::capture.output(print(cond$call)),
|
||||
type = if (length(type)) type))
|
||||
}
|
||||
else
|
||||
.invalidatedOutputValues$set(name, value)
|
||||
}, suspended=.shouldSuspend(name), label=label)
|
||||
private$invalidatedOutputValues$set(name, value)
|
||||
}, suspended=private$shouldSuspend(name), label=label)
|
||||
|
||||
obs$onInvalidate(function() {
|
||||
showProgress(name)
|
||||
self$showProgress(name)
|
||||
})
|
||||
|
||||
.outputs[[name]] <<- obs
|
||||
if (is.null(.outputOptions[[name]]))
|
||||
.outputOptions[[name]] <<- list()
|
||||
private$.outputs[[name]] <- obs
|
||||
if (is.null(private$.outputOptions[[name]]))
|
||||
private$.outputOptions[[name]] <- list()
|
||||
}
|
||||
else {
|
||||
stop(paste("Unexpected", class(func), "output for", name))
|
||||
@@ -346,30 +520,30 @@ ShinySession <- setRefClass(
|
||||
},
|
||||
flushOutput = function() {
|
||||
|
||||
.flushCallbacks$invoke()
|
||||
on.exit(.flushedCallbacks$invoke())
|
||||
private$flushCallbacks$invoke()
|
||||
on.exit(private$flushedCallbacks$invoke())
|
||||
|
||||
if (length(.progressKeys) == 0
|
||||
&& length(.invalidatedOutputValues) == 0
|
||||
&& length(.invalidatedOutputErrors) == 0
|
||||
&& length(.inputMessageQueue) == 0) {
|
||||
if (length(private$progressKeys) == 0
|
||||
&& length(private$invalidatedOutputValues) == 0
|
||||
&& length(private$invalidatedOutputErrors) == 0
|
||||
&& length(private$inputMessageQueue) == 0) {
|
||||
return(invisible())
|
||||
}
|
||||
|
||||
.progressKeys <<- character(0)
|
||||
private$progressKeys <- character(0)
|
||||
|
||||
values <- .invalidatedOutputValues
|
||||
.invalidatedOutputValues <<- Map$new()
|
||||
errors <- .invalidatedOutputErrors
|
||||
.invalidatedOutputErrors <<- Map$new()
|
||||
inputMessages <- .inputMessageQueue
|
||||
.inputMessageQueue <<- list()
|
||||
values <- private$invalidatedOutputValues
|
||||
private$invalidatedOutputValues <- Map$new()
|
||||
errors <- private$invalidatedOutputErrors
|
||||
private$invalidatedOutputErrors <- Map$new()
|
||||
inputMessages <- private$inputMessageQueue
|
||||
private$inputMessageQueue <- list()
|
||||
|
||||
json <- toJSON(list(errors=as.list(errors),
|
||||
values=as.list(values),
|
||||
inputMessages=inputMessages))
|
||||
|
||||
.write(json)
|
||||
private$write(json)
|
||||
},
|
||||
showProgress = function(id) {
|
||||
'Send a message to the client that recalculation of the output identified
|
||||
@@ -379,65 +553,56 @@ ShinySession <- setRefClass(
|
||||
|
||||
# If app is already closed, be sure not to show progress, otherwise we
|
||||
# will get an error because of the closed websocket
|
||||
if (closed)
|
||||
if (self$closed)
|
||||
return()
|
||||
|
||||
if (id %in% .progressKeys)
|
||||
if (id %in% private$progressKeys)
|
||||
return()
|
||||
|
||||
.progressKeys <<- c(.progressKeys, id)
|
||||
private$progressKeys <- c(private$progressKeys, id)
|
||||
|
||||
json <- toJSON(list(progress=list(id)))
|
||||
|
||||
.write(json)
|
||||
self$sendProgress('binding', list(id = id))
|
||||
},
|
||||
sendProgress = function(type, message) {
|
||||
json <- toJSON(list(
|
||||
progress = list(type = type, message = message)
|
||||
))
|
||||
private$write(json)
|
||||
},
|
||||
dispatch = function(msg) {
|
||||
method <- paste('@', msg$method, sep='')
|
||||
# we must use $ instead of [[ here at the moment; see
|
||||
# https://github.com/rstudio/shiny/issues/274
|
||||
func <- try(do.call(`$`, list(.self, method)), silent=TRUE)
|
||||
func <- try(do.call(`$`, list(self, method)), silent=TRUE)
|
||||
if (inherits(func, 'try-error')) {
|
||||
.sendErrorResponse(msg, paste('Unknown method', msg$method))
|
||||
private$sendErrorResponse(msg, paste('Unknown method', msg$method))
|
||||
}
|
||||
|
||||
value <- try(do.call(func, as.list(append(msg$args, msg$blobs))),
|
||||
silent=TRUE)
|
||||
if (inherits(value, 'try-error')) {
|
||||
.sendErrorResponse(msg, conditionMessage(attr(value, 'condition')))
|
||||
private$sendErrorResponse(msg, conditionMessage(attr(value, 'condition')))
|
||||
}
|
||||
else {
|
||||
.sendResponse(msg, value)
|
||||
private$sendResponse(msg, value)
|
||||
}
|
||||
},
|
||||
.sendResponse = function(requestMsg, value) {
|
||||
if (is.null(requestMsg$tag)) {
|
||||
warning("Tried to send response for untagged message; method: ",
|
||||
requestMsg$method)
|
||||
return()
|
||||
}
|
||||
.write(toJSON(list(response=list(tag=requestMsg$tag, value=value))))
|
||||
},
|
||||
.sendErrorResponse = function(requestMsg, error) {
|
||||
if (is.null(requestMsg$tag))
|
||||
return()
|
||||
.write(toJSON(list(response=list(tag=requestMsg$tag, error=error))))
|
||||
},
|
||||
.sendCustomMessage = function(type, message) {
|
||||
sendCustomMessage = function(type, message) {
|
||||
data <- list()
|
||||
data[[type]] <- message
|
||||
.write(toJSON(list(custom=data)))
|
||||
private$write(toJSON(list(custom=data)))
|
||||
},
|
||||
.sendInputMessage = function(inputId, message) {
|
||||
sendInputMessage = function(inputId, message) {
|
||||
data <- list(id = inputId, message = message)
|
||||
|
||||
# Add to input message queue
|
||||
.inputMessageQueue[[length(.inputMessageQueue) + 1]] <<- data
|
||||
private$inputMessageQueue[[length(private$inputMessageQueue) + 1]] <- data
|
||||
},
|
||||
onFlush = function(func, once = TRUE) {
|
||||
if (!isTRUE(once)) {
|
||||
return(.flushCallbacks$register(func))
|
||||
return(private$flushCallbacks$register(func))
|
||||
} else {
|
||||
dereg <- .flushCallbacks$register(function() {
|
||||
dereg <- private$flushCallbacks$register(function() {
|
||||
dereg()
|
||||
func()
|
||||
})
|
||||
@@ -446,9 +611,9 @@ ShinySession <- setRefClass(
|
||||
},
|
||||
onFlushed = function(func, once = TRUE) {
|
||||
if (!isTRUE(once)) {
|
||||
return(.flushedCallbacks$register(func))
|
||||
return(private$flushedCallbacks$register(func))
|
||||
} else {
|
||||
dereg <- .flushedCallbacks$register(function() {
|
||||
dereg <- private$flushedCallbacks$register(function() {
|
||||
dereg()
|
||||
func()
|
||||
})
|
||||
@@ -456,27 +621,19 @@ ShinySession <- setRefClass(
|
||||
}
|
||||
},
|
||||
reactlog = function(logEntry) {
|
||||
if (.showcase)
|
||||
.sendCustomMessage("reactlog", logEntry)
|
||||
},
|
||||
.write = function(json) {
|
||||
if (closed){
|
||||
return()
|
||||
}
|
||||
if (getOption('shiny.trace', FALSE))
|
||||
message('SEND ',
|
||||
gsub('(?m)base64,[a-zA-Z0-9+/=]+','[base64 data]',json,perl=TRUE))
|
||||
if (getOption('shiny.transcode.json', TRUE))
|
||||
json <- iconv(json, to='UTF-8')
|
||||
.websocket$send(json)
|
||||
if (private$showcase)
|
||||
self$sendCustomMessage("reactlog", logEntry)
|
||||
},
|
||||
|
||||
# Public RPC methods
|
||||
`@uploadieFinish` = function() {
|
||||
# Do nothing; just want the side effect of flushReact, output flush, etc.
|
||||
},
|
||||
`@uploadInit` = function(fileInfos) {
|
||||
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
|
||||
fileInfos <- lapply(fileInfos, function(fi) {
|
||||
if (is.null(fi$type))
|
||||
fi$type <- getContentType(tools::file_ext(fi$name))
|
||||
fi$type <- getContentType(fi$name)
|
||||
fi
|
||||
})
|
||||
sizes <- sapply(fileInfos, function(fi){ fi$size })
|
||||
@@ -484,15 +641,15 @@ ShinySession <- setRefClass(
|
||||
stop("Maximum upload size exceeded")
|
||||
}
|
||||
|
||||
jobId <- .fileUploadContext$createUploadOperation(fileInfos)
|
||||
jobId <- private$fileUploadContext$createUploadOperation(fileInfos)
|
||||
return(list(jobId=jobId,
|
||||
uploadUrl=paste('session', token, 'upload',
|
||||
uploadUrl=paste('session', self$token, 'upload',
|
||||
paste(jobId, "?w=", workerId(), sep=""),
|
||||
sep='/')))
|
||||
},
|
||||
`@uploadEnd` = function(jobId, inputId) {
|
||||
fileData <- .fileUploadContext$getUploadOperation(jobId)$finish()
|
||||
.input$set(inputId, fileData)
|
||||
fileData <- private$fileUploadContext$getUploadOperation(jobId)$finish()
|
||||
private$.input$set(inputId, fileData)
|
||||
invisible()
|
||||
},
|
||||
# Provides a mechanism for handling direct HTTP requests that are posted
|
||||
@@ -509,7 +666,7 @@ ShinySession <- setRefClass(
|
||||
return(httpResponse(400, 'text/html', '<h1>Bad Request</h1>'))
|
||||
|
||||
if (matches[2] == 'file') {
|
||||
savedFile <- files$get(utils::URLdecode(matches[3]))
|
||||
savedFile <- self$files$get(URLdecode(matches[3]))
|
||||
if (is.null(savedFile))
|
||||
return(httpResponse(404, 'text/html', '<h1>Not Found</h1>'))
|
||||
|
||||
@@ -517,7 +674,7 @@ ShinySession <- setRefClass(
|
||||
}
|
||||
|
||||
if (matches[2] == 'upload' && identical(req$REQUEST_METHOD, "POST")) {
|
||||
job <- .fileUploadContext$getUploadOperation(matches[3])
|
||||
job <- private$fileUploadContext$getUploadOperation(matches[3])
|
||||
if (!is.null(job)) {
|
||||
fileName <- req$HTTP_SHINY_FILE_NAME
|
||||
fileType <- req$HTTP_SHINY_FILE_TYPE
|
||||
@@ -534,6 +691,13 @@ ShinySession <- setRefClass(
|
||||
}
|
||||
}
|
||||
|
||||
if (matches[2] == 'uploadie' && identical(req$REQUEST_METHOD, "POST")) {
|
||||
id <- URLdecode(matches[3])
|
||||
res <- mime::parse_multipart(req)
|
||||
private$.input$set(id, res[[id]])
|
||||
return(httpResponse(200, 'text/plain', 'OK'))
|
||||
}
|
||||
|
||||
if (matches[2] == 'download') {
|
||||
|
||||
# A bunch of ugliness here. Filenames can be dynamically generated by
|
||||
@@ -557,8 +721,8 @@ ShinySession <- setRefClass(
|
||||
dlmatches <- regmatches(matches[3],
|
||||
regexec("^([^/]+)(/[^/]+)?$",
|
||||
matches[3]))[[1]]
|
||||
dlname <- utils::URLdecode(dlmatches[2])
|
||||
download <- downloads$get(dlname)
|
||||
dlname <- URLdecode(dlmatches[2])
|
||||
download <- self$downloads$get(dlname)
|
||||
if (is.null(download))
|
||||
return(httpResponse(404, 'text/html', '<h1>Not Found</h1>'))
|
||||
|
||||
@@ -575,23 +739,31 @@ ShinySession <- setRefClass(
|
||||
|
||||
return(httpResponse(302, 'text/html', '<h1>Found</h1>', c(
|
||||
'Location' = sprintf('%s/%s',
|
||||
utils::URLencode(dlname, TRUE),
|
||||
utils::URLencode(filename, TRUE)),
|
||||
URLencode(dlname, TRUE),
|
||||
URLencode(filename, TRUE)),
|
||||
'Cache-Control' = 'no-cache')))
|
||||
}
|
||||
|
||||
tmpdata <- tempfile()
|
||||
# Make temp file with the same extension as the user-visible filename.
|
||||
# If the extension is not used, some functions such as pdf() and zip()
|
||||
# may append the extension they expect, meaning the data we want will
|
||||
# be written to a file other than our temp file (e.g. file1231.zip
|
||||
# instead of file1231.zip).
|
||||
ext <- tools::file_ext(filename)
|
||||
if (nzchar(ext))
|
||||
ext <- paste(".", ext, sep = "")
|
||||
tmpdata <- tempfile(fileext = ext)
|
||||
result <- try(Context$new(getDefaultReactiveDomain(), '[download]')$run(
|
||||
function() { download$func(tmpdata) }
|
||||
))
|
||||
if (inherits(result, 'try-error')) {
|
||||
unlink(tmpdata)
|
||||
return(httpResponse(500, 'text/plain',
|
||||
attr(result, 'condition')$message))
|
||||
return(httpResponse(500, 'text/plain; charset=UTF-8',
|
||||
enc2utf8(attr(result, 'condition')$message)))
|
||||
}
|
||||
return(httpResponse(
|
||||
200,
|
||||
download$contentType %OR% getContentType(tools::file_ext(filename)),
|
||||
download$contentType %OR% getContentType(filename),
|
||||
# owned=TRUE means tmpdata will be deleted after response completes
|
||||
list(file=tmpdata, owned=TRUE),
|
||||
c(
|
||||
@@ -610,8 +782,8 @@ ShinySession <- setRefClass(
|
||||
dlmatches <- regmatches(matches[3],
|
||||
regexec("^([^/]+)(/[^/]+)?$",
|
||||
matches[3]))[[1]]
|
||||
dlname <- utils::URLdecode(dlmatches[2])
|
||||
download <- downloads$get(dlname)
|
||||
dlname <- URLdecode(dlmatches[2])
|
||||
download <- self$downloads$get(dlname)
|
||||
return(download$filter(download$data, req))
|
||||
}
|
||||
|
||||
@@ -620,9 +792,9 @@ ShinySession <- setRefClass(
|
||||
saveFileUrl = function(name, data, contentType, extra=list()) {
|
||||
"Creates an entry in the file map for the data, and returns a URL pointing
|
||||
to the file."
|
||||
files$set(name, list(data=data, contentType=contentType))
|
||||
self$files$set(name, list(data=data, contentType=contentType))
|
||||
return(sprintf('session/%s/file/%s?w=%s&r=%s',
|
||||
URLencode(token, TRUE),
|
||||
URLencode(self$token, TRUE),
|
||||
URLencode(name, TRUE),
|
||||
workerId(),
|
||||
createUniqueId(8)))
|
||||
@@ -638,96 +810,74 @@ ShinySession <- setRefClass(
|
||||
|
||||
fileData <- readBin(file, 'raw', n=bytes)
|
||||
|
||||
if (isTRUE(.clientData$.values$allowDataUriScheme)) {
|
||||
b64 <- base64encode(fileData)
|
||||
if (isTRUE(private$.clientData$.values$allowDataUriScheme)) {
|
||||
b64 <- rawToBase64(fileData)
|
||||
return(paste('data:', contentType, ';base64,', b64, sep=''))
|
||||
} else {
|
||||
return(saveFileUrl(name, fileData, contentType))
|
||||
return(self$saveFileUrl(name, fileData, contentType))
|
||||
}
|
||||
},
|
||||
registerDownload = function(name, filename, contentType, func) {
|
||||
|
||||
downloads$set(name, list(filename = filename,
|
||||
self$downloads$set(name, list(filename = filename,
|
||||
contentType = contentType,
|
||||
func = func))
|
||||
return(sprintf('session/%s/download/%s?w=%s',
|
||||
URLencode(token, TRUE),
|
||||
URLencode(self$token, TRUE),
|
||||
URLencode(name, TRUE),
|
||||
workerId()))
|
||||
},
|
||||
# register a data object on the server side (for datatable or selectize, etc)
|
||||
registerDataObj = function(name, data, filterFunc) {
|
||||
# abusing downloads at the moment
|
||||
downloads$set(name, list(data = data, filter = filterFunc))
|
||||
self$downloads$set(name, list(data = data, filter = filterFunc))
|
||||
return(sprintf('session/%s/dataobj/%s?w=%s',
|
||||
URLencode(token, TRUE),
|
||||
URLencode(self$token, TRUE),
|
||||
URLencode(name, TRUE),
|
||||
workerId()))
|
||||
},
|
||||
.getOutputOption = function(outputName, propertyName, defaultValue) {
|
||||
opts <- .outputOptions[[outputName]]
|
||||
if (is.null(opts))
|
||||
return(defaultValue)
|
||||
result <- opts[[propertyName]]
|
||||
if (is.null(result))
|
||||
return(defaultValue)
|
||||
return(result)
|
||||
},
|
||||
.shouldSuspend = function(name) {
|
||||
# Find corresponding hidden state clientData variable, with the format
|
||||
# "output_foo_hidden". (It comes from .clientdata_output_foo_hidden
|
||||
# on the JS side)
|
||||
# Some tricky stuff: instead of accessing names using input$names(),
|
||||
# get the names directly via input$.values, to avoid triggering reactivity.
|
||||
# Need to handle cases where the output object isn't actually used
|
||||
# in the web page; in these cases, there's no output_foo_hidden flag,
|
||||
# and hidden should be TRUE. In other words, NULL and TRUE should map to
|
||||
# TRUE, FALSE should map to FALSE.
|
||||
hidden <- .clientData$.values[[paste("output_", name, "_hidden",
|
||||
sep="")]]
|
||||
if (is.null(hidden)) hidden <- TRUE
|
||||
|
||||
return(hidden && .getOutputOption(name, 'suspendWhenHidden', TRUE))
|
||||
},
|
||||
# This function suspends observers for hidden outputs and resumes observers
|
||||
# for un-hidden outputs.
|
||||
manageHiddenOutputs = function() {
|
||||
# Find hidden state for each output, and suspend/resume accordingly
|
||||
for (outputName in names(.outputs)) {
|
||||
if (.shouldSuspend(outputName)) {
|
||||
.outputs[[outputName]]$suspend()
|
||||
for (outputName in names(private$.outputs)) {
|
||||
if (private$shouldSuspend(outputName)) {
|
||||
private$.outputs[[outputName]]$suspend()
|
||||
} else {
|
||||
.outputs[[outputName]]$resume()
|
||||
private$.outputs[[outputName]]$resume()
|
||||
}
|
||||
}
|
||||
},
|
||||
# Set the normal and client data input variables
|
||||
manageInputs = function(data) {
|
||||
|
||||
private$inputReceivedCallbacks$invoke(data)
|
||||
|
||||
data_names <- names(data)
|
||||
|
||||
# Separate normal input variables from client data input variables
|
||||
clientdata_idx <- grepl("^.clientdata_", data_names)
|
||||
|
||||
# Set normal (non-clientData) input values
|
||||
.input$mset(data[data_names[!clientdata_idx]])
|
||||
private$.input$mset(data[data_names[!clientdata_idx]])
|
||||
|
||||
# Strip off .clientdata_ from clientdata input names, and set values
|
||||
input_clientdata <- data[data_names[clientdata_idx]]
|
||||
names(input_clientdata) <- sub("^.clientdata_", "",
|
||||
names(input_clientdata))
|
||||
.clientData$mset(input_clientdata)
|
||||
private$.clientData$mset(input_clientdata)
|
||||
},
|
||||
outputOptions = function(name, ...) {
|
||||
# If no name supplied, return the list of options for all outputs
|
||||
if (is.null(name))
|
||||
return(.outputOptions)
|
||||
if (! name %in% names(.outputs))
|
||||
return(private$.outputOptions)
|
||||
if (! name %in% names(private$.outputs))
|
||||
stop(name, " is not in list of output objects")
|
||||
|
||||
opts <- list(...)
|
||||
# If no options are set, return the options for the specified output
|
||||
if (length(opts) == 0)
|
||||
return(.outputOptions[[name]])
|
||||
return(private$.outputOptions[[name]])
|
||||
|
||||
# Set the appropriate option
|
||||
validOpts <- c("suspendWhenHidden", "priority")
|
||||
@@ -735,20 +885,30 @@ ShinySession <- setRefClass(
|
||||
if (! optname %in% validOpts)
|
||||
stop(optname, " is not a valid option")
|
||||
|
||||
.outputOptions[[name]][[optname]] <<- opts[[optname]]
|
||||
private$.outputOptions[[name]][[optname]] <- opts[[optname]]
|
||||
}
|
||||
|
||||
# If any changes to suspendWhenHidden, need to re-run manageHiddenOutputs
|
||||
if ("suspendWhenHidden" %in% names(opts)) {
|
||||
manageHiddenOutputs()
|
||||
self$manageHiddenOutputs()
|
||||
}
|
||||
|
||||
if ("priority" %in% names(opts)) {
|
||||
.outputs[[name]]$setPriority(opts[['priority']])
|
||||
private$.outputs[[name]]$setPriority(opts[['priority']])
|
||||
}
|
||||
|
||||
invisible()
|
||||
}
|
||||
),
|
||||
active = list(
|
||||
session = function() {
|
||||
shinyDeprecated(
|
||||
msg = paste("Attempted to access deprecated shinysession$session object.",
|
||||
"Please just access the shinysession object directly."),
|
||||
version = "0.11.1"
|
||||
)
|
||||
self
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
231
R/shinyui.R
231
R/shinyui.R
@@ -1,143 +1,5 @@
|
||||
#' @include globals.R
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
p <- function(...) tags$p(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h1 <- function(...) tags$h1(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h2 <- function(...) tags$h2(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h3 <- function(...) tags$h3(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h4 <- function(...) tags$h4(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h5 <- function(...) tags$h5(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
h6 <- function(...) tags$h6(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
a <- function(...) tags$a(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
br <- function(...) tags$br(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
div <- function(...) tags$div(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
span <- function(...) tags$span(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
pre <- function(...) tags$pre(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
code <- function(...) tags$code(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
img <- function(...) tags$img(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
strong <- function(...) tags$strong(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
em <- function(...) tags$em(...)
|
||||
|
||||
#' @rdname builder
|
||||
#' @export
|
||||
hr <- function(...) tags$hr(...)
|
||||
|
||||
#' Include Content From a File
|
||||
#'
|
||||
#' Include HTML, text, or rendered Markdown into a \link[=shinyUI]{Shiny UI}.
|
||||
#'
|
||||
#' These functions provide a convenient way to include an extensive amount of
|
||||
#' HTML, textual, Markdown, CSS, or JavaScript content, rather than using a
|
||||
#' large literal R string.
|
||||
#'
|
||||
#' @note \code{includeText} escapes its contents, but does no other processing.
|
||||
#' This means that hard breaks and multiple spaces will be rendered as they
|
||||
#' usually are in HTML: as a single space character. If you are looking for
|
||||
#' preformatted text, wrap the call with \code{\link{pre}}, or consider using
|
||||
#' \code{includeMarkdown} instead.
|
||||
#'
|
||||
#' @note The \code{includeMarkdown} function requires the \code{markdown}
|
||||
#' package.
|
||||
#'
|
||||
#' @param path The path of the file to be included. It is highly recommended to
|
||||
#' use a relative path (the base path being the Shiny application directory),
|
||||
#' not an absolute path.
|
||||
#'
|
||||
#' @rdname include
|
||||
#' @name include
|
||||
#' @aliases includeHTML
|
||||
#' @export
|
||||
includeHTML <- function(path) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(HTML(paste(lines, collapse='\r\n')))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeText <- function(path) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(paste(lines, collapse='\r\n'))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeMarkdown <- function(path) {
|
||||
library(markdown)
|
||||
|
||||
dependsOnFile(path)
|
||||
html <- markdown::markdownToHTML(path, fragment.only=TRUE)
|
||||
Encoding(html) <- 'UTF-8'
|
||||
return(HTML(html))
|
||||
}
|
||||
|
||||
#' @param ... Any additional attributes to be applied to the generated tag.
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeCSS <- function(path, ...) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
args <- list(...)
|
||||
if (is.null(args$type))
|
||||
args$type <- 'text/css'
|
||||
return(do.call(tags$style,
|
||||
c(list(HTML(paste(lines, collapse='\r\n'))), args)))
|
||||
}
|
||||
|
||||
#' @rdname include
|
||||
#' @export
|
||||
includeScript <- function(path, ...) {
|
||||
dependsOnFile(path)
|
||||
lines <- readLines(path, warn=FALSE, encoding='UTF-8')
|
||||
return(tags$script(HTML(paste(lines, collapse='\r\n')), ...))
|
||||
}
|
||||
NULL
|
||||
|
||||
#' Load the MathJax library and typeset math expressions
|
||||
#'
|
||||
@@ -152,7 +14,7 @@ includeScript <- function(path, ...) {
|
||||
#' # now we can just write "static" content without withMathJax()
|
||||
#' div("more math here $$\\sqrt{2}$$")
|
||||
withMathJax <- function(...) {
|
||||
path <- 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
tagList(
|
||||
tags$head(
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
@@ -162,42 +24,33 @@ withMathJax <- function(...) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Include Content Only Once
|
||||
#'
|
||||
#' Use \code{singleton} to wrap contents (tag, text, HTML, or lists) that should
|
||||
#' be included in the generated document only once, yet may appear in the
|
||||
#' document-generating code more than once. Only the first appearance of the
|
||||
#' content (in document order) will be used. Useful for custom components that
|
||||
#' have JavaScript files or stylesheets.
|
||||
#'
|
||||
#' @param x A \code{\link{tag}}, text, \code{\link{HTML}}, or list.
|
||||
#'
|
||||
#' @export
|
||||
singleton <- function(x) {
|
||||
class(x) <- c(class(x), 'shiny.singleton')
|
||||
return(x)
|
||||
}
|
||||
|
||||
renderPage <- function(ui, connection, showcase=0) {
|
||||
|
||||
if (showcase > 0)
|
||||
ui <- tagList(tags$head(showcaseHead()), ui)
|
||||
ui <- showcaseUI(ui)
|
||||
|
||||
# Wrap ui in body tag if it doesn't already have a single top-level body tag.
|
||||
if (!(inherits(ui, "shiny.tag") && ui$name == "body"))
|
||||
ui <- tags$body(ui)
|
||||
|
||||
result <- renderTags(ui)
|
||||
|
||||
deps <- c(
|
||||
list(
|
||||
html_dependency("jquery", "1.11.0", "shared", script = "jquery.js"),
|
||||
html_dependency("shiny", packageVersion("shiny"), "shared",
|
||||
script = "shiny.js", stylesheet = "shiny.css")
|
||||
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
|
||||
htmlDependency("jquery", "1.11.0", c(href="shared"), script = "jquery.min.js"),
|
||||
htmlDependency("shiny", utils::packageVersion("shiny"), c(href="shared"),
|
||||
script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js",
|
||||
stylesheet = "shiny.css")
|
||||
),
|
||||
result$dependencies
|
||||
)
|
||||
deps <- resolveDependencies(deps)
|
||||
deps <- lapply(deps, createWebDependency)
|
||||
depStr <- paste(sapply(deps, function(dep) {
|
||||
sprintf("%s[%s]", dep$name, dep$version)
|
||||
}), collapse = ";")
|
||||
depHtml <- html_dependencies_as_character(deps)
|
||||
depHtml <- renderDependencies(deps, "href")
|
||||
|
||||
# write preamble
|
||||
writeLines(c('<!DOCTYPE html>',
|
||||
@@ -215,40 +68,34 @@ renderPage <- function(ui, connection, showcase=0) {
|
||||
con = connection)
|
||||
writeLines(c(result$head,
|
||||
'</head>',
|
||||
'<body>',
|
||||
recursive=TRUE),
|
||||
con = connection)
|
||||
|
||||
if (showcase > 0) {
|
||||
# in showcase mode, emit containing elements and app HTML
|
||||
writeLines(as.character(showcaseBody(result$html)),
|
||||
con = connection)
|
||||
} else {
|
||||
# in normal mode, write UI html directly to connection
|
||||
writeLines(result$html, con = connection)
|
||||
}
|
||||
writeLines(result$html, con = connection)
|
||||
|
||||
# write end document
|
||||
writeLines(c('</body>',
|
||||
'</html>'),
|
||||
writeLines('</html>',
|
||||
con = connection)
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
#'
|
||||
#' Historically this function was used in ui.R files to register a user
|
||||
#' interface with Shiny. It is no longer required; simply ensure that the last
|
||||
#' expression to be returned from ui.R is a user interface. This function is
|
||||
#' kept for backwards compatibility with older applications. It returns the
|
||||
#' value that is passed to it.
|
||||
#' interface with Shiny. It is no longer required as of Shiny 0.10; simply
|
||||
#' ensure that the last expression to be returned from ui.R is a user interface.
|
||||
#' This function is kept for backwards compatibility with older applications. It
|
||||
#' returns the value that is passed to it.
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
#'
|
||||
#' @export
|
||||
shinyUI <- function(ui) ui
|
||||
shinyUI <- function(ui) {
|
||||
.globals$ui <- list(ui)
|
||||
ui
|
||||
}
|
||||
|
||||
uiHttpHandler <- function(ui, path = "/") {
|
||||
uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
|
||||
force(ui)
|
||||
|
||||
@@ -256,7 +103,7 @@ uiHttpHandler <- function(ui, path = "/") {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (req$PATH_INFO != path)
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
@@ -273,28 +120,14 @@ uiHttpHandler <- function(ui, path = "/") {
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
}
|
||||
else
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=html))
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
}
|
||||
}
|
||||
|
||||
#' Return HTML dependencies provided by Shiny
|
||||
#'
|
||||
#' By default, Shiny supplies some framework scripts when it renders a page.
|
||||
#' \code{getProvidedHtmlDependencies} returns a list of those provided objects.
|
||||
#'
|
||||
#' @return A list of objects of type \code{html_dependency}, one per dependency
|
||||
#'
|
||||
#' @export
|
||||
getProvidedHtmlDependencies <- function() {
|
||||
list(structure(
|
||||
list(name = "jquery",
|
||||
version = "1.11.0",
|
||||
path = system.file("www/shared/jquery.js", package="shiny"),
|
||||
script = "jquery.js"),
|
||||
class = "html_dependency"))
|
||||
}
|
||||
|
||||
@@ -16,146 +16,26 @@ globalVariables('func')
|
||||
#'
|
||||
#' @export
|
||||
markRenderFunction <- function(uiFunc, renderFunc) {
|
||||
class(renderFunc) <- c("shiny.render.function", "function")
|
||||
attr(renderFunc, "outputFunc") <- uiFunc
|
||||
renderFunc
|
||||
structure(renderFunc,
|
||||
class = c("shiny.render.function", "function"),
|
||||
outputFunc = uiFunc)
|
||||
}
|
||||
|
||||
useRenderFunction <- function(renderFunc) {
|
||||
useRenderFunction <- function(renderFunc, inline = FALSE) {
|
||||
outputFunction <- attr(renderFunc, "outputFunc")
|
||||
id <- createUniqueId(8)
|
||||
id <- createUniqueId(8, "out")
|
||||
o <- getDefaultReactiveDomain()$output
|
||||
if (!is.null(o))
|
||||
o[[id]] <- renderFunc
|
||||
return(outputFunction(id))
|
||||
if (is.logical(formals(outputFunction)[["inline"]])) {
|
||||
outputFunction(id, inline = inline)
|
||||
} else outputFunction(id)
|
||||
}
|
||||
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' @seealso For more details on how the plots are generated, and how to control
|
||||
#' the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetWidth} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
#' \code{'auto'}; in the body of the function you may reference reactive
|
||||
#' values and functions.
|
||||
#' @param height The height of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetHeight} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
#' \code{'auto'}; in the body of the function you may reference reactive
|
||||
#' values and functions.
|
||||
#' @param res Resolution of resulting plot, in pixels per inch. This value is
|
||||
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
|
||||
#' rendering in R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that generates a plot (deprecated; use \code{expr}
|
||||
#' instead).
|
||||
#'
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- NULL
|
||||
|
||||
if (is.function(height))
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
return(markRenderFunction(plotOutput, function(shinysession, name, ...) {
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
# height were explicitly specified).
|
||||
prefix <- 'output_'
|
||||
if (width == 'auto')
|
||||
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
|
||||
if (height == 'auto')
|
||||
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
|
||||
|
||||
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
result <- withVisible(func())
|
||||
if (result$visible)
|
||||
print(result$value)
|
||||
|
||||
# Now capture some graphics device info before we close it
|
||||
usrCoords <- par('usr')
|
||||
usrBounds <- usrCoords
|
||||
if (par('xlog')) {
|
||||
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
|
||||
}
|
||||
if (par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
coordmap <<- list(
|
||||
usr = c(
|
||||
left = usrCoords[1],
|
||||
right = usrCoords[2],
|
||||
bottom = usrCoords[3],
|
||||
top = usrCoords[4]
|
||||
),
|
||||
# The bounds of the plot area, in DOM pixels
|
||||
bounds = c(
|
||||
left = grconvertX(usrBounds[1], 'user', 'nfc') * width,
|
||||
right = grconvertX(usrBounds[2], 'user', 'nfc') * width,
|
||||
bottom = (1-grconvertY(usrBounds[3], 'user', 'nfc')) * height,
|
||||
top = (1-grconvertY(usrBounds[4], 'user', 'nfc')) * height
|
||||
),
|
||||
log = c(
|
||||
x = par('xlog'),
|
||||
y = par('ylog')
|
||||
),
|
||||
pixelratio = pixelratio
|
||||
)
|
||||
}
|
||||
|
||||
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
|
||||
height=height*pixelratio, res=res*pixelratio, args))
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
# Return a list of attributes for the img
|
||||
return(list(
|
||||
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
|
||||
width=width, height=height, coordmap=coordmap
|
||||
))
|
||||
}))
|
||||
#' @method as.tags shiny.render.function
|
||||
as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
useRenderFunction(x, inline = inline)
|
||||
}
|
||||
|
||||
#' Image file output
|
||||
@@ -259,11 +139,7 @@ renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
}
|
||||
|
||||
# If contentType not specified, autodetect based on extension
|
||||
if (is.null(imageinfo$contentType)) {
|
||||
contentType <- getContentType(sub('^.*\\.', '', basename(imageinfo$src)))
|
||||
} else {
|
||||
contentType <- imageinfo$contentType
|
||||
}
|
||||
contentType <- imageinfo$contentType %OR% getContentType(imageinfo$src)
|
||||
|
||||
# Extra values are everything in imageinfo except 'src' and 'contentType'
|
||||
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
|
||||
@@ -302,14 +178,14 @@ renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL)
|
||||
}
|
||||
|
||||
markRenderFunction(tableOutput, function() {
|
||||
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
|
||||
classNames <- getOption('shiny.table.class') %OR% 'data table table-bordered table-condensed'
|
||||
data <- func()
|
||||
|
||||
if (is.null(data) || identical(data, data.frame()))
|
||||
return("")
|
||||
|
||||
return(paste(
|
||||
capture.output(
|
||||
utils::capture.output(
|
||||
print(xtable(data, ...),
|
||||
type='html',
|
||||
html.table.attributes=paste('class="',
|
||||
@@ -344,14 +220,15 @@ renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL)
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param func A function that may print output and/or return a printable R
|
||||
#' object (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @param width The value for \code{\link{options}('width')}.
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' function, instead of the printed output.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
renderPrint <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
|
||||
width = getOption('width')) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
@@ -359,11 +236,9 @@ renderPrint <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
}
|
||||
|
||||
markRenderFunction(verbatimTextOutput, function() {
|
||||
return(paste(capture.output({
|
||||
result <- withVisible(func())
|
||||
if (result$visible)
|
||||
print(result$value)
|
||||
}), collapse="\n"))
|
||||
op <- options(width = width)
|
||||
on.exit(options(op), add = TRUE)
|
||||
paste(utils::capture.output(func()), collapse = "\n")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -403,7 +278,7 @@ renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
|
||||
markRenderFunction(textOutput, function() {
|
||||
value <- func()
|
||||
return(paste(capture.output(cat(value)), collapse="\n"))
|
||||
return(paste(utils::capture.output(cat(value)), collapse="\n"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -448,7 +323,8 @@ renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
|
||||
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
|
||||
result <- surroundSingletons(result)
|
||||
dependencies <- lapply(getNewestDeps(findDependencies(result)), createWebDependency)
|
||||
dependencies <- lapply(resolveDependencies(findDependencies(result)),
|
||||
createWebDependency)
|
||||
names(dependencies) <- NULL
|
||||
|
||||
# renderTags returns a list with head, singletons, and html
|
||||
@@ -519,7 +395,9 @@ downloadHandler <- function(filename, content, contentType=NA) {
|
||||
#' \code{"AsIs"} (usually returned from \code{\link{I}()}) will be evaluated in
|
||||
#' JavaScript. This is useful when the type of the option value is not supported
|
||||
#' in JSON, e.g., a JavaScript function, which can be obtained by evaluating a
|
||||
#' character string.
|
||||
#' character string. Note this only applies to the root-level elements of the
|
||||
#' options list, and the \code{I()} notation does not work for lower-level
|
||||
#' elements in the list.
|
||||
#' @param expr An expression that returns a data frame or a matrix.
|
||||
#' @param options A list of initialization options to be passed to DataTables,
|
||||
#' or a function to return such a list.
|
||||
@@ -527,35 +405,118 @@ downloadHandler <- function(filename, content, contentType=NA) {
|
||||
#' frequent search requests).
|
||||
#' @param callback A JavaScript function to be applied to the DataTable object.
|
||||
#' This is useful for DataTables plug-ins, which often require the DataTable
|
||||
#' instance to be available (\url{http://datatables.net/extras/}).
|
||||
#' instance to be available (\url{http://datatables.net/extensions/}).
|
||||
#' @param escape Whether to escape HTML entities in the table: \code{TRUE} means
|
||||
#' to escape the whole table, and \code{FALSE} means not to escape it.
|
||||
#' Alternatively, you can specify numeric column indices or column names to
|
||||
#' indicate which columns to escape, e.g. \code{1:5} (the first 5 columns),
|
||||
#' \code{c(1, 3, 4)}, or \code{c(-1, -3)} (all columns except the first and
|
||||
#' third), or \code{c('Species', 'Sepal.Length')}.
|
||||
#' @references \url{http://datatables.net}
|
||||
#' @note This function only provides the server-side version of DataTables
|
||||
#' (using R to process the data object on the server side). There is a
|
||||
#' separate package \pkg{DT} (\url{https://github.com/rstudio/DT}) that allows
|
||||
#' you to create both server-side and client-side DataTables, and supports
|
||||
#' additional DataTables features. Consider using \code{DT::renderDataTable()}
|
||||
#' and \code{DT::dataTableOutput()} (see
|
||||
#' \url{http://rstudio.github.io/DT/shiny.html} for more information).
|
||||
#' @export
|
||||
#' @inheritParams renderPlot
|
||||
#' @examples # pass a callback function to DataTables using I()
|
||||
#' renderDataTable(iris,
|
||||
#' options = list(
|
||||
#' iDisplayLength = 5,
|
||||
#' fnInitComplete = I("function(oSettings, json) {alert('Done.');}")
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # pass a callback function to DataTables using I()
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' fluidRow(
|
||||
#' column(12,
|
||||
#' dataTableOutput('table')
|
||||
#' )
|
||||
#' )
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$table <- renderDataTable(iris,
|
||||
#' options = list(
|
||||
#' pageLength = 5,
|
||||
#' initComplete = I("function(settings, json) {alert('Done.');}")
|
||||
#' )
|
||||
#' )
|
||||
#' }
|
||||
#' )
|
||||
#' )
|
||||
#' }
|
||||
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
callback = 'function(oTable) {}',
|
||||
callback = 'function(oTable) {}', escape = TRUE,
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
markRenderFunction(dataTableOutput, function(shinysession, name, ...) {
|
||||
res <- checkAsIs(if (is.function(options)) options() else options)
|
||||
if (is.function(options)) options <- options()
|
||||
options <- checkDT9(options)
|
||||
res <- checkAsIs(options)
|
||||
data <- func()
|
||||
if (length(dim(data)) != 2) return() # expects a rectangular data object
|
||||
if (is.data.frame(data)) data <- as.data.frame(data)
|
||||
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
|
||||
colnames <- colnames(data)
|
||||
# if escape is column names, turn names to numeric indices
|
||||
if (is.character(escape)) {
|
||||
escape <- stats::setNames(seq_len(ncol(data)), colnames)[escape]
|
||||
if (any(is.na(escape)))
|
||||
stop("Some column names in the 'escape' argument not found in data")
|
||||
}
|
||||
colnames[escape] <- htmlEscape(colnames[escape])
|
||||
if (!is.logical(escape)) {
|
||||
if (!is.numeric(escape))
|
||||
stop("'escape' must be TRUE, FALSE, or a numeric vector, or column names")
|
||||
escape <- paste(escape, collapse = ',')
|
||||
}
|
||||
list(
|
||||
colnames = colnames(data), action = action, options = res$options,
|
||||
colnames = colnames, action = action, options = res$options,
|
||||
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
|
||||
callback = paste(callback, collapse = '\n')
|
||||
callback = paste(callback, collapse = '\n'), escape = escape
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# a data frame containing the DataTables 1.9 and 1.10 names
|
||||
DT10Names <- function() {
|
||||
rbind(
|
||||
utils::read.table(
|
||||
system.file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
|
||||
stringsAsFactors = FALSE
|
||||
),
|
||||
c('aoColumns', 'Removed') # looks like an omission on the upgrade guide
|
||||
)
|
||||
}
|
||||
|
||||
# check DataTables 1.9.x options, and give instructions for upgrading to 1.10.x
|
||||
checkDT9 <- function(options) {
|
||||
nms <- names(options)
|
||||
if (length(nms) == 0L) return(options)
|
||||
DT10 <- DT10Names()
|
||||
# e.g. the top level option name for oLanguage.sSearch should be oLanguage
|
||||
i <- nms %in% gsub('[.].*', '', DT10[, 1])
|
||||
if (!any(i)) return(options) # did not see old option names, ready to go!
|
||||
msg <- paste(
|
||||
'shiny (>= 0.10.2) has upgraded DataTables from 1.9.4 to 1.10.2, ',
|
||||
'and DataTables 1.10.x uses different parameter names with 1.9.x. ',
|
||||
'Please follow the upgrade guide https://datatables.net/upgrade/1.10-convert',
|
||||
' to change your DataTables parameter names:\n\n',
|
||||
paste(utils::formatUL(nms[i]), collapse = '\n'), '\n', sep = ''
|
||||
)
|
||||
j <- gsub('[.].*', '', DT10[, 1]) %in% nms
|
||||
# I cannot help you upgrade automatically in these cases, so I have to stop
|
||||
if (any(grepl('[.]', DT10[j, 1])) || any(grepl('[.]', DT10[j, 2]))) stop(msg)
|
||||
warning(msg)
|
||||
nms10 <- DT10[match(nms[i], DT10[, 1]), 2]
|
||||
if (any(nms10 == 'Removed')) stop(
|
||||
"These parameters have been removed in DataTables 1.10.x:\n\n",
|
||||
paste(utils::formatUL(nms[i][nms10 == 'Removed']), collapse = '\n'),
|
||||
"\n\n", msg
|
||||
)
|
||||
names(options)[i] <- nms10
|
||||
options
|
||||
}
|
||||
|
||||
# Deprecated functions ------------------------------------------------------
|
||||
|
||||
|
||||
57
R/showcase.R
57
R/showcase.R
@@ -31,13 +31,11 @@ licenseLink <- function(licenseName) {
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
html_dependency("jqueryui", "1.10.4", "shared/jqueryui/1.10.4",
|
||||
htmlDependency("jqueryui", "1.10.4", c(href="shared/jqueryui/1.10.4"),
|
||||
script = "jquery-ui.min.js"),
|
||||
html_dependency("showdown", "0.3.1", "shared/showdown/compressed",
|
||||
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
|
||||
script = "showdown.js"),
|
||||
html_dependency("font-awesome", "4.0.3", "shared/font-awesome",
|
||||
stylesheet = "css/font-awesome.min.css"),
|
||||
html_dependency("highlight.js", "6.2", "shared/highlight",
|
||||
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
|
||||
script = "highlight.pack.js")
|
||||
)
|
||||
|
||||
@@ -50,11 +48,11 @@ showcaseHead <- function() {
|
||||
href="shared/shiny-showcase.css"),
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readLines(mdfile, warn = FALSE), collapse="\n"))
|
||||
paste(readUTF8(mdfile), collapse="\n"))
|
||||
else ""
|
||||
))
|
||||
|
||||
return(attach_dependency(html, deps))
|
||||
return(attachDependencies(html, deps))
|
||||
}
|
||||
|
||||
# Returns tags containing the application metadata (title and author) in
|
||||
@@ -62,7 +60,7 @@ showcaseHead <- function() {
|
||||
appMetadata <- function(desc) {
|
||||
cols <- colnames(desc)
|
||||
if ("Title" %in% cols)
|
||||
with(tags, h4(class="muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
with(tags, h4(class="text-muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
if ("Author" %in% cols) small(
|
||||
br(), "by",
|
||||
if ("AuthorUrl" %in% cols)
|
||||
@@ -85,12 +83,13 @@ showcaseCodeTabs <- function(codeLicense) {
|
||||
rFiles <- list.files(pattern = "\\.[rR]$")
|
||||
with(tags, div(id="showcase-code-tabs",
|
||||
a(id="showcase-code-position-toggle",
|
||||
class="btn btn-default btn-small",
|
||||
class="btn btn-default btn-sm",
|
||||
onclick="toggleCodePosition()",
|
||||
i(class="fa fa-level-up", "show with app")),
|
||||
icon("level-up"),
|
||||
"show with app"),
|
||||
ul(class="nav nav-tabs",
|
||||
lapply(rFiles, function(rFile) {
|
||||
li(class=if (tolower(rFile) == "server.r") "active" else "",
|
||||
li(class=if (tolower(rFile) %in% c("app.r", "server.r")) "active" else "",
|
||||
a(href=paste("#", gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
"data-toggle"="tab", rFile))
|
||||
@@ -98,7 +97,8 @@ showcaseCodeTabs <- function(codeLicense) {
|
||||
div(class="tab-content", id="showcase-code-content",
|
||||
lapply(rFiles, function(rFile) {
|
||||
div(class=paste("tab-pane",
|
||||
if (tolower(rFile) == "server.r") " active" else "",
|
||||
if (tolower(rFile) %in% c("app.r", "server.r")) " active"
|
||||
else "",
|
||||
sep=""),
|
||||
id=paste(gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
@@ -106,8 +106,7 @@ showcaseCodeTabs <- function(codeLicense) {
|
||||
# we need to prevent the indentation of <code> ... </code>
|
||||
HTML(format(tags$code(
|
||||
class="language-r",
|
||||
paste(readLines(file.path.ci(getwd(), rFile), warn=FALSE),
|
||||
collapse="\n")
|
||||
paste(readUTF8(file.path.ci(getwd(), rFile)), collapse="\n")
|
||||
), indent = FALSE))))
|
||||
})),
|
||||
codeLicense))
|
||||
@@ -121,22 +120,24 @@ showcaseAppInfo <- function() {
|
||||
readmemd <- file.path.ci(getwd(), "Readme.md")
|
||||
hasReadme <- file.exists(readmemd)
|
||||
if (hasDesc) {
|
||||
desc <- read.dcf(descfile)
|
||||
con <- textConnection(readUTF8(descfile))
|
||||
on.exit(close(con), add = TRUE)
|
||||
desc <- read.dcf(con)
|
||||
}
|
||||
with(tags,
|
||||
div(class="container-fluid shiny-code-container well",
|
||||
id="showcase-well",
|
||||
div(class="row-fluid",
|
||||
div(class="row",
|
||||
if (hasDesc || hasReadme) {
|
||||
div(id="showcase-app-metadata", class="span4",
|
||||
div(id="showcase-app-metadata", class="col-sm-4",
|
||||
if (hasDesc) appMetadata(desc) else "",
|
||||
if (hasReadme) div(id="readme-md"))
|
||||
} else "",
|
||||
div(id="showcase-code-inline",
|
||||
class=if (hasReadme || hasDesc) "span8" else "span10 offset1",
|
||||
class=if (hasReadme || hasDesc) "col-sm-8" else "col-sm-10 col-sm-offset-1",
|
||||
showcaseCodeTabs(
|
||||
if (hasDesc && "License" %in% colnames(desc)) {
|
||||
small(class="showcase-code-license muted",
|
||||
small(class="showcase-code-license text-muted",
|
||||
"Code license: ",
|
||||
licenseLink(desc[1,"License"]))
|
||||
} else "")))))
|
||||
@@ -149,7 +150,7 @@ showcaseBody <- function(htmlBody) {
|
||||
table(id="showcase-app-code",
|
||||
tr(td(id="showcase-app-container",
|
||||
class="showcase-app-container-expanded",
|
||||
HTML(htmlBody),
|
||||
htmlBody,
|
||||
td(id="showcase-sxs-code",
|
||||
class="showcase-sxs-code-collapsed")))),
|
||||
showcaseAppInfo()))
|
||||
@@ -160,3 +161,19 @@ setShowcaseDefault <- function(showcaseDefault) {
|
||||
.globals$showcaseDefault <- showcaseDefault
|
||||
.globals$showcaseOverride <- as.logical(showcaseDefault)
|
||||
}
|
||||
|
||||
|
||||
# Given a UI tag/tagList, wrap it in appropriate tags for showcase mode.
|
||||
showcaseUI <- function(ui) {
|
||||
# If top-level tag is a body, replace its children with children wrapped in
|
||||
# showcase stuff.
|
||||
if (inherits(ui, "shiny.tag") && ui$name == "body") {
|
||||
ui$children <- showcaseUI(ui$children)
|
||||
return(ui)
|
||||
}
|
||||
|
||||
tagList(
|
||||
tags$head(showcaseHead()),
|
||||
showcaseBody(ui)
|
||||
)
|
||||
}
|
||||
|
||||
142
R/slider.R
142
R/slider.R
@@ -1,142 +0,0 @@
|
||||
hasDecimals <- function(value) {
|
||||
truncatedValue <- round(value)
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
#' @param interval The interval, in milliseconds, between each animation step.
|
||||
#' @param loop \code{TRUE} to automatically restart the animation when it
|
||||
#' reaches the end.
|
||||
#' @param playButton Specifies the appearance of the play button. Valid values
|
||||
#' are a one-element character vector (for a simple text label), an HTML tag
|
||||
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
|
||||
#' \code{\link{HTML}}).
|
||||
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
|
||||
#'
|
||||
#' @export
|
||||
animationOptions <- function(interval=1000,
|
||||
loop=FALSE,
|
||||
playButton=NULL,
|
||||
pauseButton=NULL) {
|
||||
list(interval=interval,
|
||||
loop=loop,
|
||||
playButton=playButton,
|
||||
pauseButton=pauseButton)
|
||||
}
|
||||
|
||||
# Create a new slider control (list of slider input element and the script
|
||||
# tag used to configure it). This is a lower level control that should
|
||||
# be wrapped in an "input" construct (e.g. sliderInput in bootstrap.R)
|
||||
#
|
||||
# this is a wrapper for: https://github.com/egorkhmelev/jslider
|
||||
# (www/shared/slider contains js, css, and img dependencies)
|
||||
slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
round=FALSE, format='#,##0.#####', locale='us',
|
||||
ticks=TRUE, animate=FALSE) {
|
||||
# validate inputId
|
||||
inputId <- as.character(inputId)
|
||||
if (!is.character(inputId))
|
||||
stop("inputId not specified")
|
||||
|
||||
# validate numeric inputs
|
||||
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
|
||||
stop("min, max, and value must all be numeric values")
|
||||
else if (min(value) < min)
|
||||
stop(paste("slider initial value", value,
|
||||
"is less than the specified minimum"))
|
||||
else if (max(value) > max)
|
||||
stop(paste("slider initial value", value,
|
||||
"is greater than the specified maximum"))
|
||||
else if (min > max)
|
||||
stop(paste("slider maximum is greater than minimum"))
|
||||
else if (!is.null(step)) {
|
||||
if (!is.numeric(step))
|
||||
stop("step is not a numeric value")
|
||||
if (step > (max - min))
|
||||
stop("step is greater than range")
|
||||
}
|
||||
|
||||
# step
|
||||
range <- max - min
|
||||
if (is.null(step)) {
|
||||
# short range or decimals means continuous decimal
|
||||
if (range < 2 || hasDecimals(min) || hasDecimals(max))
|
||||
step <- range / 250 # ~ one step per pixel
|
||||
else
|
||||
step = 1
|
||||
}
|
||||
|
||||
# Default state is to not have ticks
|
||||
if (identical(ticks, TRUE)) {
|
||||
# Automatic ticks
|
||||
tickCount <- (range / step) + 1
|
||||
if (tickCount <= 26)
|
||||
ticks <- paste(rep('|', floor(tickCount)), collapse=';')
|
||||
else {
|
||||
ticks <- NULL
|
||||
# # This is a smarter auto-tick algorithm, but to be truly useful
|
||||
# # we need jslider to be able to space ticks irregularly
|
||||
# tickSize <- 10^(floor(log10(range/0.39)))
|
||||
# if ((range / tickSize) == floor(range / tickSize)) {
|
||||
# ticks <- paste(rep('|', (range / tickSize) + 1), collapse=';')
|
||||
# }
|
||||
# else {
|
||||
# ticks <- NULL
|
||||
# }
|
||||
}
|
||||
}
|
||||
else if (is.numeric(ticks) && length(ticks) == 1) {
|
||||
# Use n ticks
|
||||
ticks <- paste(rep('|', ticks), collapse=';')
|
||||
}
|
||||
else if (length(ticks) > 1 && (is.numeric(ticks) || is.character(ticks))) {
|
||||
# Explicit ticks
|
||||
ticks <- paste(ticks, collapse=';')
|
||||
}
|
||||
else {
|
||||
ticks <- NULL
|
||||
}
|
||||
|
||||
# build slider
|
||||
dep <- html_dependency(name = "jslider", version = "1",
|
||||
path = "shared/slider",
|
||||
script = "js/jquery.slider.min.js",
|
||||
stylesheet = "css/jquery.slider.min.css"
|
||||
)
|
||||
sliderFragment <- list(
|
||||
attach_dependency(
|
||||
tags$input(
|
||||
id=inputId, type="slider",
|
||||
name=inputId, value=paste(value, collapse=';'), class="jslider",
|
||||
'data-from'=min, 'data-to'=max, 'data-step'=step,
|
||||
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
|
||||
'data-format'=format, 'data-scale'=ticks,
|
||||
'data-smooth'=FALSE
|
||||
),
|
||||
dep
|
||||
)
|
||||
)
|
||||
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
if (!is.null(animate) && !identical(animate, FALSE)) {
|
||||
if (is.null(animate$playButton))
|
||||
animate$playButton <- 'Play'
|
||||
if (is.null(animate$pauseButton))
|
||||
animate$pauseButton <- 'Pause'
|
||||
|
||||
sliderFragment[[length(sliderFragment)+1]] <-
|
||||
tags$div(class='slider-animate-container',
|
||||
tags$a(href='#',
|
||||
class='slider-animate-button',
|
||||
'data-target-id'=inputId,
|
||||
'data-interval'=animate$interval,
|
||||
'data-loop'=animate$loop,
|
||||
tags$span(class='play', animate$playButton),
|
||||
tags$span(class='pause', animate$pauseButton)))
|
||||
}
|
||||
|
||||
return(tagList(sliderFragment))
|
||||
}
|
||||
70
R/stack.R
Normal file
70
R/stack.R
Normal file
@@ -0,0 +1,70 @@
|
||||
# A Stack object backed by a list. The backing list will grow or shrink as
|
||||
# the stack changes in size.
|
||||
Stack <- R6Class(
|
||||
'Stack',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
|
||||
initialize = function(init = 20L) {
|
||||
# init is the initial size of the list. It is also used as the minimum
|
||||
# size of the list as it shrinks.
|
||||
private$stack <- vector("list", init)
|
||||
private$init <- init
|
||||
},
|
||||
|
||||
push = function(..., .list = NULL) {
|
||||
args <- c(list(...), .list)
|
||||
new_size <- count + length(args)
|
||||
|
||||
# Grow if needed; double in size
|
||||
while (new_size > length(stack)) {
|
||||
stack[length(stack) * 2] <<- list(NULL)
|
||||
}
|
||||
stack[count + seq_along(args)] <<- args
|
||||
count <<- new_size
|
||||
|
||||
invisible(self)
|
||||
},
|
||||
|
||||
pop = function() {
|
||||
if (count == 0L)
|
||||
return(NULL)
|
||||
|
||||
value <- stack[[count]]
|
||||
stack[count] <<- list(NULL)
|
||||
count <<- count - 1L
|
||||
|
||||
# Shrink list if < 1/4 of the list is used, down to a minimum size of `init`
|
||||
len <- length(stack)
|
||||
if (len > init && count < len/4) {
|
||||
new_len <- max(init, ceiling(len/2))
|
||||
stack <<- stack[seq_len(new_len)]
|
||||
}
|
||||
|
||||
value
|
||||
},
|
||||
|
||||
peek = function() {
|
||||
if (count == 0L)
|
||||
return(NULL)
|
||||
stack[[count]]
|
||||
},
|
||||
|
||||
size = function() {
|
||||
count
|
||||
},
|
||||
|
||||
# Return the entire stack as a list, where the first item in the list is the
|
||||
# oldest item in the stack, and the last item is the most recently added.
|
||||
as_list = function() {
|
||||
stack[seq_len(count)]
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
stack = NULL, # A list that holds the items
|
||||
count = 0L, # Current number of items in the stack
|
||||
init = 20L # Initial and minimum size of the stack
|
||||
)
|
||||
)
|
||||
637
R/tags.R
637
R/tags.R
@@ -1,637 +0,0 @@
|
||||
|
||||
|
||||
htmlEscape <- local({
|
||||
.htmlSpecials <- list(
|
||||
`&` = '&',
|
||||
`<` = '<',
|
||||
`>` = '>'
|
||||
)
|
||||
.htmlSpecialsPattern <- paste(names(.htmlSpecials), collapse='|')
|
||||
.htmlSpecialsAttrib <- c(
|
||||
.htmlSpecials,
|
||||
`'` = ''',
|
||||
`"` = '"',
|
||||
`\r` = ' ',
|
||||
`\n` = ' '
|
||||
)
|
||||
.htmlSpecialsPatternAttrib <- paste(names(.htmlSpecialsAttrib), collapse='|')
|
||||
|
||||
function(text, attribute=TRUE) {
|
||||
pattern <- if(attribute)
|
||||
.htmlSpecialsPatternAttrib
|
||||
else
|
||||
.htmlSpecialsPattern
|
||||
|
||||
# Short circuit in the common case that there's nothing to escape
|
||||
if (!any(grepl(pattern, text)))
|
||||
return(text)
|
||||
|
||||
specials <- if(attribute)
|
||||
.htmlSpecialsAttrib
|
||||
else
|
||||
.htmlSpecials
|
||||
|
||||
for (chr in names(specials)) {
|
||||
text <- gsub(chr, specials[[chr]], text, fixed=TRUE)
|
||||
}
|
||||
|
||||
return(text)
|
||||
}
|
||||
})
|
||||
|
||||
isTag <- function(x) {
|
||||
inherits(x, "shiny.tag")
|
||||
}
|
||||
|
||||
#' @export
|
||||
print.shiny.tag <- function(x, ...) {
|
||||
print(as.character(x), ...)
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
# indent can be numeric to indicate an initial indent level,
|
||||
# or FALSE to suppress
|
||||
#' @export
|
||||
format.shiny.tag <- function(x, ..., singletons = character(0), indent = 0) {
|
||||
as.character(renderTags(x, singletons = singletons, indent = indent)$html)
|
||||
}
|
||||
|
||||
#' @export
|
||||
as.character.shiny.tag <- function(x, ...) {
|
||||
renderTags(x)$html
|
||||
}
|
||||
|
||||
#' @export
|
||||
print.shiny.tag.list <- print.shiny.tag
|
||||
|
||||
#' @export
|
||||
format.shiny.tag.list <- format.shiny.tag
|
||||
|
||||
#' @export
|
||||
as.character.shiny.tag.list <- as.character.shiny.tag
|
||||
|
||||
#' @export
|
||||
print.html <- function(x, ...) {
|
||||
cat(x, "\n")
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
#' @export
|
||||
format.html <- function(x, ...) {
|
||||
as.character(x)
|
||||
}
|
||||
|
||||
normalizeText <- function(text) {
|
||||
if (!is.null(attr(text, "html")))
|
||||
text
|
||||
else
|
||||
htmlEscape(text, attribute=FALSE)
|
||||
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagList <- function(...) {
|
||||
lst <- list(...)
|
||||
class(lst) <- c("shiny.tag.list", "list")
|
||||
return(lst)
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagAppendAttributes <- function(tag, ...) {
|
||||
tag$attribs <- c(tag$attribs, list(...))
|
||||
tag
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagAppendChild <- function(tag, child) {
|
||||
tag$children[[length(tag$children)+1]] <- child
|
||||
tag
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagAppendChildren <- function(tag, ..., list = NULL) {
|
||||
tag$children <- c(tag$children, c(list(...), list))
|
||||
tag
|
||||
}
|
||||
|
||||
#' @rdname tag
|
||||
#' @export
|
||||
tagSetChildren <- function(tag, ..., list = NULL) {
|
||||
tag$children <- c(list(...), list)
|
||||
tag
|
||||
}
|
||||
|
||||
#' HTML Tag Object
|
||||
#'
|
||||
#' \code{tag()} creates an HTML tag definition. Note that all of the valid HTML5
|
||||
#' tags are already defined in the \code{\link{tags}} environment so these
|
||||
#' functions should only be used to generate additional tags.
|
||||
#' \code{tagAppendChild()} and \code{tagList()} are for supporting package
|
||||
#' authors who wish to create their own sets of tags; see the contents of
|
||||
#' bootstrap.R for examples.
|
||||
#' @param _tag_name HTML tag name
|
||||
#' @param varArgs List of attributes and children of the element. Named list
|
||||
#' items become attributes, and unnamed list items become children. Valid
|
||||
#' children are tags, single-character character vectors (which become text
|
||||
#' nodes), and raw HTML (see \code{\link{HTML}}). You can also pass lists that
|
||||
#' contain tags, text nodes, and HTML.
|
||||
#' @param tag A tag to append child elements to.
|
||||
#' @param child A child element to append to a parent tag.
|
||||
#' @param ... Unnamed items that comprise this list of tags.
|
||||
#' @param list An optional list of elements. Can be used with or instead of the
|
||||
#' \code{...} items.
|
||||
#' @return An HTML tag object that can be rendered as HTML using
|
||||
#' \code{\link{as.character}()}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' tagList(tags$h1("Title"),
|
||||
#' tags$h2("Header text"),
|
||||
#' tags$p("Text here"))
|
||||
#'
|
||||
#' # Can also convert a regular list to a tagList (internal data structure isn't
|
||||
#' # exactly the same, but when rendered to HTML, the output is the same).
|
||||
#' x <- list(tags$h1("Title"),
|
||||
#' tags$h2("Header text"),
|
||||
#' tags$p("Text here"))
|
||||
#' tagList(x)
|
||||
tag <- function(`_tag_name`, varArgs) {
|
||||
# Get arg names; if not a named list, use vector of empty strings
|
||||
varArgsNames <- names(varArgs)
|
||||
if (is.null(varArgsNames))
|
||||
varArgsNames <- character(length=length(varArgs))
|
||||
|
||||
# Named arguments become attribs, dropping NULL values
|
||||
named_idx <- nzchar(varArgsNames)
|
||||
attribs <- dropNulls(varArgs[named_idx])
|
||||
|
||||
# Unnamed arguments are flattened and added as children.
|
||||
# Use unname() to remove the names attribute from the list, which would
|
||||
# consist of empty strings anyway.
|
||||
children <- unname(varArgs[!named_idx])
|
||||
|
||||
# Return tag data structure
|
||||
structure(
|
||||
list(name = `_tag_name`,
|
||||
attribs = attribs,
|
||||
children = children),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
}
|
||||
|
||||
tagWrite <- function(tag, textWriter, indent=0, eol = "\n") {
|
||||
|
||||
if (length(tag) == 0)
|
||||
return (NULL)
|
||||
|
||||
# optionally process a list of tags
|
||||
if (!isTag(tag) && is.list(tag)) {
|
||||
tag <- dropNullsOrEmpty(flattenTags(tag))
|
||||
lapply(tag, tagWrite, textWriter, indent)
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
nextIndent <- if (is.numeric(indent)) indent + 1 else indent
|
||||
indent <- if (is.numeric(indent)) indent else 0
|
||||
|
||||
# compute indent text
|
||||
indentText <- paste(rep(" ", indent*2), collapse="")
|
||||
|
||||
# Check if it's just text (may either be plain-text or HTML)
|
||||
if (is.character(tag)) {
|
||||
textWriter(paste(indentText, normalizeText(tag), eol, sep=""))
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
# write tag name
|
||||
textWriter(paste(indentText, "<", tag$name, sep=""))
|
||||
|
||||
# Convert all attribs to chars explicitly; prevents us from messing up factors
|
||||
attribs <- lapply(tag$attribs, as.character)
|
||||
# concatenate attributes
|
||||
# split() is very slow, so avoid it if possible
|
||||
if (anyDuplicated(names(attribs)))
|
||||
attribs <- lapply(split(attribs, names(attribs)), paste, collapse = " ")
|
||||
|
||||
# write attributes
|
||||
for (attrib in names(attribs)) {
|
||||
attribValue <- attribs[[attrib]]
|
||||
if (!is.na(attribValue)) {
|
||||
if (is.logical(attribValue))
|
||||
attribValue <- tolower(attribValue)
|
||||
text <- htmlEscape(attribValue, attribute=TRUE)
|
||||
textWriter(paste(" ", attrib,"=\"", text, "\"", sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste(" ", attrib, sep=""))
|
||||
}
|
||||
}
|
||||
|
||||
# write any children
|
||||
children <- dropNullsOrEmpty(flattenTags(tag$children))
|
||||
if (length(children) > 0) {
|
||||
textWriter(">")
|
||||
|
||||
# special case for a single child text node (skip newlines and indentation)
|
||||
if ((length(children) == 1) && is.character(children[[1]]) ) {
|
||||
textWriter(paste(normalizeText(children[[1]]), "</", tag$name, ">", eol,
|
||||
sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter("\n")
|
||||
for (child in children)
|
||||
tagWrite(child, textWriter, nextIndent)
|
||||
textWriter(paste(indentText, "</", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
# only self-close void elements
|
||||
# (see: http://dev.w3.org/html5/spec/single-page.html#void-elements)
|
||||
if (tag$name %in% c("area", "base", "br", "col", "command", "embed", "hr",
|
||||
"img", "input", "keygen", "link", "meta", "param",
|
||||
"source", "track", "wbr")) {
|
||||
textWriter(paste("/>", eol, sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste("></", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doRenderTags <- function(ui, indent = 0) {
|
||||
# Render the body--the bodyHtml variable will be created
|
||||
conn <- file(open="w+")
|
||||
connWriter <- function(text) writeChar(text, conn, eos = NULL)
|
||||
htmlResult <- tryCatch({
|
||||
tagWrite(ui, connWriter, indent)
|
||||
flush(conn)
|
||||
readLines(conn)
|
||||
},
|
||||
finally = close(conn)
|
||||
)
|
||||
return(HTML(paste(htmlResult, collapse = "\n")))
|
||||
}
|
||||
|
||||
renderTags <- function(ui, singletons = character(0), indent = 0) {
|
||||
# Do singleton and head processing before rendering
|
||||
singletonInfo <- takeSingletons(ui, singletons)
|
||||
headInfo <- takeHeads(singletonInfo$ui)
|
||||
deps <- getNewestDeps(findDependencies(singletonInfo$ui))
|
||||
|
||||
headIndent <- if (is.numeric(indent)) indent + 1 else indent
|
||||
headHtml <- doRenderTags(headInfo$head, indent = headIndent)
|
||||
bodyHtml <- doRenderTags(headInfo$ui, indent = indent)
|
||||
|
||||
return(list(head = headHtml,
|
||||
singletons = singletonInfo$singletons,
|
||||
dependencies = deps,
|
||||
html = bodyHtml))
|
||||
}
|
||||
|
||||
# Walk a tree of tag objects, rewriting objects according to func.
|
||||
# preorder=TRUE means preorder tree traversal, that is, an object
|
||||
# should be rewritten before its children.
|
||||
rewriteTags <- function(ui, func, preorder) {
|
||||
if (preorder)
|
||||
ui <- func(ui)
|
||||
|
||||
if (isTag(ui)) {
|
||||
ui$children[] <- lapply(ui$children, rewriteTags, func, preorder)
|
||||
} else if (is.list(ui)) {
|
||||
ui[] <- lapply(ui, rewriteTags, func, preorder)
|
||||
}
|
||||
|
||||
if (!preorder)
|
||||
ui <- func(ui)
|
||||
|
||||
return(ui)
|
||||
}
|
||||
|
||||
# Preprocess a tag object by changing any singleton X into
|
||||
# <!--SHINY.SINGLETON[sig]-->X'<!--/SHINY.SINGLETON[sig]-->
|
||||
# where sig is the sha1 of X, and X' is X minus the singleton
|
||||
# attribute.
|
||||
#
|
||||
# In the case of nested singletons, outer singletons are processed
|
||||
# before inner singletons (otherwise the processing of inner
|
||||
# singletons would cause the sha1 of the outer singletons to be
|
||||
# different).
|
||||
surroundSingletons <- local({
|
||||
surroundSingleton <- function(uiObj) {
|
||||
if (inherits(uiObj, "shiny.singleton")) {
|
||||
sig <- digest(uiObj, "sha1")
|
||||
class(uiObj) <- class(uiObj)[class(uiObj) != "shiny.singleton"]
|
||||
return(tagList(
|
||||
HTML(sprintf("<!--SHINY.SINGLETON[%s]-->", sig)),
|
||||
uiObj,
|
||||
HTML(sprintf("<!--/SHINY.SINGLETON[%s]-->", sig))
|
||||
))
|
||||
} else {
|
||||
uiObj
|
||||
}
|
||||
}
|
||||
|
||||
function(ui) {
|
||||
rewriteTags(ui, surroundSingleton, TRUE)
|
||||
}
|
||||
})
|
||||
|
||||
# Given a tag object, apply singleton logic (allow singleton objects
|
||||
# to appear no more than once per signature) and return the processed
|
||||
# HTML objects and also the list of known singletons.
|
||||
takeSingletons <- function(ui, singletons=character(0), desingleton=TRUE) {
|
||||
result <- rewriteTags(ui, function(uiObj) {
|
||||
if (inherits(uiObj, "shiny.singleton")) {
|
||||
sig <- digest(uiObj, "sha1")
|
||||
if (sig %in% singletons)
|
||||
return(NULL)
|
||||
singletons <<- append(singletons, sig)
|
||||
if (desingleton)
|
||||
class(uiObj) <- class(uiObj)[class(uiObj) != "shiny.singleton"]
|
||||
return(uiObj)
|
||||
} else {
|
||||
return(uiObj)
|
||||
}
|
||||
}, TRUE)
|
||||
|
||||
return(list(ui=result, singletons=singletons))
|
||||
}
|
||||
|
||||
# Given a tag object, extract out any children of tags$head
|
||||
# and return them separate from the body.
|
||||
takeHeads <- function(ui) {
|
||||
headItems <- list()
|
||||
result <- rewriteTags(ui, function(uiObj) {
|
||||
if (isTag(uiObj) && tolower(uiObj$name) == "head") {
|
||||
headItems <<- append(headItems, uiObj$children)
|
||||
return(NULL)
|
||||
}
|
||||
return(uiObj)
|
||||
}, FALSE)
|
||||
|
||||
return(list(ui=result, head=headItems))
|
||||
}
|
||||
|
||||
findDependencies <- function(ui) {
|
||||
dep <- attr(ui, "html_dependency")
|
||||
if (!is.null(dep) && inherits(dep, "html_dependency"))
|
||||
dep <- list(dep)
|
||||
children <- if (is.list(ui)) {
|
||||
if (isTag(ui)) {
|
||||
ui$children
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
}
|
||||
childDeps <- unlist(lapply(children, findDependencies), recursive = FALSE)
|
||||
c(childDeps, if (!is.null(dep)) dep)
|
||||
}
|
||||
|
||||
#' HTML Builder Functions
|
||||
#'
|
||||
#' Simple functions for constructing HTML documents.
|
||||
#'
|
||||
#' The \code{tags} environment contains convenience functions for all valid
|
||||
#' HTML5 tags. To generate tags that are not part of the HTML5 specification,
|
||||
#' you can use the \code{\link{tag}()} function.
|
||||
#'
|
||||
#' Dedicated functions are available for the most common HTML tags that do not
|
||||
#' conflict with common R functions.
|
||||
#'
|
||||
#' The result from these functions is a tag object, which can be converted using
|
||||
#' \code{\link{as.character}()}.
|
||||
#'
|
||||
#' @name builder
|
||||
#' @param ... Attributes and children of the element. Named arguments become
|
||||
#' attributes, and positional arguments become children. Valid children are
|
||||
#' tags, single-character character vectors (which become text nodes), and raw
|
||||
#' HTML (see \code{\link{HTML}}). You can also pass lists that contain tags,
|
||||
#' text nodes, and HTML.
|
||||
#' @export tags
|
||||
#' @examples
|
||||
#' doc <- tags$html(
|
||||
#' tags$head(
|
||||
#' tags$title('My first page')
|
||||
#' ),
|
||||
#' tags$body(
|
||||
#' h1('My first heading'),
|
||||
#' p('My first paragraph, with some ',
|
||||
#' strong('bold'),
|
||||
#' ' text.'),
|
||||
#' div(id='myDiv', class='simpleDiv',
|
||||
#' 'Here is a div with some attributes.')
|
||||
#' )
|
||||
#' )
|
||||
#' cat(as.character(doc))
|
||||
NULL
|
||||
|
||||
#' @rdname builder
|
||||
#' @format NULL
|
||||
#' @docType NULL
|
||||
#' @keywords NULL
|
||||
tags <- list(
|
||||
a = function(...) tag("a", list(...)),
|
||||
abbr = function(...) tag("abbr", list(...)),
|
||||
address = function(...) tag("address", list(...)),
|
||||
area = function(...) tag("area", list(...)),
|
||||
article = function(...) tag("article", list(...)),
|
||||
aside = function(...) tag("aside", list(...)),
|
||||
audio = function(...) tag("audio", list(...)),
|
||||
b = function(...) tag("b", list(...)),
|
||||
base = function(...) tag("base", list(...)),
|
||||
bdi = function(...) tag("bdi", list(...)),
|
||||
bdo = function(...) tag("bdo", list(...)),
|
||||
blockquote = function(...) tag("blockquote", list(...)),
|
||||
body = function(...) tag("body", list(...)),
|
||||
br = function(...) tag("br", list(...)),
|
||||
button = function(...) tag("button", list(...)),
|
||||
canvas = function(...) tag("canvas", list(...)),
|
||||
caption = function(...) tag("caption", list(...)),
|
||||
cite = function(...) tag("cite", list(...)),
|
||||
code = function(...) tag("code", list(...)),
|
||||
col = function(...) tag("col", list(...)),
|
||||
colgroup = function(...) tag("colgroup", list(...)),
|
||||
command = function(...) tag("command", list(...)),
|
||||
data = function(...) tag("data", list(...)),
|
||||
datalist = function(...) tag("datalist", list(...)),
|
||||
dd = function(...) tag("dd", list(...)),
|
||||
del = function(...) tag("del", list(...)),
|
||||
details = function(...) tag("details", list(...)),
|
||||
dfn = function(...) tag("dfn", list(...)),
|
||||
div = function(...) tag("div", list(...)),
|
||||
dl = function(...) tag("dl", list(...)),
|
||||
dt = function(...) tag("dt", list(...)),
|
||||
em = function(...) tag("em", list(...)),
|
||||
embed = function(...) tag("embed", list(...)),
|
||||
eventsource = function(...) tag("eventsource", list(...)),
|
||||
fieldset = function(...) tag("fieldset", list(...)),
|
||||
figcaption = function(...) tag("figcaption", list(...)),
|
||||
figure = function(...) tag("figure", list(...)),
|
||||
footer = function(...) tag("footer", list(...)),
|
||||
form = function(...) tag("form", list(...)),
|
||||
h1 = function(...) tag("h1", list(...)),
|
||||
h2 = function(...) tag("h2", list(...)),
|
||||
h3 = function(...) tag("h3", list(...)),
|
||||
h4 = function(...) tag("h4", list(...)),
|
||||
h5 = function(...) tag("h5", list(...)),
|
||||
h6 = function(...) tag("h6", list(...)),
|
||||
head = function(...) tag("head", list(...)),
|
||||
header = function(...) tag("header", list(...)),
|
||||
hgroup = function(...) tag("hgroup", list(...)),
|
||||
hr = function(...) tag("hr", list(...)),
|
||||
html = function(...) tag("html", list(...)),
|
||||
i = function(...) tag("i", list(...)),
|
||||
iframe = function(...) tag("iframe", list(...)),
|
||||
img = function(...) tag("img", list(...)),
|
||||
input = function(...) tag("input", list(...)),
|
||||
ins = function(...) tag("ins", list(...)),
|
||||
kbd = function(...) tag("kbd", list(...)),
|
||||
keygen = function(...) tag("keygen", list(...)),
|
||||
label = function(...) tag("label", list(...)),
|
||||
legend = function(...) tag("legend", list(...)),
|
||||
li = function(...) tag("li", list(...)),
|
||||
link = function(...) tag("link", list(...)),
|
||||
mark = function(...) tag("mark", list(...)),
|
||||
map = function(...) tag("map", list(...)),
|
||||
menu = function(...) tag("menu", list(...)),
|
||||
meta = function(...) tag("meta", list(...)),
|
||||
meter = function(...) tag("meter", list(...)),
|
||||
nav = function(...) tag("nav", list(...)),
|
||||
noscript = function(...) tag("noscript", list(...)),
|
||||
object = function(...) tag("object", list(...)),
|
||||
ol = function(...) tag("ol", list(...)),
|
||||
optgroup = function(...) tag("optgroup", list(...)),
|
||||
option = function(...) tag("option", list(...)),
|
||||
output = function(...) tag("output", list(...)),
|
||||
p = function(...) tag("p", list(...)),
|
||||
param = function(...) tag("param", list(...)),
|
||||
pre = function(...) tag("pre", list(...)),
|
||||
progress = function(...) tag("progress", list(...)),
|
||||
q = function(...) tag("q", list(...)),
|
||||
ruby = function(...) tag("ruby", list(...)),
|
||||
rp = function(...) tag("rp", list(...)),
|
||||
rt = function(...) tag("rt", list(...)),
|
||||
s = function(...) tag("s", list(...)),
|
||||
samp = function(...) tag("samp", list(...)),
|
||||
script = function(...) tag("script", list(...)),
|
||||
section = function(...) tag("section", list(...)),
|
||||
select = function(...) tag("select", list(...)),
|
||||
small = function(...) tag("small", list(...)),
|
||||
source = function(...) tag("source", list(...)),
|
||||
span = function(...) tag("span", list(...)),
|
||||
strong = function(...) tag("strong", list(...)),
|
||||
style = function(...) tag("style", list(...)),
|
||||
sub = function(...) tag("sub", list(...)),
|
||||
summary = function(...) tag("summary", list(...)),
|
||||
sup = function(...) tag("sup", list(...)),
|
||||
table = function(...) tag("table", list(...)),
|
||||
tbody = function(...) tag("tbody", list(...)),
|
||||
td = function(...) tag("td", list(...)),
|
||||
textarea = function(...) tag("textarea", list(...)),
|
||||
tfoot = function(...) tag("tfoot", list(...)),
|
||||
th = function(...) tag("th", list(...)),
|
||||
thead = function(...) tag("thead", list(...)),
|
||||
time = function(...) tag("time", list(...)),
|
||||
title = function(...) tag("title", list(...)),
|
||||
tr = function(...) tag("tr", list(...)),
|
||||
track = function(...) tag("track", list(...)),
|
||||
u = function(...) tag("u", list(...)),
|
||||
ul = function(...) tag("ul", list(...)),
|
||||
var = function(...) tag("var", list(...)),
|
||||
video = function(...) tag("video", list(...)),
|
||||
wbr = function(...) tag("wbr", list(...))
|
||||
)
|
||||
|
||||
#' Mark Characters as HTML
|
||||
#'
|
||||
#' Marks the given text as HTML, which means the \link{tag} functions will know
|
||||
#' not to perform HTML escaping on it.
|
||||
#'
|
||||
#' @param text The text value to mark with HTML
|
||||
#' @param ... Any additional values to be converted to character and
|
||||
#' concatenated together
|
||||
#' @return The same value, but marked as HTML.
|
||||
#'
|
||||
#' @examples
|
||||
#' el <- div(HTML("I like <u>turtles</u>"))
|
||||
#' cat(as.character(el))
|
||||
#'
|
||||
#' @export
|
||||
HTML <- function(text, ...) {
|
||||
htmlText <- c(text, as.character(list(...)))
|
||||
htmlText <- paste(htmlText, collapse=" ")
|
||||
attr(htmlText, "html") <- TRUE
|
||||
class(htmlText) <- c("html", "character")
|
||||
htmlText
|
||||
}
|
||||
|
||||
#' Evaluate an expression using the \code{tags}
|
||||
#'
|
||||
#' This function makes it simpler to write HTML-generating code. Instead of
|
||||
#' needing to specify \code{tags} each time a tag function is used, as in
|
||||
#' \code{tags$div()} and \code{tags$p()}, code inside \code{withTags} is
|
||||
#' evaluated with \code{tags} searched first, so you can simply use
|
||||
#' \code{div()} and \code{p()}.
|
||||
#'
|
||||
#' If your code uses an object which happens to have the same name as an
|
||||
#' HTML tag function, such as \code{source()} or \code{summary()}, it will call
|
||||
#' the tag function. To call the intended (non-tags function), specify the
|
||||
#' namespace, as in \code{base::source()} or \code{base::summary()}.
|
||||
#'
|
||||
#' @param code A set of tags.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Using tags$ each time
|
||||
#' tags$div(class = "myclass",
|
||||
#' tags$h3("header"),
|
||||
#' tags$p("text")
|
||||
#' )
|
||||
#'
|
||||
#' # Equivalent to above, but using withTags
|
||||
#' withTags(
|
||||
#' div(class = "myclass",
|
||||
#' h3("header"),
|
||||
#' p("text")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#'
|
||||
#' @export
|
||||
withTags <- function(code) {
|
||||
eval(substitute(code), envir = as.list(tags), enclos = parent.frame())
|
||||
}
|
||||
|
||||
|
||||
# Given a list of tags, lists, and other items, return a flat list, where the
|
||||
# items from the inner, nested lists are pulled to the top level, recursively.
|
||||
flattenTags <- function(x) {
|
||||
if (isTag(x)) {
|
||||
# For tags, wrap them into a list (which will be unwrapped by caller)
|
||||
list(x)
|
||||
} else if (is.list(x)) {
|
||||
if (length(x) == 0) {
|
||||
# Empty lists are simply returned
|
||||
x
|
||||
} else {
|
||||
# For items that are lists (but not tags), recurse
|
||||
unlist(lapply(x, flattenTags), recursive = FALSE)
|
||||
}
|
||||
|
||||
} else if (is.character(x)){
|
||||
# This will preserve attributes if x is a character with attribute,
|
||||
# like what HTML() produces
|
||||
list(x)
|
||||
|
||||
} else if (is.function(x) && inherits(x, "shiny.render.function")) {
|
||||
|
||||
list(useRenderFunction(x))
|
||||
|
||||
} else {
|
||||
# For other items, coerce to character and wrap them into a list (which
|
||||
# will be unwrapped by caller). Note that this will strip attributes.
|
||||
list(as.character(x))
|
||||
}
|
||||
}
|
||||
4
R/tar.R
4
R/tar.R
@@ -46,7 +46,7 @@ untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
|
||||
mydir.create <- function(path, ...) {
|
||||
## for Windows' sake
|
||||
path <- sub("[\\/]$", "", path)
|
||||
if(file_test("-d", path)) return()
|
||||
if(utils::file_test("-d", path)) return()
|
||||
if(!dir.create(path, showWarnings = TRUE, recursive = TRUE, ...))
|
||||
stop(gettextf("failed to create directory %s", sQuote(path)),
|
||||
domain = NA)
|
||||
@@ -141,7 +141,7 @@ untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(name2), sQuote(name)), domain = NA)
|
||||
}
|
||||
} else {
|
||||
if(.Platform$OS.type == "windows") {
|
||||
if(isWindows()) {
|
||||
## this will not work for links to dirs
|
||||
from <- file.path(dirname(name), name2)
|
||||
if (!file.copy(from, name))
|
||||
|
||||
15
R/timer.R
15
R/timer.R
@@ -4,16 +4,17 @@ now <- function() {
|
||||
as.numeric(Sys.time()) * 1000
|
||||
}
|
||||
|
||||
TimerCallbacks <- setRefClass(
|
||||
TimerCallbacks <- R6Class(
|
||||
'TimerCallbacks',
|
||||
fields = list(
|
||||
.nextId = 'integer',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.nextId = 0L,
|
||||
.funcs = 'Map',
|
||||
.times = 'data.frame'
|
||||
),
|
||||
methods = list(
|
||||
.times = data.frame(),
|
||||
|
||||
initialize = function() {
|
||||
.nextId <<- 0L
|
||||
.funcs <<- Map$new()
|
||||
},
|
||||
clear = function() {
|
||||
.nextId <<- 0L
|
||||
|
||||
203
R/update-input.R
203
R/update-input.R
@@ -54,38 +54,6 @@ updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
updateCheckboxInput <- updateTextInput
|
||||
|
||||
|
||||
#' Change the value of a slider input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # Similar to number and text. only label and value can be set for slider
|
||||
#' updateSliderInput(session, "inSlider",
|
||||
#' label = paste("Slider label", x),
|
||||
#' value = x)
|
||||
#'
|
||||
#' # For sliders that pick out a range, pass in a vector of 2 values.
|
||||
#' updateSliderInput(session, "inSlider2", value = c(x-1, x+1))
|
||||
#'
|
||||
#' # An NA means to not change that value (the low or high one)
|
||||
#' updateSliderInput(session, "inSlider3", value = c(NA, x+2))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateSliderInput <- updateTextInput
|
||||
|
||||
#' Change the value of a date input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
@@ -118,7 +86,7 @@ updateSliderInput <- updateTextInput
|
||||
#' }
|
||||
#' @export
|
||||
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL) {
|
||||
min = NULL, max = NULL) {
|
||||
|
||||
# If value is a date object, convert it to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
@@ -163,8 +131,8 @@ updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' }
|
||||
#' @export
|
||||
updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
start = NULL, end = NULL, min = NULL, max = NULL) {
|
||||
|
||||
start = NULL, end = NULL, min = NULL,
|
||||
max = NULL) {
|
||||
# Make sure start and end are strings, not date objects. This is for
|
||||
# consistency across different locales.
|
||||
if (inherits(start, "Date")) start <- format(start, '%Y-%m-%d')
|
||||
@@ -217,6 +185,13 @@ updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' @rdname updateTabsetPanel
|
||||
#' @export
|
||||
updateNavbarPage <- updateTabsetPanel
|
||||
|
||||
#' @rdname updateTabsetPanel
|
||||
#' @export
|
||||
updateNavlistPanel <- updateTabsetPanel
|
||||
|
||||
#' Change the value of a number input on the client
|
||||
#'
|
||||
@@ -256,13 +231,100 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a slider input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param min Minimum value.
|
||||
#' @param max Maximum value.
|
||||
#' @param step Step size.
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' p("The first slider controls the second"),
|
||||
#' sliderInput("control", "Controller:", min=0, max=20, value=10,
|
||||
#' step=1),
|
||||
#' sliderInput("receive", "Receiver:", min=0, max=20, value=10,
|
||||
#' step=1)
|
||||
#' ),
|
||||
#' mainPanel()
|
||||
#' )
|
||||
#' ),
|
||||
#' server = function(input, output, session) {
|
||||
#' observe({
|
||||
#' val <- input$control
|
||||
#' # Control the value, min, max, and step.
|
||||
#' # Step size is 2 when input value is even; 1 when value is odd.
|
||||
#' updateSliderInput(session, "receive", value = val,
|
||||
#' min = floor(val/2), max = val+4, step = (val+1)%%2 + 1)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL)
|
||||
{
|
||||
# Make sure that value, min, max all have the same type, because we need
|
||||
# special handling for dates and datetimes.
|
||||
vals <- dropNulls(list(value, min, max))
|
||||
|
||||
type <- unique(lapply(vals, function(x) {
|
||||
if (inherits(x, "Date")) "date"
|
||||
else if (inherits(x, "POSIXt")) "datetime"
|
||||
else "number"
|
||||
}))
|
||||
if (length(type) > 1) {
|
||||
stop("Type mismatch for value, min, and max")
|
||||
}
|
||||
|
||||
if (type == "date" || type == "datetime") {
|
||||
to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
|
||||
if (!is.null(min)) min <- to_ms(min)
|
||||
if (!is.null(max)) max <- to_ms(max)
|
||||
if (!is.null(value)) value <- to_ms(value)
|
||||
}
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = formatNoSci(value),
|
||||
min = formatNoSci(min),
|
||||
max = formatNoSci(max),
|
||||
step = formatNoSci(step)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE,
|
||||
type = 'checkbox') {
|
||||
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- if (length(choices))
|
||||
format(tagList(
|
||||
generateOptions(inputId, choices, selected, inline, type = type)
|
||||
))
|
||||
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a checkbox group input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options (values) which will be selected.
|
||||
#' @inheritParams checkboxGroupInput
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxGroupInput}}
|
||||
#'
|
||||
@@ -295,27 +357,16 @@ updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
choices = NULL, selected = NULL) {
|
||||
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- if (length(choices))
|
||||
columnToRowData(list(value = choices, label = names(choices)))
|
||||
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
choices = NULL, selected = NULL,
|
||||
inline = FALSE) {
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a radio input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options (values) which will be selected.
|
||||
#' @inheritParams radioButtons
|
||||
#'
|
||||
#' @seealso \code{\link{radioButtons}}
|
||||
#'
|
||||
@@ -345,15 +396,18 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateRadioButtons <- updateCheckboxGroupInput
|
||||
updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE) {
|
||||
# you must select at least one radio button
|
||||
if (is.null(selected) && !is.null(choices)) selected <- choices[[1]]
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio')
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a select input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param choices A named vector or named list of options. For each item, the
|
||||
#' name will be used as the label, and the value will be used as the value.
|
||||
#' @param selected A vector or list of options (values) which will be selected.
|
||||
#' @inheritParams selectInput
|
||||
#'
|
||||
#' @seealso \code{\link{selectInput}}
|
||||
#'
|
||||
@@ -386,19 +440,26 @@ updateRadioButtons <- updateCheckboxGroupInput
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateSelectInput <- updateCheckboxGroupInput
|
||||
updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL) {
|
||||
choices <- if (!is.null(choices)) choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected)
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' @rdname updateSelectInput
|
||||
#' @param options a list of options (see \code{\link{selectizeInput}})
|
||||
#' @inheritParams selectizeInput
|
||||
#' @param server whether to store \code{choices} on the server side, and load
|
||||
#' the select options dynamically on searching, instead of writing all
|
||||
#' \code{choices} into the page at once (i.e., only use the client-side
|
||||
#' version of \pkg{selectize.js})
|
||||
#' @export
|
||||
updateSelectizeInput <- function(
|
||||
session, inputId, label = NULL, choices = NULL, selected = NULL,
|
||||
options = list(), server = FALSE
|
||||
) {
|
||||
updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, options = list(),
|
||||
server = FALSE) {
|
||||
if (length(options)) {
|
||||
res <- checkAsIs(options)
|
||||
cfg <- tags$script(
|
||||
@@ -407,14 +468,11 @@ updateSelectizeInput <- function(
|
||||
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
|
||||
HTML(toJSON(res$options))
|
||||
)
|
||||
session$sendInputMessage(inputId, list(newOptions = as.character(cfg)))
|
||||
session$sendInputMessage(inputId, list(config = as.character(cfg)))
|
||||
}
|
||||
if (!server) {
|
||||
return(updateSelectInput(session, inputId, label, choices, selected))
|
||||
}
|
||||
# in the server mode, the choices are not available before we type, so we
|
||||
# cannot really pre-select any options, but here we insert the `selected`
|
||||
# options into selectize forcibly
|
||||
value <- unname(selected)
|
||||
selected <- choicesWithNames(selected)
|
||||
message <- dropNulls(list(
|
||||
@@ -431,7 +489,7 @@ updateSelectizeInput <- function(
|
||||
selectizeJSON <- function(data, req) {
|
||||
query <- parseQueryString(req$QUERY_STRING)
|
||||
# extract the query variables, conjunction (and/or), search string, maximum options
|
||||
var <- fromJSON(query$field)
|
||||
var <- c(jsonlite::fromJSON(query$field))
|
||||
cjn <- if (query$conju == 'and') all else any
|
||||
# all keywords in lower-case, for case-insensitive matching
|
||||
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
|
||||
@@ -441,8 +499,8 @@ selectizeJSON <- function(data, req) {
|
||||
# convert a single vector to a data frame so it returns {label: , value: }
|
||||
# later in JSON; other objects return arbitrary JSON {x: , y: , foo: , ...}
|
||||
data <- if (is.atomic(data)) {
|
||||
data <- choicesWithNames(data)
|
||||
data.frame(label = names(data), value = data, stringsAsFactors = FALSE)
|
||||
data.frame(label = names(choicesWithNames(data)), value = data,
|
||||
stringsAsFactors = FALSE)
|
||||
} else as.data.frame(data, stringsAsFactors = FALSE)
|
||||
|
||||
# start searching for keywords in all specified columns
|
||||
@@ -459,8 +517,9 @@ selectizeJSON <- function(data, req) {
|
||||
idx <- idx | apply(matches, 1, cjn)
|
||||
}
|
||||
# only return the first n rows (n = maximum options in configuration)
|
||||
idx <- head(which(idx), mop)
|
||||
idx <- utils::head(if (length(key)) which(idx) else seq_along(idx), mop)
|
||||
data <- data[idx, ]
|
||||
|
||||
httpResponse(200, 'application/json', toJSON(columnToRowData(data)))
|
||||
res <- toJSON(columnToRowData(data))
|
||||
httpResponse(200, 'application/json', enc2utf8(res))
|
||||
}
|
||||
|
||||
486
R/utils.R
486
R/utils.R
@@ -25,7 +25,7 @@ NULL
|
||||
#' rnormB(5) # [1] -0.7946034 0.2568374 -0.6567597 1.2451387 -0.8375699
|
||||
#'
|
||||
#' @export
|
||||
repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
|
||||
repeatable <- function(rngfunc, seed = stats::runif(1, 0, .Machine$integer.max)) {
|
||||
force(seed)
|
||||
|
||||
function(...) {
|
||||
@@ -77,14 +77,25 @@ withPrivateSeed <- function(expr) {
|
||||
.globals$ownSeed, unset=is.null(.globals$ownSeed), {
|
||||
tryCatch({
|
||||
expr
|
||||
}, finally = {.globals$ownSeed <- .Random.seed})
|
||||
}, finally = {
|
||||
.globals$ownSeed <- getExists('.Random.seed', 'numeric', globalenv())
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# a homemade version of set.seed(NULL) for backward compatibility with R 2.15.x
|
||||
reinitializeSeed <- if (getRversion() >= '3.0.0') {
|
||||
function() set.seed(NULL)
|
||||
} else function() {
|
||||
if (exists('.Random.seed', globalenv()))
|
||||
rm(list = '.Random.seed', pos = globalenv())
|
||||
stats::runif(1) # generate any random numbers so R can reinitialize the seed
|
||||
}
|
||||
|
||||
# Version of runif that runs with private seed
|
||||
p_runif <- function(...) {
|
||||
withPrivateSeed(runif(...))
|
||||
withPrivateSeed(stats::runif(...))
|
||||
}
|
||||
|
||||
# Version of sample that runs with private seed
|
||||
@@ -109,6 +120,10 @@ p_randomInt <- function(...) {
|
||||
withPrivateSeed(randomInt(...))
|
||||
}
|
||||
|
||||
isWholeNum <- function(x, tol = .Machine$double.eps^0.5) {
|
||||
abs(x - round(x)) < tol
|
||||
}
|
||||
|
||||
`%OR%` <- function(x, y) {
|
||||
if (is.null(x) || isTRUE(is.na(x)))
|
||||
y
|
||||
@@ -140,20 +155,60 @@ dropNullsOrEmpty <- function(x) {
|
||||
x[!vapply(x, nullOrEmpty, FUN.VALUE=logical(1))]
|
||||
}
|
||||
|
||||
# Given a vector/list, return TRUE if any elements are unnamed, FALSE otherwise.
|
||||
anyUnnamed <- function(x) {
|
||||
# Zero-length vector
|
||||
if (length(x) == 0) return(FALSE)
|
||||
|
||||
nms <- names(x)
|
||||
|
||||
# List with no name attribute
|
||||
if (is.null(nms)) return(TRUE)
|
||||
|
||||
# List with name attribute; check for any ""
|
||||
any(!nzchar(nms))
|
||||
}
|
||||
|
||||
# Combine dir and (file)name into a file path. If a file already exists with a
|
||||
# name differing only by case, then use it instead.
|
||||
file.path.ci <- function(dir, name) {
|
||||
default <- file.path(dir, name)
|
||||
file.path.ci <- function(...) {
|
||||
result <- find.file.ci(...)
|
||||
if (!is.null(result))
|
||||
return(result)
|
||||
|
||||
# If not found, return the file path that was given to us.
|
||||
return(file.path(...))
|
||||
}
|
||||
|
||||
# Does a particular file exist? Case-insensitive for filename, case-sensitive
|
||||
# for path (on platforms with case-sensitive file system).
|
||||
file.exists.ci <- function(...) {
|
||||
!is.null(find.file.ci(...))
|
||||
}
|
||||
|
||||
# Look for a file, case-insensitive for filename, case-sensitive for path (on
|
||||
# platforms with case-sensitive filesystem). If found, return the path to the
|
||||
# file, with the correct case. If not found, return NULL.
|
||||
find.file.ci <- function(...) {
|
||||
default <- file.path(...)
|
||||
if (length(default) > 1)
|
||||
stop("find.file.ci can only check for one file at a time.")
|
||||
if (file.exists(default))
|
||||
return(default)
|
||||
if (!file.exists(dir))
|
||||
return(default)
|
||||
|
||||
matches <- list.files(dir, name, ignore.case=TRUE, full.names=TRUE,
|
||||
include.dirs=TRUE)
|
||||
dir <- dirname(default)
|
||||
name <- basename(default)
|
||||
|
||||
# If we got here, then we'll check for a directory with the exact case, and a
|
||||
# name with any case.
|
||||
all_files <- list.files(dir, all.files=TRUE, full.names=TRUE,
|
||||
include.dirs=TRUE)
|
||||
match_idx <- tolower(name) == tolower(basename(all_files))
|
||||
matches <- all_files[match_idx]
|
||||
if (length(matches) == 0)
|
||||
return(default)
|
||||
return(matches[[1]])
|
||||
return(NULL)
|
||||
|
||||
return(matches[1])
|
||||
}
|
||||
|
||||
# Attempt to join a path and relative path, and turn the result into a
|
||||
@@ -174,7 +229,7 @@ resolve <- function(dir, relpath) {
|
||||
abs.path <- normalizePath(abs.path, winslash='/', mustWork=TRUE)
|
||||
dir <- normalizePath(dir, winslash='/', mustWork=TRUE)
|
||||
# trim the possible trailing slash under Windows (#306)
|
||||
if (.Platform$OS.type == 'windows') dir <- sub('/$', '', dir)
|
||||
if (isWindows()) dir <- sub('/$', '', dir)
|
||||
if (nchar(abs.path) <= nchar(dir) + 1)
|
||||
return(NULL)
|
||||
if (substr(abs.path, 1, nchar(dir)) != dir ||
|
||||
@@ -184,6 +239,8 @@ resolve <- function(dir, relpath) {
|
||||
return(abs.path)
|
||||
}
|
||||
|
||||
isWindows <- function() .Platform$OS.type == 'windows'
|
||||
|
||||
# This is a wrapper for download.file and has the same interface.
|
||||
# The only difference is that, if the protocol is https, it changes the
|
||||
# download settings, depending on platform.
|
||||
@@ -192,7 +249,7 @@ download <- function(url, ...) {
|
||||
if (grepl('^https?://', url)) {
|
||||
|
||||
# If Windows, call setInternet2, then use download.file with defaults.
|
||||
if (.Platform$OS.type == "windows") {
|
||||
if (isWindows()) {
|
||||
# If we directly use setInternet2, R CMD CHECK gives a Note on Mac/Linux
|
||||
mySI2 <- `::`(utils, 'setInternet2')
|
||||
# Store initial settings
|
||||
@@ -201,7 +258,7 @@ download <- function(url, ...) {
|
||||
|
||||
# Needed for https
|
||||
mySI2(TRUE)
|
||||
download.file(url, ...)
|
||||
utils::download.file(url, ...)
|
||||
|
||||
} else {
|
||||
# If non-Windows, check for curl/wget/lynx, then call download.file with
|
||||
@@ -225,60 +282,17 @@ download <- function(url, ...) {
|
||||
stop("no download method found")
|
||||
}
|
||||
|
||||
download.file(url, method = method, ...)
|
||||
utils::download.file(url, method = method, ...)
|
||||
}
|
||||
|
||||
} else {
|
||||
download.file(url, ...)
|
||||
utils::download.file(url, ...)
|
||||
}
|
||||
}
|
||||
|
||||
knownContentTypes <- Map$new()
|
||||
knownContentTypes$mset(
|
||||
html='text/html; charset=UTF-8',
|
||||
htm='text/html; charset=UTF-8',
|
||||
js='text/javascript',
|
||||
css='text/css',
|
||||
png='image/png',
|
||||
jpg='image/jpeg',
|
||||
jpeg='image/jpeg',
|
||||
gif='image/gif',
|
||||
svg='image/svg+xml',
|
||||
txt='text/plain',
|
||||
pdf='application/pdf',
|
||||
ps='application/postscript',
|
||||
xml='application/xml',
|
||||
m3u='audio/x-mpegurl',
|
||||
m4a='audio/mp4a-latm',
|
||||
m4b='audio/mp4a-latm',
|
||||
m4p='audio/mp4a-latm',
|
||||
mp3='audio/mpeg',
|
||||
wav='audio/x-wav',
|
||||
m4u='video/vnd.mpegurl',
|
||||
m4v='video/x-m4v',
|
||||
mp4='video/mp4',
|
||||
mpeg='video/mpeg',
|
||||
mpg='video/mpeg',
|
||||
avi='video/x-msvideo',
|
||||
mov='video/quicktime',
|
||||
ogg='application/ogg',
|
||||
swf='application/x-shockwave-flash',
|
||||
doc='application/msword',
|
||||
xls='application/vnd.ms-excel',
|
||||
ppt='application/vnd.ms-powerpoint',
|
||||
xlsx='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
xltx='application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
potx='application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
ppsx='application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
pptx='application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
sldx='application/vnd.openxmlformats-officedocument.presentationml.slide',
|
||||
docx='application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
dotx='application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
xlam='application/vnd.ms-excel.addin.macroEnabled.12',
|
||||
xlsb='application/vnd.ms-excel.sheet.binary.macroEnabled.12')
|
||||
|
||||
getContentType <- function(ext, defaultType='application/octet-stream') {
|
||||
knownContentTypes$get(tolower(ext)) %OR% defaultType
|
||||
getContentType <- function(file, defaultType = 'application/octet-stream') {
|
||||
subtype <- ifelse(grepl('[.]html?$', file), 'charset=UTF-8', '')
|
||||
mime::guess_type(file, unknown = defaultType, subtype = subtype)
|
||||
}
|
||||
|
||||
# Create a zero-arg function from a quoted expression and environment
|
||||
@@ -403,9 +417,15 @@ installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
|
||||
#' Parse a GET query string from a URL
|
||||
#'
|
||||
#' Returns a named character vector of key-value pairs.
|
||||
#' Returns a named list of key-value pairs.
|
||||
#'
|
||||
#' @param str The query string. It can have a leading \code{"?"} or not.
|
||||
#' @param nested Whether to parse the query string of as a nested list when it
|
||||
#' contains pairs of square brackets \code{[]}. For example, the query
|
||||
#' \samp{a[i1][j1]=x&b[i1][j1]=y&b[i2][j1]=z} will be parsed as \code{list(a =
|
||||
#' list(i1 = list(j1 = 'x')), b = list(i1 = list(j1 = 'y'), i2 = list(j1 =
|
||||
#' 'z')))} when \code{nested = TRUE}, and \code{list(`a[i1][j1]` = 'x',
|
||||
#' `b[i1][j1]` = 'y', `b[i2][j1]` = 'z')} when \code{nested = FALSE}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' parseQueryString("?foo=1&bar=b%20a%20r")
|
||||
@@ -431,7 +451,7 @@ installExprFunction <- function(expr, name, eval.env = parent.frame(2),
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
parseQueryString <- function(str) {
|
||||
parseQueryString <- function(str, nested = FALSE) {
|
||||
if (is.null(str) || nchar(str) == 0)
|
||||
return(list())
|
||||
|
||||
@@ -451,10 +471,29 @@ parseQueryString <- function(str) {
|
||||
keys <- gsub('+', ' ', keys, fixed = TRUE)
|
||||
values <- gsub('+', ' ', values, fixed = TRUE)
|
||||
|
||||
keys <- vapply(keys, function(x) URLdecode(x), FUN.VALUE = character(1))
|
||||
values <- vapply(values, function(x) URLdecode(x), FUN.VALUE = character(1))
|
||||
keys <- URLdecode(keys)
|
||||
values <- URLdecode(values)
|
||||
|
||||
setNames(as.list(values), keys)
|
||||
res <- stats::setNames(as.list(values), keys)
|
||||
if (!nested) return(res)
|
||||
|
||||
# Make a nested list from a query of the form ?a[1][1]=x11&a[1][2]=x12&...
|
||||
for (i in grep('\\[.+\\]', keys)) {
|
||||
k <- strsplit(keys[i], '[][]')[[1L]] # split by [ or ]
|
||||
res <- assignNestedList(res, k[k != ''], values[i])
|
||||
res[[keys[i]]] <- NULL # remove res[['a[1][1]']]
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
# Assign value to the bottom element of the list x using recursive indices idx
|
||||
assignNestedList <- function(x = list(), idx, value) {
|
||||
for (i in seq_along(idx)) {
|
||||
sub <- idx[seq_len(i)]
|
||||
if (is.null(x[[sub]])) x[[sub]] <- list()
|
||||
}
|
||||
x[[idx]] <- value
|
||||
x
|
||||
}
|
||||
|
||||
# decide what to do in case of errors; it is customizable using the shiny.error
|
||||
@@ -473,18 +512,27 @@ shinyCallingHandlers <- function(expr) {
|
||||
#' @param new Name of replacement function.
|
||||
#' @param msg Message to print. If used, this will override the default message.
|
||||
#' @param old Name of deprecated function.
|
||||
#' @param version The last version of Shiny before the item was deprecated.
|
||||
#' @keywords internal
|
||||
shinyDeprecated <- function(new=NULL, msg=NULL,
|
||||
old=as.character(sys.call(sys.parent()))[1L]) {
|
||||
old=as.character(sys.call(sys.parent()))[1L],
|
||||
version = NULL) {
|
||||
|
||||
if (getOption("shiny.deprecation.messages", default=TRUE) == FALSE)
|
||||
if (getOption("shiny.deprecation.messages") %OR% TRUE == FALSE)
|
||||
return(invisible())
|
||||
|
||||
if (is.null(msg)) {
|
||||
msg <- paste(old, "is deprecated.")
|
||||
if (!is.null(new))
|
||||
if (!is.null(new)) {
|
||||
msg <- paste(msg, "Please use", new, "instead.",
|
||||
"To disable this message, run options(shiny.deprecation.messages=FALSE)")
|
||||
}
|
||||
}
|
||||
|
||||
if (!is.null(version)) {
|
||||
msg <- paste0(msg, " (Last used in version ", version, ")")
|
||||
}
|
||||
|
||||
# Similar to .Deprecated(), but print a message instead of warning
|
||||
message(msg)
|
||||
}
|
||||
@@ -511,15 +559,17 @@ registerDebugHook <- function(name, where, label) {
|
||||
}
|
||||
}
|
||||
|
||||
Callbacks <- setRefClass(
|
||||
Callbacks <- R6Class(
|
||||
'Callbacks',
|
||||
fields = list(
|
||||
.nextId = 'integer',
|
||||
.callbacks = 'Map'
|
||||
),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.nextId = integer(0),
|
||||
.callbacks = 'Map',
|
||||
|
||||
initialize = function() {
|
||||
.nextId <<- as.integer(.Machine$integer.max)
|
||||
.callbacks <<- Map$new()
|
||||
},
|
||||
register = function(callback) {
|
||||
id <- as.character(.nextId)
|
||||
@@ -530,7 +580,11 @@ Callbacks <- setRefClass(
|
||||
})
|
||||
},
|
||||
invoke = function(..., onError=NULL) {
|
||||
for (callback in .callbacks$values()) {
|
||||
# Ensure that calls are invoked in the order that they were registered
|
||||
keys <- as.character(sort(as.integer(.callbacks$keys()), decreasing = TRUE))
|
||||
callbacks <- .callbacks$mget(keys)
|
||||
|
||||
for (callback in callbacks) {
|
||||
if (is.null(onError)) {
|
||||
callback(...)
|
||||
} else {
|
||||
@@ -546,76 +600,95 @@ Callbacks <- setRefClass(
|
||||
|
||||
# convert a data frame to JSON as required by DataTables request
|
||||
dataTablesJSON <- function(data, req) {
|
||||
query <- req$QUERY_STRING
|
||||
n <- nrow(data)
|
||||
with(parseQueryString(query), {
|
||||
useRegex <- function(j, envir = parent.frame()) {
|
||||
# FIXME: bRegex is not part of the query string yet (DataTables 1.9.4)
|
||||
return(TRUE)
|
||||
ex <- getExists(
|
||||
if (missing(j)) 'bRegex' else sprintf('bRegex_%s', j), 'character', envir
|
||||
)
|
||||
is.null(ex) || ex == 'true'
|
||||
}
|
||||
# global searching
|
||||
i <- seq_len(n)
|
||||
sSearch <- getExists('sSearch', 'character')
|
||||
if (length(sSearch) && nzchar(sSearch)) {
|
||||
bRegex <- useRegex()
|
||||
i0 <- apply(data, 2, function(x) grep(sSearch, as.character(x), fixed = !bRegex))
|
||||
i <- intersect(i, unique(unlist(i0)))
|
||||
}
|
||||
# search by columns
|
||||
if (length(i)) for (j in seq_len(as.integer(iColumns)) - 1) {
|
||||
if (is.null(s <- getExists(sprintf('bSearchable_%d', j), 'character')) ||
|
||||
s == "0" || s == "false") next # the j-th column is not searchable
|
||||
if (is.null(k <- getExists(sprintf('sSearch_%d', j), 'character'))) next
|
||||
if (nzchar(k)) {
|
||||
dj <- data[, j + 1]
|
||||
r <- commaToRange(k)
|
||||
ij <- if (length(r) == 2 && is.numeric(dj)) {
|
||||
which(dj >= r[1] & dj <= r[2])
|
||||
} else {
|
||||
grep(k, as.character(dj), fixed = !useRegex(j))
|
||||
}
|
||||
i <- intersect(ij, i)
|
||||
}
|
||||
if (length(i) == 0) break
|
||||
}
|
||||
if (length(i) != n) data <- data[i, , drop = FALSE]
|
||||
# sorting
|
||||
oList <- list()
|
||||
for (j in seq_len(as.integer(iSortingCols)) - 1) {
|
||||
if (is.null(k <- getExists(sprintf('iSortCol_%d', j), 'character'))) break
|
||||
desc <- getExists(sprintf('sSortDir_%d', j), 'character')
|
||||
if (is.character(desc)) {
|
||||
col <- data[, as.integer(k) + 1]
|
||||
oList[[length(oList) + 1]] <- (if (desc == 'asc') identity else `-`)(
|
||||
if (is.numeric(col)) col else xtfrm(col)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (length(oList)) {
|
||||
i <- do.call(order, oList)
|
||||
data <- data[i, , drop = FALSE]
|
||||
}
|
||||
# paging
|
||||
if (iDisplayLength != '-1') {
|
||||
i <- seq(as.integer(iDisplayStart) + 1L, length.out = as.integer(iDisplayLength))
|
||||
i <- i[i <= nrow(data)]
|
||||
fdata <- data[i, , drop = FALSE] # filtered data
|
||||
} else fdata <- data
|
||||
fdata <- unname(as.matrix(fdata))
|
||||
if (nrow(fdata) == 0) fdata <- list()
|
||||
# DataTables requests were sent via POST
|
||||
params <- URLdecode(rawToChar(req$rook.input$read()))
|
||||
q <- parseQueryString(params, nested = TRUE)
|
||||
ci <- q$search[['caseInsensitive']] == 'true'
|
||||
|
||||
res <- toJSON(list(
|
||||
sEcho = as.integer(sEcho),
|
||||
iTotalRecords = n,
|
||||
iTotalDisplayRecords = nrow(data),
|
||||
aaData = fdata
|
||||
))
|
||||
httpResponse(200, 'application/json', res)
|
||||
})
|
||||
# global searching
|
||||
i <- seq_len(n)
|
||||
if (length(q$search[['value']]) && q$search[['value']] != '') {
|
||||
i0 <- apply(data, 2, function(x) {
|
||||
grep2(q$search[['value']], as.character(x),
|
||||
fixed = q$search[['regex']] == 'false', ignore.case = ci)
|
||||
})
|
||||
i <- intersect(i, unique(unlist(i0)))
|
||||
}
|
||||
|
||||
# search by columns
|
||||
if (length(i)) for (j in names(q$columns)) {
|
||||
col <- q$columns[[j]]
|
||||
# if the j-th column is not searchable or the search string is "", skip it
|
||||
if (col[['searchable']] != 'true') next
|
||||
if ((k <- col[['search']][['value']]) == '') next
|
||||
j <- as.integer(j)
|
||||
dj <- data[, j + 1]
|
||||
r <- commaToRange(k)
|
||||
ij <- if (length(r) == 2 && is.numeric(dj)) {
|
||||
which(dj >= r[1] & dj <= r[2])
|
||||
} else {
|
||||
grep2(k, as.character(dj), fixed = col[['search']][['regex']] == 'false',
|
||||
ignore.case = ci)
|
||||
}
|
||||
i <- intersect(ij, i)
|
||||
if (length(i) == 0) break
|
||||
}
|
||||
if (length(i) != n) data <- data[i, , drop = FALSE]
|
||||
|
||||
# sorting
|
||||
oList <- list()
|
||||
for (ord in q$order) {
|
||||
k <- ord[['column']] # which column to sort
|
||||
d <- ord[['dir']] # direction asc/desc
|
||||
if (q$columns[[k]][['orderable']] != 'true') next
|
||||
col <- data[, as.integer(k) + 1]
|
||||
oList[[length(oList) + 1]] <- (if (d == 'asc') identity else `-`)(
|
||||
if (is.numeric(col)) col else xtfrm(col)
|
||||
)
|
||||
}
|
||||
if (length(oList)) {
|
||||
i <- do.call(order, oList)
|
||||
data <- data[i, , drop = FALSE]
|
||||
}
|
||||
# paging
|
||||
if (q$length != '-1') {
|
||||
i <- seq(as.integer(q$start) + 1L, length.out = as.integer(q$length))
|
||||
i <- i[i <= nrow(data)]
|
||||
fdata <- data[i, , drop = FALSE] # filtered data
|
||||
} else fdata <- data
|
||||
|
||||
fdata <- unname(as.matrix(fdata))
|
||||
if (is.character(fdata) && q$escape != 'false') {
|
||||
if (q$escape == 'true') fdata <- htmlEscape(fdata) else {
|
||||
k <- as.integer(strsplit(q$escape, ',')[[1]])
|
||||
# use seq_len() in case escape = negative indices, e.g. c(-1, -5)
|
||||
for (j in seq_len(ncol(fdata))[k]) fdata[, j] <- htmlEscape(fdata[, j])
|
||||
}
|
||||
}
|
||||
|
||||
res <- toJSON(list(
|
||||
draw = as.integer(q$draw),
|
||||
recordsTotal = n,
|
||||
recordsFiltered = nrow(data),
|
||||
data = fdata
|
||||
))
|
||||
httpResponse(200, 'application/json', enc2utf8(res))
|
||||
}
|
||||
|
||||
# when both ignore.case and fixed are TRUE, we use grep(ignore.case = FALSE,
|
||||
# fixed = TRUE) to do lower-case matching of pattern on x
|
||||
grep2 <- function(pattern, x, ignore.case = FALSE, fixed = FALSE, ...) {
|
||||
if (fixed && ignore.case) {
|
||||
pattern <- tolower(pattern)
|
||||
x <- tolower(x)
|
||||
ignore.case <- FALSE
|
||||
}
|
||||
# when the user types in the search box, the regular expression may not be
|
||||
# complete before it is sent to the server, in which case we do not search
|
||||
if (!fixed && inherits(try(grep(pattern, ''), silent = TRUE), 'try-error'))
|
||||
return(seq_along(x))
|
||||
grep(pattern, x, ignore.case = ignore.case, fixed = fixed, ...)
|
||||
}
|
||||
|
||||
getExists <- function(x, mode, envir = parent.frame()) {
|
||||
@@ -640,6 +713,7 @@ commaToRange <- function(string) {
|
||||
checkAsIs <- function(options) {
|
||||
evalOptions <- if (length(options)) {
|
||||
nms <- names(options)
|
||||
if (length(nms) == 0L || any(nms == '')) stop("'options' must be a named list")
|
||||
i <- unlist(lapply(options, function(x) {
|
||||
is.character(x) && inherits(x, 'AsIs')
|
||||
}))
|
||||
@@ -714,29 +788,13 @@ cachedFuncWithFile <- function(dir, file, func, case.sensitive = FALSE) {
|
||||
|
||||
now <- file.info(fname)$mtime
|
||||
if (!identical(mtime, now)) {
|
||||
value <<- func(...)
|
||||
value <<- func(fname, ...)
|
||||
mtime <<- now
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a function that sources the file and caches the result for subsequent
|
||||
# calls, unless the file's mtime changes.
|
||||
cachedSource <- function(dir, file, case.sensitive = FALSE) {
|
||||
dir <- normalizePath(dir, mustWork=TRUE)
|
||||
cachedFuncWithFile(dir, file, function(...) {
|
||||
fname <- if (case.sensitive)
|
||||
file.path(dir, file)
|
||||
else
|
||||
file.path.ci(dir, file)
|
||||
if (file.exists(fname))
|
||||
return(source(fname, ...))
|
||||
else
|
||||
return(NULL)
|
||||
})
|
||||
}
|
||||
|
||||
# turn column-based data to row-based data (mainly for JSON), e.g. data.frame(x
|
||||
# = 1:10, y = 10:1) ==> list(list(x = 1, y = 10), list(x = 2, y = 9), ...)
|
||||
columnToRowData <- function(data) {
|
||||
@@ -800,7 +858,8 @@ columnToRowData <- function(data) {
|
||||
#' @param ... A list of tests. Each test should equal \code{NULL} for success,
|
||||
#' \code{FALSE} for silent failure, or a string for failure with an error
|
||||
#' message.
|
||||
#' @param errorClass A CSS class to apply.
|
||||
#' @param errorClass A CSS class to apply. The actual CSS string will have
|
||||
#' \code{shiny-output-error-} prepended to this value.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' # in ui.R
|
||||
@@ -815,7 +874,7 @@ columnToRowData <- function(data) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' validate(
|
||||
#' need(input$in1, 'Check at least one letter!'),
|
||||
#' need(input$in2 == '', 'Please choose a state.')
|
||||
#' need(input$in2 != '', 'Please choose a state.')
|
||||
#' )
|
||||
#' plot(1:10, main = paste(c(input$in1, input$in2), collapse = ', '))
|
||||
#' })
|
||||
@@ -833,7 +892,7 @@ validate <- function(..., errorClass = character(0)) {
|
||||
stop("Unexpected validation result: ", as.character(x))
|
||||
})
|
||||
|
||||
results <- na.omit(results)
|
||||
results <- stats::na.omit(results)
|
||||
if (length(results) == 0)
|
||||
return(invisible())
|
||||
|
||||
@@ -877,11 +936,11 @@ isTruthy <- function(x) {
|
||||
return(FALSE)
|
||||
if (all(is.na(x)))
|
||||
return(FALSE)
|
||||
if (is.character(x) && !any(nzchar(na.omit(x))))
|
||||
if (is.character(x) && !any(nzchar(stats::na.omit(x))))
|
||||
return(FALSE)
|
||||
if (inherits(x, 'shinyActionButtonValue') && x == 0)
|
||||
return(FALSE)
|
||||
if (is.logical(x) && !any(na.omit(x)))
|
||||
if (is.logical(x) && !any(stats::na.omit(x)))
|
||||
return(FALSE)
|
||||
|
||||
return(TRUE)
|
||||
@@ -892,7 +951,98 @@ isTruthy <- function(x) {
|
||||
stopWithCondition <- function(class, message) {
|
||||
cond <- structure(
|
||||
list(message = message),
|
||||
class = c(class, 'error', 'condition')
|
||||
class = c(class, 'shiny.silent.error', 'error', 'condition')
|
||||
)
|
||||
stop(cond)
|
||||
}
|
||||
|
||||
#' Collect information about the Shiny Server environment
|
||||
#'
|
||||
#' This function returns the information about the current Shiny Server, such as
|
||||
#' its version, and whether it is the open source edition or professional
|
||||
#' edition. If the app is not served through the Shiny Server, this function
|
||||
#' just returns \code{list(shinyServer = FALSE)}.
|
||||
#'
|
||||
#' This function will only return meaningful data when using Shiny Server
|
||||
#' version 1.2.2 or later.
|
||||
#' @export
|
||||
#' @return A list of the Shiny Server information.
|
||||
serverInfo <- function() {
|
||||
.globals$serverInfo
|
||||
}
|
||||
.globals$serverInfo <- list(shinyServer = FALSE)
|
||||
|
||||
setServerInfo <- function(...) {
|
||||
infoOld <- serverInfo()
|
||||
infoNew <- list(...)
|
||||
infoOld[names(infoNew)] <- infoNew
|
||||
.globals$serverInfo <- infoOld
|
||||
}
|
||||
|
||||
# see if the file can be read as UTF-8 on Windows, and converted from UTF-8 to
|
||||
# native encoding; if the conversion fails, it will produce NA's in the results
|
||||
checkEncoding <- function(file) {
|
||||
# skip *nix because its locale is normally UTF-8 based (e.g. en_US.UTF-8), and
|
||||
# *nix users have to make a conscious effort to save a file with an encoding
|
||||
# that is not UTF-8; if they choose to do so, we cannot do much about it
|
||||
# except sitting back and seeing them punished after they choose to escape a
|
||||
# world of consistency (falling back to getOption('encoding') will not help
|
||||
# because native.enc is also normally UTF-8 based on *nix)
|
||||
if (!isWindows()) return('UTF-8')
|
||||
# an empty file?
|
||||
size <- file.info(file)[, 'size']
|
||||
if (size == 0) return('UTF-8')
|
||||
|
||||
x <- readLines(file, encoding = 'UTF-8', warn = FALSE)
|
||||
# if conversion is successful and there are no embedded nul's, use UTF-8
|
||||
if (!any(is.na(iconv(x, 'UTF-8'))) &&
|
||||
!any(readBin(file, 'raw', size) == as.raw(0))) return('UTF-8')
|
||||
|
||||
# check if there is a BOM character: this is also skipped on *nix, because R
|
||||
# on *nix simply ignores this meaningless character if present, but it hurts
|
||||
# on Windows
|
||||
if (identical(charToRaw(readChar(file, 3L, TRUE)), charToRaw('\UFEFF'))) {
|
||||
warning('You should not include the Byte Order Mark (BOM) in ', file, '. ',
|
||||
'Please re-save it in UTF-8 without BOM. See ',
|
||||
'http://shiny.rstudio.com/articles/unicode.html for more info.')
|
||||
if (getRversion() < '3.0.0')
|
||||
stop('R does not support UTF-8-BOM before 3.0.0. Please upgrade R.')
|
||||
return('UTF-8-BOM')
|
||||
}
|
||||
|
||||
enc <- getOption('encoding')
|
||||
msg <- c(sprintf('The file "%s" is not encoded in UTF-8. ', file),
|
||||
'Please convert its encoding to UTF-8 ',
|
||||
'(e.g. use the menu `File -> Save with Encoding` in RStudio). ',
|
||||
'See http://shiny.rstudio.com/articles/unicode.html for more info.')
|
||||
if (enc == 'UTF-8') stop(msg)
|
||||
# if you publish the app to ShinyApps.io, you will be in trouble
|
||||
warning(c(msg, ' Falling back to the encoding "', enc, '".'))
|
||||
|
||||
enc
|
||||
}
|
||||
|
||||
# try to read a file using UTF-8 (fall back to getOption('encoding') in case of
|
||||
# failure, which defaults to native.enc, i.e. native encoding)
|
||||
readUTF8 <- function(file) {
|
||||
enc <- checkEncoding(file)
|
||||
# readLines() does not support UTF-8-BOM directly; has to go through file()
|
||||
if (enc == 'UTF-8-BOM') {
|
||||
file <- base::file(file, encoding = enc)
|
||||
on.exit(close(file), add = TRUE)
|
||||
}
|
||||
x <- readLines(file, encoding = enc, warn = FALSE)
|
||||
enc2native(x)
|
||||
}
|
||||
|
||||
# similarly, try to source() a file with UTF-8
|
||||
sourceUTF8 <- function(file, ...) {
|
||||
source(file, ..., keep.source = TRUE, encoding = checkEncoding(file))
|
||||
}
|
||||
|
||||
|
||||
URLdecode <- decodeURIComponent
|
||||
URLencode <- function(value, reserved = FALSE) {
|
||||
value <- enc2utf8(value)
|
||||
if (reserved) encodeURIComponent(value) else encodeURI(value)
|
||||
}
|
||||
|
||||
24
README.md
24
README.md
@@ -1,10 +1,10 @@
|
||||
# Shiny
|
||||
|
||||
[](https://travis-ci.org/rstudio/shiny)
|
||||
[](https://travis-ci.org/rstudio/shiny)
|
||||
|
||||
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
|
||||
|
||||
For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.com/shiny/).
|
||||
For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstudio.com/).
|
||||
|
||||
## Features
|
||||
|
||||
@@ -32,15 +32,31 @@ To install the latest development builds directly from GitHub, run this instead:
|
||||
```r
|
||||
if (!require("devtools"))
|
||||
install.packages("devtools")
|
||||
devtools::install_github("shiny", "rstudio")
|
||||
devtools::install_github("rstudio/shiny")
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
To learn more we highly recommend you check out the [Shiny Tutorial](http://rstudio.github.com/shiny/tutorial). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
|
||||
To learn more we highly recommend you check out the [Shiny Tutorial](http://shiny.rstudio.com/tutorial/). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.
|
||||
|
||||
We hope you enjoy using Shiny. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
|
||||
|
||||
## Bootstrap 3 migration
|
||||
|
||||
Shiny versions 0.10.2.2 and below used the Bootstrap 2 web framework. After 0.10.2.2, Shiny switched to Bootstrap 3. For most users, the upgrade should be seamless. However, if you have have customized your HTML-generating code to use features specific to Bootstrap 2, you may need to update your code to work with Bootstrap 3.
|
||||
|
||||
If you do not wish to update your code at this time, you can use the [shinybootstrap2](https://github.com/rstudio/shinybootstrap2) package for backward compatibility.
|
||||
|
||||
If you prefer to install an older version of Shiny, you can do it using the devtools package:
|
||||
|
||||
```R
|
||||
devtools::install_version("shiny", version = "0.10.2.2")
|
||||
```
|
||||
|
||||
## Development notes
|
||||
|
||||
The Javascript code in Shiny is minified using tools that run on Node.js. See the tools/ directory for more information.
|
||||
|
||||
## License
|
||||
|
||||
The shiny package is licensed under the GPLv3. See these files in the inst directory for additional details:
|
||||
|
||||
0
cran-comments.md
Normal file
0
cran-comments.md
Normal file
678
inst/COPYING
678
inst/COPYING
@@ -1,678 +0,0 @@
|
||||
The shiny package is licensed to you under the GPLv3, the terms of
|
||||
which are included below. The markdown pacakge includes other open
|
||||
source software whose license terms can be found in the file NOTICE.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
325
inst/NOTICE
325
inst/NOTICE
@@ -1,325 +0,0 @@
|
||||
The shiny package inludes other open source software components. The following
|
||||
is a list of these components (full copies of the license agreements used by
|
||||
these components are included below):
|
||||
|
||||
- jQuery
|
||||
- Bootstrap
|
||||
- bootstrap-datepicker, from https://github.com/eternicode/bootstrap-datepicker
|
||||
- selectize, from https://github.com/brianreavis/selectize.js
|
||||
- es5-shim
|
||||
- jslider
|
||||
- DataTables
|
||||
|
||||
jQuery License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2012 jQuery Foundation and other contributors,
|
||||
http://jquery.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Bootstrap, bootstrap-datepicker, and selectize License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
|
||||
|
||||
es5-shim License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2009-2014 Kristopher Michael Kowal and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
jslider License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2012 Egor Khmelev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
DataTables License
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2008-2010, Allan Jardine
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Allan Jardine nor SpryMedia UK may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS
|
||||
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,6 @@
|
||||
name: 01_hello
|
||||
account: admin
|
||||
server: localhost
|
||||
bundleId: 1
|
||||
url: http://localhost:3939/admin/01_hello/
|
||||
when: 1436550957.65385
|
||||
@@ -12,22 +12,22 @@ mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
|
||||
# Define server logic required to plot various variables against
|
||||
# mpg
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Compute the forumla text in a reactive expression since it is
|
||||
|
||||
# Compute the formula text in a reactive expression since it is
|
||||
# shared by the output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
|
||||
# Return the formula text for printing as a caption
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
|
||||
# Generate a plot of the requested variable against mpg and
|
||||
# only include outliers if requested
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers)
|
||||
})
|
||||
|
||||
@@ -2,39 +2,39 @@ library(shiny)
|
||||
|
||||
# Define UI for slider demo application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
|
||||
# Application title
|
||||
titlePanel("Sliders"),
|
||||
|
||||
|
||||
# Sidebar with sliders that demonstrate various available
|
||||
# options
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
# Simple integer interval
|
||||
sliderInput("integer", "Integer:",
|
||||
sliderInput("integer", "Integer:",
|
||||
min=0, max=1000, value=500),
|
||||
|
||||
|
||||
# Decimal interval with step value
|
||||
sliderInput("decimal", "Decimal:",
|
||||
sliderInput("decimal", "Decimal:",
|
||||
min = 0, max = 1, value = 0.5, step= 0.1),
|
||||
|
||||
|
||||
# Specification of range within an interval
|
||||
sliderInput("range", "Range:",
|
||||
min = 1, max = 1000, value = c(200,500)),
|
||||
|
||||
# Provide a custom currency format for value display,
|
||||
|
||||
# Provide a custom currency format for value display,
|
||||
# with basic animation
|
||||
sliderInput("format", "Custom Format:",
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000, value = 0, step = 2500,
|
||||
format="$#,##0", locale="us", animate=TRUE),
|
||||
|
||||
pre = "$", sep = ",", animate=TRUE),
|
||||
|
||||
# Animation with custom interval (in ms) to control speed,
|
||||
# plus looping
|
||||
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
|
||||
step = 10, animate=
|
||||
animationOptions(interval=300, loop=TRUE))
|
||||
),
|
||||
|
||||
|
||||
# Show a table summarizing the values entered
|
||||
mainPanel(
|
||||
tableOutput("values")
|
||||
|
||||
@@ -42,6 +42,7 @@ sd_section("UI Inputs",
|
||||
"sliderInput",
|
||||
"submitButton",
|
||||
"textInput",
|
||||
"passwordInput",
|
||||
"updateCheckboxGroupInput",
|
||||
"updateCheckboxInput",
|
||||
"updateDateInput",
|
||||
@@ -58,13 +59,14 @@ sd_section("UI Outputs",
|
||||
"Functions for creating user interface elements that, in conjunction with rendering functions, display different kinds of output from your application.",
|
||||
c(
|
||||
"htmlOutput",
|
||||
"imageOutput",
|
||||
"plotOutput",
|
||||
"outputOptions",
|
||||
"tableOutput",
|
||||
"textOutput",
|
||||
"verbatimTextOutput",
|
||||
"downloadButton"
|
||||
"downloadButton",
|
||||
"Progress",
|
||||
"withProgress"
|
||||
)
|
||||
)
|
||||
sd_section("Interface builder functions",
|
||||
@@ -105,6 +107,7 @@ sd_section("Reactive constructs",
|
||||
"isolate",
|
||||
"makeReactiveBinding",
|
||||
"observe",
|
||||
"observeEvent",
|
||||
"reactive",
|
||||
"reactiveFileReader",
|
||||
"reactivePoll",
|
||||
@@ -127,8 +130,6 @@ sd_section("Running",
|
||||
c(
|
||||
"runApp",
|
||||
"runExample",
|
||||
"runGist",
|
||||
"runGitHub",
|
||||
"runUrl",
|
||||
"stopApp"
|
||||
)
|
||||
@@ -136,6 +137,7 @@ sd_section("Running",
|
||||
sd_section("Extending Shiny",
|
||||
"Functions that are intended to be called by third-party packages that extend Shiny.",
|
||||
c(
|
||||
"createWebDependency",
|
||||
"addResourcePath",
|
||||
"registerInputHandler",
|
||||
"removeInputHandler",
|
||||
@@ -152,15 +154,26 @@ sd_section("Utility functions",
|
||||
"parseQueryString",
|
||||
"plotPNG",
|
||||
"repeatable",
|
||||
"shinyDeprecated"
|
||||
"shinyDeprecated",
|
||||
"serverInfo",
|
||||
"shiny-options"
|
||||
)
|
||||
)
|
||||
sd_section("Plot interaction",
|
||||
"Functions related to interactive plots",
|
||||
c(
|
||||
"brushedPoints",
|
||||
"brushOpts",
|
||||
"clickOpts",
|
||||
"dblclickOpts",
|
||||
"hoverOpts",
|
||||
"nearPoints"
|
||||
)
|
||||
)
|
||||
sd_section("Embedding",
|
||||
"Functions that are intended for third-party packages that embed Shiny applications.",
|
||||
c(
|
||||
"shinyApp",
|
||||
"maskReactiveContext",
|
||||
"knitr_methods",
|
||||
"getProvidedHtmlDependencies"
|
||||
"maskReactiveContext"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Spec Runner</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png">
|
||||
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
|
||||
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>
|
||||
|
||||
<!-- include source files here... -->
|
||||
|
||||
<!-- All of these includes are copied out of the HTML file generated by
|
||||
shinyUI() -->
|
||||
<script src="../www/shared/jquery.js" type="text/javascript"></script>
|
||||
<script src="../www/shared/shiny.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/shiny.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/slider/css/jquery.slider.min.css"/>
|
||||
<script src="../www/shared/slider/js/jquery.slider.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap/css/bootstrap.min.css"/>
|
||||
<script src="../www/shared/bootstrap/js/bootstrap.min.js"></script>
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"/> -->
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap/css/bootstrap-responsive.min.css"/>
|
||||
|
||||
<script src="../www/shared/datepicker/js/bootstrap-datepicker.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/datepicker/css/datepicker.css"/>
|
||||
|
||||
<script src="../www/shared/bootstrap-daterangepicker/date.js"></script>
|
||||
<script src="../www/shared/bootstrap-daterangepicker/daterangepicker.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../www/shared/bootstrap-daterangepicker/daterangepicker.css"/>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<!-- <script type="text/javascript" src="spec/SpecHelper.js"></script>
|
||||
<script type="text/javascript" src="spec/PlayerSpec.js"></script>
|
||||
-->
|
||||
<script type="text/javascript" src="spec/inputBindingSpec.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
// var currentWindowOnload = window.onload;
|
||||
|
||||
// window.onload = function() {
|
||||
// if (currentWindowOnload) {
|
||||
// currentWindowOnload();
|
||||
// }
|
||||
// execJasmine();
|
||||
// };
|
||||
|
||||
// Add a slight delay before running tests, so that Shiny has time to
|
||||
// do setup stuff.
|
||||
$(document).ready(function() {
|
||||
setTimeout(function() {
|
||||
execJasmine();
|
||||
},
|
||||
50
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
function execJasmine() {
|
||||
jasmineEnv.execute();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// Clear the Shiny disconnected gray screen shortly after loading
|
||||
$(document).ready(function() {
|
||||
setTimeout(function() {
|
||||
$('body').removeClass('disconnected');
|
||||
},
|
||||
100
|
||||
)
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
<label>Text input:</label>
|
||||
<input id="in_text" type="text" value="starting value"/>
|
||||
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2008-2011 Pivotal Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,681 +0,0 @@
|
||||
jasmine.HtmlReporterHelpers = {};
|
||||
|
||||
jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) {
|
||||
el.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
|
||||
var results = child.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
|
||||
var parentDiv = this.dom.summary;
|
||||
var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
|
||||
var parent = child[parentSuite];
|
||||
|
||||
if (parent) {
|
||||
if (typeof this.views.suites[parent.id] == 'undefined') {
|
||||
this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
|
||||
}
|
||||
parentDiv = this.views.suites[parent.id].element;
|
||||
}
|
||||
|
||||
parentDiv.appendChild(childElement);
|
||||
};
|
||||
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
|
||||
for(var fn in jasmine.HtmlReporterHelpers) {
|
||||
ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter = function(_doc) {
|
||||
var self = this;
|
||||
var doc = _doc || window.document;
|
||||
|
||||
var reporterView;
|
||||
|
||||
var dom = {};
|
||||
|
||||
// Jasmine Reporter Public Interface
|
||||
self.logRunningSpecs = false;
|
||||
|
||||
self.reportRunnerStarting = function(runner) {
|
||||
var specs = runner.specs() || [];
|
||||
|
||||
if (specs.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
createReporterDom(runner.env.versionString());
|
||||
doc.body.appendChild(dom.reporter);
|
||||
setExceptionHandling();
|
||||
|
||||
reporterView = new jasmine.HtmlReporter.ReporterView(dom);
|
||||
reporterView.addSpecs(specs, self.specFilter);
|
||||
};
|
||||
|
||||
self.reportRunnerResults = function(runner) {
|
||||
reporterView && reporterView.complete();
|
||||
};
|
||||
|
||||
self.reportSuiteResults = function(suite) {
|
||||
reporterView.suiteComplete(suite);
|
||||
};
|
||||
|
||||
self.reportSpecStarting = function(spec) {
|
||||
if (self.logRunningSpecs) {
|
||||
self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
self.reportSpecResults = function(spec) {
|
||||
reporterView.specComplete(spec);
|
||||
};
|
||||
|
||||
self.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.specFilter = function(spec) {
|
||||
if (!focusedSpecName()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return spec.getFullName().indexOf(focusedSpecName()) === 0;
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function focusedSpecName() {
|
||||
var specName;
|
||||
|
||||
(function memoizeFocusedSpec() {
|
||||
if (specName) {
|
||||
return;
|
||||
}
|
||||
|
||||
var paramMap = [];
|
||||
var params = jasmine.HtmlReporter.parameters(doc);
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
specName = paramMap.spec;
|
||||
})();
|
||||
|
||||
return specName;
|
||||
}
|
||||
|
||||
function createReporterDom(version) {
|
||||
dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
|
||||
dom.banner = self.createDom('div', { className: 'banner' },
|
||||
self.createDom('span', { className: 'title' }, "Jasmine "),
|
||||
self.createDom('span', { className: 'version' }, version)),
|
||||
|
||||
dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
|
||||
dom.alert = self.createDom('div', {className: 'alert'},
|
||||
self.createDom('span', { className: 'exceptions' },
|
||||
self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
|
||||
self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
|
||||
dom.results = self.createDom('div', {className: 'results'},
|
||||
dom.summary = self.createDom('div', { className: 'summary' }),
|
||||
dom.details = self.createDom('div', { id: 'details' }))
|
||||
);
|
||||
}
|
||||
|
||||
function noTryCatch() {
|
||||
return window.location.search.match(/catch=false/);
|
||||
}
|
||||
|
||||
function searchWithCatch() {
|
||||
var params = jasmine.HtmlReporter.parameters(window.document);
|
||||
var removed = false;
|
||||
var i = 0;
|
||||
|
||||
while (!removed && i < params.length) {
|
||||
if (params[i].match(/catch=/)) {
|
||||
params.splice(i, 1);
|
||||
removed = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (jasmine.CATCH_EXCEPTIONS) {
|
||||
params.push("catch=false");
|
||||
}
|
||||
|
||||
return params.join("&");
|
||||
}
|
||||
|
||||
function setExceptionHandling() {
|
||||
var chxCatch = document.getElementById('no_try_catch');
|
||||
|
||||
if (noTryCatch()) {
|
||||
chxCatch.setAttribute('checked', true);
|
||||
jasmine.CATCH_EXCEPTIONS = false;
|
||||
}
|
||||
chxCatch.onclick = function() {
|
||||
window.location.search = searchWithCatch();
|
||||
};
|
||||
}
|
||||
};
|
||||
jasmine.HtmlReporter.parameters = function(doc) {
|
||||
var paramStr = doc.location.search.substring(1);
|
||||
var params = [];
|
||||
|
||||
if (paramStr.length > 0) {
|
||||
params = paramStr.split('&');
|
||||
}
|
||||
return params;
|
||||
}
|
||||
jasmine.HtmlReporter.sectionLink = function(sectionName) {
|
||||
var link = '?';
|
||||
var params = [];
|
||||
|
||||
if (sectionName) {
|
||||
params.push('spec=' + encodeURIComponent(sectionName));
|
||||
}
|
||||
if (!jasmine.CATCH_EXCEPTIONS) {
|
||||
params.push("catch=false");
|
||||
}
|
||||
if (params.length > 0) {
|
||||
link += params.join("&");
|
||||
}
|
||||
|
||||
return link;
|
||||
};
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
|
||||
jasmine.HtmlReporter.ReporterView = function(dom) {
|
||||
this.startedAt = new Date();
|
||||
this.runningSpecCount = 0;
|
||||
this.completeSpecCount = 0;
|
||||
this.passedCount = 0;
|
||||
this.failedCount = 0;
|
||||
this.skippedCount = 0;
|
||||
|
||||
this.createResultsMenu = function() {
|
||||
this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
|
||||
this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
|
||||
' | ',
|
||||
this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
|
||||
|
||||
this.summaryMenuItem.onclick = function() {
|
||||
dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
|
||||
};
|
||||
|
||||
this.detailsMenuItem.onclick = function() {
|
||||
showDetails();
|
||||
};
|
||||
};
|
||||
|
||||
this.addSpecs = function(specs, specFilter) {
|
||||
this.totalSpecCount = specs.length;
|
||||
|
||||
this.views = {
|
||||
specs: {},
|
||||
suites: {}
|
||||
};
|
||||
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
var spec = specs[i];
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
|
||||
if (specFilter(spec)) {
|
||||
this.runningSpecCount++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.specComplete = function(spec) {
|
||||
this.completeSpecCount++;
|
||||
|
||||
if (isUndefined(this.views.specs[spec.id])) {
|
||||
this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
|
||||
}
|
||||
|
||||
var specView = this.views.specs[spec.id];
|
||||
|
||||
switch (specView.status()) {
|
||||
case 'passed':
|
||||
this.passedCount++;
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.failedCount++;
|
||||
break;
|
||||
|
||||
case 'skipped':
|
||||
this.skippedCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
specView.refresh();
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
this.suiteComplete = function(suite) {
|
||||
var suiteView = this.views.suites[suite.id];
|
||||
if (isUndefined(suiteView)) {
|
||||
return;
|
||||
}
|
||||
suiteView.refresh();
|
||||
};
|
||||
|
||||
this.refresh = function() {
|
||||
|
||||
if (isUndefined(this.resultsMenu)) {
|
||||
this.createResultsMenu();
|
||||
}
|
||||
|
||||
// currently running UI
|
||||
if (isUndefined(this.runningAlert)) {
|
||||
this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
|
||||
dom.alert.appendChild(this.runningAlert);
|
||||
}
|
||||
this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
|
||||
|
||||
// skipped specs UI
|
||||
if (isUndefined(this.skippedAlert)) {
|
||||
this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
|
||||
}
|
||||
|
||||
this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.skippedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.skippedAlert);
|
||||
}
|
||||
|
||||
// passing specs UI
|
||||
if (isUndefined(this.passedAlert)) {
|
||||
this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
|
||||
}
|
||||
this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
|
||||
|
||||
// failing specs UI
|
||||
if (isUndefined(this.failedAlert)) {
|
||||
this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
|
||||
}
|
||||
this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
|
||||
|
||||
if (this.failedCount === 1 && isDefined(dom.alert)) {
|
||||
dom.alert.appendChild(this.failedAlert);
|
||||
dom.alert.appendChild(this.resultsMenu);
|
||||
}
|
||||
|
||||
// summary info
|
||||
this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
|
||||
this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
|
||||
};
|
||||
|
||||
this.complete = function() {
|
||||
dom.alert.removeChild(this.runningAlert);
|
||||
|
||||
this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
|
||||
|
||||
if (this.failedCount === 0) {
|
||||
dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
|
||||
} else {
|
||||
showDetails();
|
||||
}
|
||||
|
||||
dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function showDetails() {
|
||||
if (dom.reporter.className.search(/showDetails/) === -1) {
|
||||
dom.reporter.className += " showDetails";
|
||||
}
|
||||
}
|
||||
|
||||
function isUndefined(obj) {
|
||||
return typeof obj === 'undefined';
|
||||
}
|
||||
|
||||
function isDefined(obj) {
|
||||
return !isUndefined(obj);
|
||||
}
|
||||
|
||||
function specPluralizedFor(count) {
|
||||
var str = count + " spec";
|
||||
if (count > 1) {
|
||||
str += "s"
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
|
||||
|
||||
|
||||
jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
|
||||
this.spec = spec;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.symbol = this.createDom('li', { className: 'pending' });
|
||||
this.dom.symbolSummary.appendChild(this.symbol);
|
||||
|
||||
this.summary = this.createDom('div', { className: 'specSummary' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.description)
|
||||
);
|
||||
|
||||
this.detail = this.createDom('div', { className: 'specDetail' },
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
|
||||
title: this.spec.getFullName()
|
||||
}, this.spec.getFullName())
|
||||
);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.spec);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
|
||||
this.symbol.className = this.status();
|
||||
|
||||
switch (this.status()) {
|
||||
case 'skipped':
|
||||
break;
|
||||
|
||||
case 'passed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
this.appendSummaryToSuiteDiv();
|
||||
this.appendFailureDetail();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
|
||||
this.summary.className += ' ' + this.status();
|
||||
this.appendToSummary(this.spec, this.summary);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
|
||||
this.detail.className += ' ' + this.status();
|
||||
|
||||
var resultItems = this.spec.results().getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
this.detail.appendChild(messagesDiv);
|
||||
this.dom.details.appendChild(this.detail);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
|
||||
this.suite = suite;
|
||||
this.dom = dom;
|
||||
this.views = views;
|
||||
|
||||
this.element = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
|
||||
);
|
||||
|
||||
this.appendToSummary(this.suite, this.element);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.status = function() {
|
||||
return this.getSpecStatus(this.suite);
|
||||
};
|
||||
|
||||
jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
|
||||
this.element.className += " " + this.status();
|
||||
};
|
||||
|
||||
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
|
||||
|
||||
/* @deprecated Use jasmine.HtmlReporter instead
|
||||
*/
|
||||
jasmine.TrivialReporter = function(doc) {
|
||||
this.document = doc || document;
|
||||
this.suiteDivs = {};
|
||||
this.logRunningSpecs = false;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) { el.appendChild(child); }
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
||||
var showPassed, showSkipped;
|
||||
|
||||
this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
|
||||
this.createDom('div', { className: 'banner' },
|
||||
this.createDom('div', { className: 'logo' },
|
||||
this.createDom('span', { className: 'title' }, "Jasmine"),
|
||||
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
||||
this.createDom('div', { className: 'options' },
|
||||
"Show ",
|
||||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
||||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
||||
)
|
||||
),
|
||||
|
||||
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
||||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
||||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
||||
);
|
||||
|
||||
this.document.body.appendChild(this.outerDiv);
|
||||
|
||||
var suites = runner.suites();
|
||||
for (var i = 0; i < suites.length; i++) {
|
||||
var suite = suites[i];
|
||||
var suiteDiv = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
||||
this.suiteDivs[suite.id] = suiteDiv;
|
||||
var parentDiv = this.outerDiv;
|
||||
if (suite.parentSuite) {
|
||||
parentDiv = this.suiteDivs[suite.parentSuite.id];
|
||||
}
|
||||
parentDiv.appendChild(suiteDiv);
|
||||
}
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
var self = this;
|
||||
showPassed.onclick = function(evt) {
|
||||
if (showPassed.checked) {
|
||||
self.outerDiv.className += ' show-passed';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
||||
}
|
||||
};
|
||||
|
||||
showSkipped.onclick = function(evt) {
|
||||
if (showSkipped.checked) {
|
||||
self.outerDiv.className += ' show-skipped';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
||||
var results = runner.results();
|
||||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
|
||||
this.runnerDiv.setAttribute("class", className);
|
||||
//do it twice for IE
|
||||
this.runnerDiv.setAttribute("className", className);
|
||||
var specs = runner.specs();
|
||||
var specCount = 0;
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
if (this.specFilter(specs[i])) {
|
||||
specCount++;
|
||||
}
|
||||
}
|
||||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
||||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
||||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
||||
|
||||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
||||
var results = suite.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.totalCount === 0) { // todo: change this to check results.skipped
|
||||
status = 'skipped';
|
||||
}
|
||||
this.suiteDivs[suite.id].className += " " + status;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
|
||||
if (this.logRunningSpecs) {
|
||||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
||||
var results = spec.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
||||
title: spec.getFullName()
|
||||
}, spec.description));
|
||||
|
||||
|
||||
var resultItems = results.getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
specDiv.appendChild(messagesDiv);
|
||||
}
|
||||
|
||||
this.suiteDivs[spec.suite.id].appendChild(specDiv);
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.getLocation = function() {
|
||||
return this.document.location;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
||||
var paramMap = {};
|
||||
var params = this.getLocation().search.substring(1).split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
if (!paramMap.spec) {
|
||||
return true;
|
||||
}
|
||||
return spec.getFullName().indexOf(paramMap.spec) === 0;
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
|
||||
|
||||
#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
|
||||
#HTMLReporter a { text-decoration: none; }
|
||||
#HTMLReporter a:hover { text-decoration: underline; }
|
||||
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
|
||||
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
|
||||
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#HTMLReporter .version { color: #aaaaaa; }
|
||||
#HTMLReporter .banner { margin-top: 14px; }
|
||||
#HTMLReporter .duration { color: #aaaaaa; float: right; }
|
||||
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
|
||||
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
|
||||
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
|
||||
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
|
||||
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
|
||||
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
|
||||
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
|
||||
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
|
||||
#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
|
||||
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
|
||||
#HTMLReporter .runningAlert { background-color: #666666; }
|
||||
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
|
||||
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
|
||||
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
|
||||
#HTMLReporter .passingAlert { background-color: #a6b779; }
|
||||
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
|
||||
#HTMLReporter .failingAlert { background-color: #cf867e; }
|
||||
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
|
||||
#HTMLReporter .results { margin-top: 14px; }
|
||||
#HTMLReporter #details { display: none; }
|
||||
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
|
||||
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter.showDetails .summary { display: none; }
|
||||
#HTMLReporter.showDetails #details { display: block; }
|
||||
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
|
||||
#HTMLReporter .summary { margin-top: 14px; }
|
||||
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
|
||||
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
|
||||
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
|
||||
#HTMLReporter .description + .suite { margin-top: 0; }
|
||||
#HTMLReporter .suite { margin-top: 14px; }
|
||||
#HTMLReporter .suite a { color: #333333; }
|
||||
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
|
||||
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
|
||||
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
|
||||
#HTMLReporter .resultMessage span.result { display: block; }
|
||||
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
|
||||
|
||||
#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
|
||||
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
|
||||
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
|
||||
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
|
||||
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
|
||||
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
|
||||
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
|
||||
#TrivialReporter .runner.running { background-color: yellow; }
|
||||
#TrivialReporter .options { text-align: right; font-size: .8em; }
|
||||
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
|
||||
#TrivialReporter .suite .suite { margin: 5px; }
|
||||
#TrivialReporter .suite.passed { background-color: #dfd; }
|
||||
#TrivialReporter .suite.failed { background-color: #fdd; }
|
||||
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
|
||||
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
|
||||
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
|
||||
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
|
||||
#TrivialReporter .spec.skipped { background-color: #bbb; }
|
||||
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
|
||||
#TrivialReporter .passed { background-color: #cfc; display: none; }
|
||||
#TrivialReporter .failed { background-color: #fbb; }
|
||||
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
|
||||
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
|
||||
#TrivialReporter .resultMessage .mismatch { color: black; }
|
||||
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
|
||||
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
|
||||
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
|
||||
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
|
||||
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -23,23 +23,132 @@ test_that("Repeated names for selectInput and radioButtons choices", {
|
||||
|
||||
# Select input
|
||||
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'), selectize = FALSE)
|
||||
expect_equal(format(x), '<label class="control-label" for="id">label</label>
|
||||
<select id="id"><option value="x1" selected>a</option>\n<option value="x2">a</option>\n<option value="x3">b</option></select>')
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<select id="id" class="form-control"><option value="x1" selected>a</option>\n<option value="x2">a</option>\n<option value="x3">b</option></select>',
|
||||
format(x)
|
||||
))
|
||||
|
||||
|
||||
# Radio buttons
|
||||
x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
|
||||
choices <- x$children
|
||||
|
||||
expect_equal(choices[[2]][[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]][[1]]$children[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[2]][[1]]$children[[1]]$attribs$checked, 'checked')
|
||||
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked')
|
||||
|
||||
expect_equal(choices[[2]][[2]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]][[2]]$children[[1]]$attribs$value, 'x2')
|
||||
expect_equal(choices[[2]][[2]]$children[[1]]$attribs$checked, NULL)
|
||||
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'x2')
|
||||
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL)
|
||||
|
||||
expect_equal(choices[[2]][[3]]$children[[2]]$children[[1]], 'b')
|
||||
expect_equal(choices[[2]][[3]]$children[[1]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[2]][[3]]$children[[1]]$attribs$checked, NULL)
|
||||
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], 'b')
|
||||
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL)
|
||||
})
|
||||
|
||||
|
||||
test_that("Choices are correctly assigned names", {
|
||||
# Unnamed vector
|
||||
expect_identical(
|
||||
choicesWithNames(c("a","b","3")),
|
||||
list(a="a", b="b", "3"="3")
|
||||
)
|
||||
# Unnamed list
|
||||
expect_identical(
|
||||
choicesWithNames(list("a","b",3)),
|
||||
list(a="a", b="b", "3"=3)
|
||||
)
|
||||
# Vector, with some named, some not
|
||||
expect_identical(
|
||||
choicesWithNames(c(A="a", "b", C="3", "4")),
|
||||
list(A="a", "b"="b", C="3", "4"="4")
|
||||
)
|
||||
# List, with some named, some not
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=3, 4)),
|
||||
list(A="a", "b"="b", C=3, "4"=4)
|
||||
)
|
||||
# List, named, with a sub-vector
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=c("d", "e"))),
|
||||
list(A="a", B="b", C=list(d="d", e="e"))
|
||||
)
|
||||
# List, named, with sublist
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=list("d", "e"))),
|
||||
list(A="a", B="b", C=list(d="d", e="e"))
|
||||
)
|
||||
# List, named, with a named sub-vector of length 1
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=c(D="d"))),
|
||||
list(A="a", B="b", C=list(D="d"))
|
||||
)
|
||||
# List, some named, with sublist
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=list("d", E="e"))),
|
||||
list(A="a", b="b", C=list(d="d", E="e"))
|
||||
)
|
||||
# Deeper nesting
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=list(D=list("e", "f"), G=c(H="h", "i")))),
|
||||
list(A="a", b="b", C=list(D=list(e="e", f="f"), G=list(H="h", i="i")))
|
||||
)
|
||||
# Error when sublist is unnamed
|
||||
expect_error(choicesWithNames(list(A="a", "b", list(1,2))))
|
||||
})
|
||||
|
||||
|
||||
test_that("selectOptions returns correct HTML", {
|
||||
# None selected
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", "b")), list()),
|
||||
HTML("<option value=\"a\">a</option>\n<option value=\"b\">b</option>")
|
||||
)
|
||||
# One selected
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", "b")), "a"),
|
||||
HTML("<option value=\"a\" selected>a</option>\n<option value=\"b\">b</option>")
|
||||
)
|
||||
# One selected, with named items
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list(A="a", B="b")), "a"),
|
||||
HTML("<option value=\"a\" selected>A</option>\n<option value=\"b\">B</option>")
|
||||
)
|
||||
# Two selected, with optgroup
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", B=list("c", D="d"))), c("a", "d")),
|
||||
HTML("<option value=\"a\" selected>a</option>\n<optgroup label=\"B\">\n<option value=\"c\">c</option>\n<option value=\"d\" selected>D</option>\n</optgroup>")
|
||||
)
|
||||
|
||||
# Escape HTML in strings
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("<A>"="a", B="b")), "a"),
|
||||
HTML("<option value=\"a\" selected><A></option>\n<option value=\"b\">B</option>")
|
||||
)
|
||||
})
|
||||
|
||||
test_that("selectInput selects items by default", {
|
||||
# None specified as selected (defaults to first)
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<option value="a" selected>',
|
||||
selectInput('x', 'x', list("a", "b"))
|
||||
))
|
||||
|
||||
# Nested list (optgroup)
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<option value="a" selected>',
|
||||
selectInput('x', 'x', list(A=list("a", "b"), "c"))
|
||||
))
|
||||
|
||||
# Nothing selected when choices=NULL
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<select id="x" class="form-control"></select>',
|
||||
format(selectInput('x', NULL, NULL, selectize = FALSE))
|
||||
))
|
||||
|
||||
# None specified as selected. With multiple=TRUE, none selected by default.
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<option value="a">',
|
||||
selectInput('x', 'x', list("a", "b"), multiple = TRUE)
|
||||
))
|
||||
})
|
||||
|
||||
@@ -7,10 +7,8 @@ test_that("unreferenced observers are garbage collected", {
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
|
||||
function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(attr(obs, ".xData"),
|
||||
function(e) obs_removed <<- TRUE)
|
||||
reg.finalizer(.subset2(vals,'impl'), function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(obs, function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
@@ -42,10 +40,8 @@ test_that("suspended observers are garbage collected", {
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
|
||||
function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(attr(obs, ".xData"),
|
||||
function(e) obs_removed <<- TRUE)
|
||||
reg.finalizer(.subset2(vals,'impl'), function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(obs, function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ test_that("Date converts to date", {
|
||||
x <- "2013/01/01"
|
||||
class(x) <- "shiny.date"
|
||||
handler <- inputHandlers$get('shiny.date')
|
||||
expect_identical(
|
||||
expect_identical(
|
||||
handler(x), as.Date(unclass(x))
|
||||
)
|
||||
})
|
||||
@@ -24,7 +24,7 @@ test_that("List of dates converts to vector", {
|
||||
x <- list("2013/01/01", "2014/01/01")
|
||||
class(x) <- "shiny.date"
|
||||
handler <- inputHandlers$get('shiny.date')
|
||||
expect_identical(
|
||||
expect_identical(
|
||||
handler(x), as.Date(unlist(x))
|
||||
)
|
||||
})
|
||||
@@ -41,7 +41,7 @@ test_that("Matrix converts list of lists to matrix", {
|
||||
test_that("Nulls are not converted to NAs in parsing", {
|
||||
msg <- charToRaw("{\"method\":\"init\",\"data\":{\"obs\":500,\"nullObs\":null}}")
|
||||
expect_identical(
|
||||
decodeMessage(msg),
|
||||
list(method="init", data=list(obs=500, nullObs=NULL))
|
||||
decodeMessage(msg),
|
||||
list(method="init", data=list(obs=500L, nullObs=NULL))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
237
inst/tests/test-plot-coordmap.R
Normal file
237
inst/tests/test-plot-coordmap.R
Normal file
@@ -0,0 +1,237 @@
|
||||
context("plot-coordmap")
|
||||
library(ggplot2)
|
||||
|
||||
# Sort a list by the names of its keys
|
||||
sortList <- function(x) {
|
||||
x[sort(names(x))]
|
||||
}
|
||||
|
||||
test_that("ggplot coordmap", {
|
||||
dat <- data.frame(xvar = c(0, 5), yvar = c(10, 20))
|
||||
|
||||
tmpfile <- tempfile("test-shiny", fileext = ".png")
|
||||
on.exit(rm(tmpfile))
|
||||
|
||||
# Basic scatterplot
|
||||
p <- ggplot(dat, aes(xvar, yvar)) + geom_point() +
|
||||
scale_x_continuous(expand = c(0, 0)) +
|
||||
scale_y_continuous(expand = c(0, 0))
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p, 1)
|
||||
dev.off()
|
||||
|
||||
# Check mapping vars
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar"))
|
||||
# Check domain
|
||||
expect_equal(
|
||||
sortList(m[[1]]$domain),
|
||||
sortList(list(left=0, right=5, bottom=10, top=20))
|
||||
)
|
||||
|
||||
|
||||
# Scatterplot where aes() is declared in geom
|
||||
p <- ggplot(dat, aes(xvar)) + geom_point(aes(y=yvar))
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p, 1)
|
||||
dev.off()
|
||||
|
||||
# Check mapping vars
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar"))
|
||||
|
||||
|
||||
# Plot with computed variable (histogram)
|
||||
p <- ggplot(dat, aes(xvar)) + geom_histogram(binwidth=1)
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p, 1)
|
||||
dev.off()
|
||||
|
||||
# Check mapping vars - no value for y
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = NULL))
|
||||
})
|
||||
|
||||
|
||||
test_that("ggplot coordmap with facet_wrap", {
|
||||
dat <- data.frame(xvar = c(0, 5, 10), yvar = c(10, 20, 30),
|
||||
g = c("a", "b", "c"))
|
||||
|
||||
tmpfile <- tempfile("test-shiny", fileext = ".png")
|
||||
on.exit(rm(tmpfile))
|
||||
|
||||
# facet_wrap
|
||||
p <- ggplot(dat, aes(xvar, yvar)) + geom_point() +
|
||||
scale_x_continuous(expand = c(0, 0)) +
|
||||
scale_y_continuous(expand = c(0, 0)) +
|
||||
facet_wrap(~ g, ncol = 2)
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p, 1)
|
||||
dev.off()
|
||||
|
||||
# Should have 3 panels
|
||||
expect_equal(length(m), 3)
|
||||
expect_equal(m[[1]]$panel, 1)
|
||||
expect_equal(m[[1]]$row, 1)
|
||||
expect_equal(m[[1]]$col, 1)
|
||||
expect_equal(m[[2]]$panel, 2)
|
||||
expect_equal(m[[2]]$row, 1)
|
||||
expect_equal(m[[2]]$col, 2)
|
||||
expect_equal(m[[3]]$panel, 3)
|
||||
expect_equal(m[[3]]$row, 2)
|
||||
expect_equal(m[[3]]$col, 1)
|
||||
|
||||
# Check mapping vars
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar", panelvar1 = "g"))
|
||||
expect_equal(m[[1]]$mapping, m[[2]]$mapping)
|
||||
expect_equal(m[[2]]$mapping, m[[3]]$mapping)
|
||||
# Check domain
|
||||
expect_equal(
|
||||
sortList(m[[1]]$domain),
|
||||
sortList(list(left=0, right=10, bottom=10, top=30))
|
||||
)
|
||||
expect_equal(sortList(m[[1]]$domain), sortList(m[[2]]$domain))
|
||||
expect_equal(sortList(m[[2]]$domain), sortList(m[[3]]$domain))
|
||||
|
||||
# Check panel vars
|
||||
factor_vals <- dat$g
|
||||
expect_equal(m[[1]]$panel_vars, list(panelvar1 = factor_vals[1]))
|
||||
expect_equal(m[[2]]$panel_vars, list(panelvar1 = factor_vals[2]))
|
||||
expect_equal(m[[3]]$panel_vars, list(panelvar1 = factor_vals[3]))
|
||||
})
|
||||
|
||||
|
||||
test_that("ggplot coordmap with facet_grid", {
|
||||
dat <- data.frame(xvar = c(0, 5, 10), yvar = c(10, 20, 30),
|
||||
g = c("a", "b", "c"))
|
||||
|
||||
tmpfile <- tempfile("test-shiny", fileext = ".png")
|
||||
on.exit(rm(tmpfile))
|
||||
|
||||
p <- ggplot(dat, aes(xvar, yvar)) + geom_point() +
|
||||
scale_x_continuous(expand = c(0, 0)) +
|
||||
scale_y_continuous(expand = c(0, 0))
|
||||
|
||||
# facet_grid horizontal
|
||||
p1 <- p + facet_grid(. ~ g)
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p1, 1)
|
||||
dev.off()
|
||||
|
||||
# Should have 3 panels
|
||||
expect_equal(length(m), 3)
|
||||
expect_equal(m[[1]]$panel, 1)
|
||||
expect_equal(m[[1]]$row, 1)
|
||||
expect_equal(m[[1]]$col, 1)
|
||||
expect_equal(m[[2]]$panel, 2)
|
||||
expect_equal(m[[2]]$row, 1)
|
||||
expect_equal(m[[2]]$col, 2)
|
||||
expect_equal(m[[3]]$panel, 3)
|
||||
expect_equal(m[[3]]$row, 1)
|
||||
expect_equal(m[[3]]$col, 3)
|
||||
|
||||
# Check mapping vars
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar", panelvar1 = "g"))
|
||||
expect_equal(m[[1]]$mapping, m[[2]]$mapping)
|
||||
expect_equal(m[[2]]$mapping, m[[3]]$mapping)
|
||||
# Check domain
|
||||
expect_equal(
|
||||
sortList(m[[1]]$domain),
|
||||
sortList(list(left=0, right=10, bottom=10, top=30))
|
||||
)
|
||||
expect_equal(sortList(m[[1]]$domain), sortList(m[[2]]$domain))
|
||||
expect_equal(sortList(m[[2]]$domain), sortList(m[[3]]$domain))
|
||||
|
||||
# Check panel vars
|
||||
factor_vals <- dat$g
|
||||
expect_equal(m[[1]]$panel_vars, list(panelvar1 = factor_vals[1]))
|
||||
expect_equal(m[[2]]$panel_vars, list(panelvar1 = factor_vals[2]))
|
||||
expect_equal(m[[3]]$panel_vars, list(panelvar1 = factor_vals[3]))
|
||||
|
||||
|
||||
# facet_grid vertical
|
||||
p1 <- p + facet_grid(g ~ .)
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p1, 1)
|
||||
dev.off()
|
||||
|
||||
# Should have 3 panels
|
||||
expect_equal(length(m), 3)
|
||||
expect_equal(m[[1]]$panel, 1)
|
||||
expect_equal(m[[1]]$row, 1)
|
||||
expect_equal(m[[1]]$col, 1)
|
||||
expect_equal(m[[2]]$panel, 2)
|
||||
expect_equal(m[[2]]$row, 2)
|
||||
expect_equal(m[[2]]$col, 1)
|
||||
expect_equal(m[[3]]$panel, 3)
|
||||
expect_equal(m[[3]]$row, 3)
|
||||
expect_equal(m[[3]]$col, 1)
|
||||
|
||||
# Check mapping vars
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar", panelvar1 = "g"))
|
||||
expect_equal(m[[1]]$mapping, m[[2]]$mapping)
|
||||
expect_equal(m[[2]]$mapping, m[[3]]$mapping)
|
||||
# Check domain
|
||||
expect_equal(
|
||||
sortList(m[[1]]$domain),
|
||||
sortList(list(left=0, right=10, bottom=10, top=30))
|
||||
)
|
||||
expect_equal(sortList(m[[1]]$domain), sortList(m[[2]]$domain))
|
||||
expect_equal(sortList(m[[2]]$domain), sortList(m[[3]]$domain))
|
||||
|
||||
# Check panel vars
|
||||
factor_vals <- dat$g
|
||||
expect_equal(m[[1]]$panel_vars, list(panelvar1 = factor_vals[1]))
|
||||
expect_equal(m[[2]]$panel_vars, list(panelvar1 = factor_vals[2]))
|
||||
expect_equal(m[[3]]$panel_vars, list(panelvar1 = factor_vals[3]))
|
||||
})
|
||||
|
||||
|
||||
test_that("ggplot coordmap with 2D facet_grid", {
|
||||
dat <- data.frame(xvar = c(0, 5, 10, 15), yvar = c(10, 20, 30, 40),
|
||||
g = c("a", "b"), h = c("i", "j"))
|
||||
|
||||
tmpfile <- tempfile("test-shiny", fileext = ".png")
|
||||
on.exit(rm(tmpfile))
|
||||
|
||||
p <- ggplot(dat, aes(xvar, yvar)) + geom_point() +
|
||||
scale_x_continuous(expand = c(0, 0)) +
|
||||
scale_y_continuous(expand = c(0, 0))
|
||||
|
||||
p1 <- p + facet_grid(g ~ h)
|
||||
png(tmpfile)
|
||||
m <- getGgplotCoordmap(p1, 1)
|
||||
dev.off()
|
||||
|
||||
# Should have 4 panels
|
||||
expect_equal(length(m), 4)
|
||||
expect_equal(m[[1]]$panel, 1)
|
||||
expect_equal(m[[1]]$row, 1)
|
||||
expect_equal(m[[1]]$col, 1)
|
||||
expect_equal(m[[2]]$panel, 2)
|
||||
expect_equal(m[[2]]$row, 1)
|
||||
expect_equal(m[[2]]$col, 2)
|
||||
expect_equal(m[[3]]$panel, 3)
|
||||
expect_equal(m[[3]]$row, 2)
|
||||
expect_equal(m[[3]]$col, 1)
|
||||
expect_equal(m[[4]]$panel, 4)
|
||||
expect_equal(m[[4]]$row, 2)
|
||||
expect_equal(m[[4]]$col, 2)
|
||||
|
||||
# Check mapping vars
|
||||
expect_equal(m[[1]]$mapping, list(x = "xvar", y = "yvar", panelvar1 = "h", panelvar2 = "g"))
|
||||
expect_equal(m[[1]]$mapping, m[[2]]$mapping)
|
||||
expect_equal(m[[2]]$mapping, m[[3]]$mapping)
|
||||
expect_equal(m[[4]]$mapping, m[[4]]$mapping)
|
||||
# Check domain
|
||||
expect_equal(
|
||||
sortList(m[[1]]$domain),
|
||||
sortList(list(left=0, right=15, bottom=10, top=40))
|
||||
)
|
||||
expect_equal(sortList(m[[1]]$domain), sortList(m[[2]]$domain))
|
||||
expect_equal(sortList(m[[2]]$domain), sortList(m[[3]]$domain))
|
||||
expect_equal(sortList(m[[3]]$domain), sortList(m[[4]]$domain))
|
||||
|
||||
# Check panel vars
|
||||
expect_equal(m[[1]]$panel_vars, list(panelvar1 = dat$h[1], panelvar2 = dat$g[1]))
|
||||
expect_equal(m[[2]]$panel_vars, list(panelvar1 = dat$h[2], panelvar2 = dat$g[1]))
|
||||
expect_equal(m[[3]]$panel_vars, list(panelvar1 = dat$h[1], panelvar2 = dat$g[2]))
|
||||
expect_equal(m[[4]]$panel_vars, list(panelvar1 = dat$h[2], panelvar2 = dat$g[2]))
|
||||
})
|
||||
@@ -490,30 +490,30 @@ test_that("names() and reactiveValuesToList()", {
|
||||
|
||||
test_that("Observer pausing works", {
|
||||
values <- reactiveValues(a=1)
|
||||
|
||||
|
||||
funcA <- reactive({
|
||||
values$a
|
||||
})
|
||||
|
||||
|
||||
obsB <- observe({
|
||||
funcA()
|
||||
})
|
||||
|
||||
|
||||
# Important: suspend() only affects observer at invalidation time
|
||||
|
||||
|
||||
# Observers are invalidated at creation time, so it will run once regardless
|
||||
# of being suspended
|
||||
obsB$suspend()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
|
||||
# When resuming, if nothing changed, don't do anything
|
||||
obsB$resume()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
|
||||
# Make sure suspended observers do not flush, but do invalidate
|
||||
obsB_invalidated <- FALSE
|
||||
obsB$onInvalidate(function() {obsB_invalidated <<- TRUE})
|
||||
@@ -523,20 +523,20 @@ test_that("Observer pausing works", {
|
||||
expect_equal(obsB_invalidated, TRUE)
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
|
||||
obsB$resume()
|
||||
values$a <- 2.5
|
||||
obsB$suspend()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 2)
|
||||
expect_equal(execCount(obsB), 2)
|
||||
|
||||
|
||||
values$a <- 3
|
||||
flushReact()
|
||||
|
||||
expect_equal(execCount(funcA), 2)
|
||||
expect_equal(execCount(obsB), 2)
|
||||
|
||||
|
||||
# If onInvalidate() is added _after_ obsB is suspended and the values$a
|
||||
# changes, then it shouldn't get run (onInvalidate runs on invalidation, not
|
||||
# on flush)
|
||||
@@ -554,11 +554,11 @@ test_that("Observer pausing works", {
|
||||
test_that("suspended/resumed observers run at most once", {
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
obs <- observe(function() {
|
||||
obs <- observe({
|
||||
values$A
|
||||
})
|
||||
expect_equal(execCount(obs), 0)
|
||||
|
||||
|
||||
# First flush should run obs once
|
||||
flushReact()
|
||||
expect_equal(execCount(obs), 1)
|
||||
@@ -658,9 +658,9 @@ test_that("Observer priorities are respected", {
|
||||
observe(results <<- c(results, 20), priority=20L)
|
||||
observe(results <<- c(results, 21), priority=20)
|
||||
observe(results <<- c(results, 22), priority=20L)
|
||||
|
||||
|
||||
flushReact()
|
||||
|
||||
|
||||
expect_identical(results, c(30, 20, 21, 22, 10))
|
||||
})
|
||||
|
||||
@@ -670,7 +670,7 @@ test_that("reactivePoll and reactiveFileReader", {
|
||||
write.csv(cars, file=path, row.names=FALSE)
|
||||
rfr <- reactiveFileReader(100, NULL, path, read.csv)
|
||||
expect_equal(isolate(rfr()), cars)
|
||||
|
||||
|
||||
write.csv(rbind(cars, cars), file=path, row.names=FALSE)
|
||||
Sys.sleep(0.15)
|
||||
timerCallbacks$executeElapsed()
|
||||
@@ -821,3 +821,115 @@ test_that("maskReactiveContext blocks use of reactives", {
|
||||
expect_identical(maskReactiveContext(isolate(vals$x)), 123)
|
||||
expect_identical(isolate(maskReactiveContext(isolate(vals$x))), 123)
|
||||
})
|
||||
|
||||
test_that("Flush completes even when errors occur", {
|
||||
vals <- reactiveValues(x = 1)
|
||||
|
||||
r <- reactive({
|
||||
if (vals$x == 0) stop("x is zero!")
|
||||
else vals$x
|
||||
})
|
||||
|
||||
# Set up counters
|
||||
n11 <- n12 <- n21 <- n22 <- 0
|
||||
|
||||
observe({
|
||||
n11 <<- n11 + 1
|
||||
r()
|
||||
n12 <<- n12 + 1
|
||||
})
|
||||
observe({
|
||||
n21 <<- n21 + 1
|
||||
r()
|
||||
n22 <<- n22 + 1
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_true(all(c(n11, n12, n21, n22) == 1))
|
||||
|
||||
# Trigger an error
|
||||
vals$x <- 0
|
||||
# Errors in reactive are translated to warnings in observers by default
|
||||
expect_warning(flushReact())
|
||||
# Both observers should run up until the reactive that errors
|
||||
expect_true(all(c(n11, n12, n21, n22) == c(2,1,2,1)))
|
||||
|
||||
# Nothing should happen on next flush
|
||||
flushReact()
|
||||
expect_true(all(c(n11, n12, n21, n22) == c(2,1,2,1)))
|
||||
})
|
||||
|
||||
|
||||
test_that("Alternate error handler function", {
|
||||
vals <- reactiveValues(x = 0)
|
||||
|
||||
r <- reactive({
|
||||
stop("error in reactive!")
|
||||
})
|
||||
|
||||
observe({
|
||||
r()
|
||||
})
|
||||
observe({
|
||||
r()
|
||||
})
|
||||
|
||||
# Alternate observer error handler - increment counter
|
||||
ec <- 0
|
||||
options(shiny.observer.error = function(e, label = NULL, domain = NULL) {
|
||||
ec <<- ec + 1
|
||||
})
|
||||
on.exit(options(shiny.observer.error = NULL))
|
||||
|
||||
flushReact()
|
||||
expect_identical(ec, 2)
|
||||
})
|
||||
|
||||
test_that("event handling helpers take correct dependencies", {
|
||||
vals <- reactiveValues(action = NULL, x = 1)
|
||||
|
||||
o1_count <- 0
|
||||
o1 <- observeEvent(vals$action, {
|
||||
vals$x
|
||||
o1_count <<- o1_count + 1
|
||||
})
|
||||
o2_count <- 0
|
||||
o2 <- observeEvent(ignoreNULL = FALSE, vals$action, {
|
||||
vals$x
|
||||
o2_count <<- o2_count + 1
|
||||
})
|
||||
r1 <- eventReactive(vals$action, {
|
||||
vals$x
|
||||
})
|
||||
r2 <- eventReactive(ignoreNULL = FALSE, vals$action, {
|
||||
vals$x
|
||||
})
|
||||
|
||||
flushReact()
|
||||
|
||||
expect_error(isolate(r1()))
|
||||
expect_identical(isolate(r2()), 1)
|
||||
expect_equal(o1_count, 0)
|
||||
expect_equal(o2_count, 1)
|
||||
expect_equal(execCount(o1), 1)
|
||||
expect_equal(execCount(o2), 1)
|
||||
|
||||
vals$x <- 2
|
||||
flushReact()
|
||||
|
||||
expect_error(isolate(r1()))
|
||||
expect_identical(isolate(r2()), 1)
|
||||
expect_equal(o1_count, 0)
|
||||
expect_equal(o2_count, 1)
|
||||
expect_equal(execCount(o1), 1)
|
||||
expect_equal(execCount(o2), 1)
|
||||
|
||||
vals$action <- 1
|
||||
flushReact()
|
||||
expect_identical(isolate(r1()), 2)
|
||||
expect_identical(isolate(r2()), 2)
|
||||
expect_equal(o1_count, 1)
|
||||
expect_equal(o2_count, 2)
|
||||
expect_equal(execCount(o1), 2)
|
||||
expect_equal(execCount(o2), 2)
|
||||
})
|
||||
|
||||
40
inst/tests/test-stack.R
Normal file
40
inst/tests/test-stack.R
Normal file
@@ -0,0 +1,40 @@
|
||||
context("Stack")
|
||||
|
||||
test_that("Basic operations", {
|
||||
s <- Stack$new()
|
||||
expect_identical(s$size(), 0L)
|
||||
|
||||
s$push(5)$push(6)$push(NULL)$push(list(a=1,b=2))
|
||||
|
||||
expect_identical(s$pop(), list(a=1,b=2))
|
||||
expect_identical(s$peek(), NULL)
|
||||
expect_identical(s$pop(), NULL)
|
||||
expect_identical(s$size(), 2L)
|
||||
|
||||
# as_list() returns in the order that they were inserted
|
||||
expect_identical(s$as_list(), list(5, 6))
|
||||
})
|
||||
|
||||
|
||||
test_that("Pushing multiple", {
|
||||
s <- Stack$new()
|
||||
s$push(1,2,3)
|
||||
s$push(4,5, .list=list(6,list(7,8)))
|
||||
s$push(9,10)
|
||||
expect_identical(s$as_list(), list(1,2,3,4,5,6,list(7,8),9,10))
|
||||
expect_identical(s$pop(), 10)
|
||||
expect_identical(s$pop(), 9)
|
||||
expect_identical(s$pop(), list(7,8))
|
||||
})
|
||||
|
||||
|
||||
test_that("Popping from empty stack", {
|
||||
s <- Stack$new()
|
||||
expect_null(s$pop())
|
||||
expect_null(s$pop())
|
||||
expect_null(s$peek())
|
||||
expect_identical(s$size(), 0L)
|
||||
|
||||
s$push(5)$push(6)
|
||||
expect_identical(s$as_list(), list(5, 6))
|
||||
})
|
||||
@@ -6,7 +6,7 @@ test_that("All man pages have an entry in staticdocs/index.r", {
|
||||
return()
|
||||
}
|
||||
# Known not to be indexed
|
||||
known_unindexed <- c("shiny-package")
|
||||
known_unindexed <- c("shiny-package", "knitr_methods", "knitr_methods_htmltools")
|
||||
|
||||
indexed_topics <- local({
|
||||
result <- character(0)
|
||||
|
||||
@@ -1,432 +0,0 @@
|
||||
context("tags")
|
||||
|
||||
test_that("Basic tag writing works", {
|
||||
expect_equal(as.character(tagList("hi")), HTML("hi"))
|
||||
expect_equal(
|
||||
as.character(tagList("one", "two", tagList("three"))),
|
||||
HTML("one\ntwo\nthree"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one")),
|
||||
HTML("<b>one</b>"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one", "two")),
|
||||
HTML("<b>\n one\n two\n</b>"))
|
||||
expect_equal(
|
||||
as.character(tagList(list("one"))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(list(tagList("one")))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(tags$br(), "one")),
|
||||
HTML("<br/>\none"))
|
||||
})
|
||||
|
||||
|
||||
test_that("withTags works", {
|
||||
output_tags <- tags$div(class = "myclass",
|
||||
tags$h3("header"),
|
||||
tags$p("text here")
|
||||
)
|
||||
output_withhtml <- withTags(
|
||||
div(class = "myclass",
|
||||
h3("header"),
|
||||
p("text here")
|
||||
)
|
||||
)
|
||||
expect_identical(output_tags, output_withhtml)
|
||||
|
||||
|
||||
# Check that current environment is searched
|
||||
x <- 100
|
||||
expect_identical(tags$p(x), withTags(p(x)))
|
||||
|
||||
# Just to make sure, run it in a function, which has its own environment
|
||||
foo <- function() {
|
||||
y <- 100
|
||||
withTags(p(y))
|
||||
}
|
||||
expect_identical(tags$p(100), foo())
|
||||
})
|
||||
|
||||
|
||||
test_that("HTML escaping in tags", {
|
||||
# Regular text is escaped
|
||||
expect_equivalent(format(div("<a&b>")), "<div><a&b></div>")
|
||||
|
||||
# Text in HTML() isn't escaped
|
||||
expect_equivalent(format(div(HTML("<a&b>"))), "<div><a&b></div>")
|
||||
|
||||
# Text in a property is escaped
|
||||
expect_equivalent(format(div(class = "<a&b>", "text")),
|
||||
'<div class="<a&b>">text</div>')
|
||||
|
||||
# HTML() has no effect in a property like 'class'
|
||||
expect_equivalent(format(div(class = HTML("<a&b>"), "text")),
|
||||
'<div class="<a&b>">text</div>')
|
||||
})
|
||||
|
||||
|
||||
test_that("Adding child tags", {
|
||||
tag_list <- list(tags$p("tag1"), tags$b("tag2"), tags$i("tag3"))
|
||||
|
||||
# Creating nested tags by calling the tag$div function and passing a list
|
||||
t1 <- tags$div(class="foo", tag_list)
|
||||
expect_equal(length(t1$children), 1)
|
||||
expect_equal(length(t1$children[[1]]), 3)
|
||||
expect_equal(t1$children[[1]][[1]]$name, "p")
|
||||
expect_equal(t1$children[[1]][[1]]$children[[1]], "tag1")
|
||||
expect_equal(t1$children[[1]][[2]]$name, "b")
|
||||
expect_equal(t1$children[[1]][[2]]$children[[1]], "tag2")
|
||||
expect_equal(t1$children[[1]][[3]]$name, "i")
|
||||
expect_equal(t1$children[[1]][[3]]$children[[1]], "tag3")
|
||||
|
||||
|
||||
# div tag used as starting point for tests below
|
||||
div_tag <- tags$div(class="foo")
|
||||
|
||||
# Appending each child
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChild(t2, tag_list[[2]])
|
||||
t2 <- tagAppendChild(t2, tag_list[[3]])
|
||||
t2a <- do.call(tags$div, c(tag_list, class="foo"))
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
|
||||
# tagSetChildren, using list argument
|
||||
t2 <- tagSetChildren(div_tag, list = tag_list)
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagSetChildren, using ... arguments
|
||||
t2 <- tagSetChildren(div_tag, tag_list[[1]], tag_list[[2]], tag_list[[3]])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagSetChildren, using ... and list arguments
|
||||
t2 <- tagSetChildren(div_tag, tag_list[[1]], list = tag_list[2:3])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagSetChildren overwrites existing children
|
||||
t2 <- tagAppendChild(div_tag, p("should replace this tag"))
|
||||
t2 <- tagSetChildren(div_tag, list = tag_list)
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
|
||||
# tagAppendChildren, using list argument
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, list = tag_list[2:3])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagAppendChildren, using ... arguments
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, tag_list[[2]], tag_list[[3]])
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagAppendChildren, using ... and list arguments
|
||||
t2 <- tagAppendChild(div_tag, tag_list[[1]])
|
||||
t2 <- tagAppendChildren(t2, tag_list[[2]], list = list(tag_list[[3]]))
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
# tagAppendChildren can start with no children
|
||||
t2 <- tagAppendChildren(div_tag, list = tag_list)
|
||||
expect_identical(t2a, t2)
|
||||
|
||||
|
||||
# tagSetChildren preserves attributes
|
||||
x <- tagSetChildren(div(), HTML("text"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
|
||||
# tagAppendChildren preserves attributes
|
||||
x <- tagAppendChildren(div(), HTML("text"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
})
|
||||
|
||||
|
||||
test_that("Creating simple tags", {
|
||||
# Empty tag
|
||||
expect_identical(
|
||||
div(),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list()),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# Tag with text
|
||||
expect_identical(
|
||||
div("text"),
|
||||
structure(
|
||||
list(name = "div", attribs = list(), children = list("text")),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# NULL attributes are dropped
|
||||
expect_identical(
|
||||
div(a = NULL, b = "value"),
|
||||
div(b = "value")
|
||||
)
|
||||
|
||||
# NULL children are dropped
|
||||
expect_identical(
|
||||
renderTags(div("foo", NULL, list(NULL, list(NULL, "bar"))))$html,
|
||||
renderTags(div("foo", "bar"))$html
|
||||
)
|
||||
|
||||
# Numbers are coerced to strings
|
||||
expect_identical(
|
||||
renderTags(div(1234))$html,
|
||||
renderTags(div("1234"))$html
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
test_that("Creating nested tags", {
|
||||
# Simple version
|
||||
# Note that the $children list should not have a names attribute
|
||||
expect_identical(
|
||||
div(class="foo", list("a", "b")),
|
||||
structure(
|
||||
list(name = "div",
|
||||
attribs = structure(list(class = "foo"), .Names = "class"),
|
||||
children = list(list("a", "b"))),
|
||||
.Names = c("name", "attribs", "children"),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
)
|
||||
|
||||
# More complex version
|
||||
t1 <- withTags(
|
||||
div(class = "foo",
|
||||
p("child tag"),
|
||||
list(
|
||||
p("in-list child tag 1"),
|
||||
"in-list character string",
|
||||
p(),
|
||||
p("in-list child tag 2")
|
||||
),
|
||||
"character string",
|
||||
1234
|
||||
)
|
||||
)
|
||||
|
||||
# t1 should be identical to this data structure.
|
||||
# The nested list should be flattened, and non-tag, non-strings should be
|
||||
# converted to strings
|
||||
t1_full <- structure(
|
||||
list(
|
||||
name = "div",
|
||||
attribs = list(class = "foo"),
|
||||
children = list(
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("child tag")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("in-list child tag 1")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
"in-list character string",
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list()),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
structure(list(name = "p",
|
||||
attribs = list(),
|
||||
children = list("in-list child tag 2")),
|
||||
class = "shiny.tag"
|
||||
),
|
||||
"character string",
|
||||
"1234"
|
||||
)
|
||||
),
|
||||
class = "shiny.tag"
|
||||
)
|
||||
|
||||
expect_identical(renderTags(t1)$html, renderTags(t1_full)$html)
|
||||
})
|
||||
|
||||
test_that("Attributes are preserved", {
|
||||
# HTML() adds an attribute to the data structure (note that this is
|
||||
# different from the 'attribs' field in the list)
|
||||
x <- HTML("<tag>&&</tag>")
|
||||
expect_identical(attr(x, "html"), TRUE)
|
||||
expect_equivalent(format(x), "<tag>&&</tag>")
|
||||
|
||||
# Make sure attributes are preserved when wrapped in other tags
|
||||
x <- div(HTML("<tag>&&</tag>"))
|
||||
expect_equivalent(x$children[[1]], HTML("<tag>&&</tag>"))
|
||||
expect_identical(attr(x$children[[1]], "html"), TRUE)
|
||||
expect_equivalent(format(x), "<div><tag>&&</tag></div>")
|
||||
|
||||
# Deeper nesting
|
||||
x <- div(p(HTML("<tag>&&</tag>")))
|
||||
expect_equivalent(x$children[[1]]$children[[1]], HTML("<tag>&&</tag>"))
|
||||
expect_identical(attr(x$children[[1]]$children[[1]], "html"), TRUE)
|
||||
expect_equivalent(format(x), "<div>\n <p><tag>&&</tag></p>\n</div>")
|
||||
})
|
||||
|
||||
|
||||
test_that("Flattening a list of tags", {
|
||||
# Flatten a nested list
|
||||
nested <- list(
|
||||
"a1",
|
||||
list(
|
||||
"b1",
|
||||
list("c1", "c2"),
|
||||
list(),
|
||||
"b2",
|
||||
list("d1", "d2")
|
||||
),
|
||||
"a2"
|
||||
)
|
||||
flat <- list("a1", "b1", "c1", "c2", "b2", "d1", "d2", "a2")
|
||||
expect_identical(flattenTags(nested), flat)
|
||||
|
||||
# no-op for flat lists
|
||||
expect_identical(flattenTags(list(a="1", "b")), list(a="1", "b"))
|
||||
|
||||
# numbers are coerced to character
|
||||
expect_identical(flattenTags(list(a=1, "b")), list(a="1", "b"))
|
||||
|
||||
# empty list results in empty list
|
||||
expect_identical(flattenTags(list()), list())
|
||||
|
||||
# preserve attributes
|
||||
nested <- list("txt1", list(structure("txt2", prop="prop2")))
|
||||
flat <- list("txt1",
|
||||
structure("txt2", prop="prop2"))
|
||||
expect_identical(flattenTags(nested), flat)
|
||||
})
|
||||
|
||||
test_that("Head and singleton behavior", {
|
||||
result <- renderTags(tagList(
|
||||
tags$head(singleton("hello"))
|
||||
))
|
||||
|
||||
expect_identical(result$html, HTML(""))
|
||||
expect_identical(result$head, HTML(" hello"))
|
||||
expect_identical(result$singletons, "60eed8231e688bcba7c275c58dd2e3b4dacb61f0")
|
||||
|
||||
# Ensure that "hello" actually behaves like a singleton
|
||||
result2 <- renderTags(tagList(
|
||||
tags$head(singleton("hello"))
|
||||
), singletons = result$singletons)
|
||||
|
||||
expect_identical(result$singletons, result2$singletons)
|
||||
expect_identical(result2$head, HTML(""))
|
||||
expect_identical(result2$html, HTML(""))
|
||||
|
||||
result3 <- renderTags(tagList(
|
||||
tags$head(singleton("hello"), singleton("hello"))
|
||||
))
|
||||
expect_identical(result$singletons, result3$singletons)
|
||||
expect_identical(result3$head, HTML(" hello"))
|
||||
|
||||
# Ensure that singleton can be applied to lists, not just tags
|
||||
result4 <- renderTags(list(singleton(list("hello")), singleton(list("hello"))))
|
||||
expect_identical(result4$singletons, "d7319e3f14167c4c056dd7aa0b274c83fe2291f6")
|
||||
expect_identical(result4$html, renderTags(HTML("hello"))$html)
|
||||
})
|
||||
|
||||
test_that("Factors are treated as characters, not numbers", {
|
||||
myfactors <- factor(LETTERS[1:3])
|
||||
expect_identical(
|
||||
as.character(tags$option(value=myfactors[[1]], myfactors[[1]])),
|
||||
HTML('<option value="A">A</option>')
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
as.character(tags$option(value=myfactors[[1]], value='B', value=3, myfactors[[1]])),
|
||||
HTML('<option value="A B 3">A</option>')
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Unusual list contents are rendered correctly", {
|
||||
expect_identical(renderTags(list(NULL)), renderTags(HTML("")))
|
||||
expect_identical(renderTags(list(100)), renderTags(HTML("100")))
|
||||
expect_identical(renderTags(list(list(100))), renderTags(HTML("100")))
|
||||
expect_identical(renderTags(list(list())), renderTags(HTML("")))
|
||||
expect_identical(renderTags(NULL), renderTags(HTML("")))
|
||||
})
|
||||
|
||||
test_that("Low-level singleton manipulation methods", {
|
||||
# Default arguments drop singleton duplicates and strips the
|
||||
# singletons it keeps of the singleton bit
|
||||
result1 <- takeSingletons(tags$div(
|
||||
singleton(tags$head(tags$script("foo"))),
|
||||
singleton(tags$head(tags$script("foo")))
|
||||
))
|
||||
|
||||
expect_identical(result1$ui$children[[2]], NULL)
|
||||
expect_false(is(result1$ui$children[[1]], "shiny.singleton"))
|
||||
|
||||
# desingleton=FALSE means drop duplicates but don't strip the
|
||||
# singleton bit
|
||||
result2 <- takeSingletons(tags$div(
|
||||
singleton(tags$head(tags$script("foo"))),
|
||||
singleton(tags$head(tags$script("foo")))
|
||||
), desingleton=FALSE)
|
||||
|
||||
expect_identical(result2$ui$children[[2]], NULL)
|
||||
expect_is(result2$ui$children[[1]], "shiny.singleton")
|
||||
|
||||
result3 <- surroundSingletons(tags$div(
|
||||
singleton(tags$script("foo")),
|
||||
singleton(tags$script("foo"))
|
||||
))
|
||||
|
||||
expect_identical(
|
||||
renderTags(result3)$html,
|
||||
HTML("<div>
|
||||
<!--SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
<script>foo</script>
|
||||
<!--/SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
<!--SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
<script>foo</script>
|
||||
<!--/SHINY.SINGLETON[58b302d493b50acb75e4a5606687cadccdf902d8]-->
|
||||
</div>")
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Indenting can be controlled/suppressed", {
|
||||
expect_identical(
|
||||
renderTags(tags$div("a", "b"))$html,
|
||||
HTML("<div>\n a\n b\n</div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tags$div("a", "b")),
|
||||
"<div>\n a\n b\n</div>"
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
renderTags(tags$div("a", "b"), indent = 2)$html,
|
||||
HTML(" <div>\n a\n b\n </div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tags$div("a", "b"), indent = 2),
|
||||
" <div>\n a\n b\n </div>"
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
renderTags(tags$div("a", "b"), indent = FALSE)$html,
|
||||
HTML("<div>\na\nb\n</div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tags$div("a", "b"), indent = FALSE),
|
||||
"<div>\na\nb\n</div>"
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
renderTags(tagList(tags$div("a", "b")), indent = FALSE)$html,
|
||||
HTML("<div>\na\nb\n</div>")
|
||||
)
|
||||
expect_identical(
|
||||
format(tagList(tags$div("a", "b")), indent = FALSE),
|
||||
"<div>\na\nb\n</div>"
|
||||
)
|
||||
})
|
||||
@@ -17,4 +17,26 @@ test_that("Query string parsing", {
|
||||
|
||||
# Should be the same with or without leading question mark
|
||||
expect_identical(parseQueryString("?foo=1&bar=b"), parseQueryString("foo=1&bar=b"))
|
||||
|
||||
|
||||
# Nested and non-nested query strings
|
||||
expect_identical(
|
||||
parseQueryString("a[i1][j1]=x&b[i1][j1]=y&b[i2][j1]=z"),
|
||||
list(
|
||||
"a[i1][j1]" = "x",
|
||||
"b[i1][j1]" = "y",
|
||||
"b[i2][j1]" = "z"
|
||||
)
|
||||
)
|
||||
|
||||
expect_identical(
|
||||
parseQueryString("a[i1][j1]=x&b[i1][j1]=y&b[i2][j1]=z", nested = TRUE),
|
||||
list(
|
||||
a = list(i1 = list(j1 = "x")),
|
||||
b = list(
|
||||
i1 = list(j1 = "y"),
|
||||
i2 = list(j1 = "z")
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -21,6 +21,10 @@ test_that("Setting process-wide seed doesn't affect private randomness", {
|
||||
test_that("Resetting private seed doesn't result in dupes", {
|
||||
.globals$ownSeed <- NULL
|
||||
id3 <- createUniqueId(4)
|
||||
# Make sure we let enough time pass that reinitializing the seed is
|
||||
# going to result in a different value. This is especially required
|
||||
# on Windows.
|
||||
Sys.sleep(1)
|
||||
set.seed(0)
|
||||
.globals$ownSeed <- NULL
|
||||
id4 <- createUniqueId(4)
|
||||
@@ -84,3 +88,32 @@ test_that("need() works as expected", {
|
||||
expect_null(need(c(NA, NA, TRUE), FALSE))
|
||||
expect_null(need(c(FALSE, FALSE, TRUE), FALSE))
|
||||
})
|
||||
|
||||
test_that("anyUnnamed works as expected", {
|
||||
expect_false(anyUnnamed(list()))
|
||||
expect_true(anyUnnamed(list(1,2,3)))
|
||||
expect_true(anyUnnamed(list(A = 1,2,3)))
|
||||
expect_false(anyUnnamed(list(A = 1,B = 2,C = 3)))
|
||||
|
||||
# List with named elements removed
|
||||
x <- list(A = 1, B = 2, 3, 4)
|
||||
x <- x[3:4]
|
||||
expect_true(anyUnnamed(x))
|
||||
})
|
||||
|
||||
test_that("Callbacks fire in predictable order", {
|
||||
cb <- Callbacks$new()
|
||||
|
||||
x <- numeric(0)
|
||||
cb$register(function() {
|
||||
x <<- c(x, 1)
|
||||
})
|
||||
cb$register(function() {
|
||||
x <<- c(x, 2)
|
||||
})
|
||||
cb$register(function() {
|
||||
x <<- c(x, 3)
|
||||
})
|
||||
cb$invoke()
|
||||
expect_equal(x, c(1, 2, 3))
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<ul>
|
||||
<li><code>www/index.html</code></li>
|
||||
<li><code>ui.R</code></li>
|
||||
<li><code>app.R</code></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1109
inst/www/shared/bootstrap/css/bootstrap-responsive.css
vendored
1109
inst/www/shared/bootstrap/css/bootstrap-responsive.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
470
inst/www/shared/bootstrap/css/bootstrap-theme.css
vendored
Normal file
470
inst/www/shared/bootstrap/css/bootstrap-theme.css
vendored
Normal file
@@ -0,0 +1,470 @@
|
||||
/*!
|
||||
* Bootstrap v3.3.1 (http://getbootstrap.com)
|
||||
* Copyright 2011-2014 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
.btn-default,
|
||||
.btn-primary,
|
||||
.btn-success,
|
||||
.btn-info,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-primary:active,
|
||||
.btn-success:active,
|
||||
.btn-info:active,
|
||||
.btn-warning:active,
|
||||
.btn-danger:active,
|
||||
.btn-default.active,
|
||||
.btn-primary.active,
|
||||
.btn-success.active,
|
||||
.btn-info.active,
|
||||
.btn-warning.active,
|
||||
.btn-danger.active {
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
.btn-default .badge,
|
||||
.btn-primary .badge,
|
||||
.btn-success .badge,
|
||||
.btn-info .badge,
|
||||
.btn-warning .badge,
|
||||
.btn-danger .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-image: none;
|
||||
}
|
||||
.btn-default {
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dbdbdb;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.btn-default:hover,
|
||||
.btn-default:focus {
|
||||
background-color: #e0e0e0;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-default.active {
|
||||
background-color: #e0e0e0;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
.btn-default:disabled,
|
||||
.btn-default[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-primary {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus {
|
||||
background-color: #265a88;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-primary:active,
|
||||
.btn-primary.active {
|
||||
background-color: #265a88;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary:disabled,
|
||||
.btn-primary[disabled] {
|
||||
background-color: #265a88;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success:hover,
|
||||
.btn-success:focus {
|
||||
background-color: #419641;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-success:active,
|
||||
.btn-success.active {
|
||||
background-color: #419641;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success:disabled,
|
||||
.btn-success[disabled] {
|
||||
background-color: #419641;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info:hover,
|
||||
.btn-info:focus {
|
||||
background-color: #2aabd2;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-info:active,
|
||||
.btn-info.active {
|
||||
background-color: #2aabd2;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info:disabled,
|
||||
.btn-info[disabled] {
|
||||
background-color: #2aabd2;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning:hover,
|
||||
.btn-warning:focus {
|
||||
background-color: #eb9316;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-warning:active,
|
||||
.btn-warning.active {
|
||||
background-color: #eb9316;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning:disabled,
|
||||
.btn-warning[disabled] {
|
||||
background-color: #eb9316;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger:hover,
|
||||
.btn-danger:focus {
|
||||
background-color: #c12e2a;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-danger:active,
|
||||
.btn-danger.active {
|
||||
background-color: #c12e2a;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger:disabled,
|
||||
.btn-danger[disabled] {
|
||||
background-color: #c12e2a;
|
||||
background-image: none;
|
||||
}
|
||||
.thumbnail,
|
||||
.img-thumbnail {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.dropdown-menu > li > a:hover,
|
||||
.dropdown-menu > li > a:focus {
|
||||
background-color: #e8e8e8;
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.dropdown-menu > .active > a,
|
||||
.dropdown-menu > .active > a:hover,
|
||||
.dropdown-menu > .active > a:focus {
|
||||
background-color: #2e6da4;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.navbar-default {
|
||||
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
||||
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.navbar-default .navbar-nav > .open > a,
|
||||
.navbar-default .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
|
||||
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.navbar-brand,
|
||||
.navbar-nav > li > a {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
|
||||
}
|
||||
.navbar-inverse {
|
||||
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
|
||||
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.navbar-inverse .navbar-nav > .open > a,
|
||||
.navbar-inverse .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
|
||||
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
||||
}
|
||||
.navbar-inverse .navbar-brand,
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
.navbar-static-top,
|
||||
.navbar-fixed-top,
|
||||
.navbar-fixed-bottom {
|
||||
border-radius: 0;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||
color: #fff;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
||||
}
|
||||
.alert-success {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b2dba1;
|
||||
}
|
||||
.alert-info {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #9acfea;
|
||||
}
|
||||
.alert-warning {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #f5e79e;
|
||||
}
|
||||
.alert-danger {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dca7a7;
|
||||
}
|
||||
.progress {
|
||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-striped {
|
||||
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
}
|
||||
.list-group {
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.list-group-item.active,
|
||||
.list-group-item.active:hover,
|
||||
.list-group-item.active:focus {
|
||||
text-shadow: 0 -1px 0 #286090;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #2b669a;
|
||||
}
|
||||
.list-group-item.active .badge,
|
||||
.list-group-item.active:hover .badge,
|
||||
.list-group-item.active:focus .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.panel {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
}
|
||||
.panel-default > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-primary > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-success > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-info > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-warning > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-danger > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.well {
|
||||
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dcdcdc;
|
||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-theme.css.map */
|
||||
5
inst/www/shared/bootstrap/css/bootstrap-theme.min.css
vendored
Normal file
5
inst/www/shared/bootstrap/css/bootstrap-theme.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
10899
inst/www/shared/bootstrap/css/bootstrap.css
vendored
10899
inst/www/shared/bootstrap/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
12
inst/www/shared/bootstrap/css/bootstrap.min.css
vendored
12
inst/www/shared/bootstrap/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
BIN
inst/www/shared/bootstrap/fonts/glyphicons-halflings-regular.eot
Normal file
BIN
inst/www/shared/bootstrap/fonts/glyphicons-halflings-regular.eot
Normal file
Binary file not shown.
229
inst/www/shared/bootstrap/fonts/glyphicons-halflings-regular.svg
Normal file
229
inst/www/shared/bootstrap/fonts/glyphicons-halflings-regular.svg
Normal file
@@ -0,0 +1,229 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
|
||||
<font-face units-per-em="1200" ascent="960" descent="-240" />
|
||||
<missing-glyph horiz-adv-x="500" />
|
||||
<glyph />
|
||||
<glyph />
|
||||
<glyph unicode="
" />
|
||||
<glyph unicode=" " />
|
||||
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
|
||||
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
|
||||
<glyph unicode=" " />
|
||||
<glyph unicode=" " horiz-adv-x="652" />
|
||||
<glyph unicode=" " horiz-adv-x="1304" />
|
||||
<glyph unicode=" " horiz-adv-x="652" />
|
||||
<glyph unicode=" " horiz-adv-x="1304" />
|
||||
<glyph unicode=" " horiz-adv-x="434" />
|
||||
<glyph unicode=" " horiz-adv-x="326" />
|
||||
<glyph unicode=" " horiz-adv-x="217" />
|
||||
<glyph unicode=" " horiz-adv-x="217" />
|
||||
<glyph unicode=" " horiz-adv-x="163" />
|
||||
<glyph unicode=" " horiz-adv-x="260" />
|
||||
<glyph unicode=" " horiz-adv-x="72" />
|
||||
<glyph unicode=" " horiz-adv-x="260" />
|
||||
<glyph unicode=" " horiz-adv-x="326" />
|
||||
<glyph unicode="€" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
|
||||
<glyph unicode="−" d="M200 400h900v300h-900v-300z" />
|
||||
<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
|
||||
<glyph unicode="☁" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
|
||||
<glyph unicode="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
|
||||
<glyph unicode="✏" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
|
||||
<glyph unicode="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
|
||||
<glyph unicode="" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
|
||||
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
|
||||
<glyph unicode="" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
|
||||
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
|
||||
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
|
||||
<glyph unicode="" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
|
||||
<glyph unicode="" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
|
||||
<glyph unicode="" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
|
||||
<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
|
||||
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
|
||||
<glyph unicode="" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
|
||||
<glyph unicode="" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
|
||||
<glyph unicode="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
|
||||
<glyph unicode="" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
|
||||
<glyph unicode="" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
|
||||
<glyph unicode="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
|
||||
<glyph unicode="" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
|
||||
<glyph unicode="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
|
||||
<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
|
||||
<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
|
||||
<glyph unicode="" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
|
||||
<glyph unicode="" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
|
||||
<glyph unicode="" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
|
||||
<glyph unicode="" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
|
||||
<glyph unicode="" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
|
||||
<glyph unicode="" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
|
||||
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
|
||||
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
|
||||
<glyph unicode="" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
|
||||
<glyph unicode="" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
|
||||
<glyph unicode="" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
|
||||
<glyph unicode="" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
|
||||
<glyph unicode="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
|
||||
<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
|
||||
<glyph unicode="" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
|
||||
<glyph unicode="" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
|
||||
<glyph unicode="" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
|
||||
<glyph unicode="" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
|
||||
<glyph unicode="" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
|
||||
<glyph unicode="" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
|
||||
<glyph unicode="" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
|
||||
<glyph unicode="" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
|
||||
<glyph unicode="" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
|
||||
<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
|
||||
<glyph unicode="" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
|
||||
<glyph unicode="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
|
||||
<glyph unicode="" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
|
||||
<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" />
|
||||
<glyph unicode="" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||
<glyph unicode="" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||
<glyph unicode="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
|
||||
<glyph unicode="" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
|
||||
<glyph unicode="" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
|
||||
<glyph unicode="" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
|
||||
<glyph unicode="" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
|
||||
<glyph unicode="" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
|
||||
<glyph unicode="" d="M0 547l600 453v-300h600v-300h-600v-301z" />
|
||||
<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
|
||||
<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
|
||||
<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" />
|
||||
<glyph unicode="" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
|
||||
<glyph unicode="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
|
||||
<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
|
||||
<glyph unicode="" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
|
||||
<glyph unicode="" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
|
||||
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
|
||||
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
|
||||
<glyph unicode="" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
|
||||
<glyph unicode="" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
|
||||
<glyph unicode="" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
|
||||
<glyph unicode="" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
|
||||
<glyph unicode="" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
|
||||
<glyph unicode="" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
|
||||
<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
|
||||
<glyph unicode="" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
|
||||
<glyph unicode="" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
|
||||
<glyph unicode="" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
|
||||
<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
|
||||
<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
|
||||
<glyph unicode="" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
|
||||
<glyph unicode="" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
|
||||
<glyph unicode="" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
|
||||
<glyph unicode="" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
|
||||
<glyph unicode="" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
|
||||
<glyph unicode="" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
|
||||
<glyph unicode="" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
|
||||
<glyph unicode="" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
|
||||
<glyph unicode="" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
|
||||
<glyph unicode="" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
|
||||
<glyph unicode="" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
|
||||
<glyph unicode="" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
|
||||
<glyph unicode="" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
|
||||
<glyph unicode="" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
|
||||
<glyph unicode="" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
|
||||
<glyph unicode="" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
|
||||
<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
|
||||
<glyph unicode="" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
|
||||
<glyph unicode="" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
|
||||
<glyph unicode="" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
|
||||
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
|
||||
<glyph unicode="" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
|
||||
<glyph unicode="" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
|
||||
<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
|
||||
<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
|
||||
<glyph unicode="" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
|
||||
<glyph unicode="" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
|
||||
<glyph unicode="" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
|
||||
<glyph unicode="" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
|
||||
<glyph unicode="" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
|
||||
<glyph unicode="" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
|
||||
<glyph unicode="" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
|
||||
<glyph unicode="" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
|
||||
<glyph unicode="" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
|
||||
<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
|
||||
<glyph unicode="" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
|
||||
<glyph unicode="" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 62 KiB |
BIN
inst/www/shared/bootstrap/fonts/glyphicons-halflings-regular.ttf
Normal file
BIN
inst/www/shared/bootstrap/fonts/glyphicons-halflings-regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
3504
inst/www/shared/bootstrap/js/bootstrap.js
vendored
3504
inst/www/shared/bootstrap/js/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
11
inst/www/shared/bootstrap/js/bootstrap.min.js
vendored
11
inst/www/shared/bootstrap/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
7
inst/www/shared/bootstrap/shim/html5shiv.min.js
vendored
Normal file
7
inst/www/shared/bootstrap/shim/html5shiv.min.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
// Only run this code in IE 8
|
||||
if (!!window.navigator.userAgent.match("MSIE 8")) {
|
||||
!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.2",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b)}(this,document);
|
||||
};
|
||||
8
inst/www/shared/bootstrap/shim/respond.min.js
vendored
Normal file
8
inst/www/shared/bootstrap/shim/respond.min.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
|
||||
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
|
||||
* */
|
||||
|
||||
// Only run this code in IE 8
|
||||
if (!!window.navigator.userAgent.match("MSIE 8")) {
|
||||
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user