Browse Source

Dialogs re-design, invite and password modifications

master
yanas 8 years ago
parent
commit
74f31db434
38 changed files with 2036 additions and 924 deletions
  1. 9
    39
      conference.js
  2. 84
    0
      css/_animations.scss
  3. 41
    12
      css/_base.scss
  4. 3
    2
      css/_contact_list.scss
  5. 6
    0
      css/_functions.scss
  6. 14
    0
      css/_mixins.scss
  7. 0
    5
      css/_modaldialog.scss
  8. 19
    7
      css/_side_toolbar_container.scss
  9. 4
    4
      css/_toolbars.scss
  10. 23
    5
      css/_variables.scss
  11. 81
    0
      css/buttons/_button-control.scss
  12. 49
    0
      css/input-control/_input-control.scss
  13. 19
    3
      css/main.scss
  14. 72
    45
      css/modals/_dialog.scss
  15. 49
    53
      css/modals/feedback/_feedback.scss
  16. 4
    0
      css/shortcuts/_main.scss
  17. 11
    0
      css/shortcuts/_regular-key.scss
  18. 12
    0
      css/shortcuts/_shortcuts-list.scss
  19. 46
    0
      css/themes/_light.scss
  20. 1
    4
      index.html
  21. 13
    3
      lang/main.json
  22. 59
    60
      modules/UI/UI.js
  23. 0
    244
      modules/UI/authentication/RoomLocker.js
  24. 2
    2
      modules/UI/feedback/Feedback.js
  25. 107
    119
      modules/UI/feedback/FeedbackWindow.js
  26. 33
    0
      modules/UI/invite/AskForPassword.js
  27. 216
    0
      modules/UI/invite/Invite.js
  28. 382
    0
      modules/UI/invite/InviteDialogView.js
  29. 121
    0
      modules/UI/invite/RoomLocker.js
  30. 25
    28
      modules/UI/recording/Recording.js
  31. 20
    20
      modules/UI/shared_video/SharedVideo.js
  32. 19
    0
      modules/UI/side_pannels/contactlist/Contact.js
  33. 66
    123
      modules/UI/side_pannels/contactlist/ContactList.js
  34. 262
    0
      modules/UI/side_pannels/contactlist/ContactListView.js
  35. 25
    124
      modules/UI/toolbars/Toolbar.js
  36. 88
    13
      modules/UI/util/MessageHandler.js
  37. 11
    7
      modules/keyboardshortcut/keyboardshortcut.js
  38. 40
    2
      service/UI/UIEvents.js

+ 9
- 39
conference.js View File

1
 /* global $, APP, JitsiMeetJS, config, interfaceConfig */
1
 /* global $, APP, JitsiMeetJS, config, interfaceConfig */
2
 import {openConnection} from './connection';
2
 import {openConnection} from './connection';
3
-//FIXME:
4
-import createRoomLocker from './modules/UI/authentication/RoomLocker';
5
-//FIXME:
3
+import Invite from './modules/UI/invite/Invite';
4
+import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
5
+
6
 import AuthHandler from './modules/UI/authentication/AuthHandler';
6
 import AuthHandler from './modules/UI/authentication/AuthHandler';
7
 
7
 
8
 import ConnectionQuality from './modules/connectionquality/connectionquality';
8
 import ConnectionQuality from './modules/connectionquality/connectionquality';
26
 const TrackEvents = JitsiMeetJS.events.track;
26
 const TrackEvents = JitsiMeetJS.events.track;
27
 const TrackErrors = JitsiMeetJS.errors.track;
27
 const TrackErrors = JitsiMeetJS.errors.track;
28
 
28
 
29
-let room, connection, localAudio, localVideo, roomLocker;
29
+let room, connection, localAudio, localVideo;
30
 
30
 
31
 /**
31
 /**
32
  * Indicates whether the connection is interrupted or not.
32
  * Indicates whether the connection is interrupted or not.
363
         switch (err) {
363
         switch (err) {
364
             // room is locked by the password
364
             // room is locked by the password
365
         case ConferenceErrors.PASSWORD_REQUIRED:
365
         case ConferenceErrors.PASSWORD_REQUIRED:
366
-            APP.UI.markRoomLocked(true);
367
-            roomLocker.requirePassword().then(function () {
368
-                let pass = roomLocker.password;
369
-                // we received that password is required, but user is trying
370
-                // anyway to login without a password, mark room as not locked
371
-                // in case he succeeds (maybe someone removed the password
372
-                // meanwhile), if it is still locked another password required
373
-                // will be received and the room again will be marked as locked
374
-                if (!pass)
375
-                    APP.UI.markRoomLocked(false);
376
-                room.join(roomLocker.password);
377
-            });
366
+            APP.UI.emitEvent(UIEvents.PASSWORD_REQUIRED);
378
             break;
367
             break;
379
 
368
 
380
         case ConferenceErrors.CONNECTION_ERROR:
369
         case ConferenceErrors.CONNECTION_ERROR:
403
             }, 5000);
392
             }, 5000);
404
 
393
 
405
             // notify user that auth is required
394
             // notify user that auth is required
406
-
407
-            AuthHandler.requireAuth(room, roomLocker.password);
395
+            AuthHandler.requireAuth(room, this.invite.getRoomLocker().password);
408
             break;
396
             break;
409
 
397
 
410
         case ConferenceErrors.RESERVATION_ERROR:
398
         case ConferenceErrors.RESERVATION_ERROR:
576
                 this.isDesktopSharingEnabled =
564
                 this.isDesktopSharingEnabled =
577
                     JitsiMeetJS.isDesktopSharingEnabled();
565
                     JitsiMeetJS.isDesktopSharingEnabled();
578
 
566
 
567
+                APP.UI.ContactList = new ContactList(room);
568
+
579
                 // if user didn't give access to mic or camera or doesn't have
569
                 // if user didn't give access to mic or camera or doesn't have
580
                 // them at all, we disable corresponding toolbar buttons
570
                 // them at all, we disable corresponding toolbar buttons
581
                 if (!tracks.find((t) => t.isAudioTrack())) {
571
                 if (!tracks.find((t) => t.isAudioTrack())) {
888
         room = connection.initJitsiConference(APP.conference.roomName,
878
         room = connection.initJitsiConference(APP.conference.roomName,
889
             this._getConferenceOptions());
879
             this._getConferenceOptions());
890
         this._setLocalAudioVideoStreams(localTracks);
880
         this._setLocalAudioVideoStreams(localTracks);
891
-        roomLocker = createRoomLocker(room);
881
+        this.invite = new Invite(room);
892
         this._room = room; // FIXME do not use this
882
         this._room = room; // FIXME do not use this
893
 
883
 
894
         let email = APP.settings.getEmail();
884
         let email = APP.settings.getEmail();
1316
             APP.UI.updateRecordingState(status);
1306
             APP.UI.updateRecordingState(status);
1317
         });
1307
         });
1318
 
1308
 
1319
-        room.on(ConferenceEvents.LOCK_STATE_CHANGED, (state, error) => {
1320
-            console.log("Received channel password lock change: ", state,
1321
-                error);
1322
-            APP.UI.markRoomLocked(state);
1323
-            roomLocker.lockedElsewhere = state;
1324
-        });
1325
-
1326
         room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
1309
         room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
1327
             APP.UI.updateUserStatus(id, status);
1310
             APP.UI.updateUserStatus(id, status);
1328
         });
1311
         });
1348
                 "resizable,scrollbars=yes,status=1");
1331
                 "resizable,scrollbars=yes,status=1");
1349
         });
1332
         });
1350
 
1333
 
1351
-        APP.UI.addListener(UIEvents.ROOM_LOCK_CLICKED, () => {
1352
-            if (room.isModerator()) {
1353
-                let promise = roomLocker.isLocked
1354
-                    ? roomLocker.askToUnlock()
1355
-                    : roomLocker.askToLock();
1356
-                promise.then(() => {
1357
-                    APP.UI.markRoomLocked(roomLocker.isLocked);
1358
-                });
1359
-            } else {
1360
-                roomLocker.notifyModeratorRequired();
1361
-            }
1362
-        });
1363
-
1364
         APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
1334
         APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
1365
         APP.UI.addListener(UIEvents.VIDEO_MUTED, muteLocalVideo);
1335
         APP.UI.addListener(UIEvents.VIDEO_MUTED, muteLocalVideo);
1366
 
1336
 

+ 84
- 0
css/_animations.scss View File

1
+/**
2
+* Project animations
3
+**/
4
+
5
+/**
6
+ * START of slide in animation for extended toolbar.
7
+ */
8
+@include keyframes(slideInX) {
9
+    0% { transform: translateX(-100%); }
10
+    100% { transform: translateX(0%); }
11
+}
12
+
13
+@include keyframes(slideOutX) {
14
+    0% { transform: translateX(0%); }
15
+    100% { transform: translateX(-100%); }
16
+}
17
+
18
+@include keyframes(slideInExtX) {
19
+    0% { transform: translateX(-500%); }
20
+    100% { transform: translateX(0%); }
21
+}
22
+
23
+@include keyframes(slideOutExtX) {
24
+    0% { transform: translateX(0%); }
25
+    100% { transform: translateX(-500%); }
26
+}
27
+
28
+/**
29
+ * END of slide out animation for extended toolbar.
30
+ */
31
+
32
+/**
33
+ * START of slide in / out animation for main toolbar.
34
+ */
35
+@include keyframes(slideInY) {
36
+    100% { transform: translateY(0%); }
37
+}
38
+
39
+@include keyframes(slideOutY) {
40
+    0% { transform: translateY(0%); }
41
+    100% { transform: translateY(-100%); }
42
+}
43
+
44
+/**
45
+ * END of slide in / out animation for main toolbar.
46
+ */
47
+
48
+/**
49
+ * START of slide in animation for extended toolbar (inner) panel.
50
+ */
51
+
52
+// FIX: Can't use percentage because of breaking animation when width is changed
53
+// (100% of 0 is also zero) Extracted this to config variable.
54
+@include keyframes(slideInExt) {
55
+    from { left: -$sidebarWidth; }
56
+    to   { left: 0; }
57
+}
58
+
59
+@include keyframes(slideOutExt) {
60
+    from { left: 0; }
61
+    to   { left: -$sidebarWidth; }
62
+}
63
+
64
+/**
65
+ * END of slide in animation for extended toolbar (inner) panel.
66
+ */
67
+
68
+/**
69
+* START of slide in animation for extended toolbar container
70
+**/
71
+
72
+@include keyframes(slideOutExtContainer) {
73
+    from { width: $sidebarWidth; }
74
+    to { width: 0; }
75
+}
76
+
77
+@include keyframes(slideInExtContainer) {
78
+    from { width: 0; }
79
+    to { width: $sidebarWidth; }
80
+}
81
+
82
+/**
83
+* END of slide in animation for extended toolbar container
84
+**/

+ 41
- 12
css/_base.scss View File

13
     overflow: hidden;
13
     overflow: hidden;
14
 }
14
 }
15
 
15
 
16
+p {
17
+    margin: 0;
18
+}
19
+
16
 html, body, input, textarea, keygen, select, button {
20
 html, body, input, textarea, keygen, select, button {
17
     font-family: $baseFontFamily !important;
21
     font-family: $baseFontFamily !important;
18
 }
22
 }
26
 }
30
 }
27
 
31
 
28
 input[type='text'], input[type='password'], textarea {
32
 input[type='text'], input[type='password'], textarea {
29
-    -webkit-user-select: text;
30
-    user-select: text;
31
     display: inline-block;
33
     display: inline-block;
32
-    padding: 5px;
33
-    color: $defaultDarkColor;
34
+    width: 100%;
35
+    padding: 5px 7px;
36
+    color: $inputColor;
34
     border-radius: $borderRadius;
37
     border-radius: $borderRadius;
35
     line-height: 32px;
38
     line-height: 32px;
39
+    letter-spacing: $letterSpacing;
36
     height: 32px;
40
     height: 32px;
37
     text-align: left;
41
     text-align: left;
38
     border:1px solid $inputBorderColor;
42
     border:1px solid $inputBorderColor;
43
+    background-color: $inputBackground;
39
     outline: none; /* removes the default outline */
44
     outline: none; /* removes the default outline */
40
     resize: none; /* prevents the user-resizing, adjust to taste */
45
     resize: none; /* prevents the user-resizing, adjust to taste */
41
 }
46
 }
43
 textarea {
48
 textarea {
44
     overflow: hidden;
49
     overflow: hidden;
45
     word-wrap: break-word;
50
     word-wrap: break-word;
46
-    resize: horizontal;
51
+    resize: none;
52
+    line-height: 1.5em;
47
 }
53
 }
48
 
54
 
49
 button.no-icon {
55
 button.no-icon {
53
 button, input, select, textarea {
59
 button, input, select, textarea {
54
     margin: 0;
60
     margin: 0;
55
     vertical-align: baseline;
61
     vertical-align: baseline;
56
-    color: $defaultDarkColor;
57
-    background: $inputLightBackground;
58
-    font-size: 12px;
59
-    border: none;
60
-    box-shadow: none;
61
-    outline: none;
62
+    color: $inputColor;
63
+    font-size: 1em;
64
+    letter-spacing: $letterSpacing;
62
 }
65
 }
63
 
66
 
64
 button, select, input[type="button"],
67
 button, select, input[type="button"],
72
 
75
 
73
 button {
76
 button {
74
     color: #FFF;
77
     color: #FFF;
75
-    background-color: $buttonBackground !important;
78
+    background-color: $buttonBackground;
76
     border-radius: $borderRadius;
79
     border-radius: $borderRadius;
77
 }
80
 }
78
 
81
 
159
     display: flex !important;
162
     display: flex !important;
160
 }
163
 }
161
 
164
 
165
+/**
166
+* Tooltips
167
+**/
162
 .tipsy {
168
 .tipsy {
163
     z-index: $tooltipsZ;
169
     z-index: $tooltipsZ;
164
     &-inner {
170
     &-inner {
169
         border-color: $tooltipBg;
175
         border-color: $tooltipBg;
170
     }
176
     }
171
 }
177
 }
178
+
179
+/**
180
+* Dialogs fade
181
+*/
182
+.aui-blanket {
183
+    visibility: visible;
184
+}
185
+
186
+.link {
187
+    color: $linkFontColor;
188
+    @include transition(color .1s ease-out);
189
+
190
+    &:hover {
191
+        color: $linkHoverFontColor;
192
+        text-decoration: underline;
193
+        @include transition(color .1s ease-in);
194
+    }
195
+}
196
+
197
+#inviteLinkRef {
198
+    -webkit-user-select: text;
199
+    user-select: text;
200
+}

+ 3
- 2
css/_contact_list.scss View File

4
     > ul#contacts {
4
     > ul#contacts {
5
         font-size: 12px;
5
         font-size: 12px;
6
         bottom: 0px;
6
         bottom: 0px;
7
-        margin: 0px;
7
+        margin: 0;
8
+        margin-top: 16px;
8
         padding: 0px;
9
         padding: 0px;
9
         width: 100%;
10
         width: 100%;
10
         overflow: auto;
11
         overflow: auto;
24
         white-space: nowrap;
25
         white-space: nowrap;
25
         color: #FFF;
26
         color: #FFF;
26
         font-size: 10pt;
27
         font-size: 10pt;
27
-        padding: 6px 10%;
28
+        padding: 6px 30px;
28
 
29
 
29
         &:hover,
30
         &:hover,
30
         &:active {
31
         &:active {

+ 6
- 0
css/_functions.scss View File

1
+/* Functions */
2
+
3
+/* Pixels to Ems function */
4
+@function em($value, $base: 16) {
5
+    @return #{$value / $base}em;
6
+}

+ 14
- 0
css/_mixins.scss View File

18
   animation:         $animations;
18
   animation:         $animations;
19
 }
19
 }
20
 
20
 
21
+@mixin flex() {
22
+    display: -webkit-box;
23
+    display: -moz-box;
24
+    display: -ms-flexbox;
25
+    display: -webkit-flex;
26
+    display: flex;
27
+}
28
+
21
 /**
29
 /**
22
  * Keyframes mixin.
30
  * Keyframes mixin.
23
  */
31
  */
63
     -webkit-transition: $transition;
71
     -webkit-transition: $transition;
64
     transition:         $transition;
72
     transition:         $transition;
65
 }
73
 }
74
+
75
+@mixin box-shadow($type, $h, $y, $blur, $color) {
76
+    -webkit-box-shadow: $type $h $y $blur $color;
77
+    -moz-box-shadow: $type $h $y $blur $color;
78
+    box-shadow: $type $h $y $blur $color;
79
+}

+ 0
- 5
css/_modaldialog.scss View File

19
     width: 100%;
19
     width: 100%;
20
 }
20
 }
21
 
21
 
22
-.jqibuttons button {
23
-    margin-right: 5px;
24
-    float:right;
25
-}
26
-
27
 button.jqidefaultbutton #inviteLinkRef {
22
 button.jqidefaultbutton #inviteLinkRef {
28
     color: #2c8ad2;
23
     color: #2c8ad2;
29
 }
24
 }

+ 19
- 7
css/_side_toolbar_container.scss View File

6
     position:absolute;
6
     position:absolute;
7
     top: 0px;
7
     top: 0px;
8
     left: $defaultToolbarSize;
8
     left: $defaultToolbarSize;
9
-    width: 0%;
10
-    height: 100%;
11
-    max-width: 200px;
9
+    width: 0;
12
     background-color: rgba(0,0,0,0.8);
10
     background-color: rgba(0,0,0,0.8);
11
+    height: 100%;
12
+    max-width: $sidebarWidth;
13
     z-index: 800;
13
     z-index: 800;
14
     overflow: hidden;
14
     overflow: hidden;
15
-    letter-spacing: 1px;
15
+    letter-spacing: $titleLetterSpacing;
16
 
16
 
17
     /**
17
     /**
18
      * Labels inside the side panel.
18
      * Labels inside the side panel.
24
     /**
24
     /**
25
      * Form elements and blocks.
25
      * Form elements and blocks.
26
      */
26
      */
27
-    input, label, select, button, a, .sideToolbarBlock {
27
+    input, label, select, a,
28
+    .sideToolbarBlock, .input-control, .button-control {
28
         display: inline-block;
29
         display: inline-block;
29
         margin-top: 15px;
30
         margin-top: 15px;
30
         margin-left: 10%;
31
         margin-left: 10%;
36
      */
37
      */
37
     select, input[type="button"], input[type="text"],
38
     select, input[type="button"], input[type="text"],
38
     input[type="reset"], input[type="submit"] {
39
     input[type="reset"], input[type="submit"] {
39
-        color: $defaultColor;
40
+        color: $inputColor;
40
         background: $inputBackground;
41
         background: $inputBackground;
41
         border: none;
42
         border: none;
42
     }
43
     }
63
      */
64
      */
64
     .sideToolbarContainer__inner {
65
     .sideToolbarContainer__inner {
65
         display: none;
66
         display: none;
66
-        width: 200px;
67
+        height: 100%;
68
+        width: $sidebarWidth;
69
+        position: absolute;
70
+        box-sizing: border-box;
67
         color: #FFF;
71
         color: #FFF;
68
 
72
 
69
         /**
73
         /**
100
         .first {
104
         .first {
101
             margin-top: 0px !important;
105
             margin-top: 0px !important;
102
         }
106
         }
107
+
108
+        /**
109
+         * Buttons in the side toolbar container.
110
+         */
111
+        .button-control {
112
+            margin: 9px 0;
113
+            width: 100%;
114
+        }
103
     }
115
     }
104
 }
116
 }
105
 
117
 

+ 4
- 4
css/_toolbars.scss View File

45
     border-radius: 3px;
45
     border-radius: 3px;
46
 
46
 
47
     .first {
47
     .first {
48
-        border-bottom-left-radius: 4px;
49
-        border-top-left-radius: 4px;
48
+        border-bottom-left-radius: 3px;
49
+        border-top-left-radius: 3px;
50
     }
50
     }
51
 
51
 
52
     .last {
52
     .last {
53
-        border-bottom-right-radius: 4px;
54
-        border-top-right-radius: 4px;
53
+        border-bottom-right-radius: 3px;
54
+        border-top-right-radius: 3px;
55
     }
55
     }
56
 }
56
 }
57
 
57
 

+ 23
- 5
css/_variables.scss View File

1
+/**
2
+* Theme
3
+*/
4
+@import 'themes/light';
5
+
1
 /**
6
 /**
2
  * Style variables
7
  * Style variables
3
  */
8
  */
21
  */
26
  */
22
 $defaultColor: #F1F1F1;
27
 $defaultColor: #F1F1F1;
23
 $defaultSideBarFontColor: #44A5FF;
28
 $defaultSideBarFontColor: #44A5FF;
24
-$defaultDarkColor: #4F4F4F;
29
+$defaultSemiDarkColor: #ACACAC;
30
+$defaultDarkColor: #2b3d5c;
25
 $defaultBackground: #474747;
31
 $defaultBackground: #474747;
26
 $tooltipBg: rgba(0,0,0, 0.7);
32
 $tooltipBg: rgba(0,0,0, 0.7);
27
 
33
 
33
 $toolbarToggleBackground: #12499C;
39
 $toolbarToggleBackground: #12499C;
34
 
40
 
35
 // Main controls
41
 // Main controls
36
-$inputBackground: rgba(132, 132, 132, .5);
37
 $inputSemiBackground: rgba(132, 132, 132, .8);
42
 $inputSemiBackground: rgba(132, 132, 132, .8);
38
 $inputLightBackground: #EBEBEB;
43
 $inputLightBackground: #EBEBEB;
39
-$inputBorderColor: #EBEBEB;
40
-$buttonBackground: #44A5FF;
41
 
44
 
42
 // Video layout.
45
 // Video layout.
43
 $videoThumbnailHovered: #BFEBFF;
46
 $videoThumbnailHovered: #BFEBFF;
58
  */
61
  */
59
 $borderRadius: 4px;
62
 $borderRadius: 4px;
60
 $defaultWatermarkLink: '../images/watermark.png';
63
 $defaultWatermarkLink: '../images/watermark.png';
64
+$sidebarWidth: 200px;
61
 
65
 
62
 /**
66
 /**
63
  * Z-indexes. TODO: Replace this by a function.
67
  * Z-indexes. TODO: Replace this by a function.
64
  */
68
  */
65
 $tooltipsZ: 901;
69
 $tooltipsZ: 901;
66
 $toolbarZ: 900;
70
 $toolbarZ: 900;
67
-$overlayZ: 800;
71
+$overlayZ: 800;
72
+
73
+/**
74
+ * Font Colors TODO: Change colors when general dialogs are implemented.
75
+ */
76
+$defaultFontColor: #777;
77
+$defaultLightFontColor: #F1F1F1;
78
+$defaultDarkFontColor: #000;
79
+$buttonFontColor: #777;
80
+$buttonHoverFontColor: #287ade;
81
+$auiPrimaryButtonBg: #3572b0;
82
+$auiPrimaryButtonHoverBg: #57647b;
83
+$auiPrimaryButtonColor: #fff;
84
+$auiIconColor: #707070;
85
+$inputControlEmColor: #f29424;

+ 81
- 0
css/buttons/_button-control.scss View File

1
+.button-control {
2
+    box-sizing: border-box;
3
+    display: inline-block;
4
+    border: 1px solid $buttonBorder;
5
+    vertical-align: baseline;
6
+    height: 30px;
7
+    padding: 4px 10px;
8
+    margin: 0;
9
+    line-height: 1.5em;
10
+    outline: none;
11
+    background-color: transparent;
12
+    float: right;
13
+    font-size: 14px;
14
+    margin-left: 10px;
15
+    color: $buttonColor;
16
+    letter-spacing: $letterSpacing;
17
+    font-weight: $buttonFontWeight;
18
+    @include transition(background-color .1s ease-out);
19
+
20
+    &[disabled] {
21
+        color: #666;
22
+        cursor: default;
23
+    }
24
+
25
+    &_full-width {
26
+        margin: 0;
27
+        width: 100%;
28
+    }
29
+
30
+    &:hover {
31
+        border: 1px solid $buttonHoverBorder;
32
+        background-color: $buttonHoverBackground;
33
+        @include transition(background-color .1s ease-in);
34
+    }
35
+
36
+    &:active {
37
+        @include box-shadow(inset, 0, 0, 1px, $buttonShadowColor);
38
+    }
39
+
40
+    &_light {
41
+        color: $defaultDarkColor;
42
+        background-color: $buttonLightBackground;
43
+        border: 1px solid $buttonLightBorder;
44
+
45
+        &:hover {
46
+            border: 1px solid $buttonLightHoverBorder;
47
+            background-color: $buttonLightHoverBackground;
48
+        }
49
+    }
50
+
51
+    &_link {
52
+        color: $buttonLinkColor;
53
+        background-color: $buttonLinkBackground;
54
+
55
+        &:hover {
56
+            background-color: $buttonLinkBackground;
57
+        }
58
+    }
59
+
60
+    &_primary {
61
+        background-color: $primaryButtonBackground;
62
+        border: 1px solid $primaryButtonBackground;
63
+        color: $primaryButtonColor;
64
+        font-weight: $primaryButtonFontWeight;
65
+
66
+        &:hover {
67
+            border: 1px solid $primaryButtonHoverBackground;
68
+            background-color: $primaryButtonHoverBackground;
69
+        }
70
+    }
71
+
72
+    &_close {
73
+        color: $defaultFontColor;
74
+    }
75
+    &_submit {
76
+        color: $linkFontColor;
77
+        &:hover {
78
+            color: $linkHoverFontColor;
79
+        }
80
+    }
81
+}

+ 49
- 0
css/input-control/_input-control.scss View File

1
+.input-control {
2
+    padding: 16px 0;
3
+
4
+    &__text {
5
+        margin: 8px 0;
6
+        font-size: 1em
7
+    }
8
+
9
+    &__label {
10
+        font-size: 1em;
11
+        font-weight: $labelFontWeight;
12
+    }
13
+
14
+    &__input {
15
+        margin: 8px 0;
16
+
17
+        &::selection {
18
+            background-color: $defaultDarkSelectionColor;
19
+        }
20
+    }
21
+
22
+    &__em {
23
+        color: $inputControlEmColor;
24
+    }
25
+
26
+    &__hint {
27
+        margin-top: 0;
28
+        font-size: $hintFontSize;
29
+
30
+        span {
31
+            vertical-align: middle;
32
+        }
33
+    }
34
+
35
+    &__container {
36
+        position: relative;
37
+        width: 100%;
38
+        @include flex();
39
+
40
+        .button-control {
41
+            margin: 9px 0 9px 10px;
42
+        }
43
+    }
44
+
45
+    &__right {
46
+        position: absolute;
47
+        right: 0;
48
+    }
49
+}

+ 19
- 3
css/main.scss View File

1
+/* Functions BEGIN */
2
+
3
+@import 'functions';
4
+
5
+/* Functions END */
6
+
1
 /* Variables BEGIN */
7
 /* Variables BEGIN */
2
 
8
 
3
 @import 'variables';
9
 @import 'variables';
10
 
16
 
11
 /* Mixins END */
17
 /* Mixins END */
12
 
18
 
19
+/* Animations BEGIN */
20
+
21
+@import "animations";
22
+
23
+/* Animations END */
24
+
13
 /* Fonts BEGIN */
25
 /* Fonts BEGIN */
14
 
26
 
15
 @import 'font';
27
 @import 'font';
17
 
29
 
18
 /* Fonts END */
30
 /* Fonts END */
19
 
31
 
32
+/* Theme  BEGIN */
33
+@import "themes/light";
34
+/* Theme END */
35
+
20
 /* Modules BEGIN */
36
 /* Modules BEGIN */
21
 
37
 
22
 @import 'toastr';
38
 @import 'toastr';
25
 @import 'modals/dialog';
41
 @import 'modals/dialog';
26
 @import 'modals/feedback/feedback';
42
 @import 'modals/feedback/feedback';
27
 @import 'videolayout_default';
43
 @import 'videolayout_default';
28
-@import 'jquery-impromptu';
29
-@import 'modaldialog';
30
 @import 'notice';
44
 @import 'notice';
31
 @import 'popup_menu';
45
 @import 'popup_menu';
32
 @import 'recording';
46
 @import 'recording';
43
 @import 'jquery.contextMenu';
57
 @import 'jquery.contextMenu';
44
 @import 'keyboard-shortcuts';
58
 @import 'keyboard-shortcuts';
45
 @import 'redirect_page';
59
 @import 'redirect_page';
46
-
60
+@import 'input-control/input-control';
61
+@import 'shortcuts/main';
62
+@import 'buttons/button-control';
47
 
63
 
48
 /* Modules END */
64
 /* Modules END */

+ 72
- 45
css/modals/_dialog.scss View File

1
-.dialog{
1
+.dialog {
2
     visibility: visible;
2
     visibility: visible;
3
     height: auto;
3
     height: auto;
4
 
4
 
5
-    p {
6
-        color: $defaultDarkColor;
5
+    h3 {
6
+        color: $auiDialogColor;
7
     }
7
     }
8
-    textarea {
9
-        background: none;
10
-        border: 1px solid $inputBorderColor;
11
-    }
12
-    .aui-dialog2-content:last-child {
13
-        border-bottom-right-radius: 5px;
14
-        border-bottom-left-radius: 5px;
15
-    }
16
-    .aui-dialog2-content:first-child {
17
-        border-top-right-radius: 5px;
18
-        border-top-left-radius: 5px;
19
-    }
20
-    .aui-dialog2-footer{
21
-        border-top: 0;
22
-        border-radius: 0;
23
-        padding-top: 0;
24
-        background: none;
25
-        border: none;
26
-        height: auto;
27
-        margin-top: 10px;
28
-    }
29
-    .aui-button {
30
-        height: 28px;
31
-        font-size: 12px;
32
-        padding: 3px 6px 3px 6px;
33
-        border: none;
34
-        box-shadow: none;
35
-        outline: none;
36
-
37
-        &_close {
38
-            font-weight: 400 !important;
39
-            color: $buttonBackground;
40
-            background: none !important;
41
-
42
-            :hover {
43
-                text-decoration: underline;
8
+
9
+    .aui {
10
+
11
+        &-icon {
12
+            color: $auiDialogColor;
13
+
14
+            &-small {
15
+                width: 14px;
16
+                height: 14px;
44
             }
17
             }
45
         }
18
         }
46
-        &_submit {
47
-            font-weight: 700 !important;
48
-            color: $defaultColor;
49
-            background: $buttonBackground;
50
-            border-radius: 3px;
19
+
20
+        &-iconfont-close-dialog {
21
+            cursor: pointer;
22
+            right: 20px;
23
+            position: absolute;
24
+            top: -49px;
25
+        }
26
+
27
+        &-dialog2 {
28
+            &-header, &-footer {
29
+                background-color: $auiDialogBg;
30
+                border: none;
31
+            }
32
+
33
+            &-header {
34
+                height: em(58, 12);
35
+                border-bottom: 1px solid $auiBorderColor;
36
+
37
+                h2 {
38
+                    font-size: em(20, 12);
39
+                    font-weight: $dialogTitleFontWeight;
40
+                    letter-spacing: $titleLetterSpacing;
41
+                    color: $auiDialogColor;
42
+                }
43
+
44
+                &-main {
45
+                    padding-right: 0;
46
+                }
47
+            }
48
+
49
+            &-footer {
50
+                border-top: 1px solid $auiBorderColor;
51
+            }
52
+
53
+            &-content {
54
+                font-size: em(14, 12);
55
+                min-height: 0;
56
+                background-color: $auiDialogContentBg;
57
+                color: $auiDialogColor;
58
+
59
+                p,span, h3 {
60
+                    font-weight: $labelFontWeight;
61
+                    letter-spacing: $letterSpacing;
62
+                }
63
+
64
+                &:last-child {
65
+                    border-bottom-right-radius: 5px;
66
+                    border-bottom-left-radius: 5px;
67
+                }
68
+
69
+                &:first-child {
70
+                    border-top-right-radius: 5px;
71
+                    border-top-left-radius: 5px;
72
+                }
73
+            }
51
         }
74
         }
52
     }
75
     }
53
-}
76
+
77
+    .input-control:not(:last-child) {
78
+        border-bottom: 1px solid $auiBorderColor;
79
+    }
80
+}

+ 49
- 53
css/modals/feedback/_feedback.scss View File

45
     animation-timing-function: ease-in-out
45
     animation-timing-function: ease-in-out
46
 }
46
 }
47
 
47
 
48
-.feedback {
49
-    h2 {
50
-        font-weight: 400;
51
-        font-size: 24px;
52
-        line-height: 1.2;
53
-    }
54
-    p {
55
-        font-weight: 400;
56
-        font-size: 14px;
57
-    }
58
-
59
-    &__content {
60
-        text-align: center;
61
-
62
-        textarea {
63
-            text-align: left;
64
-            min-height: 80px;
65
-            width: 100%;
48
+.feedback.aui-dialog2{
49
+    .aui-dialog2{
50
+        &-header {
51
+            background-color: $auiDialogContentBg;
52
+            border-bottom-color: transparent;
53
+            padding-top: 30px;
54
+            h2 {
55
+                text-align: center;
56
+            }
66
         }
57
         }
67
-    }
68
-    &__footer {
69
 
58
 
70
-        &:hover {
71
-            color: #287ade;
72
-            outline: 0;
73
-        }
74
-    }
75
-    &__rating {
76
-        line-height: 1.2;
77
-        padding: 20px 0;
59
+        &-content {
60
+            text-align: center;
61
+            padding: 10px 40px 20px 40px;
78
 
62
 
79
-        p {
80
-            margin: 10px 0 0;
81
-        }
63
+            .rating {
64
+                line-height: 1.2;
65
+                text-align: center;
66
+                margin-top: 10px;
82
 
67
 
83
-        .star-label {
84
-            font-size: 16px;
85
-            color: $rateStarLabelColor;
86
-        }
87
-
88
-        .star-btn {
89
-            color: $rateStarDefault;
90
-            font-size: 36px;
91
-            position: relative;
92
-            cursor: pointer;
93
-            outline: none;
94
-            text-decoration: none;
95
-            @include transition(all .2s ease);
96
-
97
-            &.starHover,
98
-            &.active,
99
-            &:hover {
100
-                color: $rateStarActivity;
101
-                > i:before {
102
-                    content: "\e90a";
68
+                .star-label {
69
+                    height: 16px;
70
+                    font-size: 14px;
103
                 }
71
                 }
104
-            };
72
+                .star-btn {
73
+                    color: $rateStarDefault;
74
+                    font-size: 36px;
75
+                    position: relative;
76
+                    cursor: pointer;
77
+                    outline: none;
78
+                    text-decoration: none;
79
+                    @include transition(all .2s ease);
80
+
81
+                    &.starHover,
82
+                    &.active,
83
+                    &:hover {
84
+                        color: $rateStarActivity;
85
+                    };
105
 
86
 
87
+                }
88
+            }
89
+
90
+            .details {
91
+                padding-left: 60px;
92
+                padding-right: 60px;
93
+                margin-top: 20px;
94
+                textarea {
95
+                    min-height: 100px;
96
+                }
97
+            }
98
+        }
99
+        &-footer {
100
+            background-color: $auiDialogContentBg;
101
+            border-top-color: transparent;
106
         }
102
         }
107
     }
103
     }
108
-}
104
+}

+ 4
- 0
css/shortcuts/_main.scss View File

1
+/* Import shortcuts blocks */
2
+
3
+@import 'regular-key';
4
+@import 'shortcuts-list';

+ 11
- 0
css/shortcuts/_regular-key.scss View File

1
+.regular-key {
2
+    display: table-cell;
3
+    width: 25px;
4
+    height: 20px;
5
+    padding: 0;
6
+    text-align: center;
7
+    vertical-align: middle;
8
+    font-family: $baseFontFamily;
9
+    color: $defaultDarkColor;
10
+    font-size: 12px;
11
+}

+ 12
- 0
css/shortcuts/_shortcuts-list.scss View File

1
+.shortcuts-list {
2
+    padding: 0;
3
+
4
+    &__description {
5
+        margin-left: em(16, 14);
6
+        vertical-align: top;
7
+    }
8
+
9
+    &__item {
10
+        margin-bottom: em(7, 14);
11
+    }
12
+}

+ 46
- 0
css/themes/_light.scss View File

1
+/**
2
+* Buttons
3
+*/
4
+$buttonBackground: #f5f5f5;
5
+$buttonHoverBackground: #e9e9e9;
6
+$buttonBorder: #ccc;
7
+$buttonHoverBorder: #999;
8
+$buttonColor: #333;
9
+
10
+$buttonLightBackground: #f5f5f5;
11
+$buttonLightHoverBackground: #e9e9e9;
12
+$buttonLightBorder: #ccc;
13
+$buttonLightHoverBorder: #999;
14
+
15
+$buttonLinkBackground: transparent;
16
+$buttonLinkColor: #0090e8;
17
+
18
+$primaryButtonBackground: #3572b0;
19
+$primaryButtonHoverBackground: #2a67a5;
20
+$primaryButtonColor: #fff;
21
+$primaryButtonFontWeight: 400;
22
+
23
+$buttonShadowColor: #192d4f;
24
+
25
+/**
26
+ * Dialog colors
27
+ **/
28
+$auiDialogColor: #333;
29
+$auiDialogBg: #f5f5f5;
30
+$auiDialogContentBg: #fff;
31
+$auiBorderColor: #ccc;
32
+$dialogTitleFontWeight: 400;
33
+
34
+// Main controls
35
+$inputBackground: #fff;
36
+$inputBorderColor: #ccc;
37
+$inputColor: #333;
38
+$defaultDarkSelectionColor: #ccc;
39
+$titleLetterSpacing: 0;
40
+$letterSpacing: 0;
41
+$buttonFontWeight: 400;
42
+$labelFontWeight: 400;
43
+$hintFontSize: em(13, 14);
44
+$linkFontColor: #3572b0;
45
+$linkHoverFontColor: darken(#3572b0, 10%);
46
+$dropdownColor: #333;

+ 1
- 4
index.html View File

132
                 </span>
132
                 </span>
133
             </a>
133
             </a>
134
             <a class="button" id="toolbar_button_record" style="display: none"></a>
134
             <a class="button" id="toolbar_button_record" style="display: none"></a>
135
-            <a class="button icon-security" id="toolbar_button_security"></a>
136
             <a class="button icon-share-doc" id="toolbar_button_etherpad"></a>
135
             <a class="button icon-share-doc" id="toolbar_button_etherpad"></a>
137
             <a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none">
136
             <a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none">
138
                 <ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
137
                 <ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
254
         </div>
253
         </div>
255
     </div>
254
     </div>
256
     <div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;">
255
     <div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;">
257
-        <div class="header"><h3 data-i18n="keyboardShortcuts.keyboardShortcuts"></h3></div>
258
         <div class="content">
256
         <div class="content">
259
-            <ul id="keyboard-shortcuts-list" class="item">
257
+            <ul id="keyboard-shortcuts-list" class="shortcuts-list">
260
             </ul>
258
             </ul>
261
         </div>
259
         </div>
262
     </div>
260
     </div>
263
-    <div id="aui-feedback-dialog" class="dialog feedback aui-layer aui-dialog2 aui-dialog2-medium" style="display: none;"></div>
264
   </body>
261
   </body>
265
 </html>
262
 </html>

+ 13
- 3
lang/main.json View File

1
 {
1
 {
2
-    "contactlist": "On Call",
2
+    "contactlist": "Participants",
3
+    "addParticipants": "Add Participants",
4
+    "roomLocked": "Callers must enter a password",
5
+    "roomUnlocked": "Anyone with the link can join",
6
+    "passwordSetRemotely": "set by another participant",
3
     "connectionsettings": "Connection Settings",
7
     "connectionsettings": "Connection Settings",
4
     "poweredby": "powered by",
8
     "poweredby": "powered by",
5
     "feedback": "Give us your feedback",
9
     "feedback": "Give us your feedback",
6
-    "roomUrlDefaultMsg": "Your conference is currently being created...",
10
+    "inviteUrlDefaultMsg": "Your conference is currently being created...",
7
     "me": "me",
11
     "me": "me",
8
     "speaker": "Speaker",
12
     "speaker": "Speaker",
9
     "raisedHand": "Would like to speak",
13
     "raisedHand": "Would like to speak",
180
         "raisedHand": "Would like to speak."
184
         "raisedHand": "Would like to speak."
181
     },
185
     },
182
     "dialog": {
186
     "dialog": {
187
+        "add": "Add",
183
         "kickMessage": "Ouch! You have been kicked out of the meet!",
188
         "kickMessage": "Ouch! You have been kicked out of the meet!",
184
         "popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser's security settings and try again.",
189
         "popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser's security settings and try again.",
190
+        "passwordErrorTitle": "Password Error",
185
         "passwordError": "This conversation is currently protected by a password. Only the owner of the conference can set a password.",
191
         "passwordError": "This conversation is currently protected by a password. Only the owner of the conference can set a password.",
186
         "passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference can set a password.",
192
         "passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference can set a password.",
187
         "connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
193
         "connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
189
         "connecting": "Connecting",
195
         "connecting": "Connecting",
190
         "copy": "Copy",
196
         "copy": "Copy",
191
         "error": "Error",
197
         "error": "Error",
198
+        "addPassword": "Add Password",
192
         "detectext": "Error when trying to detect desktopsharing extension.",
199
         "detectext": "Error when trying to detect desktopsharing extension.",
193
         "failtoinstall": "Failed to install desktop sharing extension",
200
         "failtoinstall": "Failed to install desktop sharing extension",
194
         "failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
201
         "failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
205
         "SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
212
         "SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
206
         "SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
213
         "SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
207
         "oops": "Oops!",
214
         "oops": "Oops!",
215
+        "currentPassword": "The current password is",
216
+        "passwordLabel": "Password",
208
         "defaultError": "There was some kind of error",
217
         "defaultError": "There was some kind of error",
209
         "passwordRequired": "Password required",
218
         "passwordRequired": "Password required",
210
         "Ok": "Ok",
219
         "Ok": "Ok",
211
         "Remove": "Remove",
220
         "Remove": "Remove",
221
+        "removePassword": "Remove password",
212
         "shareVideoTitle": "Share a video",
222
         "shareVideoTitle": "Share a video",
213
         "shareVideoLinkError": "Please provide a correct youtube link.",
223
         "shareVideoLinkError": "Please provide a correct youtube link.",
214
         "removeSharedVideoTitle": "Remove shared video",
224
         "removeSharedVideoTitle": "Remove shared video",
218
         "WaitForHostMsg": "The conference <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
228
         "WaitForHostMsg": "The conference <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
219
         "IamHost": "I am the host",
229
         "IamHost": "I am the host",
220
         "Cancel": "Cancel",
230
         "Cancel": "Cancel",
231
+        "Submit": "Submit",
221
         "retry": "Retry",
232
         "retry": "Retry",
222
         "logoutTitle" : "Logout",
233
         "logoutTitle" : "Logout",
223
         "logoutQuestion" : "Are you sure you want to logout and stop the conference?",
234
         "logoutQuestion" : "Are you sure you want to logout and stop the conference?",
231
         "Dial": "Dial",
242
         "Dial": "Dial",
232
         "sipMsg": "Enter SIP number",
243
         "sipMsg": "Enter SIP number",
233
         "passwordCheck": "Are you sure you would like to remove your password?",
244
         "passwordCheck": "Are you sure you would like to remove your password?",
234
-        "Remove": "Remove",
235
         "passwordMsg": "Set a password to lock your room",
245
         "passwordMsg": "Set a password to lock your room",
236
         "shareLink": "Copy and share this link",
246
         "shareLink": "Copy and share this link",
237
         "settings1": "Configure your conference",
247
         "settings1": "Configure your conference",

+ 59
- 60
modules/UI/UI.js View File

4
 import Chat from "./side_pannels/chat/Chat";
4
 import Chat from "./side_pannels/chat/Chat";
5
 import Toolbar from "./toolbars/Toolbar";
5
 import Toolbar from "./toolbars/Toolbar";
6
 import ToolbarToggler from "./toolbars/ToolbarToggler";
6
 import ToolbarToggler from "./toolbars/ToolbarToggler";
7
-import ContactList from "./side_pannels/contactlist/ContactList";
8
 import Avatar from "./avatar/Avatar";
7
 import Avatar from "./avatar/Avatar";
9
 import SideContainerToggler from "./side_pannels/SideContainerToggler";
8
 import SideContainerToggler from "./side_pannels/SideContainerToggler";
10
 import UIUtil from "./util/UIUtil";
9
 import UIUtil from "./util/UIUtil";
29
 var messageHandler = UI.messageHandler;
28
 var messageHandler = UI.messageHandler;
30
 var JitsiPopover = require("./util/JitsiPopover");
29
 var JitsiPopover = require("./util/JitsiPopover");
31
 var Feedback = require("./feedback/Feedback");
30
 var Feedback = require("./feedback/Feedback");
32
-
33
 import FollowMe from "../FollowMe";
31
 import FollowMe from "../FollowMe";
34
 
32
 
35
 var eventEmitter = new EventEmitter();
33
 var eventEmitter = new EventEmitter();
242
  * @param {string} displayName new nickname
240
  * @param {string} displayName new nickname
243
  */
241
  */
244
 UI.changeDisplayName = function (id, displayName) {
242
 UI.changeDisplayName = function (id, displayName) {
245
-    ContactList.onDisplayNameChange(id, displayName);
243
+    UI.ContactList.onDisplayNameChange(id, displayName);
246
     VideoLayout.onDisplayNameChanged(id, displayName);
244
     VideoLayout.onDisplayNameChanged(id, displayName);
247
 
245
 
248
     if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
246
     if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
292
     // "https:" + "//" + "example.com:8888" + "/SomeConference1245"
290
     // "https:" + "//" + "example.com:8888" + "/SomeConference1245"
293
     var inviteURL = window.location.protocol + "//" +
291
     var inviteURL = window.location.protocol + "//" +
294
         window.location.host + window.location.pathname;
292
         window.location.host + window.location.pathname;
295
-    Toolbar.updateRoomUrl(inviteURL);
293
+
294
+    this.emitEvent(UIEvents.INVITE_URL_INITIALISED, inviteURL);
295
+
296
     // Clean up the URL displayed by the browser
296
     // Clean up the URL displayed by the browser
297
     if (window.history && typeof window.history.replaceState === 'function') {
297
     if (window.history && typeof window.history.replaceState === 'function') {
298
         window.history.replaceState({}, document.title, inviteURL);
298
         window.history.replaceState({}, document.title, inviteURL);
299
     }
299
     }
300
 
300
 
301
     // Add myself to the contact list.
301
     // Add myself to the contact list.
302
-    ContactList.addContact(id, true);
302
+    UI.ContactList.addContact(id, true);
303
 
303
 
304
     // Update default button states before showing the toolbar
304
     // Update default button states before showing the toolbar
305
     // if local role changes buttons state will be again updated.
305
     // if local role changes buttons state will be again updated.
470
     }
470
     }
471
     VideoLayout.resizeVideoArea(true, true);
471
     VideoLayout.resizeVideoArea(true, true);
472
 
472
 
473
-    ContactList.init(eventEmitter);
474
-
475
     bindEvents();
473
     bindEvents();
476
     sharedVideoManager = new SharedVideoManager(eventEmitter);
474
     sharedVideoManager = new SharedVideoManager(eventEmitter);
477
     if (!interfaceConfig.filmStripOnly) {
475
     if (!interfaceConfig.filmStripOnly) {
608
     var id = user.getId();
606
     var id = user.getId();
609
     var displayName = user.getDisplayName();
607
     var displayName = user.getDisplayName();
610
     UI.hideRingOverLay();
608
     UI.hideRingOverLay();
611
-    ContactList.addContact(id);
609
+    UI.ContactList.addContact(id);
612
 
610
 
613
     messageHandler.notify(
611
     messageHandler.notify(
614
         displayName,'notify.somebody', 'connected', 'notify.connected'
612
         displayName,'notify.somebody', 'connected', 'notify.connected'
635
  * @param {string} displayName user nickname
633
  * @param {string} displayName user nickname
636
  */
634
  */
637
 UI.removeUser = function (id, displayName) {
635
 UI.removeUser = function (id, displayName) {
638
-    ContactList.removeContact(id);
636
+    UI.ContactList.removeContact(id);
639
 
637
 
640
     messageHandler.notify(
638
     messageHandler.notify(
641
         displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
639
         displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
786
 // FIXME check if someone user this
784
 // FIXME check if someone user this
787
 UI.showLoginPopup = function(callback) {
785
 UI.showLoginPopup = function(callback) {
788
     console.log('password is required');
786
     console.log('password is required');
789
-    var message = '<h2 data-i18n="dialog.passwordRequired">';
790
-    message += APP.translation.translateString(
791
-        "dialog.passwordRequired");
792
-    message += '</h2>' +
793
-        '<input name="username" type="text" ' +
794
-        'placeholder="user@domain.net" autofocus>' +
795
-        '<input name="password" ' +
796
-        'type="password" data-i18n="[placeholder]dialog.userPassword"' +
797
-        ' placeholder="user password">';
798
-    messageHandler.openTwoButtonDialog(null, null, null, message,
799
-        true,
800
-        "dialog.Ok",
801
-        function (e, v, m, f) {
802
-            if (v) {
803
-                if (f.username && f.password) {
804
-                    callback(f.username, f.password);
805
-                }
787
+    let titleKey = "dialog.passwordRequired";
788
+    let titleString = APP.translation.translateString(titleKey);
789
+
790
+    let message = (
791
+        `<input name="username" type="text"
792
+                placeholder="user@domain.net" autofocus>
793
+         <input name="password" type="password"
794
+                data-i18n="[placeholder]dialog.userPassword"
795
+                placeholder="user password">`
796
+    );
797
+
798
+    let submitFunction = (e, v, m, f) => {
799
+        if (v) {
800
+            if (f.username && f.password) {
801
+                callback(f.username, f.password);
806
             }
802
             }
807
-        },
808
-        null, null, ':input:first'
803
+        }
804
+    };
809
 
805
 
810
-    );
806
+    messageHandler.openTwoButtonDialog({
807
+        titleKey,
808
+        titleString,
809
+        msgString: message,
810
+        leftButtonKey: 'dialog.Ok',
811
+        submitFunction,
812
+        focus: ':input:first'
813
+    });
811
 };
814
 };
812
 
815
 
813
 UI.askForNickname = function () {
816
 UI.askForNickname = function () {
888
  */
891
  */
889
 function changeAvatar(id, avatarUrl) {
892
 function changeAvatar(id, avatarUrl) {
890
     VideoLayout.changeUserAvatar(id, avatarUrl);
893
     VideoLayout.changeUserAvatar(id, avatarUrl);
891
-    ContactList.changeUserAvatar(id, avatarUrl);
894
+    UI.ContactList.changeUserAvatar(id, avatarUrl);
892
     if (APP.conference.isLocalId(id)) {
895
     if (APP.conference.isLocalId(id)) {
893
         Profile.changeAvatar(avatarUrl);
896
         Profile.changeAvatar(avatarUrl);
894
     }
897
     }
1054
     }
1057
     }
1055
 };
1058
 };
1056
 
1059
 
1057
-/**
1058
- * Mark room as locked or not.
1059
- * @param {boolean} locked if room is locked.
1060
- */
1061
-UI.markRoomLocked = function (locked) {
1062
-    if (locked) {
1063
-        Toolbar.lockLockButton();
1064
-    } else {
1065
-        Toolbar.unlockLockButton();
1066
-    }
1067
-};
1068
-
1069
 /**
1060
 /**
1070
  * Add chat message.
1061
  * Add chat message.
1071
  * @param {string} from user id
1062
  * @param {string} from user id
1254
  * @param url {string} the url of the extension.
1245
  * @param url {string} the url of the extension.
1255
  */
1246
  */
1256
 UI.showExtensionExternalInstallationDialog = function (url) {
1247
 UI.showExtensionExternalInstallationDialog = function (url) {
1257
-    messageHandler.openTwoButtonDialog(
1258
-        "dialog.externalInstallationTitle",
1259
-        null,
1260
-        "dialog.externalInstallationMsg",
1261
-        null,
1262
-        true,
1263
-        "dialog.goToStore",
1264
-        function(e,v) {
1265
-            if (v) {
1266
-                e.preventDefault();
1267
-                eventEmitter.emit(UIEvents.OPEN_EXTENSION_STORE, url);
1268
-            }
1269
-        },
1270
-        function () {},
1271
-        function () {
1272
-            eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
1248
+    let submitFunction = function(e,v){
1249
+        if (v) {
1250
+            e.preventDefault();
1251
+            eventEmitter.emit(UIEvents.OPEN_EXTENSION_STORE, url);
1273
         }
1252
         }
1274
-    );
1253
+    };
1254
+
1255
+    let closeFunction = function () {
1256
+        eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
1257
+    };
1258
+
1259
+    messageHandler.openTwoButtonDialog({
1260
+        titleKey: 'dialog.externalInstallationTitle',
1261
+        titleString: '',
1262
+        msgKey: 'dialog.externalInstallationMsg',
1263
+        msgString: '',
1264
+        leftButtonKey: 'dialog.goToStore',
1265
+        submitFunction,
1266
+        loadedFunction: $.noop,
1267
+        closeFunction
1268
+    });
1275
 };
1269
 };
1276
 
1270
 
1277
 
1271
 
1518
  * Shows or hides the keyboard shortcuts panel, depending on the current state.'
1512
  * Shows or hides the keyboard shortcuts panel, depending on the current state.'
1519
  */
1513
  */
1520
 UI.toggleKeyboardShortcutsPanel = function() {
1514
 UI.toggleKeyboardShortcutsPanel = function() {
1521
-    $('#keyboard-shortcuts').toggle();
1515
+    let titleKey = 'keyboardShortcuts.keyboardShortcuts';
1516
+    let title = APP.translation.translateString(titleKey);
1517
+    let msg = $('#keyboard-shortcuts').html();
1518
+    let buttons = { Close: true };
1519
+
1520
+    messageHandler.openDialog(title, msg, true, buttons);
1522
 };
1521
 };
1523
 
1522
 
1524
 /**
1523
 /**

+ 0
- 244
modules/UI/authentication/RoomLocker.js View File

1
-/* global APP, JitsiMeetJS */
2
-import UIUtil from '../util/UIUtil';
3
-
4
-/**
5
- * Show dialog which asks user for new password for the conference.
6
- * @returns {Promise<string>} password or nothing if user canceled
7
- */
8
-function askForNewPassword () {
9
-    let passMsg = APP.translation.generateTranslationHTML("dialog.passwordMsg");
10
-    let yourPassMsg = APP.translation.translateString("dialog.yourPassword");
11
-    let msg = `
12
-        <h2>${passMsg}</h2>
13
-        <input name="lockKey" type="text"
14
-               data-i18n="[placeholder]dialog.yourPassword"
15
-               placeholder="${yourPassMsg}" autofocus>
16
-    `;
17
-
18
-    return new Promise(function (resolve, reject) {
19
-        APP.UI.messageHandler.openTwoButtonDialog(
20
-            null, null, null,
21
-            msg, false, "dialog.Save",
22
-            function (e, v, m, f) {
23
-                if (v && f.lockKey) {
24
-                    resolve(UIUtil.escapeHtml(f.lockKey));
25
-                }
26
-                else {
27
-                    reject(APP.UI.messageHandler.CANCEL);
28
-                }
29
-            },
30
-            null, null, 'input:first'
31
-        );
32
-    });
33
-}
34
-
35
-/**
36
- * Show dialog which asks for required conference password.
37
- * @returns {Promise<string>} password or nothing if user canceled
38
- */
39
-function askForPassword () {
40
-    let passRequiredMsg = APP.translation.translateString(
41
-        "dialog.passwordRequired"
42
-    );
43
-    let passMsg = APP.translation.translateString("dialog.password");
44
-    let msg = `
45
-        <h2 data-i18n="dialog.passwordRequired">${passRequiredMsg}</h2>
46
-        <input name="lockKey" type="text"
47
-               data-i18n="[placeholder]dialog.password"
48
-               placeholder="${passMsg}" autofocus>
49
-    `;
50
-    return new Promise(function (resolve, reject) {
51
-        APP.UI.messageHandler.openTwoButtonDialog(
52
-            null, null, null, msg,
53
-            true, "dialog.Ok",
54
-            function () {}, null,
55
-            function (e, v, m, f) {
56
-                if (v && f.lockKey) {
57
-                    resolve(UIUtil.escapeHtml(f.lockKey));
58
-                } else {
59
-                    reject(APP.UI.messageHandler.CANCEL);
60
-                }
61
-            },
62
-            ':input:first'
63
-        );
64
-    });
65
-}
66
-
67
-/**
68
- * Show dialog which asks if user want remove password from the conference.
69
- * @returns {Promise}
70
- */
71
-function askToUnlock () {
72
-    return new Promise(function (resolve, reject) {
73
-        APP.UI.messageHandler.openTwoButtonDialog(
74
-            null, null, "dialog.passwordCheck",
75
-            null, false, "dialog.Remove",
76
-            function (e, v) {
77
-                if (v) {
78
-                    resolve();
79
-                } else {
80
-                    reject(APP.UI.messageHandler.CANCEL);
81
-                }
82
-            }
83
-        );
84
-    });
85
-}
86
-
87
-/**
88
- * Show notification that user cannot set password for the conference
89
- * because server doesn't support that.
90
- */
91
-function notifyPasswordNotSupported () {
92
-    console.warn('room passwords not supported');
93
-    APP.UI.messageHandler.showError(
94
-        "dialog.warning", "dialog.passwordNotSupported");
95
-}
96
-
97
-/**
98
- * Show notification that setting password for the conference failed.
99
- * @param {Error} err error
100
- */
101
-function notifyPasswordFailed(err) {
102
-    console.warn('setting password failed', err);
103
-    APP.UI.messageHandler.showError(
104
-        "dialog.lockTitle", "dialog.lockMessage");
105
-}
106
-
107
-const ConferenceErrors = JitsiMeetJS.errors.conference;
108
-
109
-/**
110
- * Create new RoomLocker for the conference.
111
- * It allows to set or remove password for the conference,
112
- * or ask for required password.
113
- * @returns {RoomLocker}
114
- */
115
-export default function createRoomLocker (room) {
116
-    let password;
117
-    let dialog = null;
118
-
119
-    /**
120
-     * If the room was locked from someone other than us, we indicate it with
121
-     * this property in order to have correct roomLocker state of isLocked.
122
-     * @type {boolean} whether room is locked, but not from us.
123
-     */
124
-    let lockedElsewhere = false;
125
-
126
-    function lock (newPass) {
127
-        return room.lock(newPass).then(function () {
128
-            password = newPass;
129
-        }).catch(function (err) {
130
-            console.error(err);
131
-            if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
132
-                notifyPasswordNotSupported();
133
-            } else {
134
-                notifyPasswordFailed(err);
135
-            }
136
-            throw err;
137
-        });
138
-    }
139
-
140
-    /**
141
-     * @class RoomLocker
142
-     */
143
-    return {
144
-        get isLocked () {
145
-            return !!password || lockedElsewhere;
146
-        },
147
-
148
-        get password () {
149
-            return password;
150
-        },
151
-
152
-        /**
153
-         * Sets that the room is locked from another user, not us.
154
-         * @param {boolean} value locked/unlocked state
155
-         */
156
-        set lockedElsewhere (value) {
157
-            lockedElsewhere = value;
158
-        },
159
-
160
-        /**
161
-         * Whether room is locked from someone else.
162
-         * @returns {boolean} whether room is not locked locally,
163
-         * but it is still locked.
164
-         */
165
-        get lockedElsewhere () {
166
-            return lockedElsewhere;
167
-        },
168
-
169
-        /**
170
-         * Allows to remove password from the conference (asks user first).
171
-         * @returns {Promise}
172
-         */
173
-        askToUnlock () {
174
-            return askToUnlock().then(
175
-                () => { return lock(); }
176
-            ).then(function () {
177
-                JitsiMeetJS.analytics.sendEvent('toolbar.lock.disabled');
178
-            }).catch(
179
-                reason => {
180
-                    if (reason !== APP.UI.messageHandler.CANCEL)
181
-                        console.error(reason);
182
-                }
183
-            );
184
-        },
185
-
186
-        /**
187
-         * Allows to set password for the conference.
188
-         * It asks user for new password and locks the room.
189
-         * @returns {Promise}
190
-         */
191
-        askToLock () {
192
-            return askForNewPassword().then(
193
-                newPass => { return lock(newPass);}
194
-            ).then(function () {
195
-                JitsiMeetJS.analytics.sendEvent('toolbar.lock.enabled');
196
-            }).catch(
197
-                reason => {
198
-                    if (reason !== APP.UI.messageHandler.CANCEL)
199
-                        console.error(reason);
200
-                }
201
-            );
202
-        },
203
-
204
-        /**
205
-         * Asks user for required conference password.
206
-         */
207
-        requirePassword () {
208
-            return askForPassword().then(
209
-                newPass => { password = newPass; }
210
-            ).catch(
211
-                reason => {
212
-                    // user canceled, no pass was entered.
213
-                    // clear, as if we use the same instance several times
214
-                    // pass stays between attempts
215
-                    password = null;
216
-                    if (reason !== APP.UI.messageHandler.CANCEL)
217
-                        console.error(reason);
218
-                }
219
-            );
220
-        },
221
-
222
-        /**
223
-         * Show notification that to set/remove password user must be moderator.
224
-         */
225
-        notifyModeratorRequired () {
226
-            if (dialog)
227
-                return;
228
-
229
-            let closeCallback = function () {
230
-                dialog = null;
231
-            };
232
-
233
-            if (this.isLocked) {
234
-                dialog = APP.UI.messageHandler
235
-                    .openMessageDialog(null, "dialog.passwordError",
236
-                        null, null, closeCallback);
237
-            } else {
238
-                dialog = APP.UI.messageHandler
239
-                    .openMessageDialog(null, "dialog.passwordError2",
240
-                        null, null, closeCallback);
241
-            }
242
-        }
243
-    };
244
-}

+ 2
- 2
modules/UI/feedback/Feedback.js View File

1
 /* global $, APP, JitsiMeetJS */
1
 /* global $, APP, JitsiMeetJS */
2
 import UIEvents from "../../../service/UI/UIEvents";
2
 import UIEvents from "../../../service/UI/UIEvents";
3
-import FeedabckWindow from "./FeedbackWindow";
3
+import FeedbackWindow from "./FeedbackWindow";
4
 
4
 
5
 /**
5
 /**
6
  * Shows / hides the feedback button.
6
  * Shows / hides the feedback button.
49
 
49
 
50
         _showFeedbackButton(this.enabled);
50
         _showFeedbackButton(this.enabled);
51
 
51
 
52
-        this.window = new FeedabckWindow({});
52
+        this.window = new FeedbackWindow();
53
 
53
 
54
         $("#feedbackButton").click(Feedback.openFeedbackWindow);
54
         $("#feedbackButton").click(Feedback.openFeedbackWindow);
55
 
55
 

+ 107
- 119
modules/UI/feedback/FeedbackWindow.js View File

1
-/* global $, APP, interfaceConfig, AJS */
2
-
3
-const selector = '#aui-feedback-dialog';
1
+/* global $, APP, interfaceConfig */
2
+
3
+const labels = {
4
+    1: 'Very Bad',
5
+    2: 'Bad',
6
+    3: 'Average',
7
+    4: 'Good',
8
+    5: 'Very Good'
9
+};
4
 
10
 
5
 /**
11
 /**
6
  * Toggles the appropriate css class for the given number of stars, to
12
  * Toggles the appropriate css class for the given number of stars, to
9
  * @param starCount the number of stars, for which to toggle the css class
15
  * @param starCount the number of stars, for which to toggle the css class
10
  */
16
  */
11
 function toggleStars(starCount) {
17
 function toggleStars(starCount) {
18
+    let labelEl = $('#starLabel');
19
+    let label = starCount >= 0 ?
20
+        labels[starCount + 1] :
21
+        '';
22
+
12
     $('#stars > a').each(function(index, el) {
23
     $('#stars > a').each(function(index, el) {
13
         if (index <= starCount) {
24
         if (index <= starCount) {
14
             el.classList.add("starHover");
25
             el.classList.add("starHover");
15
         } else
26
         } else
16
             el.classList.remove("starHover");
27
             el.classList.remove("starHover");
17
     });
28
     });
29
+    labelEl.text(label);
18
 }
30
 }
19
 
31
 
20
 /**
32
 /**
23
  * @returns {string} the contructed html string
35
  * @returns {string} the contructed html string
24
  */
36
  */
25
 function createRateFeedbackHTML() {
37
 function createRateFeedbackHTML() {
26
-    let rateExperience
27
-            = APP.translation.translateString('dialog.rateExperience'),
28
-        feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
38
+    let feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
29
 
39
 
30
     let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION)
40
     let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION)
31
-                            ? "icon-star shake-rotate"
32
-                            : "icon-star";
41
+        ? "icon-star-full shake-rotate"
42
+        : "icon-star-full";
33
 
43
 
34
     return `
44
     return `
35
-        <div class="aui-dialog2-content feedback__content">
36
-            <form action="javascript:false;" onsubmit="return false;">
37
-                <div class="feedback__rating">
38
-                    <h2>${ rateExperience }</h2>
39
-                    <p class="star-label">&nbsp;</p>
40
-                    <div id="stars" class="feedback-stars">
41
-                        <a class="star-btn">
42
-                            <i class=${ starClassName }></i>
43
-                        </a>
44
-                        <a class="star-btn">
45
-                            <i class=${ starClassName }></i>
46
-                        </a>
47
-                        <a class="star-btn">
48
-                            <i class=${ starClassName }></i>
49
-                        </a>
50
-                        <a class="star-btn">
51
-                            <i class=${ starClassName }></i>
52
-                        </a>
53
-                        <a class="star-btn">
54
-                            <i class=${ starClassName }></i>
55
-                        </a>
56
-                    </div>
57
-                    <p>&nbsp;</p>
58
-                    <p>${ feedbackHelp }</p>
45
+        <form id="feedbackForm"
46
+            action="javascript:false;" onsubmit="return false;">
47
+            <div class="rating">
48
+                <div class="star-label">
49
+                    <p id="starLabel">&nbsp;</p>
59
                 </div>
50
                 </div>
60
-                <textarea id="feedbackTextArea" rows="10" cols="40" autofocus>
61
-                </textarea>
62
-            </form>
63
-            <footer class="aui-dialog2-footer feedback__footer">
64
-                <div class="aui-dialog2-footer-actions">
65
-                    <button
66
-                        id="dialog-close-button"
67
-                        class="aui-button aui-button_close">Close</button>
68
-                    <button
69
-                        id="dialog-submit-button"
70
-                        class="aui-button aui-button_submit">Submit</button>
51
+                <div id="stars" class="feedback-stars">
52
+                    <a class="star-btn">
53
+                        <i class=${ starClassName }></i>
54
+                    </a>
55
+                    <a class="star-btn">
56
+                        <i class=${ starClassName }></i>
57
+                    </a>
58
+                    <a class="star-btn">
59
+                        <i class=${ starClassName }></i>
60
+                    </a>
61
+                    <a class="star-btn">
62
+                        <i class=${ starClassName }></i>
63
+                    </a>
64
+                    <a class="star-btn">
65
+                        <i class=${ starClassName }></i>
66
+                    </a>
71
                 </div>
67
                 </div>
72
-            </footer>
73
-        </div>
74
-`;
68
+            </div>
69
+            <div class="details">
70
+                <textarea id="feedbackTextArea" class="input-control__input"
71
+                    placeholder="${ feedbackHelp }"></textarea>
72
+            </div>
73
+        </form>`;
75
 }
74
 }
76
 
75
 
77
 /**
76
 /**
78
- * Callback for Rate Feedback
77
+ * Feedback is loaded callback
78
+ * Calls when Modal window is in DOM
79
  *
79
  *
80
  * @param Feedback
80
  * @param Feedback
81
  */
81
  */
82
-let onLoadRateFunction = function (Feedback) {
82
+let onLoadFunction = function (Feedback) {
83
     $('#stars > a').each((index, el) => {
83
     $('#stars > a').each((index, el) => {
84
         el.onmouseover = function(){
84
         el.onmouseover = function(){
85
             toggleStars(index);
85
             toggleStars(index);
89
         };
89
         };
90
         el.onclick = function(){
90
         el.onclick = function(){
91
             Feedback.feedbackScore = index + 1;
91
             Feedback.feedbackScore = index + 1;
92
+            Feedback.setFeedbackMessage();
92
         };
93
         };
93
     });
94
     });
94
 
95
 
97
         toggleStars(Feedback.feedbackScore - 1);
98
         toggleStars(Feedback.feedbackScore - 1);
98
     }
99
     }
99
 
100
 
100
-    if (Feedback.feedbackText && Feedback.feedbackText.length > 0)
101
-        $('#feedbackTextArea').text(Feedback.feedbackText);
102
-
103
-    let submitBtn = Feedback.$el.find('#dialog-submit-button');
104
-    let closeBtn = Feedback.$el.find('#dialog-close-button');
105
-
106
-    if (submitBtn && submitBtn.length) {
107
-        submitBtn.on('click', (e) => {
108
-            e.preventDefault();
109
-            Feedback.onFeedbackSubmitted();
110
-        });
111
-    }
112
-    if (closeBtn && closeBtn.length) {
113
-        closeBtn.on('click', (e) => {
114
-            e.preventDefault();
115
-            Feedback.hide();
116
-        });
117
-    }
101
+    if (Feedback.feedbackMessage && Feedback.feedbackMessage.length > 0)
102
+        $('#feedbackTextArea').text(Feedback.feedbackMessage);
118
 
103
 
119
     $('#feedbackTextArea').focus();
104
     $('#feedbackTextArea').focus();
120
 };
105
 };
121
 
106
 
107
+/**
108
+ * On Feedback Submitted callback
109
+ *
110
+ * @param Feedback
111
+ */
112
+function onFeedbackSubmitted(Feedback) {
113
+    let form = $('#feedbackForm');
114
+    let message = form.find('textarea').val();
115
+
116
+    APP.conference.sendFeedback(
117
+        Feedback.feedbackScore,
118
+        message);
119
+
120
+    // TODO: make sendFeedback return true or false.
121
+    Feedback.submitted = true;
122
+
123
+    //Remove history is submitted
124
+    Feedback.feedbackScore = -1;
125
+    Feedback.feedbackMessage = '';
126
+    Feedback.onHide();
127
+}
128
+
129
+/**
130
+ * On Feedback Closed callback
131
+ *
132
+ * @param Feedback
133
+ */
134
+function onFeedbackClosed(Feedback) {
135
+    Feedback.onHide();
136
+}
137
+
122
 /**
138
 /**
123
  * @class Dialog
139
  * @class Dialog
124
  *
140
  *
125
  */
141
  */
126
 export default class Dialog {
142
 export default class Dialog {
127
 
143
 
128
-    constructor(options) {
144
+    constructor() {
129
         this.feedbackScore = -1;
145
         this.feedbackScore = -1;
130
-        this.feedbackText = null;
146
+        this.feedbackMessage = '';
131
         this.submitted = false;
147
         this.submitted = false;
132
-        this.onCloseCallback = null;
133
-
134
-        this.states = {
135
-            rate_feedback: {
136
-                getHtml: createRateFeedbackHTML,
137
-                onLoad: onLoadRateFunction
138
-            }
139
-        };
140
-        this.state = options.state || 'rate_feedback';
148
+        this.onCloseCallback = function() {};
141
 
149
 
142
-        this.window = AJS.dialog2(selector, {
143
-            closeOnOutsideClick: true
144
-        });
145
-        this.$el = this.window.$el;
146
-
147
-        AJS.dialog2(selector).on("hide", function() {
148
-            if (this.onCloseCallback) {
149
-                this.onCloseCallback();
150
-                this.onCloseCallback = null;
151
-            }
152
-        }.bind(this));
153
-
154
-        this.setState();
150
+        this.setDefoulOptions();
155
     }
151
     }
156
 
152
 
157
-    setState(state) {
158
-        let newState = state || this.state;
159
-
160
-        let htmlStr = this.states[newState].getHtml(this);
153
+    setDefoulOptions() {
154
+        var self = this;
155
+
156
+        this.options = {
157
+            titleKey: 'dialog.rateExperience',
158
+            msgString: createRateFeedbackHTML(),
159
+            loadedFunction: function() {onLoadFunction(self);},
160
+            submitFunction: function() {onFeedbackSubmitted(self);},
161
+            closeFunction: function() {onFeedbackClosed(self);},
162
+            wrapperClass: 'feedback',
163
+            size: 'medium'
164
+        };
165
+    }
161
 
166
 
162
-        this.$el.html(htmlStr);
167
+    setFeedbackMessage() {
168
+        let message = $('#feedbackTextArea').val();
163
 
169
 
164
-        this.states[newState].onLoad(this);
170
+        this.feedbackMessage = message;
165
     }
171
     }
166
 
172
 
167
     show(cb) {
173
     show(cb) {
168
-        this.setState('rate_feedback');
169
-        if (typeof cb == 'function') {
174
+        const options = this.options;
175
+        if (typeof cb === 'function') {
170
             this.onCloseCallback = cb;
176
             this.onCloseCallback = cb;
171
         }
177
         }
172
 
178
 
173
-        this.window.show();
174
-
179
+        this.window = APP.UI.messageHandler.openTwoButtonDialog(options);
175
     }
180
     }
176
 
181
 
177
-    hide() {
178
-        this.window.hide();
179
-    }
180
-
181
-    onFeedbackSubmitted() {
182
-        let message = this.$el.find('textarea').val();
183
-        let self = this;
184
-
185
-        if (message && message.length > 0) {
186
-            self.feedbackText = message;
187
-        }
188
-
189
-        APP.conference.sendFeedback(self.feedbackScore,
190
-                                    self.feedbackText);
191
-
192
-        // TO DO: make sendFeedback return true or false.
193
-        self.submitted = true;
194
-
195
-        this.hide();
182
+    onHide() {
183
+        this.onCloseCallback(this.feedbackScore, this.feedbackMessage);
196
     }
184
     }
197
 }
185
 }

+ 33
- 0
modules/UI/invite/AskForPassword.js View File

1
+/* global APP, $ */
2
+
3
+import UIUtil from '../util/UIUtil';
4
+
5
+/**
6
+ * Show dialog which asks for required conference password.
7
+ * @returns {Promise<string>} password or nothing if user canceled
8
+ */
9
+export default function askForPassword () {
10
+    let titleKey = "dialog.passwordRequired";
11
+    let passMsg = APP.translation.translateString("dialog.password");
12
+    let msgString = `
13
+        <input name="lockKey" type="text"
14
+               data-i18n="[placeholder]dialog.password"
15
+               placeholder="${passMsg}" autofocus>
16
+    `;
17
+    return new Promise(function (resolve, reject) {
18
+        APP.UI.messageHandler.openTwoButtonDialog({
19
+            titleKey,
20
+            msgString,
21
+            leftButtonKey: "dialog.Ok",
22
+            submitFunction: $.noop,
23
+            closeFunction: function (e, v, m, f) {
24
+                if (v && f.lockKey) {
25
+                    resolve(UIUtil.escapeHtml(f.lockKey));
26
+                } else {
27
+                    reject(APP.UI.messageHandler.CANCEL);
28
+                }
29
+            },
30
+            focus: ':input:first'
31
+        });
32
+    });
33
+}

+ 216
- 0
modules/UI/invite/Invite.js View File

1
+/* global JitsiMeetJS, APP */
2
+
3
+import InviteDialogView from './InviteDialogView';
4
+import createRoomLocker from './RoomLocker';
5
+import UIEvents from  '../../../service/UI/UIEvents';
6
+
7
+const ConferenceEvents = JitsiMeetJS.events.conference;
8
+
9
+/**
10
+ * Invite module
11
+ * Constructor takes conference object giving
12
+ * ability to subscribe on its events
13
+ */
14
+class Invite {
15
+    constructor(conference) {
16
+        this.conference = conference;
17
+        this.createRoomLocker(conference);
18
+        this.initDialog();
19
+        this.registerListeners();
20
+    }
21
+
22
+    /**
23
+     * Registering listeners.
24
+     * Primarily listeners for conference events.
25
+     */
26
+    registerListeners() {
27
+
28
+        this.conference.on(ConferenceEvents.LOCK_STATE_CHANGED,
29
+            (locked, error) => {
30
+
31
+            console.log("Received channel password lock change: ", locked,
32
+                error);
33
+
34
+            if (!locked) {
35
+                this.roomLocker.resetPassword();
36
+            }
37
+
38
+            this.setLockedFromElsewhere(locked);
39
+        });
40
+
41
+        this.conference.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
42
+            if (APP.conference.isLocalId(id)
43
+                    && this.isModerator !== this.conference.isModerator) {
44
+
45
+                this.setModerator(this.conference.isModerator);
46
+            }
47
+        });
48
+
49
+        APP.UI.addListener( UIEvents.INVITE_CLICKED,
50
+                            () => { this.openLinkDialog(); });
51
+
52
+        APP.UI.addListener( UIEvents.INVITE_URL_INITIALISED,
53
+                            (inviteUrl) => {
54
+                                this.updateInviteUrl(inviteUrl);
55
+                            });
56
+
57
+        APP.UI.addListener( UIEvents.PASSWORD_REQUIRED,
58
+            () => {
59
+                this.setLockedFromElsewhere(true);
60
+                this.roomLocker.requirePassword().then(() => {
61
+                    let pass = this.roomLocker.password;
62
+                    // we received that password is required, but user is trying
63
+                    // anyway to login without a password, mark room as not
64
+                    // locked in case he succeeds (maybe someone removed the
65
+                    // password meanwhile), if it is still locked another
66
+                    // password required will be received and the room again
67
+                    // will be marked as locked.
68
+                    if (!pass)
69
+                        this.setLockedFromElsewhere(false);
70
+                    this.conference.join(this.roomLocker.password);
71
+                });
72
+            });
73
+    }
74
+
75
+    /**
76
+     * Updates the view.
77
+     * If dialog hasn't been defined -
78
+     * creates it and updates
79
+     */
80
+    updateView() {
81
+        if (!this.view) {
82
+            this.initDialog();
83
+        }
84
+
85
+        this.view.updateView();
86
+    }
87
+
88
+    /**
89
+     * Room locker factory
90
+     * @param room
91
+     * @returns {Object} RoomLocker
92
+     * @factory
93
+     */
94
+    createRoomLocker(room = this.conference) {
95
+        let roomLocker = createRoomLocker(room);
96
+        this.roomLocker = roomLocker;
97
+        return this.getRoomLocker();
98
+    }
99
+
100
+    /**
101
+     * Room locker getter
102
+     * @returns {Object} RoomLocker
103
+     */
104
+    getRoomLocker() {
105
+        return this.roomLocker;
106
+    }
107
+
108
+    /**
109
+     * Opens the invite link dialog.
110
+     */
111
+    openLinkDialog () {
112
+        if (!this.view) {
113
+            this.initDialog();
114
+        }
115
+
116
+        this.view.open();
117
+    }
118
+
119
+    /**
120
+     * Dialog initialization.
121
+     * creating view object using as a model this module
122
+     */
123
+    initDialog() {
124
+        this.password = this.getPassword();
125
+        this.view = new InviteDialogView(this);
126
+    }
127
+
128
+    /**
129
+     * Password getter
130
+     * @returns {String} password
131
+     */
132
+    getPassword() {
133
+        return this.roomLocker.password;
134
+    }
135
+
136
+    /**
137
+     * Switches between the moderator view and normal view.
138
+     *
139
+     * @param isModerator indicates if the participant is moderator
140
+     */
141
+    setModerator(isModerator) {
142
+        this.isModerator = isModerator;
143
+
144
+        this.updateView();
145
+    }
146
+
147
+    /**
148
+     * Allows to unlock the room.
149
+     * If the current user is moderator.
150
+     */
151
+    setRoomUnlocked() {
152
+        if (this.isModerator) {
153
+            this.roomLocker.lock().then(() => {
154
+                APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
155
+                this.updateView();
156
+            });
157
+        }
158
+    }
159
+
160
+    /**
161
+     * Allows to lock the room if
162
+     * the current user is moderator.
163
+     * Takes the password.
164
+     * @param {String} newPass
165
+     */
166
+    setRoomLocked(newPass) {
167
+        let isModerator = this.isModerator;
168
+        if (isModerator && (newPass || !this.roomLocker.isLocked)) {
169
+            this.roomLocker.lock(newPass).then(() => {
170
+                APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
171
+                this.updateView();
172
+            });
173
+        }
174
+    }
175
+
176
+    /**
177
+     * Updates the room invite url.
178
+     */
179
+    updateInviteUrl (newInviteUrl) {
180
+        this.inviteUrl = newInviteUrl;
181
+        this.updateView();
182
+    }
183
+
184
+    /**
185
+     * Helper method for encoding
186
+     * Invite URL
187
+     * @returns {string}
188
+     */
189
+    getEncodedInviteUrl() {
190
+        return encodeURI(this.inviteUrl);
191
+    }
192
+
193
+    /**
194
+     * Is locked flag.
195
+     * Delegates to room locker
196
+     * @returns {Boolean} isLocked
197
+     */
198
+    isLocked() {
199
+        return this.roomLocker.isLocked;
200
+    }
201
+
202
+    /**
203
+     * Set flag locked from elsewhere to room locker.
204
+     * @param isLocked
205
+     */
206
+    setLockedFromElsewhere(isLocked) {
207
+        let oldLockState = this.roomLocker.lockedElsewhere;
208
+        if (oldLockState !== isLocked) {
209
+            this.roomLocker.lockedElsewhere = isLocked;
210
+            APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
211
+            this.updateView();
212
+        }
213
+    }
214
+}
215
+
216
+export default Invite;

+ 382
- 0
modules/UI/invite/InviteDialogView.js View File

1
+/* global $, APP, JitsiMeetJS */
2
+
3
+/**
4
+ * Substate for password
5
+ * @type {{LOCKED: string, UNLOCKED: string}}
6
+ */
7
+const States = {
8
+    LOCKED: 'locked',
9
+    UNLOCKED: 'unlocked'
10
+};
11
+
12
+/**
13
+ * Class representing view for Invite dialog
14
+ * @class InviteDialogView
15
+ */
16
+export default class InviteDialogView {
17
+    constructor(model) {
18
+        let InviteAttributesKey = 'inviteUrlDefaultMsg';
19
+        let title = APP.translation.translateString(InviteAttributesKey);
20
+
21
+        this.unlockHint = "unlockHint";
22
+        this.lockHint = "lockHint";
23
+        this.model = model;
24
+
25
+        if (this.model.inviteUrl === null) {
26
+            this.inviteAttributes = (
27
+                `data-i18n="[value]inviteUrlDefaultMsg" value="${title}"`
28
+            );
29
+        } else {
30
+            let encodedInviteUrl = this.model.getEncodedInviteUrl();
31
+            this.inviteAttributes = `value="${encodedInviteUrl}"`;
32
+        }
33
+
34
+        this.initDialog();
35
+    }
36
+
37
+    /**
38
+     * Initialization of dialog property
39
+     */
40
+    initDialog() {
41
+        let dialog = {};
42
+        dialog.closeFunction = this.closeFunction.bind(this);
43
+        dialog.submitFunction = this.submitFunction.bind(this);
44
+        dialog.loadedFunction = this.loadedFunction.bind(this);
45
+
46
+        let titleKey = "dialog.shareLink";
47
+        let titleString = APP.translation.generateTranslationHTML(titleKey);
48
+
49
+        dialog.titleKey = titleKey;
50
+        dialog.titleString = titleString;
51
+        this.dialog = dialog;
52
+
53
+        this.dialog.states = this.getStates();
54
+    }
55
+
56
+    /**
57
+     * Event handler for submitting dialog
58
+     * @param e
59
+     * @param v
60
+     */
61
+    submitFunction(e, v) {
62
+        if (v && this.model.inviteUrl) {
63
+            JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
64
+        } else {
65
+            JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
66
+        }
67
+    }
68
+
69
+    /**
70
+     * Event handler for load dialog
71
+     * @param event
72
+     */
73
+    loadedFunction(event) {
74
+        if (this.model.inviteUrl) {
75
+            document.getElementById('inviteLinkRef').select();
76
+        } else {
77
+            if (event && event.target) {
78
+                $(event.target).find('button[value=true]')
79
+                    .prop('disabled', true);
80
+            }
81
+        }
82
+    }
83
+
84
+    /**
85
+     * Event handler for closing dialog
86
+     * @param e
87
+     * @param v
88
+     * @param m
89
+     * @param f
90
+     */
91
+    closeFunction(e, v, m, f) {
92
+        $(document).off('click', '.copyInviteLink', this.copyToClipboard);
93
+
94
+        if(!v && !m && !f)
95
+            JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
96
+    }
97
+
98
+    /**
99
+     * Returns all states of the dialog
100
+     * @returns {{}}
101
+     */
102
+    getStates() {
103
+        let {
104
+            titleString
105
+        } = this.dialog;
106
+
107
+        let states = {};
108
+
109
+        states[States.UNLOCKED] = {
110
+            title: titleString,
111
+            html: this.getShareLinkBlock() + this.getAddPasswordBlock()
112
+        };
113
+        states[States.LOCKED] = {
114
+            title: titleString,
115
+            html: this.getShareLinkBlock() + this.getPasswordBlock()
116
+        };
117
+
118
+        return states;
119
+    }
120
+
121
+    /**
122
+     * Layout for invite link input
123
+     * @returns {string}
124
+     */
125
+    getShareLinkBlock() {
126
+        let copyKey = 'dialog.copy';
127
+        let copyText = APP.translation.translateString(copyKey);
128
+        let roomLockDescKey = 'roomLocked';
129
+        let roomLockDesc = APP.translation.translateString(roomLockDescKey);
130
+        let roomUnlockKey = 'roomUnlocked';
131
+        let roomUnlock = APP.translation.translateString(roomUnlockKey);
132
+        let classes = 'button-control button-control_light copyInviteLink';
133
+        return (
134
+            `<div class="input-control">
135
+                <label class="input-control__label" for="inviteLinkRef">
136
+                    ${this.dialog.titleString}
137
+                </label>
138
+                <div class="input-control__container">
139
+                    <input class="input-control__input inviteLink"
140
+                           id="inviteLinkRef" type="text"
141
+                           ${this.inviteAttributes} readonly>
142
+                    <button data-i18n="${copyKey}"
143
+                            class="${classes}">
144
+                        ${copyText}
145
+                    </button>
146
+                </div>
147
+                <p class="input-control__hint ${this.lockHint}">
148
+                   <span class="icon-security-locked"></span>
149
+                   <span data-i18n="${roomLockDescKey}">${roomLockDesc}</span>
150
+                </p>
151
+                <p class="input-control__hint ${this.unlockHint}">
152
+                   <span class="icon-security"></span>
153
+                   <span data-i18n="${roomUnlockKey}">${roomUnlock}</span>
154
+                </p>
155
+            </div>`
156
+        );
157
+    }
158
+
159
+    /**
160
+     * Layout for adding password input
161
+     * @returns {string}
162
+     */
163
+    getAddPasswordBlock() {
164
+        let addPassKey = 'dialog.addPassword';
165
+        let addPassText = APP.translation.translateString(addPassKey);
166
+        let addKey = 'dialog.add';
167
+        let addText = APP.translation.translateString(addKey);
168
+        let html;
169
+
170
+        if (this.model.isModerator) {
171
+            html = (`
172
+            <div class="input-control">
173
+                <label class="input-control__label
174
+                       for="newPasswordInput"
175
+                       data-i18n="${addPassKey}">${addPassText}</label>
176
+                <div class="input-control__container">
177
+                    <input class="input-control__input" id="newPasswordInput"
178
+                           type="text">
179
+                    <button id="addPasswordBtn" id="inviteDialogAddPassword"
180
+                            disabled data-i18n="${addKey}"
181
+                            class="button-control button-control_light">
182
+                        ${addText}
183
+                    </button>
184
+                </div>
185
+            </div>
186
+        `);
187
+        } else {
188
+            html = '';
189
+        }
190
+
191
+        return html;
192
+    }
193
+
194
+    /**
195
+     * Layout for password (when room is locked)
196
+     * @returns {string}
197
+     */
198
+    getPasswordBlock() {
199
+        let { password, isModerator } = this.model;
200
+        let removePassKey = 'dialog.removePassword';
201
+        let removePassText = APP.translation.translateString(removePassKey);
202
+        let currentPassKey = 'dialog.currentPassword';
203
+        let currentPassText = APP.translation.translateString(currentPassKey);
204
+        let passwordKey = "dialog.passwordLabel";
205
+        let passwordText = APP.translation.translateString(passwordKey);
206
+
207
+        if (isModerator) {
208
+            return (`
209
+                <div class="input-control">
210
+                    <label class="input-control__label"
211
+                           data-i18n="${passwordKey}">${passwordText}</label>
212
+                    <div class="input-control__container">
213
+                        <p class="input-control__text"
214
+                           data-i18n="${currentPassKey}">
215
+                            ${currentPassText}
216
+                            <span id="inviteDialogPassword"
217
+                                  class="input-control__em">
218
+                                ${password}
219
+                            </span>
220
+                        </p>
221
+                        <a class="link input-control__right"
222
+                           id="inviteDialogRemovePassword"
223
+                           href="#" data-i18n="${removePassKey}">
224
+                           ${removePassText}
225
+                       </a>
226
+                    </div>
227
+                </div>
228
+            `);
229
+        } else {
230
+            return (`
231
+                <div class="input-control">
232
+                    <p>A participant protected this call with a password.</p>
233
+                </div>
234
+            `);
235
+        }
236
+
237
+    }
238
+
239
+
240
+
241
+    /**
242
+     * Opening the dialog
243
+     */
244
+    open() {
245
+        let leftButton;
246
+        let {
247
+            submitFunction,
248
+            loadedFunction,
249
+            closeFunction
250
+        } = this.dialog;
251
+
252
+        let states = this.getStates();
253
+
254
+        let buttons = [];
255
+        let leftButtonKey = "dialog.Invite";
256
+        let cancelButton
257
+            = APP.translation.generateTranslationHTML("dialog.Cancel");
258
+        buttons.push({title: cancelButton, value: false});
259
+
260
+        leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
261
+        buttons.push({ title: leftButton, value: true});
262
+
263
+        let initial = this.model.roomLocked ? States.LOCKED : States.UNLOCKED;
264
+
265
+        APP.UI.messageHandler.openDialogWithStates(states, {
266
+            submit: submitFunction,
267
+            loaded: loadedFunction,
268
+            close: closeFunction,
269
+            buttons,
270
+            size: 'medium'
271
+        });
272
+        $.prompt.goToState(initial);
273
+
274
+        this.registerListeners();
275
+        this.updateView();
276
+    }
277
+
278
+    /**
279
+     * Setting event handlers
280
+     * used in dialog
281
+     */
282
+    registerListeners() {
283
+        let $passInput = $('#newPasswordInput');
284
+        let $addPassBtn = $('#addPasswordBtn');
285
+
286
+        $(document).on('click', '.copyInviteLink', this.copyToClipboard);
287
+        $addPassBtn.on('click', () => {
288
+            let newPass = $passInput.val();
289
+
290
+            if(newPass) {
291
+                this.model.setRoomLocked(newPass);
292
+            }
293
+        });
294
+        $('#inviteDialogRemovePassword').on('click', () => {
295
+            this.model.setRoomUnlocked();
296
+        });
297
+        $passInput.keyup(this.disableAddPassIfInputEmpty.bind(this));
298
+    }
299
+
300
+    /**
301
+     * Checking input and if it's empty then
302
+     * disable add pass button
303
+     */
304
+    disableAddPassIfInputEmpty() {
305
+        let $passInput = $('#newPasswordInput');
306
+        let $addPassBtn = $('#addPasswordBtn');
307
+
308
+        if(!$passInput.val()) {
309
+            $addPassBtn.prop('disabled', true);
310
+        } else {
311
+            $addPassBtn.prop('disabled', false);
312
+        }
313
+    }
314
+
315
+    /**
316
+     * Copying text to clipboard
317
+     */
318
+    copyToClipboard() {
319
+        $('.inviteLink').each(function () {
320
+            let $el = $(this).closest('.jqistate');
321
+
322
+            // TOFIX: We can select only visible elements
323
+            if($el.css('display') === 'block') {
324
+                this.select();
325
+
326
+                try {
327
+                    document.execCommand('copy');
328
+                    this.blur();
329
+                }
330
+                catch (err) {
331
+                    console.error('error when copy the text');
332
+                }
333
+            }
334
+        });
335
+    }
336
+
337
+    /**
338
+     * Method syncing the view and the model
339
+     */
340
+    updateView() {
341
+        let pass = this.model.getPassword();
342
+        if (!pass)
343
+            pass = APP.translation.translateString("passwordSetRemotely");
344
+
345
+        $('#inviteDialogPassword').text(pass);
346
+        $('#newPasswordInput').val('');
347
+        this.disableAddPassIfInputEmpty();
348
+
349
+        this.updateInviteLink();
350
+
351
+        $.prompt.goToState(
352
+            (this.model.isLocked())
353
+                ? States.LOCKED
354
+                : States.UNLOCKED);
355
+
356
+        let roomLocked = `.${this.lockHint}`;
357
+        let roomUnlocked = `.${this.unlockHint}`;
358
+
359
+        let showDesc = this.model.isLocked() ? roomLocked : roomUnlocked;
360
+        let hideDesc = !this.model.isLocked() ? roomLocked : roomUnlocked;
361
+
362
+        $(showDesc).show();
363
+        $(hideDesc).hide();
364
+    }
365
+
366
+    /**
367
+     * Updates invite link
368
+     */
369
+    updateInviteLink() {
370
+        // If the invite dialog has been already opened we update the
371
+        // information.
372
+        let inviteLink = document.querySelectorAll('.inviteLink');
373
+        let list = Array.from(inviteLink);
374
+        list.forEach((inviteLink) => {
375
+            inviteLink.value = this.model.inviteUrl;
376
+            inviteLink.select();
377
+        });
378
+
379
+        $('#inviteLinkRef').parent()
380
+            .find('button[value=true]').prop('disabled', false);
381
+    }
382
+}

+ 121
- 0
modules/UI/invite/RoomLocker.js View File

1
+/* global APP, JitsiMeetJS */
2
+import askForPassword from './AskForPassword';
3
+
4
+/**
5
+ * Show notification that user cannot set password for the conference
6
+ * because server doesn't support that.
7
+ */
8
+function notifyPasswordNotSupported () {
9
+    console.warn('room passwords not supported');
10
+    APP.UI.messageHandler.showError(
11
+        "dialog.warning", "dialog.passwordNotSupported");
12
+}
13
+
14
+/**
15
+ * Show notification that setting password for the conference failed.
16
+ * @param {Error} err error
17
+ */
18
+function notifyPasswordFailed(err) {
19
+    console.warn('setting password failed', err);
20
+    APP.UI.messageHandler.showError(
21
+        "dialog.lockTitle", "dialog.lockMessage");
22
+}
23
+
24
+const ConferenceErrors = JitsiMeetJS.errors.conference;
25
+
26
+/**
27
+ * Create new RoomLocker for the conference.
28
+ * It allows to set or remove password for the conference,
29
+ * or ask for required password.
30
+ * @returns {RoomLocker}
31
+ */
32
+export default function createRoomLocker (room) {
33
+    let password;
34
+
35
+    /**
36
+     * If the room was locked from someone other than us, we indicate it with
37
+     * this property in order to have correct roomLocker state of isLocked.
38
+     * @type {boolean} whether room is locked, but not from us.
39
+     */
40
+    let lockedElsewhere = false;
41
+
42
+    /**
43
+     * @class RoomLocker
44
+     */
45
+    return {
46
+        get isLocked () {
47
+            return !!password || lockedElsewhere;
48
+        },
49
+
50
+        get password () {
51
+            return password;
52
+        },
53
+
54
+        /**
55
+         * Allows to set new password
56
+         * @param newPass
57
+         * @returns {Promise.<TResult>}
58
+         */
59
+        lock (newPass) {
60
+            return room.lock(newPass).then(() => {
61
+                password = newPass;
62
+                // If the password is undefined this means that we're removing
63
+                // it for everyone.
64
+                if (!password)
65
+                    lockedElsewhere = false;
66
+            }).catch(function (err) {
67
+                console.error(err);
68
+                if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
69
+                    notifyPasswordNotSupported();
70
+                } else {
71
+                    notifyPasswordFailed(err);
72
+                }
73
+                throw err;
74
+            });
75
+        },
76
+
77
+        /**
78
+         * Sets that the room is locked from another user, not us.
79
+         * @param {boolean} value locked/unlocked state
80
+         */
81
+        set lockedElsewhere (value) {
82
+            lockedElsewhere = value;
83
+        },
84
+
85
+        /**
86
+         * Whether room is locked from someone else.
87
+         * @returns {boolean} whether room is not locked locally,
88
+         * but it is still locked.
89
+         */
90
+        get lockedElsewhere () {
91
+            return lockedElsewhere;
92
+        },
93
+
94
+        /**
95
+         * Reset the password. Can be useful when room
96
+         * has been unlocked from elsewhere and we can use
97
+         * this method for sync the pass
98
+         */
99
+        resetPassword() {
100
+            password = null;
101
+        },
102
+
103
+        /**
104
+         * Asks user for required conference password.
105
+         */
106
+        requirePassword () {
107
+            return askForPassword().then(
108
+                newPass => { password = newPass; }
109
+            ).catch(
110
+                reason => {
111
+                    // user canceled, no pass was entered.
112
+                    // clear, as if we use the same instance several times
113
+                    // pass stays between attempts
114
+                    password = null;
115
+                    if (reason !== APP.UI.messageHandler.CANCEL)
116
+                        console.error(reason);
117
+                }
118
+            );
119
+        }
120
+    };
121
+}

+ 25
- 28
modules/UI/recording/Recording.js View File

55
     return new Promise(function (resolve, reject) {
55
     return new Promise(function (resolve, reject) {
56
         dialog = APP.UI.messageHandler.openDialogWithStates({
56
         dialog = APP.UI.messageHandler.openDialogWithStates({
57
             state0: {
57
             state0: {
58
+                title: msg,
58
                 html:
59
                 html:
59
-                    `<h2>${msg}</h2>
60
-                    <input name="streamId" type="text"
60
+                    `<input name="streamId" type="text"
61
                     data-i18n="[placeholder]dialog.streamKey"
61
                     data-i18n="[placeholder]dialog.streamKey"
62
                     placeholder="${token}" autofocus>`,
62
                     placeholder="${token}" autofocus>`,
63
                 persistent: false,
63
                 persistent: false,
89
             },
89
             },
90
 
90
 
91
             state1: {
91
             state1: {
92
-                html: `<h2>${msg}</h2> ${streamIdRequired}`,
92
+                title: msg,
93
+                html: streamIdRequired,
93
                 persistent: false,
94
                 persistent: false,
94
                 buttons: [
95
                 buttons: [
95
                     {title: cancelButton, value: false},
96
                     {title: cancelButton, value: false},
120
  * @returns {Promise}
121
  * @returns {Promise}
121
  */
122
  */
122
 function _requestRecordingToken () {
123
 function _requestRecordingToken () {
123
-    let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
124
+    let titleKey = "dialog.recordingToken";
124
     let token = APP.translation.translateString("dialog.token");
125
     let token = APP.translation.translateString("dialog.token");
125
-
126
+    let messageString = (
127
+        `<input name="recordingToken" type="text"
128
+                data-i18n="[placeholder]dialog.token"
129
+                placeholder="${token}" autofocus>`
130
+    );
126
     return new Promise(function (resolve, reject) {
131
     return new Promise(function (resolve, reject) {
127
-        dialog = APP.UI.messageHandler.openTwoButtonDialog(
128
-            null, null, null,
129
-            `<h2>${msg}</h2>
130
-             <input name="recordingToken" type="text"
131
-                    data-i18n="[placeholder]dialog.token"
132
-                    placeholder="${token}" autofocus>`,
133
-            false, "dialog.Save",
134
-            function (e, v, m, f) {
132
+        dialog = APP.UI.messageHandler.openTwoButtonDialog({
133
+            titleKey,
134
+            messageString,
135
+            leftButtonKey: 'dialog.Save',
136
+            submitFunction: function (e, v, m, f) {
135
                 if (v && f.recordingToken) {
137
                 if (v && f.recordingToken) {
136
                     resolve(UIUtil.escapeHtml(f.recordingToken));
138
                     resolve(UIUtil.escapeHtml(f.recordingToken));
137
                 } else {
139
                 } else {
138
                     reject(APP.UI.messageHandler.CANCEL);
140
                     reject(APP.UI.messageHandler.CANCEL);
139
                 }
141
                 }
140
             },
142
             },
141
-            null,
142
-            function () {
143
+            closeFunction: function () {
143
                 dialog = null;
144
                 dialog = null;
144
             },
145
             },
145
-            ':input:first'
146
-        );
146
+            focus: ':input:first'
147
+        });
147
     });
148
     });
148
 }
149
 }
149
 
150
 
170
     }
171
     }
171
 
172
 
172
     return new Promise(function (resolve, reject) {
173
     return new Promise(function (resolve, reject) {
173
-        dialog = APP.UI.messageHandler.openTwoButtonDialog(
174
-            title,
175
-            null,
176
-            message,
177
-            null,
178
-            false,
179
-            buttonKey,
180
-            function(e,v) {
174
+        dialog = APP.UI.messageHandler.openTwoButtonDialog({
175
+            titleKey: title,
176
+            messageKey: message,
177
+            leftButtonKey: buttonKey,
178
+            submitFunction: function(e,v) {
181
                 if (v) {
179
                 if (v) {
182
                     resolve();
180
                     resolve();
183
                 } else {
181
                 } else {
184
                     reject();
182
                     reject();
185
                 }
183
                 }
186
             },
184
             },
187
-            null,
188
-            function () {
185
+            closeFunction: function () {
189
                 dialog = null;
186
                 dialog = null;
190
             }
187
             }
191
-        );
188
+        });
192
     });
189
     });
193
 }
190
 }
194
 
191
 

+ 20
- 20
modules/UI/shared_video/SharedVideo.js View File

722
  */
722
  */
723
 function showStopVideoPropmpt() {
723
 function showStopVideoPropmpt() {
724
     return new Promise(function (resolve, reject) {
724
     return new Promise(function (resolve, reject) {
725
-        dialog = APP.UI.messageHandler.openTwoButtonDialog(
726
-            "dialog.removeSharedVideoTitle",
727
-            null,
728
-            "dialog.removeSharedVideoMsg",
729
-            null,
730
-            false,
731
-            "dialog.Remove",
732
-            function(e,v) {
733
-                if (v) {
734
-                    resolve();
735
-                } else {
736
-                    reject();
737
-                }
738
-            },
739
-            null,
740
-            function () {
741
-                dialog = null;
725
+        let submitFunction = function(e,v) {
726
+            if (v) {
727
+                resolve();
728
+            } else {
729
+                reject();
742
             }
730
             }
743
-        );
731
+        };
732
+
733
+        let closeFunction = function () {
734
+            dialog = null;
735
+        };
744
 
736
 
737
+        dialog = APP.UI.messageHandler.openTwoButtonDialog({
738
+            titleKey: "dialog.removeSharedVideoTitle",
739
+            msgKey: "dialog.removeSharedVideoMsg",
740
+            leftButtonKey: "dialog.Remove",
741
+            submitFunction,
742
+            closeFunction
743
+        });
745
     });
744
     });
746
 }
745
 }
747
 
746
 
763
     return new Promise(function (resolve, reject) {
762
     return new Promise(function (resolve, reject) {
764
         dialog = APP.UI.messageHandler.openDialogWithStates({
763
         dialog = APP.UI.messageHandler.openDialogWithStates({
765
             state0: {
764
             state0: {
765
+                title: title,
766
                 html:  `
766
                 html:  `
767
-                    <h2>${title}</h2>
768
                     <input name="sharedVideoUrl" type="text"
767
                     <input name="sharedVideoUrl" type="text"
769
                            data-i18n="[placeholder]defaultLink"
768
                            data-i18n="[placeholder]defaultLink"
770
                            data-i18n-options="${JSON.stringify(i18nOptions)}"
769
                            data-i18n-options="${JSON.stringify(i18nOptions)}"
803
             },
802
             },
804
 
803
 
805
             state1: {
804
             state1: {
806
-                html: `<h2>${title}</h2> ${linkError}`,
805
+                title: title,
806
+                html: linkError,
807
                 persistent: false,
807
                 persistent: false,
808
                 buttons: [
808
                 buttons: [
809
                     {title: cancelButton, value: false},
809
                     {title: cancelButton, value: false},

+ 19
- 0
modules/UI/side_pannels/contactlist/Contact.js View File

1
+/**
2
+ * Class representing Contact model
3
+ * @class Contact
4
+ */
5
+export default class Contact {
6
+    constructor(opts) {
7
+        let {
8
+            id,
9
+            avatar,
10
+            name,
11
+            isLocal
12
+        } = opts;
13
+
14
+        this.id = id;
15
+        this.avatar = avatar || '';
16
+        this.name = name || '';
17
+        this.isLocal = isLocal || false;
18
+    }
19
+}

+ 66
- 123
modules/UI/side_pannels/contactlist/ContactList.js View File

1
-/* global $, APP, interfaceConfig */
2
-import Avatar from '../../avatar/Avatar';
3
-import UIEvents from '../../../../service/UI/UIEvents';
4
-import UIUtil from '../../util/UIUtil';
5
-
6
-let numberOfContacts = 0;
7
-
8
-/**
9
- * Updates the number of participants in the contact list button and sets
10
- * the glow
11
- * @param delta indicates whether a new user has joined (1) or someone has
12
- * left(-1)
13
- */
14
-function updateNumberOfParticipants(delta) {
15
-    numberOfContacts += delta;
1
+/* global APP */
16
 
2
 
17
-    if (numberOfContacts <= 0) {
18
-        console.error("Invalid number of participants: " + numberOfContacts);
19
-        return;
20
-    }
21
-
22
-    $("#numberOfParticipants").text(numberOfContacts);
23
-
24
-    $("#contacts_container>div.title").text(
25
-        APP.translation.translateString("contactlist")
26
-            + ' (' + numberOfContacts + ')');
27
-}
28
-
29
-/**
30
- * Creates the avatar element.
31
- *
32
- * @return {object} the newly created avatar element
33
- */
34
-function createAvatar(jid) {
35
-    let avatar = document.createElement('img');
36
-    avatar.className = "icon-avatar avatar";
37
-    avatar.src = Avatar.getAvatarUrl(jid);
38
-
39
-    return avatar;
40
-}
3
+import UIEvents from '../../../../service/UI/UIEvents';
4
+import ContactListView from './ContactListView';
5
+import Contact from './Contact';
41
 
6
 
42
 /**
7
 /**
43
- * Creates the display name paragraph.
8
+ * Model for the Contact list.
44
  *
9
  *
45
- * @param displayName the display name to set
10
+ * @class ContactList
46
  */
11
  */
47
-function createDisplayNameParagraph(key, displayName) {
48
-    let p = document.createElement('p');
49
-    if (displayName) {
50
-        p.innerHTML = displayName;
51
-    } else if(key) {
52
-        p.setAttribute("data-i18n",key);
53
-        p.innerHTML = APP.translation.translateString(key);
12
+class ContactList {
13
+    constructor(conference) {
14
+        this.conference = conference;
15
+        this.contacts = [];
16
+        this.roomLocked = false;
17
+        ContactListView.init(this);
54
     }
18
     }
55
 
19
 
56
-    return p;
57
-}
58
-
59
-function getContactEl (id) {
60
-    return $(`#contacts>li[id="${id}"]`);
61
-}
62
-
63
-/**
64
- * Contact list.
65
- */
66
-var ContactList = {
67
-    init (emitter) {
68
-        this.emitter = emitter;
69
-    },
70
     /**
20
     /**
71
-     * Indicates if the chat is currently visible.
21
+     * Is locked flag.
22
+     * Delegates to Invite module
23
+     * TO FIX: find a better way to access the IS LOCKED state of the invite.
72
      *
24
      *
73
-     * @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
74
-     * otherwise
25
+     * @returns {Boolean}
75
      */
26
      */
76
-    isVisible () {
77
-        return UIUtil.isVisible(document.getElementById("contactlist"));
78
-    },
27
+    isLocked() {
28
+        return APP.conference.invite.isLocked();
29
+    }
79
 
30
 
80
     /**
31
     /**
81
-     * Adds a contact for the given id.
82
-     * @param isLocal is an id for the local user.
32
+     * Adding new participant.
33
+     *
34
+     * @param id
35
+     * @param isLocal
83
      */
36
      */
84
-    addContact (id, isLocal) {
85
-        let contactlist = $('#contacts');
86
-
87
-        let newContact = document.createElement('li');
88
-        newContact.id = id;
89
-        newContact.className = "clickable";
90
-        newContact.onclick = (event) => {
91
-            if (event.currentTarget.className === "clickable") {
92
-                this.emitter.emit(UIEvents.CONTACT_CLICKED, id);
93
-            }
94
-        };
95
-
96
-        if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
97
-            newContact.appendChild(createAvatar(id));
98
-
99
-        newContact.appendChild(
100
-            createDisplayNameParagraph(
101
-                isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null,
102
-                isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME));
37
+    addContact(id, isLocal) {
38
+        let isExist = this.contacts.some((el) => el.id === id);
103
 
39
 
104
-        if (APP.conference.isLocalId(id)) {
105
-            contactlist.prepend(newContact);
106
-        } else {
107
-            contactlist.append(newContact);
40
+        if (!isExist) {
41
+            let newContact = new Contact({ id, isLocal });
42
+            this.contacts.push(newContact);
43
+            APP.UI.emitEvent(UIEvents.CONTACT_ADDED, { id, isLocal });
108
         }
44
         }
109
-        updateNumberOfParticipants(1);
110
-    },
45
+    }
111
 
46
 
112
     /**
47
     /**
113
-     * Removes a contact for the given id.
48
+     * Removing participant.
114
      *
49
      *
50
+     * @param id
51
+     * @returns {Array|*}
115
      */
52
      */
116
-    removeContact (id) {
117
-        let contact = getContactEl(id);
118
-
119
-        if (contact.length > 0) {
120
-            contact.remove();
121
-            updateNumberOfParticipants(-1);
122
-        }
123
-    },
124
-
125
-    setClickable (id, isClickable) {
126
-        getContactEl(id).toggleClass('clickable', isClickable);
127
-    },
53
+    removeContact(id) {
54
+        this.contacts = this.contacts.filter((el) => el.id !== id);
55
+        APP.UI.emitEvent(UIEvents.CONTACT_REMOVED, { id });
56
+        return this.contacts;
57
+    }
128
 
58
 
129
-    onDisplayNameChange (id, displayName) {
130
-        if(!displayName)
59
+    /**
60
+     * Changing the display name.
61
+     *
62
+     * @param id
63
+     * @param name
64
+     */
65
+    onDisplayNameChange (id, name) {
66
+        if(!name)
131
             return;
67
             return;
132
         if (id === 'localVideoContainer') {
68
         if (id === 'localVideoContainer') {
133
             id = APP.conference.getMyUserId();
69
             id = APP.conference.getMyUserId();
134
         }
70
         }
135
-        let contactName = $(`#contacts #${id}>p`);
136
 
71
 
137
-        if (contactName.text() !== displayName) {
138
-            contactName.text(displayName);
139
-        }
140
-    },
72
+        let contacts = this.contacts.filter((el) => el.id === id);
73
+        contacts.forEach((el) => {
74
+            el.name = name;
75
+        });
76
+        APP.UI.emitEvent(UIEvents.DISPLAY_NAME_CHANGED, { id, name });
77
+    }
141
 
78
 
142
-    changeUserAvatar (id, avatarUrl) {
143
-        // set the avatar in the contact list
144
-        let contact = $(`#${id}>img`);
145
-        if (contact.length > 0) {
146
-            contact.attr('src', avatarUrl);
147
-        }
79
+    /**
80
+     * Changing the avatar.
81
+     *
82
+     * @param id
83
+     * @param avatar
84
+     */
85
+    changeUserAvatar (id, avatar) {
86
+        let contacts = this.contacts.filter((el) => el.id === id);
87
+        contacts.forEach((el) => {
88
+            el.avatar = avatar;
89
+        });
90
+        APP.UI.emitEvent(UIEvents.USER_AVATAR_CHANGED, { id, avatar });
148
     }
91
     }
149
-};
92
+}
150
 
93
 
151
-export default ContactList;
94
+export default ContactList;

+ 262
- 0
modules/UI/side_pannels/contactlist/ContactListView.js View File

1
+/* global $, APP, interfaceConfig */
2
+import Avatar from '../../avatar/Avatar';
3
+import UIEvents from '../../../../service/UI/UIEvents';
4
+import UIUtil from '../../util/UIUtil';
5
+
6
+let numberOfContacts = 0;
7
+
8
+/**
9
+ * Updates the number of participants in the contact list button and sets
10
+ * the glow
11
+ * @param delta indicates whether a new user has joined (1) or someone has
12
+ * left(-1)
13
+ */
14
+function updateNumberOfParticipants(delta) {
15
+    numberOfContacts += delta;
16
+
17
+    if (numberOfContacts <= 0) {
18
+        console.error("Invalid number of participants: " + numberOfContacts);
19
+        return;
20
+    }
21
+
22
+    $("#numberOfParticipants").text(numberOfContacts);
23
+
24
+    $("#contacts_container>div.title").text(
25
+        APP.translation.translateString("contactlist")
26
+            + ' (' + numberOfContacts + ')');
27
+}
28
+
29
+/**
30
+ * Creates the avatar element.
31
+ *
32
+ * @return {object} the newly created avatar element
33
+ */
34
+function createAvatar(jid) {
35
+    let avatar = document.createElement('img');
36
+    avatar.className = "icon-avatar avatar";
37
+    avatar.src = Avatar.getAvatarUrl(jid);
38
+
39
+    return avatar;
40
+}
41
+
42
+/**
43
+ * Creates the display name paragraph.
44
+ *
45
+ * @param displayName the display name to set
46
+ */
47
+function createDisplayNameParagraph(key, displayName) {
48
+    let p = document.createElement('p');
49
+    if (displayName) {
50
+        p.innerHTML = displayName;
51
+    } else if(key) {
52
+        p.setAttribute("data-i18n",key);
53
+        p.innerHTML = APP.translation.translateString(key);
54
+    }
55
+
56
+    return p;
57
+}
58
+
59
+/**
60
+ * Getter for current contact element
61
+ * @param id
62
+ * @returns {JQuery}
63
+ */
64
+function getContactEl (id) {
65
+    return $(`#contacts>li[id="${id}"]`);
66
+}
67
+
68
+/**
69
+ * Contact list.
70
+ */
71
+var ContactListView = {
72
+    init (model) {
73
+        this.model = model;
74
+        this.lockKey = 'roomLocked';
75
+        this.unlockKey = 'roomUnlocked';
76
+        this.addInviteButton();
77
+        this.registerListeners();
78
+        this.toggleLock();
79
+    },
80
+    /**
81
+     * Adds layout for invite button
82
+     */
83
+    addInviteButton() {
84
+        let container = document.getElementById('contacts_container');
85
+        let title = container.firstElementChild;
86
+
87
+        let htmlLayout =  this.getInviteButtonLayout();
88
+        title.insertAdjacentHTML('afterend', htmlLayout);
89
+        $(document).on('click', '#addParticipantsBtn', () => {
90
+            APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
91
+        });
92
+    },
93
+    /**
94
+     *  Returns layout for invite button
95
+     */
96
+    getInviteButtonLayout() {
97
+        let classes = 'button-control button-control_primary';
98
+        classes += ' button-control_full-width';
99
+        let key = 'addParticipants';
100
+        let text = APP.translation.translateString(key);
101
+
102
+        let lockedHtml = this.getLockDescriptionLayout(this.lockKey);
103
+        let unlockedHtml = this.getLockDescriptionLayout(this.unlockKey);
104
+
105
+        let html = (
106
+            `<div class="sideToolbarBlock first">
107
+                <button id="addParticipantsBtn" 
108
+                         data-i18n="${key}" 
109
+                         class="${classes}">
110
+                    ${text}
111
+                </button>
112
+                <div>
113
+                    ${lockedHtml}
114
+                    ${unlockedHtml}
115
+                </div>
116
+            </div>`);
117
+
118
+        return html;
119
+    },
120
+    /**
121
+     * Adds layout for lock description
122
+     */
123
+    getLockDescriptionLayout(key) {
124
+        let classes = "input-control__hint input-control_full-width";
125
+        let description = APP.translation.translateString(key);
126
+        let padlockSuffix = '';
127
+        if (key === this.lockKey) {
128
+            padlockSuffix = '-locked';
129
+        }
130
+
131
+        return `<p id="contactList${key}" class="${classes}">
132
+                    <span class="icon-security${padlockSuffix}"></span>
133
+                    <span data-i18n="${key}">${description}</span>
134
+                </p>`;
135
+    },
136
+    /**
137
+     * Setup listeners
138
+     */
139
+    registerListeners() {
140
+        let model = this.model;
141
+        let removeContact = this.onRemoveContact.bind(this);
142
+        let changeAvatar = this.changeUserAvatar.bind(this);
143
+        let displayNameChange = this.onDisplayNameChange.bind(this);
144
+
145
+        APP.UI.addListener( UIEvents.TOGGLE_ROOM_LOCK,
146
+                            this.toggleLock.bind(this));
147
+        APP.UI.addListener( UIEvents.CONTACT_ADDED,
148
+                            this.onAddContact.bind(this));
149
+
150
+        APP.UI.addListener(UIEvents.CONTACT_REMOVED, removeContact);
151
+        APP.UI.addListener(UIEvents.USER_AVATAR_CHANGED, changeAvatar);
152
+        APP.UI.addListener(UIEvents.DISPLAY_NAME_CHANGED, displayNameChange);
153
+    },
154
+    /**
155
+     * Updating the view according the model
156
+     * @param type {String} type of change
157
+     * @returns {Promise}
158
+     */
159
+    toggleLock() {
160
+        let isLocked = this.model.isLocked();
161
+        let showKey = isLocked ? this.lockKey : this.unlockKey;
162
+        let hideKey = !isLocked ? this.lockKey : this.unlockKey;
163
+        let showId = `contactList${showKey}`;
164
+        let hideId = `contactList${hideKey}`;
165
+
166
+        $(`#${showId}`).show();
167
+        $(`#${hideId}`).hide();
168
+    },
169
+    /**
170
+     * Indicates if the chat is currently visible.
171
+     *
172
+     * @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
173
+     * otherwise
174
+     */
175
+    isVisible () {
176
+        return UIUtil.isVisible(document.getElementById("contactlist"));
177
+    },
178
+
179
+    /**
180
+     * Handler for Adding a contact for the given id.
181
+     * @param isLocal is an id for the local user.
182
+     */
183
+    onAddContact (data) {
184
+        let { id, isLocal } = data;
185
+        let contactlist = $('#contacts');
186
+        let newContact = document.createElement('li');
187
+        newContact.id = id;
188
+        newContact.className = "clickable";
189
+        newContact.onclick = (event) => {
190
+            if (event.currentTarget.className === "clickable") {
191
+                APP.UI.emitEvent(UIEvents.CONTACT_CLICKED, id);
192
+            }
193
+        };
194
+
195
+        if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
196
+            newContact.appendChild(createAvatar(id));
197
+
198
+        newContact.appendChild(
199
+            createDisplayNameParagraph(
200
+                isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null,
201
+                isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME));
202
+
203
+        if (APP.conference.isLocalId(id)) {
204
+            contactlist.prepend(newContact);
205
+        } else {
206
+            contactlist.append(newContact);
207
+        }
208
+        updateNumberOfParticipants(1);
209
+    },
210
+
211
+    /**
212
+     * Handler for removing
213
+     * a contact for the given id.
214
+     */
215
+    onRemoveContact (data) {
216
+        let { id } = data;
217
+        let contact = getContactEl(id);
218
+
219
+        if (contact.length > 0) {
220
+            contact.remove();
221
+            updateNumberOfParticipants(-1);
222
+        }
223
+    },
224
+
225
+    setClickable (id, isClickable) {
226
+        getContactEl(id).toggleClass('clickable', isClickable);
227
+    },
228
+
229
+    /**
230
+     * Changes display name of the user
231
+     * defined by its id
232
+     * @param data
233
+     */
234
+    onDisplayNameChange (data) {
235
+        let { id, name } = data;
236
+        if(!name)
237
+            return;
238
+        if (id === 'localVideoContainer') {
239
+            id = APP.conference.getMyUserId();
240
+        }
241
+        let contactName = $(`#contacts #${id}>p`);
242
+
243
+        if (contactName.text() !== name) {
244
+            contactName.text(name);
245
+        }
246
+    },
247
+
248
+    /**
249
+     * Changes user avatar
250
+     * @param data
251
+     */
252
+    changeUserAvatar (data) {
253
+        let { id, avatar } = data;
254
+        // set the avatar in the contact list
255
+        let contact = $(`#${id}>img`);
256
+        if (contact.length > 0) {
257
+            contact.attr('src', avatar);
258
+        }
259
+    }
260
+};
261
+
262
+export default ContactListView;

+ 25
- 124
modules/UI/toolbars/Toolbar.js View File

3
 import UIEvents from '../../../service/UI/UIEvents';
3
 import UIEvents from '../../../service/UI/UIEvents';
4
 import SideContainerToggler from "../side_pannels/SideContainerToggler";
4
 import SideContainerToggler from "../side_pannels/SideContainerToggler";
5
 
5
 
6
-let roomUrl = null;
7
 let emitter = null;
6
 let emitter = null;
8
-
9
-/**
10
- * Opens the invite link dialog.
11
- */
12
-function openLinkDialog () {
13
-    let inviteAttributes;
14
-
15
-    if (roomUrl === null) {
16
-        inviteAttributes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
17
-            APP.translation.translateString("roomUrlDefaultMsg") + '"';
18
-    } else {
19
-        inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
20
-    }
21
-
22
-    let inviteLinkId = "inviteLinkRef";
23
-    let focusInviteLink = function() {
24
-        $('#' + inviteLinkId).focus();
25
-        $('#' + inviteLinkId).select();
26
-    };
27
-
28
-    let title = APP.translation.generateTranslationHTML("dialog.shareLink");
29
-    APP.UI.messageHandler.openTwoButtonDialog(
30
-        null, title, null,
31
-        '<input id="' + inviteLinkId + '" type="text" '
32
-            + inviteAttributes + ' readonly/>',
33
-        false, "dialog.copy",
34
-        function (e, v) {
35
-            if (v && roomUrl) {
36
-                JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
37
-
38
-                focusInviteLink();
39
-
40
-                document.execCommand('copy');
41
-            }
42
-            else {
43
-                JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
44
-            }
45
-        },
46
-        function (event) {
47
-            if (!roomUrl) {
48
-                if (event && event.target) {
49
-                    $(event.target).find('button[value=true]')
50
-                        .prop('disabled', true);
51
-                }
52
-            }
53
-            else {
54
-                focusInviteLink();
55
-            }
56
-        },
57
-        function (e, v, m, f) {
58
-            if(!v && !m && !f)
59
-                JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
60
-        },
61
-        'Copy' // Focus Copy button.
62
-    );
63
-}
7
+let Toolbar;
64
 
8
 
65
 const buttonHandlers = {
9
 const buttonHandlers = {
66
     "toolbar_button_profile": function () {
10
     "toolbar_button_profile": function () {
98
             emitter.emit(UIEvents.VIDEO_MUTED, true);
42
             emitter.emit(UIEvents.VIDEO_MUTED, true);
99
         }
43
         }
100
     },
44
     },
101
-    "toolbar_button_security": function () {
102
-        JitsiMeetJS.analytics.sendEvent('toolbar.lock.clicked');
103
-        emitter.emit(UIEvents.ROOM_LOCK_CLICKED);
104
-    },
105
     "toolbar_button_link": function () {
45
     "toolbar_button_link": function () {
106
         JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
46
         JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
107
-        openLinkDialog();
47
+        emitter.emit(UIEvents.INVITE_CLICKED);
108
     },
48
     },
109
     "toolbar_button_chat": function () {
49
     "toolbar_button_chat": function () {
110
         JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
50
         JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
158
         emitter.emit(UIEvents.AUTH_CLICKED);
98
         emitter.emit(UIEvents.AUTH_CLICKED);
159
     },
99
     },
160
     "toolbar_button_logout": function () {
100
     "toolbar_button_logout": function () {
101
+        let titleKey = "dialog.logoutTitle";
102
+        let msgKey = "dialog.logoutQuestion";
161
         JitsiMeetJS.analytics.sendEvent('toolbar.authenticate.logout.clicked');
103
         JitsiMeetJS.analytics.sendEvent('toolbar.authenticate.logout.clicked');
162
         // Ask for confirmation
104
         // Ask for confirmation
163
-        APP.UI.messageHandler.openTwoButtonDialog(
164
-            "dialog.logoutTitle",
165
-            null,
166
-            "dialog.logoutQuestion",
167
-            null,
168
-            false,
169
-            "dialog.Yes",
170
-            function (evt, yes) {
105
+        APP.UI.messageHandler.openTwoButtonDialog({
106
+            titleKey,
107
+            msgKey,
108
+            leftButtonKey: "dialog.Yes",
109
+            submitFunction: function (evt, yes) {
171
                 if (yes) {
110
                 if (yes) {
172
                     emitter.emit(UIEvents.LOGOUT);
111
                     emitter.emit(UIEvents.LOGOUT);
173
                 }
112
                 }
174
             }
113
             }
175
-        );
114
+        });
176
     },
115
     },
177
     "toolbar_film_strip": function () {
116
     "toolbar_film_strip": function () {
178
         JitsiMeetJS.analytics.sendEvent(
117
         JitsiMeetJS.analytics.sendEvent(
246
         content: 'Share screen',
185
         content: 'Share screen',
247
         i18n: '[content]toolbar.sharescreen'
186
         i18n: '[content]toolbar.sharescreen'
248
     },
187
     },
249
-    'security': {
250
-        id: 'toolbar_button_security',
251
-        tooltipKey: 'toolbar.lock'
252
-    },
253
     'invite': {
188
     'invite': {
254
         id: 'toolbar_button_link',
189
         id: 'toolbar_button_link',
255
         tooltipKey: 'toolbar.invite',
190
         tooltipKey: 'toolbar.invite',
344
     let defaultNumber = config.defaultSipNumber
279
     let defaultNumber = config.defaultSipNumber
345
         ? config.defaultSipNumber
280
         ? config.defaultSipNumber
346
         : '';
281
         : '';
347
-
282
+    let titleKey = "dialog.sipMsg";
348
     let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
283
     let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
349
-    APP.UI.messageHandler.openTwoButtonDialog(
350
-        null, null, null,
351
-        `<h2>${sipMsg}</h2>
352
-            <input
353
-                name="sipNumber"
354
-                type="text"
355
-                value="${defaultNumber}"
356
-                autofocus>`,
357
-        false, "dialog.Dial",
358
-        function (e, v, m, f) {
284
+    let msgString = (`
285
+            <input name="sipNumber" type="text"
286
+                   value="${defaultNumber}" autofocus>
287
+    `);
288
+
289
+    APP.UI.messageHandler.openTwoButtonDialog({
290
+        titleKey,
291
+        titleString: sipMsg,
292
+        msgString,
293
+        leftButtonKey: "dialog.Dial",
294
+        submitFunction: function (e, v, m, f) {
359
             if (v && f.sipNumber) {
295
             if (v && f.sipNumber) {
360
                 emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
296
                 emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
361
             }
297
             }
362
         },
298
         },
363
-        null, null, ':input:first'
364
-    );
299
+        focus: ':input:first'
300
+    });
365
 }
301
 }
366
 
302
 
367
-const Toolbar = {
303
+Toolbar = {
368
     init (eventEmitter) {
304
     init (eventEmitter) {
369
         emitter = eventEmitter;
305
         emitter = eventEmitter;
370
         // The toolbar is enabled by default.
306
         // The toolbar is enabled by default.
446
     isEnabled() {
382
     isEnabled() {
447
         return this.enabled;
383
         return this.enabled;
448
     },
384
     },
449
-    /**
450
-     * Updates the room invite url.
451
-     */
452
-    updateRoomUrl (newRoomUrl) {
453
-        roomUrl = newRoomUrl;
454
-
455
-        // If the invite dialog has been already opened we update the
456
-        // information.
457
-        let inviteLink = document.getElementById('inviteLinkRef');
458
-        if (inviteLink) {
459
-            inviteLink.value = roomUrl;
460
-            inviteLink.select();
461
-            $('#inviteLinkRef').parent()
462
-                .find('button[value=true]').prop('disabled', false);
463
-        }
464
-    },
465
-
466
-    /**
467
-     * Unlocks the lock button state.
468
-     */
469
-    unlockLockButton () {
470
-        if ($("#toolbar_button_security").hasClass("icon-security-locked"))
471
-            UIUtil.buttonClick("toolbar_button_security",
472
-                                "icon-security icon-security-locked");
473
-    },
474
-
475
-    /**
476
-     * Updates the lock button state to locked.
477
-     */
478
-    lockLockButton () {
479
-        if ($("#toolbar_button_security").hasClass("icon-security"))
480
-            UIUtil.buttonClick("toolbar_button_security",
481
-                                "icon-security icon-security-locked");
482
-    },
483
-
484
     /**
385
     /**
485
      * Shows or hides authentication button
386
      * Shows or hides authentication button
486
      * @param show <tt>true</tt> to show or <tt>false</tt> to hide
387
      * @param show <tt>true</tt> to show or <tt>false</tt> to hide

+ 88
- 13
modules/UI/util/MessageHandler.js View File

52
         }
52
         }
53
 
53
 
54
         return $.prompt(message, {
54
         return $.prompt(message, {
55
-            title: title,
55
+            title: this._getFormattedTitleString(title),
56
             persistent: false,
56
             persistent: false,
57
+            promptspeed: 0,
58
+            classes: this._getDialogClasses(),
57
             close: function (e, v, m, f) {
59
             close: function (e, v, m, f) {
58
                 if(closeFunction)
60
                 if(closeFunction)
59
                     closeFunction(e, v, m, f);
61
                     closeFunction(e, v, m, f);
79
      *        the user press 'enter'. Indexed from 0.
81
      *        the user press 'enter'. Indexed from 0.
80
      * @return the prompt that was created, or null
82
      * @return the prompt that was created, or null
81
      */
83
      */
82
-    openTwoButtonDialog: function(titleKey, titleString, msgKey, msgString,
83
-        persistent, leftButtonKey, submitFunction, loadedFunction,
84
-        closeFunction, focus, defaultButton) {
84
+    openTwoButtonDialog: function(options) {
85
+        let {
86
+            titleKey,
87
+            titleString,
88
+            msgKey,
89
+            msgString,
90
+            leftButtonKey,
91
+            submitFunction,
92
+            loadedFunction,
93
+            closeFunction,
94
+            focus,
95
+            size,
96
+            defaultButton,
97
+            wrapperClass,
98
+            classes
99
+        } = options;
85
 
100
 
86
         if (!popupEnabled || twoButtonDialog)
101
         if (!popupEnabled || twoButtonDialog)
87
             return null;
102
             return null;
88
 
103
 
89
         var buttons = [];
104
         var buttons = [];
90
 
105
 
91
-        var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
106
+        var leftButton = leftButtonKey ?
107
+            APP.translation.generateTranslationHTML(leftButtonKey) :
108
+            APP.translation.generateTranslationHTML('dialog.Submit');
92
         buttons.push({ title: leftButton, value: true});
109
         buttons.push({ title: leftButton, value: true});
93
 
110
 
94
         var cancelButton
111
         var cancelButton
102
         if (msgKey) {
119
         if (msgKey) {
103
             message = APP.translation.generateTranslationHTML(msgKey);
120
             message = APP.translation.generateTranslationHTML(msgKey);
104
         }
121
         }
122
+        classes = classes || this._getDialogClasses(size);
123
+        if (wrapperClass) {
124
+            classes.prompt += ` ${wrapperClass}`;
125
+        }
126
+
105
         twoButtonDialog = $.prompt(message, {
127
         twoButtonDialog = $.prompt(message, {
106
-            title: title,
128
+            title: this._getFormattedTitleString(title),
107
             persistent: false,
129
             persistent: false,
108
             buttons: buttons,
130
             buttons: buttons,
109
             defaultButton: defaultButton,
131
             defaultButton: defaultButton,
110
             focus: focus,
132
             focus: focus,
111
             loaded: loadedFunction,
133
             loaded: loadedFunction,
134
+            promptspeed: 0,
135
+            classes,
112
             submit: function (e, v, m, f) {
136
             submit: function (e, v, m, f) {
113
                 twoButtonDialog = null;
137
                 twoButtonDialog = null;
114
-                if (submitFunction)
115
-                    submitFunction(e, v, m, f);
138
+                if (v){
139
+                    if (submitFunction)
140
+                        submitFunction(e, v, m, f);
141
+                }
116
             },
142
             },
117
             close: function (e, v, m, f) {
143
             close: function (e, v, m, f) {
118
                 twoButtonDialog = null;
144
                 twoButtonDialog = null;
119
-                if (closeFunction)
145
+                if (closeFunction) {
120
                     closeFunction(e, v, m, f);
146
                     closeFunction(e, v, m, f);
147
+                }
121
             }
148
             }
122
         });
149
         });
123
         return twoButtonDialog;
150
         return twoButtonDialog;
144
         if (!popupEnabled)
171
         if (!popupEnabled)
145
             return;
172
             return;
146
 
173
 
147
-        var args = {
148
-            title: titleString,
174
+        let args = {
175
+            title: this._getFormattedTitleString(titleString),
149
             persistent: persistent,
176
             persistent: persistent,
150
             buttons: buttons,
177
             buttons: buttons,
151
             defaultButton: 1,
178
             defaultButton: 1,
179
+            promptspeed: 0,
152
             loaded: loadedFunction,
180
             loaded: loadedFunction,
153
             submit: submitFunction,
181
             submit: submitFunction,
154
-            close: closeFunction
182
+            close: closeFunction,
183
+            classes: this._getDialogClasses()
155
         };
184
         };
156
 
185
 
157
         if (persistent) {
186
         if (persistent) {
161
         return new Impromptu(msgString, args);
190
         return new Impromptu(msgString, args);
162
     },
191
     },
163
 
192
 
193
+    /**
194
+     * Returns the formatted title string.
195
+     *
196
+     * @return the title string formatted as a div.
197
+     */
198
+    _getFormattedTitleString(titleString) {
199
+        let $titleString = $('<h2>');
200
+        $titleString.addClass('aui-dialog2-header-main');
201
+        $titleString.append(titleString);
202
+        titleString = $('<div>').append($titleString).html();
203
+
204
+        return titleString;
205
+    },
206
+
207
+    /**
208
+     * Returns the dialog css classes.
209
+     *
210
+     * @return the dialog css classes
211
+     */
212
+    _getDialogClasses(size = 'small') {
213
+        return {
214
+            box: '',
215
+            form: '',
216
+            prompt: `dialog aui-layer aui-dialog2 aui-dialog2-${size}`,
217
+            close: 'aui-icon aui-icon-small aui-iconfont-close-dialog',
218
+            fade: 'aui-blanket',
219
+            button: 'button-control',
220
+            message: 'aui-dialog2-content',
221
+            buttons: 'aui-dialog2-footer',
222
+            defaultButton: 'button-control_primary',
223
+            title: 'aui-dialog2-header'
224
+        };
225
+    },
226
+
164
     /**
227
     /**
165
      * Closes currently opened dialog.
228
      * Closes currently opened dialog.
166
      */
229
      */
176
     openDialogWithStates: function (statesObject, options) {
239
     openDialogWithStates: function (statesObject, options) {
177
         if (!popupEnabled)
240
         if (!popupEnabled)
178
             return;
241
             return;
179
-
242
+        let { classes, size } = options;
243
+        let defaultClasses = this._getDialogClasses(size);
244
+        options.classes = Object.assign({}, defaultClasses, classes);
245
+        options.promptspeed = options.promptspeed || 0;
246
+
247
+        for (let state in statesObject) {
248
+            let currentState = statesObject[state];
249
+            if(currentState.title) {
250
+                let title = currentState.title;
251
+                currentState.title = this._getFormattedTitleString(title);
252
+            }
253
+        }
180
         return new Impromptu(statesObject, options);
254
         return new Impromptu(statesObject, options);
181
     },
255
     },
182
 
256
 
210
                 }
284
                 }
211
             }, 200);
285
             }, 200);
212
         }
286
         }
287
+
213
         return popup;
288
         return popup;
214
     },
289
     },
215
 
290
 

+ 11
- 7
modules/keyboardshortcut/keyboardshortcut.js View File

172
      */
172
      */
173
     _addShortcutToHelp: function (shortcutChar, shortcutDescriptionKey) {
173
     _addShortcutToHelp: function (shortcutChar, shortcutDescriptionKey) {
174
 
174
 
175
-        var listElement = document.createElement("li");
175
+        let listElement = document.createElement("li");
176
+        let itemClass = 'shortcuts-list__item';
177
+        listElement.className = itemClass;
176
         listElement.id = shortcutChar;
178
         listElement.id = shortcutChar;
177
 
179
 
178
-        var spanElement = document.createElement("span");
180
+        let spanElement = document.createElement("span");
179
         spanElement.className = "item-action";
181
         spanElement.className = "item-action";
180
 
182
 
181
-        var kbdElement = document.createElement("kbd");
182
-        kbdElement.className = "regular-key";
183
+        let kbdElement = document.createElement("kbd");
184
+        let classes = 'aui-label regular-key';
185
+        kbdElement.className = classes;
183
         kbdElement.innerHTML = shortcutChar;
186
         kbdElement.innerHTML = shortcutChar;
184
         spanElement.appendChild(kbdElement);
187
         spanElement.appendChild(kbdElement);
185
 
188
 
186
-        var descriptionElement = document.createElement("span");
187
-        descriptionElement.className = "item-description";
189
+        let descriptionElement = document.createElement("span");
190
+        let descriptionClass = "shortcuts-list__description";
191
+        descriptionElement.className = descriptionClass;
188
         descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey);
192
         descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey);
189
         descriptionElement.innerHTML
193
         descriptionElement.innerHTML
190
             = APP.translation.translateString(shortcutDescriptionKey);
194
             = APP.translation.translateString(shortcutDescriptionKey);
192
         listElement.appendChild(spanElement);
196
         listElement.appendChild(spanElement);
193
         listElement.appendChild(descriptionElement);
197
         listElement.appendChild(descriptionElement);
194
 
198
 
195
-        var parentListElement
199
+        let parentListElement
196
             = document.getElementById("keyboard-shortcuts-list");
200
             = document.getElementById("keyboard-shortcuts-list");
197
 
201
 
198
         if (parentListElement)
202
         if (parentListElement)

+ 40
- 2
service/UI/UIEvents.js View File

22
     VIDEO_MUTED: "UI.video_muted",
22
     VIDEO_MUTED: "UI.video_muted",
23
     ETHERPAD_CLICKED: "UI.etherpad_clicked",
23
     ETHERPAD_CLICKED: "UI.etherpad_clicked",
24
     SHARED_VIDEO_CLICKED: "UI.start_shared_video",
24
     SHARED_VIDEO_CLICKED: "UI.start_shared_video",
25
+    /**
26
+     * Indicates that an invite button has been clicked.
27
+     */
28
+    INVITE_CLICKED: "UI.invite_clicked",
25
     /**
29
     /**
26
      * Updates shared video with params: url, state, time(optional)
30
      * Updates shared video with params: url, state, time(optional)
27
      * Where url is the video link, state is stop/start/pause and time is the
31
      * Where url is the video link, state is stop/start/pause and time is the
28
      * current video playing time.
32
      * current video playing time.
29
      */
33
      */
30
     UPDATE_SHARED_VIDEO: "UI.update_shared_video",
34
     UPDATE_SHARED_VIDEO: "UI.update_shared_video",
31
-    ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
32
     USER_KICKED: "UI.user_kicked",
35
     USER_KICKED: "UI.user_kicked",
33
     REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
36
     REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
34
     FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
37
     FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
114
     /**
117
     /**
115
      * Notifies that the avatar is displayed or not on the largeVideo.
118
      * Notifies that the avatar is displayed or not on the largeVideo.
116
      */
119
      */
117
-    LARGE_VIDEO_AVATAR_DISPLAYED: "UI.large_video_avatar_displayed"
120
+    LARGE_VIDEO_AVATAR_DISPLAYED: "UI.large_video_avatar_displayed",
121
+    
122
+    /**
123
+     * Toggling room lock
124
+     */
125
+    TOGGLE_ROOM_LOCK: "UI.toggle_room_lock",
126
+
127
+    /**
128
+     * Adding contact to contact list
129
+     */
130
+    CONTACT_ADDED: "UI.contact_added",
131
+
132
+    /**
133
+     * Removing the contact from contact list
134
+     */
135
+    CONTACT_REMOVED: "UI.contact_removed",
136
+
137
+    /**
138
+     * Indicates that a user avatar has changed.
139
+     */
140
+    USER_AVATAR_CHANGED: "UI.user_avatar_changed",
141
+
142
+    /**
143
+     * Display name changed.
144
+     */
145
+    DISPLAY_NAME_CHANGED: "UI.display_name_changed",
146
+
147
+    /**
148
+     * Indicates that the invite url has been initialised.
149
+     */
150
+    INVITE_URL_INITIALISED: "UI.invite_url_initialised",
151
+
152
+    /**
153
+     * Indicates that a password is required for the call.
154
+     */
155
+    PASSWORD_REQUIRED: "UI.password_required"
118
 };
156
 };

Loading…
Cancel
Save