Compare commits

...

17 Commits

Author SHA1 Message Date
Boris Grozev 86181408e6 Poop in, bomb out. 2018-02-09 11:23:47 -06:00
Boris Grozev 2a2e763ca9 Merge branch 'master' into shipit-reactions 2018-02-09 09:36:01 -06:00
Lyubo Marinov aee8a38753 Play reactions on the sender as well 2018-02-09 00:55:44 -06:00
Lyubo Marinov 67d6ca48c7 Don't close the Reactions dialog so we can click multiple times easily 2018-02-09 00:35:19 -06:00
Lyubo Marinov 95cd0e556b Better spacing of the buttons 2018-02-09 00:34:07 -06:00
Lyubo Marinov 024979c88a Handle multiple and repeating reactions 2018-02-09 00:28:03 -06:00
Lyubo Marinov 44b36561b4 Connect the received reactions to redux 1 at a time 2018-02-08 23:55:28 -06:00
Boris Grozev d24cd88a8a Set a tooltip on the reaction button. 2018-02-08 23:07:07 -06:00
Boris Grozev d7c5d4126f Use Yana's emojis. 2018-02-08 23:03:48 -06:00
yanas 56f82b16c2 Play animation once 2018-02-08 22:40:35 -06:00
Lyubo Marinov 75023b4224 Fix eslint errors 2018-02-08 22:23:22 -06:00
yanas 8f1ff9cfc3 Web-only animations 2018-02-08 22:16:17 -06:00
yanas 2d7dd58895 WiP emotion animations 2018-02-08 22:16:16 -06:00
Boris Grozev 550bfc14d7 Sets the selected endpoint in sendReaction. 2018-02-08 20:26:29 -06:00
Boris Grozev 1091ca4974 feat: Adds reaction counts to the speakers table. 2018-02-08 20:20:45 -06:00
Lyubo Marinov b7a7a0c7e1 Send and receive reactions over the network 2018-02-08 19:55:03 -06:00
Lyubo Marinov 8c92a0dee7 Hollow Web reactions buttons 2018-02-08 18:58:27 -06:00
53 changed files with 872 additions and 9 deletions

View File

@ -0,0 +1,97 @@
.animation-target {
-webkit-animation: animation 5800ms linear both;
animation: animation 5800ms linear both;
}
/* Generated with Bounce.js. Edit at https://goo.gl/N4iuvQ */
@-webkit-keyframes animation {
0% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1); }
0.55% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.621, 1, 0, 0, 0, 0, 1, 0, -872.394, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.621, 1, 0, 0, 0, 0, 1, 0, -872.394, 0, 0, 1); }
1.1% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.829, 1, 0, 0, 0, 0, 1, 0, -758.053, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.829, 1, 0, 0, 0, 0, 1, 0, -758.053, 0, 0, 1); }
2.19% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.477, 1, 0, 0, 0, 0, 1, 0, -568.124, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.477, 1, 0, 0, 0, 0, 1, 0, -568.124, 0, 0, 1); }
2.21% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.47, 1, 0, 0, 0, 0, 1, 0, -565.455, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.47, 1, 0, 0, 0, 0, 1, 0, -565.455, 0, 0, 1); }
3.26% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.146, 1, 0, 0, 0, 0, 1, 0, -420.951, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.146, 1, 0, 0, 0, 0, 1, 0, -420.951, 0, 0, 1); }
4.35% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.003, 1, 0, 0, 0, 0, 1, 0, -304.986, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.003, 1, 0, 0, 0, 0, 1, 0, -304.986, 0, 0, 1); }
4.42% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.007, 1, 0, 0, 0, 0, 1, 0, -298.617, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.007, 1, 0, 0, 0, 0, 1, 0, -298.617, 0, 0, 1); }
5.42% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.033, 1, 0, 0, 0, 0, 1, 0, -217.83, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.033, 1, 0, 0, 0, 0, 1, 0, -217.83, 0, 0, 1); }
6.63% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.019, 1, 0, 0, 0, 0, 1, 0, -144.967, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.019, 1, 0, 0, 0, 0, 1, 0, -144.967, 0, 0, 1); }
8.84% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.001, 1, 0, 0, 0, 0, 1, 0, -62.157, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.001, 1, 0, 0, 0, 0, 1, 0, -62.157, 0, 0, 1); }
9.73% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.002, 1, 0, 0, 0, 0, 1, 0, -41.642, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.002, 1, 0, 0, 0, 0, 1, 0, -41.642, 0, 0, 1); }
13.25% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -2.427, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -2.427, 0, 0, 1); }
13.69% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.405, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.405, 0, 0, 1); }
13.79% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
14.05% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.95, -7.992, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.95, -7.992, 0, 1); }
14.41% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.112, -15.312, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.112, -15.312, 0, 1); }
15.04% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.621, -19.275, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.621, -19.275, 0, 1); }
15.81% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 4.842, -15.606, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 4.842, -15.606, 0, 1); }
16.57% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.493, -9.111, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.493, -9.111, 0, 1); }
17.24% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.739, -4.058, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.739, -4.058, 0, 1); }
17.35% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.755, -3.395, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.755, -3.395, 0, 1); }
17.67% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.772, -1.664, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.772, -1.664, 0, 1); }
18.11% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.729, -0.013, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.729, -0.013, 0, 1); }
19.35% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.306, -1.622, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.306, -1.622, 0, 1); }
22.42% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.532, -0.002, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.532, -0.002, 0, 1); }
23.67% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.828, -0.137, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.828, -0.137, 0, 1); }
26.74% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.477, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.477, 0, 0, 1); }
27.98% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.095, -0.011, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.095, -0.011, 0, 1); }
30.93% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.495, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.495, 0, 0, 1); }
31.03% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.481, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.481, 0, 0, 1); }
34% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 514.458, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 514.458, 0, 0, 1); }
36.97% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 878.645, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 878.645, 0, 0, 1); }
39.94% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1052.286, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1052.286, 0, 0, 1); }
42.91% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1093.33, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1093.33, 0, 0, 1); }
45.29% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1078.742, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1078.742, 0, 0, 1); }
50.57% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1020.622, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1020.622, 0, 0, 1); }
58.23% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 993.768, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 993.768, 0, 0, 1); }
68.97% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.903, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.903, 0, 0, 1); }
73.56% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000.416, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000.416, 0, 0, 1); }
88.95% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.972, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.972, 0, 0, 1); }
100% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1); }
}
@keyframes animation {
0% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1); }
0.55% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.621, 1, 0, 0, 0, 0, 1, 0, -872.394, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.621, 1, 0, 0, 0, 0, 1, 0, -872.394, 0, 0, 1); }
1.1% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.829, 1, 0, 0, 0, 0, 1, 0, -758.053, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.829, 1, 0, 0, 0, 0, 1, 0, -758.053, 0, 0, 1); }
2.19% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.477, 1, 0, 0, 0, 0, 1, 0, -568.124, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.477, 1, 0, 0, 0, 0, 1, 0, -568.124, 0, 0, 1); }
2.21% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.47, 1, 0, 0, 0, 0, 1, 0, -565.455, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.47, 1, 0, 0, 0, 0, 1, 0, -565.455, 0, 0, 1); }
3.26% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.146, 1, 0, 0, 0, 0, 1, 0, -420.951, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.146, 1, 0, 0, 0, 0, 1, 0, -420.951, 0, 0, 1); }
4.35% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.003, 1, 0, 0, 0, 0, 1, 0, -304.986, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.003, 1, 0, 0, 0, 0, 1, 0, -304.986, 0, 0, 1); }
4.42% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.007, 1, 0, 0, 0, 0, 1, 0, -298.617, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.007, 1, 0, 0, 0, 0, 1, 0, -298.617, 0, 0, 1); }
5.42% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.033, 1, 0, 0, 0, 0, 1, 0, -217.83, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.033, 1, 0, 0, 0, 0, 1, 0, -217.83, 0, 0, 1); }
6.63% { -webkit-transform: matrix3d(1, 0, 0, 0, -0.019, 1, 0, 0, 0, 0, 1, 0, -144.967, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, -0.019, 1, 0, 0, 0, 0, 1, 0, -144.967, 0, 0, 1); }
8.84% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.001, 1, 0, 0, 0, 0, 1, 0, -62.157, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.001, 1, 0, 0, 0, 0, 1, 0, -62.157, 0, 0, 1); }
9.73% { -webkit-transform: matrix3d(1, 0, 0, 0, 0.002, 1, 0, 0, 0, 0, 1, 0, -41.642, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0.002, 1, 0, 0, 0, 0, 1, 0, -41.642, 0, 0, 1); }
13.25% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -2.427, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -2.427, 0, 0, 1); }
13.69% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.405, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.405, 0, 0, 1); }
13.79% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); }
14.05% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.95, -7.992, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.95, -7.992, 0, 1); }
14.41% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.112, -15.312, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.112, -15.312, 0, 1); }
15.04% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.621, -19.275, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.621, -19.275, 0, 1); }
15.81% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 4.842, -15.606, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 4.842, -15.606, 0, 1); }
16.57% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.493, -9.111, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.493, -9.111, 0, 1); }
17.24% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.739, -4.058, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.739, -4.058, 0, 1); }
17.35% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.755, -3.395, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.755, -3.395, 0, 1); }
17.67% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.772, -1.664, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.772, -1.664, 0, 1); }
18.11% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.729, -0.013, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.729, -0.013, 0, 1); }
19.35% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.306, -1.622, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.306, -1.622, 0, 1); }
22.42% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.532, -0.002, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.532, -0.002, 0, 1); }
23.67% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.828, -0.137, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2.828, -0.137, 0, 1); }
26.74% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.477, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.477, 0, 0, 1); }
27.98% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.095, -0.011, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.095, -0.011, 0, 1); }
30.93% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.495, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.495, 0, 0, 1); }
31.03% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.481, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.481, 0, 0, 1); }
34% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 514.458, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 514.458, 0, 0, 1); }
36.97% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 878.645, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 878.645, 0, 0, 1); }
39.94% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1052.286, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1052.286, 0, 0, 1); }
42.91% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1093.33, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1093.33, 0, 0, 1); }
45.29% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1078.742, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1078.742, 0, 0, 1); }
50.57% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1020.622, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1020.622, 0, 0, 1); }
58.23% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 993.768, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 993.768, 0, 0, 1); }
68.97% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.903, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.903, 0, 0, 1); }
73.56% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000.416, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000.416, 0, 0, 1); }
88.95% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.972, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 999.972, 0, 0, 1); }
100% { -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1); transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1); }
}

101
css/_emoji-icons.scss Normal file
View File

@ -0,0 +1,101 @@
.emoji-icon-background {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
}
.emoji-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: 3em;
line-height: 3em;
z-index: 1000;
}
.emoji-icon:before {
content: "\00a0";
}
.emoji-icon-bomb {
background-image: url(../images/emojis/bomb.png);
}
.emoji-icon-bulb {
background-image: url(../images/emojis/bulb.png);
}
.emoji-icon-clap {
background-image: url(../images/emojis/clap.png);
}
.emoji-icon-fistbump {
background-image: url(../images/emojis/fistbump.png);
}
.emoji-icon-heart {
background-image: url(../images/emojis/heart.png);
}
.emoji-icon-highfive {
background-image: url(../images/emojis/highfive.png);
}
.emoji-icon-perfect {
background-image: url(../images/emojis/perfect.png);
}
.emoji-icon-sleep {
background-image: url(../images/emojis/sleep.png);
}
.emoji-icon-smiley1 {
background-image: url(../images/emojis/smiley1.png);
}
.emoji-icon-smiley2 {
background-image: url(../images/emojis/smiley2.png);
}
.emoji-icon-smiley3 {
background-image: url(../images/emojis/smiley3.png);
}
.emoji-icon-smiley4 {
background-image: url(../images/emojis/smiley4.png);
}
.emoji-icon-smiley5 {
background-image: url(../images/emojis/smiley5.png);
}
.emoji-icon-smiley6 {
background-image: url(../images/emojis/smiley6.png);
}
.emoji-icon-smiley7 {
background-image: url(../images/emojis/smiley7.png);
}
.emoji-icon-smiley8 {
background-image: url(../images/emojis/smiley8.png);
}
.emoji-icon-smiley9 {
background-image: url(../images/emojis/smiley9.png);
}
.emoji-icon-smiley10 {
background-image: url(../images/emojis/smiley10.png);
}
.emoji-icon-smiley11 {
background-image: url(../images/emojis/smiley11.png);
}
.emoji-icon-star {
background-image: url(../images/emojis/star.png);
}
.emoji-icon-thumbsdown {
background-image: url(../images/emojis/thumbsdown.png);
}
.emoji-icon-thumbsup {
background-image: url(../images/emojis/thumbsup.png);
}

21
css/_reactions.scss Normal file
View File

@ -0,0 +1,21 @@
.reactions-dialog {
.reactions-dialog-title {
margin-bottom: 10px;
}
.reactions-dialog-contents {
align-items: center;
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 10px;
min-width: 250px;
}
.reactions-dialog-cell {
display: inline;
height: 40px;
margin: 10px;
width: 40px;
}
}

View File

@ -517,6 +517,14 @@
width: auto;
}
.emotionsCanvas {
bottom: 0;
height: 50%;
max-height: 200px;
position: absolute;
width: 100%;
}
#videoNotAvailableScreen {
text-align: center;
#avatarContainer {

View File

@ -30,6 +30,9 @@
@import 'flag-icon';
@import 'emoji-icons';
@import 'emoji-animations';
/* Modules BEGIN */
@import 'dial-out';
@ -75,5 +78,6 @@
@import 'unsupported-browser/main';
@import 'modals/invite/add-people';
@import 'vertical_filmstrip_overrides';
@import 'reactions';
/* Modules END */

View File

@ -32,14 +32,30 @@
margin: 5px 0;
vertical-align: middle;
}
.speaker-stats-item__poop {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
}
.speaker-stats-item__heart {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
}
.speaker-stats-item__status {
width: 5%;
}
.speaker-stats-item__name {
width: 40%;
width: 30%;
}
.speaker-stats-item__time {
width: 55%;
width: 35%;
}
.speaker-stats-item__poop {
width: 15%;
}
.speaker-stats-item__heart {
width: 15%;
}
.speaker-stats-item:nth-child(even) {
@ -52,4 +68,14 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.speaker-stats-item__poop {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.speaker-stats-item__heart {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

BIN
images/emojis/bomb.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
images/emojis/bulb.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

BIN
images/emojis/clap.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/emojis/fistbump.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

BIN
images/emojis/heart.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

BIN
images/emojis/highfive.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/emojis/perfect.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
images/emojis/poop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
images/emojis/sleep.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
images/emojis/smiley1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
images/emojis/smiley10.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

BIN
images/emojis/smiley11.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

BIN
images/emojis/smiley2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

BIN
images/emojis/smiley3.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/emojis/smiley4.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

BIN
images/emojis/smiley5.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

BIN
images/emojis/smiley6.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

BIN
images/emojis/smiley7.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/emojis/smiley8.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

BIN
images/emojis/smiley9.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

BIN
images/emojis/star.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
images/emojis/thumbsdown.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

BIN
images/emojis/thumbsup.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

View File

@ -43,7 +43,7 @@ var interfaceConfig = {
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup',
// extended toolbar
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'reactions', 'filmstrip' ],
/**
* Main Toolbar Buttons

View File

@ -498,6 +498,7 @@
"standardDefinition": "Standard definition",
"qualityButtonTip": "Change received video quality"
},
"reactionsButtonTip": "Send a reaction",
"dialOut": {
"dial": "Dial",
"dialOut": "Call a #",

5
package-lock.json generated
View File

@ -9683,6 +9683,11 @@
"resolved": "https://registry.npmjs.org/react-addons-text-content/-/react-addons-text-content-0.0.4.tgz",
"integrity": "sha1-0uJZ/clR0diQbAiQIAIQjc6HkuU="
},
"react-animations": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-animations/-/react-animations-1.0.0.tgz",
"integrity": "sha512-ePPpVgdKnNEXm+LP1ww5s3n0JzebBw9QdRfxRqogzeg1PDIn6kf0pmvgeTeVZQXXpGmHImkIeTiaQR1O6xjntA=="
},
"react-clone-referenced-element": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-clone-referenced-element/-/react-clone-referenced-element-1.0.1.tgz",

View File

@ -6,6 +6,7 @@ import React, { Component } from 'react';
import { Watermarks } from '../../base/react';
import { VideoQualityLabel } from '../../video-quality';
import { RecordingLabel } from '../../recording';
import { ReactionsCanvas } from '../../reactions';
declare var interfaceConfig: Object;
@ -79,6 +80,7 @@ export default class LargeVideo extends Component<*> {
{ this.props.hideVideoQualityLabel
? null : <VideoQualityLabel /> }
<RecordingLabel />
<ReactionsCanvas />
</div>
);
}

View File

@ -0,0 +1,11 @@
/**
* The type of (redux) action which signals that a specific reaction has been
* received by the local participant from a specific remote participant.
*
* {
* type: ADD_RECEIVED_REACTION,
* participant: Object,
* reaction: string
* }
*/
export const ADD_RECEIVED_REACTION = Symbol('ADD_RECEIVED_REACTION');

View File

@ -0,0 +1,58 @@
// @flow
import uuid from 'uuid';
import { ADD_RECEIVED_REACTION } from './actionTypes';
/**
* Creates a redux action which signals that a specific reaction has been
* received by the local participant from a specific remote participant.
*
* @param {string} reaction - The reaction which has been received.
* @param {Object} participant - The remote participant who sent the reaction.
* @returns {{
* type: ADD_RECEIVED_REACTION,
* participant: Object,
* reaction: string
* }}
*/
export function addReceivedReaction(reaction: string, participant: Object) {
return {
type: ADD_RECEIVED_REACTION,
participant,
reaction,
uuid: uuid.v4()
};
}
/**
* Sends a specific reaction of the local participant to the remote
* participants.
*
* @param {string} reaction - The reaction of the local participant to send to
* the remote participants.
* @returns {Function}
*/
export function sendReaction(reaction: string) {
// reaction = 'thumbsup', 'heart', etc
return (dispatch: Dispatch, getState: Function) => {
const selectedEndpointId
= getState()['features/base/conference']
.conference.selectedEndpointId;
const payload = {
type: 'reaction',
reaction,
targetEndpoint: selectedEndpointId || 'target'
};
getState()['features/base/conference'].conference.sendTextMessage(
JSON.stringify({
'jitsi-meet-muc-msg-topic': 'xxx',
payload
}));
// FIXME It's not a received reaction so rename the action creator.
dispatch(addReceivedReaction(reaction, /* participant */ undefined));
};
}

View File

@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
// Mobile friendly solution!
// import styled, { keyframes } from 'styled-components';
// import { merge, rollIn, zoomIn } from 'react-animations';
// const tadaFlip = merge(rollIn, zoomIn);
//
// const bounceAnimation = keyframes`${tadaFlip}`;
//
// const BouncySpan = styled.span`
// animation: ${bounceAnimation} 2s;
// animation-direction: alternate;
// `;
/**
* Implements a React {@link Component} to render an emoji icon.
*/
export default class EmojiIcon extends Component {
/**
* {@code Emoji}'s property types.
*
* @static
*/
static propTypes = {
/**
* The css style class name.
*/
className: PropTypes.string,
/**
* The emoji name.
*/
emojiName: PropTypes.string
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const iconClassName
= `emoji-icon emoji-icon-${this.props.emojiName} animation-target`;
// Part of the mobile friendly solution!
// return <BouncySpan className = { iconClassName } />;
return <span className = { iconClassName } />;
}
}

View File

@ -0,0 +1,44 @@
/* @flow */
import React, { Component } from 'react';
import { View } from 'react-native';
import EmojiIcon from './EmojiIcon';
/**
* Base style for the {@code TintedView} component.
*/
const BASE_STYLE = {
alignItems: 'center',
bottom: 0,
justifyContent: 'center',
left: 0,
position: 'absolute',
right: 0,
top: 0
};
/**
* Implements a React {@link Component} which represents the large video (a.k.a.
* the conference participant who is on the local stage) on Web/React.
*
* @extends Component
*/
export default class ReactionsCanvas extends Component<*> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<View style = { BASE_STYLE }>
<EmojiIcon emojiName = 'thumbsup' />
</View>
);
}
}

View File

@ -0,0 +1,75 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import EmojiIcon from './EmojiIcon';
type Props = {
_reactions: Array<Object>
};
/**
* Implements a React {@link Component} which represents the large video (a.k.a.
* the conference participant who is on the local stage) on Web/React.
*
* @extends Component
*/
class ReactionsCanvas extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const reactions = this.props._reactions;
if (!reactions || !reactions.length) {
return null;
}
// FIXME The use of key bellow is somewhat of a hack/workaround for
// EmojiIcon: EmojiIcon doesn't seem to restart its animation upon
// changing its emojiName value. So the key will force a new EmojiIcon
// instance upon different reactions.
return (
<div className = 'emotionsCanvas'>
{
/* eslint-disable react/jsx-wrap-multilines */
reactions.map(
({ reaction, uuid }) =>
<EmojiIcon
emojiName = { reaction }
key = { uuid } />)
/* eslint-enable react/jsx-wrap-multilines */
}
</div>
);
}
}
/**
* Maps (parts of) the redux state to the associated ReactionsCanvas' props.
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _reactions: Array<Object>
* }}
*/
function _mapStateToProps(state) { // eslint-disable-line no-unused-vars
const stateFeatureReactions = state['features/reactions'];
const receivedReactions
= stateFeatureReactions && stateFeatureReactions.receivedReactions;
return {
_reactions: receivedReactions || []
};
}
export default connect(_mapStateToProps)(ReactionsCanvas);

View File

@ -0,0 +1,135 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sendReaction } from '../actions';
type Props = {
_onSendReaction: Function,
onClose: Function
};
/**
* The list of supported reactions (i.e. reaction buttons).
*/
const REACTIONS = [
];
// FIXME Pretend there's a list of supported reactions (i.e. reaction buttons).
REACTIONS.push('heart');
REACTIONS.push('poop');
REACTIONS.push('thumbsup');
REACTIONS.push('thumbsdown');
REACTIONS.push('bulb');
REACTIONS.push('clap');
REACTIONS.push('fistbump');
REACTIONS.push('highfive');
REACTIONS.push('perfect');
REACTIONS.push('sleep');
REACTIONS.push('star');
for (let i = 1; i < 11; ++i) {
REACTIONS.push(`smiley${i}`);
}
/**
* Represents the dialog in the terms of {@link ToolbarButtonWithDialog} which
* renders the list of supported reactions (i.e. reaction buttons).
*/
class ReactionsDialog extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {React$Element}
*/
render() {
return (
<div className = 'reactions-dialog'>
<h3 className = 'reactions-dialog-title'>
Reactions
</h3>
<div className = 'reactions-dialog-contents'>
{ this._renderContents() }
</div>
</div>
);
}
/**
* Handles the click on a reaction (button).
*
* @param {*} reaction - The reaction (button) which was clicked.
* @returns {void}
*/
_onClick(reaction) {
// this.props.onClose();
this.props._onSendReaction(reaction);
}
/**
* Renders the contents of this ReactionsDialog minus its title.
*
* @returns {React$Node}
*/
_renderContents() {
const contents = [];
for (const reaction of REACTIONS) {
/* eslint-disable react/jsx-no-bind */
contents.push(
<img
className = 'reactions-dialog-cell'
onClick = { this._onClick.bind(this, reaction) }
src = { `images/emojis/${reaction}.png` } />
);
/* eslint-enable react/jsx-no-bind */
}
return contents;
}
}
/**
* Maps dispatching of some action to React component props.
*
* @param {Function} dispatch - Redux action dispatcher.
* @private
* @returns {{
* }}
*/
function _mapDispatchToProps(dispatch) {
return {
/**
* Sends a specific reaction of the local participant to the remote
* participants.
*
* @param {string} reaction - The reaction of the local participant to
* send to the remote participants.
* @private
* @returns {void}
*/
_onSendReaction(reaction) {
dispatch(sendReaction(reaction));
}
};
}
/**
* Maps (parts of) the redux state to the associated ReactionsDialog's props.
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* }}
*/
function _mapStateToProps(state) { // eslint-disable-line no-unused-vars
return {
};
}
export default connect(_mapStateToProps, _mapDispatchToProps)(ReactionsDialog);

View File

@ -0,0 +1,46 @@
// @flow
import React, { Component } from 'react';
import { ToolbarButtonWithDialog } from '../../toolbox';
import ReactionsDialog from './ReactionsDialog';
type Props = {
tooltipPosition: *
};
/**
* The {@code ToolbarButton} configuration which describes how
* {@link ReactionsToolbarButton} is to be rendered (by default).
*
* @type {object}
*/
const DEFAULT_BUTTON_CONFIGURATION = {
buttonName: 'reactions',
classNames: [ 'button', 'icon-star-full' ],
enabled: true,
id: 'toolbar_button_reactions',
tooltipKey: 'reactionsButtonTip'
};
/**
* Implements the Web {@code ToolbarButton} which shows the dialog with the list
* of supported reactions (i.e. reaction buttons).
*/
export default class ReactionsToolbarButton extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<ToolbarButtonWithDialog
button = { DEFAULT_BUTTON_CONFIGURATION }
content = { ReactionsDialog }
tooltipPosition = { this.props.tooltipPosition } />
);
}
}

View File

@ -0,0 +1,2 @@
export { default as ReactionsToolbarButton } from './ReactionsToolbarButton';
export { default as ReactionsCanvas } from './ReactionsCanvas';

View File

@ -0,0 +1,12 @@
import { createStyleSheet } from '../../base/styles';
import { merge, tada, flip } from 'react-animations';
const tadaFlip = merge(tada, flip);
export default createStyleSheet({
bounce: {
animationName: tadaFlip,
animationDuration: '10s'
}
});

View File

@ -0,0 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
import './middleware';
import './reducer';

View File

@ -0,0 +1,47 @@
// @flow
import { CONFERENCE_JOINED } from '../base/conference';
import { MiddlewareRegistry } from '../base/redux';
import { addReceivedReaction } from './actions';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_JOINED:
return _conferenceJoined(store, next, action);
}
return next(action);
});
/**
* Notifies the feature app that the action {@link CONFERENCE_JOINED} is being
* dispatched within a specific redux {@code store}.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is
* being dispatched in the specified {@code store}.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _conferenceJoined({ dispatch, getState }, next, action) {
const result = next(action);
getState()['features/base/conference'].conference.on(
'conference.endpoint_message_received',
(participant, message) => {
let payload;
if (message
&& (payload = message.payload)
&& payload.type === 'reaction') {
dispatch(addReceivedReaction(payload.reaction, participant));
}
});
return result;
}

View File

@ -0,0 +1,28 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { ADD_RECEIVED_REACTION } from './actionTypes';
const _INITIAL_STATE = {
receivedReactions: []
};
ReducerRegistry.register(
'features/reactions', (state = _INITIAL_STATE, action) => {
switch (action.type) {
case ADD_RECEIVED_REACTION:
return {
...state,
receivedReactions: [
...state.receivedReactions,
{
reaction: action.reaction,
uuid: action.uuid
}
]
};
}
return state;
});

View File

@ -0,0 +1,40 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
/**
* React component for displaying a count. For example a number of poops.
*
* @extends Component
*/
class Count extends Component {
/**
* TimeElapsed component's property types.
*
* @static
*/
static propTypes = {
/**
* Count
*/
count: PropTypes.number
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { count } = this.props;
return (
<div>
{ count }
</div>
);
}
}
export default Count;

View File

@ -13,6 +13,7 @@ import SpeakerStatsLabels from './SpeakerStatsLabels';
declare var interfaceConfig: Object;
/**
*
* React component for displaying a list of speaker stats.
*
* @extends Component
@ -122,6 +123,8 @@ class SpeakerStats extends Component<*, *> {
const isDominantSpeaker = statsModel.isDominantSpeaker();
const dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime();
const poopCount = statsModel.getPoopCount();
const heartCount = statsModel.getHeartCount();
const hasLeft = statsModel.hasLeft();
let displayName;
@ -144,8 +147,10 @@ class SpeakerStats extends Component<*, *> {
displayName = { displayName }
dominantSpeakerTime = { dominantSpeakerTime }
hasLeft = { hasLeft }
heartCount = { heartCount }
isDominantSpeaker = { isDominantSpeaker }
key = { userId } />
key = { userId }
poopCount = { poopCount } />
);
}

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import TimeElapsed from './TimeElapsed';
import Count from './Count';
/**
* React component for display an individual user's speaker stats.
@ -30,10 +31,14 @@ class SpeakerStatsItem extends Component {
*/
hasLeft: PropTypes.bool,
heartCount: PropTypes.number,
/**
* True if the participant is currently the dominant speaker.
*/
isDominantSpeaker: PropTypes.bool
isDominantSpeaker: PropTypes.bool,
poopCount: PropTypes.number
};
/**
@ -62,6 +67,14 @@ class SpeakerStatsItem extends Component {
<TimeElapsed
time = { this.props.dominantSpeakerTime } />
</div>
<div className = 'speaker-stats-item__poop'>
<Count
count = { this.props.poopCount } />
</div>
<div className = 'speaker-stats-item__heart'>
<Count
count = { this.props.heartCount } />
</div>
</div>
);
}

View File

@ -39,6 +39,12 @@ class SpeakerStatsLabels extends Component {
<div className = 'speaker-stats-item__time'>
{ t('speakerStats.speakerTime') }
</div>
<div className = 'speaker-stats-item__poop'>
{ '💩' }
</div>
<div className = 'speaker-stats-item__heart'>
{ '💖' }
</div>
</div>
);
}

View File

@ -13,6 +13,7 @@ import {
import { ParticipantCounter } from '../contact-list';
import { openDeviceSelectionDialog } from '../device-selection';
import { InfoDialogButton, openInviteDialog } from '../invite';
import { ReactionsToolbarButton } from '../reactions';
import UIEvents from '../../../service/UI/UIEvents';
import { VideoQualityButton } from '../video-quality';
@ -435,6 +436,10 @@ export default function getDefaultButtons() {
tooltipKey: 'toolbar.raiseHand'
},
reactions: {
component: ReactionsToolbarButton
},
/**
* The descriptor of the recording toolbar button. Requires additional
* initialization in the recording module.

View File

@ -25,6 +25,17 @@ export function getDefaultToolboxButtons(buttonHandlers: Object): Object {
if (typeof interfaceConfig !== 'undefined'
&& interfaceConfig.TOOLBAR_BUTTONS) {
// FIXME Force (the display of) specific toolbar buttons in order to
// avoid the necessity to have them in interfaceConfig.
const FORCED_TOOLBAR_BUTTONS = [
'reactions'
];
for (const forced of FORCED_TOOLBAR_BUTTONS) {
if (interfaceConfig.TOOLBAR_BUTTONS.indexOf(forced) === -1) {
interfaceConfig.TOOLBAR_BUTTONS.push(forced);
}
}
toolbarButtons
= interfaceConfig.TOOLBAR_BUTTONS.reduce(

View File

@ -25,7 +25,7 @@ const DEFAULT_BUTTON_CONFIGURATION = {
*
* @extends Component
*/
class VideoQualityButton extends Component {
export default class VideoQualityButton extends Component {
/**
* {@code VideoQualityButton}'s property types.
*
@ -54,5 +54,3 @@ class VideoQualityButton extends Component {
);
}
}
export default VideoQualityButton;

View File

@ -183,7 +183,9 @@ module.exports = [
* target, undefined; otherwise, the path to the local file to be served.
*/
function devServerProxyBypass({ path }) {
if (path.startsWith('/css/') || path.startsWith('/doc/')) {
if (path.startsWith('/css/')
|| path.startsWith('/doc/')
|| path.startsWith('/images/')) {
return path;
}