Dialogs re-design, invite and password modifications

This commit is contained in:
yanas 2016-10-11 19:08:24 -05:00
parent d5541f612f
commit 74f31db434
38 changed files with 2034 additions and 922 deletions

View File

@ -1,8 +1,8 @@
/* global $, APP, JitsiMeetJS, config, interfaceConfig */
import {openConnection} from './connection';
//FIXME:
import createRoomLocker from './modules/UI/authentication/RoomLocker';
//FIXME:
import Invite from './modules/UI/invite/Invite';
import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
import AuthHandler from './modules/UI/authentication/AuthHandler';
import ConnectionQuality from './modules/connectionquality/connectionquality';
@ -26,7 +26,7 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
const TrackEvents = JitsiMeetJS.events.track;
const TrackErrors = JitsiMeetJS.errors.track;
let room, connection, localAudio, localVideo, roomLocker;
let room, connection, localAudio, localVideo;
/**
* Indicates whether the connection is interrupted or not.
@ -363,18 +363,7 @@ class ConferenceConnector {
switch (err) {
// room is locked by the password
case ConferenceErrors.PASSWORD_REQUIRED:
APP.UI.markRoomLocked(true);
roomLocker.requirePassword().then(function () {
let pass = roomLocker.password;
// we received that password is required, but user is trying
// anyway to login without a password, mark room as not locked
// in case he succeeds (maybe someone removed the password
// meanwhile), if it is still locked another password required
// will be received and the room again will be marked as locked
if (!pass)
APP.UI.markRoomLocked(false);
room.join(roomLocker.password);
});
APP.UI.emitEvent(UIEvents.PASSWORD_REQUIRED);
break;
case ConferenceErrors.CONNECTION_ERROR:
@ -403,8 +392,7 @@ class ConferenceConnector {
}, 5000);
// notify user that auth is required
AuthHandler.requireAuth(room, roomLocker.password);
AuthHandler.requireAuth(room, this.invite.getRoomLocker().password);
break;
case ConferenceErrors.RESERVATION_ERROR:
@ -576,6 +564,8 @@ export default {
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
APP.UI.ContactList = new ContactList(room);
// if user didn't give access to mic or camera or doesn't have
// them at all, we disable corresponding toolbar buttons
if (!tracks.find((t) => t.isAudioTrack())) {
@ -888,7 +878,7 @@ export default {
room = connection.initJitsiConference(APP.conference.roomName,
this._getConferenceOptions());
this._setLocalAudioVideoStreams(localTracks);
roomLocker = createRoomLocker(room);
this.invite = new Invite(room);
this._room = room; // FIXME do not use this
let email = APP.settings.getEmail();
@ -1316,13 +1306,6 @@ export default {
APP.UI.updateRecordingState(status);
});
room.on(ConferenceEvents.LOCK_STATE_CHANGED, (state, error) => {
console.log("Received channel password lock change: ", state,
error);
APP.UI.markRoomLocked(state);
roomLocker.lockedElsewhere = state;
});
room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
APP.UI.updateUserStatus(id, status);
});
@ -1348,19 +1331,6 @@ export default {
"resizable,scrollbars=yes,status=1");
});
APP.UI.addListener(UIEvents.ROOM_LOCK_CLICKED, () => {
if (room.isModerator()) {
let promise = roomLocker.isLocked
? roomLocker.askToUnlock()
: roomLocker.askToLock();
promise.then(() => {
APP.UI.markRoomLocked(roomLocker.isLocked);
});
} else {
roomLocker.notifyModeratorRequired();
}
});
APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
APP.UI.addListener(UIEvents.VIDEO_MUTED, muteLocalVideo);

84
css/_animations.scss Normal file
View File

@ -0,0 +1,84 @@
/**
* Project animations
**/
/**
* START of slide in animation for extended toolbar.
*/
@include keyframes(slideInX) {
0% { transform: translateX(-100%); }
100% { transform: translateX(0%); }
}
@include keyframes(slideOutX) {
0% { transform: translateX(0%); }
100% { transform: translateX(-100%); }
}
@include keyframes(slideInExtX) {
0% { transform: translateX(-500%); }
100% { transform: translateX(0%); }
}
@include keyframes(slideOutExtX) {
0% { transform: translateX(0%); }
100% { transform: translateX(-500%); }
}
/**
* END of slide out animation for extended toolbar.
*/
/**
* START of slide in / out animation for main toolbar.
*/
@include keyframes(slideInY) {
100% { transform: translateY(0%); }
}
@include keyframes(slideOutY) {
0% { transform: translateY(0%); }
100% { transform: translateY(-100%); }
}
/**
* END of slide in / out animation for main toolbar.
*/
/**
* START of slide in animation for extended toolbar (inner) panel.
*/
// FIX: Can't use percentage because of breaking animation when width is changed
// (100% of 0 is also zero) Extracted this to config variable.
@include keyframes(slideInExt) {
from { left: -$sidebarWidth; }
to { left: 0; }
}
@include keyframes(slideOutExt) {
from { left: 0; }
to { left: -$sidebarWidth; }
}
/**
* END of slide in animation for extended toolbar (inner) panel.
*/
/**
* START of slide in animation for extended toolbar container
**/
@include keyframes(slideOutExtContainer) {
from { width: $sidebarWidth; }
to { width: 0; }
}
@include keyframes(slideInExtContainer) {
from { width: 0; }
to { width: $sidebarWidth; }
}
/**
* END of slide in animation for extended toolbar container
**/

View File

@ -13,6 +13,10 @@ html, body{
overflow: hidden;
}
p {
margin: 0;
}
html, body, input, textarea, keygen, select, button {
font-family: $baseFontFamily !important;
}
@ -26,16 +30,17 @@ html, body, input, textarea, keygen, select, button {
}
input[type='text'], input[type='password'], textarea {
-webkit-user-select: text;
user-select: text;
display: inline-block;
padding: 5px;
color: $defaultDarkColor;
width: 100%;
padding: 5px 7px;
color: $inputColor;
border-radius: $borderRadius;
line-height: 32px;
letter-spacing: $letterSpacing;
height: 32px;
text-align: left;
border:1px solid $inputBorderColor;
background-color: $inputBackground;
outline: none; /* removes the default outline */
resize: none; /* prevents the user-resizing, adjust to taste */
}
@ -43,7 +48,8 @@ input[type='text'], input[type='password'], textarea {
textarea {
overflow: hidden;
word-wrap: break-word;
resize: horizontal;
resize: none;
line-height: 1.5em;
}
button.no-icon {
@ -53,12 +59,9 @@ button.no-icon {
button, input, select, textarea {
margin: 0;
vertical-align: baseline;
color: $defaultDarkColor;
background: $inputLightBackground;
font-size: 12px;
border: none;
box-shadow: none;
outline: none;
color: $inputColor;
font-size: 1em;
letter-spacing: $letterSpacing;
}
button, select, input[type="button"],
@ -72,7 +75,7 @@ input[type="reset"], input[type="submit"] {
button {
color: #FFF;
background-color: $buttonBackground !important;
background-color: $buttonBackground;
border-radius: $borderRadius;
}
@ -159,6 +162,9 @@ form {
display: flex !important;
}
/**
* Tooltips
**/
.tipsy {
z-index: $tooltipsZ;
&-inner {
@ -169,3 +175,26 @@ form {
border-color: $tooltipBg;
}
}
/**
* Dialogs fade
*/
.aui-blanket {
visibility: visible;
}
.link {
color: $linkFontColor;
@include transition(color .1s ease-out);
&:hover {
color: $linkHoverFontColor;
text-decoration: underline;
@include transition(color .1s ease-in);
}
}
#inviteLinkRef {
-webkit-user-select: text;
user-select: text;
}

View File

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

6
css/_functions.scss Normal file
View File

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

View File

@ -18,6 +18,14 @@
animation: $animations;
}
@mixin flex() {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
/**
* Keyframes mixin.
*/
@ -63,3 +71,9 @@
-webkit-transition: $transition;
transition: $transition;
}
@mixin box-shadow($type, $h, $y, $blur, $color) {
-webkit-box-shadow: $type $h $y $blur $color;
-moz-box-shadow: $type $h $y $blur $color;
box-shadow: $type $h $y $blur $color;
}

View File

@ -19,11 +19,6 @@
width: 100%;
}
.jqibuttons button {
margin-right: 5px;
float:right;
}
button.jqidefaultbutton #inviteLinkRef {
color: #2c8ad2;
}

View File

@ -6,13 +6,13 @@
position:absolute;
top: 0px;
left: $defaultToolbarSize;
width: 0%;
height: 100%;
max-width: 200px;
width: 0;
background-color: rgba(0,0,0,0.8);
height: 100%;
max-width: $sidebarWidth;
z-index: 800;
overflow: hidden;
letter-spacing: 1px;
letter-spacing: $titleLetterSpacing;
/**
* Labels inside the side panel.
@ -24,7 +24,8 @@
/**
* Form elements and blocks.
*/
input, label, select, button, a, .sideToolbarBlock {
input, label, select, a,
.sideToolbarBlock, .input-control, .button-control {
display: inline-block;
margin-top: 15px;
margin-left: 10%;
@ -36,7 +37,7 @@
*/
select, input[type="button"], input[type="text"],
input[type="reset"], input[type="submit"] {
color: $defaultColor;
color: $inputColor;
background: $inputBackground;
border: none;
}
@ -63,7 +64,10 @@
*/
.sideToolbarContainer__inner {
display: none;
width: 200px;
height: 100%;
width: $sidebarWidth;
position: absolute;
box-sizing: border-box;
color: #FFF;
/**
@ -100,6 +104,14 @@
.first {
margin-top: 0px !important;
}
/**
* Buttons in the side toolbar container.
*/
.button-control {
margin: 9px 0;
width: 100%;
}
}
}

View File

@ -45,13 +45,13 @@
border-radius: 3px;
.first {
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;
}
.last {
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 3px;
border-top-right-radius: 3px;
}
}

View File

@ -1,3 +1,8 @@
/**
* Theme
*/
@import 'themes/light';
/**
* Style variables
*/
@ -21,7 +26,8 @@ $thumbnailToolbarHeight: 25px;
*/
$defaultColor: #F1F1F1;
$defaultSideBarFontColor: #44A5FF;
$defaultDarkColor: #4F4F4F;
$defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$defaultBackground: #474747;
$tooltipBg: rgba(0,0,0, 0.7);
@ -33,11 +39,8 @@ $toolbarBadgeColor: #FFFFFF;
$toolbarToggleBackground: #12499C;
// Main controls
$inputBackground: rgba(132, 132, 132, .5);
$inputSemiBackground: rgba(132, 132, 132, .8);
$inputLightBackground: #EBEBEB;
$inputBorderColor: #EBEBEB;
$buttonBackground: #44A5FF;
// Video layout.
$videoThumbnailHovered: #BFEBFF;
@ -58,6 +61,7 @@ $rateStarLabelColor: #333;
*/
$borderRadius: 4px;
$defaultWatermarkLink: '../images/watermark.png';
$sidebarWidth: 200px;
/**
* Z-indexes. TODO: Replace this by a function.
@ -65,3 +69,17 @@ $defaultWatermarkLink: '../images/watermark.png';
$tooltipsZ: 901;
$toolbarZ: 900;
$overlayZ: 800;
/**
* Font Colors TODO: Change colors when general dialogs are implemented.
*/
$defaultFontColor: #777;
$defaultLightFontColor: #F1F1F1;
$defaultDarkFontColor: #000;
$buttonFontColor: #777;
$buttonHoverFontColor: #287ade;
$auiPrimaryButtonBg: #3572b0;
$auiPrimaryButtonHoverBg: #57647b;
$auiPrimaryButtonColor: #fff;
$auiIconColor: #707070;
$inputControlEmColor: #f29424;

View File

@ -0,0 +1,81 @@
.button-control {
box-sizing: border-box;
display: inline-block;
border: 1px solid $buttonBorder;
vertical-align: baseline;
height: 30px;
padding: 4px 10px;
margin: 0;
line-height: 1.5em;
outline: none;
background-color: transparent;
float: right;
font-size: 14px;
margin-left: 10px;
color: $buttonColor;
letter-spacing: $letterSpacing;
font-weight: $buttonFontWeight;
@include transition(background-color .1s ease-out);
&[disabled] {
color: #666;
cursor: default;
}
&_full-width {
margin: 0;
width: 100%;
}
&:hover {
border: 1px solid $buttonHoverBorder;
background-color: $buttonHoverBackground;
@include transition(background-color .1s ease-in);
}
&:active {
@include box-shadow(inset, 0, 0, 1px, $buttonShadowColor);
}
&_light {
color: $defaultDarkColor;
background-color: $buttonLightBackground;
border: 1px solid $buttonLightBorder;
&:hover {
border: 1px solid $buttonLightHoverBorder;
background-color: $buttonLightHoverBackground;
}
}
&_link {
color: $buttonLinkColor;
background-color: $buttonLinkBackground;
&:hover {
background-color: $buttonLinkBackground;
}
}
&_primary {
background-color: $primaryButtonBackground;
border: 1px solid $primaryButtonBackground;
color: $primaryButtonColor;
font-weight: $primaryButtonFontWeight;
&:hover {
border: 1px solid $primaryButtonHoverBackground;
background-color: $primaryButtonHoverBackground;
}
}
&_close {
color: $defaultFontColor;
}
&_submit {
color: $linkFontColor;
&:hover {
color: $linkHoverFontColor;
}
}
}

View File

@ -0,0 +1,49 @@
.input-control {
padding: 16px 0;
&__text {
margin: 8px 0;
font-size: 1em
}
&__label {
font-size: 1em;
font-weight: $labelFontWeight;
}
&__input {
margin: 8px 0;
&::selection {
background-color: $defaultDarkSelectionColor;
}
}
&__em {
color: $inputControlEmColor;
}
&__hint {
margin-top: 0;
font-size: $hintFontSize;
span {
vertical-align: middle;
}
}
&__container {
position: relative;
width: 100%;
@include flex();
.button-control {
margin: 9px 0 9px 10px;
}
}
&__right {
position: absolute;
right: 0;
}
}

View File

@ -1,3 +1,9 @@
/* Functions BEGIN */
@import 'functions';
/* Functions END */
/* Variables BEGIN */
@import 'variables';
@ -10,6 +16,12 @@
/* Mixins END */
/* Animations BEGIN */
@import "animations";
/* Animations END */
/* Fonts BEGIN */
@import 'font';
@ -17,6 +29,10 @@
/* Fonts END */
/* Theme BEGIN */
@import "themes/light";
/* Theme END */
/* Modules BEGIN */
@import 'toastr';
@ -25,8 +41,6 @@
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'videolayout_default';
@import 'jquery-impromptu';
@import 'modaldialog';
@import 'notice';
@import 'popup_menu';
@import 'recording';
@ -43,6 +57,8 @@
@import 'jquery.contextMenu';
@import 'keyboard-shortcuts';
@import 'redirect_page';
@import 'input-control/input-control';
@import 'shortcuts/main';
@import 'buttons/button-control';
/* Modules END */

View File

@ -2,52 +2,79 @@
visibility: visible;
height: auto;
p {
color: $defaultDarkColor;
h3 {
color: $auiDialogColor;
}
textarea {
background: none;
border: 1px solid $inputBorderColor;
.aui {
&-icon {
color: $auiDialogColor;
&-small {
width: 14px;
height: 14px;
}
.aui-dialog2-content:last-child {
}
&-iconfont-close-dialog {
cursor: pointer;
right: 20px;
position: absolute;
top: -49px;
}
&-dialog2 {
&-header, &-footer {
background-color: $auiDialogBg;
border: none;
}
&-header {
height: em(58, 12);
border-bottom: 1px solid $auiBorderColor;
h2 {
font-size: em(20, 12);
font-weight: $dialogTitleFontWeight;
letter-spacing: $titleLetterSpacing;
color: $auiDialogColor;
}
&-main {
padding-right: 0;
}
}
&-footer {
border-top: 1px solid $auiBorderColor;
}
&-content {
font-size: em(14, 12);
min-height: 0;
background-color: $auiDialogContentBg;
color: $auiDialogColor;
p,span, h3 {
font-weight: $labelFontWeight;
letter-spacing: $letterSpacing;
}
&:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
.aui-dialog2-content:first-child {
&:first-child {
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
.aui-dialog2-footer{
border-top: 0;
border-radius: 0;
padding-top: 0;
background: none;
border: none;
height: auto;
margin-top: 10px;
}
.aui-button {
height: 28px;
font-size: 12px;
padding: 3px 6px 3px 6px;
border: none;
box-shadow: none;
outline: none;
}
}
&_close {
font-weight: 400 !important;
color: $buttonBackground;
background: none !important;
:hover {
text-decoration: underline;
}
}
&_submit {
font-weight: 700 !important;
color: $defaultColor;
background: $buttonBackground;
border-radius: 3px;
}
.input-control:not(:last-child) {
border-bottom: 1px solid $auiBorderColor;
}
}

View File

@ -45,46 +45,30 @@
animation-timing-function: ease-in-out
}
.feedback {
.feedback.aui-dialog2{
.aui-dialog2{
&-header {
background-color: $auiDialogContentBg;
border-bottom-color: transparent;
padding-top: 30px;
h2 {
font-weight: 400;
font-size: 24px;
line-height: 1.2;
}
p {
font-weight: 400;
font-size: 14px;
}
&__content {
text-align: center;
}
}
textarea {
text-align: left;
min-height: 80px;
width: 100%;
}
}
&__footer {
&-content {
text-align: center;
padding: 10px 40px 20px 40px;
&:hover {
color: #287ade;
outline: 0;
}
}
&__rating {
.rating {
line-height: 1.2;
padding: 20px 0;
p {
margin: 10px 0 0;
}
text-align: center;
margin-top: 10px;
.star-label {
font-size: 16px;
color: $rateStarLabelColor;
height: 16px;
font-size: 14px;
}
.star-btn {
color: $rateStarDefault;
font-size: 36px;
@ -98,11 +82,23 @@
&.active,
&:hover {
color: $rateStarActivity;
> i:before {
content: "\e90a";
}
};
}
}
.details {
padding-left: 60px;
padding-right: 60px;
margin-top: 20px;
textarea {
min-height: 100px;
}
}
}
&-footer {
background-color: $auiDialogContentBg;
border-top-color: transparent;
}
}
}

4
css/shortcuts/_main.scss Normal file
View File

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

View File

@ -0,0 +1,11 @@
.regular-key {
display: table-cell;
width: 25px;
height: 20px;
padding: 0;
text-align: center;
vertical-align: middle;
font-family: $baseFontFamily;
color: $defaultDarkColor;
font-size: 12px;
}

View File

@ -0,0 +1,12 @@
.shortcuts-list {
padding: 0;
&__description {
margin-left: em(16, 14);
vertical-align: top;
}
&__item {
margin-bottom: em(7, 14);
}
}

46
css/themes/_light.scss Normal file
View File

@ -0,0 +1,46 @@
/**
* Buttons
*/
$buttonBackground: #f5f5f5;
$buttonHoverBackground: #e9e9e9;
$buttonBorder: #ccc;
$buttonHoverBorder: #999;
$buttonColor: #333;
$buttonLightBackground: #f5f5f5;
$buttonLightHoverBackground: #e9e9e9;
$buttonLightBorder: #ccc;
$buttonLightHoverBorder: #999;
$buttonLinkBackground: transparent;
$buttonLinkColor: #0090e8;
$primaryButtonBackground: #3572b0;
$primaryButtonHoverBackground: #2a67a5;
$primaryButtonColor: #fff;
$primaryButtonFontWeight: 400;
$buttonShadowColor: #192d4f;
/**
* Dialog colors
**/
$auiDialogColor: #333;
$auiDialogBg: #f5f5f5;
$auiDialogContentBg: #fff;
$auiBorderColor: #ccc;
$dialogTitleFontWeight: 400;
// Main controls
$inputBackground: #fff;
$inputBorderColor: #ccc;
$inputColor: #333;
$defaultDarkSelectionColor: #ccc;
$titleLetterSpacing: 0;
$letterSpacing: 0;
$buttonFontWeight: 400;
$labelFontWeight: 400;
$hintFontSize: em(13, 14);
$linkFontColor: #3572b0;
$linkHoverFontColor: darken(#3572b0, 10%);
$dropdownColor: #333;

View File

@ -132,7 +132,6 @@
</span>
</a>
<a class="button" id="toolbar_button_record" style="display: none"></a>
<a class="button icon-security" id="toolbar_button_security"></a>
<a class="button icon-share-doc" id="toolbar_button_etherpad"></a>
<a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none">
<ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
@ -254,12 +253,10 @@
</div>
</div>
<div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;">
<div class="header"><h3 data-i18n="keyboardShortcuts.keyboardShortcuts"></h3></div>
<div class="content">
<ul id="keyboard-shortcuts-list" class="item">
<ul id="keyboard-shortcuts-list" class="shortcuts-list">
</ul>
</div>
</div>
<div id="aui-feedback-dialog" class="dialog feedback aui-layer aui-dialog2 aui-dialog2-medium" style="display: none;"></div>
</body>
</html>

View File

@ -1,9 +1,13 @@
{
"contactlist": "On Call",
"contactlist": "Participants",
"addParticipants": "Add Participants",
"roomLocked": "Callers must enter a password",
"roomUnlocked": "Anyone with the link can join",
"passwordSetRemotely": "set by another participant",
"connectionsettings": "Connection Settings",
"poweredby": "powered by",
"feedback": "Give us your feedback",
"roomUrlDefaultMsg": "Your conference is currently being created...",
"inviteUrlDefaultMsg": "Your conference is currently being created...",
"me": "me",
"speaker": "Speaker",
"raisedHand": "Would like to speak",
@ -180,8 +184,10 @@
"raisedHand": "Would like to speak."
},
"dialog": {
"add": "Add",
"kickMessage": "Ouch! You have been kicked out of the meet!",
"popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser's security settings and try again.",
"passwordErrorTitle": "Password Error",
"passwordError": "This conversation is currently protected by a password. Only the owner of the conference can set a password.",
"passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference can set a password.",
"connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
@ -189,6 +195,7 @@
"connecting": "Connecting",
"copy": "Copy",
"error": "Error",
"addPassword": "Add Password",
"detectext": "Error when trying to detect desktopsharing extension.",
"failtoinstall": "Failed to install desktop sharing extension",
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
@ -205,10 +212,13 @@
"SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
"SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
"oops": "Oops!",
"currentPassword": "The current password is",
"passwordLabel": "Password",
"defaultError": "There was some kind of error",
"passwordRequired": "Password required",
"Ok": "Ok",
"Remove": "Remove",
"removePassword": "Remove password",
"shareVideoTitle": "Share a video",
"shareVideoLinkError": "Please provide a correct youtube link.",
"removeSharedVideoTitle": "Remove shared video",
@ -218,6 +228,7 @@
"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.",
"IamHost": "I am the host",
"Cancel": "Cancel",
"Submit": "Submit",
"retry": "Retry",
"logoutTitle" : "Logout",
"logoutQuestion" : "Are you sure you want to logout and stop the conference?",
@ -231,7 +242,6 @@
"Dial": "Dial",
"sipMsg": "Enter SIP number",
"passwordCheck": "Are you sure you would like to remove your password?",
"Remove": "Remove",
"passwordMsg": "Set a password to lock your room",
"shareLink": "Copy and share this link",
"settings1": "Configure your conference",

View File

@ -4,7 +4,6 @@ var UI = {};
import Chat from "./side_pannels/chat/Chat";
import Toolbar from "./toolbars/Toolbar";
import ToolbarToggler from "./toolbars/ToolbarToggler";
import ContactList from "./side_pannels/contactlist/ContactList";
import Avatar from "./avatar/Avatar";
import SideContainerToggler from "./side_pannels/SideContainerToggler";
import UIUtil from "./util/UIUtil";
@ -29,7 +28,6 @@ UI.messageHandler = require("./util/MessageHandler");
var messageHandler = UI.messageHandler;
var JitsiPopover = require("./util/JitsiPopover");
var Feedback = require("./feedback/Feedback");
import FollowMe from "../FollowMe";
var eventEmitter = new EventEmitter();
@ -242,7 +240,7 @@ UI.showChatError = function (err, msg) {
* @param {string} displayName new nickname
*/
UI.changeDisplayName = function (id, displayName) {
ContactList.onDisplayNameChange(id, displayName);
UI.ContactList.onDisplayNameChange(id, displayName);
VideoLayout.onDisplayNameChanged(id, displayName);
if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
@ -292,14 +290,16 @@ UI.initConference = function () {
// "https:" + "//" + "example.com:8888" + "/SomeConference1245"
var inviteURL = window.location.protocol + "//" +
window.location.host + window.location.pathname;
Toolbar.updateRoomUrl(inviteURL);
this.emitEvent(UIEvents.INVITE_URL_INITIALISED, inviteURL);
// Clean up the URL displayed by the browser
if (window.history && typeof window.history.replaceState === 'function') {
window.history.replaceState({}, document.title, inviteURL);
}
// Add myself to the contact list.
ContactList.addContact(id, true);
UI.ContactList.addContact(id, true);
// Update default button states before showing the toolbar
// if local role changes buttons state will be again updated.
@ -470,8 +470,6 @@ UI.start = function () {
}
VideoLayout.resizeVideoArea(true, true);
ContactList.init(eventEmitter);
bindEvents();
sharedVideoManager = new SharedVideoManager(eventEmitter);
if (!interfaceConfig.filmStripOnly) {
@ -608,7 +606,7 @@ UI.addUser = function (user) {
var id = user.getId();
var displayName = user.getDisplayName();
UI.hideRingOverLay();
ContactList.addContact(id);
UI.ContactList.addContact(id);
messageHandler.notify(
displayName,'notify.somebody', 'connected', 'notify.connected'
@ -635,7 +633,7 @@ UI.addUser = function (user) {
* @param {string} displayName user nickname
*/
UI.removeUser = function (id, displayName) {
ContactList.removeContact(id);
UI.ContactList.removeContact(id);
messageHandler.notify(
displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
@ -786,28 +784,33 @@ UI.connectionIndicatorShowMore = function(id) {
// FIXME check if someone user this
UI.showLoginPopup = function(callback) {
console.log('password is required');
var message = '<h2 data-i18n="dialog.passwordRequired">';
message += APP.translation.translateString(
"dialog.passwordRequired");
message += '</h2>' +
'<input name="username" type="text" ' +
'placeholder="user@domain.net" autofocus>' +
'<input name="password" ' +
'type="password" data-i18n="[placeholder]dialog.userPassword"' +
' placeholder="user password">';
messageHandler.openTwoButtonDialog(null, null, null, message,
true,
"dialog.Ok",
function (e, v, m, f) {
let titleKey = "dialog.passwordRequired";
let titleString = APP.translation.translateString(titleKey);
let message = (
`<input name="username" type="text"
placeholder="user@domain.net" autofocus>
<input name="password" type="password"
data-i18n="[placeholder]dialog.userPassword"
placeholder="user password">`
);
let submitFunction = (e, v, m, f) => {
if (v) {
if (f.username && f.password) {
callback(f.username, f.password);
}
}
},
null, null, ':input:first'
};
);
messageHandler.openTwoButtonDialog({
titleKey,
titleString,
msgString: message,
leftButtonKey: 'dialog.Ok',
submitFunction,
focus: ':input:first'
});
};
UI.askForNickname = function () {
@ -888,7 +891,7 @@ UI.dockToolbar = function (isDock) {
*/
function changeAvatar(id, avatarUrl) {
VideoLayout.changeUserAvatar(id, avatarUrl);
ContactList.changeUserAvatar(id, avatarUrl);
UI.ContactList.changeUserAvatar(id, avatarUrl);
if (APP.conference.isLocalId(id)) {
Profile.changeAvatar(avatarUrl);
}
@ -1054,18 +1057,6 @@ UI.markVideoInterrupted = function (interrupted) {
}
};
/**
* Mark room as locked or not.
* @param {boolean} locked if room is locked.
*/
UI.markRoomLocked = function (locked) {
if (locked) {
Toolbar.lockLockButton();
} else {
Toolbar.unlockLockButton();
}
};
/**
* Add chat message.
* @param {string} from user id
@ -1254,24 +1245,27 @@ UI.showExtensionRequiredDialog = function (url) {
* @param url {string} the url of the extension.
*/
UI.showExtensionExternalInstallationDialog = function (url) {
messageHandler.openTwoButtonDialog(
"dialog.externalInstallationTitle",
null,
"dialog.externalInstallationMsg",
null,
true,
"dialog.goToStore",
function(e,v) {
let submitFunction = function(e,v){
if (v) {
e.preventDefault();
eventEmitter.emit(UIEvents.OPEN_EXTENSION_STORE, url);
}
},
function () {},
function () {
};
let closeFunction = function () {
eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
}
);
};
messageHandler.openTwoButtonDialog({
titleKey: 'dialog.externalInstallationTitle',
titleString: '',
msgKey: 'dialog.externalInstallationMsg',
msgString: '',
leftButtonKey: 'dialog.goToStore',
submitFunction,
loadedFunction: $.noop,
closeFunction
});
};
@ -1518,7 +1512,12 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
* Shows or hides the keyboard shortcuts panel, depending on the current state.'
*/
UI.toggleKeyboardShortcutsPanel = function() {
$('#keyboard-shortcuts').toggle();
let titleKey = 'keyboardShortcuts.keyboardShortcuts';
let title = APP.translation.translateString(titleKey);
let msg = $('#keyboard-shortcuts').html();
let buttons = { Close: true };
messageHandler.openDialog(title, msg, true, buttons);
};
/**

View File

@ -1,244 +0,0 @@
/* global APP, JitsiMeetJS */
import UIUtil from '../util/UIUtil';
/**
* Show dialog which asks user for new password for the conference.
* @returns {Promise<string>} password or nothing if user canceled
*/
function askForNewPassword () {
let passMsg = APP.translation.generateTranslationHTML("dialog.passwordMsg");
let yourPassMsg = APP.translation.translateString("dialog.yourPassword");
let msg = `
<h2>${passMsg}</h2>
<input name="lockKey" type="text"
data-i18n="[placeholder]dialog.yourPassword"
placeholder="${yourPassMsg}" autofocus>
`;
return new Promise(function (resolve, reject) {
APP.UI.messageHandler.openTwoButtonDialog(
null, null, null,
msg, false, "dialog.Save",
function (e, v, m, f) {
if (v && f.lockKey) {
resolve(UIUtil.escapeHtml(f.lockKey));
}
else {
reject(APP.UI.messageHandler.CANCEL);
}
},
null, null, 'input:first'
);
});
}
/**
* Show dialog which asks for required conference password.
* @returns {Promise<string>} password or nothing if user canceled
*/
function askForPassword () {
let passRequiredMsg = APP.translation.translateString(
"dialog.passwordRequired"
);
let passMsg = APP.translation.translateString("dialog.password");
let msg = `
<h2 data-i18n="dialog.passwordRequired">${passRequiredMsg}</h2>
<input name="lockKey" type="text"
data-i18n="[placeholder]dialog.password"
placeholder="${passMsg}" autofocus>
`;
return new Promise(function (resolve, reject) {
APP.UI.messageHandler.openTwoButtonDialog(
null, null, null, msg,
true, "dialog.Ok",
function () {}, null,
function (e, v, m, f) {
if (v && f.lockKey) {
resolve(UIUtil.escapeHtml(f.lockKey));
} else {
reject(APP.UI.messageHandler.CANCEL);
}
},
':input:first'
);
});
}
/**
* Show dialog which asks if user want remove password from the conference.
* @returns {Promise}
*/
function askToUnlock () {
return new Promise(function (resolve, reject) {
APP.UI.messageHandler.openTwoButtonDialog(
null, null, "dialog.passwordCheck",
null, false, "dialog.Remove",
function (e, v) {
if (v) {
resolve();
} else {
reject(APP.UI.messageHandler.CANCEL);
}
}
);
});
}
/**
* Show notification that user cannot set password for the conference
* because server doesn't support that.
*/
function notifyPasswordNotSupported () {
console.warn('room passwords not supported');
APP.UI.messageHandler.showError(
"dialog.warning", "dialog.passwordNotSupported");
}
/**
* Show notification that setting password for the conference failed.
* @param {Error} err error
*/
function notifyPasswordFailed(err) {
console.warn('setting password failed', err);
APP.UI.messageHandler.showError(
"dialog.lockTitle", "dialog.lockMessage");
}
const ConferenceErrors = JitsiMeetJS.errors.conference;
/**
* Create new RoomLocker for the conference.
* It allows to set or remove password for the conference,
* or ask for required password.
* @returns {RoomLocker}
*/
export default function createRoomLocker (room) {
let password;
let dialog = null;
/**
* If the room was locked from someone other than us, we indicate it with
* this property in order to have correct roomLocker state of isLocked.
* @type {boolean} whether room is locked, but not from us.
*/
let lockedElsewhere = false;
function lock (newPass) {
return room.lock(newPass).then(function () {
password = newPass;
}).catch(function (err) {
console.error(err);
if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
notifyPasswordNotSupported();
} else {
notifyPasswordFailed(err);
}
throw err;
});
}
/**
* @class RoomLocker
*/
return {
get isLocked () {
return !!password || lockedElsewhere;
},
get password () {
return password;
},
/**
* Sets that the room is locked from another user, not us.
* @param {boolean} value locked/unlocked state
*/
set lockedElsewhere (value) {
lockedElsewhere = value;
},
/**
* Whether room is locked from someone else.
* @returns {boolean} whether room is not locked locally,
* but it is still locked.
*/
get lockedElsewhere () {
return lockedElsewhere;
},
/**
* Allows to remove password from the conference (asks user first).
* @returns {Promise}
*/
askToUnlock () {
return askToUnlock().then(
() => { return lock(); }
).then(function () {
JitsiMeetJS.analytics.sendEvent('toolbar.lock.disabled');
}).catch(
reason => {
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
},
/**
* Allows to set password for the conference.
* It asks user for new password and locks the room.
* @returns {Promise}
*/
askToLock () {
return askForNewPassword().then(
newPass => { return lock(newPass);}
).then(function () {
JitsiMeetJS.analytics.sendEvent('toolbar.lock.enabled');
}).catch(
reason => {
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
},
/**
* Asks user for required conference password.
*/
requirePassword () {
return askForPassword().then(
newPass => { password = newPass; }
).catch(
reason => {
// user canceled, no pass was entered.
// clear, as if we use the same instance several times
// pass stays between attempts
password = null;
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
},
/**
* Show notification that to set/remove password user must be moderator.
*/
notifyModeratorRequired () {
if (dialog)
return;
let closeCallback = function () {
dialog = null;
};
if (this.isLocked) {
dialog = APP.UI.messageHandler
.openMessageDialog(null, "dialog.passwordError",
null, null, closeCallback);
} else {
dialog = APP.UI.messageHandler
.openMessageDialog(null, "dialog.passwordError2",
null, null, closeCallback);
}
}
};
}

View File

@ -1,6 +1,6 @@
/* global $, APP, JitsiMeetJS */
import UIEvents from "../../../service/UI/UIEvents";
import FeedabckWindow from "./FeedbackWindow";
import FeedbackWindow from "./FeedbackWindow";
/**
* Shows / hides the feedback button.
@ -49,7 +49,7 @@ var Feedback = {
_showFeedbackButton(this.enabled);
this.window = new FeedabckWindow({});
this.window = new FeedbackWindow();
$("#feedbackButton").click(Feedback.openFeedbackWindow);

View File

@ -1,6 +1,12 @@
/* global $, APP, interfaceConfig, AJS */
/* global $, APP, interfaceConfig */
const selector = '#aui-feedback-dialog';
const labels = {
1: 'Very Bad',
2: 'Bad',
3: 'Average',
4: 'Good',
5: 'Very Good'
};
/**
* Toggles the appropriate css class for the given number of stars, to
@ -9,12 +15,18 @@ const selector = '#aui-feedback-dialog';
* @param starCount the number of stars, for which to toggle the css class
*/
function toggleStars(starCount) {
let labelEl = $('#starLabel');
let label = starCount >= 0 ?
labels[starCount + 1] :
'';
$('#stars > a').each(function(index, el) {
if (index <= starCount) {
el.classList.add("starHover");
} else
el.classList.remove("starHover");
});
labelEl.text(label);
}
/**
@ -23,20 +35,19 @@ function toggleStars(starCount) {
* @returns {string} the contructed html string
*/
function createRateFeedbackHTML() {
let rateExperience
= APP.translation.translateString('dialog.rateExperience'),
feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
let feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION)
? "icon-star shake-rotate"
: "icon-star";
? "icon-star-full shake-rotate"
: "icon-star-full";
return `
<div class="aui-dialog2-content feedback__content">
<form action="javascript:false;" onsubmit="return false;">
<div class="feedback__rating">
<h2>${ rateExperience }</h2>
<p class="star-label">&nbsp;</p>
<form id="feedbackForm"
action="javascript:false;" onsubmit="return false;">
<div class="rating">
<div class="star-label">
<p id="starLabel">&nbsp;</p>
</div>
<div id="stars" class="feedback-stars">
<a class="star-btn">
<i class=${ starClassName }></i>
@ -54,32 +65,21 @@ function createRateFeedbackHTML() {
<i class=${ starClassName }></i>
</a>
</div>
<p>&nbsp;</p>
<p>${ feedbackHelp }</p>
</div>
<textarea id="feedbackTextArea" rows="10" cols="40" autofocus>
</textarea>
</form>
<footer class="aui-dialog2-footer feedback__footer">
<div class="aui-dialog2-footer-actions">
<button
id="dialog-close-button"
class="aui-button aui-button_close">Close</button>
<button
id="dialog-submit-button"
class="aui-button aui-button_submit">Submit</button>
<div class="details">
<textarea id="feedbackTextArea" class="input-control__input"
placeholder="${ feedbackHelp }"></textarea>
</div>
</footer>
</div>
`;
</form>`;
}
/**
* Callback for Rate Feedback
* Feedback is loaded callback
* Calls when Modal window is in DOM
*
* @param Feedback
*/
let onLoadRateFunction = function (Feedback) {
let onLoadFunction = function (Feedback) {
$('#stars > a').each((index, el) => {
el.onmouseover = function(){
toggleStars(index);
@ -89,6 +89,7 @@ let onLoadRateFunction = function (Feedback) {
};
el.onclick = function(){
Feedback.feedbackScore = index + 1;
Feedback.setFeedbackMessage();
};
});
@ -97,101 +98,88 @@ let onLoadRateFunction = function (Feedback) {
toggleStars(Feedback.feedbackScore - 1);
}
if (Feedback.feedbackText && Feedback.feedbackText.length > 0)
$('#feedbackTextArea').text(Feedback.feedbackText);
let submitBtn = Feedback.$el.find('#dialog-submit-button');
let closeBtn = Feedback.$el.find('#dialog-close-button');
if (submitBtn && submitBtn.length) {
submitBtn.on('click', (e) => {
e.preventDefault();
Feedback.onFeedbackSubmitted();
});
}
if (closeBtn && closeBtn.length) {
closeBtn.on('click', (e) => {
e.preventDefault();
Feedback.hide();
});
}
if (Feedback.feedbackMessage && Feedback.feedbackMessage.length > 0)
$('#feedbackTextArea').text(Feedback.feedbackMessage);
$('#feedbackTextArea').focus();
};
/**
* On Feedback Submitted callback
*
* @param Feedback
*/
function onFeedbackSubmitted(Feedback) {
let form = $('#feedbackForm');
let message = form.find('textarea').val();
APP.conference.sendFeedback(
Feedback.feedbackScore,
message);
// TODO: make sendFeedback return true or false.
Feedback.submitted = true;
//Remove history is submitted
Feedback.feedbackScore = -1;
Feedback.feedbackMessage = '';
Feedback.onHide();
}
/**
* On Feedback Closed callback
*
* @param Feedback
*/
function onFeedbackClosed(Feedback) {
Feedback.onHide();
}
/**
* @class Dialog
*
*/
export default class Dialog {
constructor(options) {
constructor() {
this.feedbackScore = -1;
this.feedbackText = null;
this.feedbackMessage = '';
this.submitted = false;
this.onCloseCallback = null;
this.onCloseCallback = function() {};
this.states = {
rate_feedback: {
getHtml: createRateFeedbackHTML,
onLoad: onLoadRateFunction
this.setDefoulOptions();
}
setDefoulOptions() {
var self = this;
this.options = {
titleKey: 'dialog.rateExperience',
msgString: createRateFeedbackHTML(),
loadedFunction: function() {onLoadFunction(self);},
submitFunction: function() {onFeedbackSubmitted(self);},
closeFunction: function() {onFeedbackClosed(self);},
wrapperClass: 'feedback',
size: 'medium'
};
this.state = options.state || 'rate_feedback';
this.window = AJS.dialog2(selector, {
closeOnOutsideClick: true
});
this.$el = this.window.$el;
AJS.dialog2(selector).on("hide", function() {
if (this.onCloseCallback) {
this.onCloseCallback();
this.onCloseCallback = null;
}
}.bind(this));
this.setState();
}
setState(state) {
let newState = state || this.state;
setFeedbackMessage() {
let message = $('#feedbackTextArea').val();
let htmlStr = this.states[newState].getHtml(this);
this.$el.html(htmlStr);
this.states[newState].onLoad(this);
this.feedbackMessage = message;
}
show(cb) {
this.setState('rate_feedback');
if (typeof cb == 'function') {
const options = this.options;
if (typeof cb === 'function') {
this.onCloseCallback = cb;
}
this.window.show();
this.window = APP.UI.messageHandler.openTwoButtonDialog(options);
}
hide() {
this.window.hide();
}
onFeedbackSubmitted() {
let message = this.$el.find('textarea').val();
let self = this;
if (message && message.length > 0) {
self.feedbackText = message;
}
APP.conference.sendFeedback(self.feedbackScore,
self.feedbackText);
// TO DO: make sendFeedback return true or false.
self.submitted = true;
this.hide();
onHide() {
this.onCloseCallback(this.feedbackScore, this.feedbackMessage);
}
}

View File

@ -0,0 +1,33 @@
/* global APP, $ */
import UIUtil from '../util/UIUtil';
/**
* Show dialog which asks for required conference password.
* @returns {Promise<string>} password or nothing if user canceled
*/
export default function askForPassword () {
let titleKey = "dialog.passwordRequired";
let passMsg = APP.translation.translateString("dialog.password");
let msgString = `
<input name="lockKey" type="text"
data-i18n="[placeholder]dialog.password"
placeholder="${passMsg}" autofocus>
`;
return new Promise(function (resolve, reject) {
APP.UI.messageHandler.openTwoButtonDialog({
titleKey,
msgString,
leftButtonKey: "dialog.Ok",
submitFunction: $.noop,
closeFunction: function (e, v, m, f) {
if (v && f.lockKey) {
resolve(UIUtil.escapeHtml(f.lockKey));
} else {
reject(APP.UI.messageHandler.CANCEL);
}
},
focus: ':input:first'
});
});
}

216
modules/UI/invite/Invite.js Normal file
View File

@ -0,0 +1,216 @@
/* global JitsiMeetJS, APP */
import InviteDialogView from './InviteDialogView';
import createRoomLocker from './RoomLocker';
import UIEvents from '../../../service/UI/UIEvents';
const ConferenceEvents = JitsiMeetJS.events.conference;
/**
* Invite module
* Constructor takes conference object giving
* ability to subscribe on its events
*/
class Invite {
constructor(conference) {
this.conference = conference;
this.createRoomLocker(conference);
this.initDialog();
this.registerListeners();
}
/**
* Registering listeners.
* Primarily listeners for conference events.
*/
registerListeners() {
this.conference.on(ConferenceEvents.LOCK_STATE_CHANGED,
(locked, error) => {
console.log("Received channel password lock change: ", locked,
error);
if (!locked) {
this.roomLocker.resetPassword();
}
this.setLockedFromElsewhere(locked);
});
this.conference.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
if (APP.conference.isLocalId(id)
&& this.isModerator !== this.conference.isModerator) {
this.setModerator(this.conference.isModerator);
}
});
APP.UI.addListener( UIEvents.INVITE_CLICKED,
() => { this.openLinkDialog(); });
APP.UI.addListener( UIEvents.INVITE_URL_INITIALISED,
(inviteUrl) => {
this.updateInviteUrl(inviteUrl);
});
APP.UI.addListener( UIEvents.PASSWORD_REQUIRED,
() => {
this.setLockedFromElsewhere(true);
this.roomLocker.requirePassword().then(() => {
let pass = this.roomLocker.password;
// we received that password is required, but user is trying
// anyway to login without a password, mark room as not
// locked in case he succeeds (maybe someone removed the
// password meanwhile), if it is still locked another
// password required will be received and the room again
// will be marked as locked.
if (!pass)
this.setLockedFromElsewhere(false);
this.conference.join(this.roomLocker.password);
});
});
}
/**
* Updates the view.
* If dialog hasn't been defined -
* creates it and updates
*/
updateView() {
if (!this.view) {
this.initDialog();
}
this.view.updateView();
}
/**
* Room locker factory
* @param room
* @returns {Object} RoomLocker
* @factory
*/
createRoomLocker(room = this.conference) {
let roomLocker = createRoomLocker(room);
this.roomLocker = roomLocker;
return this.getRoomLocker();
}
/**
* Room locker getter
* @returns {Object} RoomLocker
*/
getRoomLocker() {
return this.roomLocker;
}
/**
* Opens the invite link dialog.
*/
openLinkDialog () {
if (!this.view) {
this.initDialog();
}
this.view.open();
}
/**
* Dialog initialization.
* creating view object using as a model this module
*/
initDialog() {
this.password = this.getPassword();
this.view = new InviteDialogView(this);
}
/**
* Password getter
* @returns {String} password
*/
getPassword() {
return this.roomLocker.password;
}
/**
* Switches between the moderator view and normal view.
*
* @param isModerator indicates if the participant is moderator
*/
setModerator(isModerator) {
this.isModerator = isModerator;
this.updateView();
}
/**
* Allows to unlock the room.
* If the current user is moderator.
*/
setRoomUnlocked() {
if (this.isModerator) {
this.roomLocker.lock().then(() => {
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
this.updateView();
});
}
}
/**
* Allows to lock the room if
* the current user is moderator.
* Takes the password.
* @param {String} newPass
*/
setRoomLocked(newPass) {
let isModerator = this.isModerator;
if (isModerator && (newPass || !this.roomLocker.isLocked)) {
this.roomLocker.lock(newPass).then(() => {
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
this.updateView();
});
}
}
/**
* Updates the room invite url.
*/
updateInviteUrl (newInviteUrl) {
this.inviteUrl = newInviteUrl;
this.updateView();
}
/**
* Helper method for encoding
* Invite URL
* @returns {string}
*/
getEncodedInviteUrl() {
return encodeURI(this.inviteUrl);
}
/**
* Is locked flag.
* Delegates to room locker
* @returns {Boolean} isLocked
*/
isLocked() {
return this.roomLocker.isLocked;
}
/**
* Set flag locked from elsewhere to room locker.
* @param isLocked
*/
setLockedFromElsewhere(isLocked) {
let oldLockState = this.roomLocker.lockedElsewhere;
if (oldLockState !== isLocked) {
this.roomLocker.lockedElsewhere = isLocked;
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
this.updateView();
}
}
}
export default Invite;

View File

@ -0,0 +1,382 @@
/* global $, APP, JitsiMeetJS */
/**
* Substate for password
* @type {{LOCKED: string, UNLOCKED: string}}
*/
const States = {
LOCKED: 'locked',
UNLOCKED: 'unlocked'
};
/**
* Class representing view for Invite dialog
* @class InviteDialogView
*/
export default class InviteDialogView {
constructor(model) {
let InviteAttributesKey = 'inviteUrlDefaultMsg';
let title = APP.translation.translateString(InviteAttributesKey);
this.unlockHint = "unlockHint";
this.lockHint = "lockHint";
this.model = model;
if (this.model.inviteUrl === null) {
this.inviteAttributes = (
`data-i18n="[value]inviteUrlDefaultMsg" value="${title}"`
);
} else {
let encodedInviteUrl = this.model.getEncodedInviteUrl();
this.inviteAttributes = `value="${encodedInviteUrl}"`;
}
this.initDialog();
}
/**
* Initialization of dialog property
*/
initDialog() {
let dialog = {};
dialog.closeFunction = this.closeFunction.bind(this);
dialog.submitFunction = this.submitFunction.bind(this);
dialog.loadedFunction = this.loadedFunction.bind(this);
let titleKey = "dialog.shareLink";
let titleString = APP.translation.generateTranslationHTML(titleKey);
dialog.titleKey = titleKey;
dialog.titleString = titleString;
this.dialog = dialog;
this.dialog.states = this.getStates();
}
/**
* Event handler for submitting dialog
* @param e
* @param v
*/
submitFunction(e, v) {
if (v && this.model.inviteUrl) {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
} else {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
}
}
/**
* Event handler for load dialog
* @param event
*/
loadedFunction(event) {
if (this.model.inviteUrl) {
document.getElementById('inviteLinkRef').select();
} else {
if (event && event.target) {
$(event.target).find('button[value=true]')
.prop('disabled', true);
}
}
}
/**
* Event handler for closing dialog
* @param e
* @param v
* @param m
* @param f
*/
closeFunction(e, v, m, f) {
$(document).off('click', '.copyInviteLink', this.copyToClipboard);
if(!v && !m && !f)
JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
}
/**
* Returns all states of the dialog
* @returns {{}}
*/
getStates() {
let {
titleString
} = this.dialog;
let states = {};
states[States.UNLOCKED] = {
title: titleString,
html: this.getShareLinkBlock() + this.getAddPasswordBlock()
};
states[States.LOCKED] = {
title: titleString,
html: this.getShareLinkBlock() + this.getPasswordBlock()
};
return states;
}
/**
* Layout for invite link input
* @returns {string}
*/
getShareLinkBlock() {
let copyKey = 'dialog.copy';
let copyText = APP.translation.translateString(copyKey);
let roomLockDescKey = 'roomLocked';
let roomLockDesc = APP.translation.translateString(roomLockDescKey);
let roomUnlockKey = 'roomUnlocked';
let roomUnlock = APP.translation.translateString(roomUnlockKey);
let classes = 'button-control button-control_light copyInviteLink';
return (
`<div class="input-control">
<label class="input-control__label" for="inviteLinkRef">
${this.dialog.titleString}
</label>
<div class="input-control__container">
<input class="input-control__input inviteLink"
id="inviteLinkRef" type="text"
${this.inviteAttributes} readonly>
<button data-i18n="${copyKey}"
class="${classes}">
${copyText}
</button>
</div>
<p class="input-control__hint ${this.lockHint}">
<span class="icon-security-locked"></span>
<span data-i18n="${roomLockDescKey}">${roomLockDesc}</span>
</p>
<p class="input-control__hint ${this.unlockHint}">
<span class="icon-security"></span>
<span data-i18n="${roomUnlockKey}">${roomUnlock}</span>
</p>
</div>`
);
}
/**
* Layout for adding password input
* @returns {string}
*/
getAddPasswordBlock() {
let addPassKey = 'dialog.addPassword';
let addPassText = APP.translation.translateString(addPassKey);
let addKey = 'dialog.add';
let addText = APP.translation.translateString(addKey);
let html;
if (this.model.isModerator) {
html = (`
<div class="input-control">
<label class="input-control__label
for="newPasswordInput"
data-i18n="${addPassKey}">${addPassText}</label>
<div class="input-control__container">
<input class="input-control__input" id="newPasswordInput"
type="text">
<button id="addPasswordBtn" id="inviteDialogAddPassword"
disabled data-i18n="${addKey}"
class="button-control button-control_light">
${addText}
</button>
</div>
</div>
`);
} else {
html = '';
}
return html;
}
/**
* Layout for password (when room is locked)
* @returns {string}
*/
getPasswordBlock() {
let { password, isModerator } = this.model;
let removePassKey = 'dialog.removePassword';
let removePassText = APP.translation.translateString(removePassKey);
let currentPassKey = 'dialog.currentPassword';
let currentPassText = APP.translation.translateString(currentPassKey);
let passwordKey = "dialog.passwordLabel";
let passwordText = APP.translation.translateString(passwordKey);
if (isModerator) {
return (`
<div class="input-control">
<label class="input-control__label"
data-i18n="${passwordKey}">${passwordText}</label>
<div class="input-control__container">
<p class="input-control__text"
data-i18n="${currentPassKey}">
${currentPassText}
<span id="inviteDialogPassword"
class="input-control__em">
${password}
</span>
</p>
<a class="link input-control__right"
id="inviteDialogRemovePassword"
href="#" data-i18n="${removePassKey}">
${removePassText}
</a>
</div>
</div>
`);
} else {
return (`
<div class="input-control">
<p>A participant protected this call with a password.</p>
</div>
`);
}
}
/**
* Opening the dialog
*/
open() {
let leftButton;
let {
submitFunction,
loadedFunction,
closeFunction
} = this.dialog;
let states = this.getStates();
let buttons = [];
let leftButtonKey = "dialog.Invite";
let cancelButton
= APP.translation.generateTranslationHTML("dialog.Cancel");
buttons.push({title: cancelButton, value: false});
leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
buttons.push({ title: leftButton, value: true});
let initial = this.model.roomLocked ? States.LOCKED : States.UNLOCKED;
APP.UI.messageHandler.openDialogWithStates(states, {
submit: submitFunction,
loaded: loadedFunction,
close: closeFunction,
buttons,
size: 'medium'
});
$.prompt.goToState(initial);
this.registerListeners();
this.updateView();
}
/**
* Setting event handlers
* used in dialog
*/
registerListeners() {
let $passInput = $('#newPasswordInput');
let $addPassBtn = $('#addPasswordBtn');
$(document).on('click', '.copyInviteLink', this.copyToClipboard);
$addPassBtn.on('click', () => {
let newPass = $passInput.val();
if(newPass) {
this.model.setRoomLocked(newPass);
}
});
$('#inviteDialogRemovePassword').on('click', () => {
this.model.setRoomUnlocked();
});
$passInput.keyup(this.disableAddPassIfInputEmpty.bind(this));
}
/**
* Checking input and if it's empty then
* disable add pass button
*/
disableAddPassIfInputEmpty() {
let $passInput = $('#newPasswordInput');
let $addPassBtn = $('#addPasswordBtn');
if(!$passInput.val()) {
$addPassBtn.prop('disabled', true);
} else {
$addPassBtn.prop('disabled', false);
}
}
/**
* Copying text to clipboard
*/
copyToClipboard() {
$('.inviteLink').each(function () {
let $el = $(this).closest('.jqistate');
// TOFIX: We can select only visible elements
if($el.css('display') === 'block') {
this.select();
try {
document.execCommand('copy');
this.blur();
}
catch (err) {
console.error('error when copy the text');
}
}
});
}
/**
* Method syncing the view and the model
*/
updateView() {
let pass = this.model.getPassword();
if (!pass)
pass = APP.translation.translateString("passwordSetRemotely");
$('#inviteDialogPassword').text(pass);
$('#newPasswordInput').val('');
this.disableAddPassIfInputEmpty();
this.updateInviteLink();
$.prompt.goToState(
(this.model.isLocked())
? States.LOCKED
: States.UNLOCKED);
let roomLocked = `.${this.lockHint}`;
let roomUnlocked = `.${this.unlockHint}`;
let showDesc = this.model.isLocked() ? roomLocked : roomUnlocked;
let hideDesc = !this.model.isLocked() ? roomLocked : roomUnlocked;
$(showDesc).show();
$(hideDesc).hide();
}
/**
* Updates invite link
*/
updateInviteLink() {
// If the invite dialog has been already opened we update the
// information.
let inviteLink = document.querySelectorAll('.inviteLink');
let list = Array.from(inviteLink);
list.forEach((inviteLink) => {
inviteLink.value = this.model.inviteUrl;
inviteLink.select();
});
$('#inviteLinkRef').parent()
.find('button[value=true]').prop('disabled', false);
}
}

View File

@ -0,0 +1,121 @@
/* global APP, JitsiMeetJS */
import askForPassword from './AskForPassword';
/**
* Show notification that user cannot set password for the conference
* because server doesn't support that.
*/
function notifyPasswordNotSupported () {
console.warn('room passwords not supported');
APP.UI.messageHandler.showError(
"dialog.warning", "dialog.passwordNotSupported");
}
/**
* Show notification that setting password for the conference failed.
* @param {Error} err error
*/
function notifyPasswordFailed(err) {
console.warn('setting password failed', err);
APP.UI.messageHandler.showError(
"dialog.lockTitle", "dialog.lockMessage");
}
const ConferenceErrors = JitsiMeetJS.errors.conference;
/**
* Create new RoomLocker for the conference.
* It allows to set or remove password for the conference,
* or ask for required password.
* @returns {RoomLocker}
*/
export default function createRoomLocker (room) {
let password;
/**
* If the room was locked from someone other than us, we indicate it with
* this property in order to have correct roomLocker state of isLocked.
* @type {boolean} whether room is locked, but not from us.
*/
let lockedElsewhere = false;
/**
* @class RoomLocker
*/
return {
get isLocked () {
return !!password || lockedElsewhere;
},
get password () {
return password;
},
/**
* Allows to set new password
* @param newPass
* @returns {Promise.<TResult>}
*/
lock (newPass) {
return room.lock(newPass).then(() => {
password = newPass;
// If the password is undefined this means that we're removing
// it for everyone.
if (!password)
lockedElsewhere = false;
}).catch(function (err) {
console.error(err);
if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
notifyPasswordNotSupported();
} else {
notifyPasswordFailed(err);
}
throw err;
});
},
/**
* Sets that the room is locked from another user, not us.
* @param {boolean} value locked/unlocked state
*/
set lockedElsewhere (value) {
lockedElsewhere = value;
},
/**
* Whether room is locked from someone else.
* @returns {boolean} whether room is not locked locally,
* but it is still locked.
*/
get lockedElsewhere () {
return lockedElsewhere;
},
/**
* Reset the password. Can be useful when room
* has been unlocked from elsewhere and we can use
* this method for sync the pass
*/
resetPassword() {
password = null;
},
/**
* Asks user for required conference password.
*/
requirePassword () {
return askForPassword().then(
newPass => { password = newPass; }
).catch(
reason => {
// user canceled, no pass was entered.
// clear, as if we use the same instance several times
// pass stays between attempts
password = null;
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
}
};
}

View File

@ -55,9 +55,9 @@ function _requestLiveStreamId() {
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openDialogWithStates({
state0: {
title: msg,
html:
`<h2>${msg}</h2>
<input name="streamId" type="text"
`<input name="streamId" type="text"
data-i18n="[placeholder]dialog.streamKey"
placeholder="${token}" autofocus>`,
persistent: false,
@ -89,7 +89,8 @@ function _requestLiveStreamId() {
},
state1: {
html: `<h2>${msg}</h2> ${streamIdRequired}`,
title: msg,
html: streamIdRequired,
persistent: false,
buttons: [
{title: cancelButton, value: false},
@ -120,30 +121,30 @@ function _requestLiveStreamId() {
* @returns {Promise}
*/
function _requestRecordingToken () {
let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
let titleKey = "dialog.recordingToken";
let token = APP.translation.translateString("dialog.token");
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openTwoButtonDialog(
null, null, null,
`<h2>${msg}</h2>
<input name="recordingToken" type="text"
let messageString = (
`<input name="recordingToken" type="text"
data-i18n="[placeholder]dialog.token"
placeholder="${token}" autofocus>`,
false, "dialog.Save",
function (e, v, m, f) {
placeholder="${token}" autofocus>`
);
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey,
messageString,
leftButtonKey: 'dialog.Save',
submitFunction: function (e, v, m, f) {
if (v && f.recordingToken) {
resolve(UIUtil.escapeHtml(f.recordingToken));
} else {
reject(APP.UI.messageHandler.CANCEL);
}
},
null,
function () {
closeFunction: function () {
dialog = null;
},
':input:first'
);
focus: ':input:first'
});
});
}
@ -170,25 +171,21 @@ function _showStopRecordingPrompt (recordingType) {
}
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openTwoButtonDialog(
title,
null,
message,
null,
false,
buttonKey,
function(e,v) {
dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey: title,
messageKey: message,
leftButtonKey: buttonKey,
submitFunction: function(e,v) {
if (v) {
resolve();
} else {
reject();
}
},
null,
function () {
closeFunction: function () {
dialog = null;
}
);
});
});
}

View File

@ -722,26 +722,25 @@ function getYoutubeLink(url) {
*/
function showStopVideoPropmpt() {
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openTwoButtonDialog(
"dialog.removeSharedVideoTitle",
null,
"dialog.removeSharedVideoMsg",
null,
false,
"dialog.Remove",
function(e,v) {
let submitFunction = function(e,v) {
if (v) {
resolve();
} else {
reject();
}
},
null,
function () {
dialog = null;
}
);
};
let closeFunction = function () {
dialog = null;
};
dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey: "dialog.removeSharedVideoTitle",
msgKey: "dialog.removeSharedVideoMsg",
leftButtonKey: "dialog.Remove",
submitFunction,
closeFunction
});
});
}
@ -763,8 +762,8 @@ function requestVideoLink() {
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openDialogWithStates({
state0: {
title: title,
html: `
<h2>${title}</h2>
<input name="sharedVideoUrl" type="text"
data-i18n="[placeholder]defaultLink"
data-i18n-options="${JSON.stringify(i18nOptions)}"
@ -803,7 +802,8 @@ function requestVideoLink() {
},
state1: {
html: `<h2>${title}</h2> ${linkError}`,
title: title,
html: linkError,
persistent: false,
buttons: [
{title: cancelButton, value: false},

View File

@ -0,0 +1,19 @@
/**
* Class representing Contact model
* @class Contact
*/
export default class Contact {
constructor(opts) {
let {
id,
avatar,
name,
isLocal
} = opts;
this.id = id;
this.avatar = avatar || '';
this.name = name || '';
this.isLocal = isLocal || false;
}
}

View File

@ -1,151 +1,94 @@
/* global $, APP, interfaceConfig */
import Avatar from '../../avatar/Avatar';
/* global APP */
import UIEvents from '../../../../service/UI/UIEvents';
import UIUtil from '../../util/UIUtil';
let numberOfContacts = 0;
import ContactListView from './ContactListView';
import Contact from './Contact';
/**
* Updates the number of participants in the contact list button and sets
* the glow
* @param delta indicates whether a new user has joined (1) or someone has
* left(-1)
*/
function updateNumberOfParticipants(delta) {
numberOfContacts += delta;
if (numberOfContacts <= 0) {
console.error("Invalid number of participants: " + numberOfContacts);
return;
}
$("#numberOfParticipants").text(numberOfContacts);
$("#contacts_container>div.title").text(
APP.translation.translateString("contactlist")
+ ' (' + numberOfContacts + ')');
}
/**
* Creates the avatar element.
* Model for the Contact list.
*
* @return {object} the newly created avatar element
* @class ContactList
*/
function createAvatar(jid) {
let avatar = document.createElement('img');
avatar.className = "icon-avatar avatar";
avatar.src = Avatar.getAvatarUrl(jid);
return avatar;
class ContactList {
constructor(conference) {
this.conference = conference;
this.contacts = [];
this.roomLocked = false;
ContactListView.init(this);
}
/**
* Creates the display name paragraph.
* Is locked flag.
* Delegates to Invite module
* TO FIX: find a better way to access the IS LOCKED state of the invite.
*
* @param displayName the display name to set
* @returns {Boolean}
*/
function createDisplayNameParagraph(key, displayName) {
let p = document.createElement('p');
if (displayName) {
p.innerHTML = displayName;
} else if(key) {
p.setAttribute("data-i18n",key);
p.innerHTML = APP.translation.translateString(key);
}
return p;
}
function getContactEl (id) {
return $(`#contacts>li[id="${id}"]`);
isLocked() {
return APP.conference.invite.isLocked();
}
/**
* Contact list.
*/
var ContactList = {
init (emitter) {
this.emitter = emitter;
},
/**
* Indicates if the chat is currently visible.
* Adding new participant.
*
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
* otherwise
*/
isVisible () {
return UIUtil.isVisible(document.getElementById("contactlist"));
},
/**
* Adds a contact for the given id.
* @param isLocal is an id for the local user.
* @param id
* @param isLocal
*/
addContact(id, isLocal) {
let contactlist = $('#contacts');
let isExist = this.contacts.some((el) => el.id === id);
let newContact = document.createElement('li');
newContact.id = id;
newContact.className = "clickable";
newContact.onclick = (event) => {
if (event.currentTarget.className === "clickable") {
this.emitter.emit(UIEvents.CONTACT_CLICKED, id);
if (!isExist) {
let newContact = new Contact({ id, isLocal });
this.contacts.push(newContact);
APP.UI.emitEvent(UIEvents.CONTACT_ADDED, { id, isLocal });
}
};
if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
newContact.appendChild(createAvatar(id));
newContact.appendChild(
createDisplayNameParagraph(
isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null,
isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME));
if (APP.conference.isLocalId(id)) {
contactlist.prepend(newContact);
} else {
contactlist.append(newContact);
}
updateNumberOfParticipants(1);
},
/**
* Removes a contact for the given id.
* Removing participant.
*
* @param id
* @returns {Array|*}
*/
removeContact(id) {
let contact = getContactEl(id);
if (contact.length > 0) {
contact.remove();
updateNumberOfParticipants(-1);
this.contacts = this.contacts.filter((el) => el.id !== id);
APP.UI.emitEvent(UIEvents.CONTACT_REMOVED, { id });
return this.contacts;
}
},
setClickable (id, isClickable) {
getContactEl(id).toggleClass('clickable', isClickable);
},
onDisplayNameChange (id, displayName) {
if(!displayName)
/**
* Changing the display name.
*
* @param id
* @param name
*/
onDisplayNameChange (id, name) {
if(!name)
return;
if (id === 'localVideoContainer') {
id = APP.conference.getMyUserId();
}
let contactName = $(`#contacts #${id}>p`);
if (contactName.text() !== displayName) {
contactName.text(displayName);
let contacts = this.contacts.filter((el) => el.id === id);
contacts.forEach((el) => {
el.name = name;
});
APP.UI.emitEvent(UIEvents.DISPLAY_NAME_CHANGED, { id, name });
}
},
changeUserAvatar (id, avatarUrl) {
// set the avatar in the contact list
let contact = $(`#${id}>img`);
if (contact.length > 0) {
contact.attr('src', avatarUrl);
/**
* Changing the avatar.
*
* @param id
* @param avatar
*/
changeUserAvatar (id, avatar) {
let contacts = this.contacts.filter((el) => el.id === id);
contacts.forEach((el) => {
el.avatar = avatar;
});
APP.UI.emitEvent(UIEvents.USER_AVATAR_CHANGED, { id, avatar });
}
}
};
export default ContactList;

View File

@ -0,0 +1,262 @@
/* global $, APP, interfaceConfig */
import Avatar from '../../avatar/Avatar';
import UIEvents from '../../../../service/UI/UIEvents';
import UIUtil from '../../util/UIUtil';
let numberOfContacts = 0;
/**
* Updates the number of participants in the contact list button and sets
* the glow
* @param delta indicates whether a new user has joined (1) or someone has
* left(-1)
*/
function updateNumberOfParticipants(delta) {
numberOfContacts += delta;
if (numberOfContacts <= 0) {
console.error("Invalid number of participants: " + numberOfContacts);
return;
}
$("#numberOfParticipants").text(numberOfContacts);
$("#contacts_container>div.title").text(
APP.translation.translateString("contactlist")
+ ' (' + numberOfContacts + ')');
}
/**
* Creates the avatar element.
*
* @return {object} the newly created avatar element
*/
function createAvatar(jid) {
let avatar = document.createElement('img');
avatar.className = "icon-avatar avatar";
avatar.src = Avatar.getAvatarUrl(jid);
return avatar;
}
/**
* Creates the display name paragraph.
*
* @param displayName the display name to set
*/
function createDisplayNameParagraph(key, displayName) {
let p = document.createElement('p');
if (displayName) {
p.innerHTML = displayName;
} else if(key) {
p.setAttribute("data-i18n",key);
p.innerHTML = APP.translation.translateString(key);
}
return p;
}
/**
* Getter for current contact element
* @param id
* @returns {JQuery}
*/
function getContactEl (id) {
return $(`#contacts>li[id="${id}"]`);
}
/**
* Contact list.
*/
var ContactListView = {
init (model) {
this.model = model;
this.lockKey = 'roomLocked';
this.unlockKey = 'roomUnlocked';
this.addInviteButton();
this.registerListeners();
this.toggleLock();
},
/**
* Adds layout for invite button
*/
addInviteButton() {
let container = document.getElementById('contacts_container');
let title = container.firstElementChild;
let htmlLayout = this.getInviteButtonLayout();
title.insertAdjacentHTML('afterend', htmlLayout);
$(document).on('click', '#addParticipantsBtn', () => {
APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
});
},
/**
* Returns layout for invite button
*/
getInviteButtonLayout() {
let classes = 'button-control button-control_primary';
classes += ' button-control_full-width';
let key = 'addParticipants';
let text = APP.translation.translateString(key);
let lockedHtml = this.getLockDescriptionLayout(this.lockKey);
let unlockedHtml = this.getLockDescriptionLayout(this.unlockKey);
let html = (
`<div class="sideToolbarBlock first">
<button id="addParticipantsBtn"
data-i18n="${key}"
class="${classes}">
${text}
</button>
<div>
${lockedHtml}
${unlockedHtml}
</div>
</div>`);
return html;
},
/**
* Adds layout for lock description
*/
getLockDescriptionLayout(key) {
let classes = "input-control__hint input-control_full-width";
let description = APP.translation.translateString(key);
let padlockSuffix = '';
if (key === this.lockKey) {
padlockSuffix = '-locked';
}
return `<p id="contactList${key}" class="${classes}">
<span class="icon-security${padlockSuffix}"></span>
<span data-i18n="${key}">${description}</span>
</p>`;
},
/**
* Setup listeners
*/
registerListeners() {
let model = this.model;
let removeContact = this.onRemoveContact.bind(this);
let changeAvatar = this.changeUserAvatar.bind(this);
let displayNameChange = this.onDisplayNameChange.bind(this);
APP.UI.addListener( UIEvents.TOGGLE_ROOM_LOCK,
this.toggleLock.bind(this));
APP.UI.addListener( UIEvents.CONTACT_ADDED,
this.onAddContact.bind(this));
APP.UI.addListener(UIEvents.CONTACT_REMOVED, removeContact);
APP.UI.addListener(UIEvents.USER_AVATAR_CHANGED, changeAvatar);
APP.UI.addListener(UIEvents.DISPLAY_NAME_CHANGED, displayNameChange);
},
/**
* Updating the view according the model
* @param type {String} type of change
* @returns {Promise}
*/
toggleLock() {
let isLocked = this.model.isLocked();
let showKey = isLocked ? this.lockKey : this.unlockKey;
let hideKey = !isLocked ? this.lockKey : this.unlockKey;
let showId = `contactList${showKey}`;
let hideId = `contactList${hideKey}`;
$(`#${showId}`).show();
$(`#${hideId}`).hide();
},
/**
* Indicates if the chat is currently visible.
*
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
* otherwise
*/
isVisible () {
return UIUtil.isVisible(document.getElementById("contactlist"));
},
/**
* Handler for Adding a contact for the given id.
* @param isLocal is an id for the local user.
*/
onAddContact (data) {
let { id, isLocal } = data;
let contactlist = $('#contacts');
let newContact = document.createElement('li');
newContact.id = id;
newContact.className = "clickable";
newContact.onclick = (event) => {
if (event.currentTarget.className === "clickable") {
APP.UI.emitEvent(UIEvents.CONTACT_CLICKED, id);
}
};
if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
newContact.appendChild(createAvatar(id));
newContact.appendChild(
createDisplayNameParagraph(
isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null,
isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME));
if (APP.conference.isLocalId(id)) {
contactlist.prepend(newContact);
} else {
contactlist.append(newContact);
}
updateNumberOfParticipants(1);
},
/**
* Handler for removing
* a contact for the given id.
*/
onRemoveContact (data) {
let { id } = data;
let contact = getContactEl(id);
if (contact.length > 0) {
contact.remove();
updateNumberOfParticipants(-1);
}
},
setClickable (id, isClickable) {
getContactEl(id).toggleClass('clickable', isClickable);
},
/**
* Changes display name of the user
* defined by its id
* @param data
*/
onDisplayNameChange (data) {
let { id, name } = data;
if(!name)
return;
if (id === 'localVideoContainer') {
id = APP.conference.getMyUserId();
}
let contactName = $(`#contacts #${id}>p`);
if (contactName.text() !== name) {
contactName.text(name);
}
},
/**
* Changes user avatar
* @param data
*/
changeUserAvatar (data) {
let { id, avatar } = data;
// set the avatar in the contact list
let contact = $(`#${id}>img`);
if (contact.length > 0) {
contact.attr('src', avatar);
}
}
};
export default ContactListView;

View File

@ -3,64 +3,8 @@ import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import SideContainerToggler from "../side_pannels/SideContainerToggler";
let roomUrl = null;
let emitter = null;
/**
* Opens the invite link dialog.
*/
function openLinkDialog () {
let inviteAttributes;
if (roomUrl === null) {
inviteAttributes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
APP.translation.translateString("roomUrlDefaultMsg") + '"';
} else {
inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
}
let inviteLinkId = "inviteLinkRef";
let focusInviteLink = function() {
$('#' + inviteLinkId).focus();
$('#' + inviteLinkId).select();
};
let title = APP.translation.generateTranslationHTML("dialog.shareLink");
APP.UI.messageHandler.openTwoButtonDialog(
null, title, null,
'<input id="' + inviteLinkId + '" type="text" '
+ inviteAttributes + ' readonly/>',
false, "dialog.copy",
function (e, v) {
if (v && roomUrl) {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
focusInviteLink();
document.execCommand('copy');
}
else {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
}
},
function (event) {
if (!roomUrl) {
if (event && event.target) {
$(event.target).find('button[value=true]')
.prop('disabled', true);
}
}
else {
focusInviteLink();
}
},
function (e, v, m, f) {
if(!v && !m && !f)
JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
},
'Copy' // Focus Copy button.
);
}
let Toolbar;
const buttonHandlers = {
"toolbar_button_profile": function () {
@ -98,13 +42,9 @@ const buttonHandlers = {
emitter.emit(UIEvents.VIDEO_MUTED, true);
}
},
"toolbar_button_security": function () {
JitsiMeetJS.analytics.sendEvent('toolbar.lock.clicked');
emitter.emit(UIEvents.ROOM_LOCK_CLICKED);
},
"toolbar_button_link": function () {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
openLinkDialog();
emitter.emit(UIEvents.INVITE_CLICKED);
},
"toolbar_button_chat": function () {
JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
@ -158,21 +98,20 @@ const buttonHandlers = {
emitter.emit(UIEvents.AUTH_CLICKED);
},
"toolbar_button_logout": function () {
let titleKey = "dialog.logoutTitle";
let msgKey = "dialog.logoutQuestion";
JitsiMeetJS.analytics.sendEvent('toolbar.authenticate.logout.clicked');
// Ask for confirmation
APP.UI.messageHandler.openTwoButtonDialog(
"dialog.logoutTitle",
null,
"dialog.logoutQuestion",
null,
false,
"dialog.Yes",
function (evt, yes) {
APP.UI.messageHandler.openTwoButtonDialog({
titleKey,
msgKey,
leftButtonKey: "dialog.Yes",
submitFunction: function (evt, yes) {
if (yes) {
emitter.emit(UIEvents.LOGOUT);
}
}
);
});
},
"toolbar_film_strip": function () {
JitsiMeetJS.analytics.sendEvent(
@ -246,10 +185,6 @@ const defaultToolbarButtons = {
content: 'Share screen',
i18n: '[content]toolbar.sharescreen'
},
'security': {
id: 'toolbar_button_security',
tooltipKey: 'toolbar.lock'
},
'invite': {
id: 'toolbar_button_link',
tooltipKey: 'toolbar.invite',
@ -344,27 +279,28 @@ function showSipNumberInput () {
let defaultNumber = config.defaultSipNumber
? config.defaultSipNumber
: '';
let titleKey = "dialog.sipMsg";
let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
APP.UI.messageHandler.openTwoButtonDialog(
null, null, null,
`<h2>${sipMsg}</h2>
<input
name="sipNumber"
type="text"
value="${defaultNumber}"
autofocus>`,
false, "dialog.Dial",
function (e, v, m, f) {
let msgString = (`
<input name="sipNumber" type="text"
value="${defaultNumber}" autofocus>
`);
APP.UI.messageHandler.openTwoButtonDialog({
titleKey,
titleString: sipMsg,
msgString,
leftButtonKey: "dialog.Dial",
submitFunction: function (e, v, m, f) {
if (v && f.sipNumber) {
emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
}
},
null, null, ':input:first'
);
focus: ':input:first'
});
}
const Toolbar = {
Toolbar = {
init (eventEmitter) {
emitter = eventEmitter;
// The toolbar is enabled by default.
@ -446,41 +382,6 @@ const Toolbar = {
isEnabled() {
return this.enabled;
},
/**
* Updates the room invite url.
*/
updateRoomUrl (newRoomUrl) {
roomUrl = newRoomUrl;
// If the invite dialog has been already opened we update the
// information.
let inviteLink = document.getElementById('inviteLinkRef');
if (inviteLink) {
inviteLink.value = roomUrl;
inviteLink.select();
$('#inviteLinkRef').parent()
.find('button[value=true]').prop('disabled', false);
}
},
/**
* Unlocks the lock button state.
*/
unlockLockButton () {
if ($("#toolbar_button_security").hasClass("icon-security-locked"))
UIUtil.buttonClick("toolbar_button_security",
"icon-security icon-security-locked");
},
/**
* Updates the lock button state to locked.
*/
lockLockButton () {
if ($("#toolbar_button_security").hasClass("icon-security"))
UIUtil.buttonClick("toolbar_button_security",
"icon-security icon-security-locked");
},
/**
* Shows or hides authentication button
* @param show <tt>true</tt> to show or <tt>false</tt> to hide

View File

@ -52,8 +52,10 @@ var messageHandler = {
}
return $.prompt(message, {
title: title,
title: this._getFormattedTitleString(title),
persistent: false,
promptspeed: 0,
classes: this._getDialogClasses(),
close: function (e, v, m, f) {
if(closeFunction)
closeFunction(e, v, m, f);
@ -79,16 +81,31 @@ var messageHandler = {
* the user press 'enter'. Indexed from 0.
* @return the prompt that was created, or null
*/
openTwoButtonDialog: function(titleKey, titleString, msgKey, msgString,
persistent, leftButtonKey, submitFunction, loadedFunction,
closeFunction, focus, defaultButton) {
openTwoButtonDialog: function(options) {
let {
titleKey,
titleString,
msgKey,
msgString,
leftButtonKey,
submitFunction,
loadedFunction,
closeFunction,
focus,
size,
defaultButton,
wrapperClass,
classes
} = options;
if (!popupEnabled || twoButtonDialog)
return null;
var buttons = [];
var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
var leftButton = leftButtonKey ?
APP.translation.generateTranslationHTML(leftButtonKey) :
APP.translation.generateTranslationHTML('dialog.Submit');
buttons.push({ title: leftButton, value: true});
var cancelButton
@ -102,23 +119,33 @@ var messageHandler = {
if (msgKey) {
message = APP.translation.generateTranslationHTML(msgKey);
}
classes = classes || this._getDialogClasses(size);
if (wrapperClass) {
classes.prompt += ` ${wrapperClass}`;
}
twoButtonDialog = $.prompt(message, {
title: title,
title: this._getFormattedTitleString(title),
persistent: false,
buttons: buttons,
defaultButton: defaultButton,
focus: focus,
loaded: loadedFunction,
promptspeed: 0,
classes,
submit: function (e, v, m, f) {
twoButtonDialog = null;
if (v){
if (submitFunction)
submitFunction(e, v, m, f);
}
},
close: function (e, v, m, f) {
twoButtonDialog = null;
if (closeFunction)
if (closeFunction) {
closeFunction(e, v, m, f);
}
}
});
return twoButtonDialog;
},
@ -144,14 +171,16 @@ var messageHandler = {
if (!popupEnabled)
return;
var args = {
title: titleString,
let args = {
title: this._getFormattedTitleString(titleString),
persistent: persistent,
buttons: buttons,
defaultButton: 1,
promptspeed: 0,
loaded: loadedFunction,
submit: submitFunction,
close: closeFunction
close: closeFunction,
classes: this._getDialogClasses()
};
if (persistent) {
@ -161,6 +190,40 @@ var messageHandler = {
return new Impromptu(msgString, args);
},
/**
* Returns the formatted title string.
*
* @return the title string formatted as a div.
*/
_getFormattedTitleString(titleString) {
let $titleString = $('<h2>');
$titleString.addClass('aui-dialog2-header-main');
$titleString.append(titleString);
titleString = $('<div>').append($titleString).html();
return titleString;
},
/**
* Returns the dialog css classes.
*
* @return the dialog css classes
*/
_getDialogClasses(size = 'small') {
return {
box: '',
form: '',
prompt: `dialog aui-layer aui-dialog2 aui-dialog2-${size}`,
close: 'aui-icon aui-icon-small aui-iconfont-close-dialog',
fade: 'aui-blanket',
button: 'button-control',
message: 'aui-dialog2-content',
buttons: 'aui-dialog2-footer',
defaultButton: 'button-control_primary',
title: 'aui-dialog2-header'
};
},
/**
* Closes currently opened dialog.
*/
@ -176,7 +239,18 @@ var messageHandler = {
openDialogWithStates: function (statesObject, options) {
if (!popupEnabled)
return;
let { classes, size } = options;
let defaultClasses = this._getDialogClasses(size);
options.classes = Object.assign({}, defaultClasses, classes);
options.promptspeed = options.promptspeed || 0;
for (let state in statesObject) {
let currentState = statesObject[state];
if(currentState.title) {
let title = currentState.title;
currentState.title = this._getFormattedTitleString(title);
}
}
return new Impromptu(statesObject, options);
},
@ -210,6 +284,7 @@ var messageHandler = {
}
}, 200);
}
return popup;
},

View File

@ -172,19 +172,23 @@ var KeyboardShortcut = {
*/
_addShortcutToHelp: function (shortcutChar, shortcutDescriptionKey) {
var listElement = document.createElement("li");
let listElement = document.createElement("li");
let itemClass = 'shortcuts-list__item';
listElement.className = itemClass;
listElement.id = shortcutChar;
var spanElement = document.createElement("span");
let spanElement = document.createElement("span");
spanElement.className = "item-action";
var kbdElement = document.createElement("kbd");
kbdElement.className = "regular-key";
let kbdElement = document.createElement("kbd");
let classes = 'aui-label regular-key';
kbdElement.className = classes;
kbdElement.innerHTML = shortcutChar;
spanElement.appendChild(kbdElement);
var descriptionElement = document.createElement("span");
descriptionElement.className = "item-description";
let descriptionElement = document.createElement("span");
let descriptionClass = "shortcuts-list__description";
descriptionElement.className = descriptionClass;
descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey);
descriptionElement.innerHTML
= APP.translation.translateString(shortcutDescriptionKey);
@ -192,7 +196,7 @@ var KeyboardShortcut = {
listElement.appendChild(spanElement);
listElement.appendChild(descriptionElement);
var parentListElement
let parentListElement
= document.getElementById("keyboard-shortcuts-list");
if (parentListElement)

View File

@ -22,13 +22,16 @@ export default {
VIDEO_MUTED: "UI.video_muted",
ETHERPAD_CLICKED: "UI.etherpad_clicked",
SHARED_VIDEO_CLICKED: "UI.start_shared_video",
/**
* Indicates that an invite button has been clicked.
*/
INVITE_CLICKED: "UI.invite_clicked",
/**
* Updates shared video with params: url, state, time(optional)
* Where url is the video link, state is stop/start/pause and time is the
* current video playing time.
*/
UPDATE_SHARED_VIDEO: "UI.update_shared_video",
ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
USER_KICKED: "UI.user_kicked",
REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
@ -114,5 +117,40 @@ export default {
/**
* Notifies that the avatar is displayed or not on the largeVideo.
*/
LARGE_VIDEO_AVATAR_DISPLAYED: "UI.large_video_avatar_displayed"
LARGE_VIDEO_AVATAR_DISPLAYED: "UI.large_video_avatar_displayed",
/**
* Toggling room lock
*/
TOGGLE_ROOM_LOCK: "UI.toggle_room_lock",
/**
* Adding contact to contact list
*/
CONTACT_ADDED: "UI.contact_added",
/**
* Removing the contact from contact list
*/
CONTACT_REMOVED: "UI.contact_removed",
/**
* Indicates that a user avatar has changed.
*/
USER_AVATAR_CHANGED: "UI.user_avatar_changed",
/**
* Display name changed.
*/
DISPLAY_NAME_CHANGED: "UI.display_name_changed",
/**
* Indicates that the invite url has been initialised.
*/
INVITE_URL_INITIALISED: "UI.invite_url_initialised",
/**
* Indicates that a password is required for the call.
*/
PASSWORD_REQUIRED: "UI.password_required"
};